Repository navigation
feat(i18n): publish TranslateFn as the one authority; app-shell and fields re-export it (objectui#8261) - #10703
Conversation
…ields re-export it (objectui#8261) The narrowed i18next `t` shape `(key: string, options?: Record<string, unknown>) => string` was declared byte-identically in app-shell's writeWarningToast and fields' file-size-guard. Per the maintainer's ruling on objectui#8165 (option A) the one declaration now lives in @object-ui/i18n and is published from its entry; both former declarations re-export it, the sibling toast modules and usePublishAllDrafts import it from there, and the TranslateFn row leaves the one-authority gate's KNOWN_COLLISIONS baseline. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL What stops it: two false sentences in published prose (CHANGELOG and README bytes); nothing in code.
Generated by Claude Code |
…bject-ui/i18n; make the published prose true (objectui#8261) managedByEmptyState.ts kept a local, unexported, byte-identical copy of the narrowed `t`, which left two published sentences false: the changeset's "the name now has one declaration" and the README's "the one place that type is declared". The copy now imports the one authority; the README says "the one authority for that type" (the same shape still exists under other names), and the changeset names writeWarningToast as the only app-shell module that declared it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgments
② Semver levelUnchanged from round 1 and still right: ③ Boundary flags
Implemented-by: VERDICT: FAIL What stops it: one false clause in the landing body file (H6 changeset bullet), exact replacement given in ①; nothing in code, changeset, README or CI. Generated by Claude Code |
Contract reviewServed-tier: ① Derived judgments
② Semver levelBody-only round; the tree is unchanged since round 2. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #8261
Clause-②: yes —
@object-ui/i18npublishes a new name,TranslateFnExecutes the maintainer's ruling on objectui#8165 (comment 5565867673, decision batch #72, option A): the narrowed i18next
tgets ONE authority, exported from@object-ui/i18n, and the two remaining declarations become type re-exports of it. Round 1 was verified at367e586e4. Round 2 (b235ae53d) applies the contract review's prose ruling and re-points the last local copy; its checks are listed separately below.Spelling note: GitHub strips tag-shaped fragments from bodies, so the options parameter's type is written
Record(string, unknown)here; the source spells it with angle brackets.What changed
packages/i18n/src/translateFn.ts(new):export type TranslateFn = (key: string, options?: Record(string, unknown)) => string;with a docblock naming it the one authority.packages/i18n/src/index.ts:export type { TranslateFn } from './translateFn.js';, next to the existingSafeTranslateexport.packages/app-shell/src/providers/writeWarningToast.tsandpackages/fields/src/widgets/file-size-guard.ts: the local declaration is gone. Each now doesimport type { TranslateFn } from '@object-ui/i18n'for its own uses andexport type { TranslateFn } from '@object-ui/i18n', so every existing import of the name from these two modules still resolves. FromwriteWarningToastthat isAdapterProvider; fromfile-size-guardit isFileField,ImageField,LocationFieldandnumberBadInput. None of their import paths change.packages/app-shell/src/providers/saveAdvisoryToast.tsandmetadataReadWarningToast.tsnow re-export from@object-ui/i18ndirectly, not throughwriteWarningToast. Their docblocks saidwriteWarningToastwas the owner, which this change makes false, so they had to be rewritten anyway. Pointing them at the real owner means an import resolves in one hop and no module in the chain claims ownership it lacks.packages/app-shell/src/preview/usePublishAllDrafts.tsandpackages/app-shell/src/utils/managedByEmptyState.ts: each local, unexportedtype TranslateFnis replaced byimport type { TranslateFn } from '@object-ui/i18n'(see H1).managedByEmptyState.tswas added in round 2, when the seat widened the file surface by this one file.scripts/__tests__/one-authority-per-exported-name-6273.test.ts: theTranslateFnrow is removed fromKNOWN_COLLISIONS. Its comment block is replaced by a "sat here" note in the form the other retired rows use. No line was added..changeset/8261-translatefn-i18n-authority.md:'@object-ui/i18n': minor(a newly published name),'@object-ui/app-shell': patch,'@object-ui/fields': patch. Nomajor..changeset/8165-translatefn-one-authority.md: prose reconciled with a dated note (H6). The frontmatter is byte-identical.packages/i18n/README.md: a shortTranslateFnsubsection (H6).After this change,
git grepoverpackages/*/srcfinds exactly 1 declaration ofTranslateFn(exported or local,typeorinterface):packages/i18n/src/translateFn.ts. The same pattern finds 4 on the basec659439b4, which is the control.H1: census on the base (
c659439b4)TranslateFndeclarations:packages/app-shell/src/providers/writeWarningToast.tspackages/fields/src/widgets/file-size-guard.tspackages/app-shell/src/preview/usePublishAllDrafts.tspackages/app-shell/src/utils/managedByEmptyState.tssaveAdvisoryToast.tsandmetadataReadWarningToast.tsre-exported the name fromwriteWarningToast, as the order stated.Neither local declaration is visible to the gate. Its
DECLARATIONpattern needs a leadingexport, and it counts exported type-level names only (bound 1 in its header). Both local declarations are rows in objectui#8203's table.usePublishAllDrafts.tsis re-pointed. It is in this order's file surface. The order also asked whether it is identical in shape. It is identical in type, but not byte-identical: the parameter is spelledopts. TypeScript does not compare parameter names when it checks a function type, so the signature ofusePublishAllDrafts(t: TranslateFn)is unchanged.packages/i18n/src/__tests__/perm-home-namespace-3546.test.tsxpins the textexport function usePublishAllDrafts(t: TranslateFn), which is kept.managedByEmptyState.tsis re-pointed (round 2). Round 1 left it alone because it was outside the file surface. The contract review then found that it made two published sentences false, and the seat widened the surface by this file. Before re-pointing, its declaration was checked against the authority with theexportprefix set aside. Both aretype TranslateFn = (key: string, options?: Record(string, unknown)) => string;, 79 bytes with the newline, sha256083c3a40d29b6f256a603bb907f3e4de032a4dfe5abcca6c199220172d9dea24. It is byte-identical, so no signature moves.The same shape under other names on the base:
TranslationFn(AiChatPage),TFn(CloudAiModelStatus),OrgTranslate(exported),Translate(packages/core/src/actions/recordDelete.ts),InboxTranslate(exported),ActivityTranslate,CommentsTranslate,SummaryTranslateandTimelineTranslate(exported). Some spell the second parametervarsorparams. These are objectui#8203's population. objectui#8203 is closednot_plannedand is not addressed here.H2: byte-identity, re-measured before re-pointing
On
c659439b4, the two exported declarations are the same line:This matches the card's
29f2f334…, 86 bytes on158d75bb3, so nothing has drifted. The new declaration in@object-ui/i18nis the same line, so re-pointing changes no call site's contract.H3: the home
@object-ui/i18n'spackage.jsondeclarestypesandexports['.'].typesas./dist/index.d.ts.tsconfig.jsonsetsrootDir: src, so the entry file ispackages/i18n/src/index.ts. The package has no stale second entry.@object-ui/app-shelland@object-ui/fieldsboth already list@object-ui/i18n: workspace:*independencies.@object-ui/i18n's only workspace dependency is@object-ui/core, which depends only on@object-ui/types, so there is no cycle. Nopackage.jsonchanged.turbo run build --filter='@object-ui/app-shell^...'(28/28 successful):TranslateFnpackages/i18n/dist/index.d.tsexport type { TranslateFn } from './translateFn.js';SafeTranslate2packages/i18n/dist/translateFn.d.tspackages/fields/dist/index.d.ts@object-ui/fields' faceFileField2H5: the gate, before and after, plus ablation
c659439b4(TranslateFnrows: 1)367e586e4(TranslateFnrows: 0)Ablation was run at
367e586e4after the commit. Every leg went throughscripts/ablation-replace.mjs(objectstack) in wrap mode: the anchor had to hit exactly once, the blob change was checked on disk, and the file was restored fromHEAD. The gate reads source from disk, so no build ordistis involved in legs A and B.file-size-guard.ts. The re-export line was replaced by the old declaration: anchor x1 to x0, replacement x0 to x1, blobca873659cf07to6e625184bee3. Gate exit 1 (Tests 1 failed, 10 passed). The collision is named:600bed9be040to38991d28dc31. Gate exit 1 (Tests 1 failed, 10 passed), which proves the row removal was owed:.d.ts. A probeexport const typeProbe8261: TranslateFn = (key: number) => String(key);was planted infile-size-guard.ts, thenpnpm --filter @object-ui/fields exec tsc --noEmitwas run. Exit 1 with exactly one error,error TS2322: Type '(key: number) => string' is not assignable to type 'TranslateFn'.This shows that the typefieldsresolves from@object-ui/i18nis the real narrowed shape, not ananyfrom an unresolved or stale declaration.HEADblob:ca873659cf07…forfile-size-guard.tsand600bed9be040…for the gate.git diff HEADwas empty. After the run: declarations 0, re-export 1, rows 0.H6: published prose
packages/i18n/README.mddid not list the package's exported types; its API section documents functions. Per AGENTS.md commandment Add automated testing infrastructure and CI/CD workflows #2, it gains a short### TranslateFn — typing a t you are handedsubsection with onetssnippet.Translate.check:readme-exportsjudgesTranslateFnasrealfrom@object-ui/i18n.tsc --strictagainst the builtpackages/i18n/dist/index.d.ts: exit 0. A negative control changingt('common.save')tot(42)gave exit 2 with TS2345.git grep -l TranslateFn c659439b4 -- .changesetfinds one file,.changeset/8165-translatefn-one-authority.md. This change makes two of its sentences false: thatwriteWarningToastis the one authority, and that the name is on no package's published face. Following the objectui#10533 form:⚠️ **Dated note, 2026-09-25 — the one authority has since moved to @object-ui/i18n — objectui#8261.**paragraph follows them;2ee28f1e1b449ccc2dd97fdcc6c2bc1fbefore and after.writeWarningToast, which its sibling toast modules re-export", because on the base, among app-shell's toast modules, onlywriteWarningToastdeclared the name;saveAdvisoryToastandmetadataReadWarningToastre-exported it from there. It also keeps "the name now has one declaration", which the count above re-derives. Its frontmatter (lines 1–5) has md5014eca5f59a0b36c1901fefa5467f47cat both367e586e4andb235ae53d.check:changeset-claimslists three other pending bodies that name a file this change touches:6349-types-internal-name-collisions-batch-1,8632-malformed-picklist-option-loudand9615-locale-count-ten. I read all three and none is falsified. Each describes its own change, and the README heading8632cites still exists.Round 2 checks (at
b235ae53d)turbo run build --filter='@object-ui/app-shell^...' --concurrency=2pnpm --filter @object-ui/fields type-check(tsc --noEmit && tsc -p tsconfig.test.json)error TSpnpm --filter @object-ui/app-shell type-check(same pair)error TSvitest runover the one-authority gate andpackages/app-shell/src/utils/declares no name twice outside the named, shrinking baseline. The whole run shows Test Files 35 passed, 1 skipped (36) and Tests 559 passed, 8 skipped (567); the lock run printedVERDICT command-exit 0. The skipped file isauditHistoryDisplay.dateOnlyZone-10183.test.ts, which is timezone-conditional and unrelated to this changenode scripts/check-control-bytes.mjspnpm check:new-line-citationsVERDICT new-cross-file-line-citations: 0 new citation(s)node scripts/check-changeset-presence.mjsgit merge-tree --write-tree HEAD 56b7fb4d5(freshmain)TranslateFnlines, so no merge was madeRound 1 tests and gates (all at
367e586e4)Heavy runs went through
os-verify-lock.sh, and the verdicts are its ownVERDICTlines. Timings are shared-box seconds.turbo run build --filter='@object-ui/app-shell^...' --concurrency=2VERDICT command-exit 0pnpm --filter @object-ui/i18n type-checkerror TSpnpm --filter @object-ui/fields type-checkerror TSpnpm --filter @object-ui/app-shell type-checkerror TSpnpm type-check:scripts(covers the gate file)error TSpnpm exec vitest runover the gate,packages/i18n/, app-shellproviders/,preview/,console/home/__tests__/,MetadataService.saveAdvisories,useMetadataClient.advisorySink, and the 28 fields test files naming afile-size-guardconsumerVERDICT command-exit 0node scripts/check-changeset-presence.mjscheck-changeset-no-major/check-changeset-fixedcheck-changeset-overwrite8165edit as its case 2 ("correcting … on purpose"), and both sides declare nothingpnpm check:new-line-citationsVERDICT new-cross-file-line-citations: 0 new citation(s)pnpm check:control-bytescheck:doc-fences/docs:check-links/check:readme-exportscheck:phantom-deps/check:unused-deps/check:unreferenced-sourcescheck:pending-changeset-literals/check:i18n-keys/check:esm-specifierscheck-governed-queue-guard --teston the 11 changed pathseslint --no-inline-config --format jsonon the 8 changed source filesno-explicit-anyinusePublishAllDrafts.ts; the base file has the same 3Declared narrowings, so what was left out is visible:
import type/export typeerase at build). The full app-shell and fields suites are CI's.eslint.config.jshas no type-aware linting: noprojectService, noparserOptions.project, and a control grep fortypescript-eslintin the same file fires. So this diff cannot move the verdict on any untouched file. Repo-widepnpm lintis CI's.check:doc-snippetswas NOT MEASURED locally. It needs almost the whole workspace built. The README snippet was instead compiled standalone against the built.d.ts, with a negative control, as shown under H6.check:readme-exportssaw 8 packages unbuilt. Their rows are unjudged locally. The@object-ui/i18nrows, includingTranslateFn, were judged.69a6fc1ce. The merge-tree was clean with zero overlapping paths. Round 2's fresh-main reading is in the table above.Acceptance notes
packages/core/src/actions/recordDelete.tsTranslatecan never point at@object-ui/i18n, because@object-ui/i18ndepends on@object-ui/core.needs:contract-review. This dispatch allowed no label write beyond the PR assignee, so the seat applied it; the seat removes it at landing, after a contract review PASS.fieldshalf is done by construction here. The ruling comment says that card closes on the gate's reading of theKNOWN_COLLISIONSentry, so objectui#8165 remains open for the seat to close.⛔ Left as draft on purpose: the seat flips it after review.
Generated by Claude Code