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
Commit c1240a3
Browse filesBrowse the repository at this point in the historyBrowse files
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)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBVmFoQ6NLiPf5VfPaWXaynQ)_Fixes#30074
Supersedes #30158
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么:** 记录动态里的「变更」只放审计日志读出的字段变更(带旧值→新值);「创建了 / 更新了 / 删除了 / 分配了 /
共享了」这类活动事件一律归「活动」,不论对象是否开启字段历史。面板下拉的「字段变更」用同一规则。另保留 #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@8c5b89c` (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 #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 #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).
<details><summary>Test readings</summary>
- 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.
</details>
## 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 #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).
<details><summary>Gate readings</summary>
- 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.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzic59DvGAbu7U78pZt)_
_#30158 closed as superseded by the seat._
<!-- signed: domain:ui#1 · os-support-ai ·
session_01WGufzfE4V9fzZgJLrLDmMC -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(record page): the timeline's Changes chip holds the audit's field changes only, and is offered only while the record shows its field history
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
-**`@object-ui/plugin-detail`: what Changes holds.** The timeline stream's Changes chip, and the activity panel's `changes_only` filter, hold the field changes read from the audit log (each with its before and after values) and nothing else. The activity rows that share their look (created, updated, deleted, assigned, shared) are events, so they sit under Activity whether or not the record shows its field history; before, they sat under Changes and, on the panel, under `changes_only`.
11
+
-**`@object-ui/plugin-detail`: `RecordFieldHistoryContext`.** A host tells the record page's timeline stream whether it shows the record's field history; while it says `false`, the stream draws the All / Activity / Discussion chips and no Changes chip, and a stream left on Changes goes back to All. With no provider every chip draws, as before. `RecordActivityTimeline` takes the chips to offer as `streamFilters` (all four when absent).
12
+
-**The Console's record page** hands over its `enable.trackHistory` gate, so an object that does not opt in no longer offers a Changes chip on its timeline tab.
13
+
-**The CRM example** opens `enable.trackHistory` on `crm_lead`, `crm_account`, `crm_contact` and `crm_opportunity`, so their record pages show each field change with its before and after values. The flag stays opt-in for every other object.
0 commit comments