Skip to content

fix(console): a child written through an authored related list refreshes the record page's header - #27106

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-w7hign
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-w7hign

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #26890

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:记录页在子记录写入后重读父记录时,除了按关系推导出的相关列表,现在也盯住作者自己在页面上列出的相关列表块和侧栏引用卡片里的子对象。
  • 为什么改:作者自建的记录页可以列出一个外键写了 relatedList: false 的子对象,这时它不在推导集合里,经该列表新建、编辑或删除子记录后,表头的汇总值(如报价总额)要刷新页面才会变。
  • 风险与回滚:只扩大订阅的子对象集合,最坏多一次父记录读取;推导路径与 12244 的行为不变;回滚即 revert 本 PR。
  • 先看:RecordDetailView.tsx 中 relatedChildObjectsKey 那一段;新文件 recordPageChildObjects.ts;RecordDetailView.authoredChildWriteRefreshesParent-26890.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/RecordDetailView.authoredChildWriteRefreshesParent-26890.test.tsx,预期 4 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

  • New recordPageChildObjects(page) beside the view: the child objects a record page lists itself, read from every record:related_list block (properties.objectName, or the node's dataSource.object binding that the block's element data-source gate maps onto objectName) and every record:reference_rail entry's objectName, at any depth of the page. Sorted and deduplicated.
  • RecordDetailView's child-write subscription (objectui#12244) now watches the union of the derived related lists' children and those, still keyed by content (AGENTS.md chore: version packages #10). Nothing else in the view moves.

Why

objectui#12244 re-reads the parent on a child write, but it watched only the children deriveRelatedLists derives from the relationship graph. An authored record page declares its own lists, and a child it lists there need not be derived: its FK can opt out of the derived lists with relatedList: false, the authored page then being the only place the child is shown. A create, edit or delete through such a list reached the invalidation bus under the child's name and matched nothing, so the header kept the pre-write rollup until a reload, as the card reports.

Reproduced first on origin/main cbb3a1a1 with the new test file: the create, update/delete and rail cases were red (expected 1 to be 2 parent reads). A one-off control copy of the same file without relatedList: false (child derivable from the graph) was green on those three cases on the same tree, so the gap is specific to a child the authored page alone declares. HotCRM's own quote-line metadata was not read here (it lives outside this repository), so whether its FK carries relatedList: false is not measured; the fix covers the shape the dispatch named.

Tests

  • New RecordDetailView.authoredChildWriteRefreshesParent-26890.test.tsx (4 tests, real page + real MutationEvent bridge, a fake backend that recomputes the rollup): a create of a line listed only by the authored related list re-reads the parent once and the header shows the new total in place; an update and a delete each re-read it; a write to a child listed only in the reference rail re-reads it; a write to a child that points at the parent but is listed nowhere does not.
  • New recordPageChildObjects.test.ts (2 tests): every related list and rail entry at any depth including the dataSource.object arm, sorted and once; other blocks' objects (element:chart, record:line_items) and non-string values are not named.
  • RecordDetailView.childWriteRefreshesParent-12244.test.tsx still green (5/5) beside the new file.
  • Ablation (one-off, not kept): with , ...authoredChildObjects]) replaced in RecordDetailView.tsx via scripts/ablation-replace.mjs (anchor 1 → 0, mutation blob landed, restored blob equal to HEAD, git diff HEAD empty), the new page file goes 3 failed / 1 passed: create, update/delete and rail red, the scope control green.

Acceptance notes

  • The subscription covers what the authored page lists, not every object a page binds: a chart or metadata viewer on the record page is not a child list, and a write to its object does not re-read the record.
  • record:line_items on a record page is not added: its save writes the parent leg too, which the record's own invalidation already catches.

Verification

On head 24529675 (main merged): 24 gates derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, exits captured before any pipe; check-sdui-manifest 0. 23 exit 0; pnpm --dir ui exec vitest run scripts/ exited 1 on three tests in check-i18n-dead-keys.test.ts and check-vi-mock-inherit.test.ts that timed out at 15 s while another suite shared the box (5031 others passed); those two files rerun alone: 223/223 passed. --ran reconciles 24 derived / 24 run / 0 NOT-MEASURED. The whole RecordDetailView* suite plus the helper test: 76 files, 505 tests passed. Extra: pnpm --filter @object-ui/app-shell run type-check 0, eslint on the four touched files 0 errors (warnings pre-existing, none on touched lines), pnpm --dir ui run check:new-line-citations 0 new. @object-ui/app-shell is private: true, so skip-changeset.


Generated by Claude Code

…hes the record page's header

The record page re-read its record on a child write only for the children
the relationship graph derives. An authored page's related-list blocks and
reference-rail entries can list a child whose FK opts out of the derived
lists, so a create through that list left the header's rollup stale until
a reload. The child-write subscription now also watches the children the
page itself lists.

Claude-Session: https://claude.ai/code/session_01JQwxbZdCt7JoRJa32i5BHs
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 28, 2026
@objectstack-fleet

