Repository navigation
I18N-011: a refused write reads in the reader's language - #997
Conversation
describeDbError's five classified sentences (RLS, duplicate, missing row,
broken constraint, dropped connection) were English literals, so a German
family refused by RLS read English on a German screen. They are catalogue
keys now (dbError.*, en-US and the six full translations), and reach the
reader without touching any of the ~730 call sites:
- in the browser, LocaleProvider remembers the five sentences of its
locale after it commits (never during render, so hydration matches the
server's English; never on the server, which serves every reader), and
describeDbError answers in them;
- a sentence written on the server (a server action's { error }) is put
into the reader's language where it is shown: the toast, ActionError
and a form Field's error, with their own t;
- dbError joins ROOT_CHROME_SCOPE, beside toast, so every surface has it.
lib/supabase/errors.ts still imports nothing: a dozen tests load it in a
sandbox that refuses unlisted imports. Three e2e fixtures that mount
LocaleProvider from a source list now list it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Bounded I18N-011 source/test follow-up at47794826a3ae4fada65fe5b995e09805d5692620: describeDbError now returns localized strings, but localizeDbErrorText recognizes only English literals. A German refusal stored by ToastProvider (for example contacts-module's describeDbError(error) caller) cannot be retranslated after switching to French. English server strings do retranslate. This is a source-derived finding, not a runtime reproduction. Keep stable error identity through retained UI state, or otherwise make retained refusals follow the receiving locale without relying on a browser-global provider's last registration. Add a real mounted-provider test: create a refusal under German, retain it across a French locale change, and verify the same toast/field message becomes French. Include nested-provider/unmount isolation and preserve toast focus/queue/ARIA behavior. Current direct-registration/fresh-description tests do not cover this transition. Keep the acknowledged44 inline server-error sites and live German RLS browser proof open; no broad I18N-011 closure. Limit changes to this PR's owned source/tests, and coordinate the disclosed unpublished alternative with its owner before integration rather than duplicating that implementation. Report exact commit/tests and distinguish executed results from source assertions. |
A German refusal a toast or a field was still holding stayed German after the reader switched to French: localizeDbErrorText recognised only the English sentences. errors.ts now keeps an append-only map of every language the five sentences have been seen in (English, each catalogue a provider renders, each translator a display point uses), so a kept sentence is recognised whatever it was written in and shown with the receiving component's own t, not the provider that registered last. A mounted browser spec runs the real LocaleProvider, ToastProvider, Field and ActionError with React: a refusal written under German turns French in the same toast, field and ActionError when the reader switches, and a server's English follows each receiving provider, a nested one included, across the nested one's unmount. Red on 4779482, green here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Retained-refusal follow-up, at The gap, confirmed by execution: the new mounted spec fails on The change,
Executed (Chromium, local; no app server, every request answered by the fixture):
Toast focus, queue and ARIA behaviour are not changed by this PR, which only maps the message text. The existing toast suites ( Still open, as you said:
No broad I18N-011 closure. Your unpublished alternative Generated by Claude Code |
|
Source receipt for895ff41d6796d391750fda9ae05477229ae5aa0b: the retained German-to-French message case is repaired for current catalogues, and the mounted retained-state fixture meaningfully exercises Field/ActionError/toast. Keep work bounded to this candidate's repair; do not integrate or replace the unpublished alternative without its owner's coordination. One test follow-up for6100838353: the nested/unmount case supplies a fresh outer FR messages object on removal, re-running outer registration and potentially masking stale nested state. Keep that catalogue object stable; mount the nested German provider in a later commit, verify it registered last, then remove it without re-registering the outer provider and assert receiving-French output. Do not claim hydration or focus/queue cleanup from createRoot/pageerror checks. A uniqueness/substring-overlap guard for the35 static sentences would protect the identity-map assumption; current catalogues have no collision found. No broad I18N-011 closure: inline server-error sites and live RLS proof stay open. Report exact commit and synthetic fixture results; no private-work integration or production access. |
…rrent Review asked the nested-provider test to keep the outer catalogue object stable, so removing the nested German provider does not make the outer French one register again. With that, the stale state was real: on 895ff41, after the German provider left, describeDbError still answered in German, because the browser kept only the last registration. Registrations are now a stack. Each LocaleProvider's effect pushes its five sentences and its cleanup takes exactly that entry out again, so describeDbError answers in the latest provider still mounted, and in English when none is. errors.ts still imports nothing. Tests: - mounted spec: catalogues are one object for the page's life; a recorder shows the nested German provider registered last and the outer one did not register again on its removal; describeDbError and a new refusal are then French. It no longer claims hydration or toast focus/queue coverage. Red on 895ff41's errors.ts and with the provider's cleanup dropped. - unit: stack removal in either order and back to English; the toast harness runs effect cleanups as unmount instead of re-registering English; the 35 catalogue sentences (5 keys x 7 catalogues) each name one key and none sits inside another (fails on a planted overlap). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Nested-provider follow-up, at With the outer catalogue held stable, your suspicion was a real defect. On The change:
Executed,
Executed, unit:
Source assertions only, not executed:
Still open: the ~44 inline Generated by Claude Code |
|
Exact-head CI follow-up for964180c356628d6264a8a5a3b5d437057f18d7f9: CI38095082355 Core job114339229505 failed unit step11; DB/Mobile passed and E2E is still running. Failure cause is unknown from the accessible step metadata. The stable-catalogue/cleanup source repair addresses6101672848; please diagnose and repair this new exact-head unit failure in the same bounded lane without weakening the retained-state, stack-ownership or catalogue collision assertions. Report the failing assertion, repair commit and fresh checks. Prior895 green and local pass claims do not replace current-head evidence. Keep unpublished-work integration and broader I18N closure out of scope. |
…mports CI on 964180c (merged with main) failed tests/medications-fixture-module- graph.test.ts: tests/e2e/notification-email-preference.spec.ts, added on main by #989, stubs '@/lib/supabase/errors' with describeDbError and wroteNoRows only, and components/ui/input.tsx (Field) imports localizeDbErrorText from it on this branch. The stub now returns the text unchanged; that fixture is not about language. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Exact-head CI follow-up for The failing assertion (from the job log): Cause: an interaction with
The repair:
Fresh checks on
Not executed here: the full unit suite on this exact head. CI's core job runs it, and that result is the evidence for this head. Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
I18N-011:
describeDbError's five classified sentences were English in every locale.The defect
describeDbError(lib/supabase/errors.ts) classifies a refused database call and answers with one of five fixed English sentences:Only the caller's own
fallbackwas translated. With ~730 call sites in 175 files, a German family refused by RLS read English on an otherwise German screen.The fix
The audit's proposal was to return catalogue keys and migrate every caller with a ratchet. This reaches the reader without touching a single call site.
dbError.permission,.conflict,.notFound,.invalidand.networkare in en-US and all six full translations (de, es, fr, it, nl, pt). Each translation follows the catalogue's own register and its existing phrasing for "family admin"..networkreuses the existingsecurityPanel.errorNetworktranslations word for word.describeDbErroranswers in the reader's language. Each mountedLocaleProviderregisters the five sentences of its locale, anddescribeDbErroranswers in the latest one still mounted.windowexists. On the server, a module-level choice would be whichever request rendered last.lib/supabase/errors.tsitself. That file still imports nothing, because a dozen tests load it in a sandbox that refuses any import it does not list.{ error }arrives in English, because the server has no reader. A refusal kept in state since an earlier language arrives in that language.ActionErrorand a formField's error each recognise the five sentences, alone or inside a longer message, in any language they have been seen in. They show each one with their ownt.res.errorstraight totoastError.dbErrorjoinsROOT_CHROME_SCOPE, besidetoastand for the same reason:ToastProvidersits under the root provider on every surface, the public ones included. The cost is five sentences.LocaleProviderfrom a source list (display-clock,social-publish-consumers,social-scheduling-ui) listlib/supabase/errors.ts, which the provider now imports.tests/medications-fixture-module-graph.test.tsasks for exactly that.describeDbError's signature, its classification and its unclassified behaviour (the caller's fallback, or a codeless app-thrown message) are unchanged. On the server it still answers in English, so the existing tests that pin those English sentences against server code pass unchanged.Tests
tests/a-refusal-reads-in-the-readers-language.test.ts(unit)describeDbError:describeDbErroranddescribeActionErroranswer in German for one real error of each class, and the unclassified fallback is unchanged.localizeDbErrorTexttranslates an embedded sentence and leaves everything else alone. It does the same for a sentence kept from a language the reader has left.FieldandActionErrorrender the German sentence under a German provider and the English one under English.tests/a-refused-toast-reads-in-the-readers-language.test.ts(unit, using the toast harness froma-toast-you-can-still-reach-waits)LocaleProviderregisters only after commit: during its render,describeDbErroris still English. It takes the registration out in its effect's cleanup. On the server it never registers.tests/e2e/a-refusal-follows-the-readers-language.spec.ts(Chromium)LocaleProvider,ToastProvider,FieldandActionErrorwith React, from source, with the real de-DE and fr-FR entries. No app server is involved.describeDbErroranswers in French and a new refusal in the outer Field and toast is French.createRoot, and a cleanpageerrorlist says nothing about either. Hydration itself is not run anywhere: the unit test above only shows the provider registers after commit, not during render.Mutation-proven (each source change reverted alone, tests kept):
Field's translationActionError's translationdescribeDbError's routing to the browser's sentences4779482'serrors.ts)895ff41'serrors.ts)describeDbErrorstill answers GermanReview rounds
895ff41: a refusal kept on screen follows the reader. It added the sentence-to-key map and the mounted spec.964180c: the round-1 nested case handed the outer provider a fresh catalogue object on removal, which made it register again and hid stale nested state. With the catalogue held stable, the stale state is real: on895ff41, after the nested German provider left,describeDbErrorstill answered German. Registrations are now a stack the provider's effect cleanup unwinds. This round also added the 35-sentence overlap guard, and the spec no longer claims hydration or focus/queue coverage.964180c, this container)895ff41'serrors.ts: test 2)maintoo) / 0 / cleanWhat this does not reach
set…(res.error)sites in 35 client files render a server action's error in their own<p>instead of the toast,ActionErroror aField. Those stay English until the action translates it (64 of the 67 server-action files already callgetTranslations()) or the site renders through one of the three. This is the remaining part of I18N-011: this PR narrows the row, it does not close it.lib/ai/tools/*pass the sentence into the model's context, and the model answers in the reader's language anyway.No migration, configuration or dependency change.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4