Skip to content

fix(app-shell): a tracked record's field-change history shows again on a record page with no declared page - #29917

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-sm9sqn
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-sm9sqn

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread
Fixes #29878
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:打包应用(以及控制台里新建的应用)中,没有声明记录页的记录页面会重新显示声明了 trackHistory 的对象的字段变更历史。记录路由按名读取 sys_audit_log,并在首屏渲染前等它返回。
  • 为什么改:perf(app-shell): a packaged app's record route reads the objects pointing at the record through its package scope, not the registry #29846 / perf(app-shell): a console-created app's record route reads the objects pointing at the record from the bundle, not the registry #29697 让记录路由只读应用范围内的对象,不再读整份对象注册表,而这个范围里没有 sys_audit_log。于是记录页的「变更」筛选只剩活动摘要,用户会以为没人改过记录;同一条记录在抽屉里仍能看到变更。这是 18.3.0 的回归。
  • 风险与回滚:只改 app-shell 一个函数。对象没有 trackHistory 时请求不变;有的话多一次小的按名元数据读取,首屏要等它。读取被拒、不存在或失败时历史保持关闭,页面照常渲染,不会退回读整份注册表。回滚就是 revert 本 PR。
  • 先看:ui/packages/app-shell/src/console/appObjectScope.ts 的 readObjectRouteNames 末尾(新增的 streamRead 等待)和 recordStreamObjectNames;再看 AppContent.packagelessRecordRelated-29552.test.tsx 末尾新增的 describe。
  • 两分钟验证:cd ui && pnpm exec vitest run packages/app-shell/src/console/__tests__/AppContent.packagelessRecordRelated-29552.test.tsx,预期 23 个用例全绿;新增的 6 个用例在 main 上有 4 个是红的。
  • 需要你做的:无,席位落地。

What changes

File Change
ui/packages/app-shell/src/console/appObjectScope.ts The record arm with no declared record page passes synthesizedRecordPage. In readObjectRouteNames, once the route object's definition is in hand and it declares enable.trackHistory === true, sys_audit_log is asked by name (views: false) through the same scope as the lookup targets, and the route's ready waits on it (routeReadDone). The route's failedNames and routeObjectRefusal still read the route's own object only.
.../__tests__/AppContent.packagelessRecordRelated-29552.test.tsx New top-level describe (the #29552 harness: real MetadataProvider under the real AppContent). The harness gains a refuse map for by-name reads and records the objects each record-view render is handed.
.../views/RecordDetailView.timelineCount-28737.test.tsx Surface addition approved by the seat: the page payload's timeline asks fieldChanges: true for a tracked object when sys_audit_log is in the objects, with a CONTROL that asks false without it. boot() takes the objects.
.changeset/29878-record-route-field-history.md @object-ui/app-shell patch.

Only sys_audit_log was missing. The step-1 census of every record-page reader that looks an object up by name in the scoped list found no other: resolveFeedWriteGates gives the same verdict for sys_comment and sys_comment_reaction whether the schema is present or absent (both are managedBy: 'platform', the absent default). The reaction store's presence uses its own by-name read. Lookup targets, inbound objects and cascade labels are already in the scope.

A declared record page never held sys_audit_log, and that is not a gap: an assigned page has no history consumer. pageColumnsEligible = !assignedPage gates the timeline ask, and the audit read needs !assignedPage.

Tests

  • Harness describe a tracked object's record route asks sys_audit_log by name (objectstack#29878):
    • packaged and package-less: the record view's FIRST render holds sys_audit_log, the object calls are exactly the package list (packaged) plus object/sys_user and object/sys_audit_log, there is no object (registry) call, and the related lists keep the registry's set and order.
    • CONTROL: an object without trackHistory asks nothing more.
    • A refused read (403 PERMISSION_DENIED) and an absent object (404) keep it out of the objects and still draw the page, with no registry read.
    • CONTROL: the drawer still reads the registry.
  • Red at main: the final test files run against main's appObjectScope.ts (00f72f94): 4 failed / 25 passed (29), across both files.
  • Ablation 1, the stream name dropped (recordStreamObjectNames returns []): 4 failed / 19 passed. Restored blob 7f44431a, git diff HEAD empty.
  • Ablation 2, the wait dropped (ready without routeReadDone(streamRead)): both first-render cases red (2 failed / 21 passed). This proves the pin holds the wait, not only the ask.

Per-PR pin, not the live spec: ui/e2e/live/record-history-display.spec.ts stays on test:e2e:live:nightly. Moving it to :ci would add a live backend to every PR for a contract the unit pin above holds in Test UI (gates are added only when named by a maintainer).

Acceptance notes

  • Route reads, measured in the harness, in order, before and after:
    • Before, packaged: page, app, object?package=app.crm, object/sys_user. First render: the route object plus the related objects; sys_user (a lookup target) lands later.
    • After, packaged: the same, plus object/sys_audit_log, sent in the same render as the sys_user lookup ask, after the package list and the route object answer. First render waits on the package list, the route and related names, and the sys_audit_log answer. That is one extra by-name round trip, in parallel with the lookup target, and only for a tracked object.
    • Package-less: the same, minus the package list.
  • Live showcase not re-run here. QA's reproduction at 589049b0 names the route, and the harness reproduces it.

Verification

Local gate union at f8dab480 (see the report comment on #29878).


Generated by Claude Code

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Nov 3, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

席位核对 · ACCEPT · 2026-11-03T12:02Z

Head-sha: f8dab480177c2a43e48a4ad9f3bc223476c45025

  • Shape: draft → main, and Fixes #29878 is the only closing keyword in the body. 4 files, +185 / −10. check-governed-merges --pr 29917 reports NOT governed, at 195 lines, so this is an ordinary queue landing. Clause-②: no and Metadata-protocol: none are line-initial in the body and the changeset, as in claim 6600797360. No contract review is owed: the change is app-shell internal, nothing is exported, and no locale key is added. The changeset is a patch for @object-ui/app-shell.
  • Serial: PR fix(app-shell): a route whose object read the server answered shows the server reason, not "check your connection" #29883 landed first. The branch merged main once (a clean auto-merge on appObjectScope.ts), and every gate was re-run on the merged head.
  • File surface: the claim's three files, plus RecordDetailView.timelineCount-28737.test.tsx, which the seat approved before the edit. The PR body names it.
  • Read in the diff:
    • Only the record arm with no declared record page passes synthesizedRecordPage. An assigned page has no history consumer (pageColumnsEligible = !assignedPage).
    • sys_audit_log is asked by name only when the route object declares enable.trackHistory === true, and never for sys_audit_log itself. It is asked through the same views: false scope as the lookup targets, in the same render, so the two reads go in parallel.
    • ready waits on the stream read (routeReadDone), so the first render's fieldChanges ask sees the object.
    • routeOutcome still reads only the route's own object. A refused, 404 or failed audit read keeps history off and draws the page. Nothing falls back to the registry.
  • Tests (dev, report 6603077215):
    • 6 new harness cases. 4 are red at main's appObjectScope.ts, and the file passes 23/23. The 28737 plan-body case has a false CONTROL.
    • Ablation 1 (name dropped) reds 4. Ablation 2 (wait dropped) reds both first-render cases. Both were restored to the HEAD blob.
    • 59 affected files pass, 507/507. App-shell type-check is 65/65. All 35 derived gates exit 0, with 0 NOT-MEASURED.
  • Read cost: one extra by-name metadata round trip, for tracked objects only, in parallel with the lookup read. Untracked objects ask nothing more (CONTROL). The live spec stays nightly, and the body says why.
  • Gates (check-runs on this head): 44 success, 11 skipped (opt-in and schedule gates), 0 failed, 0 pending. Test UI, Lint & Repo Gates, TypeScript Type Check and Type Check UI all succeeded. mergeable_state is clean.
  • Accepted deviation: the live showcase was not re-run. The harness reproduces QA's measure at 589049b0.
  • Out of scope: none reported. Nothing to file.
  • Landing: pr_ready, then automerge_enable. Merging closes Console: a packaged app's record page no longer shows a record's field-change history (Changes chip), since the record route reads only its package scope (#29846) #29878.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 12:04
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 12:04
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit a7e984a Nov 3, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-sm9sqn branch November 3, 2026 12:28
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

Projects

None yet

1 participant