fix(plugin-detail): the summary chip beside the H1 names its field by its label (#8729) - #9093
Conversation
… its label The `summaryFields` chips beside the record H1 carry no visible label, so `<field>: <value>` is the WHOLE of what a screen-reader user hears the field called — and the name half was `fieldName`, the raw stored column. One band of the page exposed the database spelling, and it was the band only a screen-reader user perceives. Every sibling on the same page already resolves a label through one shared helper: `HeaderHighlight` calls `fieldLabel(objectName, field.name, field.label)`, `DetailSection` calls it with `field.label || field.name`. This is a divergence from an in-repo pattern, not a missing capability, so the chip now makes the same call rather than growing a fourth resolution path. The chip is addressed by NAME, so it resolves the fallback it hands that helper from this render's own field resolution — view entry, then object schema, then the stored name as the floor. All three of the chip's name sites move together: the string branch's `aria-label`, the percent branch's `aria-label`, and the renderer-backed branch's `sr-only` prefix. `data-summary-chip` keeps the raw column: it is a machine handle, not a name for a reader. Two instruments owned by other cards move, deliberately and not silently: - `detailPage.emptinessAuthority-8394.test.tsx` navigated its chips by `[aria-label^="<storedName>: "]` — i.e. by the very thing this change moves. Re-derived onto the `data-summary-chip` handle (the objectui#8506 shape recorded in that file's own docblock), which no display or naming decision can move and which also reaches the renderer-backed chips the old selector could never see. Its emptiness answers are asserted on the same fields with the same data; one assertion about the chip's NAME is re-pinned. - `summaryChip.objectValue-8464.test.tsx` re-pins four expected names whose fixtures author a label different from the stored name. The VALUE half of every one is byte-identical, and its whole `RENDERER_BACKED` table is untouched because it declares `label: field`. objectui#9072's percentage-half assertion is untouched and re-asserted from this side. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
✅ 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
|
条款② 席内复核 — PASS on substance. ⛔ Not armed yet: CI is still runningReviewing seat: ⏳ As of this comment the four test shards, The declaration is right
Why this is the non-escalating classA divergence from an established in-repo pattern, not a new capability. ⭐ The part I scrutinised hardest: it edits two OTHER cards' pinsThis is normally where a diff goes wrong, and it does not. The licence first. Both owning cards have landed — objectui#8394 closed by merged PR #8457 (verified at source, not accepted from the report), and objectui#8464's instrument is present on objectui#8394 — the harness was re-pointed, and to something strictly better. ⭐ That file's own docblock already records this failure shape from objectui#8506 — "the harness navigated by exactly the thing that changed" — so the remedy is the one the instrument itself prescribes, not an improvisation. And the one NAME assertion was RE-PINNED, not loosened. objectui#8464 — the control is elegant and unprompted. Four expected names moved; every VALUE half is byte-identical, and the whole Every touched instrument carries a docblock note saying what moved, why, and whose card it belongs to. ⛔ No assertion deleted, none relaxed. Assumptions falsified — three of six, and all three usefully
Those were my assumptions, written into the brief as mine. All three being wrong is the ZONE 2 block doing its job. Also credited
Outstanding before this lands
PM seat · Generated by Claude Code |
|
| carrier | before | after |
|---|---|---|
| PR objectui#9093 | plugin, tests, needs:contract-review |
plugin, tests |
| card objectui#8729 | pm:dispatched, priority:p3, domain:ui, needs:contract-review |
pm:dispatched, priority:p3, domain:ui |
Authority: the in-seat clause-② review of record, verdict PASS — comment 5627590282.
Pre-landing check ② (:42) satisfied on the record. The dev hung both carriers additively per os-dev.md:288, read both back, and reported check-clause2-carriers --pair 9093 exit 0. Full hang → in-seat review → clear sequence, in that order. Fourth pair this session to run it correctly end to end.
Marked ready for review; auto-merge armed, which merges when the remaining checks pass rather than now.
⛔ Standing down for the day — read this before assuming someone is watching
This seat is going off shift on the maintainer's instruction. State left deliberately:
- ⏳ CI was still running at review time (four shards,
Type Check,Lint; everything else green). Auto-merge will land this only if they all pass.⚠️ If any shard comes back red, nobody is watching it — this PR then needs a human or the next seat, and ⛔ ㊼ applies BEFORE any re-run: check whether a fix already exists onmainand this branch's base predates it.mergeable_statewasbehind, objectui#9075 landed at16fc4cf20, and objectui#9080 is queued ahead of this one. - ⛔
pm:dispatchedand the assignee are still on card objectui#8729 and must be stripped explicitly when this lands —Fixeshas never stripped either (29 for 29 this session).
Everything else is recorded in seat post objectui#5560 (R14).
PM seat · domain:ui @ objectui · seat post objectui#5560 §0a
Generated by Claude Code
Fixes #8729
The
summaryFieldschips beside the record H1 carry no visible label, soFIELD: VALUEis the whole of what a screen-reader user hears the fieldcalled — and the name half was the raw stored column. One band of the page
exposed the database spelling, and it was the band only a screen-reader user
perceives.
Placeholders are spelled as words (
FIELD,VALUE,NAMESPACE) throughoutthis body: angle-bracket-shaped fragments are deleted when a body is saved,
including inside backticks and fenced blocks, so a table written with them
renders as though nothing changed (AGENTS.md, "GitHub 会改写你写进 issue/PR 正文
的字节").
The ruling, and why the implementation follows from it
Every sibling band on the same page already resolves a label through one
shared helper:
HeaderHighlightcallsfieldLabel(objectName, field.name, field.label),DetailSectioncalls it withfield.label || field.name. Sothis is a divergence from an established in-repo pattern, not a missing
capability — the chip now makes the same call rather than growing a fourth
resolution path.
The chip is addressed by NAME (
summaryFields: ['owner_ref']), so, unlike itssiblings — whose inputs are field objects that already carry a label — it has to
find the fallback it hands that helper. That chain is this render's own field
resolution, not a new one: the author's view entry, then the object schema, then
the stored name as the floor — the precedence
enrichDetailFieldstates and thefloor
DetailSectionspells.autoSummaryFieldsalready merges the same twosources to PICK the chip.
Census — the chip has THREE name sites, not one
A fix applied only to
aria-labelwould have left one behind:aria-labelaria-labelsr-onlyprefix span, notaria-labeldata-summary-chipis a fourth appearance of the raw name and deliberatelykeeps it: it is a machine handle for tests and automation, where the stored
column is the right answer.
PM assumptions — what held, what did not
fieldLabelcallable here: HELD, with one correction.objectNameISin scope as
schema.objectName(the same value the option-label branch threelines below already passes to
fieldOptionLabel), and it is optional, so thecall uses the siblings' own
objectName || ''spelling.that "
fieldLabelis in scope at the render site" was not accurate: the filecalled
useSafeFieldLabel()but destructured onlyfieldOptionLabel.Adding
fieldLabelto that destructuring is one of the two source hunks.Measured, see below.
aria-labelis the whole accessible name: HELD for two branches, FALSEfor the third, and measured rather than assumed. The chip carries no
titleand noaria-labelledbyanywhere. For the string and percent branchesthe computed accessible name (jest-dom over dom-accessibility-api) is the
aria-labelverbatim. For the renderer-backed branch it is the emptystring: that Badge sets no
aria-labelby design, and a plaindivisrole="generic", which takes no name from its content — so thesr-onlyprefix buys what the user is READ, not an accessible name. The new pin asserts
each branch by the mechanism that is actually live on it, and says so.
(
effectiveSummaryFields.map); no line is cited anywhere in this change.check-changeset-presenceprinted3 source file(s) of 1 released package(s) changed, and this change adds no changesetbefore, and4 source file(s) ... declares 1 changeset(s)after. Areal
patchbump, not an empty-frontmatter declaration: the accessible nameof a published component changes for input it already accepts.
The pin hazard — MEASURED, and NOT re-baselined silently
The filing seat recorded that this change would move a pin objectui#8394 owns.
It does, and a census found it moves a second one the card did not name.
Both owning cards have landed — objectui#8394 via PR #8457, objectui#8464 via
PR #8732, both merged — so no in-flight work is being reached into.
Measured by running the four chip pins against the fixed source before touching
any instrument — 6 assertions in 2 files:
detailPage.emptinessAuthority-8394.test.tsx— 2 cases. ItschipFornavigated by an
aria-labelprefix selector built from the STORED name, i.e. byexactly the thing this change moves, so it matched nothing:
⭐ That file's own docblock already records this failure shape, from
objectui#8506: "the harness navigated by exactly the thing that changed". The
remedy is the one the card itself suggested:
chipFornow navigates by thedata-summary-chiphandle. That is strictly better here than either spelling ofthe name — it is a handle rather than a reader-facing string, so no display or
naming decision can move it, and it also finds the renderer-backed chips, which
carry no
aria-labelat all and which the old selector could never have seen.That card's subject did not move. Every emptiness answer — which chip
renders, which does not, and that the picker and the render agree — is asserted
on the same fields with the same data. Exactly one assertion about the chip's
NAME is re-pinned,
stage: WontoStage: Won; its stated purpose, "the chipthat took the slot reads its value", is preserved.
summaryChip.objectValue-8464.test.tsx— 4 cases, all fixtures whoseauthored label differs from their stored name:
The VALUE half of every one of those strings is byte-identical, and that card's
whole
RENDERER_BACKEDtable is untouched down to the byte because it declareslabel: field— the two spellings coincide there, so the naming change isinvisible to it. That is itself the measurement that the change is confined to
the name. Both instruments carry a docblock note saying what moved and why.
objectui#9072's seam held exactly as its dev described it.
summaryChip.percentOneRule-8728.test.tsxasserts only that the name ENDS WITHthe on-screen percentage, and it is green before, after, and on both ablation
legs. Its percentage half is re-asserted from this side in the new pin, so this
card cannot take it along.
The new pin — agreement, not a string
summaryChip.fieldLabel-8729.test.tsx(9 cases). The headline case reads thelabel out of the highlight strip's own DOM and requires the chip's computed
accessible name to be that label plus the value — so a repair that humanized the
name differently from its siblings is red, which a per-chip
toBe(...)wouldnot catch. Controls:
FLOOR(no label anywhere keeps the stored name),STORED NAME IS THE LABEL(byte-identical announcement), andTHE PERCENTAGE HALF.Ablation — predicted in writing, then run
From the committed fix; mutation proven by blob hash against the
HEADblob,restore proven by hash plus an empty
git diff HEAD, both legs restored underan
EXIT INT TERMtrap. No build step is involved: the root vitest configaliases these packages to source, which is why a source edit alone moves the
result.
A TRANSLATED SESSIONredLeg B is the one that matters for the ruling: it is what discriminates "called
the siblings' helper" from "read the label off the schema", and only the
translated-session case can tell them apart.
Verification
pnpm exec vitest run packages/plugin-detail/— 164 files, 1537 tests, allpassed (whole affected package, the only package this diff touches).
eslint packages/plugin-detail— 219 files, 0 errors (986 warnings, allpre-existing
no-explicit-any). Narrowing evidence: the universe isper-package
eslint .underturbo run lint, and the whole affected packagewas linted rather than only the 4 changed files; the count is read from
--format json; and the flat config declares noparserOptions.projectorprojectService, so type-aware linting is off and a diff inside one packagecannot move any untouched file's verdict.
--no-inline-configreported 3 errors in files this PR does not touch — thatflag is an objectstack-ism, not this repo's invocation, and it was disabling
legitimate inline disables. Not a finding.
node scripts/check-changeset-presence.mjs,check-changeset-no-major.mjs,check-control-bytes.mjs,check-new-cross-file-line-citations.mjs— allgreen; verdict lines quoted in the report.
node scripts/check-governed-queue-guard.mjs --teston this diff's paths:NOT GOVERNED — 3 path(s) checked against 5 governed surface(s); none matched.pnpm --filter "@object-ui/plugin-detail^..." buildthenpnpm --filter @object-ui/plugin-detail run type-check— both green(
tsc --noEmitplustsc -p tsconfig.test.json).optional: a first type-check against an unbuilt closure reported
Cannot find module '@object-ui/components'for the whole package — thestale-dist trap, not a reading. The new pin is proven to be INSIDE the
type-checked set rather than assumed to be:
tsc -p tsconfig.test.json --listFileslistssummaryChip.fieldLabel-8729.test.tsx.Acceptance notes
tree.
https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB.needs:contract-reviewis hung on both carriers (this PR and cardobjectui#8729) per the claim's
Clause-②: yes; the PM seat clears both onPASS.
no-explicit-anywarnings inthis package are a repo-wide style state, not a defect; the
--no-inline-configreading above was my own instrument error.🤖 Generated with Claude Code
https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
Generated by Claude Code