Skip to content
Merged
15 changes: 15 additions & 0 deletions .changeset/28737-record-page-timeline-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@objectstack/spec': minor
'@objectstack/rest': minor
---

feat(rest): the record page payload serves the record's 动态 count, so the activity part asks only the panel's rows (#28737)

Clause-②: yes (widening)
Metadata-protocol: added

`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.

- **`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.

`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.
17 changes: 12 additions & 5 deletions content/docs/frontend/plugins/plugin-detail.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,12 @@ declare const items: FeedItem[];
In the Console app, `RecordChatterPanel` is integrated with record detail views. A record
page that draws its feed when it opens takes the discussion from the one request it makes
on open, `POST /api/v1/data/{object}/{id}/page` (the [record page payload](/docs/references/api/record-page)).
That request asks `discussion` (the record's comment thread and those comments' reactions)
and `activity` (its newest activity rows) beside the record, and the page reads neither
`sys_comment` nor `sys_activity` on its own:
That request asks `discussion` (the record's comment thread and those comments' reactions),
`activity` (its newest activity rows: the 动态 panel's five, or the 200 the 动态 tab draws when
the page opens on it) and `timeline` (the count of the whole 动态 stream, which the panel's
count chip and the tab's count name) beside the record, and the page reads neither
`sys_comment` nor `sys_activity` on its own. Opening the 动态 tab later reads the activity
window it draws only when the record holds more rows than the panel's:

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

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

// The count of the whole stream, not of the rows held.
const total = page?.timeline && 'total' in page.timeline ? page.timeline.total : null;

// A part is its rows, or the refusal of the read it replaces.
const discussion = page?.discussion;
const comments = discussion && 'comments' in discussion ? discussion.comments : [];
Expand Down
57 changes: 53 additions & 4 deletions content/docs/references/api/record-page.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Record Page schema — API Protocol reference
navTitle: Record Page
description: "Record page payload — POST /api/v1/data/{object}/{id}/page. Reference for RecordPageActivityRequest and 16 more: every property with its type and default."
description: "Record page payload — POST /api/v1/data/{object}/{id}/page. Reference for RecordPageActivityRequest and 18 more: every property with its type and default."
---

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

```typescript
import { RecordPageActivityRequestSchema, RecordPageApprovalsSchema, RecordPageAttachmentsRequestSchema, RecordPageDiscussionSchema, RecordPageDiscussionReactionsSchema, RecordPageExplainAnswerSchema, RecordPageExplainOperationSchema, RecordPagePartErrorSchema, RecordPagePartRefusalSchema, RecordPageRelatedAnswerSchema, RecordPageRelatedRefusalSchema, RecordPageRelatedRequestSchema, RecordPageRelatedRowsSchema, RecordPageRequestSchema, RecordPageResponseSchema, RecordPageStreamRefusalSchema, RecordPageStreamRowsSchema } from '@objectstack/spec/api';
import type { RecordPageActivityRequest, RecordPageApprovals, RecordPageAttachmentsRequest, RecordPageDiscussion, RecordPageDiscussionReactions, RecordPageExplainAnswer, RecordPageExplainOperation, RecordPagePartError, RecordPagePartRefusal, RecordPageRelatedAnswer, RecordPageRelatedRefusal, RecordPageRelatedRequest, RecordPageRelatedRows, RecordPageRequest, RecordPageResponse, RecordPageStreamRefusal, RecordPageStreamRows } from '@objectstack/spec/api';
import { RecordPageActivityRequestSchema, RecordPageApprovalsSchema, RecordPageAttachmentsRequestSchema, RecordPageDiscussionSchema, RecordPageDiscussionReactionsSchema, RecordPageExplainAnswerSchema, RecordPageExplainOperationSchema, RecordPagePartErrorSchema, RecordPagePartRefusalSchema, RecordPageRelatedAnswerSchema, RecordPageRelatedRefusalSchema, RecordPageRelatedRequestSchema, RecordPageRelatedRowsSchema, RecordPageRequestSchema, RecordPageResponseSchema, RecordPageStreamRefusalSchema, RecordPageStreamRowsSchema, RecordPageTimelineCountSchema, RecordPageTimelineRequestSchema } from '@objectstack/spec/api';
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';

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

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

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

### Nested Shape: `RecordPageRequest.timeline`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **discussion** | `boolean` | ✅ | Count the record's comment thread: every comment and reply. |
| **activity** | `boolean` | ✅ | Count the record's activity rows the stream draws an entry for (not `RECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPES`). |
| **fieldChanges** | `boolean` | ✅ | Count the record's field changes (its `sys_audit_log` writes); with any, its `updated` activity rows step aside. |


---

Expand All @@ -440,6 +452,7 @@ This schema accepts one of the following structures:
| **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. |
| **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. |
| **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. |
| **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. |

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

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

### Nested Shape: `RecordPageResponse.timeline[option 1]`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **total** | `integer` | ✅ | How many entries the record's timeline stream holds for this caller, over the sources the request named. |

### Nested Shape: `RecordPageResponse.timeline[option 2]`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **error** | `{ code: string; status: integer; message: string }` | ✅ | The refusal the replaced read answers, in place of this part's answer. |


---

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

---

## RecordPageTimelineCount

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **total** | `integer` | ✅ | How many entries the record's timeline stream holds for this caller, over the sources the request named. |


---

## RecordPageTimelineRequest

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **discussion** | `boolean` | ✅ | Count the record's comment thread: every comment and reply. |
| **activity** | `boolean` | ✅ | Count the record's activity rows the stream draws an entry for (not `RECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPES`). |
| **fieldChanges** | `boolean` | ✅ | Count the record's field changes (its `sys_audit_log` writes); with any, its `updated` activity rows step aside. |


---

10 changes: 5 additions & 5 deletions content/docs/references/index.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Protocol reference — every schema by module
navTitle: Protocol Reference
description: Every schema published by @objectstack/spec — 1594 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1596 schemas across 14 protocol modules
---

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

---

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

## API Protocol

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

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

Expand Down Expand Up @@ -96,7 +96,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery.
| [`query-adapter.zod.ts`](/docs/references/api/query-adapter) | `ODataQueryAdapter`, `OperatorMapping`, `QueryAdapterConfig`, `QueryAdapterTarget`, `RestQueryAdapter` |
| [`realtime.zod.ts`](/docs/references/api/realtime) | `RealtimeConfig`, `RealtimeEvent`, `RealtimeEventType`, `RealtimePresence`, `Subscription`, `SubscriptionEvent`, `TransportProtocol` |
| [`realtime-shared.zod.ts`](/docs/references/api/realtime-shared) | `BasePresence`, `PresenceStatus`, `RealtimeRecordAction` |
| [`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` |
| [`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` |
| [`rest-server.zod.ts`](/docs/references/api/rest-server) | `BatchEndpointsConfig`, `CrudEndpointsConfig`, `CrudOperation`, `EndpointRegistry`, `GeneratedEndpoint`, `MetadataEndpointsConfig`, `RestApiConfig`, `RestServerConfig`, `RouteGenerationConfig` |
| [`router.zod.ts`](/docs/references/api/router) | `ConflictResolutionStrategy`, `HttpMethod`, `RouteCategory`, `RouteDefinition`, `RouterConfig` |
| [`sortability.zod.ts`](/docs/references/api/sortability) | `FieldSortability`, `ObjectSortability` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ hand-patch a number here** — fix the code or the verdict and regenerate.

| Dir | Sites |
|---|---|
| `api/` | 484 |
| `api/` | 486 |
Loading
Loading