You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding(spec): ComponentPropsMap['object-grid'] (ObjectGridPropsSchema) declares neither description nor emptyState, which objectui's ObjectGrid reads once objectui#11130 lands #20694
Filing-gate category: ① class (b), where the spec row lags the renderer. reach: a named consumer: objectui's ObjectGrid. After PR objectui#11130, it reads schema.description (through resolveI18nLabel in the display locale) and schema.emptyState ({ title?, message?, icon? }, drawn through DataEmptyState).
Reader: triage first (grade and route; domain:spec by the anchoring rule), then the spec seat. Filed by objectui's domain:ui seat 2 (session_011p7ikEivgXefNDaE5S5Uec) at the ACCEPT of PR objectui#11130 (objectui#11068). The source is the dev report's out_of_scope_findings[0], confirmed in that PR's contract review (5895665578, ③ 3) by reading the installed @objectstack/spec 17.4.0. ⛔ Not graded here.
Its docblock says the key set is derived from the objectui renderers' own read points.
It declares label, title, columns, filter, sort, the pagination, search, selection and action keys, navigation, data and more.
It declares neither description nor emptyState.
The same holds in the published 17.4.0 (dist/ui/index.js), which objectui pins.
What it costs today (per the review)
objectui's apps/console/src/__tests__/registry-inputs-spec-parity.test.ts judges object-grid in the FORWARD direction: "declares no top-level input the spec does not accept". So GRID_QUERY_INPUTS cannot list description or emptyState without a tracked exemption. PR objectui#11130 leaves the inputs alone and its docs say why.
objectui's SDUI parser reports an authored emptyState on object-grid as unknown-prop (a warning). description passes because it is in SDUI_BASE_PROPS.
A metadata author validating against the spec row has emptyState refused by name, although the renderer honours it.
Direction (for triage, not a ruling)
Add the two members, matching objectui's shape:
description: I18nLabelSchema.optional().
emptyState: a strict { title?, message?, icon? }, matching objectui's zod twin in packages/types/src/zod/objectql.zod.ts.
Then, in a follow-up objectui PR after the spec release is installable, list both in GRID_QUERY_INPUTS, which the parity gate's REVERSE direction requires.
objectui#11068 stays open (Part of) for showFilters / keyboardNavigation, which go back to triage there: no renderer machinery reads either, and the row refuses both. If a later objectui PR honours either, the same row needs it too.
Dedupe
About 1136 objectstack issues/PRs (the 500 most recently updated, fetched now, plus the earlier cache down to #2657) were grepped for:
ObjectGridPropsSchema, GRID_QUERY_INPUTS, and object-grid within 80 characters of emptyState / description: 1 hit, objectstack#19514 (closed; ViewFilterRuleSchema / defaultFilters, a different key).
Path: records — every key a grid honours is one the contract accepts | 缺项 (the strict ObjectGridPropsSchema declares neither description nor emptyState, which objectui's ObjectGrid reads since PR objectui#11130, nor keyboardNavigation, which objectui#11068's retriage chose to build) | P3
Triage: first grade — enhancement · priority:p3 · domain:spec · area:records · pm:queue. Direction: declare the three members, matching the spec's own shapes. keyboardNavigation is folded in here, as promised on objectui#11068
Triage: lands in packages/spec/src/ui/component.zod.ts (ObjectGridPropsSchema, :3370) ⇒ domain:spec. It takes objectui#11068's p3.
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T19:57Z. ⛔ Not a claim, ⛔ not a dispatch.
The fold. Triage's retriage answer on objectui#11068 (5896596687) chose BUILD for keyboardNavigation, and committed to folding its spec row here. This card is not in flight, so the fold is allowed.
Direction (Clause-②: yes (widening), minor).
description:I18nLabelSchema.optional().
emptyState: a strict { title?, message?, icon? } in the spec's own list-viewemptyState shape (I18nLabel members). ⛔ Not a second shape. objectui's zod twin follows the spec, not the other way round.
keyboardNavigation:z.boolean().optional(). Its describe says: arrow-key cell navigation on the WAI-ARIA grid pattern, defaulting to on when editable, and read-only grids keep their Tab behaviour unless it is set true.
Pins: the three members parse, and an unknown key is still refused.
After the release is installable in objectui, its follow-up lists all three in GRID_QUERY_INPUTS (the parity gate's reverse direction).
Deferred at selection, not taken: serial behind PR #20706's pin bump · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T21:41Z · ⛔ Not a claim; no state change.
The pin this card needs. The direction (triage 5897622232) adds description, emptyState and keyboardNavigation to ObjectGridPropsSchema, whose docblock records each key's objectui read point at the pin.
At today's pin dd3f7e1be356, ObjectGrid reads neither description nor emptyState. Those arrived with objectui#11130.
description, emptyState in the spec's own list-view shape, and keyboardNavigation;
Clause-②: yes (widening), minor;
pins for the three members and for an unknown key still refused. keyboardNavigation still has no reader (objectui#11068's BUILD), so its describe and liveness record say so.
Open PRs: as of origin/main085ca6bc1c, none touches component.zod.ts.
Provenance (whose instruction / verbatim / where): the maintainer, in session session_014EJ1ED8X4MMrT18BhVx4tx's chat: 「等这个 pin 的 6 张卡(#20287、#20578、#20293、#20313、#20299、#20694)现在可以跑各自的重启探针了。你负责处理」, clarified as 「你是让你处理这几个卡的分诊」.
Deferred at selection, not taken: serial behind open PR #20785 · domain:spec seat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T09:33Z · ⛔ Not a claim; no state change.
Claim: PM loop round 1 · 2026-09-30T13:37Z
Session: session_018fxqvRJW12TaHC7DUQ89Y6
Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee from this act)
Branch: claude/issue-20694-object-grid-desc-emptystate-keynav
Worktree: objectstack-issue-20694
Domain: domain:spec
Seat: domain:spec#1 (seat post #6017)
File surface: packages/spec/src/ui/component.zod.ts, ObjectGridPropsSchema only (:3402 at f7501191e5), gaining three members; their pins in packages/spec/src/ui/** tests; the liveness record for the three keys; the regenerated authorable-surface/ui.json, authorable-defaults, JSON schema and content/docs/references/ui/component.mdx; one .changeset/20694-*.md (minor). Stop on breach; explain in the report.
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier at f7501191e5: no path-derived mandate; floor sonnet · default opus · ceiling fable; a widening with a describe judgment ⇒ default tier)
Clause-②: yes
Thread-read: 5910094526
Serial constraints cleared: the constraint this card waited on, #20831 (ObjectGridPropsSchema.grouping), landed as f7501191e5 and closed completed. Open PR #20785 (seat 5) edits component.zod.ts only at :515 (PageHeaderProps), plus one line each in authorable-surface/ui.json (:1095), component.mdx (:1186) and component.test.ts (the PageHeaderProps block), so the regions are disjoint. The generated files are merge=os-regen, and the later lander merges main through os-regen-merge.sh. The pin .objectui-sha = db11afd4967c carries objectui#11130's read points (restart probe 5905205373).
Clause-②: yes as triage graded it (5897622232: "Clause-②: yes (widening), minor"). Three members are added to a published strict shape. An at-tier contract review is owed before enqueue. At ACCEPT, the objectui follow-up (listing the three in GRID_QUERY_INPUTS, the parity gate's reverse direction) is filed on objectui, Blocked-by: this card, per triage's direction.
os-dev-report
{
"issue": 20694,
"status": "done",
"branch": "claude/issue-20694-object-grid-desc-emptystate-keynav",
"pr": "#20882",
"session": "session_018fxqvRJW12TaHC7DUQ89Y6",
"premise_still_valid": true,
"summary": "ObjectGridPropsSchema (ComponentPropsMap['object-grid']) now declares description (I18nLabelSchema.optional()), emptyState (by reference to the list view's own shape) and keyboardNavigation (z.boolean().optional(), describe per triage, prefixed [EXPERIMENTAL — not enforced] because nothing reads it at the pin). The list view's inline emptyState strictObject is extracted into the exported EmptyStateSchema (author type EmptyState) and both doors hold it. A 112-cell parse corpus (4 list-view doors x 28 values) matches BASE 72f8c38 byte for byte once the reworded action/button guidance sentence is substituted; success, data, codes and paths match in every cell. Read points at pin db11afd4967c are recorded in the docblock: description at ObjectGrid.tsx:5677 (the resolver is resolveInlineI18nLabel, not the card's resolveI18nLabel) and emptyState at :6225 (DataEmptyState :6233). keyboardNavigation has 0 readers (control schema.editable = 3 hits). All four Zone 2 premises hold. One refinement: the liveness ledger cannot hold a ComponentPropsMap row (page/regions is undrilled and PageComponent.properties is an open record), so the ledger's own describe marker is the record. Main was merged at 07356a6 through os-regen-merge.sh (PRs #20785 and #20823 landed there). Main has since moved to 9905e61 (#20868, view.zod.ts): a merge-tree probe with no merge driver shows a clean merge. Not merged again.",
"tests": "All at head 98dad84 unless marked. (1) Pin src/ui/component-object-grid-declared-keys.pin.test.ts: 18/18 passed. (2) Ablations on committed state through scripts/ablation-replace.mjs; each restore proven (blob == HEAD c06c3e865b0a, git diff HEAD empty). Renaming keyboardNavigation: 4 failed / 14 passed. Replacing the grid's EmptyStateSchema with a copied z.strictObject: 1 failed (the identity pin) / 17 passed. (3) pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 581 files, 17154 passed, 1 todo. The first full run, at 34954b0, had 1 failure (the isomorphic pin count, 779 vs 780), fixed in 0e7831e. (4) pnpm --filter @objectstack/spec typecheck: exit 0, test-typecheck debt unchanged by this diff. (5) Consumer @objectstack/lint, upstream closure built with turbo run build --filter='@objectstack/lint...': vitest 117 files / 5437 passed; typecheck exit 0. validateComponentProps on an object-grid node gives 0 findings for the three keys; controls: emptyStat gives component-props-unknown-key at properties.emptyStat, and emptyState.heading gives it at properties.emptyState.heading, a path reachable only through the new declaration, which proves the rule read the rebuilt dist. (6) Parse corpus BASE vs head: list view 112/112 cells identical after substituting only the guidance sentence. object-grid: 3/36 accepted at BASE, 14/36 at head, newly refused = none. Both examples/app-showcase object-grid nodes parse before and after. (7) pnpm --filter @objectstack/spec check:generated: all 15 artifacts up to date. (8) Derived gates, see the gates field.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 98dad84 derived 108 families. Each exit code was captured to a file before any pipe. 104 exited 0. 4 exited 3 (PREREQUISITE NOT MET) and are NOT MEASURED: check:skill-examples (client SDK closure unbuilt), check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt (each needs the whole-repo package build CI does). Reconciled with --ran in the 'command :: exit N' format: 108 derived, 104 run, 4 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN. Every pnpm --filter gate log echoes its script name, so none is a zero-match exit 0.",
"line_budget": "n/a",
"mcp_calls": "0",
"api_writes": "3 — all through the fleet-write relay (repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, which became POST /repos/objectstack-ai/objectstack/pulls and opened #20882 as a draft; the read-back stored 8127/8127 bytes identical. (2) label-write --assign os-warren, which became POST /repos//issues/20882/assignees; read-back matches. (3) This os-dev-report comment, which becomes POST /repos//issues/20694/comments. Also git push to the branch, which is not REST. No labels were written: the dispatch named none, and a changeset is present, so skip-changeset does not apply.",
"deviations": [
"Zone 2.2 says the extraction must leave list-view unchanged. Its accept set is unchanged (success, data, codes and paths are identical in 112/112 cells). One refusal TEXT changed on purpose: the action/button guidance now reads true on both doors, because a list-view-only sentence would point object-grid authors at a block the grid does not have. This follows the VIEW_ROW_BOUND_GUIDANCE precedent for text two doors share. Every one of the 12 differing cells differs only by that sentence.",
"Zone 2.3 asks for a liveness record: measured inapplicable as a ledger row. check:liveness cannot walk ComponentPropsMap: page/regions is in undrilled-containers.baseline.json and PageComponent.properties is an open record, so a page.json row would be an orphan. The record is the ledger's own [EXPERIMENTAL — not enforced] describe marker (MARKER_RE), plus a member docblock that says to drop it when the reader lands.",
"File outside the claimed surface: packages/spec/src/type-alias-convention.pin.test.ts. gen:docs refuses a documented schema that has no type alias, so export type EmptyState was added. ADR-0122 then requires an isomorphism pin (Iso_ui_view__EmptyStateSchema), which moves the count from 779 to 780.",
"EmptyStateSchema members gained describes. The reference docs for list-view emptyState therefore lose the inherited I18nLabel sentence 'resolved at render time', which the renderers do not honour for this key (see out_of_scope_findings).",
"os-regen-merge.sh step 3: the pre-commit hook refused the hand-off commit until the os-regen artifacts were regenerated (its documented collection point). The regeneration was committed separately as 98dad84, outside MERGE state. Step 4 (both sides' bodies present) was checked by grep on PageHeaderProps.breadcrumb retiredKey, PicklistSchema and the three new members.",
"The card names the resolver resolveI18nLabel. At pin db11afd4967c it is resolveInlineI18nLabel; the docblock records the real name."
],
"files_changed": [
".changeset/20694-object-grid-description-empty-state-keyboard-navigation.md",
"content/docs/references/index.mdx",
"content/docs/references/ui/component.mdx",
"content/docs/references/ui/view.mdx",
"packages/spec/api-surface/ui.json",
"packages/spec/authorable-surface/ui.json",
"packages/spec/declaration-map/ui.json",
"packages/spec/export-origins/ui.json",
"packages/spec/json-schema.manifest/ui.json",
"packages/spec/src/type-alias-convention.pin.test.ts",
"packages/spec/src/ui/component-object-grid-declared-keys.pin.test.ts",
"packages/spec/src/ui/component.zod.ts",
"packages/spec/src/ui/view.zod.ts"
],
"open_questions": [
{
"question": "At the pin, ObjectGrid hands emptyState.title and .message to DataEmptyState unresolved, and an inline locale map there throws in React. The shared EmptyStateSchema declares I18nLabel members, per triage's 'not a second shape'. Should the objectui follow-up the seat files at ACCEPT (the GRID_QUERY_INPUTS listing) also carry the renderer fix?",
"options": [
"A: Fold it into that follow-up. ObjectGrid (and ListView, which silently drops a map through its typeof guard) resolve both members with resolveInlineI18nLabel, as description already is.",
"B: File a separate objectui card for the renderer fix.",
"C: Narrow object-grid's emptyState to string members on the spec side (rejected: triage ruled one shape, and narrowing the consumer's contract to hide a renderer gap is the backwards direction)."
],
"recommendation": "A. Business need: no producer writes a map today (examples/** and platform-objects all use strings), so this is small. It is the one remaining gap between what the spec declares and what the renderer draws for this key. Long-term: the spec declaration stands and the renderer catches up, per contract-first. AI error-proofing: until the fix lands, an AI following the I18nLabel type crashes the grid, and the changeset already tells authors to write strings. No scope creep: it is one resolver call per member in the same follow-up that already touches ObjectGrid's inputs, with no new card and no new gate."
}
],
"out_of_scope_findings": [
"carrier: the seat's objectui follow-up filed at ACCEPT (GRID_QUERY_INPUTS listing; triage 5897622232) · noted, not filed. objectui ObjectGrid.tsx:6241-6242 at db11afd4967c passes emptyState.title and .message unresolved to DataEmptyState. React 19.2.8 throws 'Objects are not valid as a React child' on a locale map (measured with renderToString). plugin-list ListView.tsx:5112/5114 drops a map to the default text through a typeof-string guard. reach: no named producer, since every emptyState in examples/** and packages/platform-objects is a plain string. Dedupe words: emptyState locale map, resolveInlineI18nLabel, DataEmptyState title object, ObjectGrid emptyState i18n, ListView emptyState typeof string."
]
}
ACCEPT — PR #20882 at head 98dad84e · domain:spec seat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T15:23Z
Reviewed against GitHub, not the report (5914012653).
PR shape: draft, base main. The first line is Fixes #20694, then Clause-②: yes, and there is no other closing keyword. The PR assignee is os-warren. check-governed-merges --pr 20882: NOT governed, 387 changed lines, 13 files.
What lands:ObjectGridPropsSchema gains three members, per triage's direction 5897622232:
description: I18nLabel.
emptyState: the list view's own shape, held by reference as the exported EmptyStateSchema.
keyboardNavigation: a boolean, with the describe triage specified, prefixed [EXPERIMENTAL — not enforced] because it has no reader at the pin.
The row still refuses every undeclared key. Pins: 18/18, with ablations 4 red (rename) and 1 red (identity pin). Changeset: minor.
File surface, amended in this act: the claim's surface gains packages/spec/src/type-alias-convention.pin.test.ts (the ADR-0122 isomorphism pin that the new EmptyState type alias requires, 779 → 780) and view.zod.ts (where the list view's inline shape is extracted). Both are forced by "not a second shape", and the reviewer judged them right.
Deviations accepted:
The list view's accept set is unchanged: success, data, codes and paths are identical in 112/112 corpus cells. One refusal sentence (action/button) is reworded to read true on both doors.
The liveness record is the ledger's own describe marker, because ComponentPropsMap is not walkable as a row.
Contract review: at-tier record 5914228301 on the PR, Head-sha = the current head, PASS, Local-runs: none, identity pair present.
The dev's open question (renderer gap for a locale map in emptyState.title / .message): answered A by the review. The spec keeps one shape; the renderers catch up in the objectui follow-up.
GRID_QUERY_INPUTS gains description and emptyState, with keyboardNavigation sequenced with its reader (objectui#11068);
the ObjectGridSchema.emptyState interface and its zod twin widen title / message to I18nLabel;
resolveInlineI18nLabel is applied at ObjectGrid.tsx:6241-6242 and ListView.tsx:5112/:5114.
Out of scope: none left unfiled. The renderer gap has no named producer today, and the changeset tells authors to write strings on object-grid until objectui#11227 lands.
Not yet landed: at this stamp, the completed runs are all success or skipped, and Test Core (1/6) and (5/6) are still running. When every run is success or a rostered skip, the seat sends pr_ready + automerge_enable and follows the PR to MERGED.
Records: ACCEPT 5914340163; at-tier contract review 5914228301 (PASS, head 98dad84e); dev report 5914012653.
Closing-keyword audit: the PR body closed only this card. The queue merge did not auto-close it, so the seat closes it now. pm:dispatched and the assignee were removed in this act.
Activity
objectstack-fleet commentedon Sep 29, 2026
Path: records — every key a grid honours is one the contract accepts | 缺项 (the strict
ObjectGridPropsSchemadeclares neitherdescriptionnoremptyState, which objectui'sObjectGridreads since PR objectui#11130, norkeyboardNavigation, which objectui#11068's retriage chose to build) | P3Triage: first grade —
enhancement·priority:p3·domain:spec·area:records·pm:queue. Direction: declare the three members, matching the spec's own shapes.keyboardNavigationis folded in here, as promised on objectui#11068Triage: lands in
packages/spec/src/ui/component.zod.ts(ObjectGridPropsSchema,:3370) ⇒domain:spec. It takes objectui#11068's p3.Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-29T19:57Z. ⛔ Not a claim, ⛔ not a dispatch.The fold. Triage's retriage answer on objectui#11068 (
5896596687) chose BUILD forkeyboardNavigation, and committed to folding its spec row here. This card is not in flight, so the fold is allowed.Direction (
Clause-②: yes (widening),minor).description:I18nLabelSchema.optional().emptyState: a strict{ title?, message?, icon? }in the spec's ownlist-viewemptyStateshape (I18nLabelmembers). ⛔ Not a second shape. objectui's zod twin follows the spec, not the other way round.keyboardNavigation:z.boolean().optional(). Its describe says: arrow-key cell navigation on the WAI-ARIA grid pattern, defaulting to on wheneditable, and read-only grids keep their Tab behaviour unless it is settrue.GRID_QUERY_INPUTS(the parity gate's reverse direction).objectstack-fleet commentedon Sep 29, 2026
Deferred at selection, not taken: serial behind PR #20706's pin bump ·
domain:specseat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T21:41Z · ⛔ Not a claim; no state change.5897622232) addsdescription,emptyStateandkeyboardNavigationtoObjectGridPropsSchema, whose docblock records each key's objectui read point at the pin.dd3f7e1be356,ObjectGridreads neitherdescriptionnoremptyState. Those arrived with objectui#11130..objectui-shatodb11afd4967c, which is objectui#11130's own merge commit (compare:identical). It also editspackages/spec/src/ui/component.zod.ts, this card's file.keyboardNavigationhas no reader yet (objectui#11068's BUILD). The dev states that in its describe and in the liveness record.Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
Restart probe run: the serial constraint this card was deferred on has cleared · triage on the maintainer's order · 2026-09-30T06:07Z
Session
session_014EJ1ED8X4MMrT18BhVx4tx(os-tesla). ⛔ Not a claim, ⛔ not a dispatch. No state change: the card stayspm:queue, unassigned.5899525593deferred it as serial behind PR chore(objectui): bump the console pin to db11afd4967c (carries objectui#11119, the objectui#11105 fix) #20706's pin bump, which also editedpackages/spec/src/ui/component.zod.ts, this card's file.a3d7588b56..objectui-shaonmainis nowdb11afd4967cd9d39381c5e21dc2deec9d706204, which is objectui#11130's own merge commit. SoObjectGrid'sdescriptionandemptyStateread points can be recorded at the live pin, as the deferral asked.5897622232:description,emptyStatein the spec's ownlist-viewshape, andkeyboardNavigation;Clause-②: yes (widening),minor;keyboardNavigationstill has no reader (objectui#11068's BUILD), so its describe and liveness record say so.origin/main085ca6bc1c, none touchescomponent.zod.ts.Provenance (whose instruction / verbatim / where): the maintainer, in session
session_014EJ1ED8X4MMrT18BhVx4tx's chat: 「等这个 pin 的 6 张卡(#20287、#20578、#20293、#20313、#20299、#20694)现在可以跑各自的重启探针了。你负责处理」, clarified as 「你是让你处理这几个卡的分诊」.Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
Deferred at selection, not taken: serial behind open PR #20785 ·
domain:specseat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T09:33Z · ⛔ Not a claim; no state change.PageHeaderProps.breadcrumb("Show breadcrumb", default true) — no renderer draws a trail and the console shell already draws one; the spec half of objectui#11166 #20758 (domain:specseat 5), the retirement ofPageHeaderProps.breadcrumb, in draft. It editspackages/spec/src/ui/component.zod.ts,packages/spec/src/ui/component.test.ts,packages/spec/authorable-surface/ui.jsonandcontent/docs/references/ui/component.mdx. Those are this card's files, on the samearea:recordsaxis, so the two are not disjoint and this card runs after it.5897622232and the restart probe5905205373. The pin now carries objectui#11130's read points.objectstack-fleet commentedon Sep 30, 2026
Serial constraint updated ·
domain:specseat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T11:18Z · ⛔ Not a claim; no state change.5908413139. That note deferred this card behind PR feat(spec)!: retire a page header's breadcrumb switch — no renderer draws a trail for it (#20758) #20785 on a file-level read ofcomponent.zod.ts. PR feat(spec)!: retire a page header's breadcrumb switch — no renderer draws a trail for it (#20758) #20785 edits only:515(PageHeaderProps), plus one line in each generated file. Read by region, that does not overlapObjectGridPropsSchema(:3397).ComponentPropsMap['object-kanban'].groupingisz.unknown()while the board readsgrouping.fields[0].fieldas its swimlane field — a padded or wrong-shaped grouping validates green and collapses the lanes #20831 (p2, claim5910072345), which types thegroupingmember ofObjectGridPropsSchema(:3596), the same schema this card widens. Fold gate ① fails (that card narrows, this one widens), so the two run serially: this card is taken after spec(ui):ComponentPropsMap['object-kanban'].groupingisz.unknown()while the board readsgrouping.fields[0].fieldas its swimlane field — a padded or wrong-shaped grouping validates green and collapses the lanes #20831's PR lands.5897622232and the restart probe5905205373.objectstack-fleet commentedon Sep 30, 2026
Claim: PM loop round 1 · 2026-09-30T13:37Z
Session:
session_018fxqvRJW12TaHC7DUQ89Y6Account:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-20694-object-grid-desc-emptystate-keynavWorktree:
objectstack-issue-20694Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface:
packages/spec/src/ui/component.zod.ts,ObjectGridPropsSchemaonly (:3402atf7501191e5), gaining three members; their pins inpackages/spec/src/ui/**tests; the liveness record for the three keys; the regeneratedauthorable-surface/ui.json,authorable-defaults, JSON schema andcontent/docs/references/ui/component.mdx; one.changeset/20694-*.md(minor). Stop on breach; explain in the report.Container & model:
M,mode:subagent,model: opus(dispatch-gates --tieratf7501191e5: no path-derived mandate; floor sonnet · default opus · ceiling fable; a widening with a describe judgment ⇒ default tier)Clause-②: yes
Thread-read: 5910094526
Serial constraints cleared: the constraint this card waited on, #20831 (
ObjectGridPropsSchema.grouping), landed asf7501191e5and closedcompleted. Open PR #20785 (seat 5) editscomponent.zod.tsonly at:515(PageHeaderProps), plus one line each inauthorable-surface/ui.json(:1095),component.mdx(:1186) andcomponent.test.ts(thePageHeaderPropsblock), so the regions are disjoint. The generated files aremerge=os-regen, and the later lander mergesmainthroughos-regen-merge.sh. The pin.objectui-sha=db11afd4967ccarries objectui#11130's read points (restart probe5905205373).Clause-②: yesas triage graded it (5897622232: "Clause-②: yes (widening),minor"). Three members are added to a published strict shape. An at-tier contract review is owed before enqueue. At ACCEPT, the objectui follow-up (listing the three inGRID_QUERY_INPUTS, the parity gate's reverse direction) is filed on objectui,Blocked-by:this card, per triage's direction.objectstack-fleet commentedon Sep 30, 2026
os-dev-report
{
"issue": 20694,
"status": "done",
"branch": "claude/issue-20694-object-grid-desc-emptystate-keynav",
"pr": "#20882",
"session": "session_018fxqvRJW12TaHC7DUQ89Y6",
"premise_still_valid": true,
"summary": "ObjectGridPropsSchema (ComponentPropsMap['object-grid']) now declares description (I18nLabelSchema.optional()), emptyState (by reference to the list view's own shape) and keyboardNavigation (z.boolean().optional(), describe per triage, prefixed [EXPERIMENTAL — not enforced] because nothing reads it at the pin). The list view's inline emptyState strictObject is extracted into the exported EmptyStateSchema (author type EmptyState) and both doors hold it. A 112-cell parse corpus (4 list-view doors x 28 values) matches BASE 72f8c38 byte for byte once the reworded action/button guidance sentence is substituted; success, data, codes and paths match in every cell. Read points at pin db11afd4967c are recorded in the docblock: description at ObjectGrid.tsx:5677 (the resolver is resolveInlineI18nLabel, not the card's resolveI18nLabel) and emptyState at :6225 (DataEmptyState :6233). keyboardNavigation has 0 readers (control schema.editable = 3 hits). All four Zone 2 premises hold. One refinement: the liveness ledger cannot hold a ComponentPropsMap row (page/regions is undrilled and PageComponent.properties is an open record), so the ledger's own describe marker is the record. Main was merged at 07356a6 through os-regen-merge.sh (PRs #20785 and #20823 landed there). Main has since moved to 9905e61 (#20868, view.zod.ts): a merge-tree probe with no merge driver shows a clean merge. Not merged again.",
"tests": "All at head 98dad84 unless marked. (1) Pin src/ui/component-object-grid-declared-keys.pin.test.ts: 18/18 passed. (2) Ablations on committed state through scripts/ablation-replace.mjs; each restore proven (blob == HEAD c06c3e865b0a, git diff HEAD empty). Renaming keyboardNavigation: 4 failed / 14 passed. Replacing the grid's EmptyStateSchema with a copied z.strictObject: 1 failed (the identity pin) / 17 passed. (3) pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 581 files, 17154 passed, 1 todo. The first full run, at 34954b0, had 1 failure (the isomorphic pin count, 779 vs 780), fixed in 0e7831e. (4) pnpm --filter @objectstack/spec typecheck: exit 0, test-typecheck debt unchanged by this diff. (5) Consumer @objectstack/lint, upstream closure built with turbo run build --filter='@objectstack/lint...': vitest 117 files / 5437 passed; typecheck exit 0. validateComponentProps on an object-grid node gives 0 findings for the three keys; controls: emptyStat gives component-props-unknown-key at properties.emptyStat, and emptyState.heading gives it at properties.emptyState.heading, a path reachable only through the new declaration, which proves the rule read the rebuilt dist. (6) Parse corpus BASE vs head: list view 112/112 cells identical after substituting only the guidance sentence. object-grid: 3/36 accepted at BASE, 14/36 at head, newly refused = none. Both examples/app-showcase object-grid nodes parse before and after. (7) pnpm --filter @objectstack/spec check:generated: all 15 artifacts up to date. (8) Derived gates, see the gates field.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 98dad84 derived 108 families. Each exit code was captured to a file before any pipe. 104 exited 0. 4 exited 3 (PREREQUISITE NOT MET) and are NOT MEASURED: check:skill-examples (client SDK closure unbuilt), check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt (each needs the whole-repo package build CI does). Reconciled with --ran in the 'command :: exit N' format: 108 derived, 104 run, 4 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN. Every pnpm --filter gate log echoes its script name, so none is a zero-match exit 0.",
"line_budget": "n/a",
"mcp_calls": "0",
"api_writes": "3 — all through the fleet-write relay (repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create, which became POST /repos/objectstack-ai/objectstack/pulls and opened #20882 as a draft; the read-back stored 8127/8127 bytes identical. (2) label-write --assign os-warren, which became POST /repos//issues/20882/assignees; read-back matches. (3) This os-dev-report comment, which becomes POST /repos//issues/20694/comments. Also git push to the branch, which is not REST. No labels were written: the dispatch named none, and a changeset is present, so skip-changeset does not apply.",
"deviations": [
"Zone 2.2 says the extraction must leave list-view unchanged. Its accept set is unchanged (success, data, codes and paths are identical in 112/112 cells). One refusal TEXT changed on purpose: the action/button guidance now reads true on both doors, because a list-view-only sentence would point object-grid authors at a block the grid does not have. This follows the VIEW_ROW_BOUND_GUIDANCE precedent for text two doors share. Every one of the 12 differing cells differs only by that sentence.",
"Zone 2.3 asks for a liveness record: measured inapplicable as a ledger row. check:liveness cannot walk ComponentPropsMap: page/regions is in undrilled-containers.baseline.json and PageComponent.properties is an open record, so a page.json row would be an orphan. The record is the ledger's own [EXPERIMENTAL — not enforced] describe marker (MARKER_RE), plus a member docblock that says to drop it when the reader lands.",
"File outside the claimed surface: packages/spec/src/type-alias-convention.pin.test.ts. gen:docs refuses a documented schema that has no type alias, so export type EmptyState was added. ADR-0122 then requires an isomorphism pin (Iso_ui_view__EmptyStateSchema), which moves the count from 779 to 780.",
"EmptyStateSchema members gained describes. The reference docs for list-view emptyState therefore lose the inherited I18nLabel sentence 'resolved at render time', which the renderers do not honour for this key (see out_of_scope_findings).",
"os-regen-merge.sh step 3: the pre-commit hook refused the hand-off commit until the os-regen artifacts were regenerated (its documented collection point). The regeneration was committed separately as 98dad84, outside MERGE state. Step 4 (both sides' bodies present) was checked by grep on PageHeaderProps.breadcrumb retiredKey, PicklistSchema and the three new members.",
"The card names the resolver resolveI18nLabel. At pin db11afd4967c it is resolveInlineI18nLabel; the docblock records the real name."
],
"files_changed": [
".changeset/20694-object-grid-description-empty-state-keyboard-navigation.md",
"content/docs/references/index.mdx",
"content/docs/references/ui/component.mdx",
"content/docs/references/ui/view.mdx",
"packages/spec/api-surface/ui.json",
"packages/spec/authorable-surface/ui.json",
"packages/spec/declaration-map/ui.json",
"packages/spec/export-origins/ui.json",
"packages/spec/json-schema.manifest/ui.json",
"packages/spec/src/type-alias-convention.pin.test.ts",
"packages/spec/src/ui/component-object-grid-declared-keys.pin.test.ts",
"packages/spec/src/ui/component.zod.ts",
"packages/spec/src/ui/view.zod.ts"
],
"open_questions": [
{
"question": "At the pin, ObjectGrid hands emptyState.title and .message to DataEmptyState unresolved, and an inline locale map there throws in React. The shared EmptyStateSchema declares I18nLabel members, per triage's 'not a second shape'. Should the objectui follow-up the seat files at ACCEPT (the GRID_QUERY_INPUTS listing) also carry the renderer fix?",
"options": [
"A: Fold it into that follow-up. ObjectGrid (and ListView, which silently drops a map through its typeof guard) resolve both members with resolveInlineI18nLabel, as description already is.",
"B: File a separate objectui card for the renderer fix.",
"C: Narrow object-grid's emptyState to string members on the spec side (rejected: triage ruled one shape, and narrowing the consumer's contract to hide a renderer gap is the backwards direction)."
],
"recommendation": "A. Business need: no producer writes a map today (examples/** and platform-objects all use strings), so this is small. It is the one remaining gap between what the spec declares and what the renderer draws for this key. Long-term: the spec declaration stands and the renderer catches up, per contract-first. AI error-proofing: until the fix lands, an AI following the I18nLabel type crashes the grid, and the changeset already tells authors to write strings. No scope creep: it is one resolver call per member in the same follow-up that already touches ObjectGrid's inputs, with no new card and no new gate."
}
],
"out_of_scope_findings": [
"carrier: the seat's objectui follow-up filed at ACCEPT (GRID_QUERY_INPUTS listing; triage 5897622232) · noted, not filed. objectui ObjectGrid.tsx:6241-6242 at db11afd4967c passes emptyState.title and .message unresolved to DataEmptyState. React 19.2.8 throws 'Objects are not valid as a React child' on a locale map (measured with renderToString). plugin-list ListView.tsx:5112/5114 drops a map to the default text through a typeof-string guard. reach: no named producer, since every emptyState in examples/** and packages/platform-objects is a plain string. Dedupe words: emptyState locale map, resolveInlineI18nLabel, DataEmptyState title object, ObjectGrid emptyState i18n, ListView emptyState typeof string."
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
ACCEPT — PR #20882 at head
98dad84e·domain:specseat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T15:23ZReviewed against GitHub, not the report (
5914012653).PR shape: draft, base
main. The first line isFixes #20694, thenClause-②: yes, and there is no other closing keyword. The PR assignee isos-warren.check-governed-merges --pr 20882: NOT governed, 387 changed lines, 13 files.What lands:
ObjectGridPropsSchemagains three members, per triage's direction5897622232:description: I18nLabel.emptyState: the list view's own shape, held by reference as the exportedEmptyStateSchema.keyboardNavigation: a boolean, with the describe triage specified, prefixed[EXPERIMENTAL — not enforced]because it has no reader at the pin.The row still refuses every undeclared key. Pins: 18/18, with ablations 4 red (rename) and 1 red (identity pin). Changeset:
minor.File surface, amended in this act: the claim's surface gains
packages/spec/src/type-alias-convention.pin.test.ts(the ADR-0122 isomorphism pin that the newEmptyStatetype alias requires, 779 → 780) andview.zod.ts(where the list view's inline shape is extracted). Both are forced by "not a second shape", and the reviewer judged them right.Deviations accepted:
action/button) is reworded to read true on both doors.ComponentPropsMapis not walkable as a row.Contract review: at-tier record
5914228301on the PR,Head-sha= the current head, PASS,Local-runs: none, identity pair present.The dev's open question (renderer gap for a locale map in
emptyState.title/.message): answered A by the review. The spec keeps one shape; the renderers catch up in the objectui follow-up.objectui follow-up, filed in this act: plugin-grid + plugin-list + types: follow the object-grid
description/emptyState/keyboardNavigationrows (objectstack#20694) once released — list them inGRID_QUERY_INPUTS, typeemptyStatetext as I18nLabel, resolve maps at both renderers objectui#11227,Blocked-by:this card, unlocking on the installable release. It carries three items:GRID_QUERY_INPUTSgainsdescriptionandemptyState, withkeyboardNavigationsequenced with its reader (objectui#11068);ObjectGridSchema.emptyStateinterface and its zod twin widentitle/messageto I18nLabel;resolveInlineI18nLabelis applied atObjectGrid.tsx:6241-6242andListView.tsx:5112/:5114.Out of scope: none left unfiled. The renderer gap has no named producer today, and the changeset tells authors to write strings on
object-griduntil objectui#11227 lands.Not yet landed: at this stamp, the completed runs are all
successor skipped, andTest Core (1/6)and(5/6)are still running. When every run issuccessor a rostered skip, the seat sendspr_ready+automerge_enableand follows the PR to MERGED.objectstack-fleet commentedon Sep 30, 2026
Landed · PR #20882 MERGED through the merge queue as
f5c7b2c753(single parent) ·domain:specseat 1 (session_018fxqvRJW12TaHC7DUQ89Y6) · 2026-09-30T15:56Zorigin/mainatf5c7b2c753:ObjectGridPropsSchema(packages/spec/src/ui/component.zod.ts:3441) carriesdescription: I18nLabelSchema.optional()(:3459),emptyState: EmptyStateSchema.optional()(:3481) andkeyboardNavigation: z.boolean().optional()(:3707).export const EmptyStateSchemais atpackages/spec/src/ui/view.zod.ts:1756, and the list view holds it by reference (:2932).Iso_ui_view__EmptyStateSchemais intype-alias-convention.pin.test.ts, andcomponent-object-grid-declared-keys.pin.test.tsis present.20694-object-grid-description-empty-state-keyboard-navigation.mdis present.VIEW_ITEM_OPTIONS_REFUSED(PR fix(metadata-protocol,spec)!: a saved view stores the parsed value of every key its body carried, and a ViewItem record's top-level options is refused by name (stage iv of #20051) #20868) is inview.zod.ts, andGroupingConfigSchema.optional()(PR fix(spec): type object-grid and object-kanban grouping by GroupingConfigSchema #20856) is on both rows.5914340163; at-tier contract review5914228301(PASS, head98dad84e); dev report5914012653.pm:dispatchedand the assignee were removed in this act.description/emptyState/keyboardNavigationrows (objectstack#20694) once released — list them inGRID_QUERY_INPUTS, typeemptyStatetext as I18nLabel, resolve maps at both renderers objectui#11227 (Blocked-by:this card). It unlocks when the release is installable and carries:GRID_QUERY_INPUTSlisting, withkeyboardNavigationsequenced with its reader, objectui#11068;emptyState;ObjectGridandListView.feat(types)!: `showFilters` is retired on `object-grid`, refused by n…
fix(spec): type object-grid and object-kanban grouping by GroupingCon…
feat(spec): object-grid declares description, emptyState (the list vi…
chore(objectui): bump the console pin to ab1879721595 (carries object…