Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/11321-record-feed-host-slots.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ A host feed slot written on a `record:activity` or `record:history` node is refu
**Across the release.** The `record:activity` and `record:history` arms have not shipped yet: `@object-ui/types` 17.6.0 has no public-block arm, and its `safeValidateSchema` refuses both types at `type`. So this narrows what earlier entries of this same release accept, and nothing a published consumer could validate before.

**Docs.** The `record:related_list` example in the schema reference's retired-`related` note now writes its props in the `properties` bag, the last of the flat public-block examples objectui#11321 named. The plugin-detail page says `items` is passed in code and refused when a document writes it.

⚠️ **Dated note, 2026-10-02 — the plugin-detail README no longer passes `items` — objectui#11515.**
At this change the plugin-detail README handed `record:activity` its `items`
inside a `DetailView` tab, the TSX composition the paragraph above names; now
that tab authors the block's declared `properties`, and no example in this
repository composes a `record:activity` node with `items`. The refusal and
what a host may still do in code are unchanged. The rest of this entry is kept as the reading of this change.
7 changes: 7 additions & 0 deletions .changeset/11478-anyschema-declared-node-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ admitted every one of these nodes.
enumerates every exported object type that extends `BaseSchema` with a literal
`type` and fails on any that is not a member. `ActionBarSchema` does not extend
`BaseSchema`, so it is held by name.

⚠️ **Dated note, 2026-10-02 — three more members — objectui#11515.**
At this change `ViewComponentSchema` was the four schemas named above; now it
also holds `DetailSectionNodeSchema` (`detail-section`), and `AnySchema` also
holds `AppSchemaRendererNodeSchema` (`app-schema-renderer`) and
`CloudPlanStatusSchema` (`cloud:plan-status`), the TypeScript twins of three
zod arms that had none. The rest of this entry is kept as the reading of this change.
7 changes: 7 additions & 0 deletions .changeset/11515-plugin-detail-readme-activity-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@object-ui/plugin-detail': patch
---

