Repository navigation
fix(app-shell): the translation preview draws the groups of the schema the designer validates against (objectui#11765) - #11767
Merged
objectstack-fleet[bot] merged 1 commit intoOct 7, 2026
Conversation
…a the designer validates against (objectui#11765)
The preview's CATEGORIES table had drifted from the per-app
TranslationDataSchema, the groups validateMetadataDraft('translation', ...)
judges a draft by (TranslationItemSchema). It omitted picklists, datasets,
pages, flows and settingsCommon, so a spec-valid bundle of only one of them
read as empty, and it listed the refused validationMessages and settings,
which both counted toward the coverage denominator.
The table is now one row per schema group, in the schema's order, typed
with satisfies against the schema's shape keys so a group added or dropped
upstream fails type-check. Coverage divides by that count. The headings of
the five new groups are rows of the designer's own string table (en and
zh); the two dropped rows are removed. The nested key-count row reads the
singular for one key (keyCountOne / keyCountOther).
Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ 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
|
objectstack-fleet
Bot
deleted the
claude/issue-11765-translation-preview-categories
branch
October 7, 2026 08:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11765
Clause-②: no
The translation designer's preview now draws one card per group of the schema the designer validates a bundle against, in that schema's order, and divides coverage by that count. A spec-valid bundle of only
pages,picklists,datasets,flowsorsettingsCommonno longer reads "This bundle is empty", and a spec-valid bundle can reach full coverage. A key the schema refuses (validationMessages, per-appsettings) is neither drawn nor counted.What changed
TranslationPreview: the file-localCATEGORIEStable is built fromCATEGORY_DEFS, one row per group of the per-appTranslationDataSchema, in the schema's declaration order:objects,picklists,apps,messages,globalActions,dashboards,datasets,pages,flows,metadataForms,settingsCommon. ThevalidationMessagesandsettingsrows are gone. The coverage denominator is stillCATEGORIES.length, which is now the schema's group count.CATEGORY_DEFSis typed withsatisfies Recordoverkeyof (typeof TranslationDataSchema)['shape'](a type-only import), so it is exhaustive in both directions at compile time.engine.translationPreview.keyCountis split intokeyCountOneandkeyCountOther, the One/Other convention this table already uses (engine.datasetPreview.measureOne,engine.screenPreview.hiddenOne).metadata-admin/i18n.ts, bothENGINE_STRINGS_ENandENGINE_STRINGS_ZH: five heading rows added (Picklists / 选项列表, Datasets / 数据集, Pages / 页面, Flows / 流程, Settings UI / 设置界面); thecategory.validationMessagesandcategory.settingsrows removed;keyCountreplaced bykeyCountOne+keyCountOther. The removed rows had no other reader:git grepat HEAD finds neither key anywhere, against 4 files that still name the keptcategory.objectsrow.messages(flat), the nested groups andsettingsCommonare read.TranslationPreview.categories-11765.test.tsx;adminPreviews.i18n-10862-s2.test.tsxlists the eleven headings, readskeyCountOneand the2/11 (18%)coverage;TranslationPreview.globalActions-11755.test.tsxreadskeyCountOther..changeset/11765-translation-preview-categories.md,patchon@object-ui/app-shell.CATEGORY_DEFS,CategoryDefand both dictionaries are module-local, and the package entry re-exports neitherTranslationPreviewnor the dictionary module. Measured on the emitted declarations as well:tsc --declaration --emitDeclarationOnlyover the package at HEAD emitsTranslationPreview.d.tswith only the unchangedTranslationPreviewsignature, and none of the 501 emitted declaration files namesCATEGORY_DEFS,keyCountOneor a new heading row. SoClause-②: noholds.Zone 2 hypotheses, measured (objectui
d50f724, installed@objectstack/spec17.7.0)clientValidation.ts'sLOADERS.translationbindsTranslationItemSchema. Its shape keys are the elevenTranslationDataSchemagroups pluslocale,name,labeland the seven envelope keys. OnlyPlatformTranslationDataSchemadeclaressettings, sosettingsgoes.TranslationDataSchemarefusesvalidationMessagesandsettingswithunrecognized_keys, and so does the designer; the pin file asserts both refusals throughvalidateMetadataDraft('translation', …)before it mounts them.TranslationDataSchema.shape[group].safeParse({ probe: 'x' })succeeds formessagesalone, the one record of strings.picklists,datasets,pagesandflowsare records of strict nodes and read nested, likeobjects.settingsCommonis one strict object, not a record; its only member today issourceLabels(the five settings source-badge labels). Its card reads it through the same nested branch: the card counts and samples its members (sourceLabels, with its inner key count). No special branch, because it needs none to read right, it stays right if the spec adds a member besidesourceLabels, and the count means what it means on every other card. AsettingsCommon: { sourceLabels: {} }counts 1 and reads{0 keys}, as an empty object node does on any nested card.satisfies Recordfails type-check when the spec adds a group without a row here, or when a row names a key the spec does not declare (ablations A and C below show both). The pin file compares the drawn headings, their order and the denominator withObject.keys(TranslationDataSchema.shape)at runtime.pnpm check:i18n-designer-parity, the parity gate over these two tables, is green. The zh case ofadminPreviews.i18n-10862-s2.test.tsxreads each new heading as a real zh row (not the echoed key, not the en row).home: { label }) is the common case.Measured before the change
The committed pin file was run against BASE's
TranslationPreview.tsxandi18n.ts. Both were checked out fromd50f724into the worktree under a trap, then restored from HEAD. The restore was proven by blob == HEAD for both files and an emptygit diff HEAD. Result:Tests 15 failed | 2 passed (17).picklists,datasets,pages,flowsorsettingsCommonrendered the empty-bundle message, where the pin expects none. Each bundle is spec-valid, andvalidateMetadataDraftaccepts each one. That is the card's reach, reproduced for all five groups.messagesorglobalActionsread1/8, not1/11. The full bundle never reached11/11 (100%). The bundle with the refused groups drew 8 headings, not 11.objects,apps,dashboardsandmetadataFormsread{1 keys}.messagescontrol.Pins:
TranslationPreview.categories-11765.test.tsx(17 tests)Every spec-valid fixture is parsed by the installed
TranslationDataSchemaand passed through the designer's ownvalidateMetadataDraft('translation', …)(asserted{ ok: true, issues: [] }) before it is mounted. Expected groups, order and denominator are read fromTranslationDataSchema; headings and count rows are read back throughtandtFormat.1/11.11/11 (100%), and no card reads empty.validationMessagesandsettingsare refused by the spec and by the designer, are not drawn, and are not counted.keyCountOne, of two keyskeyCountOther.messagescard renders"Bienvenue"as before.Ablations
All on the committed fix
7da7729, through objectstack'sscripts/ablation-replace.mjs(WRAP mode: it mutates, proves the anchor count and the blob moved, runs the command, restores, and proves blob == HEAD and an emptygit diff HEAD). The test imports./TranslationPreviewrelatively, so it is source-resolved and needs no build.Each count below was predicted before its run, and each matched.
validationMessagesback in the table, inserted ahead of thedashboardsrow. The anchor went x1 to x0 and the blob06de276d84d6to81562e4424ae. Result:Tests 14 failed | 3 passed (17). Every pin that reads the denominator or the headings went red:1/11 (9%)was not found,11/11 (100%)was not found, and 12 headings were drawn for 11 groups. The fixture-coverage test, the plural pin and the control stayed green. On the same mutation,tsc --noEmitexited 2 withTS2353: Object literal may only specify known properties, and 'validationMessages' does not exist in type 'Record...over the eleven group keys. Restored: blob == HEAD andgit diff HEADempty.pagesrow deleted. The anchor went x1 to x0 and the blob06de276d84d6to9da6d764abdf. Result:Tests 15 failed | 2 passed (17). As on BASE, the pages-only test rendered the empty-bundle message.tsc --noEmitexited 2 withTS1360 ... does not satisfy the expected typeandProperty 'pages' is missing. Together, A and C show thatsatisfiesholds in both directions. Restored the same way.n === 1 ? 'engine.translationPreview.keyCountOne' :was deleted, and the blob went06de276d84d6tod146e8c54a23. Result:Tests 7 failed | 10 passed (17): the six groups whose fixture node has one key, plus the plural pin. Restored the same way.Gates (run from the worktree root)
All on HEAD
7da7729, the only commit. Heavy runs went through objectstack'sos-verify-lock.sh. Wall-clock figures are from a shared box.pnpm exec vitest run packages/app-shell/Test Files 1056 passed, 1 skipped (1057),Tests 10338 passed, 9 skipped (10347)pnpm exec vitest run --maxWorkers=2on the pin, the objectui#11755 pin,adminPreviews.i18n-10862-s2,emptyCollection-8526,clientValidation.optOuts,StudioDesignSurface.standardNavEntries-11201Test Files 6 passed (6),Tests 121 passed (121)pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' buildpnpm --filter @object-ui/app-shell type-checktsc --noEmit && tsc -p tsconfig.test.json;--listFilesOnlyon the test project lists the new pinpnpm exec eslint --format jsonon the five touched.ts/.tsxfilespnpm check:i18n-designer-parityEvery en row has a zh row, and every shared row carries the same placeholders.pnpm check:i18n-keysEvery in-scope call-site key resolves against the en pack (3297 keys), …pnpm check:i18n-driftNo designer-table en value changed in this range.(designer table: 7 keys added, 3 removed)pnpm check:i18n-dead-keys(a report)engine.translationPreview.*candidate; the designer leg is live (it lists otherengine.*keys)pnpm check:control-bytescheck-control-bytes: OKpnpm check:test-path-rootscheck-test-path-roots: OKpnpm check:changeset-claimsNo pending changeset names a file this change touches.pnpm check:pending-changeset-literalsNo test source names a pending changeset.pnpm check:new-line-citations0 new citation(s), enforcement report-onlypnpm check:spec-symbolsnone renders an instruction its anchor card can no longer receive.node scripts/check-changeset-presence.mjs5 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)node scripts/check-changeset-no-major.mjs,check-changeset-overwrite.mjs,check-changeset-fixed.mjsLint is narrowed to the touched files, and the narrowing rests on three facts. First, the population is eslint's own result set for the five files: the JSON output counts 5, and none is ignored. Second,
eslint.config.jsenables no type-aware linting: it has noprojectServiceand noparserOptions.project, and a control grep onrulesin the same file hits 13 times. Third, no rule undereslint-rules/reads another file from disk. So this diff cannot move an untouched file's verdict. The repo-widepnpm lintis CI's.Not run locally:
check:readme-exports: no export changed, which the declaration emit above measures.apps/consoleproject: no console file is touched.The full farm is CI's.
Acceptance notes
nodeLabelstaysglobalActions-only. The four new record groups (picklists,datasets,pages,flows) declarelabelon their nodes but read their key count, asobjects,apps,dashboardsandmetadataFormsalready do. This is objectui#11755's named gap, now over four more groups. Not filed.engine.datasourcePreview.keyCount, a different preview's row in the same table, also reads{1 keys}for a single key. Polish; not filed.d.data ?? d,d.languageandd.description. Measured:TranslationItemSchemaanswersunrecognized_keysondata,languageanddescription. The console preview gallery'stranslationsample (apps/console/src/preview-samples.ts) is written in that refused record form, andpreview-samples-spec-valid.test.tsalready records that row as a mapping mismatch to settle. Outside this claim's surface; not touched and not filed.Session:
https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8Generated by Claude Code