Skip to content

Commit fc7db05

Browse files
spec(types,plugin-detail,vscode-extension): TS twins of the zod-only node arms, the README's Activity tab on its declared properties, and Export to React's typed schema constant (objectui#11515) (#11519)
Fixes #11515 Clause-②: yes (widening) Part of the preparation for objectui#11466 (V1, draft PR #11512). On PR #11512's head, `check:doc-snippets` judges 776 blocks and refuses 2: the plugin-detail README's `detail-view` "With Tabs" example and the Export to React "Output Example" in `content/docs/utilities/vscode-extension.mdx`. This PR settles both. Measured under a throwaway probe (this branch plus PR #11512's commits, merged without a commit, never pushed), the same gate judges 776 and refuses 0. With V1's own copies of the two documents in that probe, it refuses exactly those 2. ## Block 1 — TS twins of the zod-only node arms (ruling A) **Before.** `@object-ui/types` declared no TypeScript type for three arms its zod face already validates: `detail-section` (`views.zod.ts#DetailSectionNodeSchema`), `app-schema-renderer` (`app.zod.ts#AppSchemaRendererNodeSchema`) and `cloud:plan-status` (`cloud.zod.ts#CloudPlanStatusSchema`). `SchemaByType` of each literal was `never`, and the parity census carried all three as `EXCLUSIONS` ("no TS declaration in this package restates the node"). **After.** Each gains a twin beside its arm's module, member for member: | zod arm | TS twin | members | | --- | --- | --- | | `views.zod.ts#DetailSectionNodeSchema` | `DetailSectionNodeSchema` (`views.ts`, in `ViewComponentSchema`) | the ten members the arm `.pick`s from `DetailViewSectionSchema` (`title`, `description`, `icon`, `fields` required, `collapsible`, `defaultCollapsed`, `columns`, `showBorder`, `headerColor`, `hideEmpty`), each `DetailViewSection`'s own member by indexed access; `body` / `children` `?: never` | | `app.zod.ts#AppSchemaRendererNodeSchema` | `AppSchemaRendererNodeSchema` (`app.ts`, in `AnySchema`) | `schema?: AppComponentSchema` (the arm's member IS `AppComponentSchema`), `basePath?: string`, `mobileNavMode?: 'drawer' or 'bottom_nav'`; `body` / `children` `?: never` | | `cloud.zod.ts#CloudPlanStatusSchema` | `CloudPlanStatusSchema` (new `cloud.ts`, in `AnySchema`) | `properties: { plan: string }`, required and closed; `body` / `children` `?: never` | No zod schema, validator verdict or runtime behaviour changes. The widening is the TS face's declared surface only, matching what the zod face already publishes (the `Clause-②` line above). **Pins.** - `zod-mirror-parity.test.ts`: the three pairs move from `EXCLUSIONS` into `MIRRORS` / `Declared`; `EXPECTED_MIRROR_PAIRS` 191 to 194. `cloud:plan-status` measures clean in all four directions. Two pairs are born ledgered, each with the reading its by-reference member already carries one level up (same cause, not a new finding): - `KnownDrift` gains `views.zod.ts#DetailSectionNodeSchema: 'fields'`, the `DetailViewField` `options` EXPECTED DIVERGENCE, as on `views.zod.ts#DetailViewSectionSchema`. Header figures 49 / 87 to 50 / 88, with the history sentence. - `WiderThanDeclared` gains `app.zod.ts#AppSchemaRendererNodeSchema: 'schema'`, one SCHEMA-NODE arm: the app document's own wider reading through `NavigationItemSchema`'s `z.ZodType` of `any` annotation, the `app.zod.ts#AppComponentSchema` `areas` row's cause. Header figures 3 / 3 / 3 (2 / 1 / 0 / 0) to 4 / 4 / 4 (3 / 1 / 0 / 0). - `anyschema-declared-node-types-11478.test.ts` stays green: the three types are `AnySchema` members. - **`node-recursion-point-8344`'s measured set exists only on PR #11512's head.** On `main` that pin reads `never`, because `SchemaNode`'s object arm is still `BaseSchema`. Measured in the probe: with these twins, V1's `MeasuredArmDrift11466` pin goes red (TS2344) until exactly `detail-section`, `app-schema-renderer` and `cloud:plan-status` are deleted from it, and is green after (types `type-check` exit 0). That deletion lands on PR #11512 when it merges `main`. - Merging this branch into PR #11512 conflicts in `zod-mirror-parity.test.ts` (three hunks, both sides' rows kept). The union measures 6 / 6 / 8 (3 / 3 / 0 / 2) for `WiderThanDeclared`, confirmed by the derived-figure pin in the probe. **The README's Activity tab.** It authored `record:activity` with `items: activityData`, the host feed slot the node refuses by name (objectui#11321). It now authors the declared input, `properties: { limit: 20, showCompleted: false }`, as the plugin-detail guide does; the `FeedItem` import and `activityData` go. The note under the example says where the feed comes from. Rendered through `DetailTabs` and the real registry (a one-off probe, not kept), the tab draws `Activity (0)`, the `All Activity` filter and `No activity recorded`, with no unknown-type panel and no error banner. The zod docblock in `public-blocks.zod.ts` that named this README as a host composition passing `items` gets a dated parenthesis. ## Block 2 — the Export to React generator (ruling A, route changed by measurement) **Before.** The emitted constant was unannotated, so every `type` widened to `string`; under V1 the one JSX line is refused for every schema (TS2322 against the declared-node union). **The card's annotation, measured.** `const schema: SchemaNode` does NOT compile, today or under V1: `SchemaNode` also admits `number` and `boolean`, which `SchemaRendererProps['schema']` leaves out, and the narrowing at the initializer does not reach the body of `GeneratedComponent`. With it, the JSX line was refused for every schema on both trees, including the documented sample. That is the ruling's own premise ("it exists today and compiles both now and after V1") measured false, so the route follows the ruling's intent instead: a type that exists today, is not `DeclaredNode`, and compiles against the prop on both trees. **After.** The constant is typed `SchemaRendererProps['schema']`, imported type-only beside `SchemaRenderer` from the package the file already imports (no new dependency). Emitted output for the documented sample, before and after: ```text -import { SchemaRenderer } from '@object-ui/react'; +import { SchemaRenderer, type SchemaRendererProps } from '@object-ui/react'; ... -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'] = { ``` `tsc` (strict, `react-jsx`, `noUnusedLocals`) on the emitted file, against the BUILT packages of each tree: | fixture | main, before | main, after | V1 probe, before | V1 probe, after | | --- | --- | --- | --- | --- | | documented sample (`div` with an `h1` child) | 0 errors | 0 errors | 1 (TS2322) | 0 errors | | an undeclared child type | 0 | 0 | 1 | 1 (TS2322 at the child) | | an object with no `type` | 1 | 1 | 1 | 1 | The `SchemaNode` annotation and a `satisfies SchemaNode` variant were measured on the same grid; the table is in the report on the card. **The documented sample keeps `"type": "h1"`.** The card reads `h1` as undeclared; it is declared, one of the HTML passthrough tags `HtmlElementSchema` declares (objectui#8499), and `scripts/__tests__/check-doc-component-types.test.ts` pins `"type": "h1"` in this very sample as the registered replacement for `heading`. Annotated, the `h1` sample compiles on both trees (table above, and `check:doc-snippets` on both). A rewrite to `text` with `variant: 'h1'` was tried and reverted when that pin went red; the PM may still order it (one sample edit plus that pin). **Pins.** The emitter has tests: `export-to-react-compiles.test.ts` and `export-to-react-preamble.test.ts`. - The compile pin's stub typed the prop `unknown`, so it could not see an annotation. A new leg compiles the emitted file against the REAL prop type with no build: `@object-ui/types` resolves to its source entry, and the `@object-ui/react` declaration carries the `SchemaRendererProps` body and its `@object-ui/types` import read out of `packages/react/src/SchemaRenderer.tsx` at test time, so it follows the prop when V1 retypes it. Its positive control is a schema with no `type`, refused on the constant's own line. - Ablation, run once and not kept (generator emitting the BASE unannotated constant, restored by blob equality to HEAD): on `main` the control turns red (the refusal moves from the constant's line to the JSX line); in the V1 probe both legs turn red (the sample is refused with TS2322, widened `type: string`). - The preamble pin names the new import line and the annotated constant; the two documented mirrors (`DESIGN.md` section 4 and the docs page) follow the new preamble, held by the existing objectui#7976 pin. ## Corpus documents re-judged - `packages/plugin-detail/README.md`, "With Tabs": re-authored (above). Its `detail-section` tab was refused under V1 for want of a TS type; with the twins it compiles under V1 unchanged. - `content/docs/utilities/vscode-extension.mdx`, "Output Example": new preamble and annotated constant; sample unchanged. - `packages/vscode-extension/DESIGN.md`, section 4: new preamble. - `packages/cli/src/__tests__/validate-passing-keys-11440.test.ts` described the README's second tab as the host feed. It now validates the full example document as the README writes it (green on `objectui validate`), and keeps the host-feed row as the refusal's control. ## Pending changesets - Dated notes added: `8114-detail-tab-activity-timeline.md` (its `items, not data` bullet and the `activityData` sentence), `11321-record-feed-host-slots.md` (the README as the `items` composition), `11478-anyschema-declared-node-types.md` (`ViewComponentSchema`'s four members; `AnySchema` gains three). - Read and left alone, each still true as written: `10919-cloud-plan-status`, `11440-arm-passing-types`, `11494-types-app-schema-renderer-schema`, `11494-layout-app-schema-renderer-adapter` (all state the zod face and the registration; none says the TS face lacks a type), `7837`, `7862` and `7976` (Export to React preamble history; the side-effect import, the absent React import and the mirror binding all still hold), and the 25 entries `check:changeset-claims` lists as naming a touched file (each describes another pair, key or README section). - New: `11515-types-ts-twins-zod-only-arms.md` (`@object-ui/types` minor), `11515-vscode-export-react-annotation.md` (`object-ui` patch), `11515-plugin-detail-readme-activity-properties.md` (`@object-ui/plugin-detail` patch). ## Verification (head `afb660b7`) - `pnpm --filter @object-ui/types run type-check` (all three `tsc` legs), `pnpm --filter @object-ui/cli run type-check`, `pnpm --filter object-ui run type-check`: exit 0. - `pnpm exec vitest run packages/types/ packages/vscode-extension/` plus the cli validate test, the plugin-detail README reader and `scripts/__tests__/check-doc-component-types.test.ts`: `Test Files 349 passed (349)`, `Tests 9299 passed (9299)`. - `pnpm check:doc-snippets`: `776 of 776 block(s) judged, 0 failed`. `check:doc-types`, `check:doc-examples`, `check-changeset-no-major`, `check-changeset-presence`, `check:new-line-citations`, `check:control-bytes`: green. `check:changeset-claims` is report-only (read above). - V1 probe: types `type-check` exit 0 with the three names out of the 8344 set; the parity, 8344, node-slot-union and AnySchema suites pass; `check:doc-snippets` judges 776 and refuses 0. The probe's plugin-dashboard build needed four throwaway `as never` casts at the producer sites PR #11512 already reports red; they never left the probe. - NOT MEASURED: `check:sdui-registration-pins` (no registration input moved; it needs a console build), repo-wide lint (CI's). ## Acceptance notes - `export-to-react-compiles.test.ts`'s `SAMPLE_SCHEMA` still writes the retired `body` while its comment calls it the `empty` template's shape (the template writes `children`). Inert under the hermetic stub; not touched here. --- _Generated by [Claude Code](https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 95e58a3 commit fc7db05

19 files changed

Lines changed: 539 additions & 62 deletions

‎.changeset/11321-record-feed-host-slots.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,10 @@ A host feed slot written on a `record:activity` or `record:history` node is refu
1717
**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.
1818

1919
**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.
20+
21+
⚠️ **Dated note, 2026-10-02 — the plugin-detail README no longer passes `items` — objectui#11515.**
22+
At this change the plugin-detail README handed `record:activity` its `items`
23+
inside a `DetailView` tab, the TSX composition the paragraph above names; now
24+
that tab authors the block's declared `properties`, and no example in this
25+
repository composes a `record:activity` node with `items`. The refusal and
26+
what a host may still do in code are unchanged. The rest of this entry is kept as the reading of this change.

‎.changeset/11478-anyschema-declared-node-types.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,10 @@ admitted every one of these nodes.
2424
enumerates every exported object type that extends `BaseSchema` with a literal
2525
`type` and fails on any that is not a member. `ActionBarSchema` does not extend
2626
`BaseSchema`, so it is held by name.
27+
28+
⚠️ **Dated note, 2026-10-02 — three more members — objectui#11515.**
29+
At this change `ViewComponentSchema` was the four schemas named above; now it
30+
also holds `DetailSectionNodeSchema` (`detail-section`), and `AnySchema` also
31+
holds `AppSchemaRendererNodeSchema` (`app-schema-renderer`) and
32+
`CloudPlanStatusSchema` (`cloud:plan-status`), the TypeScript twins of three
33+
zod arms that had none. The rest of this entry is kept as the reading of this change.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@object-ui/plugin-detail': patch
3+
---
4+
5+
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).
6+
7+
`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.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
`@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).
6+
7+
**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.
8+
9+
**What changed, in observable terms.**
10+
11+
- `SchemaByType<'detail-section'>`, `SchemaByType<'app-schema-renderer'>` and `SchemaByType<'cloud:plan-status'>` resolve to the new types; each was `never`.
12+
- `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.
13+
- `AppSchemaRendererNodeSchema` declares `schema` (the app document, `AppComponentSchema`), `basePath` and `mobileNavMode` (`'drawer'` | `'bottom_nav'`).
14+
- `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.
15+
- On all three, `body` and `children` are `?: never`, the twins of the arms' by-name refusals.
16+
- 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>`).
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'object-ui': patch
3+
---
4+
5+
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).
6+
7+
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.
8+
9+
`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.
10+
11+
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.

