Skip to content

fix(ui): object-form drawer and modal saves toast the record-named save message - #28204

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-228tzt
Oct 30, 2026
Merged

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

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN

Requested by Tesla · project thread

Fixes #27453

Clause-②: yes (widening: @object-ui/i18n gains the public export recordSaveMessage; no accept set moves)
Metadata-protocol: none

维护者速读

  • 改了什么:object-form 块以抽屉或弹窗打开(formType: 'drawer' | 'modal',含 React 页面里 formType="drawer" 的 ObjectForm)保存成功后,现在弹出点名记录的成功提示,如 Task "Fix login" updated,与控制台记录对话框一致;提示文案改由 @object-ui/i18n 新导出的 recordSaveMessage 统一生成,控制台的两份手抄副本一并换成它。
  • 为什么改:showcase 的任务台页面在抽屉里改任务、在弹窗里建任务,写入成功却没有任何反馈,违反「每次落地的写都要有点名记录的反馈」;React 页面的作用域里拿不到 toast,页面自己补不上,所以按分诊在平台补。
  • 风险与回滚:只在块的抽屉/弹窗容器里加提示;控制台自己挂的 ModalForm、动作弹窗、设计器表单都不经过这个容器,不会双重提示(有用例钉住);带 submitHandler 的表单不提示;回滚即 revert 本 PR。
  • 先看:ui/packages/plugin-form/src/ObjectFormContainer.tsx 的 useContainerSaveFeedback、ui/packages/i18n/src/recordSaveMessage.ts、AppContent.tsx 里改为调用共享构造器的 recordSaveMessage。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-form/src/__tests__/containerSaveToastNamesRecord-27453.test.tsx,预期 13 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

