Repository navigation
Commit 1940afd
Part of #20680. This PR lands the two producer halves: the translator
rule in `@objectstack/spec` and the packaged-base accessor in
`@objectstack/metadata-protocol`. Two halves stay open, so the card must
stay open after this merges. The first is the `packages/rest` read-path
face, which hands the dashboard's packaged base to the translation;
triage names it a per-layer child, and the seat files it, dashboards
only, blocked by this PR (seat answer 5900364303 on #20680). The second
is the console renderer, which is not measured here (see the acceptance
notes).
Clause-②: yes
## The identity, measured first (as the dispatch ordered)
**Harness.** `@objectstack/verify`'s `bootStack(showcaseStack, {
orgContext: true })`, which boots the real showcase composition in
process: HTTP through the real Hono app, SQLite, an org-bound admin, and
a member of the same org who signed up. The tree is `origin/main` at
`5757463712`. I spied on `sys_metadata` `findOne` during direct
`protocol.getMetaItem` calls to see which branch answered. The probe was
a scratch file and is not committed.
| step | identity used | branch that answered | widget title served |
|---|---|---|---|
| `PUT …/system_overview?mode=draft` | row `type: dashboard`, `name:
system_overview`, `package_id: null`, `organization_id: ORG`, `state:
draft` | write | stored "Total Users (edited-20680)" |
| `POST …/publish` | the same row, `state: active` | write | stored the
edit |
| `protocol.getMetaItem({ organizationId: ORG })` | lookup `type:
dashboard, name, state: active, organization_id: ORG` (no package
filter) | step 1, the `sys_metadata` overlay lookup, which hit that row
| **edited** |
| `protocol.getMetaItems({ organizationId: ORG })` | union read |
overlay row | **edited** |
| `GET …?layers=true` (admin and member) | the same row | layered read |
overlay and effective **edited**, `overlayScope: org` |
| `GET /api/v1/meta/dashboard/system_overview` (admin and member) | the
same protocol read, then the REST translation | overlay, then
`translateDashboard` | "Total Users" (en), "用户总数" (zh-CN) |
| `GET /api/v1/meta/dashboard` (admin and member; this list is what the
console builds the board from) | the same | the same | "Total Users" /
"用户总数" |
| control `showcase_ops_dashboard`, same boot | identical row shape
(`package_id: null`, same org) | the same | **edited**, both doors |
The served `_packageId: com.objectstack.plugin-auth` is the code
artifact's protection envelope. `mergeArtifactProtection` grafts it onto
the overlay body, and plugin-auth's manifest registers
`SystemOverviewDashboard`. It is not a second identity that the overlay
missed.
**Hypotheses.** H1 (the write and the plain read disagree on
`package_id`): falsified. H2 (an earlier branch answers before the
overlay lookup): falsified, because step 1 answered. H3 (the list read
carries its own mismatch): falsified at the protocol; the REST list
shows the same translation effect as the item read. H4 (the control
works because it is keyed differently): falsified, because both are
keyed identically. The difference is that `platform-objects` ships an
`en` bundle whose `dashboards.system_overview.widgets.WIDGET_ID.title`
repeats each shipped title, while the showcase bundle carries no widget
titles for its dashboard. H5 (`packages/rest` is not the cause): partly
held. REST is not where the wrong value is produced, but its read path
is where the base has to be threaded in, so that half is the per-layer
child.
**The producer.** `translateDashboard`
(`packages/spec/src/system/i18n-resolver.ts`) applied the catalog over
whatever the document said. ADR-0029 D9.2a records one rule for both
object layers, *an explicit override beats a packaged default*, and
#8284 implemented it for objects' three scalars only. This PR applies
the same comparison to dashboards.
## What changes
- `translateDashboard` takes `TranslateDocumentOptions` (previously
`ResolveOptions`; the new type adds only the optional `packagedBase`).
When a base is supplied, the catalog replaces a string only while that
string still equals its packaged counterpart. The rule covers the
dashboard `label` and `description`, each widget's `title`,
`description` and sub-caption (`options.description`), each global
filter's label, and each static option label. Each string is matched by
the key the bundle uses for it: widget id, filter key, or option value.
A part that the base does not carry counts as authored, following the
object rule "a base that declares no such scalar". With no base, the
output is byte-identical to before.
- The comparison is still one implementation.
`scalarOverridesPackagedBase` now delegates to a private
`valueOverridesPackagedBase` that takes any key. Its exported signature
is unchanged, and its body is the same three lines.
- `ObjectStackProtocolImplementation.getPackagedDashboardBase(name)` is
the dashboard twin of `getPackagedObjectBase`. It is
`lookupArtifactItem('dashboard', name)` and nothing wider, so an overlay
hydrated under the plain registry key can never come back as the base.
It answers `undefined` for a dashboard no code package ships, for an
unknown or empty name, and for a registry that cannot answer.
## Readers of the changed functions, and which served answers move
- `translateDashboard` is reached only through
`translateMetadataDocument('dashboard', …)` in
`packages/rest/src/meta-item-read-gate.ts`, by both
`translateMetaDocument` (item) and `translateMetaList` (list). Both pass
`packagedBase: packagedObjectBaseOf(…)`, which is `undefined` for every
type but `object`. So no serving path supplies a dashboard base today,
and the new branch never runs in production until the REST child lands.
objectui imports none of these functions; it mentions them in comments
only.
- `scalarOverridesPackagedBase` is read by `translateObject` (spec) and
by the `SchemaRegistry` fold (`packages/objectql/src/registry.ts`, the
ADR-0029 D9.2a precedence). Its semantics are unchanged. The objectql
suite, 6682 tests, is green against the rebuilt spec `dist`.
- `getPackagedDashboardBase` has no caller yet. The REST child is its
first reader.
**Before/after, measured in the real boot.** I ran the same probe on two
builds. The before build was `5757463712`, in a throwaway worktree. The
after build was this branch, with `@objectstack/spec` and
`@objectstack/metadata-protocol` rebuilt. Each run covered admin and
member, en and zh-CN, and three phases: pristine; after publishing
overlays on `system_overview` and `showcase_ops_dashboard`; and after
resetting both. Each run read the dashboard list and each item (all 4
dashboards), the view list (35 views), the app list, and 4 object items
(`showcase_project`, `showcase_account`, `showcase_task`, `sys_user`). I
compared label, title, filter and option strings plus a normalized hash
of every body. **Differences: 0.** No other dashboard, view, app or
object changes its answer with this PR alone.
**Positive controls in the same runs.** Unedited translated items keep
their translations in zh-CN: `system_overview` "系统概览" / "用户总数"; the
showcase global filters "任务状态" and "区域", with their option labels; views
"进行中" and "紧急"; the app "功能展示"; objects "项目", "任务" and "用户".
`showcase_account` keeps serving its extension label "Account (Success
Overlay)", which is the #8284 object path, unchanged.
**The whole chain with the REST face applied** (a throwaway worktree,
**not** this branch): a nine-line change to `meta-item-read-gate.ts`
passes `getPackagedDashboardBase(name)` for `dashboard`. With it, the
same probe differs from this branch in exactly 16 fields, all of them
`system_overview`'s edited widget, for admin and member, en and zh-CN,
item and list (title, plus the body hashes). The edit is served in both
locales, the unedited sibling widget stays translated ("活跃会话"), reset
restores "Total Users" / "用户总数", and the showcase control is unchanged.
That is the card's pin set, measured one layer up from this PR.
## Published surface (for the Clause-② line)
- `@objectstack/spec`: no export is added, removed or renamed
(`check:api-surface`: "public API surface + factory signatures
unchanged"). One exported function's parameter type widens:
`translateDashboard`'s `opts` goes from `ResolveOptions` to
`TranslateDocumentOptions`. Every existing call still compiles.
`translateMetadataDocument` already declared and passed `packagedBase`.
- `@objectstack/metadata-protocol`: one new public method on the
exported `ObjectStackProtocolImplementation`,
`getPackagedDashboardBase`.
- Measured against the question "does this widen an accept set or the
public surface", both facts read as a widening. The seat answered
(5900364303 on #20680): `Clause-②: yes`, and both changesets are
`minor`. The claim's original `no` is corrected there.
## No documented rule is reversed
- `content/docs/ui/translations.mdx` says: "The authored label is the
default locale's text … Shipping a `zh-CN` bundle … still works and
still wins." That describes a package author's own label against the
package's own bundle. That case is kept: a value equal to the packaged
declaration still gets the catalog, and so does a label written as an
inline locale map, because only a string counts as an override.
- ADR-0029 D9.2a and the #8284 ruling (2026-08-13) say that an explicit
override beats a packaged default, in the source locale as well. This PR
applies that ruling to dashboards; it does not reverse it. No ADR states
that a catalog wins over a tenant's overlay.
## Tests
HEAD `d89ebb8c1c` for everything below.
- `packages/spec/src/system/i18n-resolver.test.ts`, a new block of 9
tests. It covers an untouched document (catalog applied to every
string), an edited title in en and zh-CN, per-string judgement, every
string kind, a tenant-added widget, no base as the pre-change control,
the ruled no-op edge, no mutation of inputs, and the generic dispatcher.
The whole spec `local` project passed: 575 files, 16969 tests.
- `packages/objectql/src/protocol-packaged-dashboard-base.test.ts`, 8
tests against a real `SchemaRegistry`. It covers the packaged body,
immunity to an overlay hydrated under the plain key, the three
`undefined` answers, the item read serving the overlay (one identity),
translation plus base serving the edit in both locales while the sibling
stays translated, the no-base control reproducing the defect, and reset.
The objectql `local` project passed: 336 files, 6682 tests.
- `@objectstack/metadata-protocol` passed: 190 files, 2792 tests.
`typecheck` is green for spec (with its test layer), metadata-protocol
and objectql (with its test layer).
- **Ablation 1** (spec, source-resolved,
`scripts/ablation-replace.mjs`): the widget-title guard was replaced
with `false && …`; the anchor went from 1 to 0 and the blob changed. The
`-t 20680` run turned 3 red: the edited title, the tenant-added widget,
and the dispatcher. The file was restored, with blob equal to HEAD and
`git diff HEAD` empty.
- **Ablation 2** (metadata-protocol, through `dist`): the accessor was
made to return `registry.getItem` (the trap). After a rebuild,
`ablation-dist-preflight` found the marker in 2 built files, and 2 tests
went red: shadow immunity, and the no-code-package case. Restore leg:
rebuild, `--absent` over 24 built files, tree clean, 8 of 8 green.
- Gates: `node scripts/pm/dispatch-gates.mjs --commands` derived 96
families. I ran all 96 at `d89ebb8c1c` and all exited 0. `--ran`
reconciliation: "96 derived, 96 run, 0 NOT-MEASURED, 0 UNRUN".
`check:engine-double-contract` asked for the new double to be recorded,
so `scripts/engine-double-contract.pinned.json` gains one `findOne` row,
written by the gate's `--write`. `check:objectql-double-limit` asked for
the double's `find` to honour `limit`.
- Narrowed lint: `eslint --no-inline-config --format json` over the 4
TypeScript files in the diff reported 4 files, 0 errors and 0 warnings.
That population comes from the config's own `files` glob
(`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`); the other 3 changed paths are
`.md` and `.json`. The config never enables type-aware linting (its own
header says so: no `parserOptions.project`), so this diff cannot move
the verdict for an untouched file.
## Acceptance notes
- **Dormant until the REST child.** With this PR alone, no served answer
changes (measured above). The `translations.mdx` sentence describing the
rule for dashboards belongs with the child that makes the rule
observable.
- **The console's board is not measured.** Read from objectui source at
`9fd6c2c`, not at the pinned `dd3f7e1`, which is absent locally:
`DashboardRenderer`'s `tWidgetTitle` re-resolves
`dashboards.NAME.widgets.WIDGET_ID.title` client-side, and a bundle
entry wins over the served title. If so, the rendered board would keep
the shipped string even after the server serves the edit. This is a
read-only inference and no card is filed. The carrier is the REST
child's browser verification.
- **A same-family reading on views** (measured in this boot): an org
overlay on `showcase_task.in_progress` ("In Progress (edited-20680)") is
served in en, but a zh-CN reader, admin or member, item or list, gets
"进行中". This is the same catalog-over-overlay shape one type over. The
seat files it as its own finding (5900364303); it is not fixed here.
- The branch is 7 commits behind `origin/main`, and none of them touches
these paths. The merge queue rebuilds the PR on the current main.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 99786f9 commit 1940afd
7 files changed
Lines changed: 582 additions & 9 deletions
File tree
- .changeset
- packages
- metadata-protocol/src
- objectql/src
- spec/src/system
- scripts
Lines changed: 11 additions & 0 deletions
| 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 | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7160 | 7160 | | |
7161 | 7161 | | |
7162 | 7162 | | |
| 7163 | + | |
| 7164 | + | |
| 7165 | + | |
| 7166 | + | |
| 7167 | + | |
| 7168 | + | |
| 7169 | + | |
| 7170 | + | |
| 7171 | + | |
| 7172 | + | |
| 7173 | + | |
| 7174 | + | |
| 7175 | + | |
| 7176 | + | |
| 7177 | + | |
| 7178 | + | |
| 7179 | + | |
| 7180 | + | |
| 7181 | + | |
| 7182 | + | |
| 7183 | + | |
| 7184 | + | |
| 7185 | + | |
| 7186 | + | |
| 7187 | + | |
| 7188 | + | |
| 7189 | + | |
| 7190 | + | |
| 7191 | + | |
| 7192 | + | |
| 7193 | + | |
| 7194 | + | |
| 7195 | + | |
| 7196 | + | |
| 7197 | + | |
| 7198 | + | |
| 7199 | + | |
| 7200 | + | |
| 7201 | + | |
| 7202 | + | |
| 7203 | + | |
| 7204 | + | |
7163 | 7205 | | |
7164 | 7206 | | |
7165 | 7207 | | |
| |||
Lines changed: 237 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 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
0 commit comments