Apelora Studio — interface specification (front end only)

Everything here runs in the browser against mock data. Nothing is sent to a server or deployed. This is the reference for what the UI must do; STUDIO-MVP.md explains how it is built.

1. Layout

RegionSpec
Header (48px)Logo + "Apelora Studio", Mock · Design only badge, breadcrumb + save state (≥1400px), Undo/Redo, density (Compact / Comfortable / Expanded), New ▾ (empty, sample, sample + network), Import, Export JSON (the single primary button), theme
Catalog (left, 176–192px)Search, module count with collapse all / expand all, and a two-level menu: categories (Custom first, with Create module… at the end of the section), each split into subcategories with counts. Only the first category starts open, and the first subcategory of a category opens with it; the open state is remembered in localStorage (apelora.studio.catalog-menu.v1). Headers are buttons with aria-expanded inside headings (category h2, subcategory h3, indented and lighter). A search lists matches from every group under their category path (“Networking › Routing”). 2-column tiles (logo + name). Tile: drag to canvas or click to add; pencil = edit custom / copy built-in
Canvas frame (everything else)Light-gray #F7F7F8, 12px radius, 1px border, full width (there is no permanent right sidebar). Title strip: three dots, tools (Select, Pan), editable assembly name, Zones menu, selection count, toggles (grid, labels Aa, cable style, routing, snap), zoom −/%/+/fit/zoom-to-selection. Validation panel docked bottom-left
Properties window (on demand)A floating, draggable window opened from the context menu (Properties…), a double-click, or Enter. See "Properties window" below

2. Module node

128×96 flow units (compact), 24px header, centred 30px logo, left inputs / right outputs. Every terminal is an RJ45-style socket with a short label; connected sockets show a plug head in the protocol colour; ×N marks several cables. Height grows only when ports need it. Status: amber/red border for warnings/errors. Resize handle bottom-right. Full geometry and behaviour: STUDIO-MVP.md.

Network interface (net)

Every module has a net terminal, the equivalent of a network card (NIC): an input of type network, single-cable, optional. It plugs into a Network module (a virtual network segment; it runs no instance) or any module that offers a network output (firewall lan, another Network). Left unplugged, the module joins the application's default network. A small link light on the jack turns green when the NIC is plugged; the inspector shows Network: default network | <module> (cable) | <module> (tag lan). The net terminal cannot be deleted from an instance, is locked in the Module Designer (every custom module gets one), and is added automatically to older saved or imported assemblies.

A link tag names a terminal so it is connected, with no drawn cable, to every other terminal carrying the same name (an output feeds the inputs of its net). Use cables for the main data flow and tags for repetitive side links (monitoring, logs, the network). A tagged terminal shows its plug and a coloured name flag; hovering a flag highlights every member; clicking it selects the tag (inspector: rename/merge, member list, Convert to cable for one-output/one-input tags, Remove tag). Create: port row "link tag" field, port menu ▸ Link by name, or a cable's menu ▸ Convert to link tag. Rules match cables: protocol compatibility, no self-link, single-cable ports accept one link in total, required inputs are satisfied by a tag that has an output; nets without a source or listener raise warnings. Tags are not copied by Duplicate/Paste.

Components (grouping)

Select two or more modules and choose Group into component… (context menu or Ctrl+G). The dialog asks for a name and lists every inner terminal; terminals that are linked to something outside the selection are pre-selected and locked (marked "linked"), others can be exposed optionally, and each exposed port gets an editable name. Creating the component replaces the selection with one module, re-plugs the outside cables onto its exposed ports, registers a reusable custom class (catalog ▸ Custom) and shows a stacked-card look with a ▣ N badge. Its net plugs every inner module's NIC into the network. Ungroup (menu, inspector button, Ctrl+Shift+G) expands it back into independent modules and internal cables, re-plugging the outside cables to the inner terminals. Both are single undo steps. Exports embed the component class (and any custom classes it uses); importing restores it. Component classes are edited by ungrouping; they cannot be re-opened in the designer. Tags that cross the selection boundary stay with the inner modules.

Tools

  • Select (V, default): click selects; drag on empty canvas draws a selection box (partial overlap selects); Shift- or Ctrl/⌘-click adds or removes; Ctrl+A selects all; clicking empty canvas clears. The strip shows how many modules or zones are selected.
  • Pan (H): dragging empty canvas moves the view. In Select mode you can pan with Space + drag or the middle mouse button; the wheel zooms.

Properties window

Properties are not shown permanently. Open them with right-click ▸ Properties… (first menu item), double-click, or Enter on a selected module. The window floats near the thing it describes (and never off-screen), has a category-coloured header, can be dragged (its position is remembered), does not block the canvas, closes with ×, Esc, or when its target is deleted, and is retargeted by opening another one.

TargetContent
Moduletabs General (name, class, id, network, size, resources, component contents, save as custom module, delete) · Configuration (n) (typed form) · Ports (n) (name, type, required, multi-cable, link tag, reorder) · Issues (n)
Cableendpoints, protocol, convert to link tag, unplug
Link tagrename or merge, members (click to select), convert to cable, remove
Zone / shapetabs Style · Label · Layout (see Zones)
Assemblycounts and format (canvas menu ▸ Assembly properties…)

Clicking an issue in the validation panel opens that module's Issues tab.

Zones and shapes

Zones are labelled shapes drawn behind modules to show sectors, trust zones or tiers. Four shapes: square, circle, rounded square, triangle.

  • Create: Zones menu in the canvas strip (adds near the view centre, fanning out so zones never stack exactly), canvas right-click ▸ Add zone (at the pointer), or select modules and choose ▸ Wrap in zone (sized to contain them, then the label editor opens).
  • Move / resize: drag the zone's border or label; resize with the corner handles. The interior does not capture clicks, so cables and modules under a zone stay usable and a selection box can start inside a zone. Modules inside a zone move with it; the whole drag is one undo step. Lock pins a zone.
  • Edit (right-click ▸ Edit shape…, or double-click the label): Style — shape kind, fill colour (8 modern palette colours plus a custom picker), fill opacity, border colour, border width, border style (solid, dashed, dotted), corner radius (rounded only). Label — text, position (top-left, top, centre, bottom), font size. Layout — x, y, width, height, lock, contents count, bring forward / send back, duplicate, delete. Each control merges rapid edits into a single undo step.
  • Right-click menu: Edit shape, Edit label, Shape ▸, Colour ▸, Border colour ▸, Border style ▸, Select modules inside, Bring forward, Send back, Duplicate, Lock, Delete.
  • Zones are view-only layout (editor.shapes): they never change deployment semantics. Colours are validated as #rrggbb, sizes are bounded, and at most 200 zones are accepted on import. They scale with the density presets and are exported and imported with the assembly.

3. Interactions

ActionHowResult
Add moduleDrag tile to canvas · click tile · canvas menu ▸ Add moduleLands under the pointer (drag/menu) or at the view centre without overlap (click); selected
Move / resizeDrag node / bottom-right gripOne undo step per gesture; size persists; density keeps manual sizes
ConnectDrag from a socket to a compatible socket (either direction)Cable created; invalid → toast with reason and hint
Re-plugDrag a cable's plug end to another socketValidated; one undo step
SelectClick · Shift/Ctrl-click · drag a box · Ctrl+ACount shown in the canvas strip; nothing opens automatically
DeleteDel/Backspace · context menuModules remove their cables; cables unplug
Copy / paste / duplicateCtrl+C / V / D, menusCopies modules + cables between them, fresh ids
Zoom / panWheel, +/−, F, drag background, buttonsViewport transform only
Undo / redoCtrl+Z, Ctrl+Shift+Z / Ctrl+Y, header100-step snapshot history
Import / exportHeaderValidated, deterministic JSON; failure leaves the assembly untouched

Context menus (right-click, Menu key, Shift+F10)

Module, several modules, port, cable and canvas menus are listed in STUDIO-MVP.md. Module menu: Properties… (Enter), Rename (F2), Configure… (opens the Configuration tab) and Reset configuration (shows how many fields changed). Keyboard: ↑/↓/Home/End, → opens a submenu, ← or Esc closes, Enter activates.

4. Configuration forms

Module classes declare typed properties; each instance stores only overrides (config), so defaults are never copied and "reset" simply removes the override.

TypeControlValidation
stringtext≤ 256 chars
intnumberinteger, optional min/max
booltoggle—
enumselectmust be one of the options
secrettext, monospacea reference name (db-password), never the secret; a pasted value fails with "Never paste the secret itself"

Fields are grouped (first group open; groups with changed or invalid fields open automatically), show a • marker and a per-field reset when changed, Reset all in the header, inline role="alert" errors with a correction hint, and mark required fields with *. Invalid values are kept (so the user sees what is wrong), flag the node red, and appear in the validation panel as config-invalid. Class property ids are stable: editing a custom class keeps ids so saved values keep matching.

5. Module Designer (custom modules)

Dialog opened from Create module… in the Custom section, a tile's pencil, or Save as custom module…. Left: name, category, summary, vCPU, memory, generic icon, live node preview. Right, two tabs:

  • Ports — name (short, shown on the canvas), protocol type, description (tooltip), required, several-cables, reorder, delete.
  • Configuration — fields with label, type, default, group, help text, required; enum options (comma separated), int min/max. Saving validates labels, enum option count, min ≤ max and that each default satisfies its own rules; errors list in a red alert and nothing is saved until fixed.

"Save as custom module…" copies the instance's ports and uses its current configuration values as the new class defaults. Custom classes live in localStorage, are embedded in exported assemblies, and are restored on import.

6. Motion

Tokens: --apl-dur-fast 120ms, --apl-dur 180ms, --apl-dur-slow 260ms, --apl-ease cubic-bezier(.2,.7,.2,1); the same values are in features/editor/motion.ts.

ElementTransition
Context menu / submenupop (opacity + scale .96→1, 120ms) from the top-left
Dialogsbox pops, backdrop fades (180ms)
Toastsrise + fade (180ms)
Node addedfade in (180ms; opacity only so geometry stays exact)
Plug connectedslides in from outside the socket (180ms)
Cable createddraws along its length (260ms); label and arrow fade in
Hover / selectionnode border and shadow, jack colour, cable width (120ms)
Catalog section open, inspector group open, validation listdrop-in fade (120ms)
Zoom, fit, zoom-to-selectionanimated viewport (120–250ms)

Reduced motion: when prefers-reduced-motion: reduce, CSS durations collapse to ~0 and programmatic viewport animations use duration 0. Nothing depends on an animation finishing.

7. Accessibility

Every control has an accessible name; sockets expose name, type, required, cable policy and plugged count via aria-label; menus use role=menu/menuitem(checkbox) with roving focus; errors use role=alert; focus is visible; colour is never the only signal (labels, ×N, text errors); contrast follows daisyUI themes (light, winter, night).

8. States and errors

Empty canvas hint; validation panel (No issues / counts / list with hints, click focuses the module); import failure dialog with the first errors; corrupted autosave is kept under apelora.studio.v1.invalid and the user is told; storage unavailable degrades silently to in-memory.

8a. Workflow aids

  • Connect faster: right-click a jack → Add module that connects here lists compatible catalog modules (exact protocol first) and places and cables one. While a cable is dragged, compatible sockets light up and the rest dim.
  • Starting points: New → From template… (nine starter diagrams built from the catalog) and From Docker Compose… (services become modules, depends_on becomes cables; the file is read as text only).
  • Layout: Tidy (Shift+T) arranges the flow left to right and re-fits zones; alignment guides and snapping while dragging; arrow keys nudge (Shift = 40); overview map; Find (Ctrl+K); only visible elements are rendered above 150 modules.
  • Documentation: sticky notes; vCPU/memory totals for the assembly and per zone; export as SVG, PNG or print; share link (the diagram is packed into the URL fragment, nothing is uploaded).
  • Safety: named snapshots with a plain-language comparison; share links keep the previous diagram as a snapshot.
  • Target platform: the assembly records a design target (Incus by default; VMware planned) in spec.target.platform; see HYPERVISOR-PROVIDERS.md.
  • Help: ? opens the shortcut sheet; a four-step tour appears once on a first visit.

9. Out of scope (this phase)

Backend, deployment, real hosts, authentication, drilling into a component on the canvas (contents are listed in the inspector), validating a component's inner assembly, setup parameters at application level.

This page is a copy of docs/architecture/STUDIO-UI-SPEC.md in the repository, which is the source of truth. Suggest a change.