Repository navigation
feat(rest,console): the record page payload serves the 动态 count, so the activity part asks only the panel's rows - #28991
Conversation
… count Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd
…members Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd
…ks only the panel's activity rows Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd
… changeset Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd
…ine part; the record-open tests and the plugin-detail docs ask the panel's rows Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd
…-6jkgb1 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd
📓 Docs Drift CheckThis PR changes 2 package(s): 22 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 171 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 81f65b36f6ecca3c12f079295cacf6d0cf6b443c && git checkout 81f65b36f6ecca3c12f079295cacf6d0cf6b443c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 00d0668d380046198dfc9151858b88df463bfd9b af2b6b3f339361b216bac0fefbb17330f9c41f4d && git checkout -B drift-repro 00d0668d380046198dfc9151858b88df463bfd9b && git merge --no-ff af2b6b3f339361b216bac0fefbb17330f9c41f4d
node scripts/docs-audit/affected-docs.mjs --json 00d0668d380046198dfc9151858b88df463bfd9b
|
|
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw Contract-review-claim: session_01QsR2jV5sBSzCYNEDgpYLJw · head af2b6b3 · 2026-11-02T06:02Z Face: schema ( Generated by Claude Code |
|
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw Contract reviewServed-tier: Inputs: card #28737 (body and its 4 comments, the os-dev-report 6570130917 included), PR #28991 body and file list, the net diff against ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
The contract holds. The timeline table defines the count by rows, source by source, with the one subtraction
Implemented-by: VERDICT: PASS Should-fix ④-1 is prose in the schema file; if the seat takes it on this branch before landing, the head moves on a prose-only hunk and carries under Generated by Claude Code |
|
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y 席位核对 · ACCEPT · 2026-11-02T06:16ZHead-sha:
Generated by Claude Code |
Requested by Steedos · project thread
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.mdteaches the page's layout, which this does not change. The hand-writtencontent/docs/frontend/plugins/plugin-detail.mdxConsole Integration section is updated.维护者速读
POST /data/:object/:id/page新增可选部件timeline,由服务端统计这条记录完整 动态 流的条数(评论 + 动态 + 字段变更,去掉面板不画的类型,有字段变更时updated动态让位)。控制台用它显示 动态 计数徽标、「查看全部 N 条」和 动态 标签页计数,动态 部件只请求面板要画的 5 行,不再一次取 200 行。@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。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']: thesys_activitytypes 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) andXParsed(output) type.RECORD_PAGE_MAX_ACTIVITY_ROWSstays 200 andRecordPageActivityRequestSchemakeeps 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'sRecordPageRequest, so it gainstimelinewith no separate declaration.@object-ui/plugin-detail: exportsRecordTimelineTotalContext(the host's count of the whole stream,number | null),RECORD_TIMELINE_PANEL_MAX_ENTRIES(5, the wide panel's rows) andRECORD_PAGE_TIMELINE_PANEL.What the count is
discussionsys_commentwherethread_id={object}:{id}($count,$top=0)activitysys_activityfor the record,typenot inRECORD_TIMELINE_UNDRAWN_ACTIVITY_TYPESfieldChangessys_audit_logfor the record,actionincreate,update,deleteThe total is their sum, less the activity
updatedrows when field changes are counted and there is at least one (the stream's ownmergeTimelineStreamrule). Each read goes throughfindDataas 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 refusedsys_audit_logread 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_TYPESlives 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 ofACTIVITY_TYPE_TO_FEED_TYPEfrom the same constant.mergeTimelineStreamusesRECORD_TIMELINE_UPDATE_ACTIVITY_TYPE. A test pins it as well: the undrawn keys ofACTIVITY_TYPE_TO_FEED_TYPEequal the constant exactly (RecordTimelineTotal-28737.test.tsx), and the spec test pins the constants as builtin activity types.Console
timelinewhenever it draws the 动态 count, which is the synthesized columns page, with the sources the panel draws.activityasksRECORD_TIMELINE_PANEL_MAX_ENTRIES(5) rows, or the 200-row window when the page opens on its 动态 tab (?related=timeline).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 withoutgetRecordPage) each counts the rows it holds, as before.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 asksactivity: { top: 5 }andtimeline, and the prose says when the tab reads the window. The reference pagecontent/docs/references/api/record-page.mdxand 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 leaveupdatedout.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$countlist 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 asksactivity: { top: 5 }andtimeline, 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=timelineasks 200 rows and makes no activity read; ablation without the served count shows the held 6.RecordDetailView.recordOpenOneRequest-28470,recordOpenWaves-13010andrecordPagePayload-27627now expectactivity: { top: 5 }plustimelineon the synthesized page.Acceptance notes
collectAuditChanges), and it reads the newest 50 ledger rows. The count names every ledger write the caller can read.main, and this branch has merged it (clean merge, re-verified at the new head), so this PR lands second.Four axes
RecordPageActivityRequestSchema。RECORD_PAGE_MAX_ACTIVITY_ROWS不变。Verification
At the head after merging
main:@objectstack/specrecord-page.test.ts10/10;@objectstack/restrecord-page-routes.test.ts28/28; resttypecheck(incl.check:test-typecheck) green; speccheck:test-typecheckgreen.record-page-timeline.dogfood.test.ts3/3.uiplugin-detail, whole package: 361 files passed, 1 skipped; 3197 tests passed.uiapp-shellRecordDetailView*+ the console phone record page: two bars stacked (app bar + record top bar) and the app bar repeats the record name — where do the app menu, search, notifications and account go? #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 importRecordDetailView,recordPagePayloadorgetRecordPagepassed (1397 tests).turbo run type-checkfor@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 --commandssweep: see the os-dev-report on the card.🤖 Generated with Claude Code
https://claude.ai/code/session_01QsR2jSrQVdYaC3GSLbQCFd
Generated by Claude Code