Skip to main content
Version: 0.2

UI guide

The interface has two halves: a developer-facing settings panel (lil-gui) and a product-facing dashboard (src/brain-ui.ts).

Settings panel (lil-gui)

src/gui.ts mounts a lil-gui panel inside the #guiHost card (falling back to a floating panel if the host is missing). It has two folders.

Info: live, read-only counts, polled via .listen():

  • Neurons
  • Axons
  • Signals

Settings: every control routes through Brain.updateSettings():

ControlRangeEffect
Max Signals0..limitSignalsLive active-signal cap (currentMaxSignals).
Signal Size0.2..2Particle point size.
Signal Min Speed0..8Lower bound of signal speed.
Signal Max Speed0..8Upper bound (clamped to ≥ min).
Neuron Size Mult0..2Neuron point-size multiplier.
Neuron Opacity0..1Neuron shader opacity.
Axon Opacity Mult0..5Axon line opacity multiplier.
Signal / Neuron / Axon ColorcolorRecolors the respective elements.
BackgroundcolorStage background (sceneSettings.bgColor).
Floor PlatformtoggleShow/hide the floor.

The "Signal Max Speed" control guards min <= max: if you drag max below min, it snaps max back up to min.

Dashboard (brain-ui.ts)

The product UI ports the original app's panels.

Mode tabs

Two modes (.mode-tab): a Virality Study mode and an EXEPERT Brain mode. Switching to the brain mode sets the stage label to "EXEPERT Human Brain Online". (The brain mode is keyed internally as 'meta' in the UI state: a residual name from before the rebrand.)

Stimulus upload and Analyze

You can upload an image or video as stimulus. The UI previews it, reports its size ("… stimulus loaded. Analyze injects a controlled pulse into the live brain simulation."), and the Analyze button injects a pulse. Under the hood this calls Brain.injectStimulus, which releases a burst of signals scaled by intensity: see Regions & Stimulus.

Telemetry readouts

Once a stimulus is loaded, the UI polls telemetry every 100 ms:

src/brain-ui.ts
telemetryTimer = setInterval(updateFromTelemetry, 100)

It maps getTelemetrySnapshot() into the cortical response index (x/100), per-region bars, and the rolling activity windows.

Activity-bar scaling

The activity bars normalize against the configured signal cap (settings.limitSignals, default 10000 in the UI fallback), so they read as a percentage of capacity rather than an absolute count:

src/brain-ui.ts
const cap = (neuralNet.settings && neuralNet.settings.limitSignals) || 10000
const v = Math.max(0, value / cap) * 100

If typical activity is a few hundred signals, the bars will sit low on this scale: that is expected given the normalization.

Brain-assistant chat

The Chat tab swaps the left region's study content for an inline chat tile (#leftChat) that streams real responses through the Supabase run-chat Edge Function and 9Router. The browser keeps the visible transcript in localStorage, and thumbs feedback on saved assistant messages calls chat-feedback.

The header holds the custom model picker, New chat button, and close button. The picker loads through GET /functions/v1/run-chat?models=1 and renders as an app-owned dark popover instead of a native browser select. It shows the provider icon, readable model label, raw model id, provider groups, search filtering, selected/active row states, and variant badges such as review, mini, high, low, none, spark, and xHigh.

The picker supports click, outside-click dismissal, Escape, ArrowUp/ArrowDown, Enter, and search filtering. It closes and disables while a response is streaming, preserving the selected model under exepert.brainChat.model.v1. If a model route fails because 9Router reports it as unavailable or deprecated, the chat renders a compact .chat-error-card, offers retry/default-model/model-picker actions, and marks that model as unavailable in the picker for the current browser session. The default-model action switches to cx/gpt-5.5 and restores the prompt without auto-sending, so the user stays in control of the next request.

Tile structure

The inline chat tile (#leftChat) is a flex-direction: column container whose five regions are direct siblings: they must not be nested inside one another:

index.html
<div class="left-chat" id="leftChat" role="dialog" aria-label="Neural Brain chat" aria-hidden="true">
<div class="brain-chat-header">searchable model menu / new chat / close</div>
<div class="chat-context">Brain Mode / Response / Signal / Model pills</div>
<div class="brain-chat-messages" id="brainMessages"></div>
<div class="quick-prompts">…seed buttons…</div>
<div class="brain-chat-input">…input · send…</div>
</div>

The column relies on .brain-chat-messages { flex: 1 } to absorb free space, so the header pins to the top and .brain-chat-input pins to the bottom.

Keep the header self-closed

.brain-chat-header is a horizontal row (display: flex; align-items: center). If its closing </div> is missing: or the opening tag is accidentally duplicated: the context pills, messages, quick-prompts, and input get absorbed into the header and collapse into that narrow centered row, breaking the layout. The sibling structure above is the contract.

Observability activity page

Trace Ingestion no longer lives below the chat tile. The observability panel (#obs-panel, built in src/ui/observability.ts) mounts into #observabilitySlot inside the right-panel data-view="observability" workbench view. The existing activity-bar Observability button switches workspace.dataset.workbenchView to observability, hides the dashboard right-panel view, hides the center brain stage, and shows the wider Trace Ingestion page.

src/ui/observability.ts still falls back to the old #obsSlot only for older shells or alternate boot pages. The collapse chevron remains available for small screens, but exepert.obsCollapsed.v2 defaults the activity-page panel to expanded because it no longer competes with chat for space.