Repository navigation
fix(platform): recover failed erasure subject directory reads - #4325
yannickmonney wants to merge 1 commit into
Conversation
Independent review (TALE-706): REQUEST CHANGES at
|
| Criterion | Result | Evidence |
|---|---|---|
| Failed read: localized alert and a focus-safe Retry, never "No matching members." | Met for a read that never answered. Not met for a failed refresh while the picker is open (B1). | Probe P1 uses the real hook: 4 attempts against a 503, then the alert. Retry keeps focus with aria-busy through a held retry, recovery hands focus to Subject, and the member can be picked. B1 is below. |
| Real empty results keep their copy | Met | Probe P2 (real hook) and the PR's own test |
| Submit gated while the subject list is unknown, in the UI and the handler | Met in the code. The PR's tests pin only the UI gate (N1). | P5: a stale failure disables File request and Enter files nothing. P6: a direct submit on the form files nothing (0 POSTs). |
| Initial loading mask | Met | P3: role=status "Loading content", the trigger is disabled, and there is no empty copy |
| Closes both duplicates | Met | Closes #3857 and Closes #3858. GraphQL closingIssuesReferences lists both. |
| EN/DE/FR | Met; one EN style nit (N2) | userPickerFailed is at the same path in all three catalogs. DE and FR are idiomatic. |
| The regression fails on main | Met | The PR suite on main's sources (the dialog and the three catalogs from b77a452ef) gives 3 failed and 5 passed. At the head it gives 8/8. My probe on main fails 6 of 7; only P2, the empty-directory control, passes. |
B1 (blocking): a failed refresh lists the open picker as "No matching members." and drops focus on Escape
Path. The Subject picker is open on a loaded directory, and the directory is re-read. The re-read can come from a member backend hint, since the key is backendKey(org, MEMBER_HINT_ENTITY, 'picker'), or from any invalidation. The re-read then fails: a 503, which retryAdaptedRead retries and which then settles in error.
At this head:
options={membersUnavailable ? [] : memberOptions}empties the list under the open popover.- The listbox now reads "No matching members." (0 options), directly under the "Unable to load members." alert. This is probe P4:
listbox=open options=0 emptyCopy=true. disabledalso flips on the trigger while its popover is open. Escape then returns focus to a disabled button, and focus lands onbody(P4b).
On main, the same path keeps listing the last answer (react-query keeps data), with no empty copy. A P4 variant on main's sources, which waits for the 5 reads instead of the alert, shows options=1 emptyCopy=false. So this path is a regression this PR introduces. It breaks the contract's "never 'No matching members.'" for a failed read, and it breaks focus safety.
Repair sketch (b1-open-picker-sketch.diff, 3 lines; submit stays gated by the unchanged hasAvailableSubject):
- options={membersUnavailable ? [] : memberOptions}
- disabled={!members.isSuccess || membersUnavailable}
- emptyText={t(
- 'dataSubjectRequests.dialogs.fileRequest.userPickerEmpty',
- )}
+ options={memberOptions}
+ disabled={members.data === undefined}
+ emptyText={
+ membersUnavailable
+ ? t('dataSubjectRequests.dialogs.fileRequest.userPickerFailed')
+ : t('dataSubjectRequests.dialogs.fileRequest.userPickerEmpty')
+ }With the sketch, the PR suite and my probe pass 15/15:
- P4: the open list keeps the last answer under the alert.
- P4b: Escape returns focus to Subject.
- P1–P3, P5 and P6 stay green.
- Selection: a stale selection keeps its label, but File request stays disabled until a successful re-read that still contains the member.
To close B1, the fix has to pass P4 and P4b, in my probe or in an equivalent test in the PR.
Non-blocking
- N1. The handler gate is not pinned. Removing
|| !hasAvailableSubjectfromonSubmitleaves the PR suite at 8/8. The Enter press in the PR's test reaches only the disabled submit button, never the handler. My P6 (fireEvent.submit(form)during a stale failure) fails on that mutant and passes at the head. Please add a test like it. - N2. EN copy. "Unable to load members." is the only "Unable to load" string in
en.yml, against 46 "Couldn't load …" strings (for example "Couldn't load your chats."). Consider "Couldn't load members." DE and FR follow their catalogs. - N3. Shared helper.
membersUnavailablere-derivesreadStateOf(app/lib/backend/read-state.ts,unavailable || stale) by hand. The truth table is the same for this read, but the helper exists for exactly this case, and the sibling fixes (fix(platform): show a failed passkey list read with a retry #4300, fix(platform): recover automation run detail read failures #4288) use it. ItsfailureCountalso works as thefailureKey.
How I verified
- Runner: installed Node v24.21.0 and vitest 4.1.11, jsdom
--project client --maxWorkers=1 --testTimeout=60000, on a detached worktree at the head. - Probe:
file-request-dialog.review-probe.test.tsx, untracked and not part of the PR. It drives the realuseOrgMembersForErasurePicker→useBackendQuery→ react-queryuseQuery→ adapter row →backendFetchpath, plus the real erasure mutation, againstsyntheticBackend()withretryDelay: 0. - Results:
- PR suite at the head: 8/8.
- PR suite on main's sources: 3 failed and 5 passed.
- Probe at the head: 5/7 (P4 and P4b fail).
- Probe on main's sources: 1/7.
- Handler mutant: PR suite 8/8, probe P6 fails.
- Sketch: 15/15.
- Files: the probe, its observation logs, the mutant and sketch logs, and the sketch diff are attached to TALE-706.
Not run:
- a real browser (B1 is jsdom-only evidence);
- a live backend or PostgreSQL;
- a real SSE member hint;
- a screen reader;
- lint or type-check of the sketch;
- full suites.
CI: at 16:19Z, 16 checks were pending and 5 were skipping on this head. I did not rerun or cancel anything.
I made no merge, push, CI action or card move.
a3579c8 to
242f857
Compare
242f857 to
b312837
Compare
b312837 to
370c97a
Compare
370c97a to
85b00c5
Compare
A failed subject-directory read now shows the existing localized failure and retry state, while filing requires a successful directory response containing the selected member in both the button and the actual submit handler. Initial loading stays masked; successful empty reads keep their empty message.
The blocking stale-picker finding B1 is repaired: a failed refresh preserves the last directory options and keeps Subject enabled so Escape returns focus to it. A failed empty refresh uses the failure message. Filing stays blocked throughout stale failures, including a direct form submit. The original feature is replayed onto main with its patches preserved; the additional production repair changes only these three picker props. Existing query, permission, erasure and form-submit behavior remains guarded.
Validation: 17 focused dialog/importer tests, 35 locale tests and 75 existing error-policy guards pass (127 distinct cases). A distinct reviewer independently reran the 17 focused cases on the exact clean prepared source. Three new B1 regressions fail on the original production source and pass after repair; the new direct-submit regression fails when its existing handler gate is removed. Real Chromium with the actual hook and adapter reproduces the original failed-refresh loss of options/focus and confirms retained options, Escape focus, retry recovery and zero stale-submit writes after repair. Narrow DE/FR failure/picker labels also render without horizontal overflow; this is scoped label proof, not a full live locale-switch claim.
Full platform types, configured typed lint, format, manual coverage, SAST, conflict and commitlint checks pass. Whole Knip retains seven inherited main export findings addressed separately by #4625; no whole-repository or hosted green result is claimed. All seven native checks remain required. Browser transport is synthetic; live PostgreSQL/backend, SSE hints, screen reader and two-session behavior remain unobserved. The prior review comment is preserved; B1 and N1 are closed by source-bound proof, with no review approval manufactured or dismissed.
Closes #3857
Closes #3858
Current-main rebase
Replayed the previously accepted source
b3128375a9ef55a47b5db05f9705a135059b181eonto maind1373d84cd56972501403f62145ec52e6f65d44a, including the merged shared CI repair in #4625. The accepted feature payload and all current-main changes are preserved in one atomic commit. Configured commit and conflict checks pass; earlier behavioral proof remains recorded above. All seven native required checks and full merge-group validation remain required for this new source.Maintenance replay: preserves the accepted feature payload on current main 7d178ca. Includes the merged #4649 Knip cleanup and the exact independently accepted one-line shared CLI inventory repair from #4654 (252f0df), which is still pending native merge on main. The fixed suite inventory keeps its discovery and source/compiled phase guards. Existing feature proof is retained; no fresh full-feature/full-workspace test or hosted-green claim. Native required checks remain mandatory.