Skip to content

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

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-7ocxgx
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-7ocxgx

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #30074
Supersedes #30158

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么: 记录动态里的「变更」只放审计日志读出的字段变更(带旧值→新值);「创建了 / 更新了 / 删除了 / 分配了 / 共享了」这类活动事件一律归「活动」,不论对象是否开启字段历史。面板下拉的「字段变更」用同一规则。另保留 feat(ui): open field history on the CRM's four main objects; the timeline's Changes chip follows trackHistory #30158 的裁决 A 部分:CRM 示例四个主对象开启 enable.trackHistory,未开启历史的对象不显示「变更」筛选。
  • 为什么改: 按裁决 C(6649643659,维护者原话 「C」),它细化裁决 A(6631104393):分类由记录本身的来源决定,不随开关变;没开历史时「变更」本就无内容,隐藏它名副其实。
  • 风险与回滚: 风险低:只改前端筛选规则与示例标志,不改数据、接口、spec,也没动 ACTIVITY_TYPE_TO_FEED_TYPE 映射表;回滚即撤销本 PR。注意:在非默认记录页上(自取 sys_activity 的面板),下拉选「字段变更」今后为空,因为那里不读审计(见 Acceptance notes)。
  • 先看: ui/packages/plugin-detail/src/recordTimelineStream.ts(新判定 isFieldHistoryEntry 与 filterTimelineStream)、ui/packages/plugin-detail/src/RecordActivityTimeline.tsx(面板 changes_only 同规则)、ui/packages/plugin-detail/src/__tests__/recordTimelineStream-27055.test.tsx(翻转的「四个筛选」pin 与新 pin)。
  • 两分钟验证: pnpm --dir ui exec vitest run packages/plugin-detail/src/__tests__/recordTimelineStream-27055.test.tsx,预期全部通过;或 pnpm dev:crm 后新建一条线索并改公司名,打开动态页签:「变更」里是「公司 旧 → 新」,「活动」里是「创建了 线索…」。
  • 需要你做的: 无,席位落地。

What changes

Resumed from claude/issue-30074-crm-track-history@8c5b89c9 (draft PR #30158, option A as built), merged with origin/main at 9b8a5867, then the ruling C delta on top.

  • Ruling C, the stream. New isFieldHistoryEntry(item) in recordTimelineStream.ts: an item is a field-history entry when it is a field_change read from the audit log (source: 'sys_audit_log', which auditEntriesToFeedItems stamps). filterTimelineStream uses it: Changes holds those only, Activity holds everything that is neither a comment nor one of those. The sys_activity rows that share the field_change presentation (created, updated, deleted, assigned, shared) are therefore Activity, whatever the history flag. ACTIVITY_TYPE_TO_FEED_TYPE is not edited.
  • Ruling C, the panel. RecordActivityTimeline's changes_only dropdown filter uses the same predicate, so the panel and the stream agree on what a change is.
  • Kept from feat(ui): open field history on the CRM's four main objects; the timeline's Changes chip follows trackHistory #30158 (ruling A): the CRM example's crm_lead, crm_account, crm_contact, crm_opportunity declare enable: { trackHistory: true }; RecordFieldHistoryContext plus timelineStreamFilters() hide the Changes chip while the record shows no field history, a stream left on Changes falls back to All, and RecordDetailView hands over its historyEnabled.
  • Changeset .changeset/30074-crm-track-history.md (from feat(ui): open field history on the CRM's four main objects; the timeline's Changes chip follows trackHistory #30158) amended to ruling C (patch, Clause-②: no, Metadata-protocol: none); plugin-detail README says what Changes holds.

File surface: #30158's 13 files plus two existing plugin-detail test files whose pins read the old classification (renderers/__tests__/record-activity.test.tsx, renderers/__tests__/recordChatterFilterModeMentions-8968.test.tsx); see Tests. No server, REST or spec change.

Tests

All readings on head 93bbdd78, the final commit.

  • Flipped pin ("the four chips", recordTimelineStream-27055.test.tsx): the activity created row now sits under Activity, Changes holds the audit change alone, and every entry lands under exactly one of Activity / Discussion / Changes.
  • New pins (same file, top-level describe): with history on AND off, Activity lists every one of the five activity events and no audit change; CONTROL with history on, Changes holds the field change with its old and new values and none of the events; the panel's changes_only keeps the same rule as the stream.
  • Fixture triage, two existing files. record-activity.test.tsx: its "filterMode seeds which slice" pin seeded changes_only and expected the activity updated row, which ruling C moves out; it now seeds tasks_only (still proves the seed), and a new pin asserts changes_only holds no activity event. recordChatterFilterModeMentions-8968.test.tsx: its field-change fixture row gains source: 'sys_audit_log', the stamp of the one producer of fieldChanges in the tree (auditEntriesToFeedItems); its assertions are unchanged.
  • Ablation via scripts/ablation-replace.mjs on recordTimelineStream.ts, anchor item.type === 'field_change' && item.source === 'sys_audit_log' replaced by item.type === 'field_change' (the subject resolves to src through the ui/vitest.config.mts aliases, so no dist leg): anchor 1 to 0, blob 16c475ce1d79 to aee54ae67d0a; 6 tests red, exactly the flipped and new pins; the 8968 file green (its row is a real field change either way). Restored: blob equals HEAD 16c475ce1d79, git diff HEAD empty.
  • Live (example CRM, objectstack dev --seed-admin --fresh, Vite console, zh-CN): a new crm_lead (history on) edited once through the data API: chips 全部 / 活动 / 讨论 / 变更; 变更 shows 「公司 Ruling C Ltd → Ruling C Holdings」 and the creation's field values; 活动 shows 「创建了 线索 "Live Check Lead"」 only. A new crm_activity (history off) edited once: chips 全部 / 活动 / 讨论, no 变更; 活动 shows 「更新了 活动 …:主题」 and 「创建了 活动 …」. On the history-on lead the updated activity row steps aside because the audit says the same update with values (mergeTimelineStream, unchanged).
Test readings
  • Targeted, the three touched test files: Test Files 3 passed (3), Tests 69 passed (69), lock VERDICT command-exit 0.
  • @object-ui/plugin-detail whole package plus the app-shell pin file RecordDetailView.recordPageTiers-26678.test.tsx: Test Files 370 passed, 1 skipped (371); Tests 3292 passed, 18 skipped (3310).
  • Type-check: @object-ui/plugin-detail exit 0; @object-ui/app-shell exit 0 after the full build (both tsc --noEmit and tsc -p tsconfig.test.json). A first app-shell run before the full build failed on unbuilt @object-ui/* declarations (TS2307), not on this diff.
  • Ablation run: Test Files 2 failed, 1 passed (3); Tests 6 failed, 63 passed (69); restore proven as above.

Acceptance notes

  • The panel's changes_only off the default record page. Off the synthesized record page the record:activity panel holds sys_activity rows only and reads no audit, so under ruling C its 字段变更 slice is empty there; before, it listed the activity created / updated rows. This follows the ruling's "same rule for the panel" and is pinned; whether that panel should offer the option at all where no audit is read is left to the seat.
  • Bears on Record timeline: compact rows name no actor, and an audit reader sees every action twice #30122 (not folded). On a history-on lead created through the data API, 全部 says the creation twice: the activity row 「创建了 线索…」 and an audit entry listing each initial value (「负责人 — → Dev Admin; 线索名称 — → …」). auditEntriesToFeedItems keeps any audit entry with changes, the create entry included, although its doc says the creation is the activity stream's own row. Under ruling C the audit half sits under 变更 and the activity half under 活动. Compact rows did name the actor in this run (「· Dev Admin · 刚刚」).
  • main moved 14 commits after the merge at 9b8a5867; none touches a path in this diff (git diff --name-only HEAD...origin/main against the 15 paths: zero hits) and git merge-tree is clean, so the branch was not re-merged. CI runs the merge ref.
  • Screenshots: kept in the project files, not committed.

Verification

Gates from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, derived at 93bbdd78 (53 commands).

Gate readings
  • 53 of 53 run serially after a full build, each exit code captured before any pipe: all 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)".
  • node scripts/check-sdui-manifest.mjs exit 0; pnpm --dir ui exec vitest run scripts/: Test Files 170 passed, 2 skipped (172); Tests 4985 passed, 2 skipped.
  • pnpm --dir ui run check:new-line-citations: 0 new citations, exit 0.
  • check-platform-checklist not owed: no export renamed or removed (isFieldHistoryEntry is added to the module, not to the package entry).
  • Not run locally, CI's: Build Core, Type Check UI and the workspace type-check lanes, Test Core, Test UI (whole app-shell), the repo-wide lint.

Generated by Claude Code

#30158 closed as superseded by the seat.

crm_lead, crm_account, crm_contact and crm_opportunity declare
enable.trackHistory, so their record pages show each field change with its
before and after values.

Claude-Session: https://claude.ai/code/session_01QSeKYnE9YNkSZen7sijLA3
Co-authored-by: Claude <noreply@anthropic.com>
… is shown

RecordDetailView hands its trackHistory gate (historyEnabled) to the stream
through RecordFieldHistoryContext; record:activity drops the changes chip
while it is false and falls back to all when the chosen chip goes away.

Claude-Session: https://claude.ai/code/session_01QSeKYnE9YNkSZen7sijLA3
Co-authored-by: Claude <noreply@anthropic.com>
… only; activity events sit under Activity

Ruling C on the record timeline: an item is a field-history entry when it is a
field_change read from the audit log (source 'sys_audit_log'). The stream's
Changes chip and the panel's changes_only hold those and nothing else; the
activity rows that share the field_change presentation (created, updated,
deleted, assigned, shared) sit under Activity whatever the history flag.
ACTIVITY_TYPE_TO_FEED_TYPE is unchanged.

Claude-Session: https://claude.ai/code/session_01WGufzic59DvGAbu7U78pZt
Co-authored-by: Claude <noreply@anthropic.com>
…nd README follow ruling C

Claude-Session: https://claude.ai/code/session_01WGufzic59DvGAbu7U78pZt
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC

维护者速读(席位终稿)

  • 改了什么:记录页「动态」的「变更」筛选现在只放字段修改(带旧值和新值,来自审计日志);「创建了 / 更新了 / 删除了 / 分配了 / 共享了」这类事件一律在「活动」里。对象没开字段历史时,不显示「变更」筛选。CRM 示例的线索、客户、联系人、商机四个对象开启字段历史。
  • 为什么改:按你对 [Decision] Field changes are recorded but not shown unless an object opts in: who decides a record shows its change history? #30074 的裁决 C(6649643659);它细化了原裁决 A。接续 os-justin 的 feat(ui): open field history on the CRM's four main objects; the timeline's Changes chip follows trackHistory #30158(已作为被取代关闭)。
  • 风险与回滚:低。只改前端筛选归类和示例对象的一个开关,不改数据、接口和 spec;没动 ACTIVITY_TYPE_TO_FEED_TYPE。回滚即 revert 本 PR。
  • 先看:ui/packages/plugin-detail/src/recordTimelineStream.ts 的 isFieldHistoryEntry 和 timelineStreamFilters。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-detail/src/__tests__/recordTimelineStream-27055.test.tsx。
  • 需要你做的:无。不是受管面,席位直接落地。
  • 席位意见:ACCEPT。一个后续可选项见下方第 5 条。

席位核对 · ACCEPT · head 93bbdd78 · 2026-11-05T05:19Z

  1. Shape: draft, base main, Fixes #30074, Supersedes #30158 (closed by the seat this act; body edited to re-fire PR-body guards, whose first run was red on the duplicate-fix step as expected). Changeset kept from feat(ui): open field history on the CRM's four main objects; the timeline's Changes chip follows trackHistory #30158, amended to ruling C, Clause-②: no / Metadata-protocol: none. 15 files, +256/−18. check-governed-merges --pr 30294: NOT governed.
  2. Read by the seat: isFieldHistoryEntry = type === 'field_change' && source === 'sys_audit_log' (the stamp auditEntriesToFeedItems sets); filterTimelineStream's changes and activity arms split on it, so an activity-sourced field_change-shaped row lands under 活动. The panel's changes_only uses the same predicate. RecordFieldHistoryContext (default true) carries the host's historyEnabled; the renderer drops 变更 from the chips when false and falls a chosen-but-withdrawn chip back to 全部. ACTIVITY_TYPE_TO_FEED_TYPE untouched. Shots lead-history-on-changes.png (变更 holds only the two audit diffs) and activity-history-off-all.png (no 变更 chip; created/updated rows present) match the ruling.
  3. Accepted deviations: two more existing plugin-detail test files touched (record-activity.test.tsx reseeded to tasks_only plus a new changes_only pin; the test(publish-smoke): build the scaffolded project, and assert its identity first #8968 fixture row gains source: 'sys_audit_log'). Both follow from the predicate change and stay inside the claimed package.
  4. Evidence: plugin-detail full suite 370 files / 3292 tests green; plugin-detail + app-shell type-check exit 0; ablation red on exactly the 6 flipped/new pins, restored; dispatch-gates 53/53 exit 0 with --ran zero NOT-MEASURED. CI on this head running at review time; auto-merge waits on it.
  5. Out of scope / open: (a) off the default record page the panel reads no audit, so its changes_only option is always empty there. Landing as ruled; a follow-up only if a maintainer wants that option hidden or fed on assigned pages. (b) For Record timeline: compact rows name no actor, and an audit reader sees every action twice #30122: on a history-on lead the creation is said twice under 全部 (activity created row plus the audit create entry with initial values); noted on Record timeline: compact rows name no actor, and an audit reader sees every action twice #30122, not folded. Skills-docs: n/a.
  6. Landing: ordinary queue, now: relay pr_ready + automerge_enable, follow to MERGED, landing record on [Decision] Field changes are recorded but not shown unless an object opts in: who decides a record shows its change history? #30074. Then Record timeline: compact rows name no actor, and an audit reader sees every action twice #30122 is unblocked for this seat.

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

Labels

documentation Improvements or additions to documentation size/m tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Decision] Field changes are recorded but not shown unless an object opts in: who decides a record shows its change history?

1 participant