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
54 changes: 54 additions & 0 deletions .changeset/8114-detail-tab-activity-timeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
'@object-ui/plugin-detail': patch
---

The README's Activity tab example names `record:activity`, a block this package
registers, instead of `activity-timeline`, which nothing registers
(objectui#8114).

`README.md` ships in this package's `files`, so the example went out in every
tarball. `DetailTabs` renders a tab's `content` through
`<SchemaRenderer schema={toRenderableSchema(tab.content)} />`, which makes
`content.type` an SDUI node position judged by the component registry — so a
reader copying the tab got the registry's `Unknown component type` panel
(OBJUI-001) where the timeline should be. Same shape as the `line-chart` widget
in `plugin-dashboard/README.md` (objectui#7896's census; fixed by objectui#7951)
and the fourth known instance.

Which type replaces it was read off the registry rather than guessed, using the
repository's own derivation — `deriveRegistryKeys()` from
`scripts/check-doc-component-types.mjs`, the 649-key universe that gate judges
against:

| key | reading |
|---|---|
| `record:activity` | REGISTERED — `packages/plugin-detail/src/index.tsx:673` |
| `activity-timeline` | absent |
| `activity` (bare) | absent — the `skipFallback: true` half |
| `related-list`, `detail-section`, `record:details` | REGISTERED (lit controls) |

Two keys move, because naming the type alone would leave the block fed through a
key it never reads:

- **`type: 'record:activity'`**, namespace spelled out. The registration passes
the bare name under `{ namespace: 'record', skipFallback: true }`, and
`skipFallback` is what keeps the bare name unclaimed — `record:activity`
resolves, `activity` does not. That `activity` is also the tab's own `key` is
a coincidence of spelling, and the card that filed this read the key as
evidence there was no such component type.
- **`items`, not `data`.** `RecordActivityRenderer` takes its feed from `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 and a bare
`<DetailView>` mounts neither, so the example's own intent — a caller handing
over a feed it already owns — is the first source. `data` is read on no path.

`activityData` is retyped from `Record<string, unknown>[]` to the exported
`FeedItem`, which puts the block under `check:doc-snippets`. No package source
and no runtime behaviour changes.

**Still ungated, stated so this is not mistaken for coverage.**
`check:doc-types` is the gate that judges `type` literals and it deliberately
does not walk `packages/NAME/README.md` — that widening is objectui#7896's, and
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.
36 changes: 33 additions & 3 deletions packages/plugin-detail/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,9 @@ const accountDetail = <DetailView

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

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

Expand Down Expand Up @@ -166,9 +167,11 @@ const accountDetail = <DetailView
key: 'activity',
label: 'Activity',
badge: '12',
// `record:activity` — the registered Activity Timeline block. Reachable
// under that exact key and no other; see the note below the block.
content: {
type: 'activity-timeline',
data: activityData,
type: 'record:activity',
items: activityData,
},
},
],
Expand All @@ -181,6 +184,33 @@ const accountDetail = <DetailView
/>;
```

A tab's `content` is an **SDUI node**, not a private vocabulary: `DetailTabs`
renders it with `<SchemaRenderer schema={toRenderableSchema(tab.content)} />`,
so `content.type` is resolved by the component registry and an unregistered
name paints the `Unknown component type` panel (**OBJUI-001**) instead of a
component. Author only types this repository registers.

Two things about the Activity tab above are worth copying rather than guessing
(objectui#8114 — it previously taught `type: 'activity-timeline'`, which
nothing registers):

- **The key is `record:activity`, with the namespace spelled out.** The
registration in `src/index.tsx` passes the bare name under
`{ namespace: 'record', skipFallback: true }`, and `skipFallback` is what
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`).
`data` is not a key this block reads.

See **The `record:activity` block** in the plugin-detail guide for its declared
inputs (`types`, `limit`, `filterMode`, `showCompleted`, …).

## Schema

The DetailView component accepts a `DetailViewSchema`, declared in
Expand Down
Loading