diff --git a/.changeset/block-config-complete.md b/.changeset/block-config-complete.md new file mode 100644 index 000000000..d6be4d1ee --- /dev/null +++ b/.changeset/block-config-complete.md @@ -0,0 +1,5 @@ +--- +"@object-ui/app-shell": minor +--- + +Complete the page-editor block configuration and prune shell-only blocks. Adds configurable property panels for the remaining content blocks with authorable properties — `page:accordion`, `record:path`, `record:quick_actions`, `ai:chat_window`, `ai:input` — so every page-content block in the palette is configurable in the UI (pure containers like `page:section` / `element:divider` correctly have no panel). Removes shell-singleton blocks (`app:launcher`, `global:notifications`, `user:profile`) from the page block palette — those are provided by the app shell, not authored as page content. diff --git a/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config.test.ts b/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config.test.ts index adf37e5bf..cd9b178bb 100644 --- a/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config.test.ts +++ b/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config.test.ts @@ -1,20 +1,34 @@ import { describe, it, expect } from 'vitest'; import { BLOCK_CONFIG, blockHasConfig } from '../block-config'; +import { BLOCK_TYPE_META } from '../block-types'; describe('block-config', () => { - it('exposes a configurable panel for the minimal SDUI block set', () => { - for (const type of ['element:text', 'element:image', 'page:header', 'page:card', 'record:related_list']) { - expect(blockHasConfig(type)).toBe(true); + it('exposes a configurable panel for every content block with authorable props', () => { + for (const type of [ + 'element:text', 'element:image', 'element:number', 'element:button', + 'page:header', 'page:card', 'page:tabs', 'page:accordion', + 'record:related_list', 'record:highlights', 'record:details', 'record:alert', + 'record:path', 'record:quick_actions', 'ai:chat_window', 'ai:input', + ]) { + expect(blockHasConfig(type), type).toBe(true); expect(BLOCK_CONFIG[type].length).toBeGreaterThan(0); } }); - it('returns false for blocks without a config schema (and for undefined)', () => { + it('returns false for pure-container blocks without scalar props (and undefined)', () => { expect(blockHasConfig('page:section')).toBe(false); - expect(blockHasConfig('nav:menu')).toBe(false); + expect(blockHasConfig('element:divider')).toBe(false); expect(blockHasConfig(undefined)).toBe(false); }); + it('prunes shell-singleton blocks from the page palette', () => { + for (const type of ['app:launcher', 'global:notifications', 'user:profile']) { + expect((BLOCK_TYPE_META as any)[type]).toBeUndefined(); + } + // page-content navigation stays + expect((BLOCK_TYPE_META as any)['nav:menu']).toBeTruthy(); + }); + it('also exposes the array-valued blocks', () => { for (const type of ['page:tabs', 'record:details', 'record:highlights']) { expect(blockHasConfig(type)).toBe(true); diff --git a/packages/app-shell/src/views/metadata-admin/previews/block-config.ts b/packages/app-shell/src/views/metadata-admin/previews/block-config.ts index 129b9f262..ca9176020 100644 --- a/packages/app-shell/src/views/metadata-admin/previews/block-config.ts +++ b/packages/app-shell/src/views/metadata-admin/previews/block-config.ts @@ -137,6 +137,19 @@ export const BLOCK_CONFIG: Record = { ], }, ], + 'page:accordion': [ + { name: 'title', label: 'Title', kind: 'text' }, + { + name: 'items', + label: 'Sections', + kind: 'array', + addLabel: 'Add section', + itemFields: [ + { name: 'value', label: 'Key', kind: 'text' }, + { name: 'label', label: 'Label', kind: 'text' }, + ], + }, + ], // ── Record context ──────────────────────────────────────────────────────── 'record:related_list': [ @@ -178,6 +191,46 @@ export const BLOCK_CONFIG: Record = { { name: 'icon', label: 'Icon', kind: 'text', placeholder: 'lucide icon name' }, { name: 'dismissible', label: 'Dismissible', kind: 'boolean' }, ], + 'record:path': [ + { name: 'statusField', label: 'Status field', kind: 'text', placeholder: 'e.g. stage' }, + { + name: 'stages', + label: 'Stages', + kind: 'array', + addLabel: 'Add stage', + itemFields: [ + { name: 'value', label: 'Value', kind: 'text' }, + { name: 'label', label: 'Label', kind: 'text' }, + ], + }, + ], + 'record:quick_actions': [ + { name: 'actionNames', label: 'Action names', kind: 'string-list', placeholder: 'action name' }, + { + name: 'location', + label: 'Location', + kind: 'select', + options: [ + { value: 'record_header', label: 'Record header' }, + { value: 'record_more', label: 'Record more menu' }, + { value: 'record_section', label: 'Record section' }, + { value: 'record_related', label: 'Record related' }, + { value: 'list_toolbar', label: 'List toolbar' }, + { value: 'list_item', label: 'List item' }, + { value: 'global_nav', label: 'Global nav' }, + ], + }, + ], + + // ── AI ──────────────────────────────────────────────────────────────────── + 'ai:chat_window': [ + { name: 'agentName', label: 'Agent', kind: 'text', placeholder: 'agent name' }, + { name: 'placeholder', label: 'Input placeholder', kind: 'text' }, + ], + 'ai:input': [ + { name: 'agentName', label: 'Agent', kind: 'text', placeholder: 'agent name' }, + { name: 'placeholder', label: 'Input placeholder', kind: 'text' }, + ], }; /** Block types that expose a configurable property panel. */ diff --git a/packages/app-shell/src/views/metadata-admin/previews/block-types.ts b/packages/app-shell/src/views/metadata-admin/previews/block-types.ts index 0d3181cbc..a1661222c 100644 --- a/packages/app-shell/src/views/metadata-admin/previews/block-types.ts +++ b/packages/app-shell/src/views/metadata-admin/previews/block-types.ts @@ -29,11 +29,8 @@ import { Zap, BookOpen, History, - Rocket, Menu, Search, - Bell, - User, Bot, Sparkles, Type, @@ -56,14 +53,11 @@ export type BlockTypeId = | 'record:details' | 'record:highlights' | 'record:related_list' | 'record:activity' | 'record:chatter' | 'record:path' | 'record:alert' | 'record:quick_actions' | 'record:reference_rail' | 'record:history' - // app:* - | 'app:launcher' - // nav:* + // nav:* — page-content navigation (shell singletons like app:launcher / + // global:notifications / user:profile are intentionally NOT page blocks) | 'nav:menu' | 'nav:breadcrumb' // global:* - | 'global:search' | 'global:notifications' - // user:* - | 'user:profile' + | 'global:search' // ai:* | 'ai:chat_window' | 'ai:suggestion' // element:* @@ -101,13 +95,10 @@ export const BLOCK_TYPE_META: Record> = { 'record:reference_rail': { label: 'Reference rail', category: 'record', Icon: BookOpen }, 'record:history': { label: 'History', category: 'record', Icon: History }, - // App & navigation - 'app:launcher': { label: 'App launcher', category: 'navigation', Icon: Rocket }, + // Navigation (page-content only; shell singletons are not page blocks) 'nav:menu': { label: 'Nav menu', category: 'navigation', Icon: Menu }, 'nav:breadcrumb': { label: 'Breadcrumb', category: 'navigation', Icon: Compass }, 'global:search': { label: 'Global search', category: 'navigation', Icon: Search }, - 'global:notifications': { label: 'Notifications', category: 'navigation', Icon: Bell }, - 'user:profile': { label: 'User profile', category: 'navigation', Icon: User }, // AI 'ai:chat_window': { label: 'AI chat window', category: 'ai', Icon: Bot },