Skip to content

Record page open: the discussion, the activity feed, the attachment count and the related rows' reference names arrive in the record's page payload, so one open is one request plus the shell's own reads #28470

Closed

Description

@objectstack-fleet

epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

Filed by the record-page epic seat on the maintainer's instruction. The seat walked the v4 record page on main and counted the requests one record open makes (2026-10-31). The maintainer answered 「两条都立:…和打开记录的请求合并进 /page,都开在 domain:ui」 (2026-11-01).

This is part of the record-page epic #26676. It continues #27627, which is closed: #27627 put the record, the related counts and first rows, field access, the approval lock and explain into POST /data/:object/:id/page. Its Acceptance 1 asked that a record open issue "exactly one page-payload request". What is still outside the payload is listed below.

Measured

Dev mode on upstream main 5da3a8ce31, with the console proxied to HotCRM metadata run by main's CLI. Opening contact John Smith from the list into the drawer made about 22 API requests:

  1. POST /api/v1/data/crm_contact/:id/page twice, for the same id, back to back.
  2. The discussion and the feed, one read each. The /page answer arrives first, and then these start:
    • GET /data/sys_comment?sort=created_at&filter=… (RecordDetailView.tsx around line 2869);
    • GET /data/sys_activity?populate=actor_id&top=200&sort=timestamp&filter=… (around line 2978, and renderers/record-activity.tsx:225);
    • GET /data/sys_attachment?… (RecordAttachmentsPanel.tsx:267).
  3. GET /data/sys_user?top=50&select=id,name,email,image: the mention or assignee candidates, read when the record opens, before anyone types.
  4. One GET /data/OBJECT?top=1&filter=… per referenced object. On the contact these were crm_event, crm_contact, crm_account, crm_campaign, sys_user and sys_organization. They look like the reference-name lookups for the related rows the payload returned.
    • The same shape on examples/app-crm, opening an account: sys_organization, sys_user, crm_contact, crm_account, crm_opportunity, each ?top=1&filter=….
  5. App-level re-reads on the same navigation: settings, i18n, meta/page, meta/dashboard, approvals and ai/agents.

Locally each stage costs tens of milliseconds. On the cloud reading in #27627, each lone call cost 0.5–1.4 s, and calls slowed to 5–8 s when fifteen ran at once. Every follow-on stage after /page is one more round trip on that path.

Target

The claiming seat designs the shape and records it in packages/spec/src/api/record-page.zod.ts, beside RecordPageRequestSchema / RecordPageResponseSchema.

  1. The record's own streams ride in the payload.
  2. The related rows carry their reference names.
    • The payload's related rows answer each reference column the list draws with its display name, as the record's own lookups already do.
    • The per-object ?top=1 name probes after the payload then retire. Identify their caller first and name it in the PR.
  3. Mention and assignee candidates load when needed. The sys_user?top=50 read moves to the first focus of the composer, or of whatever control needs it. It is not part of the payload.
  4. /page is fetched once per open.
  5. App-level re-reads are out of scope (item 5 of Measured).
  6. Retire, don't keep two paths. Once the payload carries a part, its separate read on open is deleted. There is no fallback read when the part is missing (startup posture). A refused part draws the refusal the panel draws today.

Clause-②: yes (the payload contract widens). Metadata-protocol: none.

Why (four axes)

File surface

  • Producer:
    • packages/spec/src/api/record-page.zod.ts and its test;
    • packages/rest/src/record-page-routes.ts and its test;
    • a changeset.
  • Consumer:
    • ui/packages/data-objectstack/src/index.ts (recordPage);
    • ui/packages/app-shell/src/views/RecordDetailView.tsx (the comment and activity reads);
    • ui/packages/app-shell/src/views/RecordAttachmentsPanel.tsx;
    • ui/packages/plugin-detail/src/renderers/record-activity.tsx, recordBinding.tsx and RelatedCardStack.tsx;
    • the caller of the ?top=1 probes, once identified;
    • tests beside, and a changeset.
  • Routing: the producer half sits outside domain:ui. The maintainer routed this card to domain:ui, so the claim declares both ends.

