test: shared React hook harness for the jsdom-less tests (#409) #434

Merged
maximus merged 3 commits from issue-409-hook-harness into main 2026-10-14 00:28:24 +00:00
Owner

Generated autonomously by /autopilot run of 2026-10-12

Issue #409, stacked on PR #433 (#408). Base: issue-408-db-strict-lock.

What

A shared React hook harness, src/test/hookHarness.ts, for the tests that call hooks and components as plain functions (no jsdom), and the migration of six test files to it. No production code changes, no CHANGELOG entry (tests only).

  • createHookHarness() and fakeReact(actual, harness = shared), singleton read by hookHarness(). One pattern at the head of each migrated file:
    vi.mock("react", async (orig) => (await import("<path>/test/hookHarness")).fakeReact(await orig()));
  • Semantics, written at the head of the module and each proved by src/test/hookHarness.test.ts (28 tests): useState init once + stable setter + functional updates, no auto re-render; useReducer applies the reducer, stable dispatch, every action recorded by the harness.dispatch spy; useRef kept; useMemo/useCallback recomputed only on a changed dep (Object.is); useEffect captured, run only by commit() when a dep changed, cleanups first, a render committed once; replayEffects() (StrictMode at mount) and unmount(); every uncovered hook (useContext, useLayoutEffect, useSyncExternalStore, useId, useTransition, use) throws, on the named and the default export. The module imports React only as import type, checked on its source.

Files

  • src/test/hookHarness.ts, src/test/hookHarness.test.ts (new)
  • src/hooks/useOnboardingChecklist.wiring.test.ts: on the harness; effects driven by commit() (the route dependency is now proved by behaviour); 3 new tests for RM3, RM4, RM12
  • src/pages/settings/UsersSettingsPage.test.tsx: effects committed; « never reads the preference itself, neither while rendering nor in an effect »
  • src/components/budget/BudgetTable.wiring.test.ts, src/pages/BudgetPage.wiring.test.ts, src/components/layout/AppShell.slot.test.tsx, src/components/onboarding/OnboardingStepCard.test.tsx: local slot copies removed. i18n mocks left as they are (#389).
  • docs/architecture.md: new ## Tests (vitest sans jsdom) section, just before CI/CD.

Out of scope (decision of 2026-10-10): useImportWizard.mount.test.ts, useImportWizard.executeImport.test.ts.

Proof: mutation pass on production code

One mutation at a time, npx vitest run <file>, restored by git checkout. Every red is on an AssertionError, none on « Invalid hook call ».

# Mutation File Exit Red test(s)
RM3 useOnboardingChecklist.ts: loader out of useState (const refresh = createOnboardingLoader(...)) useOnboardingChecklist.wiring 1 « keeps one loader for the provider's life » (refresh identity), « reads at mount… » (read called 2 times, expected 1)
RM4 state.skipped removed from the deps of checklist useOnboardingChecklist.wiring 1 « 'Skip' moves the next step on the next render »
RM12 mode removed from the deps of the shared value useOnboardingChecklist.wiring 1 « a change of mode alone reaches the consumers » ('none' kept)
U1 UsersSettingsPage.tsx: useEffect(() => { void getPreference(...) }, []) UsersSettingsPage 1 « never reads the preference itself… » only (getPreference called 1 time)
B1 BudgetTable.tsx: targetKind removed from the report effect's deps BudgetTable.wiring 1 « tells it once mounted, then only when it changes »
B2 BudgetPage.tsx: useState of gridTarget replaced by a local variable BudgetPage.wiring 1 3 tests (sentence, same callback, profile without transactions)
A1 AppShell.tsx: useState of listOpen replaced by a local variable AppShell.slot 1 « 'Show the list' unfolds it… »
S1 OnboardingStepCard.tsx: location.key removed from the effect's deps OnboardingStepCard 1 « does it once per arrival… » (2nd arrival). Green on the base (the old stub ran every effect regardless of deps)
S2 OnboardingStepCard.tsx: the focusedFor guard removed OnboardingStepCard 1 « does it once per arrival… » (StrictMode replay focuses twice)
H1 hookHarness.ts: import type * as React turned into a value import hookHarness.test 1 « imports React only as a type »

Test count: base 115 files / 1978 tests, this branch 116 files / 2009 tests (+28 harness, +3 onboarding). npm run build and npm test green in the worktree.

Decisions (autopilot)

  • [MEDIUM] fakeReact(actual, harness = shared) rather than fakeReact(harness, actual): the planned pattern fakeReact(await orig()) takes one argument, the harness is a module singleton read by hookHarness(); the optional second argument lets the harness's own tests use createHookHarness().
  • [MEDIUM] Values the test changes between renders (pathname, onboarding value, location…) moved to a per-file vi.hoisted ctx object; the shared harness carries React's state only.
  • [MEDIUM] The onboarding wiring tests preset the provider's state with harness.slots[0] = state (mountWith, its useReducer is its first React hook), as the old harness.state = … did.
  • [HIGH] commit() once per render, cleanups before effects; replayEffects() for StrictMode, unmount() for cleanups; default export patched too.

For the reviewer

  • mountWith relies on the reducer being the provider's first React hook; a reorder would make the preset land on another slot (the tests would then fail on their status/next-step assertions, not silently).
  • harness.dispatch records actions, the reducer is applied by the per-slot dispatch the component receives: mockReset() on the spy no longer silences the reducer.

🤖 Generated with Claude Code

Generated autonomously by /autopilot run of 2026-10-12 Issue #409, stacked on PR #433 (#408). Base: `issue-408-db-strict-lock`. ## What A shared React hook harness, `src/test/hookHarness.ts`, for the tests that call hooks and components as plain functions (no jsdom), and the migration of six test files to it. No production code changes, no CHANGELOG entry (tests only). - `createHookHarness()` and `fakeReact(actual, harness = shared)`, singleton read by `hookHarness()`. One pattern at the head of each migrated file: `vi.mock("react", async (orig) => (await import("<path>/test/hookHarness")).fakeReact(await orig()));` - Semantics, written at the head of the module and each proved by `src/test/hookHarness.test.ts` (28 tests): `useState` init once + stable setter + functional updates, no auto re-render; `useReducer` applies the reducer, stable dispatch, every action recorded by the `harness.dispatch` spy; `useRef` kept; `useMemo`/`useCallback` recomputed only on a changed dep (`Object.is`); `useEffect` captured, run only by `commit()` when a dep changed, cleanups first, a render committed once; `replayEffects()` (StrictMode at mount) and `unmount()`; every uncovered hook (`useContext`, `useLayoutEffect`, `useSyncExternalStore`, `useId`, `useTransition`, `use`) throws, on the named and the default export. The module imports React only as `import type`, checked on its source. ## Files - `src/test/hookHarness.ts`, `src/test/hookHarness.test.ts` (new) - `src/hooks/useOnboardingChecklist.wiring.test.ts`: on the harness; effects driven by `commit()` (the route dependency is now proved by behaviour); 3 new tests for RM3, RM4, RM12 - `src/pages/settings/UsersSettingsPage.test.tsx`: effects committed; « never reads the preference itself, neither while rendering nor in an effect » - `src/components/budget/BudgetTable.wiring.test.ts`, `src/pages/BudgetPage.wiring.test.ts`, `src/components/layout/AppShell.slot.test.tsx`, `src/components/onboarding/OnboardingStepCard.test.tsx`: local slot copies removed. i18n mocks left as they are (#389). - `docs/architecture.md`: new `## Tests (vitest sans jsdom)` section, just before CI/CD. Out of scope (decision of 2026-10-10): `useImportWizard.mount.test.ts`, `useImportWizard.executeImport.test.ts`. ## Proof: mutation pass on production code One mutation at a time, `npx vitest run <file>`, restored by `git checkout`. Every red is on an `AssertionError`, none on « Invalid hook call ». | # | Mutation | File | Exit | Red test(s) | |---|---|---|---|---| | RM3 | `useOnboardingChecklist.ts`: loader out of `useState` (`const refresh = createOnboardingLoader(...)`) | useOnboardingChecklist.wiring | 1 | « keeps one loader for the provider's life » (refresh identity), « reads at mount… » (read called 2 times, expected 1) | | RM4 | `state.skipped` removed from the deps of `checklist` | useOnboardingChecklist.wiring | 1 | « 'Skip' moves the next step on the next render » | | RM12 | `mode` removed from the deps of the shared value | useOnboardingChecklist.wiring | 1 | « a change of mode alone reaches the consumers » (`'none'` kept) | | U1 | `UsersSettingsPage.tsx`: `useEffect(() => { void getPreference(...) }, [])` | UsersSettingsPage | 1 | « never reads the preference itself… » only (`getPreference` called 1 time) | | B1 | `BudgetTable.tsx`: `targetKind` removed from the report effect's deps | BudgetTable.wiring | 1 | « tells it once mounted, then only when it changes » | | B2 | `BudgetPage.tsx`: `useState` of `gridTarget` replaced by a local variable | BudgetPage.wiring | 1 | 3 tests (sentence, same callback, profile without transactions) | | A1 | `AppShell.tsx`: `useState` of `listOpen` replaced by a local variable | AppShell.slot | 1 | « 'Show the list' unfolds it… » | | S1 | `OnboardingStepCard.tsx`: `location.key` removed from the effect's deps | OnboardingStepCard | 1 | « does it once per arrival… » (2nd arrival). **Green on the base** (the old stub ran every effect regardless of deps) | | S2 | `OnboardingStepCard.tsx`: the `focusedFor` guard removed | OnboardingStepCard | 1 | « does it once per arrival… » (StrictMode replay focuses twice) | | H1 | `hookHarness.ts`: `import type * as React` turned into a value import | hookHarness.test | 1 | « imports React only as a type » | Test count: base 115 files / 1978 tests, this branch 116 files / 2009 tests (+28 harness, +3 onboarding). `npm run build` and `npm test` green in the worktree. ## Decisions (autopilot) - [MEDIUM] `fakeReact(actual, harness = shared)` rather than `fakeReact(harness, actual)`: the planned pattern `fakeReact(await orig())` takes one argument, the harness is a module singleton read by `hookHarness()`; the optional second argument lets the harness's own tests use `createHookHarness()`. - [MEDIUM] Values the test changes between renders (pathname, onboarding value, location…) moved to a per-file `vi.hoisted` `ctx` object; the shared harness carries React's state only. - [MEDIUM] The onboarding wiring tests preset the provider's state with `harness.slots[0] = state` (`mountWith`, its `useReducer` is its first React hook), as the old `harness.state = …` did. - [HIGH] `commit()` once per render, cleanups before effects; `replayEffects()` for StrictMode, `unmount()` for cleanups; default export patched too. ## For the reviewer - `mountWith` relies on the reducer being the provider's first React hook; a reorder would make the preset land on another slot (the tests would then fail on their status/next-step assertions, not silently). - `harness.dispatch` records actions, the reducer is applied by the per-slot dispatch the component receives: `mockReset()` on the spy no longer silences the reducer. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 3 commits 2026-10-12 23:45:22 +00:00
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>
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>
docs(architecture): add the tests section and the hook harness
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m57s
79ba1ed2f3
Resolves #409

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maximus added the
autopilot:pending-human
label 2026-10-12 23:45:29 +00:00
Author
Owner

Self-review (autopilot worker, not independent) — verdict: APPROVE

Diff read in full against issue-408-db-strict-lock (9 files, +853/−325).

  • Security: tests and docs only, no production code, no secret, no personal data.
  • Acceptance criteria
    • src/test/hookHarness.ts with createHookHarness() and fakeReact; single pattern at the head of the module and in the 6 migrated files; harness read through hookHarness().
    • React imported only as import type, checked by hookHarness.test.ts on the source (mutation H1 red).
    • useReducer applies the reducer and records every action in harness.dispatch; the existing harness.dispatch.mock.calls / invocationCallOrder assertions are unchanged and green.
    • No auto re-render, effects only through commit() with cleanups first, uncovered hooks throw (6 hooks + default export tested).
    • Each covered semantic proved by its own test (28 tests).
    • RM3, RM4, RM12 and the UsersSettingsPage effect each red on an assertion; one mutation per migrated file red (table in the PR body). S1 (location.key dep) was green on the base.
    • No local useState/useRef slot copy left in the migrated files (grep).
    • npm test 116 files / 2009 tests (base 115 / 1978); npm run build green.
    • docs/architecture.md: ## Tests (vitest sans jsdom) created just before CI/CD.
  • Regressions: useImportWizard.mount/executeImport untouched (out of scope); i18n mocks untouched for #389.
  • i18n / CHANGELOG / migrations: no UI text, no CHANGELOG entry (tests only), no SQL touched.

Points for the independent review: mountWith presets slot 0 (the provider's useReducer), and fakeReact's argument order differs from the analysis text (the planned one-argument pattern wins, see decisions in the PR body).

## Self-review (autopilot worker, not independent) — verdict: APPROVE Diff read in full against `issue-408-db-strict-lock` (9 files, +853/−325). - **Security**: tests and docs only, no production code, no secret, no personal data. - **Acceptance criteria** - [x] `src/test/hookHarness.ts` with `createHookHarness()` and `fakeReact`; single pattern at the head of the module and in the 6 migrated files; harness read through `hookHarness()`. - [x] React imported only as `import type`, checked by `hookHarness.test.ts` on the source (mutation H1 red). - [x] `useReducer` applies the reducer and records every action in `harness.dispatch`; the existing `harness.dispatch.mock.calls` / `invocationCallOrder` assertions are unchanged and green. - [x] No auto re-render, effects only through `commit()` with cleanups first, uncovered hooks throw (6 hooks + default export tested). - [x] Each covered semantic proved by its own test (28 tests). - [x] RM3, RM4, RM12 and the UsersSettingsPage effect each red on an assertion; one mutation per migrated file red (table in the PR body). S1 (`location.key` dep) was green on the base. - [x] No local `useState`/`useRef` slot copy left in the migrated files (grep). - [x] `npm test` 116 files / 2009 tests (base 115 / 1978); `npm run build` green. - [x] `docs/architecture.md`: `## Tests (vitest sans jsdom)` created just before CI/CD. - **Regressions**: `useImportWizard.mount/executeImport` untouched (out of scope); i18n mocks untouched for #389. - **i18n / CHANGELOG / migrations**: no UI text, no CHANGELOG entry (tests only), no SQL touched. Points for the independent review: `mountWith` presets slot 0 (the provider's `useReducer`), and `fakeReact`'s argument order differs from the analysis text (the planned one-argument pattern wins, see decisions in the PR body).
Author
Owner

CI lue par l’orchestrateur de l’autopilot (2026-10-12) sur la tête 79ba1ed : PR Check — Frontend run 530 vert.

✓ built in 11.03s
Test Files  116 passed (116)
Tests  2009 passed (2009)
⚙  Summary - ## Vitest Test Report\n\n### Summary\n\n- **Test Files**: ✅ **116 passes** · 116 total\n- **Test Results**: ✅ **2009 passes** · 2009 total

RM12 relancée par l’orchestrateur sur la version finale du harnais (le worker avait réécrit hookHarness.ts après un checkout accidentel) : mode retiré des dépendances de la valeur partagée → 1 test rouge sur une AssertionError (« a change of mode alone reaches the consumers »), code 1, fichier restauré.

CI lue par l’orchestrateur de l’autopilot (2026-10-12) sur la tête `79ba1ed` : `PR Check — Frontend` run 530 vert. ``` ✓ built in 11.03s Test Files 116 passed (116) Tests 2009 passed (2009) ⚙ Summary - ## Vitest Test Report\n\n### Summary\n\n- **Test Files**: ✅ **116 passes** · 116 total\n- **Test Results**: ✅ **2009 passes** · 2009 total ``` RM12 relancée par l’orchestrateur sur la version finale du harnais (le worker avait réécrit `hookHarness.ts` après un `checkout` accidentel) : `mode` retiré des dépendances de la valeur partagée → 1 test rouge sur une `AssertionError` (« a change of mode alone reaches the consumers »), code 1, fichier restauré.
Author
Owner

Revue adversariale — PR #434 (#409), tête 79ba1ed, contre issue-408-db-strict-lock

Verdict : APPROVE

Le harnais partagé tient ses promesses : la sémantique écrite en tête de hookHarness.ts est celle que les tests prouvent, les six fichiers migrés gardent leurs assertions (plusieurs se renforcent : la dépendance de route est prouvée par comportement, le reducer est réellement appliqué, S1 rougit enfin), et aucun code de production n'est touché.

Ce que le relecteur a exécuté

Worktree détaché sur 79ba1ed, npm ci propre :

  • npx vitest run : 116 fichiers, 2009 tests verts ; tsc --noEmit vert. CI de la tête : PR Check — Frontend en success.
  • Passe de mutations du relecteur (une à la fois, restaurée par git checkout), sur les fichiers du harnais + les six fichiers migrés (69 tests) :
# Mutation Résultat
M1 useOnboardingChecklist.ts : pathname ajouté aux deps du suiveur (followDataChanges) vert (survit)
M2 commit() joue tous les effets sans comparer les deps 4 rouges (harnais, onboarding ×2, BudgetTable)
M3 useCallback ignore ses deps 2 rouges
M4 useReducer n'applique plus le reducer 4 rouges
M5 garde committed retirée 1 rouge
M6 export default non patché 1 rouge
M7 nettoyages joués après les effets 3 rouges
M8 branche previous.length !== next.length de depsChanged retirée vert (survit)
M9 useMemo recalcule à chaque rendu 5 rouges
M10 setter/dispatch recréés à chaque rendu 3 rouges (dont BudgetPage « same callback »)

Suggestions (non bloquantes)

  1. M1 — une affirmation du test n'est plus prouvée. useOnboardingChecklist.wiring.test.ts, test « re-reads 150 ms after a burst… » : le commentaire dit « A route change re-runs the read, not the follower: one subscription only », mais l'ancienne assertion expect(follow.deps).not.toContain(pathname) a disparu et rien ne la remplace : avec pathname dans les deps du suiveur, le harnais nettoie puis réabonne, il reste un seul abonnement, le test reste vert. Effet réel de la mutation : un changement de route annule une relecture différée en cours. Épingle possible par comportement : notifyDataChanged(), changement de route à t+100 ms, advanceTimersByTime(150) → attendre deux LOAD_START (route + relecture différée) ; la mutation en donne un seul. Sinon, retirer l'affirmation du commentaire.
  2. M8 : la branche « longueur des deps différente » de depsChanged n'est épinglée par aucun test. Cas sans usage réel (React avertit sur un tableau de taille variable) ; un test d'une ligne ou le retrait de la branche.
  3. Le dispatch d'un useReducer capture le reducer du premier rendu (writer créé une fois), alors que React applique le reducer courant. Sans effet sur les six fichiers (reducers de module), mais à ajouter à la liste « NOT SIMULATED » de l'en-tête et de docs/architecture.md.

Points vérifiés

  • Pas de secret, pas de .only/.skip, aucun code de production modifié, pas d'entrée CHANGELOG (tests seuls, conforme à la règle).
  • Isolation par fichier : pas de config vitest, donc isolate par défaut, le singleton est bien par fichier comme l'annonce l'en-tête.
  • Commits conventionnels (test:, docs(architecture):).
  • Fermeture de #409 : le corps de la PR ne porte aucun mot-clé de fermeture et la timeline de #409 marque cette PR en ref_action: none. Seul le message du commit 86fd496 porte Resolves #409 : l'issue se fermera quand ce commit atteindra main (merge ff de la pile), pas au merge de cette PR dans issue-408-db-strict-lock. À vérifier après le merge de la pile.
  • Le worktree de revue est supprimé en fin de revue.
## Revue adversariale — PR #434 (#409), tête `79ba1ed`, contre `issue-408-db-strict-lock` **Verdict : APPROVE** Le harnais partagé tient ses promesses : la sémantique écrite en tête de `hookHarness.ts` est celle que les tests prouvent, les six fichiers migrés gardent leurs assertions (plusieurs se renforcent : la dépendance de route est prouvée par comportement, le reducer est réellement appliqué, `S1` rougit enfin), et aucun code de production n'est touché. ### Ce que le relecteur a exécuté Worktree détaché sur `79ba1ed`, `npm ci` propre : - `npx vitest run` : **116 fichiers, 2009 tests verts** ; `tsc --noEmit` vert. CI de la tête : `PR Check — Frontend` en `success`. - Passe de mutations du relecteur (une à la fois, restaurée par `git checkout`), sur les fichiers du harnais + les six fichiers migrés (69 tests) : | # | Mutation | Résultat | |---|---|---| | M1 | `useOnboardingChecklist.ts` : `pathname` ajouté aux deps du suiveur (`followDataChanges`) | **vert (survit)** | | M2 | `commit()` joue tous les effets sans comparer les deps | 4 rouges (harnais, onboarding ×2, BudgetTable) | | M3 | `useCallback` ignore ses deps | 2 rouges | | M4 | `useReducer` n'applique plus le reducer | 4 rouges | | M5 | garde `committed` retirée | 1 rouge | | M6 | export `default` non patché | 1 rouge | | M7 | nettoyages joués après les effets | 3 rouges | | M8 | branche `previous.length !== next.length` de `depsChanged` retirée | **vert (survit)** | | M9 | `useMemo` recalcule à chaque rendu | 5 rouges | | M10 | setter/dispatch recréés à chaque rendu | 3 rouges (dont BudgetPage « same callback ») | ### Suggestions (non bloquantes) 1. **M1 — une affirmation du test n'est plus prouvée.** `useOnboardingChecklist.wiring.test.ts`, test « re-reads 150 ms after a burst… » : le commentaire dit « A route change re-runs the read, not the follower: one subscription only », mais l'ancienne assertion `expect(follow.deps).not.toContain(pathname)` a disparu et rien ne la remplace : avec `pathname` dans les deps du suiveur, le harnais nettoie puis réabonne, il reste un seul abonnement, le test reste vert. Effet réel de la mutation : un changement de route annule une relecture différée en cours. Épingle possible par comportement : `notifyDataChanged()`, changement de route à t+100 ms, `advanceTimersByTime(150)` → attendre deux `LOAD_START` (route + relecture différée) ; la mutation en donne un seul. Sinon, retirer l'affirmation du commentaire. 2. **M8** : la branche « longueur des deps différente » de `depsChanged` n'est épinglée par aucun test. Cas sans usage réel (React avertit sur un tableau de taille variable) ; un test d'une ligne ou le retrait de la branche. 3. Le `dispatch` d'un `useReducer` capture le reducer du premier rendu (`writer` créé une fois), alors que React applique le reducer courant. Sans effet sur les six fichiers (reducers de module), mais à ajouter à la liste « NOT SIMULATED » de l'en-tête et de `docs/architecture.md`. ### Points vérifiés - Pas de secret, pas de `.only`/`.skip`, aucun code de production modifié, pas d'entrée CHANGELOG (tests seuls, conforme à la règle). - Isolation par fichier : pas de config vitest, donc `isolate` par défaut, le singleton est bien par fichier comme l'annonce l'en-tête. - Commits conventionnels (`test:`, `docs(architecture):`). - **Fermeture de #409** : le corps de la PR ne porte aucun mot-clé de fermeture et la timeline de #409 marque cette PR en `ref_action: none`. Seul le message du commit `86fd496` porte `Resolves #409` : l'issue se fermera quand ce commit atteindra `main` (merge ff de la pile), pas au merge de cette PR dans `issue-408-db-strict-lock`. À vérifier après le merge de la pile. - Le worktree de revue est supprimé en fin de revue.
Author
Owner

Suivis de cette revue appliqués en tête de pile, PR #436, commit a1c31a1 (détail dans le commentaire 23535 de la PR #436, non relu).

Suivis de cette revue appliqués en tête de pile, PR #436, commit `a1c31a1` (détail dans le commentaire 23535 de la PR #436, non relu).
maximus changed target branch from issue-408-db-strict-lock to main 2026-10-14 00:28:15 +00:00
maximus merged commit 79ba1ed2f3 into main 2026-10-14 00:28:24 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#434
No description provided.