Skip to content

Add default props to all components to prevent collapse in designer - #4

Merged
huangyiirene merged 5 commits into
mainfrom
copilot/add-initial-state-to-components
Jan 13, 2026
Merged

huangyiirene merged 5 commits into
mainfrom
copilot/add-initial-state-to-components

Conversation

Copilot AI commented Jan 13, 2026 •

Copy link
Copy Markdown
Contributor

Components collapsed into invisible heaps when dragged into the designer due to missing initial dimensions and content.

Changes

Added defaultProps to 40+ component registrations across:

  • Basic (div, text, span, separator) - added minimum dimensions and padding
  • Form (button, input, checkbox, select, textarea, etc.) - added labels, placeholders, sample data
  • Layout (card, tabs) - added titles, descriptions, sample content
  • Data display (alert, avatar, badge) - added sample text and proper sizing
  • Overlay (dialog, drawer, popover, sheet, tooltip, etc.) - added trigger buttons and content
  • Complex (table, carousel, resizable, scroll-area) - added complete sample data structures
  • Navigation (sidebar, header-bar) - added default menu structures
  • Feedback (progress, skeleton, toaster) - added initial values and dimensions

Example before/after:

// Before - component collapses when dropped
ComponentRegistry.register('input', InputComponent, {
  label: 'Input Field',
  inputs: [...]
});

// After - component appears with proper dimensions and content
ComponentRegistry.register('input', InputComponent, {
  label: 'Input Field',
  inputs: [...],
  defaultProps: {
    label: 'Label',
    placeholder: 'Enter text...',
    inputType: 'text',
    id: 'input-field'
  }
});

The Canvas component already spreads defaultProps when creating nodes (lines 67-71), so no designer changes needed.

Tests added to verify all components have defaults and maintain proper structure.

Original prompt

每一个组件都应该有一个初始状态,否则拖到设计器里面缩成一团


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 3 commits January 13, 2026 14:00
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Copilot AI changed the title [WIP] Add initial state to all components Add default props to all components to prevent collapse in designer Jan 13, 2026
Copilot AI requested a review from huangyiirene January 13, 2026 14:11
@huangyiirene
huangyiirene requested review from Copilot and removed request for huangyiirene January 13, 2026 14:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds defaultProps to component registrations to prevent components from collapsing when dragged into the designer. Components now appear with meaningful initial dimensions, content, and configurations.

Changes:

  • Added defaultProps with initial dimensions, labels, placeholders, and sample content to 40+ component registrations
  • Added defaultChildren to container components that require nested elements
  • Added comprehensive test coverage for default props validation

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/renderer/src/renderers/overlay/tooltip.tsx Added default trigger button, content text, delay, and side positioning
packages/renderer/src/renderers/overlay/sheet.tsx Added default title, description, trigger button, and sample content
packages/renderer/src/renderers/overlay/popover.tsx Added default trigger button, content, alignment, and side positioning
packages/renderer/src/renderers/overlay/hover-card.tsx Added default trigger button, hover content, and side positioning
packages/renderer/src/renderers/overlay/dropdown-menu.tsx Added default trigger button and sample menu items with separator
packages/renderer/src/renderers/overlay/drawer.tsx Added default title, description, trigger button, and content
packages/renderer/src/renderers/overlay/dialog.tsx Added default title, description, modal flag, trigger button, and content
packages/renderer/src/renderers/overlay/context-menu.tsx Added default menu items with separator and trigger text
packages/renderer/src/renderers/overlay/alert-dialog.tsx Added default title, description, action/cancel text, and trigger button
packages/renderer/src/renderers/navigation/sidebar.tsx Added default props and children structure for sidebar components
packages/renderer/src/renderers/navigation/header-bar.tsx Added default breadcrumb structure
packages/renderer/src/renderers/layout/tabs.tsx Added default tab items with values and content
packages/renderer/src/renderers/layout/card.tsx Added default title, description, and width class
packages/renderer/src/renderers/form/toggle.tsx Added default label, variant, and size for toggle components
packages/renderer/src/renderers/form/textarea.tsx Added default label, placeholder, and ID
packages/renderer/src/renderers/form/switch.tsx Added default label and ID
packages/renderer/src/renderers/form/slider.tsx Added default value, range, and width
packages/renderer/src/renderers/form/select.tsx Added default label, placeholder, and sample options
packages/renderer/src/renderers/form/radio-group.tsx Added default ID and sample radio items
packages/renderer/src/renderers/form/input.tsx Added default label, placeholder, type, and ID
packages/renderer/src/renderers/form/input-otp.tsx Added default max length
packages/renderer/src/renderers/form/checkbox.tsx Added default label and ID
packages/renderer/src/renderers/form/calendar.tsx Added default mode and border styling
packages/renderer/src/renderers/form/button.tsx Added default label, variant, and size
packages/renderer/src/renderers/feedback/toaster.tsx Added default provider type
packages/renderer/src/renderers/feedback/skeleton.tsx Added default width, height, and styling
packages/renderer/src/renderers/feedback/progress.tsx Added default value and width
packages/renderer/src/renderers/disclosure/collapsible.tsx Added default trigger button, content, and width
packages/renderer/src/renderers/disclosure/accordion.tsx Added default accordion items with content
packages/renderer/src/renderers/data-display/badge.tsx Added default label and variant
packages/renderer/src/renderers/data-display/avatar.tsx Added default fallback initials and alt text
packages/renderer/src/renderers/data-display/alert.tsx Added default title, description, and variant
packages/renderer/src/renderers/complex/table.tsx Added default caption, columns, and sample data rows
packages/renderer/src/renderers/complex/scroll-area.tsx Added default dimensions, orientation, and sample content
packages/renderer/src/renderers/complex/resizable.tsx Added default direction, dimensions, and sample panels
packages/renderer/src/renderers/complex/carousel.tsx Added default orientation, arrows, and sample slides
packages/renderer/src/renderers/basic/text.tsx Added default text content
packages/renderer/src/renderers/basic/span.tsx Added default padding and child text
packages/renderer/src/renderers/basic/separator.tsx Added default orientation and margin
packages/renderer/src/renderers/basic/div.tsx Added default padding, border, and minimum height
packages/renderer/src/tests/registry.test.tsx Added tests for defaultProps and defaultChildren support
packages/renderer/src/tests/defaultProps.test.tsx Added comprehensive test suite validating all component defaults

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

Copy link
Copy Markdown
Contributor

✅ All checks passed!

  • ✅ Type check passed
  • ✅ Tests passed
  • ✅ Lint check completed

