Commit graph

117 commits

Author SHA1 Message Date
le king fu
dad81fa53f docs: fix the stale header of PriceFetchControl.test.tsx (#437)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m2s
Self-review of PR #473: the header still said every React rendering of the
file was bypassed, while its "displayed texts" tests render the control with
its hooks from the shared harness. architecture.md: the slot presets are
commented in each test file (the two direct writes of the onboarding wiring
test are covered by the comment of mountWith).

Resolves #437

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:04:15 -04:00
le king fu
ae54406354 docs: name the slot-order dependency and the known test limits (#437)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m7s
- Where a test presets a harness slot by position (PriceFetchConsentToggle,
  useOnboardingChecklist's mountWith, SystemsSettingsPage), a comment names
  the dependency on the component's hook order: any hook added before shifts
  the slots without a word (the `slot` helper of fakeReact).
- docs/architecture.md, Tests: "Limites connues" lists the slot presets, and
  links to ADR 0021 for withoutFolders and for the catch-up measurement
  instead of repeating them.
- ADR 0021 section 7: the relative-path limit is stated precisely (a run with
  two separators or more is still cut to its last segment; only the words of
  a folder holding a blank stay), and a second limit is written: a folder
  name holding one of the stops (a backtick, `;`, `|`, a closing parenthesis
  not followed by a separator...) ends the path there, and that name, or its
  start, stays in the line.
- db.ts: the comment of withoutFolders says "a closing parenthesis not
  followed by a separator", as the code does, and points to ADR 0021.

No behaviour change.

Resolves #437

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:59:54 -04:00
le king fu
bad53fe85b docs: name the budget as a reader of the hierarchy module (#393)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m6s
The architecture section said the module had three consumers; the walks
do, and the budget reads displaysAsRoot as well.

Resolves #393

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:28:24 -04:00
le king fu
03e32afc02 docs: describe the hierarchy check and the parent rules (#393)
CHANGELOG (Added: the "Check hierarchy" window; Fixed: the parent checks
at save time and the budget rows of a category whose parent is deleted or
gone), the Categories section of the user guide and its docs.* keys in
both languages, and docs/architecture.md: the categoryHierarchy module, the
diagnostic, the validation and its typed error, the window, the budget
root rule, and what stays out of scope (drag-and-drop, archive restore).

Resolves #393

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:23:14 -04:00
le king fu
14ebf0f37d docs: name the portal windows as examples, not as tested (#429)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m58s
Self-review: the Portails paragraph listed StarterAccountsModal next to
LinkTransfersModal as if both had a test of this kind; only
LinkTransfersModal has one. The other windows are named as windows the
same rule applies to.

Resolves #429

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 18:49:20 -04:00
le king fu
874563540c docs: state the exact flip rule and the portal errors (#429)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m58s
Self-review: the CHANGELOG entries said the list opens upward when the
room below is short; it does only when it also fits above (in a short
window it stays below). The Portails paragraph of architecture.md now
quotes the two errors a test meets without the mock, both reproduced
on a throwaway test: "Target container is not a DOM element" without
a DOM, "Portals are not currently supported by the server renderer"
with a DOM-like container.

Resolves #429

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 18:48:45 -04:00
le king fu
7053ae5fc6 docs: describe the category combobox in a portal and its new places (#429)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m55s
CHANGELOG (EN and FR, Changed): typing in the category of an adjustment
line and in the category filter of the "Link transfers" window, the
adjustment lines limited to the categories open to input, and the open
list no longer cut by the edge of a window or of the Transactions table.

Guide, section 7, and its in-app twin (docs.adjustments.steps, FR and
EN): type a few letters to filter the category list of a line.

architecture.md, Tests section: how a test renders a window drawn by
createPortal (identity mock, document stub), what of the combobox's
portal is pinned without a DOM (placeListbox.ts) and what is checked in
the app, and that the list no longer sits in its container's DOM.

Resolves #429

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 18:41:37 -04:00
le king fu
5decb985f1 fix: list the System sections on the Settings home in the page's order (#372)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m56s
Self-review of PR #470: the System card of the Settings home lists the
page's sections as chips, still with the version history before the logs.
The chips now follow the page (version, update, logs, history), and a test
renders the page and compares its section headings with the card, so the
two cannot drift apart again. SECTIONS is exported for that test, as
DocsContent exports its own. The /settings/systems row of
docs/architecture.md names both unreleased headings and the card.

Resolves #372

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 18:12:35 -04:00
le king fu
0513d717c7 docs: describe the Systems page order and the folded version history (#372)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m2s
User guide (section 11) and the in-app docs.settings keys, FR and EN: the
order of Settings → System and how the version history folds. One Changed
entry in both changelogs. docs/architecture.md: the /settings/systems row,
why ChangelogContent keeps a local Set rather than useCollapsibleGroups,
and the utils count (19: startupConnection and rootFiles were not counted).
CLAUDE.md no longer describes /changelog as a page with the full history:
it redirects to Settings → System since #190.

Resolves #372

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 18:07:41 -04:00
le king fu
db625d02ef docs: record the proven --locked relay of both release runners (#449)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m57s
PR Check — Rust / rust (pull_request) Successful in 9m58s
- Release skill, step 2: quote the refusal cargo actually prints, the
  same under cargo 1.94.1 and 1.99.1 (`cannot update the lock file ...
  because --locked was passed to prevent this`, code 101).
- docs/architecture.md: the relay of --locked is proven for both runners
  on a lockfile out of step with Cargo.toml: cargo-xwin 0.23.1 runs
  `cargo build ... --locked ...`, and the other cargo calls of the Tauri
  CLI and cargo-xwin are `cargo metadata --no-deps`, which never touch
  the lockfile.

Resolves #449

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:39:11 -04:00
le king fu
14badbe2b0 docs: describe the --locked builds and the lockfile check of /release (#449)
- Release skill, step 2: right after the hand edit of Cargo.lock and
  before the windows-sys guard, `cargo metadata --format-version 1
  --locked` must exit 0, since the tag builds with -- --locked and the
  release commit goes to main without a pull request. Step 0 runs cargo
  check and cargo test with --locked, like CI, so the pre-flight cannot
  fold a lockfile rewrite into the release commit.
- ADR 0023: the windows-sys guard reads the committed lockfile, cargo
  check having already refused one it would have had to rewrite.
- CLAUDE.md, REVIEW.md and docs/architecture.md: CI commands with
  --locked, release builds on the committed lockfile, cargo-xwin pinned
  (REVIEW.md said it was installed unpinned), the stale REVIEW.md
  sentence about #449 removed, and why the dormant .github mirror is
  not aligned.

Resolves #449

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:28:39 -04:00
le king fu
57ac056bf2 fix(dialogs): wait for the confirmation before deleting or removing the key
On desktop, tauri-plugin-dialog 2.6.0 replaces window.confirm with an async
function: confirm(...) returned a Promise, always truthy, so every guard
`if (!confirm(...)) return;` let the action run before the user answered.
Deleting a secondary profile removed its database even on "Cancel".

- confirmAction() (src/services/confirmService.ts) calls the plugin's ask()
  with a warning icon and buttons labelled in the app language
  (common.confirm / common.cancel); a failed call resolves false and is
  logged (fail closed). @tauri-apps/plugin-dialog 2.6.0 added, the crate's
  version.
- The six sites await it: profile, category, adjustment, budget template and
  Balance category deletions, and both confirmations of the license key
  removal (performRemoveLicense takes an async confirm).
- native-dialog-guard.test.ts fails on any native confirm/alert in src/ and
  on any confirmation not awaited (TypeScript does not flag `!promise`).
- Tests: confirmService, the deletion of a profile in ProfileFormModal, and
  the license removal tests with an async confirm (test: reproduces #421:
  six of them were red on the old code).

Refs #421

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:29:17 -04:00
le king fu
c859405deb fix(release): refuse an empty or multi-document latest.json, test the Publish step
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m4s
Follow-ups of the review of PR #418.

- check-latest-json.jq wants exactly one document and release.yml calls it
  with -s: under jq 1.6, the release container's, an empty file passed the
  check (no output, status 0) and two documents were judged on the last one,
  so the job would have deleted the published manifest and uploaded the bad
  one. Two tests cover both cases.
- build-latest-json.test.mjs runs the text of release.yml's "Publish
  latest.json to package registry" step against a fake curl: DELETE then PUT
  on a valid manifest, exit 1 and no request on a refused one.
- check-frontend no longer ignores .claude/** and release.yml (decision of
  Max): the test reads verify-release.sh and runs the Publish step, so "on
  every pull request" holds. REVIEW.md, CLAUDE.md and architecture.md follow.
- release.yml: stale ".deb fallback" comment fixed.
- qa-update-cycle.md: a tauri dev build on Linux shows TargetsNotFound,
  expected since #418.

Refs #461

Resolves #419

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 13:00:40 -04:00
le king fu
c5efc38e96 fix(deps): build the updater without its zip feature
All checks were successful
PR Check — Rust / rust (pull_request) Successful in 4m14s
Clears RUSTSEC-2026-0367, RUSTSEC-2026-0368 and RUSTSEC-2026-0369 on
zip 4.6.1, which turned the daily audit.yml red at run 558 (2026-10-17)
without any lockfile change. zip was compiled only for Windows, through
the default `zip` feature of tauri-plugin-updater 2.10.0. It is fixed
only in zip >= 9.0.0, and tauri-plugin-updater 2.14.0 still requires
zip ^4, so no cargo update can reach a fix.

That feature only unpacks archived update packages (.nsis.zip,
.msi.zip, .AppImage.tar.gz, produced by createUpdaterArtifacts
"v1Compatible"); this app ships createUpdaterArtifacts: true, whose
packages are never archives. The updater is now declared without its
default features, keeping rustls-tls. The lockfile loses zip, arbitrary
and derive_arbitrary (666 -> 663 packages, nothing added), and tar
leaves the Linux graph.

tar was the canary of the suppression guard in check-rust.yml. It is
replaced by webkit2gtk, Tauri's Linux webview, which keeps the property
that made tar useful: a dependency present on x86_64-unknown-linux-gnu
only, so finding it also proves that --target resolves conditional
edges. ADR 0018, architecture.md, qa-update-cycle.md and the release
skill are updated to match.

Verified locally: cargo-audit 0.22.2 exits 1 on the old lock with the
three IDs and 0 on the new one (663 crates, the same 12 allowed
warnings); the suppression guard is green, and red with the canary
swapped for tar or zip; windows-sys guard 9/9; cargo check and cargo
test (267 passed) on Linux; cargo xwin check for x86_64-pc-windows-msvc
is green on main (zip compiled) and on this branch (zip gone).

Refs #455

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:37:48 -04:00
le king fu
cb7f8479e4 feat(gating): reserve transaction splits for the Base edition
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m16s
The Répartition (split) button of the Transactions page now belongs to
the `adjustments` feature, as the unlock screen already promised
(upsell.features.adjustments). On the Free edition the button keeps its
place and its icon, carries a lock and opens the locked screen in a
dialog instead of the split window, for every row, split or not. While
the license is not ready it shows no lock and does nothing. Existing
splits stay in the data, counted by the reports and flagged by the
orange icon.

- splitGate.ts: pure splitAccessFor(gate) -> open | locked | pending,
  stricter than profileGate: `allowed` does not short-circuit `ready`.
- SplitButton: stateless button that decides what a click opens.
- UpsellDialog: UpsellGate in a named modal dialog, closed by its X and
  by "I already have a key".
- TransactionTable: required splitAccess prop. The split window is not
  rendered once the edition resolves to Free, and stays mounted through
  a license refresh; the upsell is not rendered once it resolves to Base.
- TransactionsPage reads useEntitlement("adjustments").
- ADR 0017 amendment and matrix, user guide, in-app docs (FR, EN),
  architecture, entitlements header comment, both changelogs.

Tests: the predicate, the button's markup and click, the dialog, the
table's wiring through the shared hook harness (#409), the page under
the three gate states, and the new key in both locales.

Fixes #369

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:02:47 -04:00
le king fu
934bcbf9aa docs(onboarding): cards of the Categories and Reports pages
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m57s
Guide and in-app docs name the five card pages, "Got it" on the two
visit cards and the Trends card that also checks the reports step;
architecture lists the new models and targets; the QA list gets the
steps 5 and 8 of the guided walk, the Free padlock variant and the
Trends click.

Resolves #432

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:48:47 -04:00
le king fu
a064043c20 docs(onboarding): eight steps, visit steps and their one-time initialization
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m58s
- Guide and docs.gettingStarted / docs.dashboard keys (FR, EN): eight
  steps, the two visit steps in the quick-start list, "Got it", 3/8 and
  7/7, a finished path finds them done.
- ADR 0022: acknowledgeStep and initializeVisitSteps do not notify (§2),
  ACKNOWLEDGE among the local actions (§4), the one-time initialization
  admitted like rule D9 (§5).
- architecture.md: eight rows, the amended "never by a click" invariant,
  the two new preferences, the provider's new action and initializer.
- QA list: step numbers moved to eight, five cases for the visit steps.
- CHANGELOG: Added entry in both files.

Resolves #425

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:37:30 -04:00
le king fu
fade01d955 docs(help): fix stale help and guide texts of Budget, Reports and Transactions
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m58s
The page help and the guide (Markdown and in-app) described a Budget split
button removed in v0.2.5, an Annual-column warning that can no longer
appear, report tabs and four report cards (the hub has five, no tabs), a
"Category Zoom" whose displayed title is "Category Analysis", and a "+"
icon on transactions (the keyword button is a tag icon, shown on
categorized rows only; the right-click menu works on every row).

Resolves #431

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:18:03 -04:00
le king fu
6b913709af docs(onboarding): changelog and QA cases of the Transactions and Budget cards
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m58s
The two older QA cases that quoted the previous card sentences now quote
the new ones.

Resolves #430

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:14:23 -04:00
le king fu
63d8ba7589 feat(onboarding): Transactions card suggests Add as keyword, Budget card names leaves and the Annual column
Both Transactions sentences name the context menu's "Add as keyword" and
carry the advice on the word and the boxes (D18); both Budget sentences say
only last-level rows take an amount and what the Annual column does. The
labels go through StepCardText.labels, quoted in the locale files (D11).
The keyword dialog is unchanged; a test pins that it marks the transactions
as categorized by hand, so the step still checks off through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:12:25 -04:00
le king fu
e177a7561f docs(import): changelog, guide, architecture and QA cases of the read-only source name
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m56s
Resolves #426

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:08:07 -04:00
le king fu
2de4d9d255 docs(import): changelog, guide, architecture and QA cases of the root files warning
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m56s
PR Check — Rust / rust (pull_request) Successful in 3m41s
Resolves #427

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:02:22 -04:00
le king fu
af884ca13b feat(import): show the expected folder structure, one subfolder per account, signs at the preview
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m57s
The Import empty state now shows an "Expected structure" box in both of its
variants (no folder, folder without sources): the parent folder to choose,
one subfolder per account, and files at its root ignored, as
scan_import_folder reads them. New ImportFolderStructure component, a nested
list with decorative icons.

The getting-started cards ask for the parent folder and one subfolder per
account instead of one per bank. The preview card says that an expense must
count among the outflows and names "Flip the signs" next to "Check
duplicates": WIZARD_ROWS rows now carry every label their sentence names.
Every Import card sentence quotes the controls it names, in French and in
English, pinned by a test that reads the locale files.

Help tips, docs.import.steps, the user guide, the QA list and architecture.md
follow; CHANGELOG entry under Changed.

Resolves #422

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:53:05 -04:00
le king fu
977ef3a1d3 docs: ADR 0024, MCP server (proposed)
Written before the code, from spec-decisions-mcp-server.md and the
reviewed spec-plan-mcp-server.md (local, git-ignored). Status Proposed;
to pass Accepted when the spec-mcp-server milestone merges. Indexed in
docs/architecture.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-13 20:29:06 -04:00
le king fu
86eff9b7a3 fix(profiles): follow-ups of the review of PR #436
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m8s
PR Check — Rust / rust (pull_request) Successful in 3m54s
- withoutFolders no longer keeps the first word of a folder that holds
  a blank (`C:\Users\Jean Dupont\...\profile_x.db` gave
  `Jean profile_x.db`): a path starts at a recognised prefix (drive,
  `/`, `~/`, `./`, UNC, `<profile folder>`) and runs to its last
  separator, blanks included, until a line break, a double quote, a
  backtick, `;`, `|`, a `,` or `:` before a blank, a closing parenthesis
  not followed by a separator, or a blank before another path. When
  nothing ends it, the text up to the last separator of the line is
  dropped rather than a folder let through. A quote before the path is
  not part of it, so `'C:\Users\Bob\x.db'` gives `'x.db'`. Relative runs
  with no recognised prefix keep the previous fallback. No error line
  contains a `/` nor a `\`, as before.
- ADR 0021 section 7 describes that rule and its limit.
- CHANGELOG.md, CHANGELOG.fr.md: no blank line between the two Fixed /
  Corrigé bullets.

Refs #395

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:57:29 -04:00
le king fu
ac3a981f64 test(i18n): follow-ups of the review of PR #435
- renderCategoryLabel.test.ts passes markedT itself, its assertions in
  the marked form (`t(balance.category.cash[defaultValue=cash])`): the
  second fake `t` of the repository is gone.
- i18n-mock-guard: a fake `t` is also caught as a binding (`t = (`,
  `t = vi.fn(`) or as a `t: vi.fn(` property, and a file that mocks
  react-i18next must call `i18nMock(`, comments dropped before the
  search. Each new rule has its own case in the guard's tests; the
  binding rule found renderCategoryLabel.test.ts before its migration.
- docs/architecture.md: the "only fake t" sentence is scoped to test
  files and says the guard only refuses the forms it recognises.

Refs #389

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:53:18 -04:00
le king fu
a1c31a1da7 test(harness): follow-ups of the review of PR #434
- useOnboardingChecklist.wiring.test.ts: a data event, then a route
  change 100 ms later, still gets its deferred re-read (two LOAD_START).
  Pins the follower's deps: with `pathname` among them, the cleanup drops
  the pending timer and the test goes red (mutation M1 of the review). The
  comment that claimed "one subscription only" now points to this test.
- hookHarness.test.ts: a dependency array that shrinks, or grows by
  `undefined`, recomputes the memo (mutation M8, length branch of
  depsChanged).
- NOT SIMULATED, in hookHarness.ts and docs/architecture.md: the dispatch
  of a useReducer keeps the reducer of the first render, where React
  applies the latest one.

Refs #409

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:51:20 -04:00
le king fu
74fae140b1 fix(db): follow-ups of the review of PR #433
- 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>
2026-10-13 19:50:12 -04:00
le king fu
419b20e769 feat(profiles): name the copy of a stopped catch-up and keep one copy per profile
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m1s
PR Check — Rust / rust (pull_request) Successful in 3m56s
- 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>
2026-10-12 20:18:18 -04:00
le king fu
2bd347b4a0 test(i18n): guard the test sources against a local fake t
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m5s
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>
2026-10-12 19:58:30 -04:00
le king fu
79ba1ed2f3 docs(architecture): add the tests section and the hook harness
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m57s
Resolves #409

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:44:43 -04:00
le king fu
b161d54e3a docs(architecture): add the DB access and transactions section
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m55s
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>
2026-10-12 19:32:05 -04:00
le king fu
4bdb00402e feat(categories): type-ahead combobox for the parent and keyword category fields
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m57s
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>
2026-10-12 17:22:31 -04:00
le king fu
9e8c41b1f3 docs: say what still reports npm advisories after review of #417
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m53s
PR Check — Rust / rust (pull_request) Successful in 3m48s
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>
2026-10-12 15:05:44 -04:00
le king fu
08d54fd176 docs(adr): correct ADR 0023 and the guard's comment after review of #415
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>
2026-10-11 19:32:51 -04:00
le king fu
230ecd85ad fix(release): drop the generic linux-x86_64 key from latest.json
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>
2026-10-11 19:30:34 -04:00
le king fu
ba3db7c35d ci(release): check latest.json with one jq filter that the tests also run
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>
2026-10-11 19:28:48 -04:00
le king fu
e8306aa260 fix(deps): refresh npm lock to clear 8 advisories
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m53s
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 #358
Resolves #317

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:14:34 -04:00
le king fu
5ce136f45e docs: name the readers of the linux-x86_64 fallback after self-review
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m57s
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>
2026-10-10 20:55:02 -04:00
le king fu
da7129d529 fix: give rpm installs their own key in latest.json
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m54s
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>
2026-10-10 20:49:12 -04:00
le king fu
3b8105c253 docs: fix two sentences of ADR 0023 after self-review
All checks were successful
PR Check — Rust / rust (pull_request) Successful in 3m36s
"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>
2026-10-10 20:17:09 -04:00
le king fu
8d9a3ca9e9 ci: guard the windows-sys 0.61.2 pin in check-rust
All checks were successful
PR Check — Rust / rust (pull_request) Successful in 5m12s
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>
2026-10-10 20:14:25 -04:00
le king fu
71a4e1e88f ci: move check-frontend, audit and release from Node 20 to Node 24
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m51s
PR Check — Rust / rust (pull_request) Successful in 4m46s
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>
2026-10-10 19:40:28 -04:00
le king fu
ee17f31352 docs(onboarding): the import counter, D9 and the QA cases from the stack's review
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m1s
- 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>
2026-10-08 21:05:30 -04:00
le king fu
5c18fddfc0 docs: say where the Import card holds back "Done." (self-review)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m6s
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>
2026-10-08 09:59:55 -04:00
le king fu
d6f42dbe32 docs: architecture, QA checklist and changelog for the guided path
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m59s
- 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>
2026-10-08 09:53:58 -04:00
le king fu
b0db1d3102 docs(guide): describe the guided getting-started path in the user guide
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>
2026-10-08 09:53:47 -04:00
le king fu
9a695a2504 docs: changelog and architecture for the Transactions step card
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m5s
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>
2026-10-08 08:56:49 -04:00
le king fu
ee26fe0888 docs: changelog and architecture for the Import step card
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m55s
Added: the getting-started card of the Import page. Fixed: the "No import
folder" empty state no longer flashes while the folder is read.
architecture.md gets the lines this change made stale: the component
count, the onboarding folder, useImportWizard's `importFolderLoaded` and
the Import row of the empty states table.

Resolves #398

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:34:57 -04:00
le king fu
46e714234e docs: changelog and architecture for the getting-started banner
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m49s
CHANGELOG (EN and FR): the banner under Added; the compact bar replaced
by the banner, the states read out by the list, and the re-show button
of Settings → Users under Changed. docs/architecture.md: the lines this
change made false (component count, the onboarding/ folder, the compact
mode and the re-show sentences). The user guide is rewritten by the
last issue of the milestone.

Resolves #397

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:02:47 -04:00