The triage ruling (6523407987) asked for the console's named-save feedback to become reachable from the platform: the drawer and modal forms raise the same save feedback the record pages do, through one exported helper rather than a second copy of recordSaveMessage, with no change to the showcase page.

  • @object-ui/i18n adds recordSaveMessage(t, { label, objectDef, saved, mode }): the record-named message (form.createdNamed / form.updatedNamed), or the object-label one (form.createSuccess / form.updateSuccess) when the ADR-0079 resolver finds nothing nameable (its Record #ID floor). It lives in @object-ui/i18n because its four keys live in that package's packs, it already depends on @object-ui/core (where getRecordDisplayName lives), and both @object-ui/plugin-form and @object-ui/app-shell already depend on it. No new dependency edge.
  • @object-ui/app-shell: the console's record dialog (AppContent's recordSaveMessage) and the record form page (RecordFormPage's handleSuccess) each carried their own copy of that logic; both now call the shared builder. Same keys, same floor, same wording; the Console: a save that passes a warning-severity rule shows one toast, the record-named save toast carrying the warning #27999 warning claim and toastRecordSave are untouched.
  • @object-ui/plugin-form: ObjectFormContainer, the container the object-form block supplies for formType: 'drawer' | 'modal' (ADR-0050 Decision 4), now wraps the author's onSuccess: when the container itself wrote (it has a data source and no submitHandler, in create or edit mode) it toasts the record-named message, then runs the author's onSuccess. A declared successMessage is used as written. The object definition is read through getObjectSchema, which the adapter has already cached for the form body. DrawerForm and ModalForm themselves are unchanged, so a host that mounts them directly (the console's record dialog, useActionModal, the designer's object and field forms) keeps owning its feedback and is never toasted twice.

examples/app-showcase is unchanged: its task-desk page's afterWrite keeps closing the overlay and reloading, and the toast now comes from the platform. No packages/spec edit.

Tests

  • ui/packages/plugin-form/src/__tests__/containerSaveToastNamesRecord-27453.test.tsx (new, 13 cases, each of the first six run for drawer and modal): edit toasts Task "Fix login" updated before the author's onSuccess, which still gets the record; create toasts Task "Write release notes" created; a metadata-only block with no onSuccess is toasted; a record with nothing nameable keeps Task created successfully; a declared successMessage is the toast; a submitHandler form is not toasted; a directly mounted DrawerForm with an onSuccess is not toasted.
  • Existing suites that pin the console wording now run through the shared builder unchanged: AppContent.recordSaveToastNamesRecord-26172, AppContent.recordSaveOneToast-27999 and every RecordFormPage.* suite (14 files, 84 tests, all green).

Ablation. With ObjectFormContainer's toast.success(message) replaced by a no-op through scripts/ablation-replace.mjs (anchor 1 → 0 on disk), the new suite went 10 red / 3 green: every "toasts" row red, the three "not toasted" rows green, as predicted before the run. Restored by the tool: blob equal to HEAD's, git diff HEAD empty.

Acceptance notes

  • Where the toast is raised, and why not inside DrawerForm / ModalForm. The ruling allowed either the form variants or a React-page toast hook; a hook would have needed the showcase page to call it, which the ruling rules out. Inside DrawerForm / ModalForm the toast would have fired for every host that mounts them directly and toasts itself (the console's record dialog through crud_success, useActionModal), so each would have needed an opt-out. ObjectFormContainer is the one place where the object-form block is the caller (ADR-0050 Decision 4), so the feedback sits there and the direct hosts are untouched; the last test row pins that.
  • Advisory write warnings on this path are not folded into the toast. The console's dialog and record page claim their write's answer warnings and carry them on the save toast (Console: a save that passes a warning-severity rule shows one toast, the record-named save toast carrying the warning #27999, claimAnswerWarnings in @object-ui/app-shell). The block container lives in @object-ui/plugin-form, below app-shell, and has no claim, so a warning from a drawer/modal save still arrives on the write-warning channel's own toast, as it did before this change. Noted, not filed.
  • successMessage on a drawer/modal block was never shown before. It is now the toast when declared; previously neither container read it.
  • Showcase unchanged (examples/app-showcase): its afterWrite keeps closing the overlay and bumping the reload counter. Live reproduction against the showcase backend was not run in this container; the behaviour is pinned through the same ObjectFormContainer route the React page's ObjectForm block takes.
  • @object-ui/app-shell is private: true, so the changeset bumps only @object-ui/i18n and @object-ui/plugin-form; the app-shell edit is a refactor onto the shared builder with no wording change.
  • Double-toast survey (e8e2bcb). Hosts that mount ModalForm / DrawerForm directly and own their feedback: AppContent (record dialog, via crud_success), useActionModal, the designer's FieldDesigner and ObjectManager. None of them passes through ObjectFormContainer, so none gets a second toast; the last test row pins a direct DrawerForm mount. Pages that use the block's container and gain a toast: the showcase task-desk page (drawer edit, modal create) and the showcase renewals page (ObjectForm formType="drawer"). Neither raised a toast of its own before, so neither is doubled.

Verification

Head e8e2bcbd (the branch merged with origin/main c48cd2c1; the four incoming commits touch no file under ui/).

  • New suite: 13/13 green. Ablation as described above: 10 red / 3 green, restored to HEAD.
  • pnpm --dir ui exec vitest run packages/plugin-form/ packages/i18n/: 306 files. The first run had 2 failures in residue-namespaces-3546.test.tsx, which pins AppContent's fallback spelling t('common.record', { defaultValue: 'Record' }); the spelling is restored in AppContent and that file plus AppContent.recordSaveToastNamesRecord-26172 re-ran 52/52 green.
  • pnpm --dir ui exec vitest run on AppContent.recordSaveToastNamesRecord-26172, AppContent.recordSaveOneToast-27999 and packages/app-shell/src/views/RecordFormPage*: 14 files, 84 tests, green.
  • type-check (with a real .d.ts build of the @object-ui/app-shell^... closure): @object-ui/i18n, @object-ui/plugin-form, @object-ui/app-shell exit 0.
  • eslint --no-inline-config on the six touched source files: 0 errors. pnpm --dir ui run check:new-line-citations: 0 new.
  • Derived gates on e8e2bcbd, every line run with stdin from /dev/null, exit codes on disk, reconciled with dispatch-gates.mjs --ran: 34 derived, 34 run, 0 NOT-MEASURED.
Derived gates (local, head e8e2bcb)
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

Generated by Claude Code

…and modal saves

The object-form block's drawer and modal containers now give a landed
write the record-named toast the console's record dialogs and record page
give, through one shared builder (recordSaveMessage in @object-ui/i18n)
that replaces the two copies in app-shell.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qiby8nyBCmFZV9NNymGG
…derive a name from

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qiby8nyBCmFZV9NNymGG
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Oct 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head e8e2bcbde743f2c8aa6ccb6b871fe2bba30ed711 · 2026-10-30T16:47Z

At CONTRACT_REVIEW_TIER, by the maintainer's dedicated contract-review thread for the lanes (「开一个新的 fable thread,30分钟定时,负责车道的契约复审」, 2026-10-30T15:39Z), on the lane seat's behalf; the reviewer is not the drafter (session_01L9qiby8nyBCmFZV9NNymGG).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: TIER_DEFAULT
Head-sha: e8e2bcbde743f2c8aa6ccb6b871fe2bba30ed711
Local-runs: none
Scope: declaration face (Clause-②: yes (widening: @object-ui/i18n gains the public export recordSaveMessage; no accept set moves)), the whole diff outside the prose faces: ui/packages/i18n/src/recordSaveMessage.ts, ui/packages/i18n/src/index.ts, ui/packages/plugin-form/src/ObjectFormContainer.tsx, ui/packages/app-shell/src/console/AppContent.tsx, ui/packages/app-shell/src/views/RecordFormPage.tsx + the cited code read on the head: ui/packages/i18n/src/useSafeTranslation.ts (createSafeTranslation L47), ui/packages/components/src/ui/sonner.tsx L19-53 (toast re-export), ui/packages/plugin-form/src/ObjectFormContainer.tsx L118-190 (the drawer and modal schema spreads).

Inputs: card #27453 body and its seven comments (triage 6491491777, devx claim 6499390780 and release 6500073473, retriage ask 6523138910, triage ruling 6523407987 「平台相关的功能,应该尽量先改平台」, ui seat 1 claim 6526202454, the os-dev-report); PR #28204 body, 7-file list and the diff against main (+348/-52); the check-runs on e8e2bcbd (read 2026-10-30T17:04Z): 64 runs over 54 names, all completed, 46 success, 17 skipped, 0 failure; the one cancelled run is the Check Changeset of 16:45Z superseded by its 16:46–16:48Z re-run on the same head, which is success; among the successes Governed Surface Queue Guard, Check Changeset, PR-body guards, Lint & Repo Gates, Lint UI, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, the three scoped lints, Type Check UI and · workspace/source/consumer/debt ledger, Test Core, Test UI 1/16-16/16, Build & E2E UI, Live E2E UI, Build UI Deps, Dogfood Regression Gate. Author user.login is objectstack-fleet[bot], so no login note is owed. The reviewer is not the drafter (session_01L9qiby8nyBCmFZV9NNymGG).

① Derived judgments

  1. @object-ui/i18n gains one public export, recordSaveMessage(t, { label, objectDef, saved, mode }), and its input type RecordSaveMessageInput (index.ts L86). It is the one builder of the record-named save message: ADR-0079 display name through getRecordDisplayName with the Record #<id> floor treated as no name, then form.createdNamed / form.updatedNamed or the object-label form.createSuccess / form.updateSuccess. The body is the console's former closure moved verbatim (compared against the deleted AppContent.tsx L885-904 and RecordFormPage.tsx L199-214 hunks: same resolver, same floor, same four keys). Right, a widening of the package's public surface and nothing else; @object-ui/i18n already depends on @object-ui/core, so no new dependency edge.
  2. @object-ui/app-shell (private: true): AppContent's recordSaveMessage and RecordFormPage's handleSuccess now call the shared builder; AppContent keeps the t('common.record', { defaultValue: 'Record' }) fallback spelling that residue-namespaces-3546 pins, and the Console: a save that passes a warning-severity rule shows one toast, the record-named save toast carrying the warning #27999 warning claim and toastRecordSave are untouched. Right, a refactor with no wording change (the pinned suites AppContent.recordSaveToastNamesRecord-26172, AppContent.recordSaveOneToast-27999 and the RecordFormPage.* files are unchanged and green per the report).
  3. @object-ui/plugin-form: ObjectFormContainer, the container the object-form block supplies for formType: 'drawer' | 'modal' (ADR-0050 D4), replaces the onSuccess it hands DrawerForm / ModalForm with handleSuccess (L145, L184: the spread ...schema is overridden by the explicit onSuccess: handleSuccess, so the author's onSuccess is called once, from inside the wrapper, not twice). The wrapper toasts only when the container itself wrote (dataSource present, no submitHandler, mode create or edit), uses a declared successMessage as written, else the shared builder with the object definition from the adapter's cached getObjectSchema, then awaits the author's onSuccess. DrawerForm / ModalForm themselves are unchanged, so direct hosts (the console's record dialog, useActionModal, the designer forms) keep their own feedback. Right: this is the triage ruling's "platform first" shape with the smallest surface, and no accept set moves (no schema key is added; successMessage was already declared and is now read where it was silently ignored).
  4. The container's translation falls back to the en pack's four keys plus common.record through createSafeTranslation (L47), so a provider-less host toasts English, not raw keys; toast comes from @object-ui/components' sonner re-export (L53). Right.

② Semver level

.changeset/27453-container-save-toast.md: @object-ui/i18n patch, @object-ui/plugin-form patch; @object-ui/app-shell is private and takes none. The @object-ui/* group's precedent for a new public export is patch with a Clause-②: yes (widening: …; no accept set moves) line (#28151, @object-ui/react unmarkedRefusalLines, landed 2026-10-30), which this changeset follows. The container's new toast is a behaviour addition on a published renderer with no removed or changed behaviour for a host that owns its feedback. Clause-②: yes (widening: …) and Metadata-protocol: none sit line-initial on the PR body and the changeset. Consistent.

③ Boundary flags

  • open_questions: none declared.
  • Advisory write warnings on the drawer/modal path are not folded into the new toast (the claim lives in @object-ui/app-shell, above plugin-form): noted, not filed. A drawer save with answer warnings therefore raises two toasts (the success toast here and the write-warning channel's own), which is the pre-change count of warning toasts plus the success toast app-showcase: the task-desk page saves a record from its drawer or modal with no success toast #27453 asked for. Not a regression; see S1.
  • successMessage on a drawer/modal block was never shown before and is now the toast: a widening a block author could notice; it is what the key declares.
  • Live reproduction against the showcase backend was not run; the behaviour is pinned through the same ObjectFormContainer route (13 jsdom cases, ablation 10 red / 3 green). Accepted for a TIER_DEFAULT face; the seat's ACCEPT may ask for the showcase smoke.
  • Double-toast survey on e8e2bcbd: AppContent (via crud_success), useActionModal, FieldDesigner, ObjectManager mount the forms directly and never pass through the container; the showcase task-desk and renewals pages gain one toast each and raised none before. Read against the diff: holds, and the last test row pins a direct DrawerForm mount.
  • The card's lane moved devx → ui by triage 6523407987; examples/app-showcase is unchanged, as that ruling asked.
  • Author: objectstack-fleet[bot]; no login note.

Should-fix (none blocks the declaration):

  • S1 File a small domain:ui card so the drawer/modal save carries its answer warnings on the success toast as the record page does (claimAnswerWarnings would need a home below app-shell, or the container could accept a claim). Until then a warned drawer save shows two toasts.

Implemented-by: claude/project-thread-228tzt
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN

席位核对

ACCEPT · seat domain:ui#1 · head e8e2bcbd · 2026-10-30T17:06Z

席位意见:在页面里用抽屉或弹窗表单(object-form 块的 formType: 'drawer' | 'modal')保存记录后,现在也会弹出点名记录的成功提示(例如 Task "TITLE" updated),与控制台的记录对话框和记录页一致。按分诊 6523407987「平台相关的功能,应该尽量先改平台」,修在平台的表单容器里,示例页面不用改。提示文案由 @object-ui/i18n 新导出的 recordSaveMessage 统一生成,三处共用一份,已按扩面申报 Clause-②: yes (widening)。

  1. Shape: Fixes #27453, draft, base main. 7 files, +348/−52: the new ui/packages/i18n/src/recordSaveMessage.ts and its export, AppContent.tsx and RecordFormPage.tsx moved onto it with the same keys and no wording change, ObjectFormContainer.tsx toasts a write the container made (not under a submitHandler), then runs the author's onSuccess. Tests containerSaveToastNamesRecord-27453.test.tsx (13 cases, ablation 10 red / 3 green) and a root changeset (i18n, plugin-form patch).
  2. Seat read: there's no double toast. AppContent, useActionModal, FieldDesigner and ObjectManager mount ModalForm / DrawerForm directly and never pass through the container; the showcase task-desk and renewals pages gain one toast each and raised none of their own before (survey in the PR body, last test row pins a direct DrawerForm mount). examples/** and packages/spec/** are untouched.
  3. Contract review: PASS at this head, record 6528599404 (TIER_DEFAULT, declaration face), by the contract-review lane, not the drafter. Its one should-fix S1 (a warned drawer/modal save shows the warning as a second toast instead of riding the success toast) is filed as Drawer and modal form saves: answer warnings show as a second toast instead of riding the success toast #28216, serial after this PR.
  4. Gates / check-runs: on e8e2bcbd: 64 runs, 46 success, 17 skipped, 0 failure; the one cancelled Check Changeset was superseded by a success on the same head; mergeable_state clean. Dev's local run: 34/34 derived gates exit 0, type-check i18n / plugin-form / app-shell exit 0.
  5. Landing: check-governed-merges --pr 28204: NOT governed, 400 changed lines ≤ 3000. The seat readies it and arms auto-merge on the maintainer's 「按 pm-dispatch 审过的pr你可以自主落地」 (seat thread, 2026-10-30T14:19Z).

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

Copy link
Copy Markdown
Contributor

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

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

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

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

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

    × the node cell and the Problems list both name the blank expression, in the same words 2483ms
    FAIL   dom  packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx > Automations page — Problems and the node agree on a blank expression (objectstack#27799) > the n
      ↳ 失败原因: AssertionError: expected 'flows[0].nodes[1].config.fields.id' to contain 'node \'write\''
    

↳ 失败原因 是判读的关键:超时(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 队列共有 8 个失败构建(不含本次)。

分诊清单:

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

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

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

    × the node cell and the Problems list both name the blank expression, in the same words 2475ms
    FAIL   dom  packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx > Automations page — Problems and the node agree on a blank expression (objectstack#27799) > the n
      ↳ 失败原因: AssertionError: expected 'flows[0].nodes[1].config.fields.id' to contain 'node \'write\''
    

↳ 失败原因 是判读的关键:超时(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 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 10 个失败构建(不含本次)。

分诊清单:

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

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

  • Test UI (1/16) — 失败步骤: (无步骤级结论)

    × the node cell and the Problems list both name the blank expression, in the same words 2255ms
    FAIL   dom  packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx > Automations page — Problems and the node agree on a blank expression (objectstack#27799) > the n
      ↳ 失败原因: AssertionError: expected 'flows[0].nodes[1].config.fields.id' to contain 'node \'write\''
    
  • Test UI — 失败步骤: (无步骤级结论)(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(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 已在队列失败 2 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 12 个失败构建(不含本次)。

分诊清单:

  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 30, 2026
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN

Re-queue · 2026-10-30T20:00Z · head e8e2bcbd unchanged.

  • The PR left the merge queue because queue run 41114677884 (CI, queue sha 86cf7cef…) ended with Test UI (1/16) (job 124266311126) cancelled, not failed. It started at 19:23:29Z and wrote its last log line at 19:28:05Z, inside packages/react/src/__tests__/SchemaRenderer.legacyPropsDataStrip-9758.test.tsx. After that it printed nothing until the job timeout at 19:58:29Z. The Test UI rollup then reported that cancellation as failure. No assertion failed in this run, and the other 26 jobs passed.
  • This diff doesn't touch @object-ui/react: it changes @object-ui/i18n, @object-ui/plugin-form and two app-shell call sites. On this head, every PR check, Test UI 1–16 included, is green, and the contract review PASS 6528599404 and the seat ACCEPT stand. The earlier queue ejections (17:13–17:33Z) were main-red Test UI red on main: AutomationsPillar.problemsAgree-27799 still expects the node config finding's where prose, and #28192 now gives it a key path #28219, which is now cleared by test(ui): the Automations Problems double serves the draft door's node path (#28192) #28227.
  • Re-queued once by re-arming auto-merge. If it's kicked a second time with the same signature (a silent shard), the seat stops and diagnoses before any further re-queue.

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 03069ca Oct 30, 2026
67 of 68 checks passed
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

Development

Successfully merging this pull request may close these issues.

app-showcase: the task-desk page saves a record from its drawer or modal with no success toast

1 participant