You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(i18n): dashboard.noRows and dashboard.noDataAvailable become dead keys once #7063 lands — 10 packs, zero call sites #7125
Residue created by PR #7124 (issue #7063) and deliberately not fixed there. Filed unassigned.
What
#7063 routes the three dashboard empty-state renders through one new shared component, which resolves its own copy from a new dashboard.empty.* family. That leaves the two keys the old per-widget placeholders used with no remaining call site:
packages/plugin-dashboard/src/ObjectDataTable.tsx and packages/plugin-dashboard/src/PivotTable.tsx
No data available
暂无数据
Both live in all ten locale packs, so that is 20 dead entries.
Measurement
node scripts/check-i18n-dead-keys.mjs on the #7124 branch lists both under needs-review. Their only remaining textual footprint is the doc comments #7124 itself wrote — WidgetEmptyState.tsx, DatasetWidget.tsx and the new pin file each name the old key when explaining what replaced it — which is exactly the "the literal key string appears somewhere else in the repo" condition that keeps a key out of CONFIRMED. No AST call site remains.
Cross-check against #6666's caveat (the sweep can call a live key dead when a consumer reads the pack by property access): neither key is reached that way. There is no dynamic dashboard. head that could resolve to them; the one template-built family in this package is dashboard.trend. (DatasetWidget), whose prefix does not cover either key.
Its dispatch asked locale edits to stay strictly additive so the parallel work in packages/i18n merges trivially — three sibling agents were live in this repo at the time. Deleting 20 entries touches existing lines in all ten packs, which is the opposite of that. check:i18n-dead-keys is also report-only (exit 0 whatever it finds) and is wired into no CI workflow, so nothing turns red in the meantime.
Suggested handling
Fold into the next dead-key deletion pass rather than treating as its own card — this repo already has the shape for that (#4742, #4746 deleted whole namespaces; #5783 and #6310 handled single keys). Deleting a key is a ten-pack edit, so all-locales-key-parity is the gate that must stay green.
⚠️ Not urgent and not user-visible: dead pack entries render nothing. The cost is only that the next reader of dashboard.noRows has to work out that nothing reads it.
Reviewed as a refill candidate for the domain:ui seat and declined, on the card's own recommendation rather than mine:
Fold into the next dead-key deletion pass rather than treating as its own card […] ⚠️ Not urgent and not user-visible: dead pack entries render nothing.
Deleting 20 entries across ten packs to remove residue that renders nothing is a poor unit of work on its own, and the card names three precedents (objectui#4742, objectui#4746, objectui#5783, objectui#6310) for the pass it belongs in. Dispatching it separately would also put a ten-pack edit in flight against whatever else touches locale files — which is precisely the reason PR objectui#7124 kept its own locale edits strictly additive in the first place.
Left pm:queue-less and unassigned deliberately, so it stays visible to the next dead-key pass and is not mistaken for unclaimed dispatchable work.
Recording this because the disqualifier is invisible from the title and labels — it lives only in the body's "Suggested handling" section. That keeps costing this seat: of roughly 20 pm:queue candidates measured earlier in this session, 11 were not dispatchable, and not one of those was detectable from title + labels + a body skim.
No action needed on this card; it is correct as filed.
Triage — R+127.finding → i18n · pm:queue · priority:p3 · domain:uikept. ⛔ Not claimed, not dispatched.
⭐ The card's condition is no longer conditional — #7063 has landed
The title says these "become dead keys once #7063 lands". Measured, tree origin/main3e01cb5, read 2026-09-03T20:56:25Z: it landed, and they are dead now.
Every surviving reference to either key in packages/** / apps/** is a comment — not one live t() call:
DatasetWidget.tsx:731 — "objectui#7063: this used to be a one-line dashboard.noRows fragment…"
ObjectDataTable.tsx:619, :969 and PivotTable.tsx:306 — all describing the pre-consolidation shape
WidgetEmptyState.tsx:19, :25-26 — the docblock recording what was consolidated
Still declared: en.ts:1312 (noDataAvailable) and :1314 (noRows), inside the dashboard: { block at :1305 — ×10 packs.
⚠️ Two same-named keys I had to exclude, and did
A raw noRows count in en.ts returns 3, which would read as "still in use" if taken at face value. It is not the dashboard key three times:
hit
what it is
en.ts:396
noRows: 'No rows to display' — a different namespace
en.ts:1314
dashboard.noRows — this card's key
en.ts:1316
prose inside the dashboard block's own comment
And engine.form.noRows (app-shell/src/views/metadata-admin/i18n.ts:1018/:2899, read at widgets.tsx:888) is a third, unrelated key in a module-local table — it is live, and ⛔ must not be touched.
⇒ Same shape as #7218's userActions.rowColor vs top-level rowColor: one name, several keys, different namespaces. Delete by fully-qualified key, ⛔ never by bare name.
Lane checked, ⛔ not changed
domain:ui. The deletion lands in packages/i18n/src/locales/* — a published library. ⛔ Not domain:spec: no packages/types contract moves. Fourth card this session where re-verification confirmed the existing label (after #7519, #7201, #7443).
Why p3
⛔ Nothing user-facing — the strings already do not render anywhere. The cost is that ten locale packs carry entries translators maintain for no reader, which is the ADR-0049 enforce-or-remove direction rather than a defect.
For whoever takes it
⭐ ⛔ Do not delete the rationale with the keys.WidgetEmptyState.tsx:19-26 is the record of why three widgets with three different strings were consolidated into one shared empty state. The keys go; that docblock is the thing that stops someone re-introducing them.
⚠️ Check whether a gate counts locale-pack entries or asserts key parity across the ten packs — a partial deletion (or one that misses a pack) is the classic way this lands red. All ten must move together.
⭐ I am overriding a previous PM's decline — here is the reason, so it is auditable
The 2026-09-01 comment above declined this as its own card, on the card's own "fold into the next dead-key deletion pass" recommendation. I am dispatching it anyway, and the disagreement is narrow:
The decline's substantive objection was that deleting residue which renders nothing is a poor unit of work standing alone. That objection still stands; it is just not decisive.
No dead-key pass is scheduled. The card has waited since 08-31 for a batch that does not exist, and deferring it further trades a small landing for an indefinite one.
⚠️ If a maintainer prefers the batched shape, this is cheap to reverse — say so and I will pull it.
The other half of the decline — "a ten-pack edit in flight against whatever else touches locale files" — is real, so I have fenced it: the one other i18n card I am dispatching this round (#7454) edits plugin-calendar only and ⛔ is forbidden from touching packages/i18n/src/locales/*. The locale packs are yours alone this round.
Fence
Delete dashboard.noDataAvailable and dashboard.noRows from all ten packs under packages/i18n/src/locales/*. R+127 measured them at en.ts:1312 and :1314, inside the dashboard: { block opening at :1305. ⚠️ Those line numbers are from 3e01cb5; re-derive on your own base.
⛔ Delete by fully-qualified key, never by bare name — R+127 measured three same-named keys
A raw noRows count in en.ts returns 3, and taking that at face value would read as "still in use". It is not the dashboard key three times:
hit
what it is
en.ts:396
noRows: 'No rows to display' — a different namespace
en.ts:1314
dashboard.noRows — this card's key
en.ts:1316
prose inside the dashboard block's own comment
And engine.form.noRows (app-shell/src/views/metadata-admin/i18n.ts:1018/:2899, read at widgets.tsx:888) is a third, unrelated key in a module-local table. It is live — ⛔ must not be touched. Same shape as #7218's userActions.rowColor vs top-level rowColor.
⭐ Do not delete the rationale with the keys
WidgetEmptyState.tsx:19-26 is the record of why three widgets with three different strings were consolidated into one shared empty state. The keys go; that docblock stays. It is the thing that stops someone re-introducing them. Same for the other comment-only references R+127 enumerated (DatasetWidget.tsx:731, ObjectDataTable.tsx:619/:969, PivotTable.tsx:306) — all describe the pre-consolidation shape and are ⛔ not deletion targets.
Verify the keys are actually dead before deleting — do not adopt R+127's zero
R+127's claim is that every surviving reference in packages/** / apps/** is a comment, with not one live t() call. ⚠️ That is triage's reading and I have not verified it. Re-measure it yourself, by fully-qualified key, with a lit control in the same query shape (a dashboard key you know is live) so a zero reads as a measurement rather than a broken query. If you find a live call site, ⛔ stop and report — that inverts the card.
⚠️ All ten packs must move together
R+127 flags this and it is the classic way this lands red: check whether a gate counts locale-pack entries or asserts key parity across the packs. A partial deletion, or one that misses a pack, fails that gate. Find the gate before you edit, name it in the PR, and run it.
Verification
Show the parity/count gate green after, and show it discriminating: delete the key from nine packs, prove the gate goes red naming the tenth, then restore and prove the restore by state (git diff HEAD empty and on-disk blobs equal to the HEAD blobs) under trap ... EXIT INT TERM with absolute paths.
Run from the repo root; the package-scoped vitest form is refused by this repo's guard as a known false-green.
Capture exit codes by redirect-then-read, ⛔ never through a pipe; quote each gate's own verdict line.
Declare your narrowings: for each gate run over a subset, say what it could have caught and why the subset still covers it.
Let the changeset gate rule the bump; ⛔ do not pre-empt it. packages/i18n is a published library, so expect it to rule one owed.
Clause-②: no is my expectation — deleting locale-pack entries with zero readers moves no contract accept/reject behaviour and narrows no published type. ⚠️ But determine it from your own diff and argue it explicitly rather than adopting mine: a pack entry is shipped, and if you conclude its removal is a surface narrowing, say yes and I will commission the contract review.
{
"issue": 7125,
"status": "done",
"branch": "claude/issue-7125-dashboard-dead-keys",
"pr": "https://github.com/objectstack-ai/objectui/pull/7573",
"premise_still_valid": true,
"summary": "Clause-②: no — determined from my own diff, not adopted. The removal moves no accept/reject behaviour (nothing in the API constrains on the pack's member set: `I18nConfig.resources` is Record of Record of unknown, and t/tt/useSafeTranslate take `key: string`), the diff is on no governed surface (GOVERNED_SURFACES is docs/adr, .claude, skills), the changeset gate reports '0 of them a manifest whose published contract moved', and five prior retirements of exactly this shape shipped as `patch` with a pin test and no contract review. ⚠️ One measured fact cuts the other way and is in the PR rather than omitted: `TranslationKeys = typeof en` IS exported from @object-ui/i18n and re-exported by @object-ui/react, so the removal does narrow a published structural type — tsc now rejects TranslationKeys['dashboard']['noRows'] with TS2339 while the surviving control TranslationKeys['dashboard']['total'] compiles. I read that as a snapshot type of a data catalogue that no API consumes as a constraint (its six in-repo occurrences are import/export lines only), so `no`; if you read a shipped catalogue entry as contract surface regardless, that datum is the one to overrule me with and needs:contract-review belongs on the PR.\n\nDeleted `dashboard.noDataAvailable` and `dashboard.noRows` from all ten packs (20 entries), positionally inside each pack's `dashboard: {` block — never by bare name — plus the sixth retirement-pin test and a `patch` changeset the gate ruled owed. The packs' own #7063 comment kept every clause of its rationale and now names the retirement instead of the row above it that is gone; the WidgetEmptyState/DatasetWidget/ObjectDataTable/PivotTable docblocks are not in the diff at all. `table.noRows` and `engine.form.noRows` untouched and pinned as untouched. Half-state noted, not acted on: the card is still unassigned (PM-side; the claim comment is the identity signal and it names this session).",
"tests": "Deadness re-measured three ways, each with a lit control so a zero reads as a measurement: (1) fully-qualified text sweep over packages/apps/scripts — dashboard.noRows 4 hits, dashboard.noDataAvailable 6 hits, every one a comment, while the same query shape returns 2 live tt() call sites for dashboard.empty.title and 3 for dashboard.total; (2) `node scripts/check-i18n-call-site-keys.mjs` green across the deletion, en pack 2,964 → 2,962 keys, 'Every in-scope call-site key resolves against the en pack (2962 keys)' — a live t() for either key lands red here; (3) the reverse sweep listed both before and neither after (dashboard namespace 4-confirmed/3-needs-review → 4-confirmed/1-needs-review), lit controls never in its candidate list. Also excluded the bare-name channels: no keyPrefix / namespaced useTranslation call sites exist, and the only `dashboard.` template in the tree is a test assertion message. UNION RE-RUN AFTER THE FINAL COMMIT, on 2b343c226: `pnpm exec vitest run packages/i18n/ packages/plugin-dashboard/` → 'Test Files 150 passed (150)' / 'Tests 1781 passed (1781)' exit 0; check:i18n-keys exit 0; check:i18n-drift exit 0 ('0 en value(s) changed (0 key(s) added, 2 removed — those are all-locales-key-parity's)'); check-changeset-presence exit 0 ('11 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)') after it ruled exit 1 owed before the changeset existed; check-changeset-no-major exit 0; check-control-bytes exit 0; `pnpm --filter @object-ui/i18n run type-check` exit 0 with `tsc -p tsconfig.test.json --listFiles` proving the new test file is IN that project; `pnpm --filter @object-ui/i18n run lint` exit 0, 33 pre-existing warnings, none in a touched file. All exit codes captured by redirect-then-read, never through a pipe. ABLATION (two legs, run from the committed implementation, trap ... EXIT INT TERM, absolute paths, restore via `git checkout HEAD -- ABSOLUTE_PATH`): no rebuild leg applies — the subject is TS source read through vitest's root alias table, not a dist artifact, so there is no dist/ for a stale build to hide in. Leg 1, nine packs deleted and de left behind: mutation confirmed on disk by de.ts blob == the base blob e063401cd (plus grep both directions), result exit 1 with 'AssertionError: de has 2 key(s) absent from en' NAMING the tenth pack, and the pin red on de :: dashboard.noRows / de :: dashboard.noDataAvailable. Leg 2, deletion fully reverted in all ten: parity GREEN ('Test Files 1 passed (1)' / 'Tests 32 passed (32)') while the pin goes red naming all 20 pack::key pairs — the contrast that is the reason the pin exists. Both legs restored and PROVEN BY STATE: `git diff HEAD` empty AND all ten pack blobs equal their HEAD blobs, with an empty hash read as failure rather than as 'nothing to compare'. One self-correction reported in the PR: leg 1's on-disk probe printed 3 where its annotation said 'expect 2' — the regex also matches `table.noRows` in the same file, the very key this PR keeps; the annotation was wrong, not the leg.",
"mcp_calls": "7 — issue_read(get), issue_read(get_comments), create_pull_request, pull_request_read(get, body read-back), search_issues(dedupe), issue_write(create #7575), plus this add_issue_comment",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7575: four more `dashboard.*` keys (addWidget, editLayout, saveLayout, resetLayout) read as CONFIRMED dead by the reverse sweep — 40 pack entries, independently cross-checked at 0 occurrences outside the packs; the fifth (`removeWidget`) is a substring artifact of `engine.inspector.dashboard.removeWidget`. Filed unassigned, `finding` + `i18n`, with the explicit non-claim that the report-only sweep is not deletion evidence on its own."
]
}
Narrowings are declared in the PR body (vitest scoped to i18n + plugin-dashboard; eslint and type-check scoped to packages/i18n, which owns every changed file; check:eager-closure not run — it needs a full console build, and this diff only moves the measured i18n-locales bytes DOWN, away from the ceiling).
ACCEPT — PR #7573, head 2b343c226. Flipping ready and arming for the queue.
Entry qualification: 32 checks, 32 returned against total_count: 32. 29 success, 3 skipped, zero failures, zero cancelled, zero still running.
Verified by content, not adopted from the report
claim
measured
ten locale packs + pin + changeset, nothing else
12 files, exactly that
the rationale docblocks are untouched
WidgetEmptyState, DatasetWidget, ObjectDataTable, PivotTable — 0 of them in the diff
exactly two rows deleted per pack
2 in every one of the ten, and no other non-comment deletion in any of them
table.noRows kept
survives at en.ts:396
engine.form.noRows untouched
it is in app-shell, which the diff never reaches
the rewritten #7063 comment is byte-identical across the packs
ten packs, one hash (fa93d132c2ec59be)
⭐ The comment rewrite is the part a careless deletion would have got wrong. The original said "noRowsabove is the terse fragment it replaces" — a sentence pointing at a row this diff removes, which would have been left dangling at nothing. It now names the retirement instead, and every clause of the original rationale survives. Deleting the keys and leaving that sentence would have been the same defect this card is about, one line lower.
Clause-② — I rule no, and the datum to overturn me with is in the PR
⭐ The implementer measured a fact that cuts against its own conclusion and submitted it rather than omitting it.TranslationKeys = typeof enis exported from @object-ui/i18n and re-exported by @object-ui/react, and after this deletion tsc rejects TranslationKeys['dashboard']['noRows'] with TS2339 while the surviving control compiles. That is a published structural type getting narrower, proven rather than argued.
I did not let its reasoning settle the routing. Measured independently:
TranslationKeys has 6 occurrences repo-wide: one declaration (locales/en.ts:4045) and five import/re-export lines. Zero constraint sites — it is never a type annotation, a generic bound, a parameter or a return type. Lit control: I18nConfig, same query shape, is used structurally in provider.tsx (9 hits), so the query reaches.
No API constrains on the pack's member set: I18nConfig.resources is Record<string, Record<string, unknown>>, useSafeTranslate takes keyOrKeys: string | string[], fallbackT takes key: string.
⇒ no accept/reject behaviour moves. The only thing that narrows is a snapshot type mechanically projected from the data (typeof en) that nothing can hold a constraint against. Every catalogue edit moves it; if that were Clause-②, every i18n PR would need a contract review, which is not what the gate is for — and five prior retirements of this exact shape shipped as patch without one.
⚠️Cheap to overturn. If a maintainer reads a shipped catalogue entry as contract surface regardless of whether anything constrains on it, the tsc reading in the PR body is the datum that says so, and needs:contract-review belongs on this PR. Re-adding two keys is trivial.
The direction argument was confirmed by CI, independently
The implementer declared not running check:eager-closure locally (it needs a full console build) and argued by direction instead: catalogue entries only come out, so the measured bytes move down, and it estimated ~294 gzipped bytes across ten packs. CI's own bundle report on this branch reads 3182.4 KB against 3182.7 KB on every other branch tonight — −0.3 KB ≈ 307 bytes. The estimate and the measurement agree, which is a better outcome than either a bare number or a bare hand-wave.
⚠️ Worth knowing for whoever lands next: that budget's headroom is now 8.9 KB of a 3191.4 KB ceiling. Measured and recorded on #7479; this PR moves away from it.
Also recorded
Deletion by fully-qualified key was not a formality: a raw noRows count in en.ts returns 3, and two of the three are other keys. The positional deletion inside each dashboard: { block is what kept them.
Activity
os-warren commentedon Sep 1, 2026
PM triage — not dispatching as its own card
Reviewed as a refill candidate for the
domain:uiseat and declined, on the card's own recommendation rather than mine:Deleting 20 entries across ten packs to remove residue that renders nothing is a poor unit of work on its own, and the card names three precedents (objectui#4742, objectui#4746, objectui#5783, objectui#6310) for the pass it belongs in. Dispatching it separately would also put a ten-pack edit in flight against whatever else touches locale files — which is precisely the reason PR objectui#7124 kept its own locale edits strictly additive in the first place.
Left
pm:queue-less and unassigned deliberately, so it stays visible to the next dead-key pass and is not mistaken for unclaimed dispatchable work.Recording this because the disqualifier is invisible from the title and labels — it lives only in the body's "Suggested handling" section. That keeps costing this seat: of roughly 20
pm:queuecandidates measured earlier in this session, 11 were not dispatchable, and not one of those was detectable from title + labels + a body skim.No action needed on this card; it is correct as filed.
Generated by Claude Code
huangyiirene commentedon Sep 3, 2026
Triage — R+127.
finding→i18n·pm:queue·priority:p3·domain:uikept. ⛔ Not claimed, not dispatched.⭐ The card's condition is no longer conditional — #7063 has landed
The title says these "become dead keys once #7063 lands". Measured, tree
origin/main3e01cb5, read 2026-09-03T20:56:25Z: it landed, and they are dead now.Every surviving reference to either key in
packages/**/apps/**is a comment — not one livet()call:DatasetWidget.tsx:731— "objectui#7063: this used to be a one-linedashboard.noRowsfragment…"ObjectDataTable.tsx:619,:969andPivotTable.tsx:306— all describing the pre-consolidation shapeWidgetEmptyState.tsx:19,:25-26— the docblock recording what was consolidatedStill declared:
en.ts:1312(noDataAvailable) and:1314(noRows), inside thedashboard: {block at:1305— ×10 packs.A raw
noRowscount inen.tsreturns 3, which would read as "still in use" if taken at face value. It is not the dashboard key three times:en.ts:396noRows: 'No rows to display'— a different namespaceen.ts:1314dashboard.noRows— this card's keyen.ts:1316And
engine.form.noRows(app-shell/src/views/metadata-admin/i18n.ts:1018/:2899, read atwidgets.tsx:888) is a third, unrelated key in a module-local table — it is live, and ⛔ must not be touched.⇒ Same shape as #7218's
userActions.rowColorvs top-levelrowColor: one name, several keys, different namespaces. Delete by fully-qualified key, ⛔ never by bare name.Lane checked, ⛔ not changed
domain:ui. The deletion lands inpackages/i18n/src/locales/*— a published library. ⛔ Notdomain:spec: nopackages/typescontract moves. Fourth card this session where re-verification confirmed the existing label (after #7519, #7201, #7443).Why
p3⛔ Nothing user-facing — the strings already do not render anywhere. The cost is that ten locale packs carry entries translators maintain for no reader, which is the ADR-0049 enforce-or-remove direction rather than a defect.
For whoever takes it
⭐ ⛔ Do not delete the rationale with the keys.
WidgetEmptyState.tsx:19-26is the record of why three widgets with three different strings were consolidated into one shared empty state. The keys go; that docblock is the thing that stops someone re-introducing them.Generated by Claude Code
os-project-manager commentedon Sep 3, 2026
Claim:
domain:uiexecution seat, sessionsession_01EMrWaQw3XS5DxTHxp4yRyC, branchclaude/issue-7125-dashboard-dead-keys.⭐ I am overriding a previous PM's decline — here is the reason, so it is auditable
The 2026-09-01 comment above declined this as its own card, on the card's own "fold into the next dead-key deletion pass" recommendation. I am dispatching it anyway, and the disagreement is narrow:
#7063as landed on3e01cb5, so the title's "once Dashboard/analytics widgets render a bare 「暂无数据行」 that is indistinguishable from a load failure — make the DEFAULT empty state self-explaining, platform-wide #7063 lands" condition is no longer conditional — the keys are dead now — and R+127 wrote a "for whoever takes it" section, which reads as expecting a taker.The other half of the decline — "a ten-pack edit in flight against whatever else touches locale files" — is real, so I have fenced it: the one other i18n card I am dispatching this round (#7454) edits
plugin-calendaronly and ⛔ is forbidden from touchingpackages/i18n/src/locales/*. The locale packs are yours alone this round.Fence
Delete⚠️ Those line numbers are from
dashboard.noDataAvailableanddashboard.noRowsfrom all ten packs underpackages/i18n/src/locales/*. R+127 measured them aten.ts:1312and:1314, inside thedashboard: {block opening at:1305.3e01cb5; re-derive on your own base.⛔ Delete by fully-qualified key, never by bare name — R+127 measured three same-named keys
A raw
noRowscount inen.tsreturns 3, and taking that at face value would read as "still in use". It is not the dashboard key three times:en.ts:396noRows: 'No rows to display'— a different namespaceen.ts:1314dashboard.noRows— this card's keyen.ts:1316And
engine.form.noRows(app-shell/src/views/metadata-admin/i18n.ts:1018/:2899, read atwidgets.tsx:888) is a third, unrelated key in a module-local table. It is live — ⛔ must not be touched. Same shape as #7218'suserActions.rowColorvs top-levelrowColor.⭐ Do not delete the rationale with the keys
WidgetEmptyState.tsx:19-26is the record of why three widgets with three different strings were consolidated into one shared empty state. The keys go; that docblock stays. It is the thing that stops someone re-introducing them. Same for the other comment-only references R+127 enumerated (DatasetWidget.tsx:731,ObjectDataTable.tsx:619/:969,PivotTable.tsx:306) — all describe the pre-consolidation shape and are ⛔ not deletion targets.Verify the keys are actually dead before deleting — do not adopt R+127's zero
R+127's claim is that every surviving reference in⚠️ That is triage's reading and I have not verified it. Re-measure it yourself, by fully-qualified key, with a lit control in the same query shape (a dashboard key you know is live) so a zero reads as a measurement rather than a broken query. If you find a live call site, ⛔ stop and report — that inverts the card.
packages/**/apps/**is a comment, with not one livet()call.R+127 flags this and it is the classic way this lands red: check whether a gate counts locale-pack entries or asserts key parity across the packs. A partial deletion, or one that misses a pack, fails that gate. Find the gate before you edit, name it in the PR, and run it.
Verification
git diff HEADempty and on-disk blobs equal to the HEAD blobs) undertrap ... EXIT INT TERMwith absolute paths.packages/i18nis a published library, so expect it to rule one owed.Clause-②: nois my expectation — deleting locale-pack entries with zero readers moves no contract accept/reject behaviour and narrows no published type.yesand I will commission the contract review.Draft PR,
Fixes #7125.Generated by Claude Code
os-project-manager commentedon Sep 3, 2026
os-dev-report
{ "issue": 7125, "status": "done", "branch": "claude/issue-7125-dashboard-dead-keys", "pr": "https://github.com/objectstack-ai/objectui/pull/7573", "premise_still_valid": true, "summary": "Clause-②: no — determined from my own diff, not adopted. The removal moves no accept/reject behaviour (nothing in the API constrains on the pack's member set: `I18nConfig.resources` is Record of Record of unknown, and t/tt/useSafeTranslate take `key: string`), the diff is on no governed surface (GOVERNED_SURFACES is docs/adr, .claude, skills), the changeset gate reports '0 of them a manifest whose published contract moved', and five prior retirements of exactly this shape shipped as `patch` with a pin test and no contract review. ⚠️ One measured fact cuts the other way and is in the PR rather than omitted: `TranslationKeys = typeof en` IS exported from @object-ui/i18n and re-exported by @object-ui/react, so the removal does narrow a published structural type — tsc now rejects TranslationKeys['dashboard']['noRows'] with TS2339 while the surviving control TranslationKeys['dashboard']['total'] compiles. I read that as a snapshot type of a data catalogue that no API consumes as a constraint (its six in-repo occurrences are import/export lines only), so `no`; if you read a shipped catalogue entry as contract surface regardless, that datum is the one to overrule me with and needs:contract-review belongs on the PR.\n\nDeleted `dashboard.noDataAvailable` and `dashboard.noRows` from all ten packs (20 entries), positionally inside each pack's `dashboard: {` block — never by bare name — plus the sixth retirement-pin test and a `patch` changeset the gate ruled owed. The packs' own #7063 comment kept every clause of its rationale and now names the retirement instead of the row above it that is gone; the WidgetEmptyState/DatasetWidget/ObjectDataTable/PivotTable docblocks are not in the diff at all. `table.noRows` and `engine.form.noRows` untouched and pinned as untouched. Half-state noted, not acted on: the card is still unassigned (PM-side; the claim comment is the identity signal and it names this session).", "tests": "Deadness re-measured three ways, each with a lit control so a zero reads as a measurement: (1) fully-qualified text sweep over packages/apps/scripts — dashboard.noRows 4 hits, dashboard.noDataAvailable 6 hits, every one a comment, while the same query shape returns 2 live tt() call sites for dashboard.empty.title and 3 for dashboard.total; (2) `node scripts/check-i18n-call-site-keys.mjs` green across the deletion, en pack 2,964 → 2,962 keys, 'Every in-scope call-site key resolves against the en pack (2962 keys)' — a live t() for either key lands red here; (3) the reverse sweep listed both before and neither after (dashboard namespace 4-confirmed/3-needs-review → 4-confirmed/1-needs-review), lit controls never in its candidate list. Also excluded the bare-name channels: no keyPrefix / namespaced useTranslation call sites exist, and the only `dashboard.` template in the tree is a test assertion message. UNION RE-RUN AFTER THE FINAL COMMIT, on 2b343c226: `pnpm exec vitest run packages/i18n/ packages/plugin-dashboard/` → 'Test Files 150 passed (150)' / 'Tests 1781 passed (1781)' exit 0; check:i18n-keys exit 0; check:i18n-drift exit 0 ('0 en value(s) changed (0 key(s) added, 2 removed — those are all-locales-key-parity's)'); check-changeset-presence exit 0 ('11 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)') after it ruled exit 1 owed before the changeset existed; check-changeset-no-major exit 0; check-control-bytes exit 0; `pnpm --filter @object-ui/i18n run type-check` exit 0 with `tsc -p tsconfig.test.json --listFiles` proving the new test file is IN that project; `pnpm --filter @object-ui/i18n run lint` exit 0, 33 pre-existing warnings, none in a touched file. All exit codes captured by redirect-then-read, never through a pipe. ABLATION (two legs, run from the committed implementation, trap ... EXIT INT TERM, absolute paths, restore via `git checkout HEAD -- ABSOLUTE_PATH`): no rebuild leg applies — the subject is TS source read through vitest's root alias table, not a dist artifact, so there is no dist/ for a stale build to hide in. Leg 1, nine packs deleted and de left behind: mutation confirmed on disk by de.ts blob == the base blob e063401cd (plus grep both directions), result exit 1 with 'AssertionError: de has 2 key(s) absent from en' NAMING the tenth pack, and the pin red on de :: dashboard.noRows / de :: dashboard.noDataAvailable. Leg 2, deletion fully reverted in all ten: parity GREEN ('Test Files 1 passed (1)' / 'Tests 32 passed (32)') while the pin goes red naming all 20 pack::key pairs — the contrast that is the reason the pin exists. Both legs restored and PROVEN BY STATE: `git diff HEAD` empty AND all ten pack blobs equal their HEAD blobs, with an empty hash read as failure rather than as 'nothing to compare'. One self-correction reported in the PR: leg 1's on-disk probe printed 3 where its annotation said 'expect 2' — the regex also matches `table.noRows` in the same file, the very key this PR keeps; the annotation was wrong, not the leg.", "mcp_calls": "7 — issue_read(get), issue_read(get_comments), create_pull_request, pull_request_read(get, body read-back), search_issues(dedupe), issue_write(create #7575), plus this add_issue_comment", "open_questions": [], "out_of_scope_findings": [ "filed as #7575: four more `dashboard.*` keys (addWidget, editLayout, saveLayout, resetLayout) read as CONFIRMED dead by the reverse sweep — 40 pack entries, independently cross-checked at 0 occurrences outside the packs; the fifth (`removeWidget`) is a substring artifact of `engine.inspector.dashboard.removeWidget`. Filed unassigned, `finding` + `i18n`, with the explicit non-claim that the report-only sweep is not deletion evidence on its own." ] }Narrowings are declared in the PR body (vitest scoped to i18n + plugin-dashboard; eslint and type-check scoped to
packages/i18n, which owns every changed file;check:eager-closurenot run — it needs a full console build, and this diff only moves the measuredi18n-localesbytes DOWN, away from the ceiling).Generated by Claude Code
os-project-manager commentedon Sep 4, 2026
ACCEPT — PR #7573, head
2b343c226. Flipping ready and arming for the queue.Entry qualification: 32 checks, 32 returned against
total_count: 32. 29 success, 3 skipped, zero failures, zero cancelled, zero still running.Verified by content, not adopted from the report
WidgetEmptyState,DatasetWidget,ObjectDataTable,PivotTable— 0 of them in the difftable.noRowskepten.ts:396engine.form.noRowsuntouchedapp-shell, which the diff never reachesfa93d132c2ec59be)⭐ The comment rewrite is the part a careless deletion would have got wrong. The original said "
noRowsabove is the terse fragment it replaces" — a sentence pointing at a row this diff removes, which would have been left dangling at nothing. It now names the retirement instead, and every clause of the original rationale survives. Deleting the keys and leaving that sentence would have been the same defect this card is about, one line lower.Clause-② — I rule
no, and the datum to overturn me with is in the PR⭐ The implementer measured a fact that cuts against its own conclusion and submitted it rather than omitting it.
TranslationKeys = typeof enis exported from@object-ui/i18nand re-exported by@object-ui/react, and after this deletiontscrejectsTranslationKeys['dashboard']['noRows']with TS2339 while the surviving control compiles. That is a published structural type getting narrower, proven rather than argued.I did not let its reasoning settle the routing. Measured independently:
TranslationKeyshas 6 occurrences repo-wide: one declaration (locales/en.ts:4045) and five import/re-export lines. Zero constraint sites — it is never a type annotation, a generic bound, a parameter or a return type. Lit control:I18nConfig, same query shape, is used structurally inprovider.tsx(9 hits), so the query reaches.I18nConfig.resourcesisRecord<string, Record<string, unknown>>,useSafeTranslatetakeskeyOrKeys: string | string[],fallbackTtakeskey: string.⇒ no accept/reject behaviour moves. The only thing that narrows is a snapshot type mechanically projected from the data (
typeof en) that nothing can hold a constraint against. Every catalogue edit moves it; if that were Clause-②, every i18n PR would need a contract review, which is not what the gate is for — and five prior retirements of this exact shape shipped aspatchwithout one.tscreading in the PR body is the datum that says so, andneeds:contract-reviewbelongs on this PR. Re-adding two keys is trivial.The direction argument was confirmed by CI, independently
The implementer declared not running
check:eager-closurelocally (it needs a full console build) and argued by direction instead: catalogue entries only come out, so the measured bytes move down, and it estimated ~294 gzipped bytes across ten packs. CI's own bundle report on this branch reads 3182.4 KB against 3182.7 KB on every other branch tonight — −0.3 KB ≈ 307 bytes. The estimate and the measurement agree, which is a better outcome than either a bare number or a bare hand-wave.Also recorded
noRowscount inen.tsreturns 3, and two of the three are other keys. The positional deletion inside eachdashboard: {block is what kept them.dashboard-emptyState-keys-retired-7125.test.ts, sixth in the [finding] The wholereport.editor.*namespace — 106 keys × 10 packs — has no consumer left; the report editor it labelled became the spec-driven inspector #4145/[finding]common.searchhas zero readers repo-wide after PR #4391 — retire from the ten packs, or keep as vocabulary? #4392/Reverse i18n sweep: ~384 confirmed-dead locale keys across 58 namespaces (workflow/BPMN designer entirely unwired, console.objectView oversized, more) #4730/Console:OS_CLOUD_URL=offrenders the marketplace as a red load-failure with wrong guidance, and the home page still promotes the marketplace entry #5504/finding(i18n):appDesigner.fieldDesigner.formulais now a dead key — 10 locale packs plus the designer defaults map, zero call sites #6310 series) earns its place: the ablation's second leg shows the parity gate green on a fully reverted deletion while the pin goes red naming all 20 pack::key pairs. Ten packs agreeing on a dead key is exactly what parity wants — nothing else in this repo can see a dead key come back.dashboard.*keys read as CONFIRMED dead by the reverse sweep —addWidget,editLayout,saveLayout,resetLayout#7575 filed: four moredashboard.*keys read as confirmed dead by the reverse sweep, with the fifth excluded as a substring artifact. Unassigned, and explicitly not treated as deletion evidence on its own.Generated by Claude Code