Repository navigation
Seam: one request opens a record — a record-page payload (record + related counts and first rows + field access + lock) and an app-scoped metadata bundle, so a record open is 2–3 requests instead of 23–45 serialized in three stages #27627
Description
epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff
Filed from the record-page epic #26676 as a sub-issue after the maintainer's 「别人的界面很丝滑,但是我们的界面总觉得卡顿,云端更卡」 and the seat's measurement (2026-10-29). Ruling: 「立…B(接缝卡:记录页一次取齐 + 元数据打包)两张卡,p1」. A Seam: card: the producer is the REST surface (packages/rest / plugin-hono-server, the data and metadata routes), the consumer is the console's data layer (ui/packages/data-objectstack) and the record page; the seat that claims it declares both ends. The client-side waste on the same path is #27626.
Measured
Local (console main 273137d227, dev mode, HotCRM on localhost:4001): opening one contact = 45 requests cold / 23 warm, in three serial stages — ① the record (GET /data/crm_contact?populate=…) + security/explain ×2 + approvals/requests; ② after the record arrives (+130 ms of client work): one GET /meta/object/<child> per related object (9 on a contact), field-access ×4, the approvals lock; ③ after the metadata arrives: one GET /data/<child>?top=1&filter=… count probe per related list (10 on a contact) + attachments + the related rows query + the account. Locally each stage is tens of milliseconds; the drawer is visible at 300 ms.
Cloud (huayan.objectos.app, Cloudflare colo ARN, h2, prod bundle 2.2 MB JS in 93 scripts): the same endpoints cost 0.5–1.4 s each when called alone (GET /api/v1/runtime/config, 293 bytes: 555 / 604 / 729 / 1068 / 1377 ms over five sequential calls; GET /meta/app 0.9–1.8 s; GET /meta/view 1.8–3.8 s; no cache-control, no server-timing). The HotCRM contacts list route fired 42 API calls on load, of which auth/me/localization 8.2 s, packages 7.9 s, sys_inbox_message 7.5 s, meta/dashboard 6.1 s, meta/object/sys_user 5.3 s, data/crm_contact 5.3 s — the same calls that take ~1 s alone take 5–8 s when fifteen of them run at once, and the list had not rendered after 50 s. A three-stage, 23-request record open on that path is a multi-second wait by arithmetic alone.
Linear, Attio and Twenty open a record with one aggregated request (counts and permissions ride with the record), load metadata once at boot, and prefetch on hover.
Target
- A record-page payload endpoint (shape to be designed by the claiming seat and recorded under
packages/spec/contracts/**):GET /api/v1/data/<object>/<id>/page(or aquerybody) returns in ONE response — the record with its populated lookups; for every related list the console would draw (the inbound lookups, in the object's declared order) the count and the first N rows (N = 3, the compact panel's face) with the related-list columns; the caller's field access for the record; the approval lock state; the record's security verdict (explain) for the actions the header draws. One request, one round trip, one server-side transaction. - An app-scoped metadata bundle:
GET /api/v1/meta/app/<appId>/bundlereturns the app, its navigation, every object the app's navigation and those objects' inbound relations reach, their views, pages and dashboards, with an ETag; the console loads it once per session and sendsIf-None-Matchafter. Today's per-objectGET /meta/object/<name>stays for Studio and for objects outside the bundle. - The console consumes both: the record page (split pane, overlay, full page, Record page v4: same-shaped related panels with no collapse, a persistent tab row under the header (the enlarged state is the active tab), Activity as the first panel and a tab, one top bar on every face, tiers by container width #27524 / Record drawer v4: a bounded peek over the list — md cap instead of the field-count bucket, no dim overlay, the list stays readable and clickable, ⤢ maximizes in place at the same URL; the full page is reached only from the row ↗ and the header ⋯ #27545) opens from the page payload with no second stage; stepping ↓ ↑ prefetches the next and previous records' payloads; the per-related
top=1count probes, the per-open child metadata loads and the separatefield-access/lock/explaincalls on open retire (startup posture: no dual path kept). - Server-side observability for the cloud reading: the REST layer emits
Server-Timing(db, meta, total) on every response, so the next measurement separates origin time from the network path; metadata responses carryCache-Control+ETag. - Clause-②: yes (new REST routes, declared in the spec contracts); Metadata-protocol: added (the bundle's shape).
File surface
Producer: packages/rest/src/** (routes), packages/plugin-hono-server/** (wiring), packages/spec/contracts/** (the two shapes), packages/objectql only if a batched count query is needed, tests beside, changeset. Consumer: ui/packages/data-objectstack/src/index.ts, ui/packages/app-shell/src/views/RecordDetailView.tsx + recordDrawerStack.tsx (prefetch), ui/packages/plugin-detail/src/renderers/recordBinding.tsx + RelatedCardStack.tsx (counts and rows from the payload), tests beside, changeset.
Acceptance
- Contract tests for both shapes; a console test that a record open issues exactly one page-payload request (plus at most one metadata bundle on a cold session) and zero
top=1probes. - The cloud reading re-run by the epic seat on
huayan.objectos.appafter landing: record open under 1 s warm, list route under 3 s cold, withServer-Timingin the PR body. - The epic seat verifies in the console preview before ACCEPT (maintainer: 「车队开发完后你审核效果」).
Activity
objectstack-fleet commentedon Oct 29, 2026
epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff
Deployment half filed · 2026-10-29T15:22Z · objectstack-ai/cloud#3378 (Ops, p1,
pm:queue): the path and origin reading forhuayan.objectos.app— the ~0.5 s floor per call, the Stockholm (ARN) colo for a China user, the 5–8 s per call under a 15-call burst, edge caching for/meta/*. Item 4 of this card (RESTCache-Control+ETag+Server-Timing) is what that card's edge rule and timing split consume; the two land independently.objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Claim: PM loop round 3 · 2026-10-29T15:31Z
Session:
session_01WGufzgbJhFoXYsLSe5WSPPAccount:
os-support-aiBranch:
claude/project-thread-2m7jqd(card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead ofclaude/issue-27627-<slug>)Worktree:
objectstack-27627Domain:
domain:specSeat:
domain:spec#3Ruling-ref: the maintainer's ruling on epic #26676 quoted in the card body (「立…B(接缝卡:记录页一次取齐 + 元数据打包)两张卡,p1」); two PRs from this branch, the first
Part of #27627File surface: producer:
packages/rest/src/**,plugin-hono-server,packages/spec/contracts/**(the two shapes),packages/objectqlonly for a batched count; consumer:ui/packages/data-objectstack,RecordDetailView.tsx,recordDrawerStack.tsx,recordBinding.tsx,RelatedCardStack.tsx; tests; one root changeset per touched published package (stop on breach; explain in the report)Container & model:
L,mode:thread,model: TIER_DEFAULT — --tier: no path-derived mandate (new REST routes; the contract review is triage's),effort: highClause-②: yes (widening)
Metadata-protocol: added (the app bundle's shape), as measured by the dev
Responsibility: an unclaimed p1
domain:speccard; seat 1 is off dutyThread-read: none
Premise: to be measured first by the dev on
origin/main178b5238: one record open issues 23 (warm) to 45 (cold) requests in three serial stagesSerial constraints cleared: #27626 (client-side waste, same path) and cloud#3378 (deployment half) are left untouched; the dev re-checks open PRs on these files before editing
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Claim amendment: one declaration and a surface extension (PR 1, #27713) ·
domain:specseat 3 · sessionsession_01WGufzgbJhFoXYsLSe5WSPP· 2026-10-29T20:18Z · same claim 6501011060, same branchclaude/project-thread-2m7jqdMetadata-protocol: added, as measured by the dev: the two new shapes enterpackages/spec/authorable-surface/api.json, andcheck-changeset-no-majorrefusesnone. Every moved package isminor.types, thecomponentsrelated-count store,plugin-detail(useRecordEditable,queueClaim,recordFieldAccess,RelatedList,index,package.jsonsideEffects), and app-shelluseRecordApprovalsplus a newrecordPagePayload.ts. It also covers the dogfood fixture and test, the rest census tests andrest-server-registrar-files.ts, thescripts/check-route-envelope.mjsMODULES row, the generated spec artifacts andcontent/docs/references/api. The PR body gives the reason. Size 2980 lines (under the 3000 human-merge threshold), 0 governed paths.