@huangyiirene
huangyiirene marked this pull request as ready for review January 13, 2026 14:26
@huangyiirene
huangyiirene merged commit 0c94c51 into main Jan 13, 2026
5 checks passed
Copilot AI added a commit that referenced this pull request Jan 24, 2026
- Fix handleExportCSV to guard on gridRef.current?.api (issue #1)
- Add dedicated onContextMenuAction callback instead of overloading onCellClicked (issue #2)
- Remove icon property from customItems to prevent HTML injection (issue #3)
- Remove validation claim from README - only basic AG Grid editing (issue #4)
- Add test assertions for all new inputs (editable, exportConfig, etc.) (issue #5)
- Fix onExport type to only support 'csv' format (issue #6)
- Remove unused ColumnConfig properties (autoSize, groupable) (issue #9)
- Type schema props with proper interfaces instead of 'any' (issue #10)
- Update export description to only mention CSV (issue #11)
- Add AG Grid Community vs Enterprise section to docs (issue #8)
- Update README and docs with new callback and clarifications

All tests pass (8/8), lint clean (0 errors)

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI added a commit that referenced this pull request Feb 28, 2026
…y chip (#7)

- ConfigRow: add label maxWidth (45%), text truncation with title tooltip for both label and value
- Toolbar section: add summary chip showing "X of Y enabled" at top of expanded section
- Add toolbarEnabledCount i18n key to all 10 locales
- Update view-config-schema tests for new _toolbarSummary field

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
os-zhuang added a commit that referenced this pull request Jun 29, 2026
… AI slot (#2060)

* feat(console): Studio WYSIWYG design surface dev harness (ADR-0080 slice-1)

Additive /dev/studio-design route + DevStudioDesign four-zone shell reusing
getMetadataPreview/getMetadataInspector + SchemaRenderer. Touches no existing surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(app-shell): promote Studio design surface + open-core AI slot (ADR-0080)

Move the design surface out of the apps/console dev harness into
@object-ui/app-shell as StudioDesignSurface. The left AI copilot becomes an
injected aiSlot prop — OSS renders three zones (nav/canvas/inspector); the
cloud edition injects AI. Repoint /dev/studio-design at the promoted component.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(app-shell): wire Studio design surface to real metadata (ADR-0080 #3/#4)

Replace the fixture with the shared MetadataClient: the single-App nav lists
real pages, clicking loads that surface into the canvas (real PagePreview +
live data), and the toolbar saves drafts / publishes through the real pipeline.
Route moves inside ProtectedRoute for the connected client + data adapter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(app-shell): route Studio as /studio/:packageId/{data,automations,interfaces}

Three pillars are now real routes scoped to the package being designed. The
shell renders the package + pillar route links; Interfaces drives the real
MetadataClient (nav/canvas/inspector + draft/publish), Data/Automations are
scaffolded placeholders pending the App-nav + object/flow surface reuse map.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(app-shell): real 3-pillar Studio — App-nav Interfaces + Data + Automations

Interfaces: load the real App by package id and render its navigation tree
(groups + typed leaves page/object/dashboard/report); clicking loads the bound
surface (PagePreview/DashboardPreview/object-grid). Data: object list + record
grid (object-grid) + field table. Automations: flow list + FlowPreview graph.
All reuse existing renderers — no new editor code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(app-shell): Data pillar = primary full-height record grid (Airtable parity)

Drop the redundant field table — the record grid's columns ARE the fields.
Data mode now reads like a spreadsheet (objects on the left, the records
directly viewable), matching Airtable's Data tab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(app-shell): object surface = object-view (our list-view model, not raw grid)

Per design direction, the Data pillar (and Interfaces object items) render an
object via object-view — its named list views + ViewSwitcher + record actions,
falling back to a grid — instead of the raw object-grid. Matches our view model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(app-shell): scope Data/Automations lists to the current package (#9)

Pass packageId into DataPillar/AutomationsPillar and use client.list(type,
{ packageId }) (=> ?package=) so each pillar lists only the current package's
objects/flows, not all env metadata.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(app-shell): render flow preview via createElement (satisfy react-hooks/static-components)

AutomationsPillar assigned `const Preview = getMetadataPreview(...)` and rendered
it as JSX, which react-hooks/static-components flags as a component created during
render. Render via React.createElement instead, and derive the type from the
selected flow (current.type) to mirror InterfacesPillar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-sales pushed a commit that referenced this pull request Oct 1, 2026
…fusal (objectui#10872 batch 10, patch round 1)

10289's migration example writes `label` and `actionType` in `properties`
beside `params`, the bag spelling AGENTS.md #4 teaches. 7867's example is
that entry's historical reading, so it is kept and gets a dated note: later
in this release a flat `actionType` and a node-level `params` object on
`action:button` are refused by name toward their bag members. Frontmatter of
both files byte-identical.

Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42
Co-authored-by: Claude <noreply@anthropic.com>
os-project-manager pushed a commit that referenced this pull request Oct 4, 2026
…he contract; §4 drops `props`

Describes what shipped with objectui#8347: `BaseSchema` declares no index
signature, a refused key is declared by reference or fixed at the producer,
never cast past, and the fresh-literal bound is stated with the pin that
measures it. §4's abridged listing drops `props`, which `BaseSchema` does not
declare, and #4's `events` sentence names the face that still keeps one.

Claude-Session: https://claude.ai/code/session_01CPvhwGcirXqBGEdPSb72TZ
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…roperties` bag (objectui#11183) (objectstack-ai#11317)

Fixes objectstack-ai#11183
Clause-②: no

Every taught `action:*` node under `AGENTS.md`, `skills/objectui/**` and
`content/docs/guide/**` now writes the block's props in the spec's
`properties` bag, as the `@objectstack/spec` `ComponentPropsMap` row
declares them, instead of flat on the node. Triage's ruling on the
objectui#10872 fork, carried by the card: **A, the `properties` bag is
the contract.** The spec's `PageComponentSchema` is `.strict()`
(ADR-0089 D3a) and refuses a node-level `actionType` / `target` as
mis-layered; objectui's `StrictAnyComponentSchema` refuses the same two
keys. The runtime reads both spellings through the `SchemaRenderer`
`properties` hoist, so only documents and their pins change — ⛔ no
runtime or validator code in this diff.

**Order: this PR lands BEFORE PR objectui#11069** (objectui#5250,
`objectui validate` onto the strict face). objectui#5250 carries
`Blocked-by: objectstack-ai#11183`; PR objectui#11069 touches
`content/docs/guide/layout.md` and `schema-rendering.md` in other
sections than the ones edited here (its `layout.md` hunk is the `label`
/ `text` paragraph under the page-buttons passage; its
`schema-rendering.md` hunk is the three validation doors), so it merges
`main` after this lands with no textual overlap.

Governed surface: `AGENTS.md`, `skills/objectui/SKILL.md`,
`skills/objectui/guides/page-builder.md`,
`skills/objectui/rules/protocol.md` (`node
scripts/check-governed-queue-guard.mjs --test` answers exit 3,
GOVERNED). This PR stays **draft** until an APPROVED review by
`os-zhuang` or `hotlong`; the claiming seat lands it. Cross-lane surface
(`content/docs/**`, `packages/**` pins), declared on the claim comment
of the card.

## 维护者速读(草稿)

**改了什么。** 文档与测试,零运行时代码。我们教给作者(和 AI)的 `action:button` 示例,把 `actionType` /
`target` 这类执行器键从节点顶层挪进了 `properties` 包里 —— 共 11 个节点、10 个文件(受管面 4
个:`AGENTS.md` 第 objectstack-ai#4
条、`skills/objectui/SKILL.md`、`guides/page-builder.md`、`rules/protocol.md`;非受管
6 个:`content/docs/guide/` 下 4
篇指南、`content/docs/plugins/plugin-detail.mdx`、随同的两条 pin 测试)。同一家族的两处 rider
一并处理:plugin-detail 指南里的 `record:activity` 示例(`limit` / `showCompleted`
进包),以及 page-builder 技能里的 `object-form` 表单示例(objectui#11291 落地后 `objectui
validate` 已按名拒绝扁平写法)。另附一个空 frontmatter 的 changeset(声明不发版)。

**为什么改。** spec 的 `PageComponentSchema` 是 `.strict()` 的(ADR-0089
D3a),节点顶层出现 `actionType` / `target` 会被当作错层键直接拒绝;objectui 自己的严格校验面
`StrictAnyComponentSchema` 同样拒绝这两个键。PR objectui#11069 正要把 `objectui
validate` 切到这个严格面,届时受管规程文件里的标准示例就会变成校验器拒收的节点,而 agent 会照抄示例。裁决是「教的内容向
spec 靠」,不是反过来给 objectui 开一条 spec 拒绝的扁平通道。实测:12
个被改示例在改前全部被两个严格面拒绝,改后在宽容面、严格面、spec 三面全部通过(见下方 census)。

**风险与代价(含回滚)。** 运行时无变化:`SchemaRenderer` 把 `properties`
整包提升到节点上再交给渲染器,两种拼写渲染结果相同;`guide-layout-page-buttons-7926` 这条真实渲染 pin
读的就是 layout 指南里被改的那段 JSON,改后 Edit / Delete 两个按钮照常画出。代价是示例各长了 2–5
行。回滚:revert 本 PR 即可,没有任何生成物、锁文件或发布物需要同步;但回滚后 objectui#11069
不能先落地,否则规程示例会被校验器拒收。

**席位意见。**(留空)

**你要做的。** 作为受管面 PR,需要你(`os-zhuang` / `hotlong`)在本 PR 上留一条 APPROVED
review;之后由认领席翻 ready 并入合并队列,⛔ 本 PR 不自行 ready、不自行入队。请顺手确认 `AGENTS.md` objectstack-ai#4
的新措辞(「props 住在 `properties` 包里,⛔ 不写在节点顶层」)是你希望 agent 读到的规则。

## Census (the gate the card asks for)

Instrument: for every `"type": "action:button|icon|group|menu"` node
under `content/docs/**`, `skills/**`, `AGENTS.md`, `README.md` (`grep
-rnE '"type":\s*"action:(button|icon|group|menu)"' content/docs skills
AGENTS.md README.md`), the keys written at the node's own level —
`actionType`, `target`, `endpoint`, `method`, `name`, `icon`, `variant`,
`params`, `label` — before the next node or the closing brace.

| reading | action nodes | nodes with a flat row key |
|---|---:|---:|
| base `caa0cd392` (objectui `origin/main` at cut) | 11 | 11 |
| this branch `f580661` | 11 | 0 |

Per site (base → branch), by heading, all in one commit each:

| file | site | moved into `properties` |
|---|---|---|
| `AGENTS.md` | objectstack-ai#4 example (one-line node) | `label`, `actionType`,
`target` |
| `skills/objectui/SKILL.md` | "4. The action system" | `label`,
`actionType`, `target` |
| `skills/objectui/rules/protocol.md` | "Rule: Actions Are Node Types,
Not An Event Bag" (the node example only; the 「Readers only.」 paragraph
is objectstack-ai#11009's and is untouched) | `label`, `actionType`, `target` |
| `skills/objectui/guides/page-builder.md` | "5. Use action data" |
`label`, `actionType`, `target` |
| `skills/objectui/guides/page-builder.md` | "Form plugin example"
(`object-form` rider) | `objectName`, `mode`, `fields` |
| `content/docs/guide/quick-start.md` | "Add Actions" | `label`,
`actionType`, `target` |
| `content/docs/guide/record-edit-modes.md` | "Triggering the routes
from JSON", three nodes | `label`, `icon`, `actionType`, and the
already-bagged `params` stays in the same bag |
| `content/docs/guide/schema-rendering.md` | "Reference actions in
schemas" | `name`, `label`, `actionType`, `endpoint`, `method` |
| `content/docs/guide/layout.md` | "Detail Page with Actions", two nodes
| `name`, `label`, `variant`, `icon`, `actionType` |
| `content/docs/plugins/plugin-detail.mdx` | "The `record:activity`
block" (`record:activity` rider) | `limit`, `showCompleted` |

Each edited fence judged on three faces (a probe run from
`packages/types` against the source `zod/index.zod.ts` and the installed
`@objectstack/spec` 17.5.0): at base, every one of the 12 is refused by
`StrictAnyComponentSchema` and by the spec's `PageComponentSchema` with
exactly the flat keys named above (the `object-form` one is refused on
the tolerant face too, by name, since objectui#11291); on this branch
all 12 pass tolerant, strict and spec. The prose beside each fence moved
with it: the sentence "the node's own keys carry that executor's
arguments" now reads "the row's other keys" / "the bag's other keys",
and each site says why the flat spelling is refused.

`skills/**` line readings (the governed-surface reading, lines; no token
meter exists for this surface — `check:skill-eval-tokens` meters eval
`must_contain` tokens, not file length): `SKILL.md` 137 → 142,
`guides/page-builder.md` 346 → 353, `rules/protocol.md` 364 → 370;
bundle `skills/objectui/**/*.md` 4782 → 4800; all `SKILL.md` files 137 →
142. The whole increase is the bag's two wrapper lines per fence plus
the one-sentence refusal note; nothing was re-wrapped.

## Pins moved

- `packages/types/src/__tests__/held-public-block-arms-10872.test.ts`
(reads the quick-start's "Add Actions" fence): the taught node now
validates AS WRITTEN on both faces and on the spec's
`PageComponentSchema`; the pre-move flat spelling is DERIVED from the
taught node (bag hoisted flat) and pinned as refused by exactly
`actionType` and `target` on both strict faces while the tolerant face
still keeps it — so the reading of the refusal stays live and moves with
the page.
-
`packages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.ts`
(same fence, through `objectui validate`): lit control that the
extracted node is in the bag spelling; the row still passes the tolerant
face `validate` runs today and will pass the strict one PR
objectui#11069 wires. ⛔ Its header and `refusedAtType` helper — the
lines PR objectui#11069 edits — are untouched here.
- Reverse leg, run with the pins committed: `git checkout BASE --
content/docs/guide/quick-start.md` ⇒ 4 red (`reads the taught node … bag
spelling`, `strict face: the taught node AS WRITTEN … validates`, `the
spec's own PageComponentSchema accepts the taught node as written`, the
ratchet's `validates a page with the taught action:button node`), 60
green; `git checkout HEAD -- …` restored, `git diff HEAD` empty, `git
hash-object` of the page equals its HEAD blob `ca70f902fc5c…`.

## Gates run locally (head `f580661`; exit codes read before any pipe)

Derived from `.github/workflows/*.yml` on `origin/main` (41 files, the
same count the working tree lists): my diff touches `**/*.md`,
`content/**`, `.changeset/**` AND two
`packages/*/src/__tests__/*.test.ts`, so the `Decide whether this change
needs a full run` step in `lint.yml` / `ci.yml` answers
`should_run=true`, every always-on doc gate fires, and
`changeset-guard.yml` fires on the `.changeset/**` path.

| gate (workflow) | command | exit |
|---|---|---:|
| Skills Paths | `node scripts/check-skills-paths.mjs` | 0 |
| Skill Examples | `node scripts/check-skill-examples.mjs --self-test` ·
`node scripts/check-skill-examples.mjs` (both after the scoped build its
`--build-filter` names, 30 tasks; before the build both answer exit 2 =
could not run, not a verdict) | 0 (60 cases) · 0 |
| Skill Eval Tokens | `node scripts/check-skill-eval-tokens.mjs
--self-test` · `node scripts/check-skill-eval-tokens.mjs` | 0 · 0 |
| Line Citation Gate | `node
scripts/check-new-cross-file-line-citations.mjs` | 0 (0 new citations) |
| Control Bytes | `node scripts/check-control-bytes.mjs` | 0 |
| Doc Fence Languages · Doc Example Ids · Doc Component Types | the
three `node scripts/check-doc-*.mjs` | 0 · 0 · 0 |
| Doc Snippet Types | `node scripts/check-doc-snippet-types.mjs` (after
the full build `pnpm turbo run build --filter='!@object-ui/site'`, 43
tasks) | 0 |
| Changeset Presence · Changeset Guard | `node
scripts/check-changeset-presence.mjs` · `node
scripts/check-changeset-no-major.mjs` · `check-changeset-claims` ·
`check-pending-changeset-literals` | 0 · 0 · 0 · 0 |
| README Exports | `node scripts/check-readme-exports.mjs` (after the
full build) | 0 |
| Docs Links · Docs Route Eager Closure · Action Ref Convention · Shell
Escape Residue · Inert vi.mock Specifiers · Pre-Install Import Graph |
each `node scripts/check-*.mjs` | 0 each |
| `node scripts/check-comment-mask-corpus.mjs` · `node
scripts/markdown-test-inputs.mjs --audit` | | 0 · 0 (107 candidate test
files adjudicated, 78 entries present) |
| Governed Surface Guard | `node scripts/check-governed-queue-guard.mjs
--test AGENTS.md skills/objectui/SKILL.md
skills/objectui/guides/page-builder.md
skills/objectui/rules/protocol.md` | 3 = GOVERNED, by design |
| Lint (`lint.yml` roster) | `pnpm lint --concurrency=2` (47 tasks, 0
errors) · `node scripts/check-vi-mock-override-shape.mjs` · `node
scripts/check-test-path-roots.mjs` · `pnpm --filter '@object-ui/cli...'
build` · `pnpm check` | 0 · 0 · 0 · 0 · 0 |
| CI (`ci.yml`) — the affected tests | `pnpm exec vitest run` on the two
moved pins + `guide-layout-page-buttons-7926` (3 files, 71 tests) · the
other eight ledgered readers of the edited docs (8 files, 87 tests) ·
the root `scripts/` suite (177 files, 5371 tests, 2 skipped) | 0 · 0 · 0
|
| type-check of what I edited | `tsc -p
packages/types/tsconfig.test.json` · `tsc --noEmit` in `packages/cli` ·
`eslint` on the two test files | 0 · 0 · 0 |

Not run locally, declared to CI: the full `pnpm test` shards (`ci.yml`),
Live E2E, Spec Main Shape Gate (it builds `@objectstack/spec` from
objectstack `main` on the runner), Bundle Analysis. Heavy steps ran
under `/home/user/objectstack/scripts/pm/os-verify-lock.sh` (three
locked runs: the scoped build 234 s held, the scripts suite + lint, the
full build + two gates after a 452 s queue wait).

## Acceptance notes

- **`packages/plugin-detail/README.md` — the `items` half of the
`record:activity` rider is NOT moved, by measurement.**
`ComponentPropsMap['record:activity']` on the installed spec has no
`items` key (its keys: `aria`, `enableMentions`, `enableReactions`,
`enableThreading`, `filterMode`, `limit`, `showCommentInput`,
`showCompleted`, `showFilterToggle`, `showSubscriptionToggle`, `types`,
`unifiedTimeline`), and both objectui faces refuse `properties.items` by
name, so the bag spelling would turn a working example into a refused
one. The README's example is TSX code composing a `DetailViewSchema`
whose Activity tab hands the block a host-supplied feed —
`record-activity.tsx` reads `schema.items` as the first of its three
feed sources — the same runtime-slot shape as `record:history`'s
`entries`. Left as is; reported in the dev report for the `domain:ui`
lane to rule on whether `items` wants a named RUNTIME SLOT refusal on
the authoring faces.
- **Out of the roots, same family: `packages/app-shell/README.md`**
teaches two flat `action:button` nodes (`navigate_create` /
`navigate_edit`, under "The handler name goes in `actionType`"), the
twin of the `record-edit-modes.md` passage moved here. Outside the
claimed surface; in the dev report as a finding, not edited.
- **Different family, noted: `content/docs/api/schema-reference.md`
"ActionSchema"** teaches `"type": "action"` nodes with `actionType:
"ajax"` and an `api` key — a vocabulary `schema-rendering.md` itself
says no action renderer forwards. Not an `action:*` public block; not
edited.
- Zone 2 item 4 of the dispatch assumed `check:skill-eval-tokens` meters
skill-file length; it meters eval `must_contain` tokens against the
bundle (its header's "THE ORACLE" section). No length ceiling gate
exists for `skills/**` in this tree; the line readings above are
reported in its place.
- objectui#11198 (`guides/app-composition.md` nav label) does not ride
this PR; that file is untouched.

Session: `session_01KTZmMfzVzjNvyaLyQ8mHvg` (PM loop round 11, claim
comment 5923347428).

---
_Generated by [Claude
Code](https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Warren Buffett <warren@objectstack.ai>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…locks, two spec-row blocks and page documents, by reference; SchemaRenderer's schema prop accepts them (objectui#11364) (objectstack-ai#11385)

Refs objectstack-ai#11364

Clause-②: yes
Why yes: new published TS types are added to `@object-ui/types`, and
`@object-ui/react`'s `SchemaRendererProps.schema` accepts them, so the
published TS surface widens.

Parent: objectui#8347. This is its preparation; the `BaseSchema`
index-signature removal itself is not in this PR, and nothing here edits
`BaseSchema`. The `props` alias question stays open for the maintainer
(see "Open questions"), which is why this PR refers to the card rather
than closing it.

Dispatched run, session
`https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT`. Draft, left
for the seat.

## What changes

- **`@object-ui/types`**: `packages/types/src/authoring-nodes.ts`,
exported from the main entry. Each type is derived by reference; no
member is restated.
- `PublicBlockNode`: one member per arm of `PublicBlockComponentSchema`
(27 arms), each the arm's own `z.input` read off its SHAPE with the
object config closed. `PublicBlockNodeOf` picks one member by its `type`
literal (for example `'element:text'`).
- `ElementTextInputNode` and `ElementRecordPickerNode`: no zod arm
exists for these two, so each is built the way an arm is built: the zod
`BaseSchema` shape extended with the `type` literal, the node envelope
(`NODE_ENVELOPE`: `responsiveStyles`, the spec's
`ResponsiveStylesSchema`), `properties` as the spec's
`ComponentPropsMap` row, and the `body` / `children` tombstones every
arm carries, each by reference. (Round 2: the round-1 type left out the
envelope and the tombstones; see below.) The record picker also declares
the node-level `dataSource` (the spec's `ElementDataSourceSchema`) its
renderer reads, and leaves the bag's `object` optional for the spec
props gate's `dataSource` waiver, the same waiver the `element:number`
arm mirrors.
- `PageDocumentNode`: the spec's `PageSchema` input with `type` (the
page kind) required, the document `PageView` hands to `SchemaRenderer`
(objectui#9642).
  - `AuthoringNode`: their union.
- **`@object-ui/react`**: `SchemaRendererProps.schema` is now
`BaseSchema | AuthoringNode | string | null | undefined`. No index
signature and no `Record` on the prop. `toRenderableSchema` returns
`SchemaRendererProps['schema']` by reference, so the bridge's return
type widens with the prop in this release (the `.d.ts` text of
`schema-input` does not change, so the H5 diff cannot show it).
- **4 component test files**: the three `page:tabs` literals move
`items` into `properties` (where `PageTabsProps` declares it); the
`action:button` literals move `label` / `actionType` into the bag
(objectui#11183's landed shape) and the helper takes the arm's node type
beside `BaseSchema`; the `element:text_input` literal handed to
`renderComponent` is typed as `ElementTextInputNode`. CASE A in
`action-params-templates-7867` keeps its node-level `params` object:
that spelling is what it proves is not read (objectui#10289).
`SchemaRenderer` hoists the bag onto the node, so every renderer reads
the same values; the suites below are green.
- **Pins**: `packages/types/src/__tests__/authoring-nodes-11364.test.ts`
(new) and assertion 1 of
`packages/react/src/__tests__/SchemaRenderer.propsResolution.test.ts`
(the prop's exact union).
- **Docs**: `packages/types/README.md`, `packages/react/README.md`,
`content/docs/guide/schema-rendering.md`.
- **Changesets**: `.changeset/11364-public-block-ts-faces.md` (types
minor, react minor). Three pending entries this release makes false get
a dated, append-only note, each with its frontmatter byte-identical (md5
of the frontmatter lines unchanged):
`9256-public-blocks-content-channels.md` ("This package has no
TypeScript declaration of these nodes"),
`8908-bridge-falsy-primitives.md` ("The return type is unchanged", round
2) and `schema-input-bridge-permanent-4622.md` (the prop's union spelled
as it was, round 2).

## The pin

`authoring-nodes-11364.test.ts` runs in every `type-check` today, before
objectui#8347 lands. It builds `PostRemovalSchema`: the prop's union
with `BaseSchema`'s index signatures stripped at the type level. Against
it:

- `{ type: 'element:text', properties: { content: 'Hello' } }` compiles;
- `properties: { contnet: 'Hello' }` is refused, as is a node-level
`contnet` and a `properties` bag on a `button` node.

It also checks that no arm is lost by the derivation
(`PublicBlockNode['type']` equals the union's own), and that no member
carries an index signature. A control proves that check can fire: the
bare `z.input` of the same arms does carry one. Every `@ts-expect-error`
there is consumed (an unused one is TS2578).

## Round 2 (contract record `5933841900`, FAIL on `29acb0107`)

Head `6af8e9da3`. `origin/main` was merged again (`3ae919307`, no
conflicts, no rebase).

- **A, the spec-row nodes.** `SpecRowNode` is now the type-level twin of
an arm's `BaseSchema.extend(...)`: `Omit` of the zod `BaseSchema` shape
by the own keys, then the `type` literal, `typeof NODE_ENVELOPE`,
`properties`, and `body` / `children` as `retirementTombstone`'s return
type, all type-only imports. The spec's own `PageComponentSchema`
refuses a node-level `children` on `element:text_input` and
`element:record_picker` (`unrecognized_keys`), as it does on
`element:text`. Measured three ways:
- before the fix (type-level P1 simulation): through the union,
`responsiveStyles` compiled on `element:text` and was TS2353 on both
spec-row nodes, and a node-level `children` was refused on
`element:text` but compiled on `element:text_input`; on the node types,
both spec-row nodes accepted `children` and refused `responsiveStyles`;
- after the fix, under the REAL P1 mutation, through the actual
`SchemaRenderer` `schema` prop (a throwaway probe compiled against the
built `react` dist and the mutated `types` dist): `responsiveStyles`
compiles on `element:text`, `element:text_input` and
`element:record_picker`, and a node-level `children` is refused (TS2353)
on all three, exactly as on `element:text`. The census still reads 10,
and the restore is proven;
- the pin's reverse check: with the fix ablated through
`ablation-replace.mjs` (restore to `HEAD` proven), the new pin case
turns red as predicted: TS2353 on the two styled spec-row literals and
TS2578 on the three spec-row `children` directives, with the
`element:text` control unchanged.
- **B, the bridge.** The `11364` entry now says the bridge's return
widens with the prop; `8908` gets the dated note.
`schema-input-bridge-permanent-4622.md` spells the prop's old union too,
so it gets the same note by the same rule.
- **C.** The `11364` entry no longer says nothing stops compiling
"because `BaseSchema` still carries its index signature". It says every
value the old union accepted is still accepted, that a key absent from a
closed member is TS2339 on a union whatever the signature allows, and
that no caller here reads a key off the prop's type or the bridge's
return (the `Type Check` job re-derives that).
- **Round-2 gates** (at `7638daa9e`; `6af8e9da3` adds only a changeset
`.md`): `@object-ui/components^...` closure build exit 0; `type-check`
types / react / components exit 0, 0 errors, scripts echoed; the pin and
`SchemaRenderer.propsResolution` under vitest 2 files, 8 tests passed;
`check-changeset-presence`, `check-changeset-no-major`,
`check:changeset-claims`, `check:pending-changeset-literals`,
`check:control-bytes`, `check:new-line-citations`, `check:spec-symbols`,
`check:phantom-deps`, `check:unreferenced-sources` all exit 0 (re-run at
`6af8e9da3`). P1 census 10, src 0.

## Measurements (round 1, at `29acb0107` unless noted)

**H1, the population** (objectui#8347's P1 mutation, `BaseSchema`'s
`[key: string]: any` deleted alone through `ablation-replace.mjs`, types
rebuilt each leg, `@object-ui/components`' test program):

| head | test-program errors | src-program errors |
|---|--:|--:|
| `b5b0afea5` (base) | 30 | 0 |
| `29acb0107` (this PR) | 10 | 0 |

The 30 at base, by reading each site: 13 public blocks (3
`element:text`, 7 `element:text_input`, 3 `element:record_picker`), 3
`page:tabs`, 5 `action:button`, 2 page documents (`type: 'home'`), 4
`props` alias, 1 `content` probe, 2 test-registered probe type. The
card's "14" public blocks reads 13 here; the 30 includes the `content`
probe.

The 10 left at head, each a literal no referenced type can or should
accept:

- 4 `props` alias (`alias-precedence-cross-channel`): waiting on the
`props` question.
- 1 `content` on `label` (`form-renderers`): the retired-spelling probe.
- 2 `properties: 'not-a-bag'` on a test-registered probe type
(`degenerate-config-bag`).
- 1 node-level `params` object on `action:button` CASE A: the
objectui#10289 probe.
- 2 `visibility` on `element:text` (`page-variables`): the spec's
deprecated alias, which objectui#10872 kept off the TS face;
`SchemaRenderer` reads it only defensively for raw metadata. TS2353 hid
this second key behind `properties` at base.

All 10 compile today; they are objectui#8347's flips. Every leg's
restore is proven: `ok restored: blob == HEAD (ce2bc9e) and git
diff HEAD is empty`, dist index-signature count back to 2. The mutation
is never committed.

**H2, arm or row per block.**

| node | zod arm | spec row | type here |
|---|---|---|---|
| `element:text`, `page:tabs`, `action:button`, and the other 24 public
blocks | yes | yes | `PublicBlockNode` member |
| `element:text_input` | none (the validator refuses it at `type`) |
`ElementTextInputPropsSchema` | `ElementTextInputNode` |
| `element:record_picker` | none (refused at `type`) |
`ElementRecordPickerPropsSchema` | `ElementRecordPickerNode` |
| page kind (`home`, `app`, `record`, `utility`) | none
(`PageNodeSchema` is `type: 'page'`; refused at `type`) | `PageSchema` |
`PageDocumentNode` |

The PM's prediction (a one-line `z.input` of each arm) is falsified in
one respect: the zod `BaseSchema` is `.passthrough()`, so the bare
`z.input` of an arm has `keyof` = `string` (`[k: string]: unknown`),
which would put the removed index signature back. The derivation reads
the arm's shape with the config closed instead. The spec rows of
`element:image`, `element:metadata_viewer`, `element:filter` and
`element:form` are not typed here: none appears in the census.

**H3, the prop.** Under P1, `SpecPageComponent` is refused by exactly
one shared key: `visibleWhen` (`BaseSchema` says `string`, the spec says
`string` or an Expression envelope). Its other keys are spec-only and do
not block a non-fresh value. So no union of referenced types makes it
assignable while keeping the pin:

- adding the spec's `PageComponent` to the union makes it assignable,
but `properties: { contnet }` on `element:text` then compiles
(measured);
- widening `BaseSchema.visibleWhen` to `ExpressionWire` (type-level
simulation) makes it assignable AND keeps the misspelling refused, but
that edits `BaseSchema`, which is excluded here.

That is the fork the dispatch asked to be reported, and it is in "Open
questions". Each new node type IS assignable to the prop, and the union
is pinned exactly.

**H4, the spec's page row.** `PageSchema.type` is `PageTypeSchema`
(`record`, `app`, `home`, `list`, `utility`; default `record`); `kind`
is the override mode (`full`, `slotted`, `html`, `react`, `jsx`).
`page:tabs` `items` is a member of the bag (`PageTabsProps`), not of the
node; each item requires `label` and `children`.

**H5, the published surface** (base `b5b0afea5` built in a comparison
worktree, against this branch):

- `@object-ui/types` `dist`: one new file, `authoring-nodes.d.ts`
(references only: `z.input` of the arms and spec rows), and one new
`export type` line in `index.d.ts`. 13 `zod/*.zod.d.ts` files differ
only by the print order of inferred members; each has a token multiset
equal to base's (whitespace, the union bar and trailing punctuation
normalised).
- `@object-ui/react` `dist`: `SchemaRenderer.d.ts` gains the
`AuthoringNode` import and the widened prop, and an existing inferred
`render()` return type prints its union members in a different order.

## Open questions (in the report, with the four-axis frame)

1. **The `props` alias on the TS authoring face.** Measured producers:
objectui's 1720 non-test TS/TSX files author no node `props` (control
`properties: {` reads 28); tracked JSON has 0 `"props": {` files
(control 10); objectstack `examples/**` TS at local `95fed33a2` has 0
(control 52); the published skill teaches node-level `props` as WRONG.
The only authors are the 4 `alias-precedence` probes. Undecided here,
per the dispatch.
2. **`SpecPageComponent` assignability** (H3): widen
`BaseSchema.visibleWhen` to the spec's union (objectui#8347's file), or
add `PageComponent` to the prop and lose the bag check, or leave it
unassignable.

## Acceptance notes

- In the prop's union `BaseSchema` (`type: string`) still accepts any
node structurally, so these types judge each node's own keys and values
(bag members, `kind`, `items`) but do not make a member's required keys
required. A page document without `label` still compiles against the
prop. Requiredness stays the validator's.
- `SchemaNode` is not widened. Measured: with the signature present, a
reader of a widened `SchemaNode` that reads an undeclared key
(`columns`) turns TS2339 on the closed member, where today it compiles
through the index signature. A literal nested in a `children` slot
therefore still needs its own typing after objectui#8347.
- The TS face now types three node kinds that `AnyComponentSchema` still
refuses at `type` (`element:text_input`, `element:record_picker`, page
documents). That refusal is the population
`registered-types-validate-ratchet-10859` counts, not a judgement that
the nodes are invalid.
- Finding, not carded: the spec's `PageComponentSchema` normalises
`visibleWhen` into a `{ dialect: 'cel', source }` envelope at parse, the
renderer evaluates it, and `AnyComponentSchema` refuses the envelope
(`invalid_type` at `visibleWhen`). Measured through the built validator.
Same root as open question 2.
- Not carded: AGENTS.md objectstack-ai#4 still teaches the flat `action:button`
executor spelling that objectui#11183 moved into the bag. That file is
governed; objectui#11183's lane carries it.

## Gates (round 1, head `29acb0107`, unless noted)

- Build: `pnpm --filter '@object-ui/components^...' build` exit 0 (at
`11eaa8ae5`, after the merge); `@object-ui/types` and `@object-ui/react`
builds exit 0 at `29acb0107`.
- `type-check` (script names echoed, `tsc --noEmit && tsc -p
tsconfig.examples.json && tsc -p tsconfig.test.json` for types, `tsc
--noEmit && tsc -p tsconfig.test.json` for react and components): types
0, react 0, components 0 errors, all exit 0.
- vitest: `packages/types/` 313 files, 8000 tests passed;
`packages/react/` 106 files, 1389 tests passed (both at `29acb0107`,
each part's exit read on its own: 0 and 0). `packages/components/` in
three shards: 1/3 117 files (1136 passed, 7 skipped) and 2/3 117 files
(1363 passed, 10 skipped) at `11eaa8ae5`; 3/3 115 passed and 1 skipped
(988 passed, 7 skipped) at `29acb0107`. The only change between those
heads is a type-alias default in `authoring-nodes.ts`, a file with only
type declarations.
- `check:doc-fences`, `check:doc-types`, `check:new-line-citations` (0
new), `check:control-bytes`, `check:changeset-claims`,
`check:pending-changeset-literals`, `check:spec-symbols`,
`check:test-path-roots`, `check:phantom-deps`, `check:self-import`,
`check:unreferenced-sources`, `check:vi-mock-specifiers`,
`check:doc-example-ids`, `check:component-surface-parity`,
`check:esm-specifiers`, `check-changeset-presence` (9 source files, 3
packages, 1 changeset), `check-changeset-no-major`: all exit 0.
- `check-governed-queue-guard --test` on the 14 paths: NOT GOVERNED.
- Lint, narrowed: the 9 touched `.ts`/`.tsx` files (population:
`eslint.config.js` `**/*.{ts,tsx}`), `--format json`: head 1 error / 19
warnings; the same 7 pre-existing files at base read 1 error / 19
warnings. The one error is a `no-restricted-imports` on `FormField` in
`packages/types/src/index.ts`, present at base. Invariance:
`eslint.config.js` sets no `parserOptions.project` / `projectService`
(type-aware linting off), so this diff cannot move a verdict on an
untouched file. Full `pnpm lint` is CI's.
- NOT MEASURED: `check:doc-snippets`, reason: its prerequisite is a
34-package build. Narrowed instead: the two fences this PR adds compile
`--strict` against the built `types/dist/authoring-nodes.d.ts` and
`react/dist/SchemaRenderer.d.ts` (exit 0, resolution read from
`--listFiles`), and a control with `contnet` in the guide fence fails
with TS2561.
- `check:readme-exports`: exit 1 is its "could not be judged" state (431
self-imports in unbuilt packages); `--list` reads both names this PR's
README imports (`ElementTextInputNode`, `PublicBlockNodeOf`) as `real`.

`origin/main` was merged in (`edfcf5a5e`, no conflicts, no rebase), and
every reading above after the merge line was retaken on the merged tree.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…d by name, toward its `properties` bag (objectui#10872, batch 10) (objectstack-ai#11391)

Part of objectstack-ai#10872
Clause-②: no

Batch 10 of objectui#10872: the flat-props refusal half. A public
block's prop written directly on the node, instead of in its
`properties` bag, is now refused by name on both faces of
`@object-ui/types/zod`, at its own path, with a message naming
`properties.KEY`. The card keeps `record:line_items` (no spec row yet)
and the six batch-4 arms' `children` narrowing.

Dispatched run, session
`https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42`, claim
`5933064114` (`domain:ui` seat 2). Draft, left for the seat.

## The ruling this executes

Triage's answer A on objectui#10872 (comment `5906817482`): "The spec's
strict page-component schema refuses flat `actionType` / `target` as
mis-layered (ADR-0089 D3a)", and "B is not taken. A flat channel in
objectui's arms is a second dialect: `objectui validate` would accept
what `os validate` refuses." Its precondition, the taught docs moving to
the bag, landed with objectui#11183 (completed 2026-10-01). Two teaching
sites that move missed are moved here (below).

## Zone 2 readings (measured before the change, objectui `2e060ba`,
`@objectstack/spec` 17.5.0)

1. **Both faces accepted flat row keys.** Population: every arm of
`PublicBlockComponentSchema` and `ObjectQLPublicBlockComponentSchema`
whose `properties` is a spec row: 34 arms. Four of them already refused
their flat spelling by name, each with its own map (`object-form`,
`object-map`, `object-gantt`: objectui#10859; `object-chart`:
objectui#11276). The other 30 (27 public blocks plus `object-metric`,
`object-master-detail-form`, `object-timeline`) are this batch. The "29"
in the release note was dated: `object-timeline` joined later. Probe:
for every row key that is not a node-level key of the spec's page
component, `{ type, KEY: v }` for seven values, read per face for an
issue at `[KEY]`.
- Tolerant face (`safeValidateSchema`, what `objectui validate` runs):
26 arms accepted at least one flat row key; 223 keys passed unjudged.
- Strict face (`StrictAnyComponentSchema`): every key `BaseSchema` does
not declare was refused, but only as an unnamed `unrecognized_keys`. 14
keys passed outright, judged by `BaseSchema`'s own type, because the row
member shares a name with a base key: `visible` (`record:alert`, the
four `action:` blocks), `disabled` (`element:button`, `action:button`,
`action:icon`), `name` and `description` (`action:button`,
`action:icon`), `description` (`object-metric`), `data`
(`object-timeline`).
   - After this change: 0 and 0, on all 34 arms.
- The spec's `PageComponentSchema` refuses every one of those keys on
the node (`unrecognized_keys`), and accepts `label`.
2. **The pull.** Instrument: the committed scanner
`scripts/body-dialect-census.mjs` (`scanNodes`, `keepFencedCodeOnly`,
`bucketOf`) under an uncommitted driver. FLAT = a node-level key that is
a member of the type's spec row, is not a node-level key of the spec's
page component, and is not already refused by name toward `properties`.
⚠️ Batch 7's dated reading counted only keys the arm leaves undeclared,
so it could not see the base-declared row keys (`visible`, `disabled`,
`name`, `description`, `data`); this reading counts them.
- objectstack `main` `454bbb6` (depth-1 scratch clone; the shared
checkout was not fetched or edited): 9627 files, 675 nodes of the 30
types (572 carry `properties`), **0 flat nodes in every bucket, tests
included**. Lit control on the same corpus and mechanism: node-level
`responsiveStyles` on 17 example-app nodes of these types, batch 8's
reading.
- objectui `2e060ba`: 9729 files, 667 nodes of the 30 types. Non-test
flat writers: 5 teaching nodes and 8 composed in code.
- Teaching: `packages/app-shell/README.md` (two `action:button` route
examples, flat `actionType`), `content/docs/guide/slotted-pages.md` (one
`record:related_list` example, flat `objectName` / `relationshipField` /
`columns` / `actions`), and two pending changesets of other cards
(`.changeset/10289-action-params-properties.md`,
`.changeset/7867-action-params-templates.md`, flat `actionType`). The
first two are moved to the bag in this PR; the changesets are not
(Acceptance notes).
- Composed in code, never judged by a zod face: `MasterDetailForm` hosts
in `plugin-form` (`ObjectForm`, `ModalForm`, `DrawerForm`) and the
console's `DevMasterDetail` page (`object-master-detail-form`),
`DashboardRenderer` / `DashboardGridLayout` (`object-metric`),
`action-bar` (`action:menu`), `ListView` (`object-timeline`).
`SchemaRenderer` runs `@object-ui/core`'s structural `validateSchema`,
not a zod face, and keeps reading both spellings.
- After this PR: the two pending changesets are the only non-test
teaching writers left.
3. **The precedent fits, generalised.** objectui#11328 (`object-chart`)
refuses each bag member written flat with `aliasKeyRefusal(KEY,
'properties.KEY', …)`, one map per arm, as `object-form` / `object-map`
/ `object-gantt` do. This batch makes that ONE helper,
`flatPropRefusals(type, row)` in `public-blocks.zod.ts`, spread into the
26 public-block arms whose row declares a member and into the three
ObjectQL arms. It also carries the two exceptions those maps had per
arm: a row key the spec's page component declares on the node (`label`,
`aria`, `type`) is left alone, as `object-gantt` leaves `label`; and a
row member the spec itself retires keeps the row's own retirement when
written flat, as `object-chart`'s three retired spellings do
(`page:header`'s `icon`, `page:card`'s `actions`, `record:details`'
`layout`).
4. **The html tier is not reached.** No non-test path feeds
`sdui-parser` output into a zod face. `objectui validate` reads JSON or
YAML (`validate.ts`), and `objectui check` globs `**/*.{json,yaml,yml}`.
`SchemaRenderer` runs the structural validator, and
`packages/sdui-parser` imports no zod face. Seven test files import both
tiers; each judges each tier on its own spelling, and all seven are in
the sweep below, green. No `packages/sdui-parser` file is touched.
5. **PR objectui#11069.** Its ratchet counts `invalid_union` at
`['type']`; no arm joins or leaves, so the count does not move (the
`packages/cli` suite, ratchet included, is green). One thing it should
account for when it un-parks: on the strict face these flat keys now
report `invalid_type` at the key, with the by-name message, instead of
`unrecognized_keys`, so its `findUndeclaredKeys` (which reads
`unrecognized_keys`) will not list them on its undeclared-key line. The
zod message names each one.

## What changed

- `packages/types/src/zod/public-blocks.zod.ts`: `flatPropRefusals` and
its message composer; `PAGE_COMPONENT_NODE_KEYS`, the spec page
component's node-level keys (transcribed; the pin re-derives them from
the installed spec's own shape); one spread line in each of the 26 arms,
placed BEFORE the arm's own members, so an arm's own refusal of a row
key keeps its message (`record:alert`'s `body`, the `action:` controls'
`onSuccess`, the content-channel tombstones). Module and `record:alert`
docblocks updated.
- `packages/types/src/zod/objectql.zod.ts`: the same spread in
`object-metric`, `object-master-detail-form` and `object-timeline`;
their docblocks, and the `object-form` sentence "Unlike the blocks
above", which this change made false.
- `packages/types/src/zod/README.md`: two lines.
- Nothing at render time moves, and no renderer is touched. The helper
is internal: not re-exported from `@object-ui/types/zod`, and the
package has no deep `./zod/*` export.

## Producer side (bounded in-place fix, declared)

The two teaching sites objectui#11183's move missed now write the bag:
the `record:related_list` actions example in
`content/docs/guide/slotted-pages.md`, and the two `action:button` route
examples in `packages/app-shell/README.md`. Each migrated snippet parses
on both faces and on the spec's page component, and its flat spelling is
refused by name (probe, not committed). The four conditions hold: the
same defect class as objectui#11183 (taught flat props on a public
block); a mechanical move whose shape AGENTS.md objectstack-ai#4 pins; no open PR
touches either file; docs only, so no new gate surface. Neither file is
governed.

## Pins

- `packages/types/src/__tests__/flat-props-refusal-10872.test.ts` (new):
the population read off the live unions, closed over the 30 covered arms
plus the four earlier ones; every owed row key refused on both faces for
five values, at its own path, with `invalid_type` and a message naming
`properties.KEY` (retired members excepted); per arm a RED row (its
known-good bag written flat is refused by exactly the owed keys, each
named, with no `unrecognized_keys` left) and a GREEN control (the same
bag in `properties` parses on both faces); the spec's page component
refusing the same flat keys; what is left alone (node-level row keys,
retired members by reference, the arms' own refusals); the earlier four
arms holding the same invariant; nested nodes in a node-level slot and
in a bag child list refused at their own paths.
- `packages/types/src/__tests__/held-public-block-arms-10872.test.ts`:
the batch-4 CONTROL on the taught node's flat spelling asserted the old
state (strict face: an unnamed `unrecognized_keys`; tolerant face:
success). It now asserts both faces refuse exactly `actionType` and
`target` by name, and keeps the spec reading in its own row.
- Ablation, implementation committed first, through objectstack
`scripts/ablation-replace.mjs` in WRAP mode plus a shell trap restoring
by absolute path. Anchor: the helper's key filter, replaced by a filter
that keeps no key (marker `ABLATION-10872-B10`), so no arm gets a
generated refusal and every explicit member stays. On disk: anchor 1 to
0, marker 0 to 1, blob `42a567fcff9e` to `a6e95cc943d1`. Result: `Tests
258 failed | 145 passed (403)`, red as predicted. The red rows: 223
per-key rows (the base probe's 223), 26 per-arm RED rows (all but
`element:divider` and the three thin `page:` containers, whose only owed
key is overridden), 3 retired-member rows, 4 nested rows, 2 batch-4
CONTROL rows. All GREEN controls stayed green. Restore: blob after
restore == HEAD blob `42a567fcff9e`, `git diff HEAD` empty. The target
and both pin files are blob-identical between the ablation commit
`2cc5b0e` and the head.
- Reverse type check through the rebuilt dist: a scratch file in
`packages/cli` (resolving `@object-ui/types/zod` to
`packages/types/dist/zod/index.zod.d.ts`) assigning `{ type:
'action:button', actionType: 'url' }` to the arm's input type got
exactly one error, `TS2322: Type 'string' is not assignable to type
'undefined'`, on that line. The bag spelling compiled. The file was
deleted, and the type-check is clean again. A first attempt imported
`zod`, which `packages/cli` does not depend on, and failed on TS2307
instead: it measured nothing and is not counted.

## Gates (head `401d406`, after merging `main` at `8673402a3f`, which
carries objectui#11384, objectui#11385, objectui#11386 and
objectui#11387)

- `pnpm --filter @object-ui/types build`: dist completeness 1 package
complete. `pnpm --filter @object-ui/types type-check`: exit 0 (its test
program lists both pin files). One lock hold with these two and the next
two joined by `&&`: VERDICT command-exit 0.
- `pnpm exec vitest run packages/types/`: `Test Files 316 passed (316)`,
`Tests 8371 passed (8371)`. `pnpm exec vitest run packages/cli/`: `24
passed (24)`, `338 passed (338)`.
- `pnpm --filter @object-ui/cli type-check`: exit 0, after `pnpm
--filter '@object-ui/cli^...' build` (9 of 47 projects; the rebuilt
`public-blocks.zod.d.ts` carries the helper). `packages/cli` is the one
package whose source reads these arms' types.
- Consumer sweep, 168 test files outside `packages/types` (re-derived
after the last merge): every test naming a face, an arm,
`@object-ui/types/zod`, the edited markdown or a changeset reader, plus
the cli suite and the arm-list consumers the batch-1 ruling names. `81
passed (81)` / `1847 passed` and `87 passed (87)` / `2126 passed`. The
same matrix was green at `da80d90` before the last merge (166 files,
3955 tests).
- Exit 0: `check:spec-symbols`, `check:component-surface-parity`
(report-only; it skips `zod/`), `check:new-line-citations` (0 new
citations), `check:control-bytes`, `check-changeset-presence` (1
changeset declared), `changeset:check`, `check:handler-key-reads` (140
arms, 56 judged), `check:changeset-claims` (report-only; 23 pending
changesets naming a touched file, each read; the four this change
falsifies get dated notes, below), `check:pending-changeset-literals`,
`check:test-path-roots`, `markdown-test-inputs.mjs --audit` (all
adjudicated), `check:doc-types`, `check-doc-links`.
- NOT MEASURED: `check:doc-snippets` and `check:doc-examples`, both exit
2, PRECONDITION NOT MET (34 packages unbuilt). This diff changes no `ts`
/ `tsx` fence and no `@example`, and no doc fence references a
public-block arm's type. CI builds and runs both. Also NOT MEASURED:
`check:readme-exports` (needs every package's dist); the README edits
add no fence or import.
- eslint, declared narrowing: the 4 changed TS files,
`--no-inline-config --format json`: 4 files, 0 errors, 1 warning, a
pre-existing `no-explicit-any` in `objectql.zod.ts` outside this diff's
hunks. `--print-config` shows no `project` / `projectService`, so
linting is not type-aware and no untouched file's verdict can move.
Repo-wide `pnpm lint` is CI's.
- `check-governed-queue-guard --test` over the 12 paths: NOT GOVERNED.

## Changesets

- `.changeset/10872-flat-props-refusal.md`: `'@object-ui/types': minor`,
the objectui#11328 and batch-5/6 precedent. The arms are unreleased
(`@object-ui/types` 17.6.0 on npm has no public-block arm: 0 hits for
`PublicBlockComponentSchema` in its dist, against 4 for
`AnyComponentSchema`), so across the release the accept set for these
types only widens.
- Dated notes, frontmatter byte-identical (md5 of the first four lines
unchanged): `10872-public-block-zod-arms.md`,
`10872-held-public-block-arms.md`, `10859-object-timeline-zod-arm.md`,
`10859-pivot-object-block-zod-arms.md`. Each stated that a flat key
passes the tolerant face unjudged, or that a base-declared row key is
judged by the base.
- `Clause-②: no` is the claim's line. The criterion is whether the card
widens an accept set or the public surface, and this one only refuses.
The precedent batches 5 and 6 and objectui#11328 declared their
narrowings `yes`; that difference is recorded for the seat rather than
chosen silently.

## objectui#11385 (landed as `304f611379` during this batch; merged into
this branch at `401d406`)

No shared file. Its `PublicBlockNode` is each arm's own `z.input` read
off the arm's shape, so the TS authoring types and these zod faces
describe the same bag by construction. Its pins run on the merged tree
and pass: `authoring-nodes-11364.test.ts` is in the `types` test program
(type-check exit 0) and in the `types` vitest run. Its two
`@ts-expect-error` rows on flat `items` and flat `actionType` stay
needed. A type probe against this branch (scratch, deleted; a
deliberately false control proved the file was in the program) found
that no arm's closed input carries an index signature. Flat
`actionType`, `visible` and `title` type as `undefined`, `label` stays a
node key, and the bag spelling compiles. Its `SchemaRenderer` hunk is
type-only (the `schema` prop union), so the hoist this batch relies on
is unchanged.

⚠️ **One disagreement, recorded rather than repaired (its files are not
this batch's).** The landed `authoring-nodes.ts` builds
`ElementTextInputNode` and `ElementRecordPickerNode`, the two spec-row
blocks with no zod arm, "the way every arm is built":
`BaseSchema.extend({ type, ...NODE_ENVELOPE, properties, body, children
})`. After this batch every arm also spreads `flatPropRefusals`, so
those two TS types are the one place the bag shape differs. A
base-declared row key written flat type-checks there: `placeholder`,
`disabled` and `description` on `element:text_input`, and `placeholder`
on `element:record_picker` (probe against the installed rows). Their
other row keys written flat are refused, but only as excess properties,
not by name. No validator disagrees today: `AnyComponentSchema` refuses
both types at `type`. When those two get zod arms, the arms should
spread `flatPropRefusals`, and the TS types can then derive from the
arms.

## Acceptance notes

- Two pending changesets of other cards teach the flat `actionType` on
`action:button` in their examples:
`.changeset/10289-action-params-properties.md` (its Migration block) and
`.changeset/7867-action-params-templates.md`. Their claims are about the
runtime and stay true, but the examples will freeze into the CHANGELOG
at release teaching a spelling `objectui validate` now refuses. Not
edited here; carrier: the seat (a dated note before the release,
objectui#11183's family).
- `object-form`, `object-map`, `object-gantt` and `object-chart` keep
their own per-arm maps and messages; folding them into
`flatPropRefusals` would change their messages, on cards held out of
this round. Carrier: objectui#10859 / objectui#11276.
- `AGENTS.md` objectstack-ai#4 and `skills/objectui/rules/protocol.md` say objectui's
strict face refuses the flat `actionType` / `target`; both faces now do.
The sentence stays true, so no governed file is touched.
- The eight in-code composers above still build flat nodes. They are the
node as the renderer reads it after the hoist, not authored documents,
and no zod face judges them.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
… literal refuses an undeclared key; the compile-fail pin, both directions (objectui#8347) (objectstack-ai#11607)

Fixes objectstack-ai#8347
Clause-②: yes (narrowing)

`BaseSchema` no longer declares `[key: string]: any`. A node literal
annotated with its node type now refuses a key that no declaration
names, a misspelled key included, and the correct spelling still
compiles. This executes the objectui#7927 ruling ("the TypeScript face
is an executable contract"), with the compile-fail pin that ruling made
non-negotiable. `ComponentRendererProps` keeps its own signature, and a
row in the pin notices if it is ever removed. The zod faces and every
runtime path are unchanged, except `visibleWhen` (below).

Governed: this diff touches `AGENTS.md` and two published-skill files
(`skills/objectui/guides/architecture.md`,
`skills/objectui/rules/protocol.md`), so the PR stays a draft until an
authorized approval is on it (`node
scripts/check-governed-queue-guard.mjs --test` answers GOVERNED for each
of them). Session:
`https://claude.ai/code/session_01CPvhwGcirXqBGEdPSb72TZ` (PM loop round
5, claim `5977679107`, amended for patch round 3 by `5979679543`).

## The maintainer's one open choice: the `props` alias on the TypeScript
face

This PR writes **option A**: `props` is not declared on the TypeScript
authoring face. The renderer still reads it at runtime (the maintainer's
2026-08-18 ruling binds the runtime and leaves the TS face open). So the
two `badge` literals in `components`'
`alias-precedence-cross-channel.test.tsx` that author `props` now carry
an `@ts-expect-error` naming this choice, and `AGENTS.md` §4's abridged
listing drops the `props` member that `BaseSchema` never declared.

- **A (written here):** not declared. The dev measurement on
objectui#11364 found no non-test TypeScript, tracked JSON or objectstack
example that authors a node `props`, and the published skill teaches it
as wrong.
- **B:** `props?: unknown` on `BaseSchema`. `AuthoringNode` derives from
the zod `BaseSchema` shape, so B also needs a zod mirror row and a
parity entry.
- **C:** declared on `AuthoringNode` only.

To flip it, remove the two directives and add the member. Nothing else
in this PR depends on the choice.

## What changed, class by class (one commit each)

1. **The two src sites.** `plugin-kanban`: `ObjectKanban` reads the
gate-written `sort` (the binding's `dataSource.sort`) through
`GateBoundKanbanSchema`, a read type private to the file;
`ObjectKanbanSchema` still declares no `sort` (objectui#8174).
`plugin-timeline`: `TimelineRenderSchema`, the private hand-off type,
declares the `onItemClick` slot `ObjectTimeline` composes;
`TimelineSchema` still declares none. Both compile with the signature
present or absent.
2. **The mechanical rows.** Two schema-to-record test reads convert
through `unknown`; the gantt and map `propertiesBag-10859` fixtures keep
`operator` a literal and type the bag node as its block node.
3. **`BaseSchema.visibleWhen`, Q6 = B.** The TS face is the spec's
`EvaluatedExpressionInput`, by reference. The zod twin is the same
accept set (a string, or the spec's own `EvaluatedExpressionSchema`
envelope) with `EvaluatedExpressionInputSchema`'s verdict and without
its string transform.
4. **The removal, and everything that can only land with it.** The
compile-fail pin; the adjudicated probe and ceiling flips; the two
`props` flips; the valueless envelope `metric-card` pinned refused on
the TS face (Q3 = A); `envelopeStray` and its `complex.ts` note deleted
per the constant's own removal condition; the two
`object-view-slot-key-lists` tripwires flipped;
`partial-schema-collapse-pin.test.ts` deleted per its header, with
`PartialSchema`'s docblock describing the alias as it now works; five
mirror-parity ledger entries (below); and the prose in those files that
described the signature as live.
5. **Docs.** The 13 listed doc blocks plus the
`objectos-integration.mdx` page block (now `regions`, and since round 4
rendered under `SchemaRendererProvider`); the published passages that
described the signature as live; the plugin-calendar README note; the
plugin-dashboard README widget-key passage; the `examples/dashboard.ts`
comment; a dated note on the pending `11564` changeset, whose "until
objectui#8347 removes it" sentence this release makes false.
6. **`AGENTS.md`.** §5 #0.1 gains the rule as shipped: the TS authoring
face is part of the contract, a refused key is declared by reference or
fixed at the producer and never cast past, and the fresh-literal bound
is named with the pin that measures it. §4 drops `props`; objectstack-ai#4's `events`
sentence names the face that still keeps one.
7. **The changeset.** `minor` for `@object-ui/types` with the breaking
note; `minor` for `plugin-timeline` (`TimelineRenderSchema`, the
`schema` prop type of the exported `TimelineRenderer`, gains one
optional member); `patch` for `plugin-kanban`.
8. **Patch round 3, after the merge of `main` at `b508ac50`.** The two
published-skill sections (H3 below); the five restored deletion guards
and the reworded test strings (Acceptance notes); `main` merged by a
merge commit, with the type-check of every package `main` moved since
the merge base green afterwards.
9. **Patch round 4, doc and comment bytes only.** The integration
guide's App block installs the adapter through `SchemaRendererProvider
dataSource` (the renderer reads it from `SchemaRendererContext`; a
`dataSource` prop on `SchemaRenderer` reaches only the root component),
and its system-objects comment names only the provider. The parity
ledger's `complex.zod.ts#DashboardWidgetSchema` `component` entry says
the mirror slot's `BaseSchema` fallback stays by objectui#8344's
decision, with no open card retiring it, instead of reserving it for
this card.

## The compile-fail pin, both directions

`packages/types/src/__tests__/base-schema-closed-face-8347.test.ts`,
read by `tsc -p tsconfig.test.json`:

- objectui#7927's P5 on a FRESH literal: `titleFieldd` on a
`CalendarViewSchema` literal is an `@ts-expect-error`, and `titleField`
compiles. The same on `BaseSchema` itself (`classNmae` against
`className`), on a node nested in a slot (`labell` on a `button` child),
and on `PartialSchema`.
- P5 as objectstack-ai#7927 ran it, on today's plugin-calendar README block, stays
green with or without the signature (measured on this card's census):
that block is a type alias plus two assignments of declared variables,
and tsc never excess-checks those. So the pin uses fresh literals, and
the bound itself is pinned as a directive-free row, so nobody reads the
file as claiming more.
- Ablation, run on the committed head through `ablation-replace.mjs`
(anchor hit 1 to 0, on-disk blob moved, trap-restored to blob equal to
HEAD, `git diff HEAD` empty): with `[key: string]: any` put back on
`BaseSchema`, the types test program exits 2 with 52 errors, and every
refusal row of the pin turns red (TS2578 on the four misspelling rows,
TS2344 on the two no-index rows). At HEAD the same program exits 0.

## The measurements the dispatch named

**H1, the re-census at BASE `2e818d0b5`, before any kept edit.**
Instrument: every package built (turbo, forced: 40 of 40, 0 cached); the
six `BaseSchema` lines deleted and nothing else; types rebuilt (dist
keeps one signature, `ComponentRendererProps`'); then every tsc program
the workspace type-check scripts name, one job each (90 jobs,
apps/site's generators included, plus the four root programs).

| | programs | red | unique errors | src | tests |
|---|---|---|---|---|---|
| BASE, signature removed alone | 90 | 13 | 81 in 44 files | 3 | 78 |
| BASE control, signature present (the same 13) | 13 | 0 | 0 | | |
| final head `e0a9af67`, removal kept | 90 | 0 | 0 | 0 | 0 |

The 81 fall exactly into the predicted classes: src 3 (`ObjectKanban`
`sort` twice, timeline `onItemClick`); tests 67 adjudicated probe and
ceiling flips (types 49; charts `xAxisKy` twice and `objectNme`;
dashboard `bogusKey` twice and `zzUndeclared7353` four times; grid
`bogusKeyRefusedNowhere` and two `Expect` wrappers; list `visibleWhn`
twice; map `enableClusterng`; tree `zzplTreeAbsentControl8655`,
`objectNaem`; components `content` on `label`), 2 `props` flips, 3
mechanical rows (two schema-to-record casts, one mirror-parity ledger
mismatch), and the 6 gantt and map fixture literals. No site needed a
contract decision. `check:doc-snippets` under the removal at BASE: 14 of
777 blocks failed, the 13 listed plus the `objectos-integration.mdx`
page block (BASE control: 0 failed); doc-examples, doc-types and
skill-examples stayed green.

**The parity ledger.** The mismatch was five pairs whose member is typed
with an `interface` (`AppSchemaRendererNodeSchema.schema`; the `reports`
pairs' `report`, `sections`, `chart`) against a `.passthrough()` mirror
whose `z.input` carries a string index signature. TypeScript grants an
implicit index signature to anonymous and mapped types and never to an
interface. Measured: the declared type is refused, and a one-level
anonymous copy of it fits the same mirror input. They are ledgered as an
artifact of the assignability test, with that reading in each entry's
docblock, and the header's derived figures moved with them.

**H2, the objectui#11573 headroom pin.** `AnyComponentSchema`'s
declaration on the compiler's counter: 12260 before, 12974 after (the
`visibleWhen` twin), limit 900000. Read through a trap-deleted copy of
the pin that writes its reading.

**H3, skill fences.** `check:skill-examples` exits 0 under the removal
at BASE and on every head since. Patch round 3 edits the two `skills/**`
files whose prose the removal made false: the architecture guide's "JSON
Protocol" section and the protocol rule's "Component Schema Structure"
section said the published `BaseSchema` declares its members plus an
index signature, and that `props` / `events` / `properties` reach a node
through it. Both now state the two verdicts a key no member declares
gets, one per face (a compile error in a fresh typed literal on the
TypeScript face, with the fresh-literal bound said as AGENTS.md §5 #0.1
says it; kept and judged by the named reader on the tolerant zod face),
and their teaching fences drop the three keys `BaseSchema` never
declared and end on the same no-index-signature line as AGENTS.md §4.
`props` keeps one sentence stating option A, so a flip to B or C is a
one-sentence edit. Neither fence is a marked example;
`check:skill-examples`, `check:skill-eval-tokens` and
`check:skills-paths` stay green.

**H4.** The flex rows' header and `examples/dashboard.ts` no longer say
"until objectui#8347"; the flex test gains the row that removal promised
(a `text` entry misspelled inside the list is refused, and the
well-formed list's `text` entry compiles).

**H5, `visibleWhen`.** `visible-when-spec-input-8347.test.ts`: the TS
member is `Equal` to the spec's `EvaluatedExpressionInput`; on 13 probe
values the mirror's verdict equals `EvaluatedExpressionInputSchema`'s
and `PageComponentSchema.visibleWhen`'s, read in the same run (the probe
set holds both verdicts); a blank string is refused with the spec's own
sentence; a string parses to the same string, while the spec rewrites it
(the control); an envelope parses to itself; and the envelope a spec
parse writes is accepted by both component faces, a dialect-less one
refused. ⚠️ This is a narrowing as well as a widening on the tolerant
face: a blank `visibleWhen` string used to parse and is refused now, as
the spec refuses it.

## Gates, on head `e0a9af67`

The dev's local readings on that head. The gate verdict on the current
head is its own check-runs.

- type-check, every program (the H1 instrument): 90 of 90 exit 0, 0
errors.
- `pnpm --filter @object-ui/types build`: exit 0. `pnpm --filter
@object-ui/types test`: exit 0, `Test Files 353 passed (353)`, `Tests
9419 passed (9419)`.
- vitest over the edited plugin packages (kanban, timeline, gantt, map,
charts, dashboard, grid, list, tree), on the same file contents: exit 0,
`Test Files 819 passed | 1 skipped (820)`. `components`: see the report.
- `check:doc-snippets` `777 of 777 block(s) judged, 0 failed.`;
`check:doc-examples` `35 compile, 87 fail, 87 of those declared`;
`check:doc-types`, `check:skill-examples`, `check:readme-exports`,
`check:spec-symbols`, `check:changeset-claims`,
`check:pending-changeset-literals`, `check:control-bytes`,
`changeset:check`, `type-check:coverage`: exit 0.
`check:new-line-citations`: `0 new citation(s)`.
- `check-governed-queue-guard.mjs --test` over the changed paths:
GOVERNED (`AGENTS.md`; since patch round 3 also the two `skills/**`
files).

## Overlap

PR objectstack-ai#11069 (draft) edits `content/docs/guide/architecture.md`'s paragraph
on a button's text key. This PR edits the same file's Type Safety block
(the button literal writes `label`). Region-disjoint; whichever lands
second merges `main`.

## Acceptance notes (read, not filed)

- `ObjectChartBlock`'s pre-gate `schema` prop is `BaseSchema` (ruling
`5810232641`). A fresh literal at that prop is now excess-checked
against `BaseSchema` itself, so a correctly spelled `objectName` is
refused there as much as the `objectNme` probe; in-tree callers pass
typed nodes. The flipped probe's comment says so.
- plugin-grid's `RemoveIndexSignature` in `ObjectGrid.tsx` strips
nothing now, as its own test predicted; retiring it is cleanup left out
of this PR.
- `object-view-slot-key-lists`' `form` `Pick` list may move back to
`Omit`, per its header's removal condition; not done here.
- `PartialSchema` has no consumer outside its declaration (a
tracked-files `git grep` for the name, with `SchemaByType` as the
positive control). The enforce-or-remove card the deleted pin's header
asks for is filed: objectui#11608 (`Blocked-by: objectstack-ai#8347`).
- Source comments, test comments, docs and the published skill that
described the signature as live, or said objectui#8347 removes it, are
repaired in this PR (patch rounds 2 and 3). The enumerating commands are
`git grep -n -i "index signature"`, `git grep -n -F '[key: string]:
any'` and `git grep -n 'objectui#8347 remove'` over `packages/`,
`apps/`, `examples/`, `content/docs/` and `skills/`, CHANGELOGs and
release notes excluded as append-only. What they still find is true at
the head: other types' own signatures (`ComponentRendererProps`,
`AuthUser`, `FormField`, a widget's own props type, the field-widget
`data-` attribute signature, `QueryParams`' statements), the tolerant
zod face's `.passthrough()`, and past-tense history.
- The deletion guards the removal silenced are restored (patch round 3).
While the signature stood, an `@ts-expect-error` over a tombstone or a
typed member also failed (TS2578) when its member was deleted; since the
removal the undeclared key satisfies the directive.
`object-kanban-card-title-9606`,
`object-grid-default-sort-tombstone-5861`,
`object-grid-show-filters-retired-11068`,
`object-grid-declared-keys-11068` and `gantt-flat-config-declared-keys`
each gain a type-level row that is a compile error when its member is
deleted and pins the declared or tombstone type; each was shown red by a
trap-restored deletion of its member and green at HEAD (readings on the
card). Three test titles and one assertion message that described the
signature as live are reworded, assertion logic untouched.

## 维护者速读(草稿)

**改了什么**:`@object-ui/types` 的 `BaseSchema` 去掉了 `[key: string]: any`。从此用
TypeScript
写节点时,拼错或未声明的键会直接编译报错,正确拼写照常通过;新增的钉子测试两个方向都钉住,并用"把签名加回去、钉子变红"的消融证明它真能失败。顺带:`visibleWhen`
改为直接引用 spec 的输入类型(字符串或 `{ dialect, source }`
信封);看板与时间轴两处运行期键改为在各自包内私有类型上声明;一批文档示例与说明改成在新约束下成立;AGENTS.md §5 #0.1
写明"TS 编写面也是契约"。已发布技能包(`skills/objectui`)里两处讲 `BaseSchema`
形状的教学文案同步改写——它们原先说该类型带索引签名、`props`/`events` 由签名承接,去掉签名后已不成立;因此本 PR 的受管面除
AGENTS.md 外还含 `skills/**`,同一份批准覆盖两者。

**为什么改**:这是 objectui#7927 的裁决(TS 面是可执行契约)的落地,也是本卡的全部范围。此前签名让任何拼写都能编译,AI
批量写错元数据时被静默放过。

**风险与代价(含回滚)**:对 TS 使用者是破坏性收窄(按仓规以 `minor` 发布并在 changeset 写明);运行期与 zod
宽容面不变,唯一例外是 `visibleWhen` 现在拒绝空字符串、接受 spec 的信封。已知边界:TS
只对"新鲜对象字面量"做多余键检查,经变量转手的值不会再查,已在 AGENTS.md 与钉子里写明。回滚:还原本 PR 即可,无数据迁移。

**席位意见**:

**你要做的**:1)裁决 `props` 别名在 TS 面上的去留:本 PR 按方案 A(不声明)写,B 是在 `BaseSchema`
上声明 `props?: unknown`,C 是只在 `AuthoringNode` 上声明;改选只需删两条
`@ts-expect-error` 并加成员。2)批准后由认领席落地(受管面,草稿态等你的批准)。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01CPvhwGcirXqBGEdPSb72TZ)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-zhuang <jack@objectstack.ai>
This was referenced Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants