Skip to content

Redesign examples based on new JSON project specification - #69

Merged
huangyiirene merged 5 commits into
mainfrom
copilot/redesign-examples-json-spec
Jan 15, 2026
Merged

huangyiirene merged 5 commits into
mainfrom
copilot/redesign-examples-json-spec

Conversation

Copilot AI commented Jan 15, 2026 •

Copy link
Copy Markdown
Contributor

Redesign Examples Based on New JSON Project Specification

This PR redesigns the examples directory to follow the new JSON project specification, introducing a JSON-first approach for demonstrating Object UI capabilities.

Changes Made

New JSON-Based Examples

Created 4 standalone JSON schema examples that can be directly rendered using the Object UI CLI:

  • basic-form/ - Contact form with validation and Tailwind styling
  • dashboard/ - Analytics dashboard with metrics, activity feeds, and responsive grids
  • data-display/ - User profiles, task lists, badges, and progress bars
  • landing-page/ - Complete marketing page with hero section, features, and CTAs

Each example includes:

  • app.json - Pure JSON schema definition
  • README.md - Comprehensive documentation with usage instructions and customization guide
  • .gitignore - Git ignore configuration

Documentation Updates

  • Created comprehensive examples/README.md with learning path and full index of all examples
  • Updated root README.md to prominently feature new JSON examples with CLI usage
  • Updated docs/guide/quick-start.md to reference actual examples instead of non-existent ones
  • Added "Running Example Projects" section to docs/CLI_GUIDE.md with detailed instructions
  • Fixed repository URL inconsistency (objectql/objectui) throughout examples documentation

Best Practices

  • All examples use pure JSON schemas (no code required)
  • Follow Tailwind CSS and Shadcn/UI conventions
  • Bilingual support where appropriate (cli-demo)
  • All JSON schemas validated successfully

Usage

Users can now quickly explore Object UI capabilities by running:

npm install -g @object-ui/cli
objectui serve examples/basic-form/app.json

This provides a clear learning path from simple forms to complex dashboards and landing pages.

Original prompt

基于新的json项目规范,重新设计examples


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel

vercel Bot commented Jan 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
objectui-playground Ready Ready Preview, Comment Jan 15, 2026 4:02am

Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>

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 redesigns the examples directory based on a new JSON project specification, introducing a JSON-first approach for demonstrating Object UI capabilities. The changes add four new standalone JSON schema examples that can be directly rendered using the Object UI CLI.

Changes:

  • Added 4 new JSON-based examples (basic-form, dashboard, data-display, landing-page) with complete documentation
  • Created comprehensive examples/README.md with learning paths and usage instructions
  • Updated root README.md to prominently feature new JSON examples with CLI usage examples

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
examples/basic-form/app.json Contact form schema demonstrating input fields, validation, and grid layouts
examples/basic-form/README.md Comprehensive documentation for the basic form example
examples/basic-form/.gitignore Standard ignore file for Object UI temp files and OS/IDE artifacts
examples/dashboard/app.json Analytics dashboard schema with metrics, activity feeds, and sticky header
examples/dashboard/README.md Detailed documentation for dashboard patterns and customization
examples/dashboard/.gitignore Standard ignore file for Object UI temp files and OS/IDE artifacts
examples/data-display/app.json Data visualization schema showcasing profiles, tasks, progress bars, and badges
examples/data-display/README.md Documentation for various data display patterns and color schemes
examples/data-display/.gitignore Standard ignore file for Object UI temp files and OS/IDE artifacts
examples/landing-page/app.json Full marketing landing page schema with hero, features, CTA, and footer sections
examples/landing-page/README.md Comprehensive guide for landing page structure and customization
examples/landing-page/.gitignore Standard ignore file for Object UI temp files and OS/IDE artifacts
examples/README.md Main examples index with learning paths, quick start guides, and feature matrix
README.md Updated to prominently feature new JSON examples with CLI commands

Comment thread examples/README.md Outdated
Comment on lines +225 to +226
- [Open an Issue](https://github.com/objectstack-ai/objectui/issues)
- [Join Discussions](https://github.com/objectstack-ai/objectui/discussions)

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

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

Repository URL inconsistency: The examples README uses 'objectstack-ai/objectui' while the rest of the repository consistently uses 'objectql/objectui' (as seen in CONTRIBUTING.md, README.md, and throughout the docs). This should be updated to match the correct repository organization name.

Suggested change
- [Open an Issue](https://github.com/objectstack-ai/objectui/issues)
- [Join Discussions](https://github.com/objectstack-ai/objectui/discussions)
- [Open an Issue](https://github.com/objectql/objectui/issues)
- [Join Discussions](https://github.com/objectql/objectui/discussions)

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 0dddf0e. Updated the repository URLs to use 'objectql/objectui' to match the convention used throughout the rest of the documentation.

@hotlong

hotlong commented Jan 15, 2026

Copy link
Copy Markdown
Contributor

@copilot 继续工作

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Redesign examples based on new JSON project specification Redesign examples based on new JSON project specification Jan 15, 2026
Copilot AI requested a review from hotlong January 15, 2026 04:04
@github-actions github-actions Bot added documentation Improvements or additions to documentation examples labels Jan 15, 2026
@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 15, 2026 04:11
@huangyiirene
huangyiirene merged commit 882bd53 into main Jan 15, 2026
11 checks passed
os-warren added a commit that referenced this pull request Sep 9, 2026
…objectui#8317)

Decision batch #90 (2026-09-08) ruled that batch #69's principle — a validator
validates, it does not write values into an author's document — holds for every
key `safeValidateSchema` answers, not only the 41 this repo authored. The 57
`ZodDefault` nodes that arrived by reference from `@objectstack/spec` are now
stripped where the spec enters this package.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
os-warren pushed a commit that referenced this pull request Sep 9, 2026
…faces (objectui#8315)

The 2026-09-07 ruling on objectui#6950 (director decision batch #69) retired the
zero-writer `'field'` arm under ADR-0049, naming one coordinate: the serializer's
input boundary, which PR #8297 narrowed. `ManifestInput.binding` and
`ManifestValidationResult.bindings[].kind` kept it, so one published package
narrowed on one face and stayed wide on the other.

Both now read `'object'`. The subset counter-argument — a reader accepting a value
no producer emits is permissive, not wrong — does not reach either declaration:
`bindings[].kind` is a PRODUCER face that `validateTree` writes, where a wider
union obliges consumers rather than accepting more; and the two are coupled by
`kind: input.binding`, so narrowing one alone would need a cast at the only
conversion site. `ManifestInput` is also `manifestFromConfigs`'s return type, so
it is not a pure reader face either.

Measured on both heads with a firing control: zero positive writers of
`binding: 'field'` in objectui (the 2 occurrences are `@ts-expect-error` pins)
and zero in objectstack; control `binding: 'object'` fires 19 and 2 times.

The narrowing is compile-time only and a pin says so: this package has no runtime
validator for a manifest it is handed. Two prose statements in `index.ts` and the
pin test's header claimed the reader face was deliberately left wide; they became
false with this change and are corrected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 9, 2026
…put instead of casting it in (objectstack-ai#8297)

`binding` on a component input is framework-set, not author-declared
(maintainer ruling of 2026-09-07, director decision batch objectstack-ai#69).

- `@object-ui/types` exports `InjectedComponentInput`, an interface
  extending `ComponentInput` with the required marker `binding: 'object'`.
  `ComponentInput` itself is unchanged: no member added, and authoring
  `binding` on a registration stays an excess-property tsc error. The two
  tombstone docblocks that listed `binding` among the forwarded per-input
  keys now say it is forwarded from the framework's injected input.
- `@object-ui/core` types `ELEMENT_DATA_SOURCE_INPUT` as
  `InjectedComponentInput` and splices it through a typed local in
  `withElementDataSourceInput`; the `as ComponentMeta` cast is gone.
- `@object-ui/sdui-parser` narrows `RegistryConfigLike.inputs[].binding`
  from `'object' | 'field'` to `'object'` (ADR-0049 enforce-or-remove):
  the `'field'` arm had zero writers -- every `binding:` literal under
  `packages/`, `apps/` and `examples/` is `'object'`, 7 of 7 at the
  merge-base. `ManifestInput.binding` (the manifest reader's vocabulary)
  is not narrowed.
- Pins, one per face: the type-level half and the source-text pins on the
  two write sites in `@object-ui/types`; the runtime half (manifest still
  carries `binding: 'object'`, `validateTree` still records the binding
  site, the boundary refuses `'field'`) in `@object-ui/sdui-parser`, driven
  through the real registry.


Claude-Session: https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 9, 2026
…tstack-ai#8299)

* fix(types,components): the zod mirrors stop authoring defaults

A `.default(v)` on a zod mirror member is not documentation: `parse`
SUBSTITUTES `v` into the output when the key is absent, so one authored
document took two shapes depending on whether it had been through
`safeValidateSchema` — and where the mirror and the renderer disagreed,
the mirror silently won.

  ContainerSchema.maxWidth   mirror 'lg'      container.tsx  ?? 'xl'
  FlexSchema.align           mirror 'center'  flex.tsx       || 'start'

Ruled by the director seat (decision batch objectstack-ai#69, 2026-09-07, maintainer
reply 「其他同意」): a validator validates, it does not write values into
an author's document. The renderer's fallback is the single authoritative
default, the JSDoc `@default` describes it, and the mirror carries none.

Removes all 41 `.default()` call sites under `packages/types/src/zod/`
(layout 22, crud 11, form 5, views 2, app 1). Every one was spelled
`.optional().default(v)`, so the accept set is untouched — this narrows
`parse` OUTPUT only. Reconciles the third face objectui#8229 found:
`flex`'s registration seeded `defaultProps.align: 'center'`, the value
its own renderer never applies.

Two derived pins land with it: the mirror carries no `.default()` CALL
SITE (parsed, not grepped — four legitimate docblock mentions survive and
a text pin would be red on landing), and every `defaultProps`/fallback
pair in flex.tsx and stack.tsx agrees.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX

* fix(types,components): correct the four @default tags that describe the mirror, not the renderer

Contract-review patch round for PR objectstack-ai#8299. The 41-site removal, both pins, the
accept-set claim and the `flex.tsx` value are unchanged — the review reproduced
all of them. What failed was four written statements the tree contradicts.

1. THE CHANGESET STATED A FALSE MEASUREMENT. "Rendering does not change …
   the two that disagreed were the bug" quoted a 7-key triage sample as if it
   were the population. Auditing all 22 layout keys against their renderers
   finds SIX disagreements: container.maxWidth, flex.align, grid.columns
   (3 vs `let baseCols = 2`), text.variant ('body' vs none), resizable.withHandle
   (true vs a bare forward that draws no grip) and page.template ('default' vs a
   pageType dispatch). The note now says that, and says plainly that the change
   is BREAKING for consumers that read `result.data`.

2. RULING NOTE 3(ii) WAS MEASURED AGAINST THE WRONG REFERENCE. The tags were
   compared with the value the MIRROR used to write; the ruling asks for the
   RENDERER's fallback, which is the whole point of option 乙. Re-measured
   against the renderer, all 41 keys: 19 agree, 2 are deliberately untagged
   (objectstack-ai#7361/objectstack-ai#7734), 16 have no registered renderer reading them at all, and 4
   misdescribed the renderer. Those four are corrected here — `grid.columns`
   3 -> 2, and `text.variant` / `resizable.withHandle` / `page.template` lose
   their tag for prose naming the read site, the shape objectstack-ai#7361 established for a
   value no renderer applies.

   The pin follows: `layout-default-jsdoc-7361.test.ts` gains four derived rows
   (both sides read off disk, with positive controls) and a discrimination
   control — that docblock's prose quotes the retired `@default 'body'`, so the
   tag extractor is shown ignoring a backtick-quoted mention while still seeing
   a real tag next door. The 33 rows it does not reach are declared as a named
   narrowing in the PR body and filed as objectui#8318.

3. Two shipped `text.tsx` docblocks said the mirror declares/publishes
   `.default('body')`. This PR is what made them false; both now say so in the
   past tense.

4. `flex.tsx:145` cited the pin as `.test.tsx`; the file is `.test.ts`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 9, 2026
…faces (objectui#8315) (objectstack-ai#8723)

The 2026-09-07 ruling on objectui#6950 (director decision batch objectstack-ai#69) retired the
zero-writer `'field'` arm under ADR-0049, naming one coordinate: the serializer's
input boundary, which PR objectstack-ai#8297 narrowed. `ManifestInput.binding` and
`ManifestValidationResult.bindings[].kind` kept it, so one published package
narrowed on one face and stayed wide on the other.

Both now read `'object'`. The subset counter-argument — a reader accepting a value
no producer emits is permissive, not wrong — does not reach either declaration:
`bindings[].kind` is a PRODUCER face that `validateTree` writes, where a wider
union obliges consumers rather than accepting more; and the two are coupled by
`kind: input.binding`, so narrowing one alone would need a cast at the only
conversion site. `ManifestInput` is also `manifestFromConfigs`'s return type, so
it is not a pure reader face either.

Measured on both heads with a firing control: zero positive writers of
`binding: 'field'` in objectui (the 2 occurrences are `@ts-expect-error` pins)
and zero in objectstack; control `binding: 'object'` fires 19 and 2 times.

The narrowing is compile-time only and a pin says so: this package has no runtime
validator for a manifest it is handed. Two prose statements in `index.ts` and the
pin test's header claimed the reader face was deliberately left wide; they became
false with this change and are corrected.


Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 9, 2026
… this mirror authors no default, imported subschemas included (objectui#8317) (objectstack-ai#8721)

* fix(types): strip the imported defaults at the spec import boundary (objectui#8317)

Decision batch objectstack-ai#90 (2026-09-08) ruled that batch objectstack-ai#69's principle — a validator
validates, it does not write values into an author's document — holds for every
key `safeValidateSchema` answers, not only the 41 this repo authored. The 57
`ZodDefault` nodes that arrived by reference from `@objectstack/spec` are now
stripped where the spec enters this package.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w

* refactor(types): spell the import boundary at every crossing, not once per file

`check:spec-symbols` (ci.yml) reads exactly ONE hop: a mirror export under a
spec-owned name must show the `@objectstack/spec` import binding in its own
initializer. Re-binding the imports to a local `const Spec… =
stripImportedDefaults(Imported…)` put that binding one hop away and turned 16
declarations red (measured: green at da5e4f6, red at 99bde74). Wrapping each
crossing instead keeps the provenance where the gate — and a reader — looks for
it, with no gate surgery and no ALLOW entries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
os-zhuang pushed a commit that referenced this pull request Sep 9, 2026
…d of dropping it

`DataTableSchema.emptyAction` is declared `SchemaNode` on both published faces
(`packages/types/src/data-display.ts` and its Zod twin), and `SchemaNode` is
`BaseSchema | string | number | boolean | null | undefined`. The empty-state
render path additionally required `typeof … === 'object'`, so an authored
`emptyAction: 'Create the first record'` rendered nothing and reported nothing —
declared wider than enforced, failing in the direction that loses the author's
content without a diagnostic.

The declaration is unchanged; it was the correct side. objectui#7105 (director
seat, decision batch #69, 2026-09-07) settled the identical shape one slot over
on `EmptySchema.action` as relax the renderer, do not narrow the declaration,
and shipped it through the repo's permanent `toRenderableSchema` bridge. Nothing
had to be invented here either.

The truthiness leg is kept on purpose, and the `&&` chain became a ternary.
Together they make the slot behave exactly as handing the raw node to
`SchemaRenderer` would: `''` / `0` / `false` keep rendering nothing (pinned,
objectui#4548) because they never reach the bridge, whose `String` mapping would
otherwise turn the last two into the text "0" and "false". The ternary also
closes a measured leak — with `emptyAction: 0` the old chain evaluated to the
number `0` itself, which React renders as a stray "0" inside the empty state.

The slot still mounts through `SchemaRenderer` rather than resolving the
registry, so the single central `visibleWhen` gate still applies unchanged
(objectui#5926 gap 1); its pin is the regression control here and is green on
both sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 17, 2026
…d of dropping it (objectstack-ai#8909)

`DataTableSchema.emptyAction` is declared `SchemaNode` on both published faces
(`packages/types/src/data-display.ts` and its Zod twin), and `SchemaNode` is
`BaseSchema | string | number | boolean | null | undefined`. The empty-state
render path additionally required `typeof … === 'object'`, so an authored
`emptyAction: 'Create the first record'` rendered nothing and reported nothing —
declared wider than enforced, failing in the direction that loses the author's
content without a diagnostic.

The declaration is unchanged; it was the correct side. objectui#7105 (director
seat, decision batch objectstack-ai#69, 2026-09-07) settled the identical shape one slot over
on `EmptySchema.action` as relax the renderer, do not narrow the declaration,
and shipped it through the repo's permanent `toRenderableSchema` bridge. Nothing
had to be invented here either.

The truthiness leg is kept on purpose, and the `&&` chain became a ternary.
Together they make the slot behave exactly as handing the raw node to
`SchemaRenderer` would: `''` / `0` / `false` keep rendering nothing (pinned,
objectui#4548) because they never reach the bridge, whose `String` mapping would
otherwise turn the last two into the text "0" and "false". The ternary also
closes a measured leak — with `emptyAction: 0` the old chain evaluated to the
number `0` itself, which React renders as a stray "0" inside the empty state.

The slot still mounts through `SchemaRenderer` rather than resolving the
registry, so the single central `visibleWhen` gate still applies unchanged
(objectui#5926 gap 1); its pin is the regression control here and is green on
both sides.


Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 17, 2026
…objectui#7804, plugin-detail slice) (objectstack-ai#9343)

* fix(types): declare onNavigate and onAddComment on the detail arm

`BaseSchemaCore` ends `.passthrough()`, so a key the arm for `type: 'detail'`
does not declare is not refused - it stops being judged and the value is KEPT,
then reaches the renderer. `DetailView` is registered RAW for `'detail'`, so
both keys arrived by identity and RAN: `onNavigate` is called in the component's
own body (`handleBack` / `handleEdit` / the post-delete redirect), `onAddComment`
is forwarded as a prop into the comment composer that awaits it.

Measured on the unmodified arm before the code: an authored
`{ "action": "toast" }` parsed GREEN on both keys with the object surviving into
the parsed output, and the Back click then reported
`TypeError: schema.onNavigate is not a function`. `onBack`, already a named
refusal on the same arm, was refused on the same document and is the lit control.

Both are therefore RUNTIME SLOTS, measured per key rather than per prefix: a
named refusal on the JSON face, a callable twin on the TypeScript face. The two
`KNOWN_UNDECLARED_READS` rows they held are drained (39 -> 37).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ

* test(plugin-detail): type the detail handler-slot probe's each-tables uniformly

`it.each([...DECLARED, ['onBack']])` widened the row type to
`string[] | readonly [key: string]`, which vitest cannot narrow into a callback
signature (TS2345 under `tsc -p tsconfig.test.json`). Both tables are now
`as const` tuples, so the key parameter types as the literal union and the
`.shape` index needs no cast.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ

* test(plugin-kanban): re-derive the ledger control's witness after the detail slice drained it

A semantic collision the merge could not see: PR objectstack-ai#9338's `suite 4` CONTROL
names one surviving `KNOWN_UNDECLARED_READS` row to prove the leg reads a
SHRINKING map rather than an empty one, and the row it named was
`detail::DetailSchema.onNavigate` — exactly the row this branch drains. Both
PRs were green alone; the control only reddens once both are in one tree.

Re-derived against the merged ledger rather than weakened: the witness moves to
`button::ButtonSchema.onSuccess`, one of the 35 rows that survive both slices
and belongs to neither. The assertion keeps both halves it had — a non-empty
map AND a named row — because the length check alone passes on a map holding a
single stale row, which is the reading the control exists to refuse.

The comment now records that the witness is re-derived on each landing, so the
next slice of objectui#7804 to drain it knows to move it rather than drop it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ

* docs(changeset): point the fixed-group sentence at config.json, not at a count

The sentence explaining why this ships as `minor` stated a package count for
the `fixed` group. The count did not match what `.changeset/config.json`
enumerates, and a changeset body publishes VERBATIM into the CHANGELOG, so the
wrong figure was headed for a release note.

Repaired per AGENTS.md objectstack-ai#9 rather than by correcting the digits: the sentence now
points at the instrument that re-derives the group — the `fixed` array
`.changeset/config.json` enumerates — and carries no figure at all. A count
written down here is derived once and never again; the next member added to or
dropped from the group falsifies it silently, and a reader who spot-checks a
still-correct figure confirms it and is wrong the moment the group moves.

The sentence keeps its job: the reader still learns this package ships inside a
fixed group, that any `major` would carry every member with it, and that
`scripts/check-changeset-no-major.mjs` is what makes `major` unavailable.

Why no gate caught it: `scripts/check-changeset-claims.mjs` is report-only and
covers WENT FALSE only. This was BORN FALSE — a cardinal in prose, which that
gate's own header records as the shape it refuses to judge.

Frontmatter and every other line of the changeset are untouched.

Refs: objectui#7804

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt

* test(plugin-detail): retire the defunct cast-blind narration in the 7804 slots suite

Suite 4's last leg in `detail-handler-slots-7804.test.tsx` shipped a green
assertion narrating a fact this tree contradicts. Its name said a cast HIDES
the read from the repo gate, and its comment said the key is therefore absent
from `KNOWN_UNDECLARED_READS`. Neither holds at this head: the gate unwraps
`as` / `!` / `satisfies` / parens before it names the receiver, so
`(schema as any).onTabChange` in `DetailView.tsx` IS judged and IS carried as
a ledger row attributed to objectui#7804. The assertion itself is a SOURCE
census of the cast spelling and stays correct, which is precisely why nothing
went red -- the AGENTS.md objectstack-ai#9 shape.

Repaired, narration only:

- the leg's name and comment now describe the tree they ship in, and say out
  loud what the leg does measure (the source spelling, not the gate's reach)
  and where the key's disposition is decided (objectui#9344 item 2, in the zod
  arms);
- the file docblock's hard-coded ledger size is gone in favour of the
  instrument -- the count is whatever `check-handler-key-read-sites.mjs`
  prints, and is no longer written down. The figure that stood there went
  stale inside the life of this branch.

The assertion, the declarations, the zod arms and the changeset are untouched.

Measured at this working tree:
- `node scripts/check-handler-key-read-sites.mjs` exit 0, and its `--list`
  output is byte-identical before and after this commit;
- the ledger holds 37 rows by three independent instruments (the gate's own
  printed line, `KNOWN_UNDECLARED_READS.size` read off the module, and the
  `[UNDECLARED]` occurrences in `--list`), with
  `detail::DetailSchema.onTabChange` last in declaration order;
- the four touched test files: 4 files / 130 tests passed, exit 0;
- eslint on the edited file: exit 0, 0 errors, 0 warnings;
- `check:control-bytes`, `check:new-line-citations`, `check:comment-mask-corpus`
  and `check:test-path-roots` all exit 0.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt

* test(plugin-kanban): defer the 7804 slice docblock's row count to the instrument

The sibling of the narration repair one commit back, and the same defect
class: the docblock hard-coded a row count that nothing re-derives, and the
figure had already been overtaken by the population it described.

Seat ruling on the record: the count is dropped in favour of the instrument,
⛔ not restated as the current one. Restating it would have been a NEW false
claim rather than a repair -- the rows live on this tree are not the same SET
the slice's finding named. Measured across four refs: at a686403 the ledger
held 39 rows with no cast-spelled rows and no cast-piercing in the gate; at
this head it holds 37, two of them `onTabChange` rows that arrived with
objectui#9392 and two `detail` rows drained by this branch. Same cardinality
family, different set.

This file already reads the ledger live -- it imports `KNOWN_UNDECLARED_READS`
from the gate of record -- so the docblock now points at that import and at
the gate's printed line and `--list`, per AGENTS.md objectstack-ai#9.

⛔ Narration only, in one docblock sentence. Proved rather than asserted: the
file's non-comment lines are byte-identical before and after (209 lines,
sha256 d7e9bae66... both sides), so no assertion, import or executable line
moved. The changeset, the declarations, the zod arms, the labels and the draft
state are untouched.

Measured at this working tree:
- `node scripts/check-handler-key-read-sites.mjs` exit 0, and its `--list`
  output is byte-identical to the previous head's;
- the four touched test files: 4 files / 130 tests passed, exit 0, per-file
  split unchanged (71 / 32 / 14 / 13);
- eslint on the edited file exit 0; `check:comment-mask-corpus`,
  `check:control-bytes` and `check:new-line-citations` all exit 0.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt

* docs(api): say by name that the detail arm refuses its handler keys

`content/docs/api/schema-reference.md` is the author-facing reference for the
`type: "detail"` face, and this branch moves `onNavigate` and `onAddComment` on
that face from ACCEPTED AND KEPT to REFUSED BY NAME. The reference said nothing
about either key, so it was left teaching the old acceptance, and nothing
mechanical catches that: `check:doc-types` judges registered component TYPES,
not accept sets, and is exit 0 either way.

Same shape and role as the note objectui#9338 — the preceding slice of this same
card, under the same batch objectstack-ai#69 ruling — put under `### ObjectKanbanSchema` in
this same file, but measured against THIS arm rather than transposed:

  - three keys are refused here, not two: `onBack` since objectui#7344, plus
    `onNavigate` and `onAddComment` from this change;
  - the refusal message is quoted from what `handlerKeyRefusal` actually emits,
    read off the built mirror (`packages/types/dist/zod/index.zod.js`);
  - the two keys reach the renderer on DIFFERENT channels, so the "used to do"
    clause names both: `schema.onNavigate` is called in `DetailView`'s own body
    (`handleBack`, `handleEdit`, the post-delete redirect), while
    `onAddComment` is forwarded as a prop into the comment composer, which
    renders BECAUSE the key is truthy and then awaits it on send;
  - the still-undeclared third key differs from the kanban board's. `onCardMove`
    is accepted and DROPPED; `detail`'s `onTabChange` is accepted, KEPT, read
    through a cast and handed to the tab strip's `onValueChange` — so it still
    reaches a call site expecting a function. The note says so rather than
    forcing the parallel.

Docs only: no schema, no renderer, no test and no changeset change.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt

* docs(api): re-point the DetailSchema note at the tombstone the merge created

The `### DetailSchema` note contrasted `onTabChange` with "the `object-kanban`
board's third key" to make one point: an authored `onTabChange` is KEPT and
reaches a call site, where the board's loose key was accepted and dropped.

Merging origin/main moved the comparand. objectui#9342 gave `onCardMove` a
tombstone on both faces, so the board's third key is no longer accepted-and-
dropped — it is refused by name. Re-measured on the BUILT mirror
(`packages/types/dist/zod/index.zod.js`) after a scoped build:

  ObjectKanbanSchema + onCardMove  -> REFUSED
    "Card move handler — `onCardMove` is RETIRED (objectui#6124, ADR-0049)"
  DetailSchema      + onTabChange  -> ACCEPTED, key kept in the parsed output
  DetailSchema      + onNavigate   -> REFUSED ("SPA navigation callback …")
  DetailSchema      + onAddComment -> REFUSED ("New comment callback …")
  DetailSchema      + onBack       -> REFUSED ("Custom back action …")

Controls for that reading, so the refusals are not a blanket rejection and the
acceptances are not a dead probe: the bare `{ type: 'detail' }` document parses
green, and an invented `zzNotAHandler` key is ACCEPTED and kept on both arms.

The contrast the sentence was making still holds and is now stated against what
the arm actually does. Every other clause in the note was re-checked and none
of them moved: the three refusal messages, their lead labels, the objectui#6124
sentence and the node-type closing are verbatim from the probe above, and the
read sites it names are still `DetailView.tsx` 699-704, 715-720, 784-785 for
`onNavigate`, 1753 for the `onAddComment` prop, and the cast read at 1696 that
reaches `<Tabs onValueChange>` at 1765. main did not touch `DetailView.tsx`.

No count is written down here: `onTabChange` remains a live
`KNOWN_UNDECLARED_READS` row, and `check-handler-key-read-sites.mjs --list`
stays the instrument that enumerates it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt

* docs(plugin-detail): re-point the onTabChange disposition at its live carrier

`detail-handler-slots-7804.test.tsx` routed the key's objectui#6124
disposition to "objectui#9344's item ②". That card is no longer open —
the API reads `state_reason: completed`, dated 2026-09-13T12:55:04Z — and
its item 2 was never carried anywhere, so the comment sent the reader to a
card that cannot answer it. The same file already said, nine lines above,
that the row is "attributed to objectui#7804"; the two halves of one
comment block disagreed, and the published note in
`content/docs/api/schema-reference.md` plus the ledger rows in
`check-handler-key-read-sites.mjs` both name objectui#7804.

The sentence now says what is true: the disposition is OPEN, is decided
neither here nor anywhere else yet, and sits on objectui#7804 — the card
the gate script itself names for this row. ⛔ No new card is invented, and
⛔ the gap is not described as decided.

Comment-only. The assertion `expect(castReads).toEqual(['onTabChange'])`
is untouched, as is every other line of the suite.

Measured at this head, from the repository root:
- gate of record: 60 reachable / 60 judged / 36 exempted, exit 0
- `--list`: 61 lines, sha256 prefix 0b586b1c9e568ffc
- the four touched test files: 4 files / 130 tests passed, exit 0
- `check:control-bytes`: 7625 tracked text files, exit 0

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt

* docs(plugin-detail): count channels and dispositions separately in the kanban cross-reference

The docblock claimed the sibling kanban slice got "three DIFFERENT
dispositions out of three keys". It cannot have: HandlerKeyDisposition
has exactly two members, so three keys can never carry three distinct
dispositions. What that slice measured is three different CHANNELS
carrying two dispositions - two runtime slots and one tombstone - which
is what its own objectql.zod.ts docblock already says in as many words.

The corrected reading is the stronger argument for per-key measurement,
not a climb-down: it is exactly the case where counting keys and counting
dispositions come apart, so neither can be read off the other.

Comment-only. The gate of record and its --list digest are unmoved.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-tesla <yinlianghui@steedos.com>

This branch was successfully deployed

1 active deployment
Preview — b9464e81 Deployed Jan 15, 2026 by vercel[bot]
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 examples

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants