Skip to main content

Editing the Excalidraw Demo Configuration

This guide explains how to change the current Excalidraw adaptive UI implementation. Use it when adding a variant, changing toolbar behavior, tuning MCP persona scoring, or swapping the mock LLM connector for a fetch-based connector.

Configuration Map​

ConcernFile
Deterministic variant UI definitionsdemos/excalidraw/frontend/src/config/variantConfig.ts
MCP personality resources, scoring rules, and action UI statesdemos/excalidraw/backend/src/services/ExcalidrawMcpResources.ts
Mode switch and provider wiringdemos/excalidraw/frontend/src/App.tsx
Applying the selected UI state to Excalidrawdemos/excalidraw/frontend/src/components/EditorShell.tsx
Custom allowlisted toolbar renderingdemos/excalidraw/frontend/src/components/DynamicToolbar.tsx
Debug readout for mode, scores, confidence, and lock statedemos/excalidraw/frontend/src/components/DebugPanel.tsx
Runtime admin console overlaydemos/excalidraw/frontend/src/App.tsx and @dionysys/react AdminConsole
Dionysys client/session/event/decision adaptersdemos/excalidraw/frontend/src/dionysys/
Telemetry collection and flushingdemos/excalidraw/frontend/src/core/eventCollector.ts

Runtime Editing with the Admin Console​

For fast experimentation, enable the admin console and edit runtime configuration from the browser:

ADMIN_CONSOLE_ENABLED=true npm run dev --workspace=@dionysys-demo/excalidraw-backend
npm run dev --workspace=@dionysys-demo/excalidraw-frontend

The demo shows an Admin button in development. The console is seeded from the current files on backend startup, then lets you edit:

  • default mode, event threshold, and polling interval
  • prototype vs production presentation mode
  • immediate vs next-refresh decision application
  • deterministic personas, initial counts, event rules, heuristics, and epsilon
  • MCP resources, base weights, signal rules, actions, and UI states
  • MCP confidence floor and fallback variant
  • supported tools and menu items through the full JSON editor

Saving changes affects runtime decisions only. It does not rewrite variantConfig.ts or ExcalidrawMcpResources.ts. Use Export to download a JSON snapshot when you want to preserve a tuning session.

Production mode hides the top-bar mode switch, admin entry point, debug panel, active variant badge, personality labels, and persona scores. It shows only a front-facing feedback panel.

Prototype Experience​

Prototype mode is the demo's configuration and debugging view. It shows:

  • deterministic/MCP mode switch
  • Admin button for the runtime console overlay
  • session id
  • active variant badge
  • debug panel with events, policy status, scores, confidence, and MCP personality
  • pending refresh badge when a decision has been stored for the next load

Use prototype mode when tuning scoring rules, comparing deterministic and MCP decisions, validating personality resources, or checking whether the next-refresh behavior is storing the expected decision.

The current prototype debug controls should use useAdaptiveUI().setManualOverride(...) for manual layout switches. The old raw-store approach is kept only as package compatibility debt and should not be copied into new demo code.

Production Experience​

Production mode is the front-facing user view. It hides the mode switch, Admin button, session id, variant badge, debug panel, personality labels, and persona probability charts. Users should not see which personality or variant they are in.

Instead, production mode renders a lightweight feedback panel through AdaptiveFeedback. The demo records feedback as a feedback_submitted event so it can be analyzed with the rest of the session telemetry.

Adaptive Modes in the Demo​

The top bar switch in EditorShell selects the mode:

  • Deterministic: AdaptiveProvider resolves decisions through the Dionysys client against /api/dionysys/decisions:resolve with mode: 'deterministic'.
  • MCP: AdaptiveProvider resolves decisions through the Dionysys client against /api/dionysys/decisions:resolve with mode: 'mcp', then renders lastDecision.uiState.

App.tsx remounts the provider when the mode or runtime admin config changes, so switching modes or saving admin config resets lock state, selected variant, and MCP decision fields.

When decisionApplication is next-refresh, policy/MCP decisions are stored as pending decisions. The active Excalidraw toolbar, menu, and welcome UI remain unchanged for the current workspace session. On the next browser refresh or provider mount, the pending decision is applied before the user starts working again.

Editing Deterministic Variants​

Edit demos/excalidraw/frontend/src/config/variantConfig.ts.

Each entry in VARIANT_CONFIGS controls the UI for a variant:

export const VARIANT_CONFIGS = {
draw_first: {
showWelcomeScreen: false,
toolbar: { mode: 'allowlist', tools: ['selection', 'rectangle', 'ellipse'] },
canvasActions: {
saveAsImage: true,
clearCanvas: true,
},
mainMenuItems: ['saveAsImage', 'export', 'clearCanvas'],
},
};

Supported fields:

FieldBehavior
showWelcomeScreenShows the guided Excalidraw welcome content when true.
toolbar.modeallowlist hides the native toolbar and renders the custom toolbar; blocklist leaves the native toolbar visible.
toolbar.toolsTool ids rendered by DynamicToolbar when mode is allowlist.
canvasActionsPassed into Excalidraw UIOptions.canvasActions.
mainMenuItemsControls which Excalidraw MainMenu.DefaultItems are rendered.

Current toolbar icon ids are selection, rectangle, ellipse, diamond, arrow, line, freedraw, text, image, and eraser. If you add a new tool id, add an icon and click handling support in DynamicToolbar.tsx.

Current main menu item ids are saveAsImage, export, clearCanvas, help, and toggleTheme. If you add a new menu item, add its render mapping in EditorShell.tsx.

MCP action payloads can still carry arbitrary string menu ids through mainMenu or mainMenuItems, but the Excalidraw demo intentionally filters them down to this supported set before rendering. If a new id should render in the demo, update both the filter in variantConfig.ts and the corresponding MainMenu render mapping in EditorShell.tsx.

Editing MCP Personality Resources​

Edit demos/excalidraw/backend/src/services/ExcalidrawMcpResources.ts.

Each MCP resource has three important parts:

{
id: 'guided_novice',
name: 'Guided Novice',
description: 'A user who appears early in the workflow and benefits from fewer visible choices.',
decisionHints: ['Low event volume', 'Low tool diversity'],
scoring: {
baseWeight: 1,
signals: [
{
id: 'low_event_volume',
description: 'User has not generated many events yet.',
metric: 'totalEvents',
operator: '<',
value: 5,
weight: 3,
},
],
},
actions: [
{
id: 'show_guided_toolbar',
description: 'Show a reduced toolbar and welcome guidance.',
isSafeFallback: true,
uiState: {
variant: 'guided_novice',
showWelcomeScreen: true,
toolbar: { mode: 'allowlist', tools: ['selection', 'rectangle', 'text'] },
canvasActions: { saveAsImage: false, clearCanvas: false, toggleTheme: false },
mainMenu: { allowedItems: ['help'] },
mainMenuItems: ['help'],
},
},
],
}

Scoring rules are deterministic. For each personality, the scorer starts from baseWeight or 1, adds every matching signal's weight, clamps the result to >= 0, then normalizes all raw scores into probabilities.

Supported signal metrics:

MetricRequiresMeaning
totalEventsvalueCompare against total event count.
eventCounteventType, valueCompare against count of one event type.
eventRatioeventType, valueCompare event type count divided by total events.
elementCountelementType, valueCompare against count of one element type.
toolDiversityvalueCompare number of distinct tools or event types observed.
textToShapeRatiovalueCompare text events/elements against shape events/elements.
timeToFirstEventMsvalueCompare first event delay from session start.
timeSinceLastEventMsvalueCompare idle time since the latest event.
recentEventTypestring valueCheck whether recent event types contain a value.

Supported operators are <, <=, >, >=, ==, !=, contains, and notContains. Use contains or notContains for recentEventType.

Adding a New Variant and Persona​

  1. Add the variant key to UiVariant in demos/excalidraw/frontend/src/config/variantConfig.ts.
  2. Add a matching VARIANT_CONFIGS[variant] entry for deterministic mode.
  3. Add a matching PersonalityResource in demos/excalidraw/backend/src/services/ExcalidrawMcpResources.ts.
  4. Set every action uiState.variant to the variant key you want the frontend badge and config resolver to use.
  5. Add mainMenu and mainMenuItems to MCP action UI states. mainMenu is part of the package UI schema; mainMenuItems is the current Excalidraw demo's render helper.
  6. Mark at least one conservative action with isSafeFallback: true when it should be eligible for fallback.
  7. Add toolbar icons or menu render mappings if your config references ids that are not currently supported.

Keep deterministic config and MCP action UI states aligned when they represent the same experience. That makes mode switching easier to reason about and avoids a variant looking different only because of the decision path.

LLM Connector Configuration​

The backend defaults to MockLLMDecisionConnector, which selects the highest normalized persona score and the first safe/default action. No API key is needed for the mock connector.

To call an external decision service, set environment variables before starting the backend:

ADAPTIVE_LLM_ENDPOINT=https://example.com/decision \
ADAPTIVE_LLM_API_KEY=replace-me \
ADAPTIVE_LLM_MODEL=optional-model-name \
npm run dev --workspace=@dionysys-demo/excalidraw-backend

The fetch connector sends:

  • personality ids, names, descriptions, hints, and available action ids/descriptions
  • interactionSummary
  • rawScores
  • normalized personaScores

It does not need to send raw unbounded event payloads. Keep API keys server-side and do not add provider SDKs to @dionysys/core or @dionysys/react.

Resource Debugging​

Use the read-only resource endpoint to inspect the active backend resource set:

GET http://localhost:3001/api/adaptive/resources

Use the decision endpoint to inspect the active decision for the current frontend session:

POST http://localhost:3001/api/adaptive/decision
Content-Type: application/json

{
"sessionId": "session_...",
"mode": "mcp"
}

The frontend displays the current session id in the top bar. Dionysys-specific browser/session helpers live in demos/excalidraw/frontend/src/dionysys/session.ts; Excalidraw scene persistence still lives in the demo frontend and should not be promoted into public SDK packages.

Verification​

Run targeted checks after configuration edits:

npm run test --workspace=@dionysys-demo/excalidraw-frontend
npm run test --workspace=@dionysys-demo/excalidraw-backend
npm run build --workspace=@dionysys-demo/excalidraw-frontend
npm run build --workspace=@dionysys-demo/excalidraw-backend

Run the demo manually:

npm run dev --workspace=@dionysys-demo/excalidraw-backend
npm run dev --workspace=@dionysys-demo/excalidraw-frontend

Then open the frontend, switch between Deterministic and MCP, draw shapes or add text, and confirm the debug panel shows the expected mode, persona scores, selected personality, confidence, and variant.

Common Gotchas​

  • If MCP mode always returns neutral, confirm the frontend and backend are using the same SESSION_ID.
  • If a custom toolbar does not show, confirm toolbar.mode is allowlist and every tool id exists in DynamicToolbar.tsx.
  • If a menu item does not show, confirm it is included in mainMenuItems and rendered in EditorShell.tsx.
  • If a manual layout preview needs to change the demo state, use setManualOverride(...) rather than _store.setState(...).
  • If a connector decision is ignored, check that personalityId, actionId, and confidence are valid and meet the resolver's minConfidence.
  • If deterministic mode changes but MCP mode does not, update both VARIANT_CONFIGS and EXCALIDRAW_PERSONALITY_RESOURCES.