Acceptance

  1. Contract tests for the widened payload:
    • each new part answers the read it replaces for the same caller;
    • a refused part is a per-part refusal, not a whole-answer failure;
    • the body stays strict.
  2. A console test that a record open issues exactly one /page request and zero sys_comment, sys_activity, sys_attachment, sys_user?top=50 and ?top=1 name reads, with an ablation.
  3. The PR body has before and after request lists for HotCRM contact John Smith on a production build: count and URLs, as in Measured.
  4. The epic seat checks the record page in the console preview before ACCEPT: the feed, the discussion, the attachment count and the related names all draw from the payload.

Activity

  1. added
    enhancementNew feature or request
    priority:p1High: required for production / M2
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    on Oct 31, 2026
  2. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p1 · domain:ui · agreed · 2026-10-31T20:55Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

    Queued · 2026-11-01T10:23Z · pm:queue added. The maintainer, in the epic seat's session, on #28508 and #28470 together: 「#28508 和 #28470 都挂 pm:queue,交给车队派发」. The epic seat hands dispatch of this card to the fleet; the domain:ui lane claims it.

  4. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    Claim: PM loop round 4 · 2026-11-01T10:53Z
    Session: session_01QsR2jSSzaZAuzM1JMwdqpC (card thread, os-dev); dispatched by seat session session_01QsR2jR7ZFkpqmm4U8S1f9y
    Account: zhuangjianguo
    Branch: claude/project-thread-iz2uqj (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-28470
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: packages/spec/src/api/record-page.zod.ts (beside RecordPageRequestSchema / RecordPageResponseSchema), the runtime POST /data/:object/:id/page route and its builder, the record page's readers in ui/packages/app-shell (RecordDetailView.tsx, RecordAttachmentsPanel.tsx, the activity renderer) and the reference-name lookup caller, their tests and a changeset (stop on breach; explain in the report)
    Container & model: L, mode:thread, model: default tier build (dispatch-gates --tier at 6e11a929: no path-derived mandate; Clause-② SUSPECT on packages/spec/src/** → review at CONTRACT_REVIEW_TIER), effort: xhigh
    Clause-②: yes (widening: new members of the published RecordPageRequestSchema / RecordPageResponseSchema and of POST /api/v1/data/:object/:id/page's answer; owes a CONTRACT_REVIEW_TIER review, which under the maintainer's no-Fable rule waits for the director; the PR stays draft until it is recorded)
    Metadata-protocol: none
    Responsibility: one record open issues /page plus discussion, activity, attachment, mention-candidate and per-object reference-name reads | /page already carries the record, related counts and first rows, field access, lock and explain (#27627) | every user opening a record, most on the cloud path where each round trip costs 0.5–1.4 s
    Thread-read: 6556685987
    Premise: holds (at origin/main 6e11a929 the record page reads sys_comment, sys_activity and sys_attachment after /page answers; no open PR touches record-page.zod.ts, RecordDetailView.tsx or the attachment/activity readers)
    Serial constraints cleared: none open; #28619 (#28546, landed 8a078da0) changed RecordDetailView's record-page plan for sys_approval_request, and that object keeps asking no page payload; #28538's partitionRecordTabsByRecords keeps receiving the related-panel counts

    Scope notes from the card: count the doubled POST …/page on a built Console first; only a doubling that survives the production build is in scope.


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    Claim amended · 2026-11-01T11:41Z · file surface widened; it supersedes the File surface line of this seat's claim 6556969643. Branch, session and declaration lines are unchanged (Clause-②: yes (widening), Metadata-protocol: none).

    File surface (added, each needed by the card's own targets per the dev's measurement):

    • ui/packages/types/src/data.ts: RecordPagePayload gains the discussion, activity and attachment parts, the UI mirror of the widened response. This is a published type, already inside the claim's Clause-② widening.
    • ui/packages/react/src/context/DiscussionContext.tsx: onMentionSuggestionsWanted, so the sys_user?top=50 read waits for the composer's first focus (target 3).
    • ui/packages/plugin-detail/src/{RichTextCommentInput,RecordActivityTimeline,RecordChatterPanel}.tsx and renderers/record-chatter.tsx: thread that hook to the textarea's focus.
    • ui/packages/plugin-detail/src/RelatedList.tsx: the ?top=1 reference-name fallback, the caller of target 4.
    • ui/packages/app-shell/src/views/recordPagePayload.ts: the part-seeding helper beside RecordDetailView.
    • packages/qa/dogfood/**: a new record-page-streams dogfood plus one case in record-page.dogfood.

    Measured on a production-built Console: one POST /page per record open. The two other posts are the prev/next prefetch (#27056 / #27545), for different ids, by design. So the doubling the card names is dev-only React.StrictMode replay and is out of scope.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jSSzaZAuzM1JMwdqpC
    os-dev-report

    Reported 2026-11-01T13:22Z · draft PR #28682 at head 167492e.

    {
      "issue": 28470,
      "status": "done",
      "branch": "claude/project-thread-iz2uqj",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/28682",
      "head": "167492ed",
      "session": "session_01QsR2jSSzaZAuzM1JMwdqpC",
      "premise_still_valid": true,
      "measured": "Production-built Console over examples/app-crm (pnpm build && pnpm console:build, dev:crm --fresh --ui --no-watch), Playwright as admin, every /api/ request from the open until idle + 3 s. Before (main 6e11a929): contact drawer 13, account drawer 13, contact record route 37. After (167492ed): 4, 5 and 28. Gone on every open: sys_user?top=50, sys_comment, sys_activity, and the five ?top=1 name probes (sys_organization, sys_user, crm_contact = the parent FK, crm_account, crm_opportunity); on the route also meta/object/sys_organization, the schema read that fed the sys_organization probe. Composer focus: exactly one sys_user?top=50, at the focus. /page goes out once per record; the other drawer /page posts are the prev/next prefetch (item 4 changes nothing, out of scope per the amended claim). The ?top=1 caller is RelatedList's reference-name fallback. Main merged at 3944d2e1 (13 commits; RecordDetailView.tsx changed on main in an unrelated hunk, no parallel fix).",
      "summary": "POST /data/:object/:id/page gains request members related[].expand, discussion, activity {top 1..200}, attachments {top 0..100} and response members discussion {comments, reactions | refusal} | refusal, activity {total, records} | refusal, attachments {total, records} | refusal. Each part is the read it replaces, as the same caller, behind the stream object's exposure gate, refused on its own; the body stays strict. The console asks discussion/activity when a feed block is on screen at open and attachments when the page draws them; the feed, the Attachments panel and the tab badge take the parts with no fallback read. Stream asks ride the plan's payload without keying it, so an assigned page landing after a synthesized one asks nothing again. Related lists ask their rows' lookups expanded and RelatedList's fallback skips the parent FK. The mention roster loads on the composer's first focus; a reply or edit with an @ before then reads it at the write so mentions still resolve. @object-ui/types RecordPagePayload gains discussion/activity/attachments; all members are listed together in the PR body for the contract review.",
      "tests": "New: spec record-page.test.ts 2 describes (7 in file); rest record-page-routes.test.ts 7 new (20); dogfood record-page-streams 4 (member parity with GET reads byte for byte, private record whole refusal, attachments 3 own / 2 secret) + record-page.dogfood expand case (8 across both, against the rebuilt rest dist); console RecordDetailView.recordOpenOneRequest-28470 5 (one /page, zero stream/mention/?top=1 reads, composer-focus read, refused part, 2 ablations; parent-FK skip ablation checked red then restored). Updated: recordPagePayload-27627, recordOpenWaves-13010, withheldMentions-28218. Regression: 330 ui test files importing a changed module under os-verify-lock, all green after updates (two suites that failed to load under load reran green: 63). Spec test 782 files / 23126 passed, test:repo 87 / 1448 passed. Type-check types, react, plugin-detail, app-shell (incl. test configs), rest (incl. test typecheck), dogfood: 0. ESLint on all touched files: 0 errors. At the merged head: 5 record-open ui files 42, route suite 20, spec record-page 7.",
      "gates": "dispatch-gates --commands: 108 derived, 108 run, all exit 0 (check-plugin-teardown-shape --self-test after git fetch --depth=1 of its pinned fixture; ui scripts/ rerun alone after two 15 s timeouts under a concurrent spec suite: 169 files passed). The 9 that finished before the mid-sweep rest commit reran at the final head: 0. check:generated current after the merge regen; check-regen-pending clear; check-sdui-manifest 0. PR-body gates on the final body: check-closing-keyword-parity 0, check-partof-closing-keyword 0, check-adr-0087-registration 0, check-empty-changeset 0, check-changeset-no-major 1 (see deviations). CI check-runs on #28682: not yet reported at report time.",
      "dogfood_battery": "n/a: no ! in the title and no (narrowing) arm; the two record page dogfood suites ran (8 passed).",
      "line_budget": "31 files, +1807 / -143 = 1950 changed lines (927 in tests), under the 3000-line Tier H threshold",
      "files_changed": [
        ".changeset/28470-record-page-streams.md",
        ".changeset/28470-console-record-open-one-request.md",
        "packages/spec/src/api/record-page.{zod,test}.ts",
        "packages/spec/{api-surface,authorable-surface,declaration-map,export-origins,json-schema.manifest}/api.json (generated)",
        "content/docs/references/{api/record-page,index}.mdx, docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md (generated)",
        "packages/rest/src/record-page-routes{,.test}.ts",
        "packages/qa/dogfood/test/record-page{-streams,}.dogfood.test.ts",
        "ui/packages/types/src/data.ts",
        "ui/packages/react/src/context/DiscussionContext.tsx",
        "ui/packages/plugin-detail/src/{RichTextCommentInput,RecordActivityTimeline,RecordChatterPanel,RelatedList}.tsx",
        "ui/packages/plugin-detail/src/renderers/record-{chatter,activity}.tsx",
        "ui/packages/app-shell/src/views/{RecordDetailView,RecordAttachmentsPanel}.tsx, recordPagePayload.ts",
        "ui/packages/app-shell/src/views/RecordDetailView.{recordOpenOneRequest-28470,recordPagePayload-27627,recordOpenWaves-13010,withheldMentions-28218}.test.tsx"
      ],
      "deviations": [
        "Declaration lines vs the changeset gate: the PR body and changeset carry the claim's lines verbatim (Clause-②: yes (widening), Metadata-protocol: none). With the final body, check-changeset-no-major --base origin/main exits 1 on its level axis: packages/spec/authorable-surface/api.json gains 17 api/RecordPage* keys, which the gate reads as Metadata-protocol: added, graded at least minor on @objectstack/spec. CI's Check Changeset step will be red the same way until the claim moves (to added + '@objectstack/spec': minor) or the red is accepted. The fix is one line in the PR body and in .changeset/28470-record-page-streams.md plus the level.",
        "The activity part asks 200 rows, the console's prior window, because the 动态 count chip and tab count the merged stream over the rows held (accepted as a named deviation).",
        "ui/packages/app-shell/src/views/RecordAttachmentsPanel.tsx and the 13010/11699/27627/28218 test files were touched beyond the amended file surface: the panel takes the payload's attachment rows (target 1), and the tests pinned the retired open reads.",
        "A reply or an edit whose text holds an @ before the composer was ever focused reads the roster at the write; without it, such a mention would silently resolve to nobody now that the open no longer reads the roster.",
        "PR opened without assignees or reviewers; no GitHub MCP write."
      ],
      "mcp_calls": "3: GitHub MCP issue_read on #28470 (get x2, get_comments x1), all reads. No MCP write. Separately, 1 subscribe_pr_activity on #28682 (claude-code-remote, not GitHub MCP). Other reads went through gh api (#28470 and #28561 comments).",
      "api_writes": "2: relay repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches x2 (pr_create #28682, run 41310754784, read-back identical 19118 bytes; this os-dev-report comment). git push x1 is not a REST write.",
      "open_questions": [
        "Amend the claim's Metadata-protocol line to added (with @objectstack/spec minor), or keep none and accept the Check Changeset red?"
      ],
      "out_of_scope_findings": [
        "class: b · reach: every record page that draws the 动态 panel or tab · a server-side count of the merged 动态 stream (comments + activity + field changes, minus the types the panel drops), answered with the page payload, so the activity part can ask the panel's 3 or 5 rows instead of 200 and the count chip still holds · evidence: the chip and tab count are computed client-side over the rows held (RecordDetailView feed pipeline), which is why the part asks 200 · dedupe words: merged activity count, 动态 count server side, record page activity window"
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    Claim amended · 2026-11-01T13:23Z · supersedes the Metadata-protocol line of this seat's claim 6556969643. Branch, session, file surface (as amended 6557459800) and the Clause-② line are unchanged.

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

    Reading (the dev's measurement on draft PR #28682 at 167492ed, report 6558534355): the authorable-surface api shard gains 17 api/RecordPage* keys, the new members of RecordPageRequestSchema / RecordPageResponseSchema. check-changeset-no-major reads a new authorable key as Metadata-protocol: added (AGENTS.md: "a new key, accepted value or metadata type is added"), and that kind takes at least minor on @objectstack/spec. So the PR body and changeset carry Metadata-protocol: added and the changeset moves @objectstack/spec to minor. The CONTRACT_REVIEW_TIER review owed by the Clause-② widening still waits for the director, and the PR stays draft until it is recorded.


    Generated by Claude Code

  8. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    Claim amended · 2026-11-01T14:16Z · file surface widened by one docs file; it adds to the File surface lines of this seat's claim 6556969643 and amendment 6557459800. Branch, session and the declaration lines of amendment 6558547648 are unchanged (Clause-②: yes (widening), Metadata-protocol: added).

    File surface (added):

    • content/docs/frontend/plugins/plugin-detail.mdx, the "Console Integration" section only. It still says the console fetches a record's comments with dataSource.find('sys_comment', …), and its field names are already stale on main. With this card, a record open takes the discussion from the POST /data/:object/:id/page payload, so the section is corrected in the same PR. The dev measured that no other hand-written page documents /page or the retired reads.

    Generated by Claude Code

  9. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    ACCEPT · 2026-11-02T01:04Z · PR #28682 at 9ef31f41, record 6566613018. The contract review PASS on this head is 6566467938 (director seat). Ordinary queue landing follows.


    Generated by Claude Code

  10. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    Landed · 2026-11-02T01:48Z · PR #28682 merged through the merge queue as e7a6cc74. Record: ACCEPT 6566613018 at 9ef31f41. The CONTRACT_REVIEW_TIER PASS on that head is 6566467938 (director seat), after triage#1's PASS 6560407872 and its delta PASS 6561738249.

    probe before (e7a6cc74~1) now (origin/main)
    RecordPageDiscussionSchema in packages/spec/src/api/record-page.zod.ts 0 5
    RECORD_PAGE_MAX_ACTIVITY_ROWS in the same file 0 2
    discussion in packages/rest/src/record-page-routes.ts 0 6
    onMentionSuggestionsWanted in plugin-detail/src/RichTextCommentInput.tsx 0 3
    onMentionSuggestionsWanted in app-shell/src/views/RecordDetailView.tsx 0 2

    The card closed through Fixes. pm:dispatched comes off in the same stroke as this note. Carried, as accepted:


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:uienhancementNew feature or requestpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions