Skip to content

fix(i18n): align the executive pipeline tile to its shared factory, and guard the class - #1360

Merged
os-steve merged 3 commits into
mainfrom
claude/issue-980-shared-widget-i18n-drift
Aug 27, 2026
Merged

fix(i18n): align the executive pipeline tile to its shared factory, and guard the class#1360
os-steve merged 3 commits into
mainfrom
claude/issue-980-shared-widget-i18n-drift

Conversation

@claude

@claude claude Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #980

All four locale bundles gave executive_dashboard's pipeline_by_stage tile a different description from the crm_overview_dashboard and sales_dashboard entries fed by the same shared factory. This aligns the four drifted entries to the factory literal and adds the guard that keeps them there.

The hole

src/dashboards/shared-widgets.ts exists so that "what counts as open pipeline" is stated once — #539 deleted the three source-side copies of the pipeline funnel by moving the definition into a factory the three dashboards each call. The i18n side was never deduplicated. Every dashboard still carries its own dashboards.DASHBOARD.widgets.WIDGET.description in every locale, so one factory-owned sentence is stored as twelve independently editable strings — the exact shape #539 removed, one layer down.

Nothing was watching: pnpm lint --skip-i18n skips the bundles, and test/i18n-references.test.ts asserts key coverage — every authored surface translated, every key resolving — never that two entries fed by one source string still agree.

Re-measured on main @ 46238e1

Source of truth, src/dashboards/shared-widgets.ts:35:

description: 'Open opportunity value at each sales stage',
             crm_overview            executive  (drifts)        sales
en.ts    :1380 at each sales stage   :1395 by sales stage   :1408 at each sales stage
zh-CN.ts :1375 按阶段统计的…          :1390 按销售阶段统计的… :1403 按阶段统计的…
es-ES.ts :1446 en cada etapa…        :1461 por etapa…       :1474 en cada etapa…
ja-JP.ts :1391 各営業ステージの…      :1406 営業ステージ別の…  :1419 各営業ステージの…

title is uniform in all twelve entries; only description forked. All four bundles drift in the same direction, which is a translation lineage rendered from one early copy, not four independent typos.

Correction to a claim in the card

The card (and the triage comment) state that en.ts's description also disagrees with the source, making "a fourth wording". Measured false. en.ts:1380 and en.ts:1408 are byte-identical to shared-widgets.ts:35; only en.ts:1395 differs. There is no fourth wording — there are two, and the source already agrees with two of the three English entries. The two correct entries are left alone.

The guard, and why it is not "all entries must be equal"

The blunt rule would have been red on landing for a legitimate reason. avgDealSizeMetricWidget takes overrides and Sales uses them — its tile is pinned to the quarter, so its description ends "this quarter" while CRM's does not, and all four bundles already reflect that correctly. Failing it would teach authors to erase a real distinction.

So test/i18n-shared-widget-parity.test.ts states the invariant against the source, one level up from the strings:

a locale must group dashboards by description exactly the way the source groups them.

One rule, both failure directions — forking a description the factory unifies, and merging one the source deliberately keeps apart. A second assertion anchors the baseline itself: for a factory-owned widget the en bundle is not a translation but the same sentence, so it must reproduce the literal byte for byte. Without it a family of entries could agree with each other perfectly and still all disagree with the code that renders them.

Widget ids are discovered by invoking every factory the shared module exports, not transcribed into the test, so a factory added later is covered the day it lands. Occurrences are matched by widget id rather than by "was this a factory call", so a hand-inlined copy is held to the same rule.

Red before green — both states shown

This card inverts the usual order deliberately: the existing drift is precisely what the guard exists to catch, so it must fail on the pre-alignment tree. That red is the reverse-verification. A guard never observed failing has not been shown to work. The two states are separate commits.

d3005d4 — guard only, on the unaligned tree. RED, naming all four entries:

FAIL test/i18n-shared-widget-parity.test.ts > groups dashboards exactly the way the source groups them

  en · pipeline_by_stage: source groups these dashboards as
    [crm_overview_dashboard+executive_dashboard+sales_dashboard],
    en groups them as [crm_overview_dashboard+sales_dashboard | executive_dashboard]
      executive_dashboard
        source: "Open opportunity value at each sales stage"
        en: "Open opportunity value by sales stage"
  zh-CN · pipeline_by_stage: … executive_dashboard: "按销售阶段统计的进行中商机金额"
  ja-JP · pipeline_by_stage: … executive_dashboard: "営業ステージ別のオープン商談金額"
  es-ES · pipeline_by_stage: … executive_dashboard: "Valor de oportunidades abiertas por etapa de venta"

FAIL … > reproduces the factory literal verbatim in the English bundle
  executive_dashboard · pipeline_by_stage
    src/dashboards/shared-widgets.ts: "Open opportunity value at each sales stage"
    src/translations/en.ts:           "Open opportunity value by sales stage"

Tests  2 failed | 1 passed (3)

Note what did not fail: avg_deal_size, whose per-dashboard override is legitimate. The guard is red on the fork and silent on the deliberate difference — which is the whole point of stating it as a partition.

b07933b — four lines aligned, one per locale. GREEN:

Test Files  1 passed (1)
     Tests  3 passed (3)

The third test is an anti-vacuity check: it fails if no factory is found, no locale pack loads, or no shared widget is used by more than one dashboard — so the parity assertion can never pass by reading nothing.

