Skip to content

feat(spec): object-grid declares description, emptyState (the list view's own shape) and keyboardNavigation - #20882

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-20694-object-grid-desc-emptystate-keynav
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-20694-object-grid-desc-emptystate-keynav

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20694
Clause-②: yes

ComponentPropsMap['object-grid'] (ObjectGridPropsSchema) gains the three members named in triage's direction (5897622232). This is a widening. Nothing that parsed before is refused, and the row still refuses every key it does not declare.

What changes

  • description: I18nLabelSchema.optional(). At the .objectui-sha pin db11afd4967c, plugin-grid/src/ObjectGrid.tsx:5677 resolves it with resolveInlineI18nLabel(schema.description, displayLocale) and draws it above the rows at :5784, :6303 and :6342. The card calls the function resolveI18nLabel; at the pin it is named resolveInlineI18nLabel.
  • emptyState: the list view's own empty-state shape, taken by reference. It used to be an inline strictObject at ListViewShapeSchema.emptyState (view.zod.ts). It is now the exported EmptyStateSchema (author type EmptyState), and list-view and object-grid both hold that one schema. At the pin it is read at ObjectGrid.tsx:6225 and drawn through DataEmptyState at :6233.
  • keyboardNavigation: z.boolean().optional(). Its describe is the one triage specified: arrow-key cell navigation on the WAI-ARIA grid pattern, on by default when editable is set, and a read-only grid keeps its Tab behaviour unless this is true. It is prefixed [EXPERIMENTAL — not enforced] because nothing reads it at the pin. Under objectui packages/ and apps/, excluding tests, CHANGELOGs, READMEs and packages/types (which holds its type and zod twin), it has 0 hits; the control schema.editable has 3 hits in ObjectGrid.tsx.
  • The block docblock records each key's read point at the pin, following its convention, and each member repeats its own.

The extraction, and the one text change

  • A parse corpus, run as a scratch script and not committed, covers 28 emptyState values on each of the four list-view doors: ListViewSchema, ObjectListViewSchema, ViewSchema.list and the flattened overlay arm of ViewMetadataSchema. That is 112 cells, compared between BASE 72f8c38201 and head.
    • success and parsed data are identical in 112 of 112 cells.
    • Issue codes, paths and keys are identical in 112 of 112 cells (every message field stripped at every depth).
    • After substituting only the guidance sentence, the full JSON of each result is byte-identical in 112 of 112 cells.
  • The 12 cells that differ at all are the action, button and mixed inputs on each of the four doors. In those cells the action/button guidance is reworded so that it also reads true on object-grid, which has no addRecord block. It still points at addRecord (view-strictness-batch18.test.ts still passes), and it follows the VIEW_ROW_BOUND_GUIDANCE precedent for text that two doors share.
  • The members gain short describes. The list view's reference docs lose the inherited sentence claiming the inline locale map is "resolved at render time"; see Acceptance note 1 for why that claim did not hold.

Liveness record for keyboardNavigation

The liveness ledger has no row for any ComponentPropsMap key. The page ledger leaves page/regions undrilled (it is listed in undrilled-containers.baseline.json), and PageComponent.properties is an open record, so the walk never reaches ObjectGridPropsSchema; a row in page.json would be an orphan. The record is therefore the describe marker [EXPERIMENTAL — not enforced], which is the ledger's own spelling for a declared key that is not enforced (MARKER_RE in check-liveness.mts). The member docblock says to drop the marker in the change that lands the reader. check:liveness is green.

Files outside the claimed surface

  • packages/spec/src/type-alias-convention.pin.test.ts: gen:docs refuses to document a schema that has no type alias, so EmptyState is exported. ADR-0122 then requires an isomorphism pin (Iso_ui_view__EmptyStateSchema), which moves the pin count from 779 to 780. The schema's tree has no default and no transform, so a synonym EmptyStateParsed would be wrong.

Evidence (head 98dad84e21, which includes a merge of origin/main at 07356a6ab0 through os-regen-merge.sh)

  • object-grid corpus: 3 of 36 cases parsed at BASE and 14 of 36 parse at head. The newly accepted cases are exactly the valid values of the three members, and no case is newly refused. Both examples/app-showcase object-grid nodes (command-center cc_queue_g and the my-work queue) parse before and after.
  • Pins: component-object-grid-declared-keys.pin.test.ts passes 18 of 18. §1 checks that the three members parse byte-identically. §2 checks that an unknown top-level key is refused with unrecognized_keys at the row, and that a wrong value in each member is refused (code and path). §3 checks that the grid and the list view hold EmptyStateSchema by identity, and that the grid answers exactly what that schema answers.
  • Ablations, run on the committed state through scripts/ablation-replace.mjs. Each restore was proven: the blob equals HEAD and git diff HEAD is empty.
    • Renaming the keyboardNavigation member: 4 tests fail and 14 pass.
    • Replacing the grid's EmptyStateSchema with a copied z.strictObject: only the identity pin fails, and 17 pass.
  • pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 581 files, 17154 passed, 1 todo.
  • pnpm --filter @objectstack/spec typecheck: exit 0. This includes check:test-typecheck, and the debt ledger is unchanged.
  • Consumer @objectstack/lint. Its closure was built upstream with turbo run build --filter='@objectstack/lint...' (lint plus its dependencies).
    • Tests: 117 files, 5437 passed. typecheck: exit 0.
    • Component-props reading through validateComponentProps on an object-grid node:
      • The three keys produce 0 findings.
      • The control emptyStat produces component-props-unknown-key at properties.emptyStat.
      • The control emptyState.heading produces the same rule at properties.emptyState.heading. That path exists only through the new declaration, which shows the rule read the rebuilt dist.
  • pnpm --filter @objectstack/spec check:generated: all 15 artifacts are up to date.
  • Derived gates (dispatch-gates.mjs --commands, 108 families, run at head): 104 exited 0. Four exited 3 with PREREQUISITE NOT MET, so they are NOT MEASURED:
    • check:skill-examples: the client SDK closure is not built.
    • check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt: each needs the whole-repo package build that CI does.
    • Reconciled with --ran: all 108 accounted for, 104 run, 4 NOT MEASURED.

Acceptance notes

  1. objectui renderer gap: an inline locale map in emptyState.title or .message. At the pin, ObjectGrid passes both to DataEmptyState unresolved (:6241-6242), unlike description. React throws on an object child, measured on react 19.2.8: "Objects are not valid as a React child". ListView guards with typeof === 'string' (plugin-list/src/ListView.tsx:5112 and :5114), so a map there silently falls back to the default text. No producer in examples/** or platform-objects writes a map there; all of them use strings. So this is not filed here. Carrier: the objectui follow-up the seat files at ACCEPT, which lists the three keys in GRID_QUERY_INPUTS. Its fix is one resolver, resolveInlineI18nLabel, applied at both renderers. Meanwhile the changeset tells authors to write strings on object-grid.
  2. origin/main moved to 9905e61ca2 after the merge; 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 edits view.zod.ts and view.mdx. A merge-tree probe from a bare clone with no merge driver shows a clean merge, and the hunks in both files are disjoint from this branch's. CI validates the merge ref.
  3. The object-grid follow-up in objectui, per triage: after the release is installable, list description, emptyState and keyboardNavigation in GRID_QUERY_INPUTS (the reverse direction of the parity gate).

Generated by Claude Code

…Navigation

The list view's inline empty-state shape is extracted into the exported
EmptyStateSchema and taken by reference on both doors; its action/button
guidance is reworded to read true on the object-grid door as well.

Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6
Co-authored-by: Claude <noreply@anthropic.com>
…igation

The three members parse byte-identical, the row still refuses an unknown key
and a wrong value in each member (code and path), and both the grid's and the
list view's emptyState hold EmptyStateSchema itself.

Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6
Co-authored-by: Claude <noreply@anthropic.com>
The reference docs gate refuses a documented schema without its type alias.
The schema has no default or transform, so it is pinned isomorphic (ADR-0122)
rather than given an EmptyStateParsed synonym.

Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6
Co-authored-by: Claude <noreply@anthropic.com>
Regenerated by the build and `check:generated --fix`: authorable-surface,
json-schema manifest, api-surface, export-origins, declaration-map and the
reference docs.

Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6
Co-authored-by: Claude <noreply@anthropic.com>
authorable-surface/ui.json and the reference docs were merged by the os-regen
driver without a text merge; regenerated from the merged tree so both sides'
entries are present (PageHeaderProps.breadcrumb [RETIRED] from main, the
object-grid and EmptyState rows from this branch).

Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 5 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/authorable-surface/ui.json, packages/spec/declaration-map/ui.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/authorable-surface/ui.json, packages/spec/declaration-map/ui.json, …) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 9905e61ca2fddb43d266c23cdb12ced5019c1a74 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f94385ab56cd01d1f71ff7a9f34bc38ea5ed9a4e — the merge of head 98dad84e210101e764efd40d32446187ffed7c92 into base 9905e61ca2fddb43d266c23cdb12ced5019c1a74, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f94385ab56cd01d1f71ff7a9f34bc38ea5ed9a4e && git checkout f94385ab56cd01d1f71ff7a9f34bc38ea5ed9a4e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9905e61ca2fddb43d266c23cdb12ced5019c1a74 98dad84e210101e764efd40d32446187ffed7c92 && git checkout -B drift-repro 9905e61ca2fddb43d266c23cdb12ced5019c1a74 && git merge --no-ff 98dad84e210101e764efd40d32446187ffed7c92

node scripts/docs-audit/affected-docs.mjs --json 9905e61ca2fddb43d266c23cdb12ced5019c1a74

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 98dad84e210101e764efd40d32446187ffed7c92
Local-runs: none

Inputs, and nothing else: card #20694 (body and all 7 comments: triage 5897622232, the two deferrals 5899525593 / 5908413139, the restart probe 5905205373, the serial correction 5910094526, the claim 5912442541, the dev's os-dev-report 5914012653); PR #20882's body, its 13-file list and the net diff origin/main...refs/review/pr20882 (353 additions / 34 deletions, merge-base 07356a6ab0, origin/main at 0803a8b871 when fetched, one commit past the 9905e61ca2 the PR body names); the check-runs on the head. Renderer claims were read at objectstack's pin .objectui-sha = db11afd4967cd9d39381c5e21dc2deec9d706204 with git show in objectui. Nothing was built, run or re-run; the only local git act was a driverless merge-tree --write-tree from a bare shared clone in scratch, a read.

① Derived judgments

Every accept-set and public-surface change the diff implies, each named right or wrong.

  1. ComponentPropsMap['object-grid'] (ObjectGridPropsSchema) accepts three new top-level keys and nothing else changes in its accept set. description: I18nLabelSchema.optional() — RIGHT: read at the pin, plugin-grid/src/ObjectGrid.tsx:5677 resolveInlineI18nLabel(schema.description, displayLocale), drawn at :5784, :6303, :6342 (all five lines verified; the card's resolveI18nLabel is a misnaming, the docblock records the real name). emptyState: EmptyStateSchema.optional() — RIGHT: read at :6225 (schema.emptyState), drawn through DataEmptyState at :6233 with the gating the docblock describes (!loading, not search-emptied, zero rows / zero groups, :6227-6231). keyboardNavigation: z.boolean().optional() — RIGHT per triage's fold from objectui#11068's BUILD: git grep at the pin over objectui packages/ and apps/, excluding tests, CHANGELOGs, READMEs and packages/types, returns zero hits, against the control schema.editable at ObjectGrid.tsx:1708, :1736, :1756. The describe is the one triage specified, prefixed [EXPERIMENTAL — not enforced], which packages/spec/scripts/liveness/check-liveness.mts:500-505 (MARKER_RE.experimental) classifies as experimental — declared, not enforced, a status the ledger recognises (:60-61). The block docblock's own convention (keys derived from renderer read points) is deliberately departed from for this one key, and the docblock says so and says when to drop the marker. Not an advertised capability: the describe, the reference row and the changeset each say authoring it changes nothing today.
  2. The row stays closed. The unknown-key control in component-object-grid-declared-keys.pin.test.ts §2 pins unrecognized_keys at the row for notAGridKey, plus code AND path for a wrong value in each new member. RIGHT — a widening that proves it did not open the row.
  3. EmptyStateSchema is extracted from ListViewShapeSchema.emptyState (view.zod.ts:2879 at origin/main) and both doors hold it by reference. Shape is byte-for-byte the former inline one: strictObject with the same surface, history: VIEW_HISTORY, the same six aliases, members title?: I18nLabel, message?: I18nLabel, icon?: string. StrictObjectOptions.aliases / .guidance are refusal-text options (strict-object.ts:150, :155), so the list view's accept set is unchanged; the dev's 112-cell corpus (success, data, codes, paths identical) is consistent with the shape read. RIGHT. Triage's ⛔ "not a second shape" is honoured, and §3 of the pin asserts identity of _zod.def on both doors, so a later copy reds.
  4. One refusal TEXT changes: the action / button guidance now reads true on both doors (still names addRecord). git grep at origin/main finds the old sentence only in view.zod.ts:2888-2889 itself; no test pins it. Message text is not contract; codes and paths are. RIGHT.
  5. Public surface widens by two exports on @objectstack/spec/ui: EmptyStateSchema (const) and EmptyState (type) — recorded in api-surface/ui.json, declaration-map/ui.json, export-origins/ui.json, json-schema.manifest/ui.json (ui/EmptyState), and authorable-surface/ui.json (ui/EmptyState:{icon,message,title} plus ui/ObjectGridProps:{description,emptyState,keyboardNavigation}). No existing EmptyState / EmptyStateSchema name in packages/spec/src at origin/main, and objectui at the pin has no export * from '@objectstack/spec…', so the new names collide with nothing. RIGHT, additive.
  6. ADR-0122 alias: export type EmptyState = z.input with an isomorphic pin (Iso_ui_view__EmptyStateSchema, 779 to 780). I18nLabelSchema is a z.union([z.string(), InlineLocaleMapSchema]) and InlineLocaleMapSchema a z.record — no default, no transform anywhere in the tree — so z.input === z.infer and an EmptyStateParsed synonym would be wrong. RIGHT.
  7. Generated docs (content/docs/references/ui/component.mdx, view.mdx, index.mdx 1524 to 1525 schemas) and the five spec artefacts read as the generators' output for this source delta; Type Check · source gates (the job the artefact gates ride) reads success.
  8. Net diff against a moved main. origin/main gained 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 (view.zod.ts hunks at 398, 487, 903, 1631, 2611, 5095, 5202, 5239, 5262, 5346, 5377, 6111; view.mdx two options rows at 2260 / 2361) and docs(spec): state the one resolution order for a navigation entry's label #20875 (app.zod.ts, app.mdx); this branch's view.zod.ts hunks are at 1719, 2872, 7146 and its view.mdx hunks at 59, 160, 1128, 1511 — disjoint. component.zod.ts is untouched on main since the merge-base. A driverless git merge-tree --write-tree (bare shared clone, merge.os-regen.driver unset) against 0803a8b871 writes a tree with no conflicted path (exit 0); the same against 9905e61ca2 also clean. The queue's rebuilt generation re-runs the artefact gates on the textual merge, which is where a jointly-wrong generated file would surface.
  9. Check-runs on 98dad84e21 at my last read (2026-09-30T15:17Z), 33 runs: every completed run reads success except Console Pin Gate and Packed-tarball smoke (opt-in), both skipped (path-filtered; the diff removes nothing the pinned sibling imports). Still in_progress at that read: Lint & Repo Gates, Test Core (1/6, 4/6, 5/6, 6/6), Type Check · workspace. Of the seven required contexts, Build Core, Dogfood Regression Gate, Temporal Conformance (live PG + MySQL) and Governed Surface Queue Guard read success; Lint & Repo Gates, Test Core and TypeScript Type Check were not yet concluded. Spec property liveness, Check Changeset, The card this PR closes must claim this branch, No other open PR may claim the same issue all success. This record judges the contract; the landing still waits on every required context reading success (§7).

No governed-surface path in the file list; head repo equals base repo; 387 changed lines.

② Semver level

  • .changeset/20694-object-grid-description-empty-state-keyboard-navigation.md: '@objectstack/spec': minor. The only released package the diff touches is @objectstack/spec (content/docs/** publishes nothing). RIGHT.
  • The diff publishes: three optional keys on a strict authorable shape, two new exports, one refusal message reworded, no removal, no rename, no narrowing — nothing that parsed before is refused (§1 above). A minor widening, no migration owed. RIGHT.
  • Clause-②: yes (widening) in the changeset; Clause-②: yes in the PR body — one declaration, yes with the (widening) arm, matching triage's grade (5897622232) and the claim (5912442541), and yes takes at least minor, which it has. Check Changeset reads success.
  • The changeset body carries the interim authoring guidance for the renderer gap (write plain strings on object-grid's emptyState.title / .message), which is the right place for it since it ships as CHANGELOG.md.

Clause-②: yes (widening) — minor, matches the diff.

③ Boundary flags

Every dev flag and every open_questions entry, answered or escalated.

  • Deviation 1 — the guidance sentence reworded on the list-view doors. ANSWERED, accepted: accept set unchanged (①.3), text not contract, no test pins the old sentence (①.4), addRecord still named, and the VIEW_ROW_BOUND_GUIDANCE precedent covers text two doors share.
  • Deviation 2 — no liveness ledger row; the describe marker is the record. ANSWERED, accepted: page/regions is in scripts/liveness/undrilled-containers.baseline.json:113 and PageComponent.properties is an open record, so a page.json row would be an orphan; MARKER_RE.experimental recognises the marker (①.1); Spec property liveness reads success. The member docblock says to drop the marker in the change that lands the reader — that instruction is the ADR-0049 hand-off.
  • Deviation 3 — type-alias-convention.pin.test.ts outside the claimed surface. ANSWERED, accepted: a mechanical consequence of exporting EmptyState (gen:docs refuses an undocumented-alias schema); the pin's shape is right (①.6); count 779 to 780 with the rationale recorded at the assertion.
  • Deviation 4 — the list-view reference docs lose the inherited "resolved at render time" sentence. ANSWERED, accepted: at the pin plugin-list/src/ListView.tsx:5112 / :5114 guard typeof … === 'string' and drop a map to the default text, so the inherited sentence overstated the renderer; the member describes now say what the members are without claiming resolution.
  • Deviation 5 — os-regen-merge.sh step 3 regeneration committed separately as 98dad84e21. ANSWERED, procedural: the head under review is the regenerated state; the artefact gates ride Type Check · source gates (success); the queue re-validates the merge ref.
  • Deviation 6 — card names resolveI18nLabel, pin has resolveInlineI18nLabel. ANSWERED: verified at ObjectGrid.tsx:5677; the docblock records the real name. Correct.
  • Open question 1 — should the objectui follow-up filed at ACCEPT also carry the renderer fix for an inline locale map in emptyState.title / .message? ANSWERED: A, and the spec declaration stands as it is. Verified at the pin: ObjectGrid.tsx:6241-6242 pass authoredEmptyState?.title || t('table.noResults') and authoredEmptyState?.message || undefined to DataEmptyState unresolved, unlike description at :5677; and objectui's own twin packages/types/src/zod/objectql.zod.ts:392-399 declares the two members as z.string(), narrower than this shape. Triage ruled one shape with I18nLabel members and that objectui's twin follows the spec, not the reverse (5897622232), and Prime Directive Add comprehensive test suite for Zod schema validation #12 puts the catch-up on the consumer. So option C is rightly rejected, and B would split one objectui change into two cards for no gain. The seat's ACCEPT-time objectui follow-up card names all three, in its body, so none rides in silently: (i) GRID_QUERY_INPUTS lists description, emptyState, keyboardNavigation (the parity gate's reverse direction, per triage); (ii) the twin's emptyState.title / .message widen to the spec's I18nLabel; (iii) resolveInlineI18nLabel applied at ObjectGrid.tsx:6241-6242 and at ListView.tsx:5112 / :5114. Blocked-by the spec release being installable. Until then the changeset's "write plain strings" line is the authoring guidance, and the ⚠️ paragraph in the member docblock keeps the declaration honest (Prime Directive chore: version packages #10: the gap is stated, not advertised over).
  • Out-of-scope finding 1 — the same renderer gap, reach: no named producer. ANSWERED: verified as above; carrier is the objectui follow-up in the previous item; not a card on this repo. Noted, not filed here, is the right disposition — the defect is objectui's, and its card is the follow-up the seat files.
  • Acceptance note 2 — main moved after the merge. ANSWERED: re-probed against the newer 0803a8b871 (①.8); clean, hunks disjoint. No second merge owed before enqueue; the queue validates the merge ref.
  • Acceptance note 3 — the objectui follow-up for GRID_QUERY_INPUTS. ANSWERED: folded into the single follow-up card above, per triage.
  • keyboardNavigation declared ahead of any reader (the row's docblock convention is read-point-derived). ANSWERED, not escalated: triage's fold (5897622232, confirmed by the restart probe 5905205373) directed it, the ledger's experimental class is the recorded status, the reference row says "No renderer reads it yet", and objectui#11068's BUILD is the reader's card. The marker-drop instruction in the member docblock is the exit.

Nothing escalated.

Implemented-by: claude/issue-20694-object-grid-desc-emptystate-keynav
Reviewed-by: session_018fxqvRJW12TaHC7DUQ89Y6

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 15:28
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit f5c7b2c Sep 30, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20694-object-grid-desc-emptystate-keynav branch September 30, 2026 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

2 participants