Commit graph

795 commits

Author SHA1 Message Date
le king fu
45d849362b state: sync after the lot-v018 stack (#468-#475, #482)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:33:58 -04:00
le king fu
c50ed53ba7 docs: the command that lists the hook harness users counts test files only (#437)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m8s
PR Check — Rust / rust (pull_request) Successful in 3m43s
Review 25974, suggestion 4: `grep -rl "test/hookHarness" src` printed
22 lines, the harness itself included; with --include='*.test.*' it
prints the 21 test files the sentence counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:15:31 -04:00
le king fu
13dca87d04 test(categories): a failed re-read of the categories shows in the window (#393)
Review 25974, suggestion 1 (Q6): the JSDoc, architecture.md and the PR
body say a failure of readCategoriesAgain shows in the hierarchy window,
and no test held it. The read is rejected once at the opening (the
diagnostic does not run on the old categories) and once after a
committed reattachment: each time the window shows the message, the
page's banner behind it stays empty, and saving is over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:15:05 -04:00
le king fu
a17c1b7604 docs: placeListbox test header and CLAUDE.md module count (#429, #384)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m2s
PR Check — Rust / rust (pull_request) Successful in 3m52s
Review 25921, points 4 and 6: the header of placeListbox.test.ts still
listed the re-read after a render among what needs a DOM, though
CategoryCombobox.placement.test.ts pins it; only the paint and React's
skipped render remain. CLAUDE.md announced 10 command modules and listed
twelve: 12 modules, 10 of them with commands, 45 commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:43:18 -04:00
le king fu
b626bcbabe fix(i18n): say « become a parent » precisely, and what to do with budget amounts (#393)
Decision of Max of 2026-10-20 and review 25921, point 3. The #393
changelog entries, the guide tip and docs.categories.tips (FR and EN)
now say a category that still takes input cannot become a parent, and
that an existing parent, no longer inputable, accepts new subcategories
as before. The parent_has_budget message and the tip say amounts of any
year count, to be set to zero in the grid year by year, and that a
template cannot be edited, only deleted; the tip adds that zeroing a
past year also takes its planned amounts out of Budget vs Actual.

architecture.md: the rule and its reason, the hierarchy rows now
carrying is_inputable, readCategoriesAgain after a reattachment (a
form open on the orphan itself keeps its old parent: known limit), and
the hook harness users presented as a count with the command that lists
them (21 on 2026-10-20), not as an exhaustive list (25921, point 6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:43:18 -04:00
le king fu
bc6610103b fix(categories): a reattachment keeps a form open behind the window (#393)
Review 25921, point 5 (pre-existing since #472): after a reattachment,
reattachOrphan reloaded through loadCategories, whose loading state
unmounted the detail panel, so a category form open behind the window
lost its unsaved changes. The silent re-read of checkHierarchy becomes
readCategoriesAgain (waits for a drag-and-drop's writes, a newer load
wins, no loading state), used by both; a failed re-read after a
reattachment now shows in the window, where the user is.

Also pins the guard « a newer load wins » (review 25921, R8): a load
started during the check's read lands, the check's older answer is
dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:41:17 -04:00
le king fu
1386abd91e fix(categories): check a parent's content only when it would become one (#393)
Decision of Max of 2026-10-20 (review 25921): transactions, non-zero
budget amounts and active keywords are checked only while the target
still takes input (is_inputable = 1, the condition of markAsParent),
that is when it really becomes a parent and stops taking input. An
existing parent, no longer inputable, accepts a new or reattached
subcategory whatever it holds, as in every released version: the
refusal protected nothing there. All three codes and all three entry
points follow the rule; the hierarchy rows now carry is_inputable,
and createCategory reads them through its transaction's handle.

Also pins a negative amount, in the grid and in a template, as a
refusal (review 25921, R1/R2): the grid stores what is typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:40:04 -04:00
le king fu
273cf36eb7 fix(categories): the hierarchy check reads after the drag-and-drop's writes (#393)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m20s
PR Check — Rust / rust (pull_request) Successful in 3m52s
Self-review of the reload: updateCategorySortOrders writes one UPDATE
per category, each through the lock on its own, so a check opened right
after a drag-and-drop could read halfway and show half the move in the
tree. moveCategory keeps its write's promise, settled either way, and
checkHierarchy waits for it before reading. Pinned by a hook test with
the write held open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:25:49 -04:00
le king fu
1a3fa1dcc9 docs: review follow-ups in REVIEW.md and architecture.md (#384, #437, #446, #449)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m22s
PR Check — Rust / rust (pull_request) Successful in 4m11s
REVIEW.md, outside « Niveaux »: a pitfall on the vitest tests that read
src-tauri/ (a PR touching only src-tauri/** skips check-frontend, review
25592 point 6), and, in the Release zone, the --locked relay to re-prove
when @tauri-apps/cli or cargo-xwin is bumped (review 25416 point 2).
architecture.md: the same relay, the six cargo metadata calls coming
from the Tauri CLI only (25416 point 3); the mount error of Transactions
wiped by the first debounced page read, and the banner cleared by
refreshFolder and browseFolder (25592 points 7 and 2); the catch-up
measurement asserts success, not duration, and PriceFetchControl among
the harness users (25443 points 1 and 5); the Ctrl+click note's reason
as read in tauri 2.10.2 and wry 0.54.1 (25487 point 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:11:34 -04:00
le king fu
9fc942d295 docs: CLAUDE.md tree recounted, ten command modules and three component folders (#384, #446)
Review 25592, point 5: the commands/ line said 3 modules and 17
commands; generate_handler! registers 45, in 10 modules (fs 7,
export_import 5, profile 8, license 11, auth 5, token_store 1,
entitlements 1, feedback 2, backup 3, balance 2), listed as in
docs/architecture.md. Review 25487, point 7: balance/ (12),
categories-migration/ (5) and onboarding/ (3) added, of the 138
components (non-test .tsx under src/components).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:11:34 -04:00
le king fu
e275a8ad8d docs: name the logs as the interface does in the #372 entry (#372)
Review 25424, point 3: the English interface never says "Journal" (the
card is "Logs"), the French one says "Journaux". Unreleased entry only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:11:34 -04:00
le king fu
a2c4fd4685 test(budget): a collapsed sub-group last in its group keeps its subtotal (#446)
Review 25487, point 2 (R13): [A, z, Vide] gives z, Vide, A; dropping
the subtotal of an empty block at the final flush turned no test red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:10:03 -04:00
le king fu
0df60f6042 test: the transfer tooltip, the cancel button and the opened notes row (#437)
Review 25443, point 2: the transfer icon's tooltip goes through t with
its account and direction (X4), LicenseKeyForm's cancel button through
t (X11), and the notes textarea opens under the row whose button was
clicked, read in the Fragment keyed by that row's id (X8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:10:03 -04:00
le king fu
2389d51b48 test(settings): a late failure of the previous language keeps the history (#372)
Review 25424, point 1: the `if (!cancelled)` of ChangelogContent's
.catch had no test (V1). Modelled on the late-answer test: the French
file rejects after the English one was read, and the history still shows
the English notes, not the empty message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:10:03 -04:00
le king fu
4f060ba7d7 test(i18n): the key guard's canary sees two modules, and lists leaves only (#453)
Review 25409, points 1 and 2: the canary now fails if both imports name
one file (T4), and a fixture pins that a missing branch is reported by
its leaves, never by its container (T1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:10:03 -04:00
le king fu
f27bdc4afa test(rust): a file named like a sample folder is refused, nothing created (#384)
Review 25592, point 1: the pre-check reads exists(), not is_dir(); under
is_dir() a file named "Exemple Tangerine" let "Exemple Desjardins" be
created before create_dir failed with a raw "File exists". The
reviewer's test pins it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:09:01 -04:00
le king fu
ffe6288e4a test(import): the sample folder messages name the folder in both languages (#384)
Review 25592, point 3: the page's tests take the marked t and the key
guard compares no value, so a translation that lost {{name}} passed the
suite. The real locale files, through a real i18next with the app's
options, now render folderExists with the folder's name in French and in
English, and the two other codes without a leftover placeholder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:09:01 -04:00
le king fu
48d1314d6d fix(import): clear the error banner when the folder is read again (#384)
Review 25592, point 2 (pre-existing): the banner of a refused sample
folder asks to refresh the list, yet survived a "Refresh" that went
through, and a folder change. refreshFolder, and browseFolder once a
folder is chosen, now clear the error before reading the folder, as
selectSource does; a failed rescan puts up its own error, and a
cancelled picker keeps the banner. The hook tests also pin browseFolder's
order (review 25487, point 1): the folder is set before its scan, so the
root files shown are the new folder's. Fixed entry in both changelogs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:09:01 -04:00
le king fu
a52958aff3 docs: the open category list reads its position after each render (#429)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:06:22 -04:00
le king fu
5f2c4dd5bd fix(combobox): follow the input when a render moves it (#429)
Review 25431: a window centered on screen grows when its content
arrives, and moves its fields with no scroll nor resize; the open list,
placed in a portal, stayed where it was. While open, the combobox now
reads its position again after every render (useLayoutEffect without
dependencies); `keepPlacement` hands back the same state object for an
unchanged position, so React renders nothing more. Pinned by
CategoryCombobox.placement.test.ts (harness, useLayoutEffect mapped on
its useEffect, fake input) and keepPlacement's own tests. The boundary
test of placeListbox now sits at exactly 240 px below with room above
(point 1), and AddKeywordDialog's comment no longer claims the list is
cut (point 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:06:22 -04:00
le king fu
813ee07559 fix(i18n): name the eight getting-started steps in the dashboard help (#446)
Decision of Max of 2026-10-19 (review 25487, point 4):
docs.dashboard.steps[0] (FR and EN) and step 1 of the guide's Dashboard
section listed six of the eight steps. They now name the eight, by the
labels of onboarding.checklist.items, in the list's order, and say the
two visit steps are checked with the card's "Got it" or, for the
reports, a click on the outlined Trends card. A test holds the step to
the checklist's labels and order; the #446 changelog entries follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:03:59 -04:00
le king fu
a5b92b975e docs: the hierarchy window reads the categories again on opening (#393)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:59:27 -04:00
le king fu
7f25293669 test(categories): pin the rules the review of #393 left green (#393)
Review 25437, point 2: the loop named by its own smallest member, not by
a smaller id under it (RM1); the page hands the window every category,
not the tree (RM11); a category that is its own parent stays out of the
budget, and displaysAsRoot says so (RM15). RM7 (depth counted on active
categories at the service) is pinned in categoryService.test.ts by
1a36d4e, RM2 by be4b923.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:59:07 -04:00
le king fu
be4b923451 fix(categories): reload the categories before the hierarchy check (#393)
After a drag-and-drop, SET_TREE leaves state.categories with what the
tree shows, without the members of a loop: the window then showed "—"
for the orphan and offered a parent the service refused (review 25437,
point 1). checkHierarchy now reads the categories again before the
diagnostic, without the page's loading state, so a form open behind
the window stays as it is; a load started meanwhile wins. Pinned by a
drag-and-drop-then-check hook test, plus the refusal-then-success case
of the window's message (RM2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:59:02 -04:00
le king fu
c8c8c068fc docs: a parent's budget amounts and keywords, the create path, node:sqlite in tests (#393)
The unreleased #393 entries now say the refusal covers budget amounts
and keywords and applies when a subcategory is created; guide tip and
architecture.md (validation, createCategory, out-of-scope drag-and-drop,
the node:sqlite test note) follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:51:29 -04:00
le king fu
1a36d4e618 feat(categories): refuse a parent that holds budget amounts or keywords, on create too (#393)
Decision of Max of 2026-10-19 (review 25437, point 3): a category that
holds direct transactions, a non-zero budget amount (budget_entries or
budget_template_entries) or active keywords cannot become a parent. The
check (`checkParentContent`) runs through the transaction's handle,
before any write, in `updateCategory` (changed parent), `reattachCategory`
and now `createCategory`, which writes its row and the parent's flag in
one transaction. Order: transactions, budget, keywords; the first found
is the refusal (`parent_has_budget`, `parent_has_keywords` added to
`CategoryHierarchyError`, translated in FR and EN).

The service tests run the content queries as written on node:sqlite,
tables created from consolidated_schema.sql.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:51:24 -04:00
le king fu
08e6f045ae test: read the banner's dots once in the presence test (#446)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m2s
Self-review tidy-up: the presence test looked the dots up twice; it now
keeps the one list it asserts on. No assertion changed.

Resolves #446

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:07:44 -04:00
le king fu
52e005ef92 docs: components recounted, five report cards, Ctrl+click on Trends (#446)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m2s
architecture.md: 138 components, counted as the .tsx files of
src/components outside tests (find src/components -name '*.tsx'
! -name '*.test.tsx' | wc -l), was 61; per domain, balance 12 with its
actual names (BalanceOnboardingCard is gone), categories 8, reports 35,
and the missing categories-migration/ line (5). The /reports hub has 5
navigation cards, named. The useImportWizard row says SET_IMPORT_FOLDER
empties rootFiles and keeps the scanned sources until the next scan.
"La mise en évidence" notes that a Ctrl+click on the Trends card fires
onClick, so it checks the "Browse the reports" step while the router
does not navigate; judged theoretical without tabs in the webview.

CLAUDE.md: 138 components (was 53), import 18 (was 14).

CHANGELOG (EN, FR): Changed for the help texts, Fixed for the root
files warning after choosing another import folder.

Resolves #446

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:04:46 -04:00
le king fu
d068c7ecbb fix(i18n): name the reports by their cards, and how the visit steps get checked (#446)
reports.help.tips[2] and [3] named "Monthly Trends" and "Category Over
Time" ("Tendances mensuelles", "Catégories dans le temps"), tabs that
no longer exist: they now name the Trends and Category Analysis cards
of the hub. docs.dashboard.features[6] said the eight steps tick
themselves from the data; the two visit steps are checked with "Got it".
French texts as validated by Max on 2026-10-16, English aligned; the
guide's line of the dashboard features follows docs.dashboard.features.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:59:22 -04:00
le king fu
d551c4b0d4 test: pin the collapsed sub-group order and the banner's dot (#446)
reorderRows: a collapsed sub-group (a subtotal with no child row)
followed by a sibling leaf gives Vide, z, A, the order read by running
the case first; then the same block followed by a sibling root, a group
(Vide, z, A, b, B) or a leaf (Vide, z, A, R).

OnboardingBanner: the decorative h-1 w-1 dot is absent while the visit
steps are not initialized and while the Budget entitlement is unknown,
and present, between the counter and the next step, once the total is
known.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:59:17 -04:00
le king fu
a62150d926 fix(import): empty the root files when another import folder is chosen (#446)
SET_IMPORT_FOLDER now empties rootFiles. Until the new folder's scan
landed, or for good when that scan failed, the warning about files
placed directly in the import folder named the previous folder's files
under the new folder's name. scannedSources is left as it was: the
source list of the last scan stays until the new one, as before.

Reducer tests next to "a scan error leaves the root files of the last
scan in place": a folder change empties the root files and keeps the
sources until the next scan, and a scan error after a folder change
names no root file of the previous folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:59:13 -04:00
le king fu
08c2f57559 docs: wording of the read paragraph and of the write errors (#384)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m4s
PR Check — Rust / rust (pull_request) Successful in 3m41s
Self-review findings, no behaviour change. architecture.md: the mount reads
paragraph no longer plays on "compte" (count and account). fs_commands.rs:
the doc comment of create_sample_import_folders said a write error carries
"never a path", while it names the sample folder and its file; it now says
it never carries the import folder's path.

Resolves #384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:48:03 -04:00
le king fu
1ed6a67af0 fix(i18n): the "already exists" message also holds for a file (#384)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m6s
PR Check — Rust / rust (pull_request) Successful in 3m46s
Self-review finding: create_sample_import_folders refuses on any existing
entry named like a sample folder, a file included, which "Refresh the list"
never shows. The message now says to refresh the list and, if the entry
does not show up, to rename it before trying again (FR and EN), and the
changelog entries follow.

Resolves #384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:47:05 -04:00
le king fu
a64ac3b8a8 docs: commands and hooks recounted, sample folder codes, guide wording (#384)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m59s
PR Check — Rust / rust (pull_request) Successful in 9m48s
architecture.md: the Tauri commands recounted from generate_handler! (45,
was 39), with the five that were never named documented in new sections
of their modules (feedback_commands.rs: send_feedback,
get_feedback_user_agent; backup_commands.rs: ensure_backup_dir,
get_file_size, file_exists) and fetch_price described as it is, not as
"to come in Phase 5" (also in the balance service and the tree's command
modules, now 10). Hooks: 29 files, the three undocumented ones added to the
table. The Transactions empty state reads the count alone (D1), the mount
reads stand alone, and the sample folder flow describes its codes, its
errorValues and its Journal line.

CLAUDE.md: 29 hooks in the tree. Guide and docs.gettingStarted.steps:
"the keywords that come with the app" instead of the seed jargon.
CHANGELOG (EN, FR): Changed for the Transactions card, Fixed for the sample
folder messages and banners.

Resolves #384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:42:23 -04:00
le king fu
038bc41cb3 test(import): pin the empty state's variant for a folder stored as "" (#384)
With folderName as its only prop, ImportEmptyState tells "no folder" from
"a folder" by null alone. The page hands "" for an import folder stored as
"", as hasFolder did, so the empty state keeps the "no source" variant the
step card describes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:31:50 -04:00
le king fu
66bdb19f44 test: correct the seed priority comment of the onboarding samples test (#384)
The seed is not all priority 0: COSTCO and WALMART are at 50, COUCHE-TARD
ESSENCE at 10. The comment now says why the mock's sort stays right: it
sorts by descending priority like the SQL, and no line of either sample is
matched by more than one seed keyword, so the order cannot move the counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:30:42 -04:00
le king fu
3d917ed803 fix(transactions): show the import-first card on the count alone (#384)
D1 of Max: showsEmptyState of Transactions reads totalInDb === 0 only, like
Budget. On an empty profile the card comes up as soon as the count is
loaded, instead of the filter bar, the totals and the loading line until
the first page is back. A null count (not loaded, or failed) keeps the
regular layout.

The mount reads of useTransactions and useBudget run under
Promise.allSettled: each read that succeeds is dispatched, the first
failure becomes the page's error, and a failed count leaves totalInDb null
instead of emptying the category, source and account filters.

Tests: the reducer test of the loading page rewritten for D1, the page's
empty-profile case rendered (card in place of the filter bar, table and
pagination), and both hooks' mount reads run through the shared harness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:30:42 -04:00
le king fu
11eb911657 fix(import): translate the sample folder refusals, banner after a good rescan (#384)
create_sample_import_folders refuses with stable codes instead of English
sentences: sample_folder_exists:<name>, import_folder_missing (no longer
carrying the folder's path) and invalid_date. The wizard maps them to
import.empty.sampleErrors.* through the pure sampleFolderError, the folder
name interpolated from the new errorValues of the state; any other message
stays raw. The raw refusal also goes to the Journal as a warning: a code, or
a write error naming a sample folder and the OS error, never a path.

scanFolderInternal now tells whether the scan went through, and the success
banner only follows a rescan that did: a failed rescan leaves its error
alone on screen.

ImportEmptyState keeps folderName alone (null without a folder); the page
passes "" for a folder stored as "", as hasFolder did.

Rust tests: the three codes asserted exactly, the case of the second sample
folder already there (nothing written), and {{M1}} pinned as the most
recent month line by line against the templates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:30:35 -04:00
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
3e8c79f057 test: pin the \r path stop and the surviving marked-t keys (#437)
- db.test.ts: `\r` joins the path stops of withoutFolders (removing it from
  PATH_STOP now fails).
- The six keys whose "t removed" mutation survived the review of PR #435
  (comment 23499) are asserted in their marked form:
  transactions.transferIcon.ariaLabel (a row linked to a transfer) and
  transactions.notes.placeholder (title of every row's notes button) in
  TransactionTable.render.test.tsx; license.keyPlaceholder and
  license.activate in LicenseKeyForm.render.test.tsx;
  dashboard.kpiSectionTitle in DashboardPage.render.test.tsx;
  balance.priceFetching.consent.accept in PriceFetchControl.test.tsx, the
  consent dialog opened by presetting the control's first harness slot
  (the dialog's title, body and decline texts are pinned with it).
- TransactionTable.wiring.test.ts opens a row's notes through its button
  and pins the textarea placeholder, the second call of the notes key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:46:47 -04:00
le king fu
8a9d9909f9 test: pin the reattachment count to active descendants (#393)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m9s
A deactivated child of the reattached category is not counted in the
number of categories put back, which the Journal line reports.

Resolves #393

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:28:45 -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
cb0e2d2536 fix: reload the category tree even when the check after a reattachment fails (#393)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m13s
The reattachment is committed before the orphan list is checked again: if
that check failed, the tree kept showing the categories as they were. The
tree now reloads in a finally block, and the failure shows in the window.

Resolves #393

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:24:31 -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
21da7a3827 fix: show a category whose parent is deleted or gone in the budget (#393)
The budget grid and the Budget vs Actual report read active categories
only and started from the categories without a parent, so a category whose
parent was deactivated or no longer exists vanished from both, budget
amounts included. It now stands at the top level, as in the tree and the
category pickers (displaysAsRoot).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:16:25 -04:00
le king fu
db5ee65720 fix: refuse a looping or too deep parent, and reattach orphan categories (#393)
updateCategory checks a new parent before writing: the category itself,
one of its descendants or a category caught in a cycle (cycle), a fourth
level counting its sub-tree (too_deep), a parent holding transactions of
its own (parent_has_transactions). The refusal is a CategoryHierarchyError
that the Categories page shows by the translation of its code. The reads
and the writes, the new parent's is_inputable included, run in one
transaction through its handle; an unchanged parent is not checked, so a
category stored before these rules can still be renamed.

getCategoryDepth keeps the ids it has seen and ends on a cycle.

A "Check hierarchy" button, between "All keywords" and "Re-initialize",
opens a window that lists the orphan categories (caught in a parent loop,
parent deleted, parent gone), one per group, with a new parent for each:
the root by default, or a parent of the category form's list.
reattachCategory applies the same rules; after it, the list and the tree
reload and the Journal gets one line with the reason and the number of
categories put back, no name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:16:21 -04:00
le king fu
df3fefe934 feat: gather the category hierarchy rules in one module (#393)
src/utils/categoryHierarchy.ts holds the depth, height and descent walks
that buildParentOptions had inline, each with a set of the categories it
has seen, so a parent_id cycle ends the walk. A category caught in a cycle,
or under one, now has no depth instead of a made-up one, and the parent
list of the category form never offers it: a child placed there would be
hidden with it. The module also carries the orphan diagnostic, the parent
change refusal and the root rule that the service, the Categories page and
the budget use next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:16: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