Repository navigation
Redesign examples based on new JSON project specification - #69
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
There was a problem hiding this comment.
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 |
| - [Open an Issue](https://github.com/objectstack-ai/objectui/issues) | ||
| - [Join Discussions](https://github.com/objectstack-ai/objectui/discussions) |
There was a problem hiding this comment.
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.
| - [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) |
There was a problem hiding this comment.
Fixed in commit 0dddf0e. Updated the repository URLs to use 'objectql/objectui' to match the convention used throughout the rest of the documentation.
|
@copilot 继续工作 |
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
✅ All checks passed!
|
…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
…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
…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>
…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>
…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>
… 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>
…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
…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>
…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>
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:
Each example includes:
app.json- Pure JSON schema definitionREADME.md- Comprehensive documentation with usage instructions and customization guide.gitignore- Git ignore configurationDocumentation Updates
examples/README.mdwith learning path and full index of all examplesREADME.mdto prominently feature new JSON examples with CLI usagedocs/guide/quick-start.mdto reference actual examples instead of non-existent onesdocs/CLI_GUIDE.mdwith detailed instructionsBest Practices
Usage
Users can now quickly explore Object UI capabilities by running:
This provides a clear learning path from simple forms to complex dashboards and landing pages.
Original prompt
💡 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.