Verification

pnpm verify green in full, re-run after the changeset correction on the exact tree at ecd9e18 (working tree clean, so the run and the commit are the same bytes):

✓ i18n lint gate: 0 `i18n/missing-*` issues (55 total lint issue(s) reported, unaffected by this gate)
✓ source hygiene clean
✓ source token ratchet clean
  Artifact: dist/objectstack.json (1969.8 KB)
Test Files  144 passed (144)
     Tests  3012 passed | 1 skipped (3013)

The three fast gates were re-run at ecd9e18 after the final commit; the verdict lines above are each gate's own output, not a shell exit code. Confirmed the new suite is really measured rather than silently skipped: 144 test/*.test.ts files on disk against 144 files in the run, and tsc --noEmit --listFiles includes the new file.

Scope

  • In: the four executive pipeline_by_stage.description entries, and the guard.
  • Out: i18n key restructuring — giving the three dashboards one shared key. The card calls it a platform-convention question and it is adjudicated out of scope.
  • Out: title, already uniform across all twelve entries. The guard asserts description only.
  • Out: content/docs/analytics/dashboards.mdx:129 and the Quick Tour section, which quote the source text and are unaffected.
  • No docs-anchor or docs-drift test file is touched; the guard lands in the i18n family as a new file.

No other shared-factory widget drift surfaced. With the guard covering both exported factories across all five registered dashboards and all four locales, avg_deal_size is clean — nothing to report for a follow-up card.

Changeset

'hotcrm': patch.

It landed as empty frontmatter first — the "releases nothing" declaration changeset-check.yml sanctions — and that was wrong. The locale bundles compile into the shipped artifact, which this PR's own verify run prints as Artifact: dist/objectstack.json (1969.8 KB), so four user-visible strings in four locales ship with it. The change is still a synonym and the changeset still says so, but that is a statement about severity, not about whether something shipped. Every other empty-frontmatter changeset in that directory earns the exemption by being test-only, prose-only or CI-only; this one edits src/ and cannot borrow the sentence, and a false declaration in the release ledger is worse than an over-counted patch. Corrected in ecd9e18, confirmed with changeset status --since=origin/main:

info Packages to be bumped at patch:
- hotcrm

Generated by Claude Code

claude added 2 commits August 27, 2026 09:12
`src/dashboards/shared-widgets.ts` states each shared widget's semantics
once, but every dashboard still carries its own locale entry for it, so
one factory-owned sentence is stored as (dashboards x locales) freely
editable strings. Nothing watched them: `pnpm lint --skip-i18n` skips the
bundles and `test/i18n-references.test.ts` asserts key coverage, not that
two entries fed by one source string still agree.

The rule is stated against the source rather than as "all entries must
match": a locale must group dashboards by description exactly the way the
source groups them. That tolerates `avgDealSizeMetricWidget`'s deliberate
per-dashboard override (Sales pins itself to QTD) while still catching a
fork of a description the factory unifies. A second assertion anchors the
baseline itself — for a factory-owned widget the `en` bundle is not a
translation but the same sentence, so it must reproduce the literal.

This commit is deliberately RED. The existing drift is precisely what the
guard exists to catch, and the alignment lands in the next commit — a
guard never observed failing has not been shown to work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WMzCeNC4SZcPNBpE2zCVCg
All four locale bundles gave `executive_dashboard`'s `pipeline_by_stage`
a different description from the `crm_overview_dashboard` and
`sales_dashboard` entries fed by the same factory — "by sales stage"
against "at each sales stage" in `en`, and the same split in `zh-CN`,
`es-ES` and `ja-JP`. Four bundles drifting the same way is a translation
lineage rendered from one early copy, not four independent typos.

The baseline is the factory literal in `src/dashboards/shared-widgets.ts`,
not the wording two of the three entries happen to share. Those coincide
today; anchoring on the source is what keeps them coinciding — and it is
what the guard's English assertion pins.

Four lines change, one per locale. `title` was already uniform and is
untouched. No user reads a different meaning: the Executive tile now
reads exactly as the identical tile already read on the other two
dashboards. The guard added in the previous commit goes from red to green
on this diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WMzCeNC4SZcPNBpE2zCVCg
@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 27, 2026 9:26am

Request Review

@github-actions github-actions Bot added ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces labels Aug 27, 2026
The frontmatter was empty, declaring that this PR publishes nothing. That
declaration is false. The locale bundles compile into the shipped
artifact — the same `pnpm verify` run that gates this PR prints
`Artifact: dist/objectstack.json (1969.8 KB)` — so four user-visible
strings in four locales ship with it.

The change is still a synonym, and the changeset still says so: no user
reads a different meaning, and the Executive tile now reads exactly as
the identical tile already read on the other two dashboards. But that is
a statement about severity, not about whether something shipped. Every
other empty-frontmatter changeset in the directory earns the exemption by
being test-only, prose-only or CI-only; this one edits `src/` and cannot
borrow that sentence. A false declaration in the release ledger is worse
than an over-counted patch.

Changeset frontmatter and its justifying paragraph only. The four
translation edits, the guard and `title` are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WMzCeNC4SZcPNBpE2zCVCg
@os-steve
os-steve marked this pull request as ready for review August 27, 2026 09:29
@os-steve
os-steve added this pull request to the merge queue Aug 27, 2026
Merged via the queue into main with commit 3e1b00b Aug 27, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants