Repository navigation
Description
domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC
Category: ① product defect with a named location. ⛔ Not a claim. Carried from #30122's PR #30342 (report 6651588211 on #30122, open_questions), by agreement with that card's executor: the client half landed there; the count lives in spec + REST and is outside that card's surface.
Business reason. After PR #30342 the record timeline draws one row per action (an activity row carrying its audit diff). The 动态 tab badge and the peek's 「查看全部 N 条」 still count the activity row and the audit entry separately, so a lead created and then edited once reads 「动态 3」 above two rows. An administrator who counts rows against the badge sees a mismatch and suspects missing entries.
reach: measured live on the example CRM (zh-CN, admin) by #30122's executor: one create + one update → 动态 3 over 2 drawn rows (shot /mnt/project-files/ui-orders/30122-shots/after-admin-all.png). A member without audit read sees no mismatch (no audit rows counted).
Where. packages/rest/src/record-page-routes.ts answerTimeline computes the total; packages/spec/src/api/record-page.zod.ts spells the formula (the RecordPageTimelineRequestSchema table, its describe() text, RECORD_TIMELINE_UPDATE_ACTIVITY_TYPE): comments + audit writes + activity, minus updated activity rows when any field change exists. The spec calls the total an upper bound of drawn entries, not their number (#28737 set this up).
Options (executor's, seat recommends A):
- A: when any field change is counted,
createdanddeletedactivity rows step aside too, besideupdated(spec table + describe +answerTimeline+ its REST pins). The badge then equals the drawn rows for an audit reader in the common case. - B: with both sources on, count activity rows only and drop audit writes from the total.
- C: keep the upper bound as specified; document that the badge may exceed the rows.
Expected. For an audit reader on a history-on object, one action adds one to the badge; the member reading is unchanged.
Reader. Spec + REST mirror (domain:spec or domain:server; triage routes), plus the ui pin that reads the badge. A spec contract edit, so the spec seat's review path applies.
Duplicate search (open and closed): answerTimeline count 1 hit (#28737, closed, which introduced this total); timeline count upper bound created 21 hits, all QA-run posts, none recording this mismatch.
Part-of: #30122
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:server·pm:queue· 2026-11-05T07:13Z⛔ Not a claim.
restrecord-page-routes.ts, specrecord-page.zod.ts), sodomain:server. p2.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Claim: PM loop round 1
Session:
session_01Utp6CYVV6fcAQrNTDQRvPb(seat) · dev = project card threadsession_01Utp6CYeVL11pNodMuPhy8qAccount:
os-billBranch:
claude/project-thread-pqdmqj(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30343Domain:
domain:serverSeat:
domain:server#5File surface:
packages/rest/src/record-page-routes.tsand tests;packages/spec/src/api/record-page.zod.ts(table,describe()text, activity-type constants) and its generated artefacts;ui/**test files only where a pin asserts this total;content/docs/**only for sentences this PR makes false (notreleases/**); one changeset. ⛔ui/**source,skills/**,docs/adr/**,.claude/**,AGENTS.md(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default tier (opus),effort: highClause-②: yes (narrowing) provisional — the answered
totalfalls for the same rows (one action counted once for an audit reader); an added exported constant is widening in the same line; the seat runs a contract reviewMetadata-protocol: none (provisional) — a read answer's count, not what an author writes
Responsibility:
rest answerTimeline counts an activity row and its audit write separately for created/deleted (only updated steps aside), so one create + one edit reads 动态 3 over 2 drawn rows (measured live by #30122's executor, report 6651588211) | none: an administrator counting rows against the badge suspects missing entries | every audit reader on a history-on objectThread-read: 6652104225
Premise: holds per the card's live measurement; the dev's first act is to measure PR #30342's client fold rule and mirror it (option A, seat ruling)
Serial constraints cleared:
PR #30342 (client half) merged; no open PR edits record-page-routes.ts or record-page.zod.ts at claim time (dev re-checks).Order: project file
server-seat5/dispatch-30343.md, delivered to the card thread. Option A (seat ruling, PM discretion): the server total mirrors the client's fold.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
os-dev-report
{ "issue": 30343, "status": "done", "branch": "claude/project-thread-pqdmqj", "pr": "https://github.com/objectstack-ai/objectstack/pull/30405", "session": "session_01Utp6CYeVL11pNodMuPhy8q", "premise_still_valid": true, "summary": "Measured PR #30342's client fold first: recordTimelineStream.ts pairActions is a per-row pairing (audit create/update/delete with activity created/updated/deleted, same actor, within TIMELINE_PAIR_WINDOW_MS 5 s, nearest first), plus a type rule that unpaired updated rows step aside; unpaired created/deleted rows stay. Reported needs_decision to the seat (option A literally under-counts a record created before its history was kept); the seat ruled the closest countable rule at 08:04Z. With a field change counted, answerTimeline now steps aside every updated row plus min(created rows, create writes) and min(deleted rows, delete writes), from four more caller-scoped counts in the same Promise.all; a refused ledger count pairs nothing. Spec gains RECORD_TIMELINE_FOLDED_ACTIVITY_TYPES beside the unchanged RECORD_TIMELINE_UPDATE_ACTIVITY_TYPE; the table, describe() texts and upper-bound sentences name the rule and its one exception (an unpaired create/delete).", "tests": "Red first on fdad1786: one create + one edit, audit reader, both sources answered total 3 (expected 2); a comment case 4 (expected 3). Green at dc2c02ab8: rest test 386 files, 7335 passed / 332 skipped; record-page-routes.test.ts 37/37 (merged head 821fd5381 too); rest and spec typecheck exit 0; spec test 25553 passed / 24 failed, all 24 in scripts/liveness/check-liveness.test.ts (main red, see deviations), and record-page.test.ts + check-liveness.test.ts 85/85 on the merged head. Ablation once via scripts/ablation-replace.mjs (rest tests import src, no dist leg): paired += Math.min(...) replaced by paired += 0 -> 4 failed / 33 passed; restored blob == HEAD ffe994f7, git diff HEAD empty.", "gates": "dispatch-gates --commands at 1c674657: 104 derived; loop run there, 102 exit 0; check-adr-0087-registration exit 1 (changeset lacked the marker; fixed in a52401996, re-run exit 0), check:where-matcher exit 1 (new row double did not refuse combinators; fixed in dc2c02ab8, re-run exit 0), check:liveness + check:generated exit 1 (main red query/searchFields, #30368), check:skill-examples + check:dual-build-cjs-loads + check-plugin-teardown-shape --self-test exit 3 then exit 0 after a whole-workspace build and the pinned-commit fetch. --ran at dc2c02ab8: 104 derived, 104 run, 0 NOT-MEASURED. After merging origin/main 8fad7b19 (head 821fd5381; regen wrote no diff): check:liveness, check:generated, check:api-surface, check:export-origins, check:docs, adr-0087, where-matcher, changeset-no-major all exit 0. pnpm lint exit 0. PR check-runs: in_progress at report time, not read.", "dogfood_battery": "dc2c02ab8 · os-verify-lock: VERDICT command-exit 0 · Test Files 419 passed / 3 skipped · Tests 3492 passed / 36 skipped · 0 failures", "line_budget": "+226 / -31 over 9 paths before the merge (rest source +27/-6, spec source +42/-11, tests +122/-14, generated +17/-5, changeset +15)", "files_changed": [ ".changeset/30343-timeline-one-action-one-entry.md", "content/docs/references/api/record-page.mdx", "packages/qa/dogfood/test/record-page-timeline.dogfood.test.ts", "packages/rest/src/record-page-routes.test.ts", "packages/rest/src/record-page-routes.ts", "packages/spec/api-surface/api.json", "packages/spec/export-origins/api.json", "packages/spec/src/api/record-page.test.ts", "packages/spec/src/api/record-page.zod.ts" ], "deviations": "1) packages/qa/dogfood/test/record-page-timeline.dogfood.test.ts is outside the order's file surface: it is the route's end-to-end pin of this total and its expected formula is the spec table's, so it moves with the formula (os-dev stale-fixture rule). No ui/** file changed. 2) The rule is the seat's 08:04Z ruling (min rule), not option A taken literally, after the needs_decision the order's stop condition required. 3) check:liveness was red on main fdad1786 (query/searchFields -> objectql engine.ts after #30341), fixed on main by #30368, green after the merge. 4) The harness assign/review step skipped per the order; the harness reminder names a model in Co-Authored-By and the pre-push hook refused it once, so every commit carries the model-free pair.", "mcp_calls": "2 — reads only: issue_read x2 (get #30343, get_comments #30343)", "api_writes": "2 — relay pr_create (POST pulls, #30405, read-back identical 9713 bytes); this os-dev-report comment via post-stamped (POST issues/30343/comments)", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [] }Report taken 2026-11-05T09:47Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Seat ruling · 2026-11-05T09:55Z (given to the card thread at 2026-11-05T08:04Z, recorded here in its own words): the Claim 6652912193 named option A, provisional on the dev's measurement of PR #30342's client fold. The dev measured that the client pairs per row (audit
create/update/delete↔ activitycreated/updated/deleted, same actor, withinTIMELINE_PAIR_WINDOW_MS= 5 s,pairActionsinui/packages/plugin-detail/src/recordTimelineStream.ts), and that an unpairedupdatedsteps aside by type (#28737). Option A as written (everycreated/deletedsteps aside once a field change is counted) would undercount a record created before its history was kept.Ruled, PM discretion: the min rule. With any field change counted, total = comments + ledger writes + activity −
updated− min(createdactivity,createwrites) − min(deletedactivity,deletewrites); without a field change the formula is unchanged. It is the client's maximum pair count per type, so the one divergence left is a create's (delete's) two rows by different actors or more than 5 s apart, which may count one short: the spec's "upper bound" sentence names that exception. Implemented by PR #30405.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Landing · 2026-11-05T09:58Z: PR #30405 (head
821fd538), contract review PASS 6655016200, seat ACCEPT posted, ready + auto-merge enabled.Fixes #30343.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Landed · 2026-11-05T10:43Z: PR #30405 merged as
29702d572(contract review PASS 6655016200, seat ACCEPT 6655033594). Card closed byFixes #30343; the client-side constant follow-up is #30408.