Skip to content

Commit c708de6

Browse files
feat(rest,console): the record page payload serves the 动态 count, so the activity part asks only the panel's rows (#28991)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbNLXLNMXwrkkwwQqRkBQHM5)_ Fixes #28737 Clause-②: yes (widening) Metadata-protocol: added Skills-docs: none — no file under `skills/**` teaches the record page payload's parts; `skills/objectstack-ui/rules/record-page.md` teaches the page's layout, which this does not change. The hand-written `content/docs/frontend/plugins/plugin-detail.mdx` Console Integration section is updated. ## 维护者速读 - **改了什么**:记录页请求 `POST /data/:object/:id/page` 新增可选部件 `timeline`,由服务端统计这条记录完整 动态 流的条数(评论 + 动态 + 字段变更,去掉面板不画的类型,有字段变更时 `updated` 动态让位)。控制台用它显示 动态 计数徽标、「查看全部 N 条」和 动态 标签页计数,动态 部件只请求面板要画的 5 行,不再一次取 200 行。 - **为什么改**:#28682 里动态部件为了让计数正确只能取 200 行,这是它的合约审查 should-fix ③-2 记下的偏差。现在计数由服务端给出,行数可以按面板来取。 - **风险与回滚**:协议只增不减(Clause-② 加宽,Metadata-protocol: added,`@objectstack/spec` 与 `@objectstack/rest` 定为 minor),新成员全部可选,旧客户端不受影响。`RECORD_PAGE_MAX_ACTIVITY_ROWS`(200)和 `RecordPageActivityRequestSchema` 的上下限都没动。评论或动态读取被拒时整个 `timeline` 部件被拒,字段变更读取被拒时记 0 条。回滚就是还原本 PR。 - **先看**:`packages/spec/src/api/record-page.zod.ts`(`timeline` 的来源表和共享的类型排除常量),`packages/rest/src/record-page-routes.ts` 的 `answerTimeline`,`ui/packages/app-shell/src/views/RecordDetailView.tsx`(什么时候只请求 5 行、什么时候读完整窗口)。 - **两分钟验证**:`cd ui && pnpm exec vitest run packages/app-shell/src/views/RecordDetailView.timelineCount-28737.test.tsx`,四条通过:超过 200 条的记录显示服务端给的 341,请求只要 5 行;没有 动态 的记录显示 0,也没有回退读取;直接打开 动态 标签页时请求 200 行;去掉服务端计数的消融测试会退回本地计数 6。 - **需要你做的**:总监做合约审查(CONTRACT_REVIEW_TIER),由席位转交。PR 保持草稿。 ## What changes The record page payload counts the record's 动态 stream on the server, so the console no longer reads 200 activity rows to name a count. The 动态 panel draws its newest 3 or 5 entries, and the page now asks only those rows. ### Contract members added (one complete list for the contract review) `@objectstack/spec` (`packages/spec/src/api/record-page.zod.ts`, exported from `@objectstack/spec/api`): Request (`RecordPageRequestSchema`, still `.strict()`): - `timeline?: { discussion: boolean; activity: boolean; fieldChanges: boolean }` (`RecordPageTimelineRequestSchema`, strict, every source required): ask for the count of the 动态 stream drawn from the sources named. Response (`RecordPageResponseSchema`): - `timeline?: { total: int ≥ 0 } | { error }` (`RecordPageTimelineCountSchema` | `RecordPageStreamRefusalSchema`). Constants: - `RECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPES = ['commented', 'mentioned', 'login', 'logout']`: the `sys_activity` types the stream draws no entry for (`satisfies readonly SysActivityBuiltinType[]`). - `RECORD_TIMELINE_UPDATE_ACTIVITY_TYPE = 'updated'`: the activity type that steps aside once field changes are in the stream. New exports: those two schemas and two constants, and for each schema its `X` (input) and `XParsed` (output) type. `RECORD_PAGE_MAX_ACTIVITY_ROWS` stays 200 and `RecordPageActivityRequestSchema` keeps its bounds (ruling: not narrowed here). `@object-ui/types` (`ui/packages/types/src/data.ts`): `RecordPagePayload.timeline?: { total: number } | { error: RecordPagePartRefusal }`. The request type is the spec's `RecordPageRequest`, so it gains `timeline` with no separate declaration. `@object-ui/plugin-detail`: exports `RecordTimelineTotalContext` (the host's count of the whole stream, `number | null`), `RECORD_TIMELINE_PANEL_MAX_ENTRIES` (5, the wide panel's rows) and `RECORD_PAGE_TIMELINE_PANEL`. ### What the count is | Source | Counted as the caller's read | When named | | --- | --- | --- | | `discussion` | `sys_comment` where `thread_id` = `{object}:{id}` (`$count`, `$top=0`) | the panel draws the discussion | | `activity` | `sys_activity` for the record, `type` not in `RECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPES` | the panel draws activity | | `fieldChanges` | `sys_audit_log` for the record, `action` in `create`, `update`, `delete` | field history is on for the object | The total is their sum, less the activity `updated` rows when field changes are counted and there is at least one (the stream's own `mergeTimelineStream` rule). Each read goes through `findData` as the caller, behind the stream object's API exposure gate, like the parts #28682 added. A refused comment or activity read refuses the part. A refused `sys_audit_log` read counts 0, because the console draws no field change it cannot read. A source the boot does not register (`OBJECT_NOT_FOUND`) holds no entry. ### One definition for the dropped types (ruling: name which) **One shared definition.** `RECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPES` lives in the spec. The server's count excludes it (`$nin`), and the console's activity reading (`ui/packages/plugin-detail/src/renderers/recordActivityFeed.ts`) builds its "no entry" rows of `ACTIVITY_TYPE_TO_FEED_TYPE` from the same constant. `mergeTimelineStream` uses `RECORD_TIMELINE_UPDATE_ACTIVITY_TYPE`. A test pins it as well: the undrawn keys of `ACTIVITY_TYPE_TO_FEED_TYPE` equal the constant exactly (`RecordTimelineTotal-28737.test.tsx`), and the spec test pins the constants as builtin activity types. ### Console - The page asks `timeline` whenever it draws the 动态 count, which is the synthesized columns page, with the sources the panel draws. `activity` asks `RECORD_TIMELINE_PANEL_MAX_ENTRIES` (5) rows, or the 200-row window when the page opens on its 动态 tab (`?related=timeline`). - The count chip, 「查看全部 N 条」, the 动态 tab's count and the tab row's probe name the served total (`RecordTimelineTotalContext`). They still name nothing while the feed loads or when a read is refused. Without a served total (an authored page, a refused part, a data source without `getRecordPage`) each counts the rows it holds, as before. - Comments written on the page after it opened stay counted: the total swaps the payload's comment count for the comments the page holds now. - Opening the 动态 tab later reads the 200-row activity window once (`sys_activity`, `$top=200`, `$expand=actor_id`), and only when the payload asked the panel's rows and the record holds more activity rows than that. A record with none makes no extra read. ### Docs `content/docs/frontend/plugins/plugin-detail.mdx`, Console Integration: the request snippet now asks `activity: { top: 5 }` and `timeline`, and the prose says when the tab reads the window. The reference page `content/docs/references/api/record-page.mdx` and the other spec artifacts are regenerated. ## Tests - `packages/spec/src/api/record-page.test.ts` `[#28737]`: the request member is strict (every source required, booleans only, no extra key); the response is a count or a refusal; the constants are builtin activity types and leave `updated` out. - `packages/rest/src/record-page-routes.test.ts` `[#28737]` (8): the sum and the exact reads asked; only the named sources are read; zero; a refused comment or activity read refuses the part; a refused ledger read counts 0; not asked means not served; the exposure gate refuses without reading; the body stays strict. - `packages/qa/dogfood/test/record-page-timeline.dogfood.test.ts` (3, real HTTP + security stack, plugin-audit's real writers, org-bound boot): the served total equals the sum of the separate `$count` list reads asked as the same member, with the updates stepping aside; without field changes the updates stay; only the named sources are counted. Armed controls: the stores hold the stream (3 comments, ≥ 3 ledger rows, 2 updates) and the member reads each source. - `ui/packages/plugin-detail/src/__tests__/RecordTimelineTotal-28737.test.tsx`: the chip and 「查看全部」 show the served 341 while 5 rows are held; a served 0 shows 0; no count while loading or refused; control without a served count shows the held 6; the full stream and the probe report the served count; the shared-definition pin; the update steps aside. - `ui/packages/app-shell/src/views/RecordDetailView.timelineCount-28737.test.tsx` (the card's two tests and more): a record with more than 200 merged entries shows the served 341, the page request asks `activity: { top: 5 }` and `timeline`, and opening the 动态 tab reads the window once; a record with none shows 0 and after opening the tab only the page request went out; opening on `?related=timeline` asks 200 rows and makes no activity read; ablation without the served count shows the held 6. - Updated expectations, same behaviour otherwise: `RecordDetailView.recordOpenOneRequest-28470`, `recordOpenWaves-13010` and `recordPagePayload-27627` now expect `activity: { top: 5 }` plus `timeline` on the synthesized page. ## Acceptance notes - **Ruled sources only.** The count is comments + drawn activity + ledger writes, less the updates. Known places where the drawn stream can still differ from the count, all toward the count being the larger: the console draws a ledger row only when it has a change it can display (`collectAuditChanges`), and it reads the newest 50 ledger rows. The count names every ledger write the caller can read. - **The tab pages through what it holds.** The 动态 tab can page through the 200 activity rows and 50 ledger rows it holds while the count names the whole stream, as it did before this change for a record with more than 200 rows. - **A refused ledger read counts 0** by design: the console draws no field change it cannot read. Pinned in the rest unit test only, because on the dogfood boot the platform's default sets let a member read the ledger rows of records they read. - **Serial constraint.** Seat 1's #28483 (RecordDetailView guard states) is already on `main`, and this branch has merged it (clean merge, re-verified at the new head), so this PR lands second. - **Size.** 1366 changed lines (1281 + / 85 −), under 3000, so not Tier H. ## Four axes - **实际业务需求**:动态 面板只画 3 或 5 条,但每次打开记录都要取 200 行动态来算计数,这是 #28682 合约审查记下的实测偏差(should-fix ③-2)。打开任何有 动态 的记录都会走这条路径。 - **项目长远合理性**:计数是服务端对同一个流的读,契约先在 spec 里声明(来源表、拒绝规则、共享常量),实现兑现声明。没有在消费端截断或猜计数,也没有收窄已发布的 `RecordPageActivityRequestSchema`。 - **防 AI 写错**:被丢弃的类型只有一个定义,服务端和控制台都从 spec 常量取,另有测试钉住两边完全一致,以后加一个不画的类型不会只改一边。请求体保持 strict,三个来源都必须明确给出布尔值,拼错或漏写会得到 400。 - **创业阶段不扩散**:只加一个可选的计数部件,消费者就是记录页本身。没有新门禁,没有分页或按类型细分的计数,`RECORD_PAGE_MAX_ACTIVITY_ROWS` 不变。 ## Verification At the head after merging `main`: - `@objectstack/spec` `record-page.test.ts` 10/10; `@objectstack/rest` `record-page-routes.test.ts` 28/28; rest `typecheck` (incl. `check:test-typecheck`) green; spec `check:test-typecheck` green. - dogfood `record-page-timeline.dogfood.test.ts` 3/3. - `ui` plugin-detail, whole package: 361 files passed, 1 skipped; 3197 tests passed. - `ui` app-shell `RecordDetailView*` + the #28483 header test + the new plugin-detail test after the merge: 98 files, 610 tests passed. Before the merge, the 151 other ui test files that import `RecordDetailView`, `recordPagePayload` or `getRecordPage` passed (1397 tests). - `turbo run type-check` for `@object-ui/types`, `@object-ui/plugin-detail`, `@object-ui/app-shell`: 67/67. - `pnpm --filter @objectstack/spec check:generated`: all 14 artifacts current, against a dist this run built. - `node scripts/check-sdui-manifest.mjs`: intact. - `node scripts/pm/dispatch-gates.mjs --commands` sweep: see the os-dev-report on the card. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd _Generated by [Claude Code](https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0be247c commit c708de6

28 files changed

Lines changed: 1281 additions & 85 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/rest': minor
4+
---
5+
6+
feat(rest): the record page payload serves the record's 动态 count, so the activity part asks only the panel's rows (#28737)
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
`POST /api/v1/data/{object}/{id}/page` counts the record's 动态 stream on the server, so a record page no longer reads 200 activity rows to name a count.
12+
13+
- **`timeline: { discussion, activity, fieldChanges }`** (request) → **`timeline`** (response): `{ total }` — the number of entries the record's 动态 stream holds for the caller, from the sources named: the comment thread (`sys_comment`), the activity rows the stream draws (`sys_activity` without the types in `RECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPES`) and the ledger's field changes (`sys_audit_log` writes). With field changes counted, the activity's `updated` rows step aside for them, as the stream does. Each source is counted as the caller's own read, behind its object's exposure gate; a refused comment or activity read refuses the part (`RecordPageStreamRefusalSchema`), a refused ledger read counts no field change, and a source the boot does not register holds no entry.
14+
15+
`RECORD_PAGE_MAX_ACTIVITY_ROWS` stays 200 and `RecordPageActivityRequestSchema` keeps its bounds: the 动态 tab still asks the window it draws. New exports in `@objectstack/spec/api`: `RecordPageTimelineRequestSchema`, `RecordPageTimelineCountSchema`, `RECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPES`, `RECORD_TIMELINE_UPDATE_ACTIVITY_TYPE`, and their types. The console's activity reading maps exactly those undrawn types to no entry, so the server's count and the drawn stream share one definition.

‎content/docs/frontend/plugins/plugin-detail.mdx‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,12 @@ declare const items: FeedItem[];
103103
In the Console app, `RecordChatterPanel` is integrated with record detail views. A record
104104
page that draws its feed when it opens takes the discussion from the one request it makes
105105
on open, `POST /api/v1/data/{object}/{id}/page` (the [record page payload](/docs/references/api/record-page)).
106-
That request asks `discussion` (the record's comment thread and those comments' reactions)
107-
and `activity` (its newest activity rows) beside the record, and the page reads neither
108-
`sys_comment` nor `sys_activity` on its own:
106+
That request asks `discussion` (the record's comment thread and those comments' reactions),
107+
`activity` (its newest activity rows: the 动态 panel's five, or the 200 the 动态 tab draws when
108+
the page opens on it) and `timeline` (the count of the whole 动态 stream, which the panel's
109+
count chip and the tab's count name) beside the record, and the page reads neither
110+
`sys_comment` nor `sys_activity` on its own. Opening the 动态 tab later reads the activity
111+
window it draws only when the record holds more rows than the panel's:
109112

110113
```tsx
111114
import type { DataSource, FeedItem } from '@object-ui/types';
@@ -115,12 +118,16 @@ declare const objectName: string;
115118
declare const recordId: string;
116119
declare const dataSource: DataSource;
117120

118-
// The record page's one request; the discussion and the activity are two of its parts.
121+
// The record page's one request; the discussion, the activity and the 动态 count are three of its parts.
119122
const page = await dataSource.getRecordPage?.(objectName, recordId, {
120123
discussion: true,
121-
activity: { top: 200 },
124+
activity: { top: 5 },
125+
timeline: { discussion: true, activity: true, fieldChanges: true },
122126
});
123127

128+
// The count of the whole stream, not of the rows held.
129+
const total = page?.timeline && 'total' in page.timeline ? page.timeline.total : null;
130+
124131
// A part is its rows, or the refusal of the read it replaces.
125132
const discussion = page?.discussion;
126133
const comments = discussion && 'comments' in discussion ? discussion.comments : [];

‎content/docs/references/api/record-page.mdx‎

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Record Page schema — API Protocol reference
33
navTitle: Record Page
4-
description: "Record page payload — POST /api/v1/data/{object}/{id}/page. Reference for RecordPageActivityRequest and 16 more: every property with its type and default."
4+
description: "Record page payload — POST /api/v1/data/{object}/{id}/page. Reference for RecordPageActivityRequest and 18 more: every property with its type and default."
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -39,7 +39,10 @@ The three stream parts are each that read exactly as the table on
3939
`RecordPageActivityRequestSchema` spells it: the route composes the record's
4040
scope and the order, and the request names only whether a stream is wanted
4141
and how many rows. Paging further, posting, reacting and uploading keep
42-
their own routes. Object permissions, row-level security, sharing and
42+
their own routes. [#28737] The `timeline` part is the size of the record's
43+
动态 stream — the comments, the activity and the field changes it merges —
44+
counted by those reads as the caller (`RecordPageTimelineRequestSchema`), so
45+
a page draws the stream's count without holding every row of it. Object permissions, row-level security, sharing and
4346
field-level security therefore apply exactly as they apply to those reads.
4447
A part whose read refuses carries that refusal in place of an answer — a
4548
related list the caller may not read carries neither a count nor rows — and
@@ -54,8 +57,8 @@ no part discloses a record the caller could not open.
5457
## TypeScript Usage
5558

5659
```typescript
57-
import { RecordPageActivityRequestSchema, RecordPageApprovalsSchema, RecordPageAttachmentsRequestSchema, RecordPageDiscussionSchema, RecordPageDiscussionReactionsSchema, RecordPageExplainAnswerSchema, RecordPageExplainOperationSchema, RecordPagePartErrorSchema, RecordPagePartRefusalSchema, RecordPageRelatedAnswerSchema, RecordPageRelatedRefusalSchema, RecordPageRelatedRequestSchema, RecordPageRelatedRowsSchema, RecordPageRequestSchema, RecordPageResponseSchema, RecordPageStreamRefusalSchema, RecordPageStreamRowsSchema } from '@objectstack/spec/api';
58-
import type { RecordPageActivityRequest, RecordPageApprovals, RecordPageAttachmentsRequest, RecordPageDiscussion, RecordPageDiscussionReactions, RecordPageExplainAnswer, RecordPageExplainOperation, RecordPagePartError, RecordPagePartRefusal, RecordPageRelatedAnswer, RecordPageRelatedRefusal, RecordPageRelatedRequest, RecordPageRelatedRows, RecordPageRequest, RecordPageResponse, RecordPageStreamRefusal, RecordPageStreamRows } from '@objectstack/spec/api';
60+
import { RecordPageActivityRequestSchema, RecordPageApprovalsSchema, RecordPageAttachmentsRequestSchema, RecordPageDiscussionSchema, RecordPageDiscussionReactionsSchema, RecordPageExplainAnswerSchema, RecordPageExplainOperationSchema, RecordPagePartErrorSchema, RecordPagePartRefusalSchema, RecordPageRelatedAnswerSchema, RecordPageRelatedRefusalSchema, RecordPageRelatedRequestSchema, RecordPageRelatedRowsSchema, RecordPageRequestSchema, RecordPageResponseSchema, RecordPageStreamRefusalSchema, RecordPageStreamRowsSchema, RecordPageTimelineCountSchema, RecordPageTimelineRequestSchema } from '@objectstack/spec/api';
61+
import type { RecordPageActivityRequest, RecordPageApprovals, RecordPageAttachmentsRequest, RecordPageDiscussion, RecordPageDiscussionReactions, RecordPageExplainAnswer, RecordPageExplainOperation, RecordPagePartError, RecordPagePartRefusal, RecordPageRelatedAnswer, RecordPageRelatedRefusal, RecordPageRelatedRequest, RecordPageRelatedRows, RecordPageRequest, RecordPageResponse, RecordPageStreamRefusal, RecordPageStreamRows, RecordPageTimelineCount, RecordPageTimelineRequest } from '@objectstack/spec/api';
5962

6063
// Validate data
6164
const result = RecordPageActivityRequestSchema.parse(data);
@@ -397,6 +400,7 @@ This schema accepts one of the following structures:
397400
| **discussion** | `boolean` | optional | Return the record's discussion: its comment thread and those comments' reactions (`RecordPageDiscussionSchema`). |
398401
| **activity** | `{ top: integer }` | optional | Return the record's newest activity rows and their count (`RecordPageStreamRowsSchema`). |
399402
| **attachments** | `{ top: integer }` | optional | Return the record's newest attachments and their count (`RecordPageStreamRowsSchema`). |
403+
| **timeline** | `{ discussion: boolean; activity: boolean; fieldChanges: boolean }` | optional | Return the size of the record's timeline stream over the sources named (`RecordPageTimelineCountSchema`). |
400404

401405
### Nested Shape: `RecordPageRequest.related[number]`
402406

@@ -421,6 +425,14 @@ This schema accepts one of the following structures:
421425
| :--- | :--- | :--- | :--- |
422426
| **top** | `integer` | ✅ | How many of the record's newest attachments to return with their count; 0 returns the count alone. |
423427

428+
### Nested Shape: `RecordPageRequest.timeline`
429+
430+
| Property | Type | Required | Description |
431+
| :--- | :--- | :--- | :--- |
432+
| **discussion** | `boolean` | ✅ | Count the record's comment thread: every comment and reply. |
433+
| **activity** | `boolean` | ✅ | Count the record's activity rows the stream draws an entry for (not `RECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPES`). |
434+
| **fieldChanges** | `boolean` | ✅ | Count the record's field changes (its `sys_audit_log` writes); with any, its `updated` activity rows step aside. |
435+
424436

425437
---
426438

@@ -440,6 +452,7 @@ This schema accepts one of the following structures:
440452
| **discussion** | `{ comments: Record<string, any>[]; reactions: object \| object } \| { error: object }` | optional | The discussion part, when the request asked for it: the thread and its reactions, or the refusal of the comment read. |
441453
| **activity** | `{ total: integer; records: Record<string, any>[] } \| { error: object }` | optional | The activity part, when the request asked for it: the newest rows and their count, or the refusal of their read. |
442454
| **attachments** | `{ total: integer; records: Record<string, any>[] } \| { error: object }` | optional | The attachments part, when the request asked for it: the newest rows and their count, or the refusal of their read. |
455+
| **timeline** | `{ total: integer } \| { error: object }` | optional | The timeline part, when the request asked for it: the stream's size, or the refusal of a comment or activity read. |
443456

444457
### Nested Shape: `RecordPageResponse.related[number][option 1]`
445458

@@ -526,6 +539,18 @@ This schema accepts one of the following structures:
526539
| :--- | :--- | :--- | :--- |
527540
| **error** | `{ code: string; status: integer; message: string }` | ✅ | The refusal the replaced read answers, in place of this part's answer. |
528541

542+
### Nested Shape: `RecordPageResponse.timeline[option 1]`
543+
544+
| Property | Type | Required | Description |
545+
| :--- | :--- | :--- | :--- |
546+
| **total** | `integer` | ✅ | How many entries the record's timeline stream holds for this caller, over the sources the request named. |
547+
548+
### Nested Shape: `RecordPageResponse.timeline[option 2]`
549+
550+
| Property | Type | Required | Description |
551+
| :--- | :--- | :--- | :--- |
552+
| **error** | `{ code: string; status: integer; message: string }` | ✅ | The refusal the replaced read answers, in place of this part's answer. |
553+
529554

530555
---
531556

@@ -560,3 +585,27 @@ This schema accepts one of the following structures:
560585

561586
---
562587

588+
## RecordPageTimelineCount
589+
590+
### Properties
591+
592+
| Property | Type | Required | Description |
593+
| :--- | :--- | :--- | :--- |
594+
| **total** | `integer` | ✅ | How many entries the record's timeline stream holds for this caller, over the sources the request named. |
595+
596+
597+
---
598+
599+
## RecordPageTimelineRequest
600+
601+
### Properties
602+
603+
| Property | Type | Required | Description |
604+
| :--- | :--- | :--- | :--- |
605+
| **discussion** | `boolean` | ✅ | Count the record's comment thread: every comment and reply. |
606+
| **activity** | `boolean` | ✅ | Count the record's activity rows the stream draws an entry for (not `RECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPES`). |
607+
| **fieldChanges** | `boolean` | ✅ | Count the record's field changes (its `sys_audit_log` writes); with any, its `updated` activity rows step aside. |
608+
609+
610+
---
611+

‎content/docs/references/index.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Protocol reference — every schema by module
33
navTitle: Protocol Reference
4-
description: Every schema published by @objectstack/spec — 1594 schemas across 14 protocol modules
4+
description: Every schema published by @objectstack/spec — 1596 schemas across 14 protocol modules
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -21,7 +21,7 @@ counts are sums of the rows they head. Regenerate with
2121
| Module | Pages | Schemas | Description |
2222
| :--- | ---: | ---: | :--- |
2323
| [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. |
24-
| [API Protocol](/docs/references/api) | 34 | 469 | REST contracts, endpoints, routing, realtime, batch, discovery. |
24+
| [API Protocol](/docs/references/api) | 34 | 471 | REST contracts, endpoints, routing, realtime, batch, discovery. |
2525
| [Automation Protocol](/docs/references/automation) | 15 | 78 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
2626
| [Data Protocol](/docs/references/data) | 31 | 174 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
2727
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
@@ -34,7 +34,7 @@ counts are sums of the rows they head. Regenerate with
3434
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3535
| [System Protocol](/docs/references/system) | 37 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
3636
| [UI Protocol](/docs/references/ui) | 16 | 175 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
37-
| **Total** | **205** | **1594** | 14 protocol modules |
37+
| **Total** | **205** | **1596** | 14 protocol modules |
3838

3939
---
4040

@@ -63,7 +63,7 @@ Agents, tools, skills, RAG and knowledge sources, model registry, conversations.
6363

6464
## API Protocol
6565

66-
**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **34 pages, 469 schemas**
66+
**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **34 pages, 471 schemas**
6767

6868
REST contracts, endpoints, routing, realtime, batch, discovery.
6969

@@ -96,7 +96,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery.
9696
| [`query-adapter.zod.ts`](/docs/references/api/query-adapter) | `ODataQueryAdapter`, `OperatorMapping`, `QueryAdapterConfig`, `QueryAdapterTarget`, `RestQueryAdapter` |
9797
| [`realtime.zod.ts`](/docs/references/api/realtime) | `RealtimeConfig`, `RealtimeEvent`, `RealtimeEventType`, `RealtimePresence`, `Subscription`, `SubscriptionEvent`, `TransportProtocol` |
9898
| [`realtime-shared.zod.ts`](/docs/references/api/realtime-shared) | `BasePresence`, `PresenceStatus`, `RealtimeRecordAction` |
99-
| [`record-page.zod.ts`](/docs/references/api/record-page) | `RecordPageActivityRequest`, `RecordPageApprovals`, `RecordPageAttachmentsRequest`, `RecordPageDiscussion`, `RecordPageDiscussionReactions`, `RecordPageExplainAnswer`, `RecordPageExplainOperation`, `RecordPagePartError`, `RecordPagePartRefusal`, `RecordPageRelatedAnswer`, `RecordPageRelatedRefusal`, `RecordPageRelatedRequest`, `RecordPageRelatedRows`, `RecordPageRequest`, `RecordPageResponse`, `RecordPageStreamRefusal`, `RecordPageStreamRows` |
99+
| [`record-page.zod.ts`](/docs/references/api/record-page) | `RecordPageActivityRequest`, `RecordPageApprovals`, `RecordPageAttachmentsRequest`, `RecordPageDiscussion`, `RecordPageDiscussionReactions`, `RecordPageExplainAnswer`, `RecordPageExplainOperation`, `RecordPagePartError`, `RecordPagePartRefusal`, `RecordPageRelatedAnswer`, `RecordPageRelatedRefusal`, `RecordPageRelatedRequest`, `RecordPageRelatedRows`, `RecordPageRequest`, `RecordPageResponse`, `RecordPageStreamRefusal`, `RecordPageStreamRows`, `RecordPageTimelineCount`, `RecordPageTimelineRequest` |
100100
| [`rest-server.zod.ts`](/docs/references/api/rest-server) | `BatchEndpointsConfig`, `CrudEndpointsConfig`, `CrudOperation`, `EndpointRegistry`, `GeneratedEndpoint`, `MetadataEndpointsConfig`, `RestApiConfig`, `RestServerConfig`, `RouteGenerationConfig` |
101101
| [`router.zod.ts`](/docs/references/api/router) | `ConflictResolutionStrategy`, `HttpMethod`, `RouteCategory`, `RouteDefinition`, `RouterConfig` |
102102
| [`sortability.zod.ts`](/docs/references/api/sortability) | `FieldSortability`, `ObjectSortability` |

‎docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ hand-patch a number here** — fix the code or the verdict and regenerate.
1919

2020
| Dir | Sites |
2121
|---|---|
22-
| `api/` | 484 |
22+
| `api/` | 486 |

0 commit comments

Comments
 (0)