Uh oh!
There was an error while loading. Please reload this page.
fix(fields,i18n): localize manage_sharing and pin the curated capability set to PLATFORM_CAPABILITIES - #6323
Merged
os-support-ai merged 4 commits intoAug 25, 2026
Conversation
…ILITIES
`CURATED_CAPABILITY_LABELS` was a seven-member literal under a doc comment
claiming it mirrored `@objectstack/spec/security`'s `PLATFORM_CAPABILITIES`.
The spec grew an eighth member and the copy did not follow, so `manage_sharing`
fell through to the English label the `sys_capability` registry serves and
rendered untranslated in all ten packs beside seven siblings that localize.
The set now derives from `PLATFORM_CAPABILITIES`, applying the same dot ->
underscore transform the call site uses, and `capability.label.manage_sharing`
is authored in the ten packs and in the field widgets' provider-less defaults
map. `labelFor` gains a `defaultValue` so the window between a spec bump and a
translation degrades to the registry label instead of a raw i18n key.
KNOWN RED, deliberately not worked around: deriving turns the declaration into a
computed value, which `scripts/check-i18n-call-site-keys.mjs`'s `readVocabulary`
cannot read, so the `capability.label.` family moves to `enumerable: false` /
`external-vocabulary`. That drops `counters.enumerableFamilies` 18 -> 17 and
`counters.checkedMembers` 112 -> 105, and
`scripts/__tests__/check-i18n-call-site-keys.test.ts` pins both as a ONE-WAY
ratchet ("LOSING coverage does fail it"). Re-baselining that ratchet to land this
shape is a maintainer decision, not this branch's to take — see the report on
objectui#6285.
Part of #6285
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4qContributor
✅ 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
|
… not by derivation PM adjudication on #6285 chose shape B over the runtime derivation the first commit implemented, and this commit is that switch. Nothing about the defect or its user-visible fix changes: `manage_sharing` still joins the curated set and still gets its `capability.label.*` key in the ten packs and in the field widgets' provider-less defaults map, and `labelFor` keeps the `defaultValue` degradation. What changes is HOW the `Mirrors PLATFORM_CAPABILITIES` claim is held. - `CURATED_CAPABILITY_LABELS` goes back to a repo-local literal, now with `manage_sharing`. That keeps it readable to `check-i18n-call-site-keys.mjs`, whose `readVocabulary` needs a literal `new Set([…])`; the derivation made it `unreadable-vocabulary` and forced the family to `enumerable: false`, dropping the gate from 18 vocabularies / 113 exactly-checked members to 17 / 105 and tripping the one-way coverage ratchet in `scripts/__tests__/check-i18n-call-site-keys.test.ts`. Moving that ratchet is gate-weakening and belongs on the maintainer's floor, so it was not moved. - `scripts/check-i18n-call-site-keys.mjs` is reverted to the base version; the family keeps its `kind: 'set'` vocabulary. - The pin is renamed `specDerivation` -> `specParity` and grows the assertion that makes the claim mechanical: it reads the declaration through the gate's OWN `readVocabulary` and asserts set equality with `PLATFORM_CAPABILITIES` (dot -> underscore normalised) in BOTH directions, alongside the rendering assertions it already carried. Measured under this shape: `check:i18n-keys` green at 18 vocabularies / 113 members — one MORE than base's 112, the extra member being `manage_sharing` — and the ratchet test 109/109 green. The derivation remains a coherent choice and the branch history plus the PR body keep its measurements; it simply costs a guard, and this shape costs none. Fixes#6285 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
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
|
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
|
os-support-ai
marked this pull request as ready for review
August 25, 2026 13:27
Uh oh!
There was an error while loading. Please reload this page.
os-support-ai
deleted the
claude/issue-6285-capability-labels-derive-from-spec
branch
August 25, 2026 13:40
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 freeto 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#6285
Note
Shape adjudicated on #6285 after both candidates were measured. The first commit implemented a runtime derivation and hit a one-way coverage ratchet; this one keeps a repo-local literal and makes its
Mirrorsclaim mechanical with a spec-importing parity test. No guard was moved. Draft on purpose — the PM lands it. The A-vs-B table below is kept deliberately: it is the record of why B was chosen and why the ratchet was not touched.The defect (confirmed, re-measured, not inherited)
CURATED_CAPABILITY_LABELSinpackages/fields/src/widgets/CapabilityMultiSelectField.tsxwas a seven-member literal under a doc comment claiming it mirrored@objectstack/spec/security'sPLATFORM_CAPABILITIES. Read out of the installed@objectstack/spec@17.2.0on this branch's base:So
manage_sharingfell through tobyName.get(name)?.label— the English label thesys_capabilityregistry serves — and rendered untranslated in all ten packs, beside seven siblings that localize. A user-visible missing translation.Nothing caught it, and the reason is exact:
scripts/check-i18n-call-site-keys.mjsreads that declaration as thecapability.label.family's vocabulary and checks the members it names haveenkeys. All seven did. No instrument compared the vocabulary to the array it was named after, so the member the list never mentioned was invisible to the whole toolchain.A finding the card did not carry, and it changes the fix:
capability.label.manage_sharingdid not exist as an i18n key anywhere — not in the ten packs, not inuseFieldTranslation.ts's provider-less defaults map. Fixing membership alone would have traded a missing translation for a raw i18n key on screen, which is worse than the defect.What is in this diff
CapabilityMultiSelectField.tsxmanage_sharingjoins the curated set; the doc comment stops asserting the mirror and names the test that checks it;labelForgainsdefaultValueuseFieldTranslation.ts+ 10packages/i18n/src/locales/*.tscapability.label.manage_sharingauthored (Manage Sharing/ 管理共享 / 共有の管理 / 공유 관리 / Freigaben verwalten / Gérer le partage / Gestionar el uso compartido / Gerenciar compartilhamento / Управление общим доступом / إدارة المشاركة)CapabilityMultiSelectField.specParity-6285.test.tsxscripts/check-i18n-call-site-keys.mjsis untouched in the final diff (byte-identical to the merge base). The family keeps itskind: 'set'vocabulary.labelFor'sdefaultValueis deliberate and survives either shape: a capability that lands in a future spec bump before its translation is authored degrades to the registry's English label rather than renderingcapability.label.foo_barat a user. It is a last-resort fallback, not the mechanism — the parity test reds in CI on the same event.File surface note. The dispatch scoped this to
CapabilityMultiSelectField.tsxand its tests. Eleven further files are touched, and each is forced rather than chosen: the ten packs and the defaults map, because the new member has no key without them andall-locales-key-parity.test.tsrequires every pack to define everyenkey. None of the four sibling branches in flight this wave touches any of them — checked withgit diff --name-only origin/main...origin/<branch>on all four — and PR #6292'spackages/fields/src/index.tsxis untouched here.Why a literal, when the card asked for a derivation
The derivation was built and measured, not argued about. It is the first commit on this branch.
readVocabularyparses repo source and needs a literalnew Set([…]). With the derivation in place and the registry entry left alone, the gate says so itself:So the family would have to move to
enumerable: false, andscripts/__tests__/check-i18n-call-site-keys.test.tspins the split as a one-way ratchet — "paying off aunnamed-unionorexternal-vocabularyentry raises coverage without failing this test — but LOSING coverage does fail it."Both rows measured, not reasoned. B is one member ahead of base — the extra member is
manage_sharing.Two other routes were checked and closed: the gate's own documented bridge for an external vocabulary ("a repo-local exhaustive
Record<Union, …>this reader can read") is unavailable, becausePlatformCapability.nameis typedstringand the spec publishes no union to key aRecordby; and pointing the reader at the dependency's own source is out, because@objectstack/specships only the*.zod.tssubset undersrc/andPLATFORM_CAPABILITIESis not in it.Bundle size is not the reason, and that is worth stating because the neighbouring precedent (
useTenancyPosture.ts) is about bundle size. Measured here on the real console build: the derivation cost +0.3 KB gzipped on the eager closure. It was affordable. It simply cost a guard.And the benefit the derivation promised is not available in either shape. "The next spec addition arrives automatically" cannot happen: a new capability still needs
capability.label.*authored by a human in ten packs, or it renders registry English (this card's exact defect) or a raw key.manage_sharingis the proof — it had no key. Derivation does not remove the human step; it only chooses which instrument reports it. B keeps two instruments reporting it — the static gate and the test — for the same user-visible outcome.Verification — all at
823a024cc, worktree cleanpnpm check:i18n-keyspnpm exec vitest run scripts/__tests__/check-i18n-call-site-keys.test.tspnpm check:i18n-driftpnpm check:spec-symbolsCLAIM_ALLOWentry is added or needed)node scripts/check-changeset-presence.mjspnpm turbo run type-check(fields, i18n) +pnpm type-check:scriptspnpm exec vitest run packages/fields/pnpm exec vitest run packages/i18n/eslint --no-inline-configon all 13 changed.ts/.tsxfilesas anyidiomnode scripts/check-eager-closure-budget.mjstype-checkfor@object-ui/fieldsrunstsc --noEmit && tsc -p tsconfig.test.json, and--listFilesconfirms the new test is inside that program (1 hit) — so "type-check clean" is a statement about it, not around it. The test reachesreadVocabularythrough a relative import ofscripts/check-i18n-call-site-keys.mjs, the same arrangementpackages/layout/src/__tests__/readme-registration-keys.test.tsuses forscripts/component-registrations.mjs, and roots it at the process cwd rather thanresolve(__dirname, …)because this package's test program deliberately does not nametypes: ['node']— a test file is not the place to change that program.Ablation — two legs, both directions
Committed first. Mutation proven on disk each time by two separate greps (injected and removed text) and a blob-hash move; restored under
trap restore EXIT INT TERMcallinggit checkout HEAD -- "$REPO_ROOT/<path>"(absolute,HEAD-pinned so a written index cannot hand the mutation back); after each leggit diff HEAD --statwas empty and the blob hash was back atcf60991d….No rebuild leg is needed and that is a property of the harness, not an omission: the test imports the subject by relative path and
@object-ui/i18nresolves through the root vitest alias topackages/i18n/src, so nothing here readsdist/.Leg 1 — a spec member missing from the literal (the defect this card is). Predicted 3 of 7 red and named which; observed exactly those:
Leg 2 — a stale member the spec never had (
ghost_capability). Predicted exactly 1 red, and it is the assertion this shape adds over a rendering-only pin — a stale member is invisible to every render, because nothing ever asks for it:The assertions that stayed green in each leg are the ones describing behaviour the mutation did not touch, which is the point: the file is not one assertion wearing seven hats.
The most instructive thing here is a prediction that was wrong
Before touching the console, the eager-closure gate was predicted RED, from a standalone esbuild probe: spec root alone 398,100 B gzipped, root +
@objectstack/spec/security455,547 B — +57,447 B against 43.7 KB of aggregate headroom and 18.1 KB onvendor-objectstack.useTenancyPosture.tsrecords +237 KB minified for the same import and agrees in spirit. Every number was real.The real console before/after build measured +0.3 KB, green — off by two orders of magnitude. The probe answered marginal cost over the spec root alone; the question was marginal cost over the console's actual graph, which already reaches those modules. A wrong instrument reading as a confident number, and it nearly produced a false escalation on a cost that does not exist. The only thing that caught it was building the real artefact and reading the real gate instead of trusting the probe. Worth more to the next reader than the fix itself.
A second miss belongs next to it:
check:i18n-keysgoing green after the family conversion was predicted and observed, and the prediction stopped there — that a gate's own__tests__can pin invariants the gate's exit code cannot was not anticipated, and it is the finding that decided the shape.Generated by Claude Code