From ca561f46125278b5a86776c759326bb081025b9a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 03:35:07 +0000 Subject: [PATCH] =?UTF-8?q?feat(app-shell):=20book=20audience=20gates=20by?= =?UTF-8?q?=20permission=20set=20=E2=80=94=20mirror=20the=20spec's=20{=20p?= =?UTF-8?q?ermissionSet=20}=20shape=20(ADR-0090)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The framework retired BookAudience's { profile } arm (ADR-0090 D2 removed the Profile concept; D9 makes the gate a capability reference). Update the three UI mirrors: the metadata-admin default JSON schema oneOf, the BookPreview audience chip, and the book list-column renderer. The "Role- gated" form title also violated the D3 word ban and is now "Permission-set gated". Companion to objectstack-ai/framework#2732. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Ph4jEAfWDh6taMbZweWhom --- .changeset/book-audience-permission-set.md | 14 ++++++++++++++ .../app-shell/src/services/builtinComponents.tsx | 4 ++-- .../src/views/metadata-admin/default-schemas.ts | 14 ++++++++++---- .../views/metadata-admin/previews/BookPreview.tsx | 6 +++--- 4 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 .changeset/book-audience-permission-set.md diff --git a/.changeset/book-audience-permission-set.md b/.changeset/book-audience-permission-set.md new file mode 100644 index 000000000..611856e21 --- /dev/null +++ b/.changeset/book-audience-permission-set.md @@ -0,0 +1,14 @@ +--- +'@object-ui/app-shell': minor +--- + +Book audience mirrors the spec's permission-set gate (ADR-0090). + +`@objectstack/spec` renamed the gated arm of `BookAudience` from +`{ profile: string }` to `{ permissionSet: string }` — ADR-0090 D2 removed +the Profile concept, and D9 makes the gate a capability reference (a +permission-set name the reader must hold, e.g. `crm_admin`). Updated the +three mirrors: the metadata-admin default JSON schema (`book.audience` +`oneOf`), the `BookPreview` audience chip, and the book list-column +renderer. One-step rename, no alias, matching the spec's launch-window +discipline. diff --git a/packages/app-shell/src/services/builtinComponents.tsx b/packages/app-shell/src/services/builtinComponents.tsx index 5d399c366..517898723 100644 --- a/packages/app-shell/src/services/builtinComponents.tsx +++ b/packages/app-shell/src/services/builtinComponents.tsx @@ -188,8 +188,8 @@ registerMetadataResource({ render: (v) => v == null ? 'org' - : typeof v === 'object' && v && 'profile' in (v as Record) - ? `profile: ${(v as { profile: string }).profile}` + : typeof v === 'object' && v && 'permissionSet' in (v as Record) + ? `permission set: ${(v as { permissionSet: string }).permissionSet}` : String(v), }, { diff --git a/packages/app-shell/src/views/metadata-admin/default-schemas.ts b/packages/app-shell/src/views/metadata-admin/default-schemas.ts index 40c5f262e..799aec57c 100644 --- a/packages/app-shell/src/views/metadata-admin/default-schemas.ts +++ b/packages/app-shell/src/views/metadata-admin/default-schemas.ts @@ -260,12 +260,18 @@ const SCHEMAS: Record> = { }, audience: { title: 'Audience', - description: "Access audience. 'org' (default) inherits the package grant; 'public' is anonymously readable; { profile } gates by role.", - // Union of the two scalar literals and the { profile } object — kept lax - // so the role-gated object form round-trips untouched through the form. + description: + "Access audience. 'org' (default) inherits the package grant; 'public' is anonymously readable; { permissionSet } gates by a permission set the reader must hold (ADR-0090).", + // Union of the two scalar literals and the { permissionSet } object — kept lax + // so the permission-set-gated object form round-trips untouched through the form. oneOf: [ { type: 'string', enum: ['org', 'public'] }, - { type: 'object', title: 'Role-gated', properties: { profile: { type: 'string', title: 'Profile' } }, required: ['profile'] }, + { + type: 'object', + title: 'Permission-set gated', + properties: { permissionSet: { type: 'string', title: 'Permission set' } }, + required: ['permissionSet'], + }, ], }, groups: { diff --git a/packages/app-shell/src/views/metadata-admin/previews/BookPreview.tsx b/packages/app-shell/src/views/metadata-admin/previews/BookPreview.tsx index 14ba67c7a..cded0f9fe 100644 --- a/packages/app-shell/src/views/metadata-admin/previews/BookPreview.tsx +++ b/packages/app-shell/src/views/metadata-admin/previews/BookPreview.tsx @@ -35,7 +35,7 @@ import { import type { MetadataPreviewProps } from '../preview-registry'; import { PreviewShell, PreviewErrorBoundary, PreviewEmptyState } from './PreviewShell'; -type Audience = 'org' | 'public' | { profile: string } | undefined; +type Audience = 'org' | 'public' | { permissionSet: string } | undefined; type Include = string | { tag: string } | undefined; interface PageNode { @@ -116,8 +116,8 @@ function audienceChip(audience: Audience): { icon: React.ReactNode; label: strin if (audience === 'public') { return { icon: , label: 'Public', tone: 'text-emerald-700 bg-emerald-50 border-emerald-200' }; } - if (audience && typeof audience === 'object' && typeof audience.profile === 'string') { - return { icon: , label: `Profile: ${audience.profile}`, tone: 'text-amber-800 bg-amber-50 border-amber-200' }; + if (audience && typeof audience === 'object' && typeof audience.permissionSet === 'string') { + return { icon: , label: `Permission set: ${audience.permissionSet}`, tone: 'text-amber-800 bg-amber-50 border-amber-200' }; } // 'org' (default) or absent — inherits the package grant. return { icon: , label: 'Org', tone: 'text-muted-foreground bg-muted/40 border-muted' };