Skip to content

Commit cba12d2

Browse files
fix(app-shell): the permission matrix's field boxes stay reachable while the matrix scrolls sideways (#29744)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9Ubb4LsB3BascUZYPdoexzAj3a)_ Fixes #29677 Clause-②: no Metadata-protocol: none Claim: [6593552174](#29677 (comment)) ## 维护者速读 - **改了什么**:权限集矩阵里展开一个对象后,下方的字段面板(筛选、计数、批量按钮和字段表)现在固定在视口内,横向滚动矩阵时不再跟着跑,也不再被“字段名”那一列盖住;Studio Access 和元数据管理两处的矩阵都改了。 - **为什么改**:矩阵最窄 1304 px,窗口更窄就要横向滚动;原来钉在左边的字段名列宽约 700 px,滚动约 80 px 后就把每个字段的“读 / 编辑”勾选框全部盖住,窄屏上管理员根本点不到,字段权限改不了。 - **风险与回滚**:只改了一个文件里的样式类(滚动区加容器查询、字段面板改为吸附定位),没有数据、接口或元数据变化;窗口够宽、矩阵不滚动时外观与原来一致。回滚即还原本 PR。 - **先看**:`PermissionMatrixEditor.tsx` 里的 `FIELD_PANEL` / `MATRIX_PANE` 两个常量和 `FieldsSubTable` 根节点;`ui/e2e/permission-field-panel-scroll.spec.ts`(浏览器里逐个滚动位置点中每个勾选框)。 - **两分钟验证**:`cd ui && CI=1 pnpm exec playwright test e2e/permission-field-panel-scroll.spec.ts --project=chromium`,预期 6 条全绿;在 main 的编辑器上同一命令在 scrollLeft 80 处报“勾选框被字段名单元格盖住”。 - **需要你做的**:无,席位落地。 ## What changes `ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx` only: - **The field panel is pinned, not the name column.** The field sub-table under an expanded object sits in one cell spanning the whole 1304 px matrix, and the table is `w-full`, so its name column (the only unsized one) took all the leftover width, about 697 px. objectstack#26846 pinned that column `sticky left-0`, so once the pane scrolled about 80 px it slid over both boxes. Bounding the column's width alone would not do: the boxes sit right after the name, so any scroll past the cell's inset still slides them under a pinned name. Now `FieldsSubTable`'s root (filter, count, bulk buttons, legend and table) carries `FIELD_PANEL` = `sticky left-12 max-w-[calc(100cqi-6rem)]`: it holds its place at the expanded cell's own inset (`pl-12` on Studio's matrix, `px-12` on the metadata admin's) and is no wider than the pane shows less both insets. The name header and cells lose their own pin (`FIELD_NAME_CELL` is gone); the header keeps its top pin from objectstack#27866 (`FIELD_HEAD_CELL`). - **The pane is the size container.** Both `MatrixScrollPane` call sites pass `MATRIX_PANE` = `flex-1 @container`, which sets the container on the pane's outer box (the same width as its scroll box), so `100cqi` is the pane's width. `MatrixScrollPane.tsx` is unchanged. - Where the pane holds the whole matrix, nothing scrolls and the panel spans the cell as the table did (measured: equal on the metadata admin's 48 + 48 px insets, 24 px narrower on Studio's 48 + 24 px). The QA item `studio-authoring.permission-matrix-editor-ux` (revision 10) gains clause acceptance[6], step B5 (steps[8]) and negative[2] for the field boxes under a sideways scroll, appended so no existing index moves, and its `automated.ref` names the new spec. One changeset, `@object-ui/app-shell` patch. ## Why The card's runner reading on main at `3482f3d8d2`: `elementFromPoint` at a field box centre answered `perm-field-name-OBJ-FIELD` from scrollLeft 80 up, and below 1304 px of pane width the boxes could not be reached at all. Re-measured on main at `c76cdebf` before the fix (below). ## Tests - **New: `ui/e2e/permission-field-panel-scroll.spec.ts`** (Playwright, picked up by `ui/playwright.config.ts`, `testDir: ./e2e`; CI runs it in the required `Build & E2E UI` job, `pnpm --dir ui run test:e2e --project=chromium`). It bundles the real `PermissionMatrixEditPage` from source with Vite (the metadata client stubbed at bundle time the way the jsdom tests `vi.mock` it), styles it with the real Tailwind theme, expands an object of eight fields, and at scroll offsets 0, 40, 57, 80, 120, 200, 320, 480 and the far end reads `elementFromPoint` at every Read and Edit box centre and at each field name. Six cases: Studio and metadata-admin renderings at 1000 px and 720 px panes (every box answers, the panel holds one left offset); a CONTROL at a 1600 px pane (nothing scrolls, every box answers at scrollLeft 0, green on main too); and the pin not binding at 1600 px (panel width equals the cell's content width). - **Updated jsdom pins** (class contract, guarded in Test UI): `PermissionMatrixEditor.fieldTableLayout-26846.test.tsx` now pins, on both renderings, the panel's `sticky left-12 max-w-[calc(100cqi-6rem)]`, the cell's 48 px inset, `@container` on the pane, the toolbar, header, names and both boxes inside the panel, and no `sticky` on the name cells; `PermissionMatrixEditor.gridChrome-27866.test.tsx`'s field-header pin now expects the name header without `left-0` (it pinned the old `left-0 z-[3]`). - **Ablation** (main's `PermissionMatrixEditor.tsx` restored into the tree under a trap, blob `aa5d4f53` = `origin/main`; restored to HEAD blob `a0b449fd`, `git status` clean): the spec's four sweep cases go red with `Read box of amount at scrollLeft 80 (pane 1000/1304) is covered by td[data-testid=perm-field-name-a_project-amount]` (same at 720 px), the 1600 px CONTROL stays green, the no-bind case is red (no panel); jsdom 4 of 12 red (both panel pins, both field-header pins). <details><summary>Live Console measure (Vite on source + app-crm backend, 1000 px window)</summary> `/studio/~all/metadata/permission/qa_field_scroll` (a writable env-scope set granting `crm_account`, metadata-admin rendering, pane 1000/1304) and `/studio/com.example.crm/access?item=crm_sales_user` (Studio Access rendering, pane 760/1304), `crm_account` expanded, 18 field boxes in view: | | scrollLeft 0 | 40 | 57 | 80 | 200 | far end | |:--|--:|--:|--:|--:|--:|--:| | main, metadata admin (name cell 705 px) | 18/18 | 18/18 | 18/18 | 9/18 | 0/18 | 0/18 | | main, Studio Access (name cell 720 px) | 0/18 (off pane) | 9/18 | 9/18 | 0/18 | 0/18 | 0/18 | | this PR, metadata admin (name cell 506 px) | 18/18 | 18/18 | 18/18 | 18/18 | 18/18 | 18/18 | | this PR, Studio Access (name cell 350 px) | 18/18 | 18/18 | 18/18 | 18/18 | 18/18 | 18/18 | Every covered box answered `perm-field-name-crm_account-FIELD`. Before/after screenshots at scrollLeft 0, 200 and the far end are kept with the card thread's files. </details> <details><summary>Local runs on the final head 9d196c5 (after merging main)</summary> - Spec: 6 passed. jsdom: `fieldTableLayout-26846`, `gridChrome-27866`, `fieldBulk`, `readonly`: 4 files, 26 tests passed. - Before the merge, at 5799825: the 52 test files under `ui/packages` naming `PermissionMatrixEditor`, `perm-field-`, `FieldsSubTable` or `MatrixScrollPane`, 368 tests passed; `pnpm --filter @object-ui/app-shell run type-check` exit 0; `tsc -p ui/tsconfig.e2e.json` exit 0; eslint on the four touched ui files 0 errors (19 warnings, all pre-existing lines); `pnpm --dir ui exec vitest run scripts/` 170 files passed. - `dispatch-gates --commands` derived 42 families at 5799825; all 42 ran, exit 0, reconciled with `--ran` (42 run, 0 NOT-MEASURED). Re-run on 9d196c5: `check-sdui-manifest`, `check:platform-checklist`, `check-changeset-no-major --base origin/main`, `check:nul-bytes`, all exit 0. - Main's 12 incoming commits touch none of this PR's files. </details> ## Acceptance notes - Seen on main and on this branch alike, not changed here: on the Studio rendering, the object row that is expanded or under the pointer lets the scrolled object boxes show through its pinned Object cell (the row's tint is not opaque over them); see the far-end screenshots. Reported to the seat, not fixed here. - The panel reserves 6rem (both insets of the metadata admin's cell); Studio's cell has a 24 px right inset, so there the panel ends 24 px short of the cell's edge when the pin binds. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. (eslint on the touched ui files: 0 errors) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (not touched) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QsR2jTaAUD6ttuQLjbPsrZ --- _Generated by [Claude Code](https://claude.ai/code/session_01QsR2jTaAUD6ttuQLjbPsrZ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6d1c659 commit cba12d2

6 files changed

Lines changed: 541 additions & 33 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): the permission matrix's field Read and Edit boxes stay reachable while the matrix scrolls sideways
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The permission matrix has a 1304 px floor, so a narrower pane scrolls sideways. Under an expanded object, the field sub-table's name column was its flexible column (about 697 px wide at the floor) and was pinned to the pane's left edge, so after a scroll of about 80 px it covered every field's Read and Edit box, and below 1304 px of pane the boxes could not be clicked at all. The whole field panel (filter, count, bulk buttons, legend and the field table) now holds its place at the expanded cell's inset and is no wider than the pane shows, on both the Studio Access matrix and the metadata-admin one. Every box stays in view and clickable at any scroll offset, and the field name beside it stays readable. Nothing changes where the pane holds the whole matrix.

‎docs/qa/platform-checklist/areas/studio-authoring.json‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@
906906
"title": "Permission-matrix editor UX: field-filter + bulk apply to EXACTLY the visible fields, and the Bulk column never clips at narrow widths",
907907
"since": "v16",
908908
"status": "active",
909-
"revision": 9,
909+
"revision": 10,
910910
"priority": "P2",
911911
"surface": "mixed",
912912
"personas": ["admin"],
@@ -930,7 +930,8 @@
930930
"Save; GET /api/v1/meta/permission/<name> (or the package draft read) and assert the fields map (keyed `${object}.${field}`) carries overrides for ONLY the fields that were visible when each bulk fired — never a filter-hidden field",
931931
"B3 clip: shrink the viewport to a narrow width; confirm the object matrix's enclosing overflow-auto container SCROLLS horizontally (the table carries min-w-[1304px]; Studio's is data-testid perm-studio-matrix) and the Bulk column (Read / CRUD / All / None per row) stays reachable, not clipped off the right edge",
932932
"read-only gate: open a read-only package/type set; confirm Save is hidden, every checkbox is disabled, the field sub-table's bulk buttons (Read-only all / Write all / Clear) are ABSENT — FieldsSubTable renders them only while the set is writable — and the object row's Read / CRUD / All / None, which Studio shows only on the row under the pointer or with keyboard focus, are disabled when they show; the read-only badge names package-vs-type as the reason",
933-
"the grant→access flip (a flipped verb actually changing a persona's API access) is access-security.permission-matrix-edit-loop — cite a pass there; this item does NOT re-prove that a saved grant changes access"
933+
"the grant→access flip (a flipped verb actually changing a persona's API access) is access-security.permission-matrix-edit-loop — cite a pass there; this item does NOT re-prove that a saved grant changes access",
934+
"B5 field boxes under a sideways scroll: at a pane narrower than the matrix floor (a 1000 px pane, and a narrower one), expand an object, scroll the matrix container right in steps (0, 80, 200 px and its far end); at each offset read document.elementFromPoint at the centre of every field row's Read and Edit box, and at the field name"
934935
],
935936
"acceptance": [
936937
{
@@ -968,19 +969,26 @@
968969
"oracle": "screenshot",
969970
"verify": "on the read-only set Save is absent, every checkbox is disabled, the field bulk buttons (Read-only all / Write all / Clear) are absent — not merely disabled (PermissionMatrixEditor.tsx FieldsSubTable renders them only while writable; QA run #26715 read them absent in read-only Studio) — and the object row's bulk, which appears only on hover or keyboard focus (engine.studio.access.bulkHint), is disabled when it appears; an operating bulk control is the FAIL. The badge names the gate that holds; the console distinguishes FOUR (objectui PermissionMatrixEditor.tsx at the pin): the package gate (engine.studio.pkg.readonly — Studio on a read-only package), the artifact gate (perm.readOnly.artifact, 'Read-only (this set is provided by a code package)' — a set a code package ships, in the metadata admin), the wall (perm.readOnly.walled — the security catalog under isolated/group tenancy) and the type gate (perm.readOnly — a type with no runtime write channel). On a stock boot only the package and artifact gates are reachable — the wall needs a walled tenancy posture and the type gate a permission type with no write channel, which stock does not have — so score those two on the packaged showcase_contributor set (Studio Access and the metadata admin) and record the other two as not reachable on stock. A badge that blames the wrong gate is the FAIL",
970971
"evidence": "the read-only screenshot"
972+
},
973+
{
974+
"clause": "an expanded object's field Read and Edit boxes stay in view and clickable at every horizontal scroll offset of the matrix and at any pane width, and each field's name stays readable beside them — the field panel holds its place at the cell's inset, no wider than the pane shows, instead of scrolling with the object columns or sitting under a pinned name cell (objectstack#29677)",
975+
"oracle": "dom",
976+
"verify": "at a narrow pane (scrollWidth > clientWidth on the matrix container) and at each scroll offset of step B5, elementFromPoint at the centre of every field row's Read and Edit box returns that box (or a node inside it) and the centre lies inside the container's visible box; the field name answers at its first characters. A box answering the field-name cell, or a box only reachable at scrollLeft 0, is the FAIL",
977+
"evidence": "the elementFromPoint readings per offset + screenshots at scrollLeft 0 and at the far end"
971978
}
972979
],
973980
"negative": [
974981
"a field bulk that writes grants to fields hidden by the filter is the FAIL this item exists for — a wrong bulk scope silently grants or revokes access on fields the admin never saw (assert against the SAVED fields map, not the repaint)",
975-
"the Bulk column clipped off the right edge at a narrow width with no horizontal scroll is the B3 FAIL — the admin cannot reach Read / CRUD / All / None"
982+
"the Bulk column clipped off the right edge at a narrow width with no horizontal scroll is the B3 FAIL — the admin cannot reach Read / CRUD / All / None",
983+
"a field row's Read or Edit box covered by the field-name cell, or carried out of view, once the matrix scrolls sideways is the B5 FAIL — below the matrix floor the admin cannot grant or revoke that field at all"
976984
],
977985
"variants": [
978986
"field bulk: Read-only all (readable)",
979987
"field bulk: Write all (writable)",
980988
"field bulk: Clear"
981989
],
982990
"traps": ["stale-console-bundle", "hydration-race", "automation-input"],
983-
"automated": { "kind": "unit", "ref": "objectui code under ui/: ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.fieldBulk.test.tsx (+ PermissionMatrixEditor.readonly.test.tsx, PermissionMatrixEditor.artifactTier.test.tsx). fieldBulk asserts filter narrowing, the '2 / 8' counter and the bulk scope as jsdom checkbox states: component-layer evidence for acceptance[0] and [1], never the SAVED fields map that acceptance[2], [3] and negative[0] make the authority. For acceptance[5], readonly covers the package gate, the type gate and the capability gate, and artifactTier covers the artifact gate ('provided by a code package'). Every screenshot and api clause stays the run's" },
991+
"automated": { "kind": "unit", "ref": "objectui code under ui/: ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.fieldBulk.test.tsx (+ PermissionMatrixEditor.readonly.test.tsx, PermissionMatrixEditor.artifactTier.test.tsx). fieldBulk asserts filter narrowing, the '2 / 8' counter and the bulk scope as jsdom checkbox states: component-layer evidence for acceptance[0] and [1], never the SAVED fields map that acceptance[2], [3] and negative[0] make the authority. For acceptance[5], readonly covers the package gate, the type gate and the capability gate, and artifactTier covers the artifact gate ('provided by a code package'). For acceptance[6], ui/e2e/permission-field-panel-scroll.spec.ts measures elementFromPoint at every field box over a sweep of scroll offsets in Chromium on both matrix renderings (a stubbed client; Build & E2E UI runs it). Every screenshot and api clause stays the run's" },
984992
"source": [
985993
"objectui: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx (FieldsSubTable B4 — field filter + bulkSetFields over visibleNames; PermissionTable min-w-[1304px] B3 anti-clip on the Bulk column; the writable gate + celErrorCount Save gate)",
986994
"objectui: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.fieldBulk.test.tsx ('the filter narrows the field set and bulk acts only on what is visible'; 'Clear drops the overrides so fields fall back to the read-only default')",
@@ -1027,6 +1035,12 @@
10271035
"date": "2026-11-02",
10281036
"change": "steps[0]'s second door re-pointed (#28579 slice 2, FOLLOW-UPS §13c): the Setup metadata item URL it named redirects since #28818 to Studio's All metadata item page, which mounts the same PermissionMatrixEditor, so the step names the landing URL. No clause changed. Read at base eb123b7ccc",
10291037
"ref": "#28579"
1038+
},
1039+
{
1040+
"revision": 10,
1041+
"date": "2026-11-03",
1042+
"change": "new clause acceptance[6], step B5 (steps[8]) and negative[2] (objectstack#29677, from QA run #29672, whose acceptance[4] covers only the Bulk column): an expanded object's field Read and Edit boxes stay in view and clickable at every horizontal scroll offset and pane width. On main at 3482f3d8d2 the field sub-table's name column, its flexible column and about 697 px wide, was pinned left and covered both boxes from a scroll of about 80 px on, so below the 1304 px floor they could not be reached. The field panel is now pinned at the cell's inset instead (FIELD_PANEL in ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx); automated.ref names the browser geometry pin ui/e2e/permission-field-panel-scroll.spec.ts. Appended, so no existing index moves; no other clause changed",
1043+
"ref": "#29677"
10301044
}
10311045
]
10321046
},

0 commit comments

Comments
 (0)