Repository navigation
feat(spec,cli): positions and permission sets are translatable — positions and permissions in the translation bundle - #25701
Conversation
…eta translators Claude-Session: https://claude.ai/code/session_01AwcSGXmnoviawmFu9pGgNZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwcSGXmnoviawmFu9pGgNZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwcSGXmnoviawmFu9pGgNZ Co-authored-by: Claude <noreply@anthropic.com>
…ctness and liveness counts Claude-Session: https://claude.ai/code/session_01AwcSGXmnoviawmFu9pGgNZ Co-authored-by: Claude <noreply@anthropic.com>
…n sets into zh-CN The new walk counts them; without these, check:i18n-coverage grows 101 -> 109 (crm) and 320 -> 349 (showcase). Claude-Session: https://claude.ai/code/session_01AwcSGXmnoviawmFu9pGgNZ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 151 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c5a5bbe05cdc2b19aacce1cbd75c0102fe2dadb0 && git checkout c5a5bbe05cdc2b19aacce1cbd75c0102fe2dadb0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b1790841bb0bf2c7c696e858de51e8105704471a 5ec0b745fd6c34ec295b1d97bfb25399366e69c4 && git checkout -B drift-repro b1790841bb0bf2c7c696e858de51e8105704471a && git merge --no-ff 5ec0b745fd6c34ec295b1d97bfb25399366e69c4
node scripts/docs-audit/affected-docs.mjs --json b1790841bb0bf2c7c696e858de51e8105704471a
|
Claude-Session: https://claude.ai/code/session_01AwcSGXmnoviawmFu9pGgNZ Co-authored-by: Claude <noreply@anthropic.com>
…y; lint leg Claude-Session: https://claude.ai/code/session_01AwcSGXmnoviawmFu9pGgNZ Co-authored-by: Claude <noreply@anthropic.com>
席位核对ACCEPT · #25652 ·
Generated by Claude Code |
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS
Generated by Claude Code |
…ssions groups, at an objectstack pin that declares them (objectui#13478) (#13485) <!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DSu9uoz6bLHskQmxnyBE86T)_ Fixes #13478 Clause-②: yes Metadata-protocol: none objectstack's `Console Pin Gate` is red on main because app-shell no longer type-checks against objectstack `47af3dde42` (#25701). That change added the `positions` and `permissions` groups to the per-app `TranslationDataSchema`, and `TranslationPreview`'s `CATEGORY_DEFS` is exhaustive over that schema's shape by `satisfies`. This PR adds the two rows and moves objectui's own pin to `47af3dde42`, so objectui type-checks against the same spec. Once it merges, objectstack can move its `.objectui-sha` past the merge commit (objectstack's own card names this one as its blocker). ## What changed - **`TranslationPreview.tsx`**: two `CATEGORY_DEFS` rows, in the schema's order (after `flows`, before `metadataForms`): `positions` with `Users` and `permissions` with `ShieldCheck`, the icons `PositionPreview` and `PermissionPreview` already draw. Both are nested groups (`flat: false`), so each card counts the group's top-level keys and samples them with their inner key count, as `objects` and `flows` do. - **`metadata-admin/i18n.ts`** (the designer table, the only pack these keys live in, `en` and `zh`): `engine.translationPreview.category.positions` = "Positions" / "岗位" and `engine.translationPreview.category.permissions` = "Permission Sets" / "权限集", the words the same table already uses for the two metadata types. - **Tests**: `TranslationPreview.categories-11765.test.tsx` gets a spec-valid fixture for each new group (its first case requires one per schema group). `adminPreviews.i18n-10862-s2.test.tsx` lists the two headings among the rendered locale sites and reads coverage over 13 groups (`2/13 (15%)`, was `2/11 (18%)`). - **Changeset** `.changeset/13478-translation-preview-positions-permissions.md`: `@object-ui/app-shell` patch. ## The pin - `.objectstack-sha` moves from `e649c2d91e` (the 18.1.0 release commit) to `47af3dde42bb4e77fd92025605b89b4a473295a4`. REST compare `e649c2d91e...47af3dd`: `status: ahead`, `ahead_by: 51`, `behind_by: 0`; the bump script also reported "(on origin/main)". It is pinned at the card's floor, not at objectstack main's head: the four commits after it on objectstack main are left for the next bump. - Path: `scripts/bump-objectstack.sh 47af3dd --no-commit` (built and packed the 7 packages), then `pnpm install`. - What moved besides the sha: three packed manifests in `.objectstack-pin/` (`core`, `lint`, `spec`), each only `zod` `^4.6.5` to `^4.6.6` (#25657). The lockfile already resolves `zod@4.6.7`, so **`pnpm-lock.yaml` is unchanged** and `pnpm install --frozen-lockfile` exits 0. - **Why the rows and the pin land together** (the seat's hypothesis; it held). At `e649c2d91e` the schema declares neither group: `grep -c '^ positions: z.record'` over that commit's `packages/spec/src/system/translation.zod.ts` reads 0, and 1 at `47af3dde42`; the same for `permissions`. Under `satisfies`, an extra key in an object literal is TS2353 "Object literal may only specify known properties" (probed with this repo's tsc 6.0.3 on a two-key `Record`). So the rows alone fail at the old pin, and the pin alone fails as the gate does. ## Clause-② reading (the claim asked for it) The claim declares `yes` because the seat could not tell whether `metadata-admin/i18n.ts` reaches app-shell's published entry, and the two lines above are copied from the claim. Measured on the built declaration closure (`packages/app-shell/dist`, walking the relative imports from `index.d.ts`, the only `exports` entry with types): - The closure holds 182 `.d.ts` files. `views/metadata-admin/i18n.d.ts` **is** one of them (the positive control), but its face is `t(key: string, …)`, `tFormat(key: string, …)` and the other helpers: the key table is not exported, and no key is a type. - Closure files that carry the string `translationPreview`: **0**. `previews/TranslationPreview.d.ts` is not in the closure at all. So the diff widens no published accept set and no public face of app-shell, and the reading is `no`. The declaration is the seat's, so the lines stay as claimed. ## Verification (local, this worktree, at the new pin) - `pnpm exec turbo run build --filter=@object-ui/console... --concurrency=2` (under the verify lock, 6144 MB heap): 35 of 35 tasks, including `@object-ui/app-shell`'s `tsc` and the console's vite build, SDUI manifest and plugin tsc. That is the build the Console Pin Gate runs, here at objectui's own pin. - `pnpm exec vitest run packages/app-shell/src/views/metadata-admin/ packages/types/` (under the lock): 980 files passed; 17091 tests passed, 1 skipped. - The three TranslationPreview test files: 63 of 63. Reverse check: with the two rows removed from `TranslationPreview.tsx` (on-disk count 0, back to 2 after the restore), the `categories-11765` file goes red on its order-and-coverage case and on every per-group case, the two new groups included. - Gates, each exit 0: `check:i18n-keys`, `check:i18n-designer-parity`, `check:i18n-dead-keys`, `check:i18n-drift`, `check:installed-pin-claims`, `check:spec-symbols`, `check:icon-record-names`, `check:control-bytes`, `check:new-line-citations` (0 new), `check-changeset-presence`, `check-changeset-no-major`, and `eslint` on the four changed source files. - `check:spec-floors`: exit 1 with the same two `floor-too-low` findings (`@object-ui/auth` `MEMBERSHIP_ROLE_EXTERNAL`, `@object-ui/types` `navItemVisibleDataNeedsDataSource`) that #13472 recorded as pre-existing on main. This diff touches neither package. The gate runs on push and schedule with `--cross-check`, which is its measurement, and that run is green on main `cd70b78`. NOT MEASURED locally (left to CI): the rest of the workspace `vitest` shards, `lint` and `type-check` across all packages, and e2e. ## Acceptance notes - objectui#13477 (the label render of the same objectstack change) is a different defect; it remains open and nothing here touches it. - [x] Adds no product-specific widget, dialog, hook or string (see AGENTS.md › Product-neutral renderer) Implemented by the ui lane card thread, session `https://claude.ai/code/session_01Usjyy1hT9FNxZHea9u7drp`; claim on the card, comment 6391374949. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Usjyy1hT9FNxZHea9u7drp --- _Generated by [Claude Code](https://claude.ai/code/session_01Usjyy1hT9FNxZHea9u7drp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #25652
Clause-②: yes (widening)
Metadata-protocol: added
Ruling-ref: none; triage first grade 6385078248 (a protocol addition under the standing order, p1)
What
A translation bundle can now carry positions and permission sets:
Before, the same
enentry in a Chinese-authored app (hotsrm#22) failedobjectstack validatewithUnrecognized key(s) on this locale of the translation bundle: positions, so position and permission-set labels stayed in the authoring language while every object, app and flow label beside them was translated, andos i18n check --stricthad nowhere to report the gap.@objectstack/spec(system/translation.zod.ts): two new groups on the shared per-app shape, so the bundle entry (TranslationData), the registeredtranslationitem and the platform bundle all accept them. Each is keyed by the item'snameand closed atlabel/description(strict: any other key is refused;name/titleare refused with a message pointing atlabel, andposition/permission/permissionSetswith a message pointing at the groups). The group for permission sets is spelledpermissions, as the stack spells the collection.@objectstack/spec(system/i18n-resolver.ts):translatePositionandtranslatePermissionSet, registered inMETADATA_DOCUMENT_TRANSLATORS.TRANSLATABLE_METADATA_TYPESis derived from that table and@objectstack/restreads it, soGET /meta/positionandGET /meta/permission(list and item, both transports) are now served in the request's locale. These two lists are what the console'sposition-refandpermission-refpickers draw. A key the bundle does not carry leaves the authored string, and an untouched document keeps its reference.@objectstack/spec(liveness ledger):translation/positionsandtranslation/permissionswere added aslive, with drilledlabel/descriptionchildren. They were seeded together with their reader (the datasets precedent), never declared first and wired later.@objectstack/cli:os i18n extractwalkspositions[]andpermissions[](walkSecurityCatalog), andos i18n check/os lintcoverage report a gap in each group's own bucket (i18n/missing-position,i18n/missing-permission). Every key is recorded withpushOptional, so a string nobody wrote is never required to have a translation.@objectstack/lint:translation-target-unknownresolves eachpositions.NAMEkey against the stack'spositions[]names and eachpermissions.NAMEkey against itspermissions[]names, sibling packages' declarations included. An unknown name is anerrorwith the nearest declared name; the audience anchorseveryone/guestalways resolve.os explain translation-target-unknownnames both legs.scripts/fixtures/i18n-walk-parity/every-group.stack.json: authors one member of each new group, socheck:i18n-walk-paritysees both walked.packages/runtimemeta-list projection census:permission/permissionsjoined the list axis, andposition/permissionjoined the item axis. The census requires every translatable type to be a cell. The reference-moves block pins the zh-CN label on both reads.examples/app-crm,examples/app-showcase: zh-CN translations for their 5 + 19 positions and permission sets. The new walk counts them, and without thesecheck:i18n-coveragegrew 101 → 109 (crm) and 320 → 349 (showcase). The fix was to translate them, not to raise the ratchet.protocol/kernel/i18n-standard.mdxandui/translations.mdxnow include the new groups. They also include the missingpicklists/picklist, which those same sentences claimed exhaustively. The leg table ini18n-standard.mdxnames the two newtranslation-target-unknownlegs.Acceptance notes
/meta/positionand/meta/permission, so the one-line registration in the translator table is the overlay for those two surfaces, and the runtime census had to learn the two cells. Anywhere else the console renders a position or permission-set label from another source still belongs to objectui's card.@objectstack/lint:translation-target-unknownresolvespositions.NAME/permissions.NAMEagainst the declared names (anchorseveryone/guestalways resolve); an unknown name is an error. Added in patch round 1, after the dev reported the silent orphan as a finding.!in the title and no(narrowing)arm, so no full dogfood run is owed.Verification
Patch round 1, at head
5ec0b745f(touches onlypackages/lint, the changeset and the docs):@objectstack/linttest161 files / 7274 tests,typecheckexit 0;dispatch-gates --commands129 families, all exit 0,--ran0 NOT-MEASURED, 0 UNRUN. Ablation: replacingif (known.has(name)) continue;withcontinue;failed 2 of the 5 new tests, then was restored blob-identical.The package suites ran on
8ec6209f. The only commit after it, the final headf444163b7, changes the two example bundles and nothing else. Every result below is exit 0:@objectstack/spec:test21786 passed,test:repo1269 passed,typecheckpassed.@objectstack/cli: unit project 4821 passed,typecheckpassed.@objectstack/rest:test6406 passed,test:repo280 passed.@objectstack/runtime:test7080 passed,test:repo908 passed,typecheckpassed.@objectstack/lint(7269 passed),@objectstack/platform-objects(1352 passed) and@objectstack/metadata-protocol(21475 passed):test.example-crm/example-showcasetypecheckpassed onf444163b7.dispatch-gates --commandsatf444163b7derived 129 families. Each was run with its exit code captured before any pipe, and all 129 exited 0.--ranreports 129 run, 0 NOT-MEASURED, 0 UNRUN.check:generated --fixregenerated api-surface, export-origins, references, the strictness counts and the liveness counts.git diff HEADempty):position: translatePosition,turnsi18n-security-catalog.test.tsred (2 of 10 failed).walkSecurityCatalog(config, out);call turnsi18n-security-catalog-walk.test.tsred (5 of 5 failed).permissionSetskey underpositions.sales_repin the crm bundle failstscwith TS2353 on the rebuilt.d.ts, then restores.Body edited by the
domain:specseat 2 after patch round 1, as the dev proposed in6388798472, plus the lint bullet and the round's readings.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01AwcSGXmnoviawmFu9pGgNZ