‎.changeset/8114-detail-tab-activity-timeline.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,12 @@ does not walk `packages/NAME/README.md` — that widening is objectui#7896's, an
5252
objectui#7896 is blocked by this card. Measured rather than assumed: with an
5353
unregistered type substituted back into this very block, `check:doc-snippets`
5454
and `check:doc-types` both still exit 0.
55+
56+
⚠️ **Dated note, 2026-10-02 — the Activity tab authors `properties`, not `items` — objectui#11515.**
57+
At this change the README's Activity tab handed `record:activity` a feed it
58+
already owned as `items: activityData`, typed `FeedItem[]`; now the tab authors
59+
the block's declared inputs in `properties` (`{ limit: 20, showCompleted: false }`),
60+
`activityData` and its `FeedItem` import are gone, and in a bare `<DetailView>`
61+
the tab draws the block's empty state, because `items` is the host's feed slot
62+
the node refuses by name (objectui#11321). The `items, not data` bullet above
63+
and the sentence on `activityData` no longer describe the README. The rest of this entry is kept as the reading of this change.

‎content/docs/utilities/vscode-extension.mdx‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,24 @@ This is the file the command produces — your exported schema comes back verbat
8686
as the `schema` constant, and both imports are part of the output.
8787
`import '@object-ui/components'` is load-bearing: importing the package is what
8888
registers the default renderers, so a file that drops it renders nothing.
89+
The constant is typed as `SchemaRendererProps['schema']`, the schema
90+
`SchemaRenderer` takes, so the compiler checks your schema where it is written:
91+
each node's `type` stays a literal and is checked against the node types the
92+
prop accepts, and a schema it does not accept is refused on that line.
8993

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

100-
const schema = {
104+
// Typed as the schema SchemaRenderer takes, so each "type" below stays a literal
105+
// and the compiler checks the schema against the node types it accepts.
106+
const schema: SchemaRendererProps['schema'] = {
101107
"type": "div",
102108
"className": "p-4",
103109
"children": [

‎packages/cli/src/__tests__/validate-passing-keys-11440.test.ts‎

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
* - the plugin-detail package's own `detail-view` example, whose first tab's
1818
* content is a `detail-section` node (its "With Tabs" section).
1919
*
20-
* That example is TSX, and its second tab hands `record:activity` a
21-
* host feed (`items: activityData`), which objectui#11321 refuses by name in a
22-
* JSON document — that refusal names this example as the TSX composition it
23-
* stays legal in. So the row below validates the example's first tab as
24-
* written (restated here, not read from the page, so this file reads no
25-
* markdown), and the second row holds the host-feed refusal as the ONLY issue
26-
* left on the full document.
20+
* That example is TSX. Its second tab handed `record:activity` a host feed
21+
* (`items: activityData`), which objectui#11321 refuses by name in a JSON
22+
* document, so the rows below validated the first tab alone and held the
23+
* host-feed refusal as the ONLY issue left on the full document.
24+
* objectui#11515 re-authored that tab to the block's declared `properties`, so
25+
* the full document as the page now writes it validates (the third row). The
26+
* host-feed row stays, as the refusal's control on the old spelling. Each
27+
* document is restated here, not read from the page, so this file reads no
28+
* markdown.
2729
*/
2830

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

133-
it('on the example\'s full document, the only issue left is the host feed on `record:activity` (objectui#11321)', async () => {
135+
it('validates the example\'s full document as the page writes it since objectui#11515: the Activity tab authors `properties`', async () => {
136+
const text = await run('detail-view-full-declared', {
137+
type: 'detail-view',
138+
title: 'Account: Acme Corp',
139+
objectName: 'accounts',
140+
resourceId: '12345',
141+
fields: [{ name: 'name', label: 'Account Name' }, { name: 'industry', label: 'Industry' }],
142+
tabs: [
143+
{ key: 'details', label: 'Details', icon: '📄', content: EXAMPLE_DETAIL_SECTION },
144+
{ key: 'activity', label: 'Activity', badge: '12', content: { type: 'record:activity', properties: { limit: 20, showCompleted: false } } },
145+
],
146+
showEdit: true,
147+
showDelete: true,
148+
});
149+
expect(text).not.toContain('Schema validation failed');
150+
expect(text).toContain('Schema is valid');
151+
expect(exitCodes).toEqual([0]);
152+
});
153+
154+
it('on the example\'s old full document, the only issue left is the host feed on `record:activity` (objectui#11321)', async () => {
134155
const text = await run('detail-view-full', {
135156
type: 'detail-view',
136157
title: 'Account: Acme Corp',

‎packages/plugin-detail/README.md‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,7 @@ const accountDetail = <DetailView
134134

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

139-
declare const activityData: FeedItem[];
140138
declare const navigate: (url: string) => void;
141139
declare const deleteAccount: (id: string) => void;
142140

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

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

0 commit comments

Comments
 (0)