objectstack-fleet Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Two Test UI shards are red on head 2452967, and neither failure is this PR's:

  • Test UI (13/16): PermissionMatrixEditor.packageDoorDraftBase-26837.test.tsx (Unable to find an element with the text: a_account)
  • Test UI (12/16): PermissionMatrixEditor.objectGrantInherited-27010.test.tsx

Neither file is in this PR's tree; CI picked them up from main through the merge ref. main is red on the same two files on its own runs (shards 3 and 5 on 5fe0b96, 6e5f909 and f2fdac5), tracked as #27095. This PR touches only RecordDetailView.tsx, a helper beside it and tests.

The fix is the open draft #27103, which adds withPreviewDrafts to those two test fakes. It is test-only and outside this card's claimed file surface, so it is not ported here. Re-running the failed jobs would reuse the same merge ref, so none is requested now. Once #27103 lands, main will be merged into this branch so CI runs against the fixed base.

(Edited: the first version of this comment lost its code spans to a shell quoting slip.)

Update, head 074f09f (main merged after #27103 landed): the two permission-matrix shards are green. One shard is red:

  • Test UI (16/16): AppReportIndex.navGates-26892.test.tsx, expected [ 'Alpha', 'Beta', 'Gamma' ] to deeply equal [ 'Alpha', 'Beta', 'Delta', 'Gamma' ]

This PR does not touch that file or the report index. On main the same file fails intermittently: red on shard 10 at 53e0663 and 0fb0dda, green on 6817bf1 and 5193634. The merge queue has filed it as #27116. No fix exists yet. The failed jobs of run 40646258425 are re-run once.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · seat domain:ui#5 · session_01JQwxbXGpVJHZTXXerMoY4C · head 074f09ff · 2026-10-28T07:06Z

席位意见:记录页写入子记录后重读父记录(objectui#12244)原先只监听关系图推导出的相关列表;页面作者自己放的相关列表块和引用栏条目里的子对象(外键设了 relatedList: false,只在该页面出现)不在其中,所以在那里新建子记录后表头汇总不刷新。现在两者合并监听。

  1. Shape: Fixes #26890; 4 files, +500/−3: RecordDetailView.tsx +10/−2 (the subscription's child set only, inside the claimed region), a new pure helper views/recordPageChildObjects.ts (54 lines) with its unit test, and one new integration test (351 lines). skip-changeset: @object-ui/app-shell is private: true.
  2. Premise measured: the order's first mechanism held, and the dev reproduced it on origin/main cbb3a1a1: a child that only the authored page lists (a record:related_list block, or a record:reference_rail entry) whose FK has relatedList: false never re-read the parent; a derivable child already did. The seat read the diff: recordPageChildObjects walks the effective page at any depth for record:related_list (properties.objectName or dataSource.object) and record:reference_rail entries, memoised on the page; the set is keyed by content as before, so a dispatch still re-reads once. Dev: 6 new tests; dropping the authored set turns 3 red, restored.
  3. Gates / check-runs: PR check-runs on 074f09ff: 43 success, 12 skipped, 0 failure; Lint & Repo Gates, TypeScript Type Check, Type Check UI and Test UI success. The head is a merge of origin/main (after test(app-shell): give the new package-door matrix doubles the draft-overlaid read #27103) into the reviewed 24529675; the PR diff is byte-identical (same patch-id). One Test UI 16/16 red on the way was the Queue-flake anchor: packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx #27116 race in the "All reports" index lists reports the app's navigation hides from the persona, which then open empty #26892 suite (not this PR), passed on the single re-run. Dev's local run: 24 derived, 24 run, 0 NOT-MEASURED (the ui scripts suite had 3 load timeouts; the two files rerun alone, 223/223); 76 RecordDetailView files / 505 tests green.
  4. Accepted deviations: a related-list block or rail entry naming an object that is not a child (a rail entry can name a lookup's target) now also re-reads the record on that object's writes: one extra read, never a wrong value, and the same trade objectui#12244 made for a '*' change.
  5. Out of scope: whether HotCRM's own quote-line FK carries relatedList: false was not measured (outside this repo): Acceptance notes; if HotCRM still reproduces after this lands, the hotcrm seat re-files with its shape. Skills-docs: none — record page refresh only.
  6. Landing: not Tier H (no governed path, +503 lines); ready for landing (ready + auto-merge).

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 28, 2026 07:07
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 28, 2026 07:07
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 40648195810 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test UI (16/16) — 失败步骤: Run the ui tests (shard 16/16)

    × (f) CONTROL: offers it, once, to a member granted that permission 63ms
    FAIL   dom  packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx > ⌘K offers its navigation entries as the sidebar shows them (objectstack#26892) > (f) CONTROL: offers it, onc
      ↳ 失败原因: AssertionError: expected [ 'Alpha', 'Beta', 'Gamma' ] to deeply equal [ 'Alpha', 'Beta', 'Delta', 'Gamma' ]
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 25 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 28, 2026
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 1900c02 Oct 28, 2026
86 of 88 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-w7hign branch October 28, 2026 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants