Skip to content

fix(app-shell): a locked metadata item's designer canvas is read-only - #27035

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-jdv1ky
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-jdv1ky

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #26888

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Setup 元数据编辑页对"不可写"的条目(被包锁定,或调用者无权编写)不再把 onPatch 交给设计器画布,画布随之只读。
  • 为什么改:锁定对象的画布仍可加字段、移动/删除分区并显示"Unsaved",但页面没有保存按钮,离开即静默丢失。
  • 风险与回滚:只改一个 prop;可写条目行为不变。回滚即还原该 prop,无数据或协议影响。
  • 先看:ResourceEditPage.tsx 中预览组件的 onPatch 三元表达式;其次新测试文件 lockedCanvas-26888。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/ResourceEditPage.lockedCanvas-26888.test.tsx,预期 2 passed。
  • 需要你做的:无,席位落地。

What changes

MetadataResourceEditPage passes the registered designer preview onPatch={readOnly ? undefined : …} instead of an unconditional onPatch. readOnly is the page's existing !canWrite && !createMode, so it covers a locked item (layered.editable === false), a caller without manage_metadata, and a walled catalog. Per the preview contract (MetadataPreviewProps.onPatch), a canvas with no onPatch is read-only; ObjectFormCanvas derives readOnly = !onPatch, which hides Add field, Add section, Add field to this section, Move section up/down and Remove section. This mirrors Studio's read-only package handling (objectui#11136, onPatch={readOnly ? undefined : onPatch} in StudioDesignSurface). The fix is at the page, so every Setup designer type goes read-only on a locked item, not only Object; ObjectPreview.tsx needed no change.

The Description box (ObjectDefaultInspector) was already disabled={readOnly} through the page's formReadOnly: measured, already done.

Tests

New ResourceEditPage.lockedCanvas-26888.test.tsx mounts the real page with the built-in designers registered:

  • control: a writable object shows Add field, Add field to this section, Move section up/down, Remove section;
  • a locked object (editable: false, lock: 'full') shows the lock banner, none of those controls, and no "Unsaved".

Ablation (restore the unconditional onPatch via scripts/ablation-replace.mjs, anchor hit 1 → 0, blob 6cd1fb36 → 256b0123): the locked case goes red (the found Move/Remove BUTTON list did not deeply equal []), control stays green; restore verified blob == HEAD and git diff HEAD empty.

Acceptance notes

  • None.

Verification

All local readings on head a512197f (base 7eb613c3).

  • Closure build pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build under the lock: exit 0 (6m16s).
  • New test: 2/2 passed. Nearby suites ResourceEditPage* + ObjectFormCanvas: 44 files, 224 tests passed.
  • pnpm --filter @object-ui/app-shell run type-check: exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands: 34 derived families, all exit 0; --ran reconciliation: 34 run, 0 NOT-MEASURED, 0 UNRUN. Includes node scripts/check-sdui-manifest.mjs and pnpm --dir ui exec vitest run scripts/ (176 files passed, 2 skipped).
  • Also run: pnpm check:changeset-fixed (roster under .changeset) exit 0; pnpm --dir ui run check:new-line-citations exit 0.
  • Type-check lanes, artifact rosters and wide-population families: left to this PR's CI check-runs.

Generated by Claude Code

ResourceEditPage handed the registered preview an onPatch even when the
page cannot write the item, so the object canvas offered add field and
section move/remove on a locked item, with no Save to keep the edit.
Pass no onPatch when the page is read-only, as Studio does for a
read-only package.

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

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · head a512197f61 · 2026-10-28T02:29Z

席位意见:一个 prop 的修复,落在页面层,所以 Setup 里所有设计器类型在锁定条目上都只读,不只 Object;可写条目和新建模式不受影响。

  1. Shape: draft, base main; the body says Fixes #26888, which matches the order (whole card). 3 files, +163/−2; not governed (check-governed-merges --pr 27035: 0 of 3 paths hit the register); ≤ 3000 lines.
  2. Premise measured: seat read of ResourceEditPage.tsx at head: readOnly is the page's existing const readOnly = !canWrite && !createMode (L2146), so the preview gets no onPatch exactly when the page offers no Save. Create mode keeps a live canvas. Same contract as StudioDesignSurface (objectui#11136).
  3. Gate check-runs (pinned to head a512197f61): in progress at this record (7 success, 10 skipped, 14 running); landing waits for every check green. The dev's local reading: 34/34 derived families exit 0, --ran 0 NOT-MEASURED, new test 2/2, ablation red then restored.
  4. Accepted deviations: origin/main moved to cb8988ee after the gates; none of it touches these files, and there was no merge. CI runs on the merge ref.
  5. Out of scope: none reported. Serial note: Setup → Email Template: a template carried in two locales lists as two rows, but both rows open the same locale, so the other language cannot be opened or edited from Setup #26649 (seat 2) also edits ResourceEditPage.tsx in a disjoint region; whichever lands second merges main first.
  6. Landing clause: not governed. When every check on the current head is green, the seat lands it (pr_ready + automerge_enable), or asks the maintainer to click if the classifier refuses.

This was referenced Oct 28, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 28, 2026 08:03
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 28, 2026 08:03
@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 构建失败 — 先分诊,再决定要不要重排

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

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

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

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

    × zh-CN: the header reads the served 销售代表, and a save keeps the authored label 1112ms
    × ja-JP: the header reads the label the served row carries 1045ms
    × a set the published list does not hold keeps its draft label 1044ms
    × an edit to the label shows the edit, not the served label 1038ms
    FAIL   dom  packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.servedLabel-26884.test.tsx > PermissionMatrixEditPage — header shows the served label (objectstack#26884) > zh-CN: the he
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    FAIL   dom  packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.servedLabel-26884.test.tsx > PermissionMatrixEditPage — header shows the served label (objectstack#26884) > ja-JP: the he
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    FAIL   dom  packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.servedLabel-26884.test.tsx > PermissionMatrixEditPage — header shows the served label (objectstack#26884) > a set the pub
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    FAIL   dom  packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.servedLabel-26884.test.tsx > PermissionMatrixEditPage — header shows the served label (objectstack#26884) > an edit to th
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 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 无队列失败记录(首次)。
  • 过去 24h 队列共有 29 个失败构建(不含本次)。

分诊清单:

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

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

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

2 participants