Skip to content

feat(app-shell): a row-table page block lists the rows a page api binding answers - #31883

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-oorsb5
Nov 9, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-oorsb5

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #31856

Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — skills/ teaches no product block (git grep tile-grid|usage-bar skills/ = 0)

维护者速读

  • 改了什么:控制台新增一个页面块 row-table,把页面 api 绑定读到的一组数据画成表格:列在元数据里声明(字段路径、标题、格式:文本 / 日期时间 / 枚举标签),每一行都画,没有数据时显示空状态文字。
  • 为什么改:云端的 AI 员工页(cloud#4073)和 AI 调用追踪页(cloud#4058)要列出接口返回的多行数据,现在只能用固定条数的文字行凑合;这个块让它们按真实行数显示。
  • 风险与回滚:纯新增,没有页面在用,现有块和页面不变;不改 @objectstack/spec,SDUI 清单不动。回滚即还原本 PR。
  • 先看:ui/packages/app-shell/src/console/blocks/productBlocks.tsx(readRowTable 的拒收判定与 rowTableCell)、ui/packages/types/src/zod/product-blocks.zod.ts(RowTableColumnSchema 的严格校验)、ui/packages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.ts(命名空间计数 306→307)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/console/blocks/__tests__/rowTable-31856.test.tsx packages/types/src/__tests__/product-block-arms-12197.test.ts,预期两个文件全绿。
  • 需要你做的:无,席位落地。

What changes

  • @object-ui/app-shell: the row-table block, the sixth product-page block beside hero, stat, usage-bar, checklist and tile-grid, registered under the app-shell namespace with the bare row-table as its authorable key and the same API_BINDING_INPUT.
    • rows: a list, or one whole ${…} hole naming a list in the bound data, as tile-grid's items. Every row is drawn, in order, with no cap.
    • columns: { path, label, format?, enum? }, drawn in the order written. The keys are resultDialog.fields' vocabulary (path / label / format, text shared); datetime and enum are this block's own formats. path is read per row with selectElementApiPath (@object-ui/react), the binding's own path reader.
    • text draws as written (an object as compact JSON); datetime goes through formatDateTime(value, { locale: useDisplayLocale(), style: 'compact' }) from @object-ui/core, the datetime field cell's face, in the display zone the host declared, else the viewer's; enum draws the label enum: { value: label } names, and a value the map does not name as written.
    • A field the row lacks, null or '' draws —. An empty list draws emptyLabel, else the existing table.noRows key. No new i18n key.
    • A rows hole that resolves to anything but a list, or a column the block cannot draw (no path, an unknown format, enum with no map), draws an error face with the binding's own title (element.component.apiUnreadable) and a sentence naming the input. Never the empty state.
  • @object-ui/types: its arm. RowTableBlockSchema and RowTableColumnSchema, with the TS twins RowTableBlockSchema, RowTableBlockProps and RowTableColumn, joining ProductBlockComponentSchema, so objectui validate claims the type. The arm is strict: an unknown format, an enum column with no map, a map on a column that is not enum, a path that is not a dotted path, an undeclared key (the card's prose word header included) and an empty columns are refused. dataSource is the spec's ElementApiDataSourceSchema, by reference. The mirror-parity pin registers both pairs (199 → 201) as spec-derived.
  • @object-ui/cli: known-schema-types.ts regenerated (row-table, app-shell:row-table). The 10859 ratchet's namespaced count moves 306 → 307 for app-shell:row-table, a named stopgap raise of that one key, written beside the objectui#13301 raise it follows: the namespaced twin is refused at type by design so the bare key stays the one authorable spelling. Both productBlocks-namespace-13301 lists (app-shell and cli) gain row-table.
  • Docs: the app-shell README block table and a row-table example, content/docs/frontend/data-source.mdx and content/docs/ui/pages.mdx (the block lists that take the api binding).
  • No @objectstack/spec change. The type parses through PageComponentSchema.type's open string arm, and component-type-unknown claims only colon namespaces. The SDUI manifest does not move: @object-ui/app-shell is not one of the manifest generator's registry modules (node scripts/check-sdui-manifest.mjs: exit 0, 106 components).

Tests

  • rowTable-31856.test.tsx (new, eight cases, through the real SchemaRenderer and the host apiFetch): the registration's inputs; every bound row in authored column order; enum labels and an unnamed value drawn raw; datetime in a pinned display zone (Asia/Tokyo) equal to formatDateTime's face; — for a missing and a null field; the authored empty label and the bundle's; a non-list hole draws the error face and not the empty state; an unknown format draws the error face.
  • product-block-arms-12197.test.ts: the row-table fixture joins the shared accept / content-channel / undeclared-prop loop, plus six row-table cases (literal rows, unknown format, enum without a map and a map without enum, a non-dotted path, header, empty columns and a partial hole).
Runs (branch on 1ff1d45; head 5e6d804 merges main a57a007, which moves no `packages/spec` or `ui/packages` file, so the runs below stand)
  • pnpm --dir ui exec vitest run packages/app-shell/src/console/blocks/: Test Files 4 passed (4), Tests 39 passed (39) (re-run after the last source commit).
  • pnpm --dir ui exec vitest run packages/app-shell/src/console/: Test Files 267 passed (267), Tests 2038 passed (2038).
  • pnpm --dir ui exec vitest run packages/types/: Test Files 389 passed (389), Tests 10489 passed (10489); pnpm --filter @object-ui/types run type-check (the mirror-parity type legs): exit 0.
  • pnpm --dir ui exec vitest run packages/cli/: Test Files 31 passed (31), Tests 420 passed (420).
  • pnpm --filter @object-ui/app-shell run type-check (tsc --noEmit and tsc -p tsconfig.test.json, test files included): exit 0.
  • pnpm --dir ui exec vitest run scripts/: Test Files 170 passed | 2 skipped (172), Tests 5027 passed | 2 skipped.
  • ui censuses, each exit 0: check:spec-symbols, check:component-surface-parity, check:element-data-source-declaration, check:registry-bare-names, check:doc-types, check:doc-snippets (537 of 537 blocks), check:doc-examples, check:i18n-keys, check:i18n-drift, check:new-line-citations, check:control-bytes.
  • dispatch-gates --commands: 69 families derived, 69 run, each exit 0 (--ran: 0 NOT-MEASURED, 0 UNRUN; the list is identical after the merge of main). check:skill-examples first answered exit 3 (client SDK unbuilt) and was re-run green after building it; check:query-options-erasure was re-run green with a longer timeout.
Ablation (scripts/ablation-replace.mjs, restored to the HEAD blob)
  • const rows = props.rows; → const rows = Array.isArray(props.rows) ? props.rows : []; (the non-array guard made an empty list): exactly "refuses a hole that resolves to a non-list with the error panel, never the empty state" goes red, 1 failed | 7 passed; restored, git diff HEAD empty.

Acceptance notes

  • Consumers: cloud#4073 (the AI workforce page, which replaces its capped element:text rows with this block) and cloud#4058 (the AI call trace page). Neither is in this repo; no page here places the block yet.
  • os validate does not judge the block's properties, as for every product block: there is no ComponentPropsMap row, so the strict arm is judged by objectui validate / safeValidateSchema. The seat ruled this a family question for all six blocks, not this card's.
  • The error face is drawn by the block, not by ElementDataSourceErrorPanel: that panel marks a consumer of the object-binding gate (check:element-data-source-declaration refused the first draft), which an api-bound product block is not. It uses the same classes, role="alert" and the binding's own title key.
  • NOT MEASURED locally: check:sdui-registration-pins (it weighs a built console bundle; CI's Build & E2E UI builds it). check:type-check-debt ran under the verify lock: exit 0.

…ding answers

The sixth product-page block: rows from a list (or a whole hole naming one
in the bound data), declared columns with path, label and a text, datetime
or enum format, every row drawn, an empty label, and the binding's error
panel for a list it cannot read. Its strict @object-ui/types arm lands with
it, and the cli's known types are regenerated.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTx4LkMSXMTUGZuzQW
…nding's panel

The object-binding error panel marks a consumer of ElementDataSourceGate,
which an api-bound product block is not.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTx4LkMSXMTUGZuzQW
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 9, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · zhuangjianguo · session_01QsR2jVEjiYrK7onirxUGyo

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 5e6d804d82bcf99052eefbaa44531f1a42f1bf09
Local-runs: none

This is the lane seat's own review of record, owed by the Clause-②: yes declaration (face declaration). packages/spec/** and the server are untouched. check-governed-merges --pr 31883 reads NOT governed: 0 of 17 paths, 596 lines. dispatch-gates --tier gives no path-derived mandate. The precedent is #31370, which added a registered widget's @object-ui/types arm and was reviewed at TIER_DEFAULT.

① Derived judgments

  • @object-ui/types is published and only widens.
    • RowTableBlockSchema joins ProductBlockComponentSchema.
    • RowTableColumnSchema and the TS twins (RowTableBlockSchema, RowTableBlockProps, RowTableColumn) are new exports.
    • No existing arm changes. A document that parsed before parses the same way. A document typed row-table was refused before and is now judged by the new strict arm.
  • The arm's refusals:
    • an unknown key, at properties and in each column;
    • a column path outside the binding's own path grammar, with a message naming the grammar;
    • a format outside text | datetime | enum;
    • enum without format: 'enum', and format: 'enum' without enum, each at the enum path;
    • an empty columns;
    • body and children, by the family's tombstones.
      The api binding is the spec's ElementApiDataSourceSchema, by reference (apiBinding()).
  • @object-ui/app-shell is private. It adds RowTableBlock and its registration under app-shell with the bare row-table.
    • The renderer re-judges what it reads. A rows hole that resolves to a non-list, or a column the arm would refuse, draws the error face naming the input, and never the empty state.
    • Cells are read with selectElementApiPath. Datetime goes through formatDateTime in the display locale, and it re-renders when the host's display zone changes.
    • An enum value the map lacks is drawn raw.
  • @object-ui/cli: known-schema-types.ts is regenerated with row-table and app-shell:row-table.
  • Metadata-protocol: added is RIGHT, as ruled in amendment 6735476936: a page author can write a new type with judged keys. The PR body carries Skills-docs: none — ….
  • Accept sets: one widening, the row-table arm. There is no narrowing.

② Semver level

  • .changeset/31856-row-table-block.md marks @object-ui/types as minor, and @object-ui/app-shell and @object-ui/cli as patch.
  • Both declaration lines are at line start, in the changeset and in the PR body.
  • That is RIGHT: the protocol addition is minor on the package that publishes the arm.

③ Boundary flags

  • Accepted deviations, as the report names them:
    1. NAMESPACED_REFUSED_AT_TYPE goes 306 → 307. This is a named one-key stopgap raise for app-shell:row-table, as objectui#13301 did for the five.
    2. The error face is drawn by the block itself, with the binding's classes, role="alert" and its apiUnreadable title. Importing the panel made check:element-data-source-declaration read productBlocks.tsx as an object-binding consumer.
    3. Mirror pairs go 199 → 201.
    4. One namespace-13301 test title drops the word "five".
  • NOT MEASURED: check:sdui-registration-pins, which needs the console bundle. CI measures it.
  • open_questions: none.

Implemented-by: claude/project-thread-oorsb5
Reviewed-by: session_01QsR2jVEjiYrK7onirxUGyo

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · zhuangjianguo · session_01QsR2jVEjiYrK7onirxUGyo

席位核对 · ACCEPT · 2026-11-09T09:40Z · head 5e6d804d82bcf99052eefbaa44531f1a42f1bf09

Seat domain:ui#3 reviewed the diff of its own dispatch for #31856. The card thread is session_01QsR2jTx4LkMSXMTUGZuzQW, and the os-dev-report is 6736548473. I checked against GitHub, not against the report.

  1. Shape: 17 files, +570 / -26, draft on main, Fixes #31856.
    • The row-table block is the sixth app-shell product block, registered beside tile-grid with API_BINDING_INPUT. It takes rows (a list, or a whole ${…} hole), columns (path / label / format: text | datetime | enum / enum) and emptyLabel.
    • Its strict @object-ui/types arm is RowTableBlockSchema.
    • The cli's known-schema-types is regenerated.
    • The docs are three pages.
  2. Premise: at 1ff1d45d, no api-bound block drew rows. The cloud lane shows capped element:text rows as a stopgap (cloud#4073, cloud#4058). The step-1 report is 6735450873, and the amendment is 6735476936.
  3. Gates and check-runs:
    • 61 runs on this head: 51 success, 10 skipped, 0 red. mergeable_state is clean.
    • Dev: 69/69 derived gates exit 0, with --ran reconciled.
    • By hand, all green: blocks 39/39, app-shell console/ 2038/2038, types 10489/10489, cli 420/420, ui scripts/ vitest, and the ui censuses (check:spec-symbols included).
    • The non-array guard ablation turns exactly its pin red, and it restores clean.
  4. Declaration review: TIER_DEFAULT PASS, record 6736580074 on this head.
  5. Tests read:
    • Column order.
    • Datetime in a pinned display zone.
    • Enum labels, with an unmapped value drawn raw.
    • A missing field reads —.
    • The empty state: emptyLabel, else table.noRows.
    • A non-array hole draws the error face, not the empty state.
    • An unknown format is refused. The arm pins a refused path, a refused format and the enum pairing.
  6. Accepted deviations:
  7. Landing:

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 9, 2026 09:41
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 9, 2026 09:41
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit ac8fede Nov 9, 2026
62 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-oorsb5 branch November 9, 2026 10:01
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 size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant