Skip to content

finding(components): the static table renderer reads 9 of the 20 keys TableSchema/TableColumn declare — hoverable, striped, align, sortable, cell and 6 more are inert, and the reference page documents two of them as working #5474

Description

@os-support-ai

Found while implementing #5350 (retiring that renderer's undeclared column aliases). Observation class — filed unassigned and deliberately NOT fixed there, because which way it resolves is an ADR-0049 enforce-or-remove contract decision, not a re-spelling. #5350 stayed inside the spelling question the family ruling settled.

Measured

Every key packages/components/src/renderers/complex/table.tsx reads, derived from the file rather than recalled:

grep -oE '(schema|col)\.[a-zA-Z]+' packages/components/src/renderers/complex/table.tsx | sort -u
col.accessorKey  col.cellClassName  col.className  col.header  col.width
schema.caption   schema.columns     schema.data    schema.footer

Nine. Against that, packages/types/src/data-display.ts declares:

declaring type declared and read declared and inert
TableSchema caption, columns, data, footer hoverable, striped
TableColumn header, accessorKey, className, cellClassName, width minWidth, align, fixed, type, sortable, filterable, resizable, editable, cell

Eleven declared keys are never read. hoverable and striped even carry @default annotations (true / false) describing behaviour that does not exist on this renderer.

The published page documents two of them as working

content/docs/api/schema-reference.md §TableSchema:

Property Type Description
hoverable boolean Highlight rows on hover.
striped boolean Alternate row background colors.

and its copyable example sets "hoverable": true, "striped": true. Both are inert. The section header one line above likewise reads "A data table with columns, optional striping, and hover effects."

This is the same declared-but-unenforced shape as #5373 (type: 'crud': four declaration faces, no renderer), one level down: the type renders, but a majority of what it declares does not. #5350 corrected the same section's spelling contract and deliberately left hoverable/striped in the example, because deleting them would have pre-empted this decision.

Not merely cosmetic for AI authoring

align and sortable are the two most likely for a generator to emit — a right-aligned amount column is the obvious thing to author, and the pre-#5350 reference example did exactly that ({ "name": "amount", "label": "Amount", "align": "right" }). It is accepted, type-checks, and does nothing, with no diagnostic. Same silent class as #5349.

The routes, not pre-decided

  1. Implement the inert keys on this renderer (hoverable/striped/align are a few lines each; sortable/filterable/resizable/editable/cell are substantially more, and arguably why data-table exists).
  2. Retire them from TableSchema/TableColumn under ADR-0049 and point authors at data-table for the interactive set — the narrow reading being that table is deliberately the dumb static renderer and its type over-declares.
  3. Split the type: keep TableColumn as the rich shared shape data-table honours and give the static renderer a narrow declared subset, so declared = enforced holds per renderer.

Route 2 or 3 look consistent with the startup-focus / implementation-first standing instruction, but the pick is a maintainer call. Note TableColumn is shared: DataTableSchema and others consume it, so a retirement must be checked against those consumers first — this is not a single-renderer edit.

Reproduce

grep -oE '(schema|col)\.[a-zA-Z]+' packages/components/src/renderers/complex/table.tsx | sort -u
sed -n '227,278p' packages/types/src/data-display.ts    # TableColumn
sed -n '285,313p' packages/types/src/data-display.ts    # TableSchema
sed -n '394,422p' content/docs/api/schema-reference.md  # the page, post-#5350

Related: #5350 (the spelling half, which left this alone), #5373 (same enforce-or-remove class), #5349 (the silence).

Generated by Claude Code

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Aug 21, 2026
  2. added theissue type on Aug 21, 2026
  3. os-zhuang commented on Aug 21, 2026

    @os-zhuang
    Contributor

    Triage (first-touch): routed to the decision inbox (needs-user-decision, domain:ui, type Task). Eleven declared-but-inert keys on a published authoring surface, two of them documented as working — this is an enforce-or-remove contract-shape call, which is the manual floor. Chinese four-facet analysis below.

    os-decision-facets(四棱卡面块;标记按字面文本写入)

    一句话问题:静态表格组件的「使用说明书」列了 20 个旋钮,实际接线的只有 9 个 —— 文档还专门教用户去拧那 11 个没接线的(斑马纹、悬浮高亮、右对齐都在内),拧了没反应也不报错。

    选项与真实代价:

    • A. 把没接线的旋钮实现掉:业务上 = 静态表格长出排序/筛选/编辑,和交互表格 data-table 越长越像。真实代价:重复建设一套 data-table 已有的能力,养两个交互表格。
    • B. 从说明书上删掉没接线的旋钮(ADR-0049 退役),指路 data-table:业务上 = 「简单表格就是简单表格,要交互用 data-table」,一句话讲清;AI 生成器不再产出无效配置。真实代价:存量 schema 里写了这些键的会被严格校验响亮拒绝,需按退役正道走(墓碑 + 迁移提示);TableColumn 是共享类型,DataTableSchema 等消费者要先查清再动。
    • C. 拆类型:富形状归 data-table,静态表格声明自己的窄子集 —— 每个渲染器 declared = enforced。业务上 = 说明书按产品分册,各自诚实。真实代价:比 B 多一次类型拆分与迁移,但共享消费者问题就地解决。
    • D. 不动:AI 和用户继续拧假旋钮,静默无效 —— 本仓反复付过账的那一类。

    四棱:

    • ① 项目长远合理性:B/C 收敛契约,A 是能力面扩张;C 结构最诚实(声明即强制按渲染器成立)。
    • ② 实际业务拉动:零拉动实现(A)违背 implementation-first;今天的真实伤害是假旋钮(右对齐金额列是生成器最常写的),删除即止血。
    • ③ 防 AI 犯错:B/C 让无效配置在编写期响亮被拒;A 治标不治「声明先于实现」的病根;D 是静默容忍的温床。
    • ④ 创业阶段不扩散:B 最瘦;C 次之但一步到位;A 扩面。
    • 推荐:C(回退:B —— 若共享消费者排查证明拆分成本过高)。回一个字母即可。

    置信缺口(本分析看不见什么):TableColumn 的全部消费者面未清点(卡内点名 DataTableSchema,可能还有别家);存量用户 schema 里这 11 个键的实际书写量未测(若大量存在,B/C 的迁移提示要更周全)。裁后执行:C 或 B 均按 ADR-0049 退役正道立实施卡入 ui 车道(墓碑、schema-reference 文档同步、palette/生成器扫尾),A 则拆卡按能力逐个排队;文档先行修正(schema-reference 删 hoverable/striped 教学)可作首段独立小卡。


    Generated by Claude Code

  4. huangyiirene commented on Aug 22, 2026

    @huangyiirene
    Collaborator

    Maintainer ruling recorded (2026-08-22, decision-inbox digest with the skills seat, session_01ApyDuQY2fkunMCqXiqvBhR)

    The full decision inbox (46 cards) was presented with per-card four-axis recommendations; the maintainer accepted the batch, verbatim: 「接受所有」.

    Ruled: Option C — split the types. TableColumn remains the rich shared shape that data-table honours, and the static table renderer gets its own narrow declared subset, so declared = enforced holds per renderer. Execution follows the ADR-0049 retirement path for the keys leaving the static renderer's surface (tombstones, schema-reference doc sync, palette/generator sweep), and the shared-consumer surface of TableColumn (DataTableSchema and any others) must be enumerated before the split. Attached condition from the batch: the teaching-material removal (deleting the hoverable/striped teaching from schema-reference.md) may be split out as its own standalone first card. Fallback recorded in the analysis stands: if the shared-consumer audit shows the split's cost is prohibitive, the fallback is Option B (plain retirement pointing authors at data-table).

    State: needs-user-decision removed → pm:queue (ready for dispatch).


    Generated by Claude Code

  5. claude commented on Aug 23, 2026

    @claude
    Contributor

    Claim — domain:ui execution seat.

    Dispatching this round (R27) at the contract-review tier — the maintainer's ruled Option C splits a published type, and the keys leaving the static renderer's surface go down the ADR-0049 retirement path.


    Generated by Claude Code

  6. claude commented on Aug 23, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 5474,
      "status": "done",
      "branch": "claude/issue-5474-static-table-narrow-types",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5822",
      "premise_still_valid": true,
      "summary": "Option C executed verbatim: new narrow StaticTableColumn (header/accessorKey/className/cellClassName/width) + interface/zod exports; TableSchema.columns moved to it; the 11 never-read keys retired as ADR-0049 tombstones (?: never / z.never().optional(), crud.ts confirm convention) so authored values are refused loudly with the key named — rich TableColumn byte-untouched and pinned untouched. Census re-derived with a stronger instrument (full-file read + SchemaRenderer prop-flow; the instrument now lives in-tree and fails on destructure/computed/helper patterns that would blind it): card's 9-read/11-inert numbers exact. Shared consumers enumerated (DataTableSchema, CRUDSchema, views.ts relations, CRUDBuilder, zod DataTableSchema) — all stay rich; fallback B not needed. Doc sync: schema-reference §TableSchema loses the hoverable/striped teaching (example + table + intro), columns row now names the enforced subset; sweep cleaned types/examples json + site playground; palette inputs description gains cellClassName.",
      "tests": "All at c3cb8c3c4 (the branch's single commit). Union: pnpm exec vitest run packages/types/ packages/components/src/renderers/ scripts/__tests__/doc-version-claims.test.ts scripts/__tests__/check-doc-links.test.ts -> 'Test Files 126 passed (126) / Tests 1318 passed (1318)'. Typecheck: 5 tsc projects green (types: noEmit+examples+tests; components: noEmit+tests) against the rebuilt turbo closure — components leg imports StaticTableColumn through dist/index.d.ts, a symbol only the new build carries, so green cannot be stale. Reverse verification, both legs from committed state, predicted direction red observed: (A) soften hoverable tombstone -> 'Tests 2 failed | 19 passed' (refusal pin + tombstone-set pin); (B) delete the renderer's cellClassName read -> instrument red on 'declared live but never read'. Ablation rebuild statement: no build leg needed — the root vitest config aliases @object-ui/types and /zod to src, so the mutated files ARE what vitest loads; mutations confirmed on disk by anchored grep before each run (leg A marker=1/removed-anchor=0; leg B col.cellClassName=0), restores proven by inverse counts, trap-guarded scripts. Gates: check-changeset-presence OK ('5 source file(s) of 2 released package(s) ... declares 1 changeset(s)'); check:control-bytes/doc-types/spec-symbols exit=0; turbo lint types+components '3 successful'. check:doc-snippets self-reported NOT RUN locally (needs full workspace build) — declared narrowing, closed by CI. CI read once by name at ~10.5 min after run start (13 runs on head c3cb8c3c4): all completed, 12 success + Dependabot Auto-merge skipped, zero failure/cancelled, mergeable_state clean — including Doc Snippet Types success and CI success. No 'Build Docs'-named workflow materialized on this head; the completed/no-failure/clean judge holds over the actual 13.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #5821: rich TableColumnSchema zod mirror omits `editable` — a key data-table reads at 3 sites is silently stripped by validation, so a column locked editable:false re-opens after parse (#4605 class; my rich-accept test loop excludes editable with a comment pointing at it)",
        "NOT re-filed: stale skills teaching of `label` on a static table column (skills/objectui/guides/schema-expressions.md) already open as #5473 — search-first caught it; nothing new added there"
      ]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:queue

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions