Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/block-config-complete.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ export const BLOCK_CONFIG: Record<string, BlockPropField[]> = {
],
},
],
'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': [
Expand Down Expand Up @@ -178,6 +191,46 @@ export const BLOCK_CONFIG: Record<string, BlockPropField[]> = {
{ 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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,8 @@ import {
Zap,
BookOpen,
History,
Rocket,
Menu,
Search,
Bell,
User,
Bot,
Sparkles,
Type,
Expand All @@ -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:*
Expand Down Expand Up @@ -101,13 +95,10 @@ export const BLOCK_TYPE_META: Record<BlockTypeId, Omit<BlockTypeMeta, 'id'>> = {
'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 },
Expand Down
Loading