Repository navigation
spec(ui): record:details hideEmpty: false — in read-only mode an all-empty section renders its heading plus one fold row; describe() says so - #25612
Conversation
…y fold for false Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PC4NPXPgPmpDRG9DdDnyXT
… changeset Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PC4NPXPgPmpDRG9DdDnyXT
…de-empty-describe
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 144 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
席位核对ACCEPT · #25511 ·
Generated by Claude Code |
|
Correction to the The clause-② enqueue gate's path limb hits: the diff touches Generated by Claude Code |
Contract reviewServed-tier: Inputs read: card #25511 (body and all three comments), ruling C on objectstack-ai/objectui#13386 (body, ruling comment 6369306878, triage comment 6370301339), PR #25612 body and file list, the net diff of merge-base e649c2d to the head (three files, +30/-2), and the 47 check-runs on the head: 37 success, 10 skipped (opt-in harnesses, the filtered dogfood/pin/temporal jobs), 0 failed, no legacy commit statuses. The dispatching seat's PR comment was not read before this verdict was formed. ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
…-empty body (objectui#13386) (#13483) <!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D3jMyxwR7kHLu9j9TjYFimf)_ Fixes #13386 Fixes #12618 Seat: domain:ui#4 · card thread `session_01AqaieDWuDnBRyh3u8epGx6` · claim 6391455338 Ruling-ref: 6369306878 (batch #7 item 4, letter C) Clause-②: no · Metadata-protocol: semantic (the spec side landed as #25612) ## What changes Ruling C: in read-only mode an all-empty section that the page keeps draws its heading plus ONE "Show N empty fields" row, the same toggle a partly-filled section uses, instead of one em-dash row per field. Edit mode is unchanged, and a body whose sections are all empty folds once more (item 2 of #12618). | Where | Before | After | |---|---|---| | `DetailSection`, all-empty section kept (`hideEmpty: false`, or omitted where no default resolves it), read-only | heading + one `No value` row per field | heading + one "Show N empty fields" row; the toggle reveals the rows and "Hide empty fields" folds them back | | same, edit mode | every field an editable row | unchanged | | same, print page | every row prints | unchanged: every row prints, no toggle (#12717) | | `hideEmpty: true` all-empty section | renders nothing | unchanged | | partly-filled section | heuristic fold (#7129 Q2-C) | unchanged | | detail body (`DetailView` sections) where every section is empty and at least two are kept | one folded section after another | ONE "Show N empty fields" row for the body (new `EmptySectionsFold`); revealing it draws each section folded on its own | No key, shape or default moved, and no new i18n key: both rows reuse `detail.showEmptyFields` / `detail.hideEmptyFields`. The `hideEmpty` descriptions in `@object-ui/types` (`DetailViewSection`, `RecordDetailsSection`, the zod mirror) and the two `@object-ui/plugin-detail` registration texts now say what a kept section draws. ## Readings taken (for the seat's review) 1. **"Kept" includes an omitted key where no default resolves it.** The ruling names `hideEmpty: false`. On `detail-view`, the `detail-section` node and the `record:details` direct-`fields` fallback, omitted already meant the same as `false` (keep), and their descriptions said so. The fold keeps them equal, so the test is "kept" (`!hideAllEmptySection`), not `=== false`. On `record:details` authored sections nothing changes for an omitted key: it still resolves `?? true` and hides. 2. **Copy.** The ruling's 「N 个空字段 · 展开」 is described as "the same fold the mixed sections use", so the row is that fold's existing copy: `Show 4 empty fields` (en), `显示 4 个空字段` (zh). No new string. 3. **Body fold threshold.** The body folds only when every section that draws anything is all-empty AND at least two are kept: one kept section is already one row, so folding it again would only drop its heading. Section groups (`sectionGroups`) turn the body fold off, as they draw fields of their own beside the sections. A malformed section (no field array, or a read that throws) turns it off too, so each section still renders or fails inside its own boundary (#8497). ## Tests - New: `packages/plugin-detail/src/__tests__/DetailSection.allEmptyFold-13386.test.tsx` — the 「交易说明」 fixture: heading + one "Show 4 empty fields" row and no em-dash; reveal and fold back; untitled section; `hideEmpty: true` control; edit mode draws four textboxes and no fold; print page prints four rows; mixed sections (small and large) unchanged; body fold counts 8 over three sections, reveals each section folded, folds back; controls for a filled field anywhere, a single kept section, edit mode and print page; `emptySectionsFoldCount` rules including malformed sections. - Reverse check: with `DetailSection.tsx` at base, 6 of the new file's cases go red (the edit-mode, print, mixed and count cases stay green, as they pin unchanged behaviour). - Updated pins that read an all-empty kept section's rows, now through the fold: `record-details.hideEmptyRetired-7129` 4/4(b), `record-details.emptySectionDefault` (`false` keeps; direct-`fields` fallback), `detailSectionAuthoredHideEmpty-10485`, the AMPLIFICATION cases of `DetailSection.emptyArray-8474` and `DetailSection.emptinessAuthority-8376` (now: the fold counts all four rows, so the empty value is not counted as filled), `inlineCredentialGate` (`signature`'s empty sample reveals the fold before reading the pencil), `record-details.unresolvedIdentityFailClosed-9054` (the legacy `fieldName` entry survives as "Show 1 empty field"). ## Verification (local) - `pnpm exec vitest run packages/plugin-detail/ packages/types/` — green after the pin updates (the first full run surfaced the six pins above; their files re-run green: 132 passed). - `pnpm --filter @object-ui/plugin-detail type-check`, `pnpm --filter @object-ui/types type-check` — clean. - `pnpm --filter @object-ui/plugin-detail lint` — 0 errors. - `check:new-line-citations` 0 new · `check:doc-json` · `check:doc-fences` · `check:control-bytes` · `check:i18n-keys` · `check-changeset-presence` — pass. - `node scripts/check-governed-queue-guard.mjs --test <diff>` — NOT GOVERNED. - NOT MEASURED: `check:doc-snippets` / `check:doc-examples` (need the whole build; CI runs them). NOT MEASURED: screenshots of the HotCRM opportunity record at 1280 px (#12618 acceptance 2), reason: no HotCRM console stack in this container; the 「交易说明」 case is pinned in the DOM test above. ## Acceptance notes - The `hideEmpty` describe text on the installed spec pin still carries the pre-#25612 wording; the next pin move picks up the ruled text. Nothing here reads that prose. - [x] Adds no product-specific widget, dialog, hook or string (see AGENTS.md › Product-neutral renderer) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01AqaieDWuDnBRyh3u8epGx6 --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #25511
Clause-②: no
Metadata-protocol: semantic
What this changes
The
record:detailssection keyhideEmpty(packages/spec/src/ui/component.zod.ts, theRecordDetailsProps.sections[]object form) gets a newdescribe()text. It implements ruling C on objectstack-ai/objectui#13386 (batch #7 item 4, the maintainer's 「objectui 三个都同意」, 2026-10-23T16:08:53Z):true): unchanged. An all-empty section renders nothing at all: no heading, no skeleton.false, read-only mode: an all-empty section renders its heading plus one fold row that counts the empty fields and reveals them on request. This is the same fold a partly-filled section uses.false, edit mode: unchanged. Every field renders as an editable row.No key, shape or default changes. The renderer change is objectui's (
DetailSection); objectstack-ai/objectui#13386 carriesBlocked-by:this card and remains open.describe()falseto render empty rows, keeping the section's label skeleton on an all-empty record (e.g. a brand-new one).falseto keep it on an all-empty record (e.g. a brand-new one): in read-only mode it renders its heading plus one fold row that counts the empty fields and reveals them on request — the same fold a partly-filled section uses; in edit mode it renders every field as an editable row.Files:
packages/spec/src/ui/component.zod.ts: the describe string, plus one docblock paragraph above it recording the re-ruledfalserendering and that the key governs only the all-empty case. The docblock's existing text is about the default and the pre-declaration history, so it stays as it was.content/docs/references/ui/component.mdx: regenerated withgen:docs.check:generatednamed it as the only stale artifact. The shippedjson-schema/ui/RecordDetailsProps.json(gitignored, infiles[]) carries the new text after a build..changeset/25511-hide-empty-describe.md:@objectstack/specminor, with both declaration lines.Landing: the measured producer is the one the card named,
component.zod.tsat:1440. No other producer of this text exists (git grepfor the old phrases finds only the generated mdx, a test comment and the release-ownedCHANGELOG.md).Two wording calls
detail.showEmptyFields,packages/i18n/src/locales/en.tsat objectui7981384). Quoting "N empty fields · Show" would contradict "the same fold", and quoting objectui's string would pin a label the spec does not own. So the text says what the row does: it counts the empty fields and reveals them on request.truedrops the section,falsefolds the rows), so the old lead no longer told the two values apart. The key never governed a partly-filled section's empty rows either: the renderer's own fold handles those whatever the key says (objectui#7129 Q2-C). The default sentence keeps its meaning; only its grammar follows the new lead.Verification
All readings were taken on head
0ed74f8840(the merge oforigin/maine649c2d91e; the branch delta againstorigin/mainis exactly the three files above).pnpm --filter @objectstack/spec build: lock VERDICT command-exit 0. The build left no tracked file changed (authorable-surface.base.jsonuntouched).pnpm --filter @objectstack/spec check:generated, before regeneration: exit 1,✗ 1 of 14 artifact(s) stale: content/docs/references/**. Aftergen:docs, on the final head:✓ All 14 generated artifacts are up to date.pnpm --filter @objectstack/spec test:Test Files 714 passed (714),Tests 21687 passed | 1 todo (21688).pnpm --filter @objectstack/spec typecheck(tsc, scripts typecheck, test typecheck): exit 0. Lock VERDICT command-exit 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 107 commands from this diff. Each was run with its exit code captured before any pipe: 106 exited 0. One,pnpm check:dual-build-cjs-loads, exited 3 (PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/), so it is NOT MEASURED. It needs every workspace package built, and only the spec, lint, objectql, client, client-react and formula closures were built (targeted build; declared narrowing). CI'sLint & Repo Gatesruns it. Reconciliation:✓ dispatch-gates --ran: 107 derived famil(ies) accounted for — 106 run, 1 NOT-MEASURED (0 DERIVED from a recorded exit 3, 1 claimed).Selected verdict lines:check:generated✓ All 14 generated artifacts are up to date;check:docs✅ 227 generated files in sync with packages/spec;check:api-surfacepublic API surface + factory signatures unchanged ✓;check:doc-authoringclean (9559 describe strings read);check:nul-bytesOK;check-adr-0087-registrationthis PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).check:dev-prereqs: its self-test passes, but the scan exits 1 with✗ The workspace is not built — 1 unmet precondition(33 of 70 packages have nodist/, same targeted-build cause). NOT MEASURED. The spec build's own stamp (✓ packages/spec/dist/.build-input-hash) andcheck:generated's declaration-stamp match showpackages/spec/distis current.Acceptance notes
falseall-empty section still renders empty rows in read-only mode. The ruling orders it this way (spec text first, renderer second).hideAllEmptySectionrequires!isEditing,DetailSection.tsxat objectui7981384). The default sentence says "renders nothing at all" with no mode qualifier, as the card asked. No author writes wrong metadata because of it.packages/spec/src/ui/component.test.tsat about line 636 still callsfalse"the only spelling that keeps a section's label skeleton on an all-empty record". That file is outside the claim's file surface, and no assertion depends on the wording.Generated by Claude Code