Skip to content

fix(app-shell): Studio Access pillar polish — plain-words draft race refusal, OWD banner clears on revert, live rail label, field table count and pinned name column - #27148

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

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-sfrpf5

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #26846

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 访问(Access)支柱四处小修:草稿抢先创建时的拒绝改成作者能懂的话;OWD 总览撤回改动后拒绝提示随之消失;权限集改名后左侧列表立即显示新标签;字段子表的「可见/总数」计数挪到筛选框旁、字段名列横向滚动时固定在左侧。第五项(恢复已忽略的建议绑定)服务端没有对应接口,本 PR 不做,交席位立服务端卡。
  • 为什么改:QA 在一次 Studio 巡检里实测到这五处,单个都小,合起来让访问支柱显得不可靠(卡 Studio Access polish: jargon duplicate-draft refusal, OWD banner outliving revert, stale set label in the rail, clipped field counter, no re-grant for a dismissed suggestion #26846,分诊 p3)。
  • 风险与回滚:只动 @object-ui/app-shell 三个组件与 @object-ui/i18n 新增一个键,无契约、无服务端改动;回滚即 revert 本 PR。
  • 先看:StudioDesignSurface.tsx(AccessPillar 的创建拒绝映射与矩阵保存后重读列表)、PackageOwdOverviewPanel.tsx(回到无改动时清除拒绝)、PermissionMatrixEditor.tsx(字段子表的计数位置与固定列)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/PackageOwdOverviewPanel.refusalClearsOnRevert-26846.test.tsx packages/app-shell/src/views/studio-design/StudioDesignSurface.accessRailLabelLive-26846.test.tsx packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.fieldTableLayout-26846.test.tsx packages/app-shell/src/views/studio-design/StudioDesignSurface.accessCreateTaken-26836.test.tsx,预期 4 个文件、15 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

Each item was re-reproduced on origin/main 07d18e69 first (the QA run was at 3fcafcd3).

  1. Duplicate-draft refusal in plain words (item 1, narrowed). The card's path no longer reproduces: since Studio Access: creating a permission set whose derived id equals an already-published set puts a blank draft over the live set (publishing it would wipe its grants), and Studio offers no way to discard it #26836 the Access "+ New" reads findTakenIdentifier before its save and refuses a taken identifier with an author-facing notice, writing nothing. The server's first-write-pin sentence still reached the dialog in the window between that check and the save, and the existing race test pinned it verbatim. AccessPillar.doCreate now reads that refusal by its code: this create always sends the pin (ifNoneMatch: '*'), so METADATA_CONFLICT means the identifier holds a draft, and the dialog shows engine.studio.access.createDraftTaken (en + zh, new in @object-ui/i18n). Any other refusal keeps its own text. The server message is unchanged.
  2. OWD refusal banner outlives its revert (item 2). The refusal state lives in PackageOwdOverviewPanel (the Access pillar's sharing-baseline overview), not in PermissionMatrixEditor. Its saveError now clears on the dirty-to-clean transition, when every row is back at its loaded value ("No changes"). A row that is still dirty keeps the banner.
  3. Stale set label in the Access rail (item 3). The matrix page's onDraftSaved in AccessPillar updated only the draft-name set; the rail list was read on mount, publish and the live-metadata pulse. That callback now also calls the pillar's own load(), so a label an autosave changed shows at once, with no polling.
  4. Clipped field counter, non-sticky name column (item 4). The studio matrix has a min-w-[1304px] floor, wider than the Studio content column beside the rail at 1440 px, so the field sub-table's ml-auto counter sat past the visible edge of the scroll container. The counter now sits right after the field filter (shrink-0 whitespace-nowrap), and the field-name header and cells carry FIELD_NAME_CELL (sticky left-0 z-[1] bg-background), so the column stays in view while the matrix scrolls sideways. The sub-table is shared, so the non-Studio matrix page gets the same.
  5. No re-grant for a dismissed suggestion (item 5): left out. Measured: the server exposes only GET /security/suggested-bindings, POST …/:id/confirm and POST …/:id/dismiss; confirmAudienceBindingSuggestion in plugin-security refuses any row whose status is not pending ("only pending suggestions can be accepted"), and no route re-opens a dismissed row. Per the dispatch order no endpoint is invented here; the seat can file a server-side card.

Why

Five small Access-pillar defects measured in one Studio QA pass (#26846). Triage graded them p3 together: "One PR is fine."

Tests

All at head 8220f32d (after merging origin/main, which brought #26999 into the same two files).

  • New PackageOwdOverviewPanel.refusalClearsOnRevert-26846.test.tsx: a revert to the loaded value clears the banner; CONTROL: a still-dirty edit keeps it.
  • New StudioDesignSurface.accessRailLabelLive-26846.test.tsx: the real AccessPillar with a matrix stand-in that reports one draft save; the rail row goes from "Dispatcher" to "Dispatcher v2".
  • New PermissionMatrixEditor.fieldTableLayout-26846.test.tsx: the count is the filter's next sibling and has no ml-auto; the name header and every name cell carry the pinned classes; CONTROL: a box cell is not sticky. jsdom lays nothing out, so this pins structure and classes, not pixels.
  • Updated StudioDesignSurface.accessCreateTaken-26836.test.tsx: the race test now expects the author-facing sentence and the absence of the pin sentence; a new CONTROL keeps a VALIDATION_ERROR refusal's own text.
Runs
  • The four files: Test Files 4 passed (4), Tests 15 passed (15) (lock VERDICT command-exit 0).
  • Every app-shell test file naming PermissionMatrixEdit, AccessPillar, PackageOwdOverviewPanel or an engine.studio.access. key (65 files): Test Files 65 passed (65), Tests 595 passed (595) (lock VERDICT command-exit 0, 5m37s).
  • pnpm --filter @object-ui/app-shell run type-check exit 0; pnpm --filter @object-ui/i18n run type-check exit 0; pnpm --dir ui run type-check:scripts exit 0.
  • Ablations, one per behaviour, each through scripts/ablation-replace.mjs with the restore proved by blob equal to HEAD and an empty git diff HEAD:
    • item 1, the METADATA_CONFLICT test replaced by false: the race test fails, 9 others pass.
    • item 2, the clear replaced by void 0;: the revert test fails, its CONTROL passes.
    • item 3, the void load(); deleted: the rail-label test fails. (A first attempt that replaced the line was refused by the tool before any run, the replacement count not rising; the delete form ran.)
    • item 4, FIELD_NAME_CELL emptied: the pinned-column test fails, the counter test passes.
    • item 4, the counter's class put back to ml-auto …: the counter test fails, the pinned-column test passes.

Acceptance notes

Verification

  • Derived gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 8220f32d): 34 commands, all exit 0; --ran reconciliation: "34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED". Also run outside the derivation: pnpm check:changeset-fixed exit 0 and pnpm --dir ui run check:new-line-citations exit 0 ("0 new citation(s)").
  • dispatch-gates --tier: no path-derived mandate; 349 changed lines (+341 / -8), under the 3000 threshold.
  • Changeset .changeset/26846-studio-access-polish.md: @object-ui/i18n patch (new key), @object-ui/app-shell patch.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (untouched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is the dispatch order's Chinese section).

Generated by Claude Code

…refusal, OWD banner clears on revert, live rail label, field table count and sticky name column

Claude-Session: https://claude.ai/code/session_01JQwxbYZa5ViBSW4Et4fr91
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · seat domain:ui#1 · session_01JQwxbZeBv2y91towxasSUT · head 8220f32d · 2026-10-28T08:28Z

席位意见:Access 支柱四处小修可以落地:草稿抢先创建的拒绝按错误码改成作者能懂的话,OWD 总览撤回到「No changes」后拒绝提示消失,草稿保存后左侧列表重读标签,字段子表计数挪到筛选框旁、字段名列横向滚动时固定。第五项(恢复已忽略的建议绑定)服务端没有接口,已立服务端卡 #27149。

  1. Shape: Fixes #26846; 10 files, +341/−8: StudioDesignSurface.tsx +15/−1, PackageOwdOverviewPanel.tsx +6, PermissionMatrixEditor.tsx +26/−3, one new key engine.studio.access.createDraftTaken (en + zh), three new tests and one updated (Studio Access: creating a permission set whose derived id equals an already-published set puts a blank draft over the live set (publishing it would wipe its grants), and Studio offers no way to discard it #26836 race test), changeset @object-ui/i18n + @object-ui/app-shell patch.
  2. Premise measured: item 1: the create sends only the first-write pin, and the door refuses a pin over an existing row 409 METADATA_CONFLICT (spec protocol.zod.ts ≈L921), so mapping that code (not its text) to the new key is sound; the server sentence is unchanged. Item 2: the refusal state lives in PackageOwdOverviewPanel (accepted deviation from the order), cleared on isDirty false; a still-dirty edit keeps it. Item 3: onDraftSaved now calls the pillar's own load(); load keeps current (setCurrent((c) => c ?? …)) and the matrix stays keyed on current, so the editor is not remounted. Scope note: by load's existing rule (and the Data pillar's, StudioDesignSurface.tsx ≈L6612) a published set keeps its published label while a draft relabels it; the live label applies to draft-only sets, which matches the card's repro ("until publish/reload"). Item 4: count beside the filter, FIELD_NAME_CELL sticky on header and rows, pinned by class assertions.
  3. Gates / check-runs: PR check-runs on 8220f32d: 43 success, 11 skipped, 0 failure; mergeable_state clean. Dev's local run: 34/34 derived gates exit 0 (--ran 34/34), type-check 0, 65 affected files 595/595; five ablations each turn a test red.
  4. Accepted deviations: item 1 narrowed to the check-to-save race (the card's path is caught by the Studio Access: creating a permission set whose derived id equals an already-published set puts a blank draft over the live set (publishing it would wipe its grants), and Studio offers no way to discard it #26836 pre-check on main); item 2 landed in PackageOwdOverviewPanel.tsx; item 3 uses load() instead of a surface nonce. Seat trial merges of this head with origin/main and with the Studio hook form: refusals cannot be authored — the record editor stamps the key into a closed entry; needs a key-only record control (follow-up owed by #26170) #26835 branch (same i18n files) are clean.
  5. Out of scope: item 5 → filed Studio Access: a dismissed or revoked suggested binding cannot be accepted again #27149 (server: dismissed suggested bindings are terminal; confirm refuses non-pending rows). The other Studio creates (flow, object, app, interfaces) show the same pin sentence in the same race; polish, not filed. Skills-docs: none — UI only.
  6. Landing: not Tier H; the seat readies and arms auto-merge.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 28, 2026 08:29
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 28, 2026 08:29
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit ff648d7 Oct 28, 2026
55 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-sfrpf5 branch October 28, 2026 09:01
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