Skip to content

apps/diagram — Feature Requirements

Status: proposed — every row is not started · Date: 2026-08-08 · Scope: apps/diagram — architecture (DIA), camera (CAM), world (WLD), interaction (IXN), rendering (RND).

Architecture (DIA)

IDRequirementStatusTraces to
DIA1The application depends on sparkles:ui, sparkles:ui-app, sparkles:input, sparkles:base, sparkles:core-cli and nothing else — no sparkles:ui-tui, sparkles:ui-raylib, raylib or sparkles.tui import anywhere under apps/diagram/ (APP2).not startedapps/diagram/dub.sdl
DIA2The isolation check is a manual grep, run at every phase gate and recorded in the PR: rg -n "ui_tui|ui_raylib|sparkles\.tui|import raylib" apps/diagram/ must find nothing. Deliberately not automated — the decision is recorded in the ui-app plan.not startedPR checklists
DIA3The app enters through runApp (HST10): a component whose view supplies the (empty) page tree, whose handle feeds systemInput, and whose paint (HST13) replays the frame's op buffer onto host.canvas through the toolkit's immediate interpreter — so the board renders identically on both arms.not startedsrc/app.d
DIA4Two configurations: the default carries the host's full closure; a no-gui configuration carries tui only — proving the app builds and runs where no GPU stack exists.not startedapps/diagram/dub.sdl
DIA5The world's columns and the frame's op buffer are SmallBuffer/fixed arrays; labels live in fixed char[labelCap] slots — the steady-state frame is @nogc, asserted by compiling a frame path under the attribute.not startedsrc/world.d; src/systems/
DIA6Every system is a free function over WorldEvent → World mutations and World → ops renders — so every behavior is a scripted-event or pure-function test against the recording target (TST1), and main is the only untested line.not startedsrc/systems/

Camera (CAM)

IDRequirementStatusTraces to
CAM1The camera is origin (world cell at the viewport's top-left) plus a discrete zoom level (powers of two, clamped) — integer cell geometry on both targets, so GUI and TUI agree on every mapping (P3 constraints).not startedsrc/camera.d
CAM2worldToScreen/screenToWorld round-trip within a cell at every zoom; zoomAt(pivot) keeps the world cell under the pointer stationary; panBy is unbounded (an infinite canvas has no edge).not startedsrc/camera.d
CAM3visibleWorldRect bounds render culling; contentBounds over live entities backs fit-all (f) and the minimap's content fit.not startedsrc/camera.d
CAM4Minimap math — content→panel fit, panel local↔world, camera frustum in panel space — is pure and lives with the camera, tested without any render.not startedsrc/camera.d
CAM5Camera math is @safe pure nothrow @nogc, tested at runtime (union-backed vectors have no CTFE field reads — the recorded sparkles:math limitation).not startedsrc/camera.d

World (WLD)

IDRequirementStatusTraces to
WLD1Entities are dense uint indices with a free list; components are SoA columns (bounds, zOrder, group, label slot + length) sized by compile-time caps.not startedsrc/world.d
WLD2Groups are a group column (0 = none): grouping stamps a fresh group id on the selection, ungrouping clears it, and a move applies to every member — no nested hierarchy in the MVP.not startedsrc/world.d
WLD3Edges are (from, to) entity pairs in their own columns; deleting an entity deletes its edges.not startedsrc/world.d
WLD4Selection is a capped entity list plus the marquee in progress; all interaction state (tool, drag, menu, label edit, capture/press/hover) lives in World so a scripted test inspects one value.not startedsrc/world.d

Interaction (IXN)

IDRequirementStatusTraces to
IXN1Pointer routing is layered, topmost first: context menu → toolbar → minimap → board (screenToWorld); a capture owner (create, marquee, move, pan, minimap scrub) holds the drag through the toolkit's CaptureState.not startedsrc/systems/input.d
IXN2Tools: select (v), rect-create (r), connect (c). Create drags a new box; select clicks/Shift-toggles/marquees; connect completes an edge on the second entity click; Esc cancels the pending half.not startedsrc/systems/input.d
IXN3Pan is middle-drag, Space+LMB, and arrows/WASD — never a held-key-only binding, because a terminal cannot report key releases (INP16); every binding works identically on both targets.not startedsrc/systems/input.d
IXN4Wheel zooms toward the pointer via zoomAt; +/-/0 zoom from the keyboard; m toggles the minimap; f fits content.not startedsrc/systems/input.d
IXN5The context menu (RMB) offers Group, Ungroup, Label…, Connect, Delete; label editing is the toolkit's LineEditState over the fixed edit buffer, committed to the entity's label slot on Enter and dismissed on Esc.not startedsrc/systems/input.d
IXN6Dismissal is a chain: Esc closes the menu, then cancels the pending interaction, then clears the selection, and only then quits; q quits directly.not startedsrc/systems/input.d

Rendering (RND)

IDRequirementStatusTraces to
RND1Three op streams appended in order — board, minimap, chrome — into one reused SmallBuffer!(DrawOp, N); z-order is append order.not startedsrc/systems/render.d
RND2The board culls with visibleWorldRect: an off-camera entity emits nothing to the board stream and still appears on the minimap — asserted as a test, not an optimization note.not startedsrc/systems/render.d
RND3Connectors are orthogonal routes drawn with box-drawing glyphs on both axes (─ │ ╭ ╮ ╰ ╯ + arrowheads) — they route through the GPU backend's procedural box drawing, so arms connect across cells on both targets; the canvas line primitive is not used for them (P3 constraints).not startedsrc/systems/render.d
RND4The grid is zoom-aware and faint; groups outline their members; the marquee and the create preview render from the in-progress drag state.not startedsrc/systems/render.d
RND5Colors come from the theme's slots — the app names Slots, never RGB, except where the theme's page colors are the explicit page (CLI's --theme works unmodified).not startedsrc/systems/render.d

Non-goals (MVP)

Save/load, undo, freehand drawing, diagonal connectors, continuous zoom, multi-select handles/resize, edge labels, nested groups.