| name | dashboard |
| description | Authoritative reference for the Lagune dashboard, a live view of a project's .lagune/ chain with a locked-down local action surface. Use before changing anything under src/dashboard/ or src/types/dashboard/. |
| user-invocable | true |
| metadata | {"internal":true} |
Lagune dashboard
A live view of a project's .lagune/ chain: charter, findings, hardening, verification, and the applied sub-skills. It parses the real memory artifacts and tracking map on every request and pushes a browser reload whenever anything under .lagune/ changes. Viewing is read-only. The one write path is the Settings action surface, which runs the CLI's own core in-process behind the guards described under Actions.
Authored in strict TypeScript with Node APIs, runtime-agnostic across Node, Bun, and Deno. It ships as a self-contained static client plus a zero-dependency server, both bundled into lib/ at build time. src/ is never published.
The product mission and workflow philosophy live in CLAUDE.md. The toolchain, code conventions, and build path live in the engineering skill, the repository layout in the architecture skill, and design-engineering principles, including how to verify rendered output, in the /interface skill.
Consistency
The client follows one visual system. Same-styled elements never carry their own local variants, and when two of them disagree on a value, standardize on the smallest one already in use. New kinds of visual consistency belong here as subsections, never as new top-level sections.
For general guidelines on the user interface, CDP, screenshots, etc., see: /interface
Spacing
- One card inset: content inside any surface card sits
p-4.5 (18px) from every edge, all four sides. The metric cards, finding cards, charter cards, rail blocks, and detail cards all share it.
- List cards put every padding on the row: the container is only
overflow-hidden rounded-lg bg-surface shadow-card, with no padding of its own. Each row owns px-4.5 py-3, and the first and last rows close the card edge with first:pt-4.5 and last:pb-4.5 (in the findings table, the header is the first row: pt-4.5 pb-3). This keeps row hover and dividers spanning the full card width, and keeps every spacing readable on the element itself. Never assemble an inset from a parent padding plus a child padding: a hidden contribution on the parent is exactly the inconsistency this rule exists to prevent.
- Fixed rhythm: rows use
py-3 with gap-3, card grids use gap-4, sections end with mb-6, and section heads sit mb-3 above their content.
Tiles and icons
Icon tiles in rows are size-8.5 with text-[1.05rem] glyphs, circular arrow affordances are size-8.5 rounded-full text-[0.95rem], and large tiles are size-11 rounded-md text-[1.25rem].
Typography
Micro labels (uppercase group titles, table headers, card labels) come from MICRO_LABEL in client/styles/classes.ts, badges from BADGE, group heads from GROUP_HEAD. Reuse the token instead of retyping a near-copy. Card titles are text-[0.9rem] font-bold tracking-[-0.01em], with extrabold reserved for page and section headings.
Run
End users run it against their own project, with no install:
npx lagune dashboard
That serves the built client and opens the browser on the workspace's .lagune/.
Local development in this repo:
npm run dashboard:dev
npm run dashboard:build
npm run typecheck
The server prints its URL. By default it binds port 0, so the OS hands back a free port that never conflicts (set PORT to pin one, where it climbs on conflict). In npm run dashboard:dev, the browser opens on the Vite URL: client edits hot-swap modules in place (state survives), while the node server, pinned on port 3001 behind Vite's proxy, keeps watching .lagune/ and reloads the page on any edit there. Nothing is written to disk in dev, Vite serves the client from memory.
How it fits together
- Server (
server/): a node:http server, runtime-agnostic across Node, Bun, and Deno.
start.ts is the reusable entry: it resolves the paths, listens (port 0 by default, climbing only when PORT pins a busy one), opens the browser, and stays up until SIGINT. The dashboard CLI command and the dev entry (tools/dashboard-serve.ts) both call it.
data/ parses the Markdown memories, tracking.json, and manifest.json into one typed DashboardData object, served at GET /api/data. Understanding markdown is deliberately not our code: the markdown/ module asks mdast-util-from-markdown (micromark, the same CommonMark engine behind the website's Docusaurus, bundled at build time so the published package still needs zero runtime installs) which lines are code and which spans are HTML comments. Code of any kind (fenced, indented, diffs, mermaid diagrams) is never structure, and comments are stripped before any parsing without ever touching code or inline code. Only the domain mapping is manual: which heading opens a section, which - **Field:** line is a field, what goes where. An h1 ends any section, and h4 to h6 stay inside the block they annotate, out of the extracted prose. Field lines are read by a staged grammar (fieldValue) whose canonical shape is the template's and whose fallbacks absorb common LLM punctuation drift: any list marker (-, *, +), optional bold (** or __), case-insensitive field name, then a required separator (colon canonically, em or en dash, or a spaced hyphen). The separator is what keeps prose from matching, and the value is never rewritten. Never hand-roll markdown lexing here (fence pairing, comment masking): extend the mapping over the tree instead.
live-reload.ts watches .lagune/, streaming a reload over GET /events (SSE).
static-files.ts serves the built client, guarded against path traversal.
- The server reads
.lagune/ from the invoking working directory and the client from the packaged location, never from src/.
- Actions (
actions.ts, guards.ts, ): the Settings route runs real commands (Install, Pull, Update, Specialize) through , in-process via the CLI's own pure-fs core. Every change must preserve these invariants:
Security context
Read all, one at a time:
Routes
#/ overview · #/findings and #/findings/:id · #/sidequests · #/charter · #/skills · #/settings.
Add ?theme=dark to the URL to force a theme.