- close() on a getDb() handle runs as a "connection close" section and
checks its generation: a handle kept from a previous profile rejects
with DbConnectionClosedError instead of closing the current profile.
- closeUnlocked never rejects: the state is cleared and the generation
bumped before close(), a failing close of the old pool is logged with
logWarn, connectToProfile still opens the next profile and closeDb()
resolves without a connection.
- The blocking alert names a getDb() call in a callback as a possible
cause only: a long restore also waits 15 s.
- The generation check of the transaction handle is documented as a
defence, not reachable today.
- Tests: the "behind the queue" alert (pins the reset of lockHolder),
close() on a stale and on the current handle, a failing close during a
switch and in closeDb().
Refs #408
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ProfilePreparation.catch_up_backup carries the file name of the copy as
soon as it exists, so the Journal error line of a catch-up that stops
afterwards (history_write_failed, pragma_failed, apply_pending) names it.
- Error lines and the JS fallback message keep only the last segment of a
path (withoutFolders): no error line contains a slash or a backslash.
- The copy is taken again at every catch-up, written as .tmp, synced and
renamed over a same-name copy; after a successful catch-up only, the
other copies of that profile are pruned (remove_backups keep argument).
- Pins: a pre-existing foreign key violation in the profile without
category 31 (RV4, RV7) and the Journal line that reports it (RT2).
- Ignored test measuring prepare_at on 100 000 transactions at floor 6:
306 ms in release, so STARTUP_TIMEOUT_MS stays 10 s (ADR 0021).
- Guide, docs.profiles.tips, ADR 0021, architecture and changelogs.
Resolves#395
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 10 s startup delay of App.tsx sets the error page while the profile
is still opening; a connection that succeeded afterwards set dbReady but
left the error, so the error page stayed. The startup logic moves to
src/utils/startupConnection.ts (startProfileConnection), which clears the
error on success and is tested with fake timers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/__integration__/i18n-mock-guard.test.ts reads every test file of src
and fails on a useTranslation: or t: ( property (word-bounded) outside
src/test/i18nMock.ts, or on a react-i18next mock that does not go
through i18nMock. docs/architecture.md gets the convention: never a raw
t, always i18nMock.
Resolves#389
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 12 test files that already marked their output each carried a copy
of the same fake t. They now use the shared i18nMock; no assertion
changes. LicenseCard keeps its per-test language through a getter.
Refs #389
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 15 test files whose fake t returned the bare key (the 12 of the
issue, plus the three category render tests added by #423) now mock
react-i18next with i18nMock. Assertions naming a displayed text are
rewritten in the marked form, t(key[options]), so a text that skipped
t fails them. Components without such an assertion get one:
PriceFetchControl, PriceFetchConsentToggle (rendered through the hook
harness, its loading slot preset), CategoryDetailPanel, SourceList and
SourceCard, and the region label of OnboardingChecklist.
Each component was proved by a mutation removing one t call: every one
turns at least one test red (exit 1).
Refs #389
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/test/i18nMock.ts holds the one fake t of the suite: markedT renders
t(key) or t(key[a=1,b=2]), and i18nMock({ language?, fixture? }) builds
the react-i18next module for vi.mock. The local echo of
useLicenseMachines.test.ts is replaced by markedT.
Refs #389
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
useOnboardingChecklist.wiring gains three pins the old stubs could not hold:
one loader for the provider's life (refresh and the shared value keep their
identity, no re-read), 'Skip' moving the next step on the next render, and a
change of mode alone reaching the consumers. UsersSettingsPage now commits
its effects, so an effect that read the preference goes red on the
assertion rather than on 'Invalid hook call'. BudgetTable.wiring,
BudgetPage.wiring, AppShell.slot and OnboardingStepCard drop their local
slot copies; OnboardingStepCard plays StrictMode through replayEffects and
runs its effect only on a changed dependency, which now pins location.key.
Refs #409
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/test/hookHarness.ts gives the tests that call hooks and components as
plain functions one fake React with React's semantics: state initialized
once with stable setters, the reducer applied with a dispatch spy, refs kept,
memos and callbacks recomputed only on a changed dependency, effects run by
commit() only when a dependency changed (cleanups first), and every hook it
does not cover throws instead of reaching the real React. Each semantic is
proved by hookHarness.test.ts, which also checks that the module imports
React only as a type.
Refs #409
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strict lock, transaction handle, single section for a profile switch,
connection generation and blocking alert.
Resolves#408
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The global inTransaction flag let every execute/select bypass the FIFO
lock while a withTransaction callback ran, wherever it came from. The
callback now receives its own handle, which runs directly, refuses
close() and throws once the callback settled; any other call, getDb()
from a callback included, queues behind the transaction.
connectToProfile, initializeNewProfileDb and closeDb each run as one
exclusive section (close the old pool, prepare, load), and every open
or close bumps a connection generation: an operation queued for a
closed connection throws "connection closed by profile switch". An
operation waiting more than 15 s for the lock writes one Journal
warning naming the holder and since when.
findOrCreateSecurity now requires its executor: its getDb() fallback
would deadlock if called from a transaction callback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #423
- parentOptions.ts: say that this list is the only guard today (updateCategory
checks neither depth nor cycle, #393 will), instead of pointing to a
service-side validation that does not exist yet. The height walk is also
bounded by the number of categories, so a missing cycle guard gives a wrong
height instead of an endless loop; a test edits a member of a cycle.
- AddKeywordDialog.tsx: drop `categoriesLoaded`, which only repeated what the
null initial category already guarantees.
- Tests: the form and the detail panel are pinned to what they hand to
buildParentOptions (editingId, currentParentId), through a pass-through spy.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Refs #423
The review of PR #428 showed that the rule only looked at direct children:
editing a root that carries grandchildren still offered the other roots as
a parent, and saving created a fourth level, which nothing guards against
in updateCategory or moveCategory. The allowed parent depth is now
MAX_PARENT_DEPTH minus the height of the edited sub-tree (0, 1 or 2), so a
category with grandchildren gets no parent at all, only its current one.
Test written first on the reviewer's probe ([1, 10->1, 100->10, 2],
editing 1: [] expected, [2] obtained before the fix).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fixes#423
The "Parent category" field of CategoryForm and the "Category" field of
AddKeywordDialog were native selects with no typing. Both now use
CategoryCombobox, like the category cell of the Transactions page: typed
letters filter the list, which is ordered hierarchically with indentation
and shows seed categories through their i18n_key.
- CategoryCombobox accepts a minimal structural type (CategoryLike), so
CategoryTreeNode rows pass without conversion; sortHierarchical is
generic over it. Callers are unchanged.
- buildParentOptions (pure, tested) keeps roots and level-1 categories,
leaves out the edited category, and keeps only roots when the edited
category has children. That rule alone also leaves out its descent (a
descendant is never a root), so no separate subtree walk is needed. The
parent currently stored is always kept so the field never looks blank.
CategoryForm gets an editingId prop, passed by CategoryDetailPanel.
- AddKeywordDialog reads the Transactions list (active and inputable,
decision D2) through loadKeywordCategories, keeps the pre-filled
category only when it is in that list (reconcileInitialCategory), and
holds the apply button until the list is in. Its two fields sit above
the scrolling block of matches, so the open list is not clipped by the
scrollport.
- Tests: parentOptions, keywordCategory, and static renders of both
components. Changelogs and the user guide (with its i18n mirror).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Milestone planned-2026-10-10-release-deps merged (PRs #414 to #418,
main 9e8c41b): new position paragraph and decision entry, the oldest
decision (#365) rotated out. Blockers: #419 point 1 before the next tag,
stale mentions of #376, #317, #345, #358 and #320 replaced by their
closure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up of PR #417. CLAUDE.md and docs/architecture.md said
nothing re-evaluates `npm audit` in CI. No job fails on it, but the
`npm ci` step of check-frontend audits on every run and prints the count
(run 520: found 0 vulnerabilities; runs 512 and 519: 8), and the
Defenseur reports advisories on its side, which is where #358 came from.
Refs #358
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups of PR #414.
- audit.yml points to the concurrency comment of check-rust.yml, whose
measurement covers push and pull_request runs only: say this workflow
runs on schedule and workflow_dispatch, which it does not cover.
- reportService.test.ts said CI runs Node 20, so no node:sqlite; CI runs
Node 24 since #376, and the harness itself is #392's.
- The release skill said /actions/tasks ignores `limit`. Measured
2026-10-12: only without `page` (613 rows); with `page` it pages by 30
or by `limit`.
Refs #376
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups of PR #415. The container does have python3 (pulled by
the apt packages of the install step, run 517): awk is kept because mawk
is part of the ubuntu:22.04 base image, which the workflow comment and
the ADR now say. #344 is a PR and the second drift, not the third. The
awk exit-code test is described by what it alone catches (an awk that
fails after printing every fact). The repair procedure gains the two
failures it had no answer for, and the ADR names the canary's limit.
The release commit edits Cargo.lock on main without a PR, so /release
now replays the guard's exact step text right after that edit (step 2),
checked on the stack's lock (exit 0) and on a drifted edge (exit 1).
Refs #345
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Max's decision on PR #416, after its review measured who still read the
key. tauri-plugin-updater looks up "{os}-{arch}-{installer}" first, and
every version that ships the updater (v0.2.1 and later, plugin 2.10.0,
tauri-utils 2.8.2 in each tag's Cargo.lock) carries its bundle-type
marker, so .deb and .rpm installs never reach the generic key. Its last
readers were the AppImage installs v0.4.3 to v0.4.5 (v0.4.2 targets a
route that answers 404, earlier versions read GitHub) and binaries run
outside a package; none of them can install the .deb it served, and the
updater wrote that .deb over the running file. They now get
TargetsNotFound when checking: an error on the Updates card, nothing
installed.
The key leaves the script, scripts/check-latest-json.jq and
verify-release.sh together; the test compares the three lists. Docs fix
the readers named by #416 (v0.4.2 is not one) and the CHANGELOG entry
says what changes for those AppImage installs.
Refs #320
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups of PR #416. The check that release.yml runs before
deleting the published manifest moves from an inline jq program to
scripts/check-latest-json.jq, which release.yml applies with
`jq -e --arg version ... -f`. scripts/build-latest-json.test.mjs now runs
that same filter on every pull request: on the script's output, on a
wrong version, on nine broken manifests and on the shape published up to
v0.17.1. It also compares the key lists of the script, of the filter and
of verify-release.sh, so a list left behind shows before the tag.
Two new cases pin the version argument: 0.18.0 must come out as 0.18.0
(the other fixtures pass 0.17.1, the version the bundle names carry, so a
script that ignored its argument passed them all), and an empty version
must fail. Both mutations survived the review's pass.
No change to what the manifest holds: still four keys.
Refs #320
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
npm audit reported 8 vulnerabilities (5 high, 3 moderate) on the lock
of main: react-router and react-router-dom (one advisory,
GHSA-qwww-vcr4-c8h2), vitest and @vitest/mocker (one advisory,
GHSA-82fw-gwwq-j7x9), browserslist, baseline-browser-mapping, nanoid
and source-map-js. Every fix sits within the range package.json
already declares, so only the lock moves; package.json is untouched.
Lock generated with npm 11.21.0 exactly:
npx --yes npm@11.21.0 update react-router-dom react-router vitest \
@vitest/mocker browserslist baseline-browser-mapping nanoid \
source-map-js --package-lock-only --ignore-scripts
259 entries before and after, 20 changed, each resolved from
registry.npmjs.org with a new sha512 integrity. npm ci, under npm 10
and under npm 11.19.0, leaves the lock as is; npm audit: 0.
Only react-router 7.18.1 -> 7.18.4 changes the built app: a build from
a lock where only react-router moved equals the full build byte for
byte. Since 7.18.3, useNavigate() and <Navigate> reject a navigation
to another origin; the app only navigates to internal paths.
CLAUDE.md and docs/architecture.md no longer describe 2 high
advisories accepted for good: npm audit is at 0 on 2026-10-10, a
dated reading that nothing re-runs in CI.
Resolves#358Resolves#317
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fallback key is also read by the AppImage installs of v0.4.2 to
v0.4.5 (bundled until v0.7.3, published on Forgejo for v0.4.2-v0.4.3
and on the GitHub mirror for v0.4.3-v0.4.5, all reading the registry),
which look up linux-x86_64-appimage, absent, then linux-x86_64. Like a
marker-less binary, they cannot install the .deb it serves: the updater
writes its bytes over the running AppImage. State since v0.4.2, not
changed by this PR; the key stays as decided.
Also quote the error the update card really shows for a refused
package (invalid updater binary format).
Refs #320
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
latest.json only had linux-x86_64, built from the .deb. An .rpm install
looks up linux-x86_64-rpm first, then fell back on linux-x86_64,
downloaded the .deb and refused it (InvalidUpdaterFormat).
- scripts/build-latest-json.sh writes linux-x86_64-deb,
linux-x86_64-rpm, linux-x86_64 (the .deb, as a fallback) and
windows-x86_64 from the release assets. It fails unless it finds
exactly one .deb, one .rpm and one -setup.exe, each with a non-empty
.sig. The JSON comes from jq alone, notes read with --rawfile, file
names encoded with @uri like the upload step.
- scripts/build-latest-json.test.mjs (vitest) runs it under
bash -e -o pipefail on a temporary assets dir; check-frontend
installs jq.
- release.yml calls the script, writes the notes output under a random
delimiter, checks the manifest with jq before the registry DELETE,
and names the .rpm instead of an AppImage in the release notes.
- verify-release.sh requires the four keys, the extension of each URL
and the minisign file: of each signature, and exits 1 on any failure.
- Docs: release skill, qa-update-cycle section 3 (rpm procedure, keys,
reason and removal condition of the fallback), architecture, README,
CHANGELOG, STATE.
Resolves#320
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Ce sous-arbre" lost its referent once the trigger paragraph stopped
naming a subtree: the sentence now says what is measured, that both past
drifts (#310, #344) came with RustSec advisory fixes. Rule 1's sentence on
a missing block is reworded.
Refs #345
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven crates in Cargo.lock accept windows-sys through a wide bound
(colored, dirs-sys, errno, rustix, rustls-platform-verifier, tempfile,
winapi-util), so cargo may point them at any windows-sys version already in
the graph. All seven point at 0.61.2, the state every Windows release has
shipped with since v0.14.0. Some updates re-resolve them (cargo update -p
hyper, -p h2, -p rustls-webpki -p tar did) down to 0.48/0.52/0.59 while
cargo only reports the packages it bumped: the Windows build then compiles
five windows-sys versions instead of three, and nothing says so.
A new blocking step, "Verify windows-sys pinning", reads the lockfile with
awk right after the suppression guard and fails if any edge of the seven
crates leaves 0.61.2 (a bare edge only passes when the lockfile holds a
single windows-sys, at 0.61.2), if a guarded crate has no block, or if the
set of windows-sys versions changes. awk's exit code is tested apart from
its output, every check is logged with a summary line, and a canary
(tempfile) must be read and checked by the loop.
The step text was extracted from the YAML and run as is, with the runner's
bash options, on copies of the lockfile: main's and the five release tags'
locks pass (9 checks, exit 0); the hyper-bumped lock, an added
windows-sys 0.53.0, a renamed guarded crate, a missing lockfile and a
guarded list without the canary all exit 1.
ADR 0023 records why 0.61.2, the measured cost (3 versions compiled for
Windows instead of 5), the triggers, the exit condition (windows-sys 0.62,
or convergence on a single version) and the repair when the step fails.
CLAUDE.md and docs/architecture.md cite the guard next to the suppression
check. Cargo.lock and .cargo/audit.toml are untouched.
Resolves#345
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The /actions/tasks endpoint ignores `limit` and returns the whole list,
newest first, so `?limit=5` promised five rows it does not give. The
skill now selects the release.yml run of the tag directly (python3, as
verify-release.sh does), checked on v0.17.1: run 508, `v20.20.2`.
Also clarify check-frontend.yml's concurrency comment: same setup as
check-rust.yml, not the same group.
Refs #376
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node 20 reached end of life on 2026-04-30. check-frontend.yml, audit.yml
and release.yml now install Node.js 24 through NodeSource's setup_24.x,
as check-rust.yml has done since #375. Each step still prints
`node --version` in the job log.
The concurrency comment of check-rust.yml claimed the key cancels
obsolete runs on force-push. la-compagnie-maximus docs/forgejo-ops.md
measured the opposite on Forgejo 8.0.3: no pull_request run was
cancelled by a later one, and push runs are cancelled without the key.
The comment now states that measurement; the keys stay (harmless,
meaningful where honoured), and check-frontend.yml and audit.yml point
to it.
release.yml names the trust it places in NodeSource: the script runs as
root in the job container, whose Node.js then runs `npx tauri build`
with TAURI_SIGNING_PRIVATE_KEY. Kept on purpose, also documented in
docs/architecture.md, whose release.yml section described two jobs
(windows-latest, ubuntu-22.04) where there is one containerised job
cross-compiling Windows with cargo-xwin.
release.yml only runs on a tag, so Node 24 there is proven at the next
release: the /release skill now reads `node --version` from the release
run's log (step 9), and STATE.md lists it under the checks due at the
next release.
No CHANGELOG entry: CI only.
Resolves#376
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Guide (FR, EN, in-app keys and `.md`) and CHANGELOG: the Import card
never says "Done." for an import that added no transaction, and the
step waits for a file that brought one; a Fixed entry, since v0.17.0
already ticked the step after such an import.
- CHANGELOG: the Free edition shows the Budget step locked, it does not
lack one.
- architecture.md: the import counter's query, and D9's flag read
outside any transaction, its log line written after the COMMIT.
- ADR 0022: `applyMigration` and `deleteTransaction` named among the
writes that do not notify, with their reason.
- QA list (44 cases): the noop import at rest, the focus ring on the
highlighted control, "Skip" on the folder step without a folder, the
Transactions and Budget cards on a profile without transactions, the
focus after "Hide" and "Close", the doubled counter of the unfolded
list, and a month-agnostic note on the off-screen cell.
Reviews of #402 to #407.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a profile without transactions whose import steps were skipped, the
rows can land before the transaction count, both reads queueing behind
the DB lock: the grid mounted, reported "Expand all", then gave way to
the empty state, and the card kept "Click Expand all…" above it with no
grid on the page. The page now passes no target while it shows the
empty state, and a wiring test replays that sequence. Invisible in dev,
where StrictMode reorders the two reads.
The month test sets the clock to 23:00 on December 31, which is still
December in UTC: under the CI runner's zone, a month read in UTC passed.
The zone is pinned west of Greenwich, as in useImportWizard.test.ts.
Review of #400 (PR #406).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reducer tests sent SET_PAGE_RESULT right after the initial state,
while the hook sends SET_LOADING then SET_ERROR(null) before each read,
and the mount effect's categories, sources and count before or after
the first page. Had any of those three actions disarmed the guided
fallback, the fallback would never run in the app, and the tests stayed
green: a test now reduces both real orders.
The page tests now also let the list's filters move, as a later
dispatch would: after the fallback the card names every transaction,
and an ordinary visit with "Uncategorized" picked by hand names the
uncategorized ones. The sentence follows the list, not the navigation
state.
Review of #399 (PR #405).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ring of `[data-onboarding-highlight="true"]` is an author outline:
it wins over the browser's focus ring, so a keyboard user who reached
the highlighted control saw nothing change (WCAG 2.4.7, failure F78).
Measured by the review in headless Chromium, WebView2's engine: the
highlighted button kept `solid 3px` focused or not. `outline: revert`
under `:focus-visible` gives the browser's ring back, kept as is in the
built stylesheet; the contrast test reads the rule from the stylesheet.
Review of #398 (PR #404).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADR 0022 wants a transactional write to notify once `withTransaction`
has returned, never from its callback. Only the categorization test
checked the lock; the restore tests checked that the notification
followed the COMMIT, which a notification sent from inside the callback
also satisfies. Each restore mode now asserts the lock was released.
Review of #396 (PR #402).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`initializeGuidedPath` opened BEGIN, read the flag, then COMMIT at every
mount of the provider: start-up, and every profile switch, creation or
deletion. While a `withTransaction` callback runs, `db.ts` lets every
other query bypass its lock, the overlap its serialization exists to
prevent.
The flag is now read first, outside any transaction, through the queued
connection: once D9 ran, an opening costs that one read. Without it, the
transaction re-reads the flag before writing, so two first loads still
cannot apply the rule twice (CWE-362).
Pinned: no transaction and no write with the flag set; the flag counts
only for "1", on both reads; a first load that sets the flag between
the two reads wins; the in-transaction reads still follow BEGIN.
Review of #396 (PR #402).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "Import a first file" step counted `imported_files` rows in status
`completed`. `executeImport` writes that row before its first insert, so
an import that added nothing (a file holding only its header, or one
whose rows were all in error in the preview) ticked the step: the Import
card then said "Done." at rest and sent the user to /transactions, which
had nothing to categorize and led back to the import.
The counter now keeps a completed file only when a transaction points at
it (`EXISTS` on `transactions.file_id`, covered by
`idx_transactions_file`). Replayed with sqlite3 on
`consolidated_schema.sql`: 0 after an import without a valid row, 0
before a successful import's first insert and 1 after, 0 once the file
turns to `error`. Duplicates only change nothing: their transactions
came with an earlier completed file, and a restore writes its own.
The tests run without SQLite, so the rule is pinned on the query, and
the Import page is pinned at rest after such an import: step 4 again,
on the first source card, without "Done.".
Review of #398 (PR #404), option A chosen by Max.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guide and the changelog said the card never says "Done." for an
import that added no transaction. It holds it back on the report only:
on the Import page the card moves from one step to the next, and says
"Done." once the file is imported. The locale test no longer depends on
the order of the sections in the files, which DocsContent ignores.
Resolves#401
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- architecture.md: the Onboarding section describes the guided path as
delivered (provider and useOnboarding(), the data-change channel of
ADR 0022, the three onboarding preferences, rule D9, the pure models of
onboardingPath.ts, the banner, the card latch and CARD_PAGES, the step
cards, the highlight by props and CSS); the utils count and the
user_preferences row follow the stack.
- qa-onboarding.html: the phase 1 cases that still hold, without the
compact bar ones, and four guided-path groups, including the manual
checks the five links left for a release build (WebKitGTK and WebView2,
Orca and NVDA, reduced motion, keyboard, Free edition, rule D9).
- CHANGELOG (EN and FR): one entry for what every step card does, one per
page, the path hidden on active profiles covers the banner too, the
skipped state, and the guide's new sub-section.
- STATE.md: manual checks due on the next release.
Resolves#401
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DocsContent and DocsSubsections read the guide straight from fr.json and
en.json, but their tests run on fixtures: a string where an array is
expected, a sub-section without its steps or a French list longer than
its English one only showed in the app. DocsContent.locales.test.ts reads
the real files: every section DocsContent renders and no other, the
strings and string arrays it reads, sub-sections that readSubsections
keeps whole, anchors of their own, French and English item for item, and
the path's sub-section in the section "Open the guide" opens.
DocsContent exports SECTIONS for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "Getting Started" section, which "Open the guide" opens at the end of
the path, gains a "The getting-started path" sub-section in both locales
and in docs/guide-utilisateur.md: the banner and its list, the step cards
and their outline, "Skip this step" and "Resume", the end of the path,
the profiles that find it hidden from the start, and how to show it again
from Settings -> Users. It uses the sub-section shape DocsSubsections
already reads for the Import section.
The Dashboard and Settings sections no longer describe the compact bar
the banner replaced, and the Profiles section says the path belongs to
each profile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Added: the getting-started card of the Budget page. architecture.md
stays as it is: no line of it is made stale (the budget folder still
holds five components, the pure module is not one).
Resolves#400
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The budget grid opens collapsed and only its leaf rows take an amount,
so the card first points at "Expand all", then at the current month's
cell of the first editable row shown (D12). `budgetOnboardingTarget`
(pure, beside budgetTableResults.ts) decides it from the rows each
section shows, in the order it shows them; the collapse state stays
BudgetTable's own and the card never unfolds the grid.
BudgetTable rings that one control when the page asks
(`onboardingTarget`) and reports its kind through
`onOnboardingTargetChange`, in an effect, at mount and on change only:
the page needs it for the card's sentence before it asks for the ring.
`budgetCardModel(input, checklist)` gives the "collapsed" sentence for
"Expand all", the "flat" one for a cell, no sentence and no ring before
the grid says, and "Done." once the budget step is checked, which the
card needs to end the path. CARD_PAGES gets /budget. BudgetPage renders
the card first, outside the loading dim the grid gets after each save,
and asks for the ring only while the card shows. The Free edition never
mounts the page and never latches it: its path ends at 5/5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Added: the getting-started card of the Transactions page and the guided
entry its step link opens. architecture.md gets the lines this change
touches: useTransactions' start filters and single fallback, and the
i18next `lowercase` formatter in the Internationalisation section.
Resolves#399
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>