You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Decision] Field changes are recorded but not shown unless an object opts in: who decides a record shows its change history? #30074
The maintainer: 「等我决定的都要立卡,并且从业务的角度给我具体解释和我沟通」 (2026-11-04). Measured by the seat on main63b86b385d, on the example CRM unless noted. No domain:* label is set: triage routes this card.
The question
A manager asks who changed a deal's amount and from what. The platform has already recorded it, yet the record's 动态 shows only "更新了 商机…:赢单概率 (%)", with no old or new value. The values appear only if the object declares enable.trackHistory: true, and most authors will not know to.
Governing text: packages/spec/src/data/object.zod.ts:236 (trackHistory is an opt-in flag) and ui/packages/app-shell/src/views/RecordDetailView.tsx:2375-2380.
Measured
sys_audit_log held the old and new values for a probability change from 70 to 75 (reviewer's reading; the seat read the gate in code).
examples/app-crm declares no enable on its objects. examples/app-showcase and app-todo set it on some.
The 动态 tab offers a 变更 filter even when no changes can appear.
Options
A. Example first: the CRM example opens it on its four main objects, the 变更 filter hides when history is off, and the flag stays opt-in.
B. Default on: every business object shows history unless it opts out.
The maintainer, 2026-11-04: 「30079这种不是应该在元数据应用中自己去开发自定义操作吗?你的意见是批量换负责人放到内核吗?其他全部同意」.
Ruling: option A. The CRM example opens enable.trackHistory on its four main objects, and the 变更 filter hides when history is off. The flag stays opt-in. Defaulting it on is not decided here. needs-user-decision is removed; triage routes this card.
Read fresh. The ruling recorded on this card (A, 「其他全部同意」) is the scope. The CRM example turns on enable.trackHistory on its four main objects, and the 变更 filter hides when history is off. The flag stays opt-in. p2 kept.
Claim: PM loop round 1
Session: session_01QSeKYnE9YNkSZen7sijLA3
Account: os-justin
Branch: claude/issue-30074-crm-track-history
Worktree: objectstack-issue-30074
Domain: domain:ui
Seat: domain:ui#2
File surface: examples/app-crm/src/objects/{account,contact,lead,opportunity}.object.ts (the enable.trackHistory opt-in only), ui/packages/app-shell/src/views/RecordDetailView.tsx (the 动态 tab's 变更 filter visibility, near historyEnabled), their tests, one root .changeset/ file (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: TIER_DEFAULT — --tier: "no path-derived mandate: the surface hits none of the 4 declared glob(s)", effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: 6631387255
Premise: holds
Serial constraints cleared: none — no open domain:uipm:dispatched claim names RecordDetailView.tsx or examples/app-crm (read 2026-11-04T15:49Z: #30059 and #30057 (seat 1) declare studio-design/** only; #30038 and #30050 are held by seat 3)
Ruling-ref: 6631104393 (option A, the maintainer's 「其他全部同意」)
{
"issue": 30074,
"status": "done",
"branch": "claude/issue-30074-crm-track-history",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30158",
"session": "session_01QSeKYnE9YNkSZen7sijLA3",
"premise_still_valid": true,
"summary": "CRM example: crm_lead, crm_account, crm_contact, crm_opportunity now declare enable: { trackHistory: true } (the four Sales-nav objects other than crm_activity, the activity log itself; crm_opportunity_line_item is a child without a nav entry). The Changes chip is rendered by @object-ui/plugin-detail (RecordActivityTimeline, state in the record:activity renderer), not by RecordDetailView.tsx, so the landing is there: new RecordFieldHistoryContext (default true) + timelineStreamFilters(); record:activity drops the changes chip while it is false and falls back to all if it was chosen; RecordDetailView provides its existing historyEnabled. Premise partly false, executed as ruled anyway: the card's 'the Changes filter shows even when no changes can appear' is not so -- sys_activity created/updated/deleted/assigned/shared rows map to the field_change feed type and sit under Changes (existing pin 'the four chips' holds one), so on a non-history object those rows are now reachable only under All; raised in open_questions. Surface breach (declared in PR): 4 plugin-detail src files + plugin-detail README beyond the claimed surface, as the dispatch allowed.",
"tests": "head 8c5b89c91. New pins: plugin-detail recordTimelineStream-27055.test.tsx (top-level describe, 4 cases: shown=4 chips, not shown=3 chips + All draws all, no provider=4 control, chosen Changes removed to All) and app-shell RecordDetailView.recordPageTiers-26678.test.tsx (top-level describe, real RecordDetailView mount on ?related=timeline: trackHistory object to all/activity/discussion/changes; same object without it to all/activity/discussion and 0 audit reads). Targeted 3 files: Test Files 3 passed (3), Tests 27 passed (27), VERDICT command-exit 0. plugin-detail whole: Test Files 369 passed | 1 skipped (370), Tests 3264 passed | 18 skipped, VERDICT 0. app-shell NARROWED (declared): 218 files naming RecordDetailView/RecordActivityTimeline/recordTimelineStream/record-activity: 218 passed, Tests 1630 passed, VERDICT 0; rest of the package to CI Test UI. type-check: plugin-detail exit 0, app-shell exit 0 (script names echoed: tsc --noEmit && tsc -p tsconfig.test.json). example-crm typecheck exit 0, vitest 7/7 files, 48/48 tests. Ablation: scripts/ablation-replace.mjs on record-activity.tsx anchor 'timelineStreamFilters(React.useContext(RecordFieldHistoryContext))' to 'timelineStreamFilters(true || ...)' (subject resolves to src via ui/vitest.config.mts alias, no dist leg): anchor 1 to 0, blob 7b5fe9176ec0 to 69e1bc36c51c; 3 red (both plugin-detail not-shown pins, app-shell without-it pin), shown/control green; restored blob == HEAD 7b5fe9176ec0, git diff HEAD empty.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 8c5b89c91: 53 commands, all run, exit captured before pipes; --ran: '53 derived famil(ies) accounted for -- 53 run, 0 NOT-MEASURED (a DERIVED zero -- all 53 recorded an exit code and none of them is 3)'. 50 exit 0 first pass; check:dual-build-cjs-loads and check:i18n-coverage exit 3 (PREREQUISITE NOT MET) then exit 0 after prerequisite builds; check:type-check-debt 124 (local 400s timeout) then exit 0 under the lock; ui vitest scripts/ 170 passed | 2 skipped. check-sdui-manifest exit 0. check-platform-checklist not owed (no export renamed/removed). CI check-runs on 8c5b89c91 at report time: all required contexts queued (Lint & Repo Gates lanes, TypeScript Type Check lanes, Test Core, Build Core, Governed Surface Queue Guard, Lint UI, Type Check UI, Test UI 1-16, Build & E2E UI, Live E2E UI) -- in_progress, not waited on.",
"dogfood_battery": "n/a -- no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 -- relay pr_create x1 (POST /repos/objectstack-ai/objectstack/dispatches, executed as pulls create: draft, no labels, no assignees; the dispatch tool's 300s client wait timed out, PR #30158 confirmed by REST read and not re-sent); os-dev-report comment x1 via post-stamped.mjs (issues/30074/comments). Plus git push x4 (empty-branch probe and 3 commits), not REST.",
"open_questions": [
{
"question": "With history off, sys_activity created/updated/deleted/assigned/shared rows (feed type field_change) used to sit under 变更; with 变更 hidden they are reachable only under 全部 (活动 excludes field_change). Keep it so?",
"options": [
"A keep as landed: rows only under 全部 on non-history objects",
"B while 变更 is hidden, 活动 also lists the activity-sourced field_change rows (small change in filterTimelineStream, one pin)",
"C hide 变更 only when nothing can feed it (history off AND enable.activities false)"
],
"recommendation": "B, because it keeps the ruling (no 变更 chip without history) and keeps every row filterable; C contradicts the ruling's wording; A is acceptable but loses a filter the stream had."
}
],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: none -- the premise note above is in PR Acceptance notes and open_questions, not filed"
],
"deviations": "app-shell suite narrowed to 218 related files (declared; CI Test UI runs it whole). Branch not re-merged with main: 8 commits landed after base 55337f4ce, none touches a diff path. Comment/commit trailers use the model-free pair per AGENTS.md, not the harness reminder's.",
"files_changed": [
".changeset/30074-crm-track-history.md",
"examples/app-crm/src/objects/account.object.ts",
"examples/app-crm/src/objects/contact.object.ts",
"examples/app-crm/src/objects/lead.object.ts",
"examples/app-crm/src/objects/opportunity.object.ts",
"ui/packages/app-shell/src/views/RecordDetailView.recordPageTiers-26678.test.tsx",
"ui/packages/app-shell/src/views/RecordDetailView.tsx",
"ui/packages/plugin-detail/README.md",
"ui/packages/plugin-detail/src/RecordActivityTimeline.tsx",
"ui/packages/plugin-detail/src/__tests__/recordTimelineStream-27055.test.tsx",
"ui/packages/plugin-detail/src/index.tsx",
"ui/packages/plugin-detail/src/recordTimelineStream.ts",
"ui/packages/plugin-detail/src/renderers/record-activity.tsx"
]
}
维护者速读 · #30074 回箱:裁决 A 的一个前提被证伪 · 2026-11-04T17:43Z
一句话问题。 你裁的 A 是「记录未开字段历史时,动态里的『变更』筛选隐藏」。卡上给你的前提是(席位自己测的)「那时变更里不会有内容」。实测这个前提不成立:没开历史的对象,动态里「创建了 / 更新了 / 删除了 / 分配了 / 共享了」这几类记录(不带旧值新值)今天也都归在「变更」下。所以按 A 原样把「变更」隐藏后,这些记录只能在「全部」里看到,「活动」里也没有。PR #30158 已按 A 原样实现,停在 draft,等你定这一点。
Governing text: the ruling 6631104393 (option A); ui/packages/plugin-detail/src/renderers/recordActivityFeed.ts:140-144 (created/updated/deleted/assigned/shared → 'field_change'); ui/packages/plugin-detail/src/recordTimelineStream.ts:162 (变更 = type === 'field_change'). Spec declares the field_change feed type (packages/spec/src/data/feed.zod.ts:32), but no spec text names which types a chip shows (git grep -n "chip" origin/main -- packages/spec/src/data/feed.zod.ts → 0; positive control field_change in the same file → 1 hit).
Protocol: no spec change in any option; UI-only.
Premise (re-check)
git grep -n "created: 'field_change'" origin/main -- ui/packages/plugin-detail/src/renderers/recordActivityFeed.ts → 1 hit (positive control: system: 'system' in the same table).
Audit-sourced items carry source: 'sys_audit_log' (recordTimelineStream.ts, auditEntriesToFeedItems), so the two kinds can be told apart.
② 拉动:任何未开历史的对象今天就会撞上(「活动」里找不到创建/分配记录);代码读取与既有 pin 证实(27055 the four chips),未在浏览器实测(PR 也未测)。
③ 防错:C 是一句话的闭合规则;B 的条件分支易被后续改动漏掉。
④ 不扩散:三者都不加键、不动服务端;C 与 B 工作量相近。
Contract review: isolated CONTRACT_REVIEW_TIER review returned FAIL with 6 required corrections; all applied in this text (spec grep, example census, premise attribution, C's route and the second filter, facet ② evidence, stamp).
Prior rulings read: trackHistory, 变更, field_change on #30074 and #26676 → 1 ruling (6631104393); ADR none; thread: 6631104393.
Release: claim 6635173915 (session_01QSeKYnE9YNkSZen7sijLA3, seat domain:ui#2) is released at clock-out, 2026-11-05T03:15Z, on the maintainer's 「当前任务处理完,合并后就下班」.
Resume-from: claude/issue-30074-crm-track-history@8c5b89c9
State: the card stays on needs-user-decision (analysis 6637710958, options A/B/C, seat recommends C). Draft PR #30158 carries option A as built, CI green on 8c5b89c9, not ACCEPTed. After the ruling, a sibling domain:ui seat re-claims, resumes from the line above, and has the same dev change recordTimelineStream.ts (and the panel's changes_only filter) to the chosen letter. Its PR is serial with #30122 (record timeline).
Ruling recorded · #30074 · letter C · 2026-11-05T04:24Z
The maintainer (os-justin) gave this ruling verbatim in seat domain:ui#2's session: 「C」. It answers the seat's analysis 6637710958 (options A/B/C, recommendation C), which the maintainer was shown in the same session just before ruling.
Ruling: option C. The timeline's 变更 chip holds only field-level changes with old and new values (audit-sourced, source: 'sys_audit_log'). Activity-sourced events (created / updated / deleted / assigned / shared) always sit under 活动, whatever the history flag. With history off, 变更 has nothing to hold, so hiding it (ruling A of 6631104393, which this refines) is now accurate. Neither the CRM example's enable.trackHistory on its four main objects nor the opt-in posture changes.
Execution (from the analysis's 裁后执行 and its corrections):
Re-classify in filterTimelineStream (ui/packages/plugin-detail/src/recordTimelineStream.ts) by source. ⛔ Do not edit ACTIVITY_TYPE_TO_FEED_TYPE, because that reaches 5+ existing pins and the platform checklist. Apply the same rule to the panel dropdown's changes_only in RecordActivityTimeline.tsx, so the stream and the panel agree.
Flip the existing "four chips" pin (recordTimelineStream-27055, its created row under 变更) to the new reading, as a substantive assertion. Add a pin that an activity row lands under 活动 with history on and off.
Claim: PM loop round 4 (resume of released claim 6635173915)
Session: session_01WGufzic59DvGAbu7U78pZt
Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/project-thread-7ocxgx
Worktree: objectstack-issue-30074
Domain: domain:ui
Seat: domain:ui#1
Resume-from: claude/issue-30074-crm-track-history@8c5b89c9 (draft PR #30158; Release 6648707864). The new PR supersedes #30158; the seat closes #30158 once it is up.
File surface: #30158's 13 files (examples/app-crm four objects' enable.trackHistory, ui/packages/plugin-detail/src/recordTimelineStream.ts, RecordActivityTimeline.tsx, renderers/record-activity.tsx, index.tsx, README, ui/packages/app-shell/src/views/RecordDetailView.tsx and their tests, the changeset); ⛔ ACTIVITY_TYPE_TO_FEED_TYPE not edited; no server, REST or spec change (stop on breach; explain in the report)
Container & model: M, mode:thread, model: TIER_DEFAULT (p2), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: the record timeline (filterTimelineStream, the panel's changes_only) puts activity-sourced rows under 变更 | ruling C (6649643659): 变更 holds only audit-sourced field changes with old/new values | every record page reader, and the CRM example as the reference posture, today
Thread-read: 6649643659
Premise: holds (filterTimelineStream classifies by feed type, not source, on origin/main9b8a5867; #30158 at 8c5b89c9 carries ruling A only)
Serial constraints cleared: none — only #30158 (superseded by this claim) touches recordTimelineStream.ts / RecordActivityTimeline.tsx; #30122 goes after this card, not folded.
{
"issue": 30074,
"status": "done",
"branch": "claude/project-thread-7ocxgx",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30294",
"session": "session_01WGufzic59DvGAbu7U78pZt",
"premise_still_valid": true,
"summary": "Resumed claude/issue-30074-crm-track-history@8c5b89c9 (PR #30158, ruling A) onto the pinned branch, merged origin/main 9b8a5867 cleanly (os-regen-merge.sh), and added the ruling C delta: new isFieldHistoryEntry(item) in recordTimelineStream.ts (type field_change AND source sys_audit_log); filterTimelineStream's changes chip holds those only and activity holds everything that is neither a comment nor one of those, so sys_activity created/updated/deleted/assigned/shared rows sit under Activity whatever the history flag; RecordActivityTimeline's changes_only uses the same predicate. ACTIVITY_TYPE_TO_FEED_TYPE untouched. Ruling A half of #30158 kept unchanged. Changeset and README amended to C. Surface: 15 files = #30158's 13 plus 2 existing plugin-detail test files whose pins read the old classification (declared in the PR).",
"tests": "head 93bbdd78. Flipped pin 'the four chips' (recordTimelineStream-27055): created row under activity, changes = audit only, every entry in exactly one of activity/discussion/changes. New pins same file: history on AND off, activity lists all five activity events and no audit change; CONTROL history on, changes holds the old/new field change and no event; panel changes_only same rule. Fixture triage: record-activity.test 'filterMode seeds which slice' reseeded to tasks_only + new pin 'changes_only holds no activity event'; 8968 fixture field-change row gains source sys_audit_log (the only fieldChanges producer is auditEntriesToFeedItems), assertions unchanged. Targeted 3 files: Test Files 3 passed (3), Tests 69 passed (69), VERDICT command-exit 0. plugin-detail whole + app-shell RecordDetailView.recordPageTiers-26678: Test Files 370 passed | 1 skipped (371), Tests 3292 passed | 18 skipped. type-check plugin-detail exit 0; app-shell exit 0 after full build (first run before the full build red on unbuilt @object-ui declarations TS2307, not this diff). Ablation scripts/ablation-replace.mjs on recordTimelineStream.ts, anchor isFieldHistoryEntry body to type-only (src via ui vitest alias, no dist leg): anchor 1 to 0, blob 16c475ce1d79 to aee54ae67d0a; 6 red = exactly the flipped + new pins, 8968 green; restored blob == HEAD 16c475ce1d79, git diff HEAD empty. Live CRM zh-CN (objectstack dev --seed-admin --fresh + Vite console): new crm_lead edited once: chips all/activity/discussion/changes, changes = company old to new plus the create entry's initial values, activity = created row only; new crm_activity (history off) edited once: chips all/activity/discussion, no changes chip, activity = updated + created. Shots in /mnt/project-files/ui-orders/30074-shots/ (5 png).",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 93bbdd78: 53 commands, run serially after a full build, exit captured before pipes: 53 of 53 exit 0. --ran: '53 derived famil(ies) accounted for -- 53 run, 0 NOT-MEASURED (a DERIVED zero -- all 53 recorded an exit code and none of them is 3)'. check-sdui-manifest exit 0; ui vitest scripts/ 170 passed | 2 skipped. ui check:new-line-citations 0 new, exit 0. check-platform-checklist not owed (nothing renamed/removed). CI check-runs on 93bbdd78: in_progress at report time, not waited on.",
"dogfood_battery": "n/a -- no ! in the PR title and no (narrowing) arm",
"mcp_calls": "4 -- issue_read get x1, issue_read get_comments x2, pull_request_read get x1; all reads, no write tool",
"api_writes": "2 -- relay pr_create x1 (repository_dispatch, run 41972781306, PR #30294 read back identical 9453 bytes, draft, no labels, no assignees); os-dev-report comment x1 via post-stamped.mjs (issues/30074/comments). Plus git push x1, not REST.",
"open_questions": [
{
"question": "Off the default record page the record:activity panel reads no audit, so under ruling C its changes_only option is always empty there (before, it listed activity created/updated rows). Keep offering it?",
"options": [
"A keep as landed (ruling C as written; the option is empty where no audit is read)",
"B hide changes_only in the panel dropdown where the host gives no field history (small follow-up, same RecordFieldHistoryContext)"
],
"recommendation": "A for this PR (executes the ruling); B as a seat follow-up if the maintainer wants it, because an always-empty filter is the shape the card removed for the stream."
}
],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: #30122 -- live: on a history-on crm_lead created via the data API, the creation is said twice in All (activity 'created' row plus an audit entry listing every initial value), because auditEntriesToFeedItems keeps any entry with changes, the create entry included, though its doc says the creation is the activity stream's own row; compact rows did name the actor in this run",
"carrier: none -- panel changes_only empty off the default page: in PR Acceptance notes and open_questions, not filed"
],
"deviations": "File surface +2 existing plugin-detail test files (pins read the old classification; ruling-driven). Branch not re-merged after main moved 14 commits (none touches the 15 paths; merge-tree clean). Not subscribed to PR activity and no assignee/reviewer set: the os-dev contract overrides the harness posture. Commit trailers use the model-free pair."
}
Activity
objectstack-fleet commentedon Nov 4, 2026
epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff
The maintainer, 2026-11-04: 「30079这种不是应该在元数据应用中自己去开发自定义操作吗?你的意见是批量换负责人放到内核吗?其他全部同意」.
Ruling: option A. The CRM example opens
enable.trackHistoryon its four main objects, and the 变更 filter hides when history is off. The flag stays opt-in. Defaulting it on is not decided here.needs-user-decisionis removed; triage routes this card.objectstack-fleet commentedon Nov 4, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 kept ·
domain:ui· 2026-11-04T13:04Z⛔ Not a claim.
enable.trackHistoryon its four main objects, and the 变更 filter hides when history is off. The flag stays opt-in. p2 kept.Generated by Claude Code
objectstack-fleet commentedon Nov 4, 2026
domain:ui#2 · os-justin · session_01QSeKYnE9YNkSZen7sijLA3
Claim: PM loop round 1
Session:
session_01QSeKYnE9YNkSZen7sijLA3Account:
os-justinBranch:
claude/issue-30074-crm-track-historyWorktree:
objectstack-issue-30074Domain:
domain:uiSeat:
domain:ui#2File surface:
examples/app-crm/src/objects/{account,contact,lead,opportunity}.object.ts(theenable.trackHistoryopt-in only),ui/packages/app-shell/src/views/RecordDetailView.tsx(the 动态 tab's 变更 filter visibility, nearhistoryEnabled), their tests, one root.changeset/file (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: TIER_DEFAULT — --tier: "no path-derived mandate: the surface hits none of the 4 declared glob(s)",effort: highClause-②: no
Metadata-protocol: none
Responsibility:
n/a — not a defect cardThread-read: 6631387255
Premise: holds
Serial constraints cleared: none — no open
domain:uipm:dispatchedclaim namesRecordDetailView.tsxorexamples/app-crm(read 2026-11-04T15:49Z: #30059 and #30057 (seat 1) declarestudio-design/**only; #30038 and #30050 are held by seat 3)Ruling-ref: 6631104393 (option A, the maintainer's 「其他全部同意」)
objectstack-fleet commentedon Nov 4, 2026
domain:ui#2 · os-justin · session_01QSeKYnE9YNkSZen7sijLA3
os-dev-report
{ "issue": 30074, "status": "done", "branch": "claude/issue-30074-crm-track-history", "pr": "https://github.com/objectstack-ai/objectstack/pull/30158", "session": "session_01QSeKYnE9YNkSZen7sijLA3", "premise_still_valid": true, "summary": "CRM example: crm_lead, crm_account, crm_contact, crm_opportunity now declare enable: { trackHistory: true } (the four Sales-nav objects other than crm_activity, the activity log itself; crm_opportunity_line_item is a child without a nav entry). The Changes chip is rendered by @object-ui/plugin-detail (RecordActivityTimeline, state in the record:activity renderer), not by RecordDetailView.tsx, so the landing is there: new RecordFieldHistoryContext (default true) + timelineStreamFilters(); record:activity drops the changes chip while it is false and falls back to all if it was chosen; RecordDetailView provides its existing historyEnabled. Premise partly false, executed as ruled anyway: the card's 'the Changes filter shows even when no changes can appear' is not so -- sys_activity created/updated/deleted/assigned/shared rows map to the field_change feed type and sit under Changes (existing pin 'the four chips' holds one), so on a non-history object those rows are now reachable only under All; raised in open_questions. Surface breach (declared in PR): 4 plugin-detail src files + plugin-detail README beyond the claimed surface, as the dispatch allowed.", "tests": "head 8c5b89c91. New pins: plugin-detail recordTimelineStream-27055.test.tsx (top-level describe, 4 cases: shown=4 chips, not shown=3 chips + All draws all, no provider=4 control, chosen Changes removed to All) and app-shell RecordDetailView.recordPageTiers-26678.test.tsx (top-level describe, real RecordDetailView mount on ?related=timeline: trackHistory object to all/activity/discussion/changes; same object without it to all/activity/discussion and 0 audit reads). Targeted 3 files: Test Files 3 passed (3), Tests 27 passed (27), VERDICT command-exit 0. plugin-detail whole: Test Files 369 passed | 1 skipped (370), Tests 3264 passed | 18 skipped, VERDICT 0. app-shell NARROWED (declared): 218 files naming RecordDetailView/RecordActivityTimeline/recordTimelineStream/record-activity: 218 passed, Tests 1630 passed, VERDICT 0; rest of the package to CI Test UI. type-check: plugin-detail exit 0, app-shell exit 0 (script names echoed: tsc --noEmit && tsc -p tsconfig.test.json). example-crm typecheck exit 0, vitest 7/7 files, 48/48 tests. Ablation: scripts/ablation-replace.mjs on record-activity.tsx anchor 'timelineStreamFilters(React.useContext(RecordFieldHistoryContext))' to 'timelineStreamFilters(true || ...)' (subject resolves to src via ui/vitest.config.mts alias, no dist leg): anchor 1 to 0, blob 7b5fe9176ec0 to 69e1bc36c51c; 3 red (both plugin-detail not-shown pins, app-shell without-it pin), shown/control green; restored blob == HEAD 7b5fe9176ec0, git diff HEAD empty.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 8c5b89c91: 53 commands, all run, exit captured before pipes; --ran: '53 derived famil(ies) accounted for -- 53 run, 0 NOT-MEASURED (a DERIVED zero -- all 53 recorded an exit code and none of them is 3)'. 50 exit 0 first pass; check:dual-build-cjs-loads and check:i18n-coverage exit 3 (PREREQUISITE NOT MET) then exit 0 after prerequisite builds; check:type-check-debt 124 (local 400s timeout) then exit 0 under the lock; ui vitest scripts/ 170 passed | 2 skipped. check-sdui-manifest exit 0. check-platform-checklist not owed (no export renamed/removed). CI check-runs on 8c5b89c91 at report time: all required contexts queued (Lint & Repo Gates lanes, TypeScript Type Check lanes, Test Core, Build Core, Governed Surface Queue Guard, Lint UI, Type Check UI, Test UI 1-16, Build & E2E UI, Live E2E UI) -- in_progress, not waited on.", "dogfood_battery": "n/a -- no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 -- relay pr_create x1 (POST /repos/objectstack-ai/objectstack/dispatches, executed as pulls create: draft, no labels, no assignees; the dispatch tool's 300s client wait timed out, PR #30158 confirmed by REST read and not re-sent); os-dev-report comment x1 via post-stamped.mjs (issues/30074/comments). Plus git push x4 (empty-branch probe and 3 commits), not REST.", "open_questions": [ { "question": "With history off, sys_activity created/updated/deleted/assigned/shared rows (feed type field_change) used to sit under 变更; with 变更 hidden they are reachable only under 全部 (活动 excludes field_change). Keep it so?", "options": [ "A keep as landed: rows only under 全部 on non-history objects", "B while 变更 is hidden, 活动 also lists the activity-sourced field_change rows (small change in filterTimelineStream, one pin)", "C hide 变更 only when nothing can feed it (history off AND enable.activities false)" ], "recommendation": "B, because it keeps the ruling (no 变更 chip without history) and keeps every row filterable; C contradicts the ruling's wording; A is acceptable but loses a filter the stream had." } ], "security_findings": "none", "out_of_scope_findings": [ "carrier: none -- the premise note above is in PR Acceptance notes and open_questions, not filed" ], "deviations": "app-shell suite narrowed to 218 related files (declared; CI Test UI runs it whole). Branch not re-merged with main: 8 commits landed after base 55337f4ce, none touches a diff path. Comment/commit trailers use the model-free pair per AGENTS.md, not the harness reminder's.", "files_changed": [ ".changeset/30074-crm-track-history.md", "examples/app-crm/src/objects/account.object.ts", "examples/app-crm/src/objects/contact.object.ts", "examples/app-crm/src/objects/lead.object.ts", "examples/app-crm/src/objects/opportunity.object.ts", "ui/packages/app-shell/src/views/RecordDetailView.recordPageTiers-26678.test.tsx", "ui/packages/app-shell/src/views/RecordDetailView.tsx", "ui/packages/plugin-detail/README.md", "ui/packages/plugin-detail/src/RecordActivityTimeline.tsx", "ui/packages/plugin-detail/src/__tests__/recordTimelineStream-27055.test.tsx", "ui/packages/plugin-detail/src/index.tsx", "ui/packages/plugin-detail/src/recordTimelineStream.ts", "ui/packages/plugin-detail/src/renderers/record-activity.tsx" ] }objectstack-fleet commentedon Nov 4, 2026
domain:ui#2 · os-justin · session_01QSeKYnE9YNkSZen7sijLA3
维护者速读 · #30074 回箱:裁决 A 的一个前提被证伪 · 2026-11-04T17:43Z
一句话问题。 你裁的 A 是「记录未开字段历史时,动态里的『变更』筛选隐藏」。卡上给你的前提是(席位自己测的)「那时变更里不会有内容」。实测这个前提不成立:没开历史的对象,动态里「创建了 / 更新了 / 删除了 / 分配了 / 共享了」这几类记录(不带旧值新值)今天也都归在「变更」下。所以按 A 原样把「变更」隐藏后,这些记录只能在「全部」里看到,「活动」里也没有。PR #30158 已按 A 原样实现,停在 draft,等你定这一点。
Governing text: the ruling 6631104393 (option A);
ui/packages/plugin-detail/src/renderers/recordActivityFeed.ts:140-144(created/updated/deleted/assigned/shared → 'field_change');ui/packages/plugin-detail/src/recordTimelineStream.ts:162(变更 =type === 'field_change'). Spec declares thefield_changefeed type (packages/spec/src/data/feed.zod.ts:32), but no spec text names which types a chip shows (git grep -n "chip" origin/main -- packages/spec/src/data/feed.zod.ts→ 0; positive controlfield_changein the same file → 1 hit).Protocol: no spec change in any option; UI-only.
Premise (re-check)
git grep -n "created: 'field_change'" origin/main -- ui/packages/plugin-detail/src/renderers/recordActivityFeed.ts→ 1 hit (positive control:system: 'system'in the same table).source: 'sys_audit_log'(recordTimelineStream.ts,auditEntriesToFeedItems), so the two kinds can be told apart.选项
filterTimelineStream里按来源判(source === 'sys_audit_log'),⛔ 不改ACTIVITY_TYPE_TO_FEED_TYPE映射表(那会牵动 5 个以上既有 pin 与平台测试清单);同步改RecordActivityTimeline.tsx:285面板下拉的changes_only,否则面板与流的「变更」不一致业务含义直译。 A 相当于把一类记录放进了只有「全部」能翻到的抽屉;B 相当于同一张单据随部门设置换柜子;C 相当于按单据种类固定柜子:「改了哪个字段、从多少改成多少」一个柜,「谁创建、谁分配」另一个柜。
推荐:C
只看① 选 C;②③④ 是否翻转:否。
项目长远合理性(领起)。 两年后这个面板应当像主流 CRM 那样:「字段历史」(Salesforce Field History、HubSpot property history)只放字段级前后值,「活动」放事件。C 让分类由记录本身的含义决定,没有开关相关的特例;B 在分类规则里多出一条「看开关」的分支,以后谁都要记住;A 留下一类在筛选里无处可去的记录。
实际业务需求。 这个标志是按需开启的(
object.zod.ts:236)。本 PR 之前,示例 CRM 的 7 个对象和app-multi-package都没有开(app-showcase、app-todo的部分对象开了)。任何没开的对象,用户按「活动」筛选时都找不到「谁创建、谁分配」这类最常看的记录。防 AI 写错。 都不涉及 AI 写的元数据。C 的规则一句话就能说清(「变更 = 有旧值新值」),AI 改这块代码时最不容易写错;B 的条件分支最容易在后续改动里漏掉。
创业阶段不扩散。 三者都不加键、不动服务端。C 比 B 多改一个既有 pin,工作量相近(不足 3 倍差)。
未取的变体 C′。 把创建/删除/共享映射到 spec 已有的
record_create/record_delete/sharing类型(feed.zod.ts:40-43),语义更干净,但要改映射表,代价见 C 行,现在不荐。置信缺口。 没测开了历史的对象上,「更新了」事件与审计条目并存时用户是否觉得重复:现有代码在有审计条目时已去掉「更新了」事件(
recordTimelineStream.ts:138),C 不改这一点。裁后执行。 选 C:同一 dev 在 PR #30158 上续改
recordTimelineStream.ts的分类与 pin,席位复核后落地。选 B:同上,改法换成 B。选 A:席位直接按现 PR 复核落地。os-decision-facets
the four chips),未在浏览器实测(PR 也未测)。Contract review: isolated
CONTRACT_REVIEW_TIERreview returned FAIL with 6 required corrections; all applied in this text (spec grep, example census, premise attribution, C's route and the second filter, facet ② evidence, stamp).Prior rulings read:
trackHistory,变更,field_changeon #30074 and #26676 → 1 ruling (6631104393); ADR none; thread: 6631104393.objectstack-fleet commentedon Nov 5, 2026
domain:ui#2 · os-justin · session_01QSeKYnE9YNkSZen7sijLA3
Release: claim 6635173915 (
session_01QSeKYnE9YNkSZen7sijLA3, seatdomain:ui#2) is released at clock-out, 2026-11-05T03:15Z, on the maintainer's 「当前任务处理完,合并后就下班」.Resume-from:
claude/issue-30074-crm-track-history@8c5b89c9State: the card stays on
needs-user-decision(analysis 6637710958, options A/B/C, seat recommends C). Draft PR #30158 carries option A as built, CI green on8c5b89c9, not ACCEPTed. After the ruling, a siblingdomain:uiseat re-claims, resumes from the line above, and has the same dev changerecordTimelineStream.ts(and the panel'schanges_onlyfilter) to the chosen letter. Its PR is serial with #30122 (record timeline).objectstack-fleet commentedon Nov 5, 2026
domain:ui#2 · os-justin · session_01QSeKYnE9YNkSZen7sijLA3
Ruling recorded · #30074 · letter C · 2026-11-05T04:24Z
The maintainer (os-justin) gave this ruling verbatim in seat
domain:ui#2's session: 「C」. It answers the seat's analysis 6637710958 (options A/B/C, recommendation C), which the maintainer was shown in the same session just before ruling.Ruling: option C. The timeline's 变更 chip holds only field-level changes with old and new values (audit-sourced,
source: 'sys_audit_log'). Activity-sourced events (created/updated/deleted/assigned/shared) always sit under 活动, whatever the history flag. With history off, 变更 has nothing to hold, so hiding it (ruling A of 6631104393, which this refines) is now accurate. Neither the CRM example'senable.trackHistoryon its four main objects nor the opt-in posture changes.Execution (from the analysis's 裁后执行 and its corrections):
claude/issue-30074-crm-track-history@8c5b89c9(draft PR feat(ui): open field history on the CRM's four main objects; the timeline's Changes chip follows trackHistory #30158; the Release is 6648707864).filterTimelineStream(ui/packages/plugin-detail/src/recordTimelineStream.ts) by source. ⛔ Do not editACTIVITY_TYPE_TO_FEED_TYPE, because that reaches 5+ existing pins and the platform checklist. Apply the same rule to the panel dropdown'schanges_onlyinRecordActivityTimeline.tsx, so the stream and the panel agree.recordTimelineStream-27055, itscreatedrow under 变更) to the new reading, as a substantive assertion. Add a pin that an activity row lands under 活动 with history on and off.State:
needs-user-decision→pm:queuein this stroke. The card is unassigned; anydomain:uiseat may claim it.objectstack-fleet commentedon Nov 5, 2026
domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC
Claim: PM loop round 4 (resume of released claim 6635173915)
Session:
session_01WGufzic59DvGAbu7U78pZtAccount:
os-support-ai(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/project-thread-7ocxgxWorktree:
objectstack-issue-30074Domain:
domain:uiSeat:
domain:ui#1Resume-from:
claude/issue-30074-crm-track-history@8c5b89c9(draft PR #30158; Release 6648707864). The new PR supersedes #30158; the seat closes #30158 once it is up.File surface: #30158's 13 files (
examples/app-crmfour objects'enable.trackHistory,ui/packages/plugin-detail/src/recordTimelineStream.ts,RecordActivityTimeline.tsx,renderers/record-activity.tsx,index.tsx, README,ui/packages/app-shell/src/views/RecordDetailView.tsxand their tests, the changeset); ⛔ACTIVITY_TYPE_TO_FEED_TYPEnot edited; no server, REST or spec change (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT(p2),effort: highClause-②: no
Metadata-protocol: none
Responsibility: the record timeline (
filterTimelineStream, the panel'schanges_only) puts activity-sourced rows under 变更 | ruling C (6649643659): 变更 holds only audit-sourced field changes with old/new values | every record page reader, and the CRM example as the reference posture, todayThread-read: 6649643659
Premise: holds (
filterTimelineStreamclassifies by feed type, not source, onorigin/main9b8a5867; #30158 at8c5b89c9carries ruling A only)Serial constraints cleared: none — only #30158 (superseded by this claim) touches
recordTimelineStream.ts/RecordActivityTimeline.tsx; #30122 goes after this card, not folded.objectstack-fleet commentedon Nov 5, 2026
domain:ui#1 · os-support-ai · session_01WGufzic59DvGAbu7U78pZt
os-dev-report
Stamp: 2026-11-05T05:15Z
objectstack-fleet commentedon Nov 5, 2026
domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC
Landed · PR #30294 · 2026-11-05T05:48Z
c1240a38acat 2026-11-05T05:47:51Z, via the merge queue (seat ACCEPT on PR #30294)completedbyFixes #30074origin/mainisFieldHistoryEntryinui/packages/plugin-detail/src/recordTimelineStream.tsenable.trackHistory(ruling A's half, resumed from feat(ui): open field history on the CRM's four main objects; the timeline's Changes chip follows trackHistory #30158, which is closed as superseded).changes_onlyoption is always empty there (PR feat(ui): the timeline's Changes chip holds the audit's field changes only; the CRM opens field history on its four main objects #30294's ACCEPT, item 5a). No card unless a maintainer asks.pm:dispatchedremoved in the same stroke.