The README's Activity tab authors `record:activity`'s declared inputs in `properties` (`{ limit: 20, showCompleted: false }`) instead of a host feed in `items` (objectui#11515).

`items` is the host's feed slot, which `@object-ui/types` refuses by name on the node (objectui#11321), so the example taught a key the node's declaration refuses. The note under the example now says where the block's feed comes from: a record host, such as the console's record page, or, in a bare `<DetailView>` like the example, the block's empty state ("No activity recorded"), which is what the tab draws there. No package source changes.
16 changes: 16 additions & 0 deletions .changeset/11515-types-ts-twins-zod-only-arms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@object-ui/types': minor
---

`@object-ui/types` declares TypeScript types for three node types its zod face already validates: `DetailSectionNodeSchema` (`detail-section`), `AppSchemaRendererNodeSchema` (`app-schema-renderer`) and `CloudPlanStatusSchema` (`cloud:plan-status`), each the twin of the zod arm of the same name (objectui#11515).

**Clause-②: yes (widening)** — the TypeScript face's declared surface widens to match what the zod face already publishes: three new exported types, each a member of `AnySchema` (`DetailSectionNodeSchema` through `ViewComponentSchema`). No zod schema, validator verdict or runtime behaviour changes.

**What changed, in observable terms.**

- `SchemaByType<'detail-section'>`, `SchemaByType<'app-schema-renderer'>` and `SchemaByType<'cloud:plan-status'>` resolve to the new types; each was `never`.
- `DetailSectionNodeSchema` declares the ten section members the arm picks from `DetailViewSectionSchema` (`title`, `description`, `icon`, `fields`, `collapsible`, `defaultCollapsed`, `columns`, `showBorder`, `headerColor`, `hideEmpty`), each `DetailViewSection`'s own member by reference, `fields` required.
- `AppSchemaRendererNodeSchema` declares `schema` (the app document, `AppComponentSchema`), `basePath` and `mobileNavMode` (`'drawer'` | `'bottom_nav'`).
- `CloudPlanStatusSchema` declares a required `properties` bag holding `plan: string` and no other key. The arm also refuses an empty `plan`, which the type cannot state.
- On all three, `body` and `children` are `?: never`, the twins of the arms' by-name refusals.
- Each type is a `zod-mirror-parity` pair with its arm. `cloud:plan-status` measures clean. The other two carry the reading their by-reference member already carries one level up: `fields` (the `DetailViewField` `options` divergence) and `schema` (the app document's wider reading through `NavigationItemSchema`'s `z.ZodType<any>`).
11 changes: 11 additions & 0 deletions .changeset/11515-vscode-export-react-annotation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'object-ui': patch
---

The VS Code extension's **Export to React** command types the `schema` constant it emits as `SchemaRendererProps['schema']`, imported type-only from `@object-ui/react` beside `SchemaRenderer` (objectui#11515).

Unannotated, every `type` in the constant widened to `string`. A `SchemaRenderer` prop that discriminates on the literal `type` refuses such a value, so the generated file stopped compiling as soon as the prop is narrowed to the declared node types (objectui#11466). Annotated, each `type` stays a literal and the schema is checked where it is written: a schema the prop does not accept is refused on the constant's own line. Measured with `tsc` against the built packages, the emitted file compiles today and with objectui#11466 applied.

`SchemaNode` was the annotation first proposed, and it does not fit: it also admits `number` and `boolean`, which the prop leaves out, so every generated file was refused at its one JSX line.

The compile pin gains a leg against the real prop type, read from `SchemaRenderer.tsx`, with a positive control. The two documented copies of the preamble (`DESIGN.md` and the docs page) follow it. The docs page's sample keeps its `h1` node: `h1` is a declared type (`HtmlElementSchema`), and the annotated sample compiles both today and with objectui#11466 applied.
9 changes: 9 additions & 0 deletions .changeset/8114-detail-tab-activity-timeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,12 @@ does not walk `packages/NAME/README.md` — that widening is objectui#7896's, an
objectui#7896 is blocked by this card. Measured rather than assumed: with an
unregistered type substituted back into this very block, `check:doc-snippets`
and `check:doc-types` both still exit 0.

⚠️ **Dated note, 2026-10-02 — the Activity tab authors `properties`, not `items` — objectui#11515.**
At this change the README's Activity tab handed `record:activity` a feed it
already owned as `items: activityData`, typed `FeedItem[]`; now the tab authors
the block's declared inputs in `properties` (`{ limit: 20, showCompleted: false }`),
`activityData` and its `FeedItem` import are gone, and in a bare `<DetailView>`
the tab draws the block's empty state, because `items` is the host's feed slot
the node refuses by name (objectui#11321). The `items, not data` bullet above
and the sentence on `activityData` no longer describe the README. The rest of this entry is kept as the reading of this change.
10 changes: 8 additions & 2 deletions content/docs/utilities/vscode-extension.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,24 @@ This is the file the command produces — your exported schema comes back verbat
as the `schema` constant, and both imports are part of the output.
`import '@object-ui/components'` is load-bearing: importing the package is what
registers the default renderers, so a file that drops it renders nothing.
The constant is typed as `SchemaRendererProps['schema']`, the schema
`SchemaRenderer` takes, so the compiler checks your schema where it is written:
each node's `type` stays a literal and is checked against the node types the
prop accepts, and a schema it does not accept is refused on that line.

```typescript
// No React import: under the automatic JSX runtime ("jsx": "react-jsx",
// what a new Vite or Next project is configured with) nothing here reads that
// identifier, so it compiles as an unused local. Add the import back only if
// this file is built with the classic "jsx": "react" transform.
import { SchemaRenderer } from '@object-ui/react';
import { SchemaRenderer, type SchemaRendererProps } from '@object-ui/react';
// Importing the package registers every default renderer as a side effect —
// there is no separate registration call.
import '@object-ui/components';

const schema = {
// Typed as the schema SchemaRenderer takes, so each "type" below stays a literal
// and the compiler checks the schema against the node types it accepts.
const schema: SchemaRendererProps['schema'] = {
"type": "div",
"className": "p-4",
"children": [
Expand Down
37 changes: 29 additions & 8 deletions packages/cli/src/__tests__/validate-passing-keys-11440.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
* - the plugin-detail package's own `detail-view` example, whose first tab's
* content is a `detail-section` node (its "With Tabs" section).
*
* That example is TSX, and its second tab hands `record:activity` a
* host feed (`items: activityData`), which objectui#11321 refuses by name in a
* JSON document — that refusal names this example as the TSX composition it
* stays legal in. So the row below validates the example's first tab as
* written (restated here, not read from the page, so this file reads no
* markdown), and the second row holds the host-feed refusal as the ONLY issue
* left on the full document.
* That example is TSX. Its second tab handed `record:activity` a host feed
* (`items: activityData`), which objectui#11321 refuses by name in a JSON
* document, so the rows below validated the first tab alone and held the
* host-feed refusal as the ONLY issue left on the full document.
* objectui#11515 re-authored that tab to the block's declared `properties`, so
* the full document as the page now writes it validates (the third row). The
* host-feed row stays, as the refusal's control on the old spelling. Each
* document is restated here, not read from the page, so this file reads no
* markdown.
*/

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
Expand Down Expand Up @@ -130,7 +132,26 @@ describe('objectui validate — the documents objectui#11440 arms', () => {
expect(exitCodes).toEqual([0]);
});

it('on the example\'s full document, the only issue left is the host feed on `record:activity` (objectui#11321)', async () => {
it('validates the example\'s full document as the page writes it since objectui#11515: the Activity tab authors `properties`', async () => {
const text = await run('detail-view-full-declared', {
type: 'detail-view',
title: 'Account: Acme Corp',
objectName: 'accounts',
resourceId: '12345',
fields: [{ name: 'name', label: 'Account Name' }, { name: 'industry', label: 'Industry' }],
tabs: [
{ key: 'details', label: 'Details', icon: '📄', content: EXAMPLE_DETAIL_SECTION },
{ key: 'activity', label: 'Activity', badge: '12', content: { type: 'record:activity', properties: { limit: 20, showCompleted: false } } },
],
showEdit: true,
showDelete: true,
});
expect(text).not.toContain('Schema validation failed');
expect(text).toContain('Schema is valid');
expect(exitCodes).toEqual([0]);
});

it('on the example\'s old full document, the only issue left is the host feed on `record:activity` (objectui#11321)', async () => {
const text = await run('detail-view-full', {
type: 'detail-view',
title: 'Account: Acme Corp',
Expand Down
20 changes: 11 additions & 9 deletions packages/plugin-detail/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ const accountDetail = <DetailView

```tsx
import { DetailView } from '@object-ui/plugin-detail';
import type { FeedItem } from '@object-ui/types';

declare const activityData: FeedItem[];
declare const navigate: (url: string) => void;
declare const deleteAccount: (id: string) => void;

Expand Down Expand Up @@ -169,9 +167,10 @@ const accountDetail = <DetailView
badge: '12',
// `record:activity` — the registered Activity Timeline block. Reachable
// under that exact key and no other; see the note below the block.
// Its inputs go in `properties`, as the spec declares them.
content: {
type: 'record:activity',
items: activityData,
properties: { limit: 20, showCompleted: false },
},
},
],
Expand Down Expand Up @@ -200,12 +199,15 @@ nothing registers):
stops the bare name from also being claimed globally — so `record:activity`
resolves and `activity` resolves to nothing. `activity` is additionally a tab
**key** in the example above; the two are unrelated.
- **The feed arrives as `items`, not `data`.** `record:activity` takes its feed
from three sources, in precedence order: `items` on the node, a mounted
discussion context, or a self-fetch from `sys_activity` scoped off
`useRecordContext`. The last two need a record host; a bare `<DetailView>`
like the one above mounts neither, so a caller that already owns the feed
passes it in as `items` (the convention `record:history` uses for `entries`).
- **The node declares the feed's filters, not the feed.** Its inputs (`limit`,
`showCompleted`, `types`, `filterMode`, …) go in its `properties` bag, and
the block finds its own feed: a mounted discussion context, or a self-fetch
from `sys_activity` scoped to the record `useRecordContext` binds. Both need
a record host; a bare `<DetailView>` like the one above mounts neither, so
there the tab draws the block's empty state ("No activity recorded"). Under
a record host, such as the console's record page, the same node draws that
record's activity. `items` (with its `loading` flag) is the host's feed
slot, not a key of this node: `@object-ui/types` refuses it by name, and
`data` is not a key this block reads.

See **The `record:activity` block** in the plugin-detail guide for its declared
Expand Down
Loading
Loading