Schema-Driven Unified Settings Sidebar

## Context

Context

The SDK editor's sidebar settings are currently all stubbed (settings-stub.ts returns () => null for every component). The old main-app approach had 17 separate *Settings.tsx React components — one per component type — each re-declaring the same tab structure with minor variations. This caused the entire sidebar to unmount/remount when switching between selected components, leading to flicker, lost scroll position, and wasted renders.

Goal: Build a single Inspector component that renders the sidebar shell once. Standard sections (Appearance, Layout, Hover & Click, Animations, Accessibility, Style) are always present and driven by a config object per component type. When selection changes, only the config/values change — React reconciles minimal DOM diffs instead of tearing down the whole tree.


Architecture

The Schema

Each component type declares a plain config object describing what its settings look like:

interface ComponentSettingsSchema {
  displayName: string; // Tab label ("Text", "Button", etc.)
  icon: ReactNode; // Tab icon
  MainTab: ComponentType; // Component-specific content (1st tab)

  // Standard section configs (each is data, not JSX)
  appearance?: AppearanceConfig; // { textColor, bgColor, font, border, shadow, ... }
  layout?: "spacing" | "hidden" | ReactNode;
  hoverClick?: { variant: string } | "hidden" | ReactNode;
  animations?: "standard" | "hidden";
  accessibility?: "standard" | "hidden";
  style?: StyleConfig; // { displaySettings, typeInput, importExport }
}

Standard sections render from these config flags — no custom JSX needed for 90% of cases. The first tab (MainTab) is always a React component because component-specific UIs (codemirror editors, child accordions, media uploaders) can't be expressed as data.

Rendering Flow

Selection changes
  → Inspector reads node.data.displayName
  → Looks up schema from registry
  → buildHead(schema) → tab icons (memoized, changes rarely)
  → buildSections(schema) → section config passed to static renderers
  → Standard section components receive new config props, reconcile in-place
  → MainTab component swaps (only part that remounts)

File Structure

packages/sdk/src/chrome/toolbar/Inspector/
├── index.ts                     # barrel export
├── types.ts                     # ComponentSettingsSchema + sub-types
├── Inspector.tsx          # the single settings component
├── registry.ts                  # schema definitions + lookup map
├── builders.ts                  # buildHead() + buildSections()
├── sections/
│   ├── AppearanceSection.tsx    # renders from AppearanceConfig
│   ├── LayoutSection.tsx        # renders SpacingInput or custom
│   ├── HoverClickSection.tsx    # renders HoverClickInput w/ variant
│   ├── AnimationsSection.tsx    # renders AnimationsInput or Notice
│   ├── AccessibilitySection.tsx # renders AccessibilityInput
│   └── StyleSection.tsx         # renders DisplaySettings + TypeInput + extras
└── mainTabs/
    ├── TextMainTab.tsx          # codemirror, presets, AI, ipsum
    ├── ButtonMainTab.tsx        # text input, presets, icon, type
    ├── ContainerMainTab.tsx     # page/imageContainer branching, presets, order
    ├── ImageMainTab.tsx         # media upload, presets, size, loading
    ├── VideoMainTab.tsx         # provider, videoId
    ├── AudioMainTab.tsx         # audio source, playback
    ├── EmbedMainTab.tsx         # embed code editor
    ├── FormMainTab.tsx          # form type, submission config
    ├── FormElementMainTab.tsx   # input type, options, validation
    ├── ButtonListMainTab.tsx    # child accordion w/ NodeProvider
    ├── ImageListMainTab.tsx     # child management, infinite scroll
    ├── DividerMainTab.tsx       # style, thickness
    ├── SpacerMainTab.tsx        # height control
    └── BackgroundMainTab.tsx    # background image settings

Files Modified

FileChange
Each packages/sdk/src/components/*.tsx (17 files)Change .craft.related.toolbarInspector
packages/sdk/src/components/settings-stub.tsDelete after migration

Existing Code Reused (no changes needed)

  • chrome/toolbar/Inputs/* — all 60+ Input components stay as-is (they use useNode() internally)
  • chrome/toolbar/ToolbarItem.tsx — base input component
  • chrome/toolbar/ToolbarSection.tsx — collapsible section wrapper
  • chrome/toolbar/InspectorTab.tsx — tab header + scrollable section body
  • chrome/toolbar/primitives/tableBodyControls.tsxTBWrap wrapper (formerly Helpers/SettingsHelper.tsx)
  • chrome/toolbar/ToolBarWrapper.tsx — footer with delete/clone/copy/paste
  • utils/selectorPresets.ts — centralized preset definitions
  • chrome/toolbar/index.tsxReact.createElement(related.toolbar) dispatch (unchanged)

Migration Order

Build infrastructure first, then migrate components simplest-to-hardest:

  1. Infrastructure — types, Inspector shell, builders, standard section renderers
  2. Simple — Container presets, Audio, Embed, Video (prove the pattern works)
  3. Medium — Text, Button, Image, FormElement
  4. Complex — Container, Form, Header, Footer
  5. Lists — ButtonList, ImageList, Background
  6. Cleanup — delete settings-stub.ts, move orphaned atom exports

Each phase is independently shippable — old stubs and new unified system coexist since each component independently references its toolbar.

Verification

  1. Select each component type in the editor canvas → sidebar should show correct tabs and sections
  2. Edit props in the sidebar → canvas component updates in real-time
  3. Switch between different component types → sidebar transitions smoothly without full remount (inspect via React DevTools Profiler)
  4. Responsive view switching (mobile/tablet/desktop) → settings reflect correct view values
  5. Run npm run dev and verify no console errors
  6. Run SDK build (cd packages/sdk && npm run build) to confirm bundling works