From 9a6529d80115645fa0fd47802114eafe4143f9f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 6 Sep 2026 10:35:16 +0000 Subject: [PATCH] docs(permissions): compile the permissions README's 7 blocks and drop its ledger entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `packages/permissions/README.md` leaves `UNGATED_DOCS` in `scripts/check-doc-snippet-types.mjs` and all 7 of its ts/tsx blocks now compile against the built types. The gate file's only change is the two lines of that one entry — removals only, inside the object literal. All 18 measured diagnostics were semantic (zero parse, zero bound). Seven of them were real drift between the page and the SHIPPED dist types of the same package rather than fragment shape: `PermissionProvider` taught `roles` as bare strings, `permissions` as an object map and omitted the required `userRoles`; `PermissionGuard` taught a `resource` prop and a JSX-valued `fallback`; `usePermissions().can` was documented with the action first and the object second; `useFieldPermissions` was called with two arguments and destructured for `isVisible` / `isEditable`; `evaluatePermission` was given `resource` with no `object`, `userRoles` or `user`; `createPermissionStore().check` was documented action-first and annotated as returning a boolean. Each is corrected in the README to the package's own dist type, per the card's standing ruling. The remaining blocks gained real self-imports and `declare const` placeholders typed to the shipped surface, with bare JSX bound to a name and not re-indented. No `packages/**` source touched, no public type widened, no lenient alias added, no gate loosened, and no new fragment marker: declared fragments stay at 158. Part of objectui#5174 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr --- packages/permissions/README.md | 142 ++++++++++++++++++++++------ scripts/check-doc-snippet-types.mjs | 2 - 2 files changed, 114 insertions(+), 30 deletions(-) diff --git a/packages/permissions/README.md b/packages/permissions/README.md index 12093a873e..d059fe8dae 100644 --- a/packages/permissions/README.md +++ b/packages/permissions/README.md @@ -24,16 +24,31 @@ npm install @object-ui/permissions ## Quick Start ```tsx -import { PermissionProvider, usePermissions, PermissionGuard } from '@object-ui/permissions'; +import { + PermissionProvider, + PermissionGuard, + usePermissions, + type ObjectPermissionConfig, + type RoleDefinition, +} from '@object-ui/permissions'; + +const roles: RoleDefinition[] = [ + { name: 'admin', label: 'Administrator' }, + { name: 'editor', label: 'Editor' }, +]; + +const permissions: ObjectPermissionConfig[] = [ + { + object: 'orders', + roles: { + editor: { actions: ['read', 'create', 'update'] }, + }, + }, +]; function App() { return ( - + ); @@ -45,15 +60,19 @@ function Dashboard() { return (

Orders

- No access

}> + No access

}>
- {can('delete', 'orders') && } + {can('orders', 'delete') && }
); } ``` +Roles are `RoleDefinition` records (identity and inheritance); what a role may +do lives in each object's `ObjectPermissionConfig.roles` map, and `userRoles` +names the roles the current user actually holds. + ## API ### PermissionProvider @@ -61,39 +80,81 @@ function Dashboard() { Wraps your application with permission context: ```tsx - - - +import { + PermissionProvider, + type ObjectPermissionConfig, + type RoleDefinition, +} from '@object-ui/permissions'; + +declare const roleDefinitions: RoleDefinition[]; +declare const permissionMap: ObjectPermissionConfig[]; + +function App() { + return
Your application
; +} + +const tree = ( + + + +); ``` ### usePermissions -Hook for checking permissions programmatically: +Hook for checking permissions programmatically. `can` and `cannot` take the +object first and the action second: ```tsx -const { can, cannot, roles } = usePermissions(); +import { usePermissions } from '@object-ui/permissions'; -if (can('update', 'orders')) { - // allow editing +function OrderToolbar() { + const { can, cannot, roles } = usePermissions(); + + if (can('orders', 'update')) { + // allow editing + } + + return

{cannot('orders', 'delete') ? 'Read only' : roles.join(', ')}

; } ``` ### useFieldPermissions -Hook for field-level permission checks: +Hook for field-level permission checks. It takes the object name, and returns +predicates you call per field: ```tsx -const { isVisible, isEditable } = useFieldPermissions('orders', 'discount'); +import { useFieldPermissions } from '@object-ui/permissions'; + +function DiscountField() { + const { canRead, canWrite } = useFieldPermissions('orders'); + + const isVisible = canRead('discount'); + const isEditable = canWrite('discount'); + + return isVisible ? : null; +} ``` ### PermissionGuard -Conditionally renders children based on permissions: +Conditionally renders children based on permissions. `fallback` selects the +denied behaviour, and `fallbackContent` carries the node rendered for +`fallback="custom"`: ```tsx -Read only}> - - +import { PermissionGuard } from '@object-ui/permissions'; + +function DeleteButton() { + return ; +} + +const guard = ( + Read only}> + + +); ``` ### evaluatePermission @@ -101,14 +162,25 @@ Conditionally renders children based on permissions: Programmatic permission evaluation: ```tsx -import { evaluatePermission } from '@object-ui/permissions'; +import { + evaluatePermission, + type ObjectPermissionConfig, + type RoleDefinition, +} from '@object-ui/permissions'; + +declare const roleDefinitions: RoleDefinition[]; +declare const permissionConfig: ObjectPermissionConfig[]; const result = evaluatePermission({ - action: 'update', - resource: 'orders', - roles: ['editor'], + roles: roleDefinitions, permissions: permissionConfig, + userRoles: ['editor'], + user: { id: 'user-1', roles: ['editor'] }, + object: 'orders', + action: 'update', }); + +result.allowed; // true | false ``` ### createPermissionStore @@ -116,8 +188,22 @@ const result = evaluatePermission({ Creates a permission store for advanced use cases: ```tsx -const store = createPermissionStore(permissionConfig); -store.check('read', 'orders'); // true | false +import { + createPermissionStore, + type ObjectPermissionConfig, + type RoleDefinition, +} from '@object-ui/permissions'; + +declare const roleDefinitions: RoleDefinition[]; +declare const permissionConfig: ObjectPermissionConfig[]; + +const store = createPermissionStore({ + roles: roleDefinitions, + permissions: permissionConfig, + userRoles: ['editor'], +}); + +store.check('orders', 'read'); // PermissionCheckResult: { allowed, reason?, ... } ``` ## Links diff --git a/scripts/check-doc-snippet-types.mjs b/scripts/check-doc-snippet-types.mjs index 2351b38343..d824e7f722 100644 --- a/scripts/check-doc-snippet-types.mjs +++ b/scripts/check-doc-snippet-types.mjs @@ -721,8 +721,6 @@ const UNGATED_DOCS = { '2 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines; 1 unresolved-module diagnostic(s)', 'packages/layout/README.md': '3 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines; 3 unresolved-module diagnostic(s)', - 'packages/permissions/README.md': - '12 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines; plus TS2322x4 TS2345x1 TS2353x1 — candidate real defects, un-triaged', 'packages/plugin-ai/README.md': '5 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines; plus TS2322x3 — candidate real defects, un-triaged', 'packages/plugin-charts/README.md':