Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-detail,i18n): localize record:path's container labels and announce its goal terminus - #6000
Merged
Merged
Conversation
…inert group label
`record:path` had two container `aria-label` literals that objectui#5916
deliberately left alone while it localized every stage inside them. They were
two different defects and get two different fixes.
1. Both the desktop and the mobile `role="list"` row did
`aria-label={schema.aria?.label || 'Record path'}`. A zh/ja/ar session heard
`Record path` for the list while every stage inside announced in the session
locale — one control speaking two languages at once. The fallback is now
`detail.pathLabel`, added to all ten packs with real translations and to
`DETAIL_DEFAULT_TRANSLATIONS` (which `defaults-maps-mirror-en-pack` compares
against the `en` pack key by key, so the two hosts cannot fork). The
`schema.aria.label` author override still wins ahead of it.
2. The lost-terminal alt group carried `aria-label="Alternative terminal
stages"` on a bare `div`. A `div` is `generic`, and browsers expose no
accessible name on a generic element, so that string reached nobody: inert,
not merely untranslated. Translating it would have shipped copy to ten packs
that no user can hear. It is removed rather than given a `role` that takes a
name, on three measurements recorded in the renderer: nothing is lost
(it was never announced); it would be redundant (every stage inside already
announces `closed lost` in the session locale after objectui#5916, in the one
place `role="list"` can carry it); and it would fork the two rows (the mobile
row renders one flat list with no alt group, so a named group would make one
control expose two structures by viewport).
`record-path.containerLabel.test.tsx` pins both halves: the list name follows
the session locale across en/zh/de/ja with a non-vacuity case, the author
override still wins, no element claims the old group name or a named role, and
the lost stages still announce their own terminal state — the positive fact that
makes the removal safe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L`railClass` paints an unreached `won` terminus `bg-emerald-500/30` where a plain
upcoming stage gets `bg-muted` — the renderer's own note calls this "a faint
emerald so the goal is legible" — while both announced the identical
`{{stage}}, upcoming`. Two stages ahead of the record painted differently and
read the same: the WCAG 2.2 SC 1.4.1 class objectui#5916 closed, on the one
distinction it left behind. Reachable without authors opting in, because
`classify()` finds `won` through the `WON_TOKENS` heuristic
(`won|success|成交|赢|完成`) as well as an explicit `terminal: 'won'`.
New key `detail.pathStageWonUpcoming` — `{{stage}}, goal stage, not reached` —
in all ten packs and in `DETAIL_DEFAULT_TRANSLATIONS`. "Goal" is the file's own
word for this stage, not new vocabulary.
Scoped to the UNREACHED goal, which is a measurement of the stylesheet rather
than a preference. A reached goal terminus paints `bg-primary` when current and
`bg-emerald-500` when completed — byte-identical to any other current or
completed stage — so naming it apart would hand a screen reader a distinction
the screen does not make, which is the mirror image of the defect, and would be
dead copy in ten packs besides. One new key, not a pair; a test pins that
decision so it cannot drift into a fourth state unnoticed.
`record-path.wonTerminusAccessibleName.test.tsx` asserts the card's criterion as
the PAIR it is — the goal stage's name must differ from a plain upcoming stage's
while both carry `data-stage-state="upcoming"` — rather than only against a
literal, which would still pass if every upcoming stage later became a goal
stage. Both the explicit and the heuristic route are exercised, on both rows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7LContributor
✅ 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
|
yinlianghui
marked this pull request as ready for review
August 24, 2026 11:44
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 24, 2026
Merged
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#5956
Fixes#5957
record:pathfinishes localizing and de-colouring its accessible names — the two residues #5916 named and deliberately left behind. One branch, two commits, one per card, each independently reviewable and independently verifiable.⛔ #5916 is merged and is not touched here: its travelled / current / upcoming / lost-terminal names are unchanged, and its two suites still pass untouched. This builds on the seam it established (
detail.pathStage*,record-path.stageStateAccessibleName.i18n.test.tsx).Member A — the container labels (
#5956)Two defects wearing the same clothes, and they get two different fixes.
A1 — an English literal on a localized surface. Both the desktop and the mobile
role="list"row didaria-label={schema.aria?.label || 'Record path'}, so a zh/ja/ar session heardRecord pathfor the list while every stage inside it announced in the session locale — one control speaking two languages at once. The fallback is nowdetail.pathLabel, translated in all ten packs. Theschema.aria.labelauthor override still wins ahead of it, and that is asserted, not assumed.A2 — an
aria-labelthat named nothing. The lost-terminal alt group carriedaria-label="Alternative terminal stages"on a barediv. Adivhas thegenericrole and browsers expose no accessible name on a generic element, so that string reached nobody: inert, not merely untranslated. Translating it would have shipped copy to ten packs that no user can hear.Decision: remove it rather than give the wrapper a
rolethat takes a name. The card asked for this to be measured, so it was, on three counts:closed lostin the session locale, in the one placerole="list"can carry it. The forward/alt distinction is already in the accessible name; the group label would restate it.record-path.stageStateAccessibleName.i18n.test.tsxstates in its own header ("both rows carry identical names").The reasoning is recorded in the renderer beside the code, so the next reader does not re-litigate it.
Member B — the goal terminus (
#5957)railClasspaints an unreachedwonterminusbg-emerald-500/30where a plain upcoming stage getsbg-muted— the renderer's own note calls this "a faint emerald so the goal is legible" — while both announced the identical{{stage}}, upcoming. Two stages ahead of the record painted differently and read the same: the WCAG 2.2 SC 1.4.1 class #5916 already handled, on the one distinction it left behind. Reachable without authors opting in, sinceclassify()findswonthrough theWON_TOKENSheuristic as well as an explicitterminal: 'won'; both routes are exercised.New key
detail.pathStageWonUpcoming—{{stage}}, goal stage, not reached— in all ten packs. "Goal" is the file's own word for this stage (from therailClasscomment) and the word both cards use, not new vocabulary.Decision: one new key, not a pair — and that scope is a measurement of the stylesheet, not a preference. The defect is information carried by colour alone, so the name may only restate a distinction the colour actually makes:
bg-emerald-500/30vsbg-mutedbg-primary— identical to any current stagebg-emerald-500— identical to any completed stageSo a reached goal terminus keeps announcing as an ordinary current/completed stage. Naming it apart would hand a screen-reader user a distinction the screen does not make — the mirror image of the defect, and dead copy in ten packs besides. Because it is a decision rather than an omission, it is pinned by a test so it cannot drift into a fourth state unnoticed.
terminalis read from the same valuerenderStagehandsrailClass, so the name tracks the paint on each row by construction rather than via a second, drift-prone classification.Verification
All gate output below is from
a178b0237, the branch head and the final commit.pnpm exec vitest run packages/plugin-detail/ packages/i18n/Test Files 152 passed (152)/Tests 1825 passed (1825)Test Files 21 passed (21)/Tests 224 passed (224)pnpm check:i18n-drift0 en value(s) changed (2 key(s) added, 0 removed)…No en value changed in this range.pnpm check:i18n-keysEvery in-scope call-site key resolves against the en pack (2936 keys)…no call site carries a literal fallback beside itselfpnpm check:control-bytes✅ check-control-bytes: OK (scanned 4959 tracked text file(s); skipped 85 binary).type-check— plugin-detail, i18n, app-shellDone, exit 0 (app-shell after building its closure)eslint . --no-inline-config(whole repo, 3624 files)pnpm --filter … lint(as CI invokes it)0 errors, exit 0Reverse verification, both members, direction predicted before running.
'Record path': 3 of 8 red — the zh, de/ja and non-vacuity cases — while theen, author-override and group-absence cases stay green, which is correct since none of them depend on the pack key.wonbranch: 4 of 8 red, including the load-bearing pair case.The first
wonrun turned up a bug in my own test, which is worth stating plainly. The pair case originally compared the two wholearia-labelstrings and passed with the fix reverted — vacuously, because已成交and谈判中differ regardless of state. It now compares the state half (the name with the stage's own label stripped), which fails as it should. The mutation and its restore were each confirmed on disk by grep, not by an editor exit code, and every mutation ran under a restoretrap.The 15
no-explicit-anywarnings on the renderer are pre-existing: theanycount inrecord-path.tsxis 15 at the merge base and 15 at head, and the only added lines matchinganyare prose plus the two rewrittenaria-labellines that already carriedas any.Fence note
packages/plugin-detail/src/useDetailTranslation.tsis edited although the dispatch fence enumerated only the renderer, the packs, the renderer's tests and a changeset. Both call sites are baret()with no inlinedefaultValue, so without a row inDETAIL_DEFAULT_TRANSLATIONSa provider-less host would render the raw keydetail.pathLabelinto the label. The shape is pinned by existing evidence sitting in the same file — #5916 put its own fivedetail.pathStage*rows in exactly this map — anddefaults-maps-mirror-en-pack.test.tsxcompares the map against theenpack key by key, so both new rows are now covered by it. Same defect class, same gate family, no other claim on the file.Filed, not fixed here
#5998 — the desktop and mobile rows disagree about which stage is a
wonterminus (desktop restricts it to the last forward stage, mobile accepts any won-classified stage), so a mid-path完成paints and now announces two ways by viewport. The paint half predates this PR; deriving the name from the same valuerailClassconsumes keeps each row self-consistent and leaves the underlying disagreement exactly as it was. Which row is right is a call about what the heuristic is for, so it is filed unassigned rather than folded in.Generated by Claude Code