Repository navigation
fix(app-shell): the field-permission editor shows and writes exactly what the runtime enforces for a field with no explicit rule - #27006
Merged
objectstack-fleet[bot] merged 8 commits intoOct 28, 2026
Conversation
…object grant Claude-Session: https://claude.ai/code/session_01J8VVAkZns5rNnK2rtszzbQ Co-authored-by: Claude <noreply@anthropic.com>
…time table Claude-Session: https://claude.ai/code/session_01J8VVAkZns5rNnK2rtszzbQ Co-authored-by: Claude <noreply@anthropic.com>
…the editor Claude-Session: https://claude.ai/code/session_01J8VVAkZns5rNnK2rtszzbQ Co-authored-by: Claude <noreply@anthropic.com>
…eld-perm-editor-display
… row Claude-Session: https://claude.ai/code/session_01J8VVAkZns5rNnK2rtszzbQ Co-authored-by: Claude <noreply@anthropic.com>
…allow copy Claude-Session: https://claude.ai/code/session_01J8VVAkZns5rNnK2rtszzbQ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkZns5rNnK2rtszzbQ Co-authored-by: Claude <noreply@anthropic.com>
…eld-perm-editor-display # Conflicts: # ui/packages/app-shell/src/views/metadata-admin/i18n.ts
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contributor
Author
|
ACCEPT — PM review on head
Landing: ready + auto-merge. Generated by Claude Code |
objectstack-fleet
Bot
deleted the
claude/issue-26597-field-perm-editor-display
branch
October 28, 2026 02:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #26597
Clause-②: no
Metadata-protocol: none
维护者速读
permission-slice.ts的fieldRuleCell/applyFieldBulk(显示与批量写入的唯一来源);PermissionMatrixEditor.tsx的FieldRuleBox与FieldsSubTable;previews/PermissionPreview.tsx的字段列表。pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/permission-slice.fieldRule-26597.test.ts,预期 16 passed。What changes
The field-permission editor now shows and writes exactly what the runtime enforces for a field with no explicit rule.
ui/packages/app-shell/src/views/metadata-admin/permission-slice.ts):fieldRuleCell(entry, objectPerm)is what a field row draws. With no entry, the row draws the object row's own Read and Edit, read through the spec'sobjectPermissionGrants(the fold the evaluator answers from, super-user bits included), and is marked inherited. With an entry, the row draws the entry's bits, read as the runtime merge reads them (=== true, asfieldPermissionVotedoes). A bit the entry grants and the object row does not is marked capped.fieldRuleSeedis what a click starts from: the entry, or on a row without one exactly the bits the row draws, so a click changes only the box it lands on.applyFieldBulkdrives the three field shortcuts. Each moves the visible rows to its target and leaves a row that already draws the target as it is, so a shortcut writes only the rows whose boxes change. Clear deletes the entries, and those rows draw the object grant again.withFieldBits(keeps an authoredwhen) moved here from the editor unchanged.PermissionMatrixEditor.tsx):FieldsSubTablereceives the object row's permission and draws each box throughFieldRuleBox. An inherited box is grey with a dashed edge and carriesdata-field-rule="inherited"and a tooltip. A capped box carries an amber ring,data-cappedand a tooltip saying a holder of this set alone cannot use it. A legend line explains the grey boxes.updateFieldPermandupdateFieldWhenseed fromfieldRuleSeed, andbulkSetFieldscallsapplyFieldBulk. Both object layouts (the page table and the Studio matrix) render the same sub-table.previews/PermissionPreview.tsx, also shown per set under a position) lists every field entry the set declares, tagged hidden, read-only or editable, with awhennamed in the chip's title.@object-ui/i18n,studionamespace):perm.field.bulk.readablereads "Read-only all" (zh 「全部只读」) because the shortcut switches Edit off; four new keys explain the grey and amber boxes;engine.permissionPreview.fls.readOnly(en + zh) tags a read-only rule in the preview.content/docs/permissions/field-level-security.mdx(the Studio bullet) andaccess-recipes.mdx(step 2 now says to set Edit off).packages/plugins/plugin-security/src/field-rule-object-grant.test.ts.Same-default sweep across
ui/: the field sub-table's render, the click seed (updateFieldPerm), the condition seed (updateFieldWhen) and the bulk shortcuts are all in the one editor and are all fixed here. The preview's field list filtered entries the same way and is fixed too.MePermissionsProvider.checkFieldalready defers a field without an entry to the object grant. The explain panel draws no field rows.apps/console/src/preview-samples.tsis sample data.Tests
permission-slice.fieldRule-26597.test.ts(16): the six cells (no entry / read-only / editable × object Edit on / off) against the write gate's verdict, the super-user fold, the capped marks, the seed, and every shortcut's writes, including that it writes nothing for rows already at its target.PermissionMatrixEditor.fieldRuleInherited-26597.test.tsx(8): the rendered boxes and theirdata-field-rule/data-cappedmarks, and the saved payload after a click, a shortcut and Clear.previews/PermissionPreview.fieldRules-26597.test.tsx(3): every declared entry is listed and tagged.field-rule-object-grant.test.tsin plugin-security (7): the same six cells through a realObjectQLover SQLite with the realSecurityPluginmiddleware, refusals asserted oncode+status(anddetails.forbiddenFieldsfor the field gate), plus the read of a field without an entry.PermissionMatrixEditor.fieldBulk.test.tsxandPermissionMatrixEditor.fieldWhen-25866.test.tsx(the renamed shortcut; the Clear case now asserts the inherited mark),previews/adminPreviews.i18n-10862-s2.test.tsx(the new preview row in en and zh).ab5961d4): withfieldRuleCell's no-entry branch mutated so it no longer reads the object grant, 12 of the 24 cases in the two new editor-side files went red. The mutation went throughscripts/ablation-replace.mjs(anchor hit 1 → 0, blob changed) and was restored to the HEAD blob (db141c2b).bootStack's real HTTP door by a member holding only the fixture set agreed with both pins in every cell.Acceptance notes
'*'wildcard, so for a set whose grant on an object comes only from'*'the object row and the field rows under it do not show that grant. This predates this change.dispatch-gates.mjs --commands … CHANGED_PATHS; the role file says to derive without paths so the script reads the change set from the merge base. The role file was followed.@object-ui/app-shellis private; the changeset names it with@object-ui/console(which ships it) and@object-ui/i18n, as the other app-shell changesets in.changeset/do.Verification
All readings at
a723bac9(this branch merged withorigin/mainc6853b12), unless a line says otherwise.Builds and type-checks
pnpm turbo run build --concurrency=2 --filter='@object-ui/app-shell^...' --filter='@objectstack/plugin-security^...': exit 0pnpm --filter @object-ui/app-shell run type-check(tsc --noEmit, thentsc -p tsconfig.test.json): exit 0pnpm --filter @object-ui/i18n run type-check: exit 0pnpm --filter @objectstack/plugin-security run typecheck: exit 0Tests
pnpm --dir ui exec vitest run --maxWorkers=2over metadata-adminPermissionMatrixEditor*,permission-slice*,previews/PermissionPreview*,previews/adminPreviews.i18n-10862-s2.test.tsx,PositionPermissionSetsSection.test.tsx,studio-design/StudioDesignSurface.accessTabs-12456.test.tsxand the two@object-ui/i18nstudio-namespace tests: Test Files 42 passed (42), Tests 318 passed (318), exit 0.pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/field-rule-object-grant.test.ts: Test Files 1 passed (1), Tests 7 passed (7), exit 0.Test UI).Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths) derived 96 families for the 15-path change set. All 96 ran locally, each exit 0,pnpm --dir ui exec vitest run scripts/included (Test Files 178 passed, 2 skipped).--ran: "96 derived, 96 run, 0 NOT-MEASURED, 0 UNRUN".ui/package.jsongates:check:i18n-keys,check:i18n-drift,check:i18n-dead-keys,check:i18n-designer-parity,check:designer-field-key-parityandcheck:new-line-citations(0 new citations): exit 0 each.Generated by Claude Code