Repository navigation
Commit 179f6fe
fix(app-shell): the translation preview draws the groups of the schema the designer validates against (objectui#11765) (#11767)
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`, `flows` or `settingsCommon` no longer reads
"This bundle is empty", and a spec-valid bundle can reach full coverage.
A key the schema refuses (`validationMessages`, per-app `settings`) is
neither drawn nor counted.
## What changed
- `TranslationPreview`: the file-local `CATEGORIES` table is built from
`CATEGORY_DEFS`, one row per group of the per-app
`TranslationDataSchema`, in the schema's declaration order: `objects`,
`picklists`, `apps`, `messages`, `globalActions`, `dashboards`,
`datasets`, `pages`, `flows`, `metadataForms`, `settingsCommon`. The
`validationMessages` and `settings` rows are gone. The coverage
denominator is still `CATEGORIES.length`, which is now the schema's
group count.
- `CATEGORY_DEFS` is typed with `satisfies Record` over `keyof (typeof
TranslationDataSchema)['shape']` (a type-only import), so it is
exhaustive in both directions at compile time.
- The nested key-count row reads the singular for one key.
`engine.translationPreview.keyCount` is split into `keyCountOne` and
`keyCountOther`, the One/Other convention this table already uses
(`engine.datasetPreview.measureOne`, `engine.screenPreview.hiddenOne`).
- `metadata-admin/i18n.ts`, both `ENGINE_STRINGS_EN` and
`ENGINE_STRINGS_ZH`: five heading rows added (Picklists / 选项列表, Datasets
/ 数据集, Pages / 页面, Flows / 流程, Settings UI / 设置界面); the
`category.validationMessages` and `category.settings` rows removed;
`keyCount` replaced by `keyCountOne` + `keyCountOther`. The removed rows
had no other reader: `git grep` at HEAD finds neither key anywhere,
against 4 files that still name the kept `category.objects` row.
- The module docblock says which schema the categories follow, that a
refused key is neither drawn nor counted, and how `messages` (flat), the
nested groups and `settingsCommon` are read.
- Tests beside them: the new pin file
`TranslationPreview.categories-11765.test.tsx`;
`adminPreviews.i18n-10862-s2.test.tsx` lists the eleven headings, reads
`keyCountOne` and the `2/11 (18%)` coverage;
`TranslationPreview.globalActions-11755.test.tsx` reads `keyCountOther`.
- `.changeset/11765-translation-preview-categories.md`, `patch` on
`@object-ui/app-shell`.
- All six files are on the amended claim surface (comment 6032111854).
No export, prop, type member or language-pack key is added:
`CATEGORY_DEFS`, `CategoryDef` and both dictionaries are module-local,
and the package entry re-exports neither `TranslationPreview` nor the
dictionary module. Measured on the emitted declarations as well: `tsc
--declaration --emitDeclarationOnly` over the package at HEAD emits
`TranslationPreview.d.ts` with only the unchanged `TranslationPreview`
signature, and none of the 501 emitted declaration files names
`CATEGORY_DEFS`, `keyCountOne` or a new heading row. So `Clause-②: no`
holds.
## Zone 2 hypotheses, measured (objectui `d50f724`, installed
`@objectstack/spec` 17.7.0)
1. **Which schema the designer applies: the per-app one.**
`clientValidation.ts`'s `LOADERS.translation` binds
`TranslationItemSchema`. Its shape keys are the eleven
`TranslationDataSchema` groups plus `locale`, `name`, `label` and the
seven envelope keys. Only `PlatformTranslationDataSchema` declares
`settings`, so `settings` goes. `TranslationDataSchema` refuses
`validationMessages` and `settings` with `unrecognized_keys`, and so
does the designer; the pin file asserts both refusals through
`validateMetadataDraft('translation', …)` before it mounts them.
2. **Flat or nested.** The probe
`TranslationDataSchema.shape[group].safeParse({ probe: 'x' })` succeeds
for `messages` alone, the one record of strings. `picklists`,
`datasets`, `pages` and `flows` are records of strict nodes and read
nested, like `objects`. `settingsCommon` is one strict object, not a
record; its only member today is `sourceLabels` (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 beside `sourceLabels`, and the
count means what it means on every other card. A `settingsCommon: {
sourceLabels: {} }` counts 1 and reads `{0 keys}`, as an empty object
node does on any nested card.
3. **Derive or pin: pinned, two ways.** The schema's shape can supply
the keys and their order, but not a group's icon or its heading row in
two languages. A list derived at runtime would draw a group the spec
adds with an echoed key for a heading, or would need a renderer-side
fallback, which is the tolerance AGENTS.md #0.1 rules out. So the rows
stay hand-written, and drift is made loud instead. `satisfies Record`
fails 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 with `Object.keys(TranslationDataSchema.shape)` at
runtime.
4. **Rows in both dictionaries.** `pnpm check:i18n-designer-parity`, the
parity gate over these two tables, is green. The zh case of
`adminPreviews.i18n-10862-s2.test.tsx` reads each new heading as a real
zh row (not the echoed key, not the en row).
5. **The plural.** Folded in, as the card allows. With the five new
nested groups, a node of one key (`home: { label }`) is the common case.
## Measured before the change
The committed pin file was run against BASE's `TranslationPreview.tsx`
and `i18n.ts`. Both were checked out from `d50f724` into the worktree
under a trap, then restored from HEAD. The restore was proven by blob ==
HEAD for both files and an empty `git diff HEAD`. Result: `Tests 15
failed | 2 passed (17)`.
- A bundle of only `picklists`, `datasets`, `pages`, `flows` or
`settingsCommon` rendered the empty-bundle message, where the pin
expects none. Each bundle is spec-valid, and `validateMetadataDraft`
accepts each one. That is the card's reach, reproduced for all five
groups.
- A bundle of only `messages` or `globalActions` read `1/8`, not `1/11`.
The full bundle never reached `11/11 (100%)`. The bundle with the
refused groups drew 8 headings, not 11.
- The one-key nodes of `objects`, `apps`, `dashboards` and
`metadataForms` read `{1 keys}`.
- Green: the fixture-coverage test and the `messages` control.
## Pins: `TranslationPreview.categories-11765.test.tsx` (17 tests)
Every spec-valid fixture is parsed by the installed
`TranslationDataSchema` and passed through the designer's own
`validateMetadataDraft('translation', …)` (asserted `{ ok: true, issues:
[] }`) before it is mounted. Expected groups, order and denominator are
read from `TranslationDataSchema`; headings and count rows are read back
through `t` and `tFormat`.
1. Every group the spec declares has a fixture in the file.
2. One card per schema group, in schema order; the denominator is the
schema's group count.
3. to 13. One test per group, the five new ones included: a bundle of
only that group reads non-empty, shows the group's card with its sample
row, and reads `1/11`.
14. A bundle carrying every group reaches `11/11 (100%)`, and no card
reads empty.
15. Refused groups: `validationMessages` and `settings` are refused by
the spec and by the designer, are not drawn, and are not counted.
16. A nested node of one key reads `keyCountOne`, of two keys
`keyCountOther`.
17. Control: the `messages` card renders `"Bienvenue"` as before.
## Ablations
All on the committed fix `7da7729`, through objectstack's
`scripts/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 empty `git diff HEAD`). The test imports
`./TranslationPreview` relatively, so it is source-resolved and needs no
build.
Each count below was predicted before its run, and each matched.
- **A: `validationMessages` back in the table**, inserted ahead of the
`dashboards` row. The anchor went x1 to x0 and the blob `06de276d84d6`
to `81562e4424ae`. 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 --noEmit` exited 2 with `TS2353:
Object literal may only specify known properties, and
'validationMessages' does not exist in type 'Record...` over the eleven
group keys. Restored: blob == HEAD and `git diff HEAD` empty.
- **C: the `pages` row deleted.** The anchor went x1 to x0 and the blob
`06de276d84d6` to `9da6d764abdf`. Result: `Tests 15 failed | 2 passed
(17)`. As on BASE, the pages-only test rendered the empty-bundle
message. `tsc --noEmit` exited 2 with `TS1360 ... does not satisfy the
expected type` and `Property 'pages' is missing`. Together, A and C show
that `satisfies` holds in both directions. Restored the same way.
- **D: the singular row unread.** The head `n === 1 ?
'engine.translationPreview.keyCountOne' : ` was deleted, and the blob
went `06de276d84d6` to `d146e8c54a23`. Result: `Tests 7 failed | 10
passed (17)`: the six groups whose fixture node has one key, plus the
plural pin. Restored the same way.
- The first attempt at A and D was refused by the tool as a no-op,
because the anchor overlapped the replacement, so the counts could not
move. The tool restored the file (blob == HEAD), and both ablations were
re-run with non-overlapping anchors. The results above come from the
re-run.
## Gates (run from the worktree root)
All on HEAD `7da7729`, the only commit. Heavy runs went through
objectstack's `os-verify-lock.sh`. Wall-clock figures are from a shared
box.
| command | exit | verdict line |
|---|---|---|
| `pnpm exec vitest run packages/app-shell/` | 0 | `Test Files 1056
passed, 1 skipped (1057)`, `Tests 10338 passed, 9 skipped (10347)` |
| `pnpm exec vitest run --maxWorkers=2` on the pin, the objectui#11755
pin, `adminPreviews.i18n-10862-s2`, `emptyCollection-8526`,
`clientValidation.optOuts`,
`StudioDesignSurface.standardNavEntries-11201` | 0 | `Test Files 6
passed (6)`, `Tests 121 passed (121)` |
| `pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...'
build` | 0 | dependency closure built for the type-check |
| `pnpm --filter @object-ui/app-shell type-check` | 0 | `tsc --noEmit &&
tsc -p tsconfig.test.json`; `--listFilesOnly` on the test project lists
the new pin |
| `pnpm exec eslint --format json` on the five touched `.ts` / `.tsx`
files | 0 | 5 files, 0 errors, 0 warnings |
| `pnpm check:i18n-designer-parity` | 0 | `Every en row has a zh row,
and every shared row carries the same placeholders.` |
| `pnpm check:i18n-keys` | 0 | `Every in-scope call-site key resolves
against the en pack (3297 keys), …` |
| `pnpm check:i18n-drift` | 0 | `No designer-table en value changed in
this range.` (designer table: 7 keys added, 3 removed) |
| `pnpm check:i18n-dead-keys` (a report) | 0 | no
`engine.translationPreview.*` candidate; the designer leg is live (it
lists other `engine.*` keys) |
| `pnpm check:control-bytes` | 0 | `check-control-bytes: OK` |
| `pnpm check:test-path-roots` | 0 | `check-test-path-roots: OK` |
| `pnpm check:changeset-claims` | 0 | `No pending changeset names a file
this change touches.` |
| `pnpm check:pending-changeset-literals` | 0 | `No test source names a
pending changeset.` |
| `pnpm check:new-line-citations` | 0 | `0 new citation(s), enforcement
report-only` |
| `pnpm check:spec-symbols` | 0 | `none renders an instruction its
anchor card can no longer receive.` |
| `node scripts/check-changeset-presence.mjs` | 0 | `5 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.mjs` | 0 | each
OK |
Lint 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.js` enables no type-aware linting: it has no
`projectService` and no `parserOptions.project`, and a control grep on
`rules` in the same file hits 13 times. Third, no rule under
`eslint-rules/` reads another file from disk. So this diff cannot move
an untouched file's verdict. The repo-wide `pnpm lint` is CI's.
Not run locally:
- `check:readme-exports`: no export changed, which the declaration emit
above measures.
- The `apps/console` project: no console file is touched.
The full farm is CI's.
## Acceptance notes
- `nodeLabel` stays `globalActions`-only. The four new record groups
(`picklists`, `datasets`, `pages`, `flows`) declare `label` on their
nodes but read their key count, as `objects`, `apps`, `dashboards` and
`metadataForms` already 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.
- The preview reads three keys that the designer's schema refuses,
through `d.data ?? d`, `d.language` and `d.description`. Measured:
`TranslationItemSchema` answers `unrecognized_keys` on `data`,
`language` and `description`. The console preview gallery's
`translation` sample (`apps/console/src/preview-samples.ts`) is written
in that refused record form, and `preview-samples-spec-valid.test.ts`
already 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_01CGZy1BGCjdN5cXqL9cnvB8`
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1bc17da commit 179f6fe
6 files changed
Lines changed: 261 additions & 40 deletions
File tree
- .changeset
- packages/app-shell/src/views/metadata-admin
- previews
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1626 | 1626 | | |
1627 | 1627 | | |
1628 | 1628 | | |
| 1629 | + | |
1629 | 1630 | | |
| 1631 | + | |
1630 | 1632 | | |
1631 | 1633 | | |
1632 | | - | |
1633 | 1634 | | |
1634 | 1635 | | |
1635 | | - | |
| 1636 | + | |
| 1637 | + | |
| 1638 | + | |
1636 | 1639 | | |
| 1640 | + | |
1637 | 1641 | | |
1638 | 1642 | | |
1639 | 1643 | | |
1640 | 1644 | | |
1641 | | - | |
| 1645 | + | |
| 1646 | + | |
1642 | 1647 | | |
1643 | 1648 | | |
1644 | 1649 | | |
| |||
4645 | 4650 | | |
4646 | 4651 | | |
4647 | 4652 | | |
| 4653 | + | |
4648 | 4654 | | |
4649 | 4655 | | |
4650 | | - | |
4651 | 4656 | | |
4652 | 4657 | | |
4653 | | - | |
| 4658 | + | |
| 4659 | + | |
| 4660 | + | |
4654 | 4661 | | |
| 4662 | + | |
4655 | 4663 | | |
4656 | 4664 | | |
4657 | 4665 | | |
4658 | 4666 | | |
4659 | | - | |
| 4667 | + | |
| 4668 | + | |
4660 | 4669 | | |
4661 | 4670 | | |
4662 | 4671 | | |
| |||
Lines changed: 177 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
70 | | - | |
| 70 | + | |
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
83 | | - | |
| 83 | + | |
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
0 commit comments