Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-detail): ActivityTimeline's empty state resolves from the locale packs - #7150
Conversation
…locale packs
The empty-state title was a raw English JSX literal —
`title="No activity recorded"`, not a `t()` call and not an inline
`defaultValue` — so it never reached the pack system and stayed English in all
ten locales. Measured by rendering `activities={[]}` under a zh `I18nProvider`:
the card read "Activity(0)No activity recorded" while its sibling
`RecordActivityTimeline` rendered "活动(0)全部动态暂无活动记录" from the same
packs.
The call site now reads `detail.noActivity` through `useDetailTranslation`, the
key the sibling already uses. Reusing it rather than minting a second key is a
measured decision, not an assumption: the `en` pack value for that key is
'No activity recorded', byte-identical to the literal it replaces, so both
surfaces were already saying the same words in English and a second key would
have forked one sentence across ten packs for no copy difference. The key is
already translated in all ten packs (read out of each pack object, with a
present-key control and an absent-key control), so no pack is edited here, and
`DETAIL_DEFAULT_TRANSLATIONS` already carries the byte-identical English row
that serves a provider-less host.✅ 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-warren
commented
Sep 1, 2026
✅ Reviewed and armedAll 30 check runs in one call at You verified the thing I got wrong, instead of taking my wordI told you to determine which i18n system this file belongs to rather than trust me — because I gave the wrong answer on objectui#7121, where You went to the code: The pack probe avoided the trap it needed to⛔ A dotted-key grep for This seat has been bitten by exactly that false zero before. Good. Ruling 2 settled by rendering, not by assumingThe Ablation Three NOT MEASURED, named rather than counted
|
Fixes#7142
ActivityTimeline's empty-state title was a raw English JSX literal —title="No activity recorded", not at()call and not an inlinedefaultValue— so it never reached the pack system and stayed English in all ten locales. It now readsdetail.noActivitythroughuseDetailTranslation, the hook its sibling already uses.Three lines of production change: the hook import, the
const { t } = useDetailTranslation();call, and the title expression. No pack was edited.The key-reuse decision was measured, not assumed
The card and the dispatch both flagged this as the thing to settle first:
RecordActivityTimeline.tsx:415already renderstitle={emptyLabel ?? t('detail.noActivity')}, but that proves a key exists, not that the two surfaces want the same words. So both were rendered empty and read.The
enpack value fordetail.noActivityis'No activity recorded'— byte-identical to the literal being replaced. The two surfaces were already saying exactly the same words in English, so reuse changes no copy in any locale, while a second key would fork one sentence across ten packs for zero copy difference. Reuse it is.The key is genuinely translated in all ten packs
Read out of the pack objects directly rather than grepped — the packs are nested (
detail: {…noActivity:), so a dotted-key grep fordetail.noActivityreturns zero against a pack that defines it. Two controls ran on the same instrument:detail.noRelatedRecordsanddetail.loadingreturned present in all ten (positive), anddetail.zzzNotAKeyEverreturned absent in all ten (negative, proving the probe can report absence).DETAIL_DEFAULT_TRANSLATIONSalready carries the byte-identical English row, so the provider-less path is unchanged anddefaults-maps-mirror-en-packstays green. No inlinedefaultValueanywhere (#3517).Test
packages/plugin-detail/src/ActivityTimeline.i18n.test.tsx, six cases: zh, ar, en, no-provider, never-the-raw-key, and the second route into the box — a populated timeline filtered down to a type with no entries.zhandarare the load-bearing half. Anen-only assertion would have been green before the fix too, since the literal and theenpack value are the same string; only a non-Latin locale discriminates a pack lookup from a hardcoded literal.Ablation
Predicted before running: restoring the literal turns exactly the three locale-discriminating cases red and leaves the other three green. Observed
Tests 3 failed | 3 passed (6)— the passing count is quoted deliberately, since a suite collapsed to zero survivors also shows red and would look stronger.Mutation proven on disk by marker count in both directions (
t('detail.noActivity')1 to 0,title="No activity recorded"0 to 1) and by blob hash (6bb6ce32to678d2e03), never by an editor's exit code. Restore proven by state —git diff HEAD,git diff --cachedandgit status --shortall empty, and the blob back to6bb6ce32— never by a trap firing. No rebuild leg applies: the test imports./ActivityTimelinerelatively and the root vitest config aliases@object-ui/i18ntopackages/i18n/src, so nothing in the closure resolves throughdist/.Verification
All readings below are on
0e210de56, the final commit.Green:
pnpm exec vitest run packages/plugin-detail/ packages/i18n/src/__tests__/all-locales-key-parity.test.ts packages/app-shell/src/__tests__/defaults-maps-mirror-en-pack.test.tsx—Test Files 123 passed (123),Tests 1157 passed (1157)check:i18n-keys— "Every in-scope call-site key resolves against the en pack (2845 keys) … no call site carries a literal fallback beside itself" (2604/2604 literal keys resolve)check:i18n-drift— "No en value changed in this range."check:i18n-dead-keys— report, not a gate;detail.noActivitygains a call site here rather than losing onecheck-changeset-presence— "1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"check-changeset-no-major— "No changeset declares amajorbump." (quoted rather than predicted)type-check(tsc --noEmit && tsc -p tsconfig.test.json) — exit 0. Confirmed it actually covers the diff:--listFilesputsActivityTimeline.tsxin the main pass andActivityTimeline.i18n.test.tsxin thetsconfig.test.jsonpass, with a present-file control and a nonexistent-path control both behaving.eslint .inpackages/plugin-detail— 0 errors. The one warning onActivityTimeline.tsx(Filterimported unused, line 11) is pre-existing onorigin/mainand untouched here.check:control-bytes,check:self-import,check:esm-specifiers,check:phantom-deps,check:vi-mock-specifiers,check:vi-mock-inherit,check:dist-completeness,check:side-effects-arrayNOT MEASURED — recorded as the absence of a reading, not as passes:
check:sdui-registration-pins— exit 2, "a run with nothing to read has measured nothing. Build the console first". The console build needs 28 unbuilt packages; that full-monorepo build is CI's, and this diff registers no SDUI component.check:eager-closure— exit 2, "a broken gauge, not a sensitive gate": noapps/console/dist/eager-closure.jsonlocally. Same cause.check:readme-exports— the gate declares its own run void ("the population COLLAPSED — this run proves nothing") while packages are unbuilt. Narrowed and declared: after building@object-ui/plugin-detail, its README dropped out of the unjudged list (1 hit to 0), so this package's slice is judged; the residual collapse is the other 24 unbuilt packages.type-checkfirst came back exit 2 with TS2307 across every@object-ui/*— an unbuilt dependency closure, which is a non-reading rather than a red. The closure was built (pnpm --filter '@object-ui/plugin-detail^...' build) and it was re-run to get the exit 0 above.Scope
Only the empty-state title, per the dispatch ruling. Sweeping the file found the component uses no translation hook at all on
origin/main— 18 further hardcoded English literals (relative timestamps, the six filter chips, theActivitycard title, the filteraria-label, the six activity-description sentences), 12 of which already have pack keys sitting unused. That inventory is filed as #7149, unassigned, rather than folded in here.No public export added, no accept/reject behaviour changed — contract clause 2 stays disengaged. No
rolewas added, removed or aligned on theDataEmptyStatecall site; that is #7132's decision and it is already made.Generated by Claude Code