Skip to content

feat: dashboard home, Chat, Fleet and a Storybook catalog - #73

Merged
vishr merged 48 commits into
mainfrom
feat/panels
Oct 26, 2026
Merged

vishr merged 48 commits into
mainfrom
feat/panels

Conversation

@vishr

@vishr vishr commented Oct 26, 2026

Copy link
Copy Markdown
Member

What changed

The desktop app becomes a dashboard home with Chat docked beside it. Panels come from a dashboard spec and a panel engine shared by the app, the MCP tools and Chat. The header shows the six attention and hygiene counts. A new Fleet page shows a sankey, a session tree and an inspector. Machine cards show CPU, memory and disk. Every component, panel, page and flow has a Storybook story, and the catalog runs those stories as tests. The TUI now says the same things as the app in the same words: one stuck state, the same header counts, CPU % and disk, and per-session turn strips and files.

A full code review of the branch was applied on top. The fixes include:

  • one-time confirm tokens on destructive desktop actions;
  • Host/Origin checks on the local server;
  • notification clicks that open the item they are about;
  • session identity keyed by machine;
  • the capture policy applied to Chat cards;
  • approval for Chat replace/restore.

Why

The owner asked for a richer dashboard and header than the plain board. Driga needed one renderer for the app, Storybook and MCP Apps so a component cannot drift between them. Drafts were built side by side in Storybook and chosen there. Rejected variants were deleted rather than kept: the topology Fleet with React Flow and ELK, the attention-bar header, and the donut and gallery widgets.

Need you counts sessions blocked on the user (a permission or a question), not Claude sessions sitting at their prompt after a turn. Those are idle, and To review already lists finished turns. The inbox, header, board and pulse now share one live-wait rule, so the counts agree.

Risk and rollout

  • Migrations: two additive migrations.
    • 0004_panel_samples adds disk_total and cpu_percent to machine_samples, and creates the panel_polls, prompt_intervals and panel_events tables.
    • 0005_prompt_sessions adds prompt_intervals.session_id.
    • Both run against an existing database. A downgrade sees extra columns and tables it ignores.
  • Agent settings: nothing new is written to ~/.claude, ~/.codex or other agent settings.
  • Text capture: file paths edited per session and Chat card content follow capture_text, and no file contents are stored.
  • Processes: Stop, kill and cleanup from the app now need a one-time confirm token from the app's own dialog. The local server rejects forged Host/Origin requests, so a web page cannot trigger them.

Contract changes

  • Renamed config key: stalled_after / DRIGA_STALLED_AFTER is now stuck_after / DRIGA_STUCK_AFTER, with no alias (alpha). Users who set the old key must rename it.
  • Schema 2 label: the stalled session label is now stuck.
  • New MCP tools: panel and dashboard tools (internal/mcp/panels.go) and the panels MCP App (internal/mcp/apps/panels.html).

Verified

All runs below were on macOS (host, not sandboxed), at 05d172e:

  • just check, including 1,008 Storybook stories (plays, axe, layout) and 140 UI unit tests.
  • TZ=UTC scripts/isolated.sh go test -count=1 ./....
  • scripts/isolated.sh go test -race -count=1 ./....
  • GOOS=linux go build ./....
  • gitleaks over the branch: no leaks.
  • Installed as a local dev build. The service migrated the existing database, and the app and TUI were checked against live data.
  • I have accepted the CLA.

https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4

vishr added 30 commits October 25, 2026 06:55
The app's pages showed what the TUI acts on but sent every action to the
CLI or Chat. They now carry the TUI's actions as buttons, each confirmed
in the app's dialog where the TUI asks:

- Inbox: Ack per item, and Ack all.
- Reclaim · Processes: Kill and Force kill for verified idle trees on
  this machine; young, unverified and remote trees say why not.
- Reclaim · Disk: Remove for ready candidates; others name the command.
- A project's page: Summarize.

One endpoint, /app/action, replaces /chat/api/flow-action. It checks
each request against the page's snapshot (local rows only, verified idle
and old enough for kills, a confirmation for every change) and runs the
same actions Chat's tools run, which check again before they act.

The menus gain what a Mac app and the browser offered: File > Close
Window (Cmd+W), View > Back and Forward (Cmd+[ and Cmd+]), View > Inbox
(Cmd+4), Reclaim Processes and Reclaim Disk, and Settings and About in
the tray menu.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Driga's own panel format runs over the same snapshot as the app's views:
ten sources (sessions, inbox, projects, orphans, cleanup, activity, memory,
costs, machines, summary), filters with $variables, grouping and
aggregation. The default Home dashboard is ordered by what people
supervising agents need most: the inbox, finished work, runaway processes,
stuck sessions, sessions to resume, then disk.

Dashboards are versioned files in the data directory. Edits are typed
operations checked against the version read; restoring saves a new
version; an agent's replacement keeps dragged positions.

driga mcp gains describe_panels, show_panels, open_dashboard,
list_dashboards, list_dashboard_versions and the dashboard writes, which
return fragments rendered by the ui://driga/panels.html MCP App. Chat may
use them; dashboard writes need no approval card because they change only
the user's dashboards and can be restored.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
The app opens on a dashboard the user owns: Home by default, ordered by
what needs them. Edit mode drags and resizes panels on a 12-column grid,
and every move saves a version. Chat sits beside it and opens with the
inbox and a one-line status, as if an assistant had watched while they
were away. When Chat calls a panel tool, the card in the conversation
runs the spec again for live data and can be added to the dashboard.

The panels render with ECharts through Driga's own spec, never options
from the model, and are escaped before any tooltip. The same renderer
ships as one self-contained file for MCP App hosts (ui://driga/panels.html),
with no eval and no network. ui/ is a Bun, React and Vite workspace;
just ui builds into the committed embeds, so a Go build needs no
JavaScript toolchain. The palette comes from the console theme.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Every view is now a dashboard. Flow, Projects and Machines ship as
editable built-ins beside Home, listed as tabs; the app's menus open them.
New sources (flow, waits) and panel kinds (cards, timeline) cover what
those pages showed: each session drawn from start to end with its waits on
you marked, and projects and machines as tiles. A sum with no readings is
shown as a dash, never zero.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Rows keep hidden identity columns so the app can act on them. Tables,
lists and cards offer the TUI's actions for their source: stop or open a
session, kill a verified idle process tree, remove a ready cleanup
candidate, summarize a project. Anything that changes the machine asks
the app's dialog first. The inbox lists pending permission prompts with
Allow and Deny buttons that arm after a moment.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
The app is Chat with a row of metrics above it. The header is a stored
dashboard of stat panels, Need you, To review, Possibly stuck, Leftover
processes and Reclaimable by default, that the user or Chat can change.
Every other dashboard is one the user or Chat made; opening one docks Chat
beside it. Chat's brief reads the whole inbox whatever is open.

The Flow, Projects, Machines and Home presets are gone, as are the htmx
pages, their templates, scripts, styles and routes, the update long poll
and flow.json. The View menu loses its page items and Help loses Keyboard
Shortcuts. Dashboards gain create and delete in the app and a
delete_dashboard MCP tool that Chat runs only after approval.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
The app opens on Home again, a shipped dashboard under the metrics header
with Chat docked on the side. A viewed dashboard leaves no blank space:
list-like panels take the rows their content needs, every panel moves up,
the tallest give up rows and scroll inside when content is taller than the
window, and each panel then widens and grows into the free space beside
and below it. Chat's brief leaves the inbox to the dashboard when it shows
one, and chat cards can join the open dashboard or start a new one.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
The app and the panels MCP App follow LabStack's Canon: Mantine components
themed through ui/src/theme, Phosphor icons, TanStack Table for sortable
tables, react-markdown for text panels, Mantine notifications, and one
ECharts wrapper that reads chart colors from the same tokens. The tokens
map Canon's surface, text and tone roles onto the console palette, which
internal/view now writes as ui/src/theme/palette.json, with a contrast test
for both schemes.

The header is a row of stat tiles with the real brand mark and wordmark;
a stat over readings in time returns its trend, drawn as a sparkline with
the change over the hour. The app page carries a per-response style nonce
so Mantine's theme variables load under the strict policy. Status shows
as a badge with an icon and text. The leftover flow-board browser script
is removed.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
…ents

One process per machine, the Driga service, collects, stores, hosts Chat
and posts notifications from login, whether or not the app or the terminal
UI is open. It is drigad, the same program started under that name, and
`driga service run` runs it in the foreground; `driga collect` is gone.
It installs as run.driga.service on macOS, the driga systemd unit on Linux
and the "Driga service" task on Windows. A driga-dev build installs its
own label and log, so it never takes over the release's. Installing
removes the run.driga.collector agent, unit or task earlier releases
installed.

The app no longer runs a collector child. On launch it installs or starts
the service with the login shell's environment, restarts it when it runs
another version, and shows its state in the tray; quitting leaves it
running. The child supervisor, the notification and lifeline pipes, the
app owner kind and the service replacement are removed, and notification
clicks from drigad jump through the driga CLI beside it.

Releases ship drigad: macOS copies it into Driga.app and signs it before
the bundle, and Linux and Windows build it beside driga. Generated
third-party notices for the bundled UI replace the vendored scripts'
license file. Doctor checks are renamed service, service_rss,
other_services and service_definition, and messages and docs say Driga
service throughout.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
…arklines

The header's metrics drop their borders and backgrounds and are set apart
by space, as Visitors' and Fingerprint's stat strips are: a Phosphor icon
and label, the value in mono with its unit, a badge with the change over
the hour, and an ECharts sparkline whose area fades to the baseline and
marks the latest reading. A count that needs the person takes its tone
with a dot; a hover tint shows the tile asks Chat.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Chat runs in the Driga service, and it started its MCP server, jumps and
session actions with its own executable: drigad mcp --chat ran another
service instead, so Chat answered that the driga MCP server was
unavailable. internal/self names the driga beside drigad, and every place
Driga starts itself again uses it, hook paths included.

On macOS a drigad started through a symlinked directory restarted itself
with "service run" already added, and added it again; the restart now
passes the original arguments.

TestChatThroughTheService builds driga and drigad, runs the service in an
isolated home with a stand-in claude that completes the MCP handshake and
calls show_panels, and asks a question from the CLI. It fails without
either fix.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Every row repeated its buttons, Open pane and Stop on each line. A row now
opens its session's pane when clicked or on Enter, and one ⋯ button, shown
on hover, keyboard focus or touch, lists its actions with icons: the main
one first, then Stop, Kill, Force kill, Remove or Summarize in red, each
still asking the app's confirmation. Tables, lists, cards and the inbox
share it.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
A failed turn's error reached the message only while the service ran, and
only within the capture policy, so a failure was gone after a restart. The
service log now records it, scrubbed.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
driga dev serve serves the app's page and API over HTTP on loopback, from
the data directory the environment names, with the same handlers as the
app: dashboards, Chat through the Driga service, and local actions. just
dev runs it under watchexec, restarting on Go changes, behind Vite's dev
server, which hot-reloads the UI and proxies the API. Without the app's
native dialog, confirmation falls back to the browser's.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Home now answers "what needs me" more honestly and with fewer clicks.

- The inbox collapses a session's finished turns into one row, and
  acknowledging it clears every turn.
- Sessions show the state without the "~" marker, outline inferred
  states, and explain them with the evidence in a tooltip. Unknown
  memory reads "–" rather than 0 B.
- The stuck panel and the header count match "~stalled?" as well as
  "stalled?".
- The memory tile shows the total ("26 / 36 GB"), and its chart axis
  steps in powers of 1024.
- Clicking a header tile opens a menu: show the panel on Home, or ask
  Chat about it.
- Empty panels collapse into an all-clear strip above the grid.
- Chat cards are kept whatever the capture policy, Chat notes when
  earlier messages were dropped, and a failed reply points at the
  service logs.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Finished turns now say what there is to review, past sessions can be
found and resumed, and the page says when its numbers are stale.

- Each finished inbox item carries the files its session edited and its
  checkout's uncommitted and unpushed counts and latest commit. The inbox
  shows them as "To review" with a Mark reviewed button on the row.
- ⌘K, or the header's Search button, opens a Spotlight over live
  sessions and those that ended in the last two weeks. A live session
  opens its pane; an ended one copies its resume command, or asks Chat
  when its agent has none. The board snapshot keeps that history.
- "Need you" counts Chat's permission prompts as well as waits.
- When a refresh fails the page says since when it is stale, and the
  prompts last seen stay listed.
- Chat stays mounted while hidden, so a draft survives, and whether it
  is open is remembered.
- Working is no longer green: success is kept for verified outcomes,
  and each state has its own icon.
- Chart cards state their reading in words and can show their data as
  a table. Machine memory draws one line per machine, and project
  memory says it covers this machine only.
- `just check` now runs the UI tests and fails when the committed UI
  build is behind ui/; CI installs a pinned Bun to run it. The build
  committed with the last change was stale, which this would have
  caught.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Every Chat turn failed with 'unexpected tool "mcp__driga__delete_dashboard";
Chat requires driga read tools only'. Chat refuses to start when its MCP
server offers a tool it does not expect, and the server offered every
dashboard tool: delete to Chat with actions, and all the writes to
read-only Chat.

Chat's server now registers dashboard writes only for Chat with actions,
and delete_dashboard never; outside MCP clients keep all of them.

The fake claude in the end-to-end test now lists the server's tools in
its init event as claude does, hiding app-only tools, so the test fails
without this fix.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
- "Need you" counts machine warnings still in effect, such as memory
  pressure and low disk, as well as waits. The inbox lists warnings in
  their own Machines section with a Dismiss button; they no longer pass
  for finished work.
- Every review row says what is left: its files, uncommitted and
  unpushed counts, "clean" when the checkout has nothing left over, or
  "no checkout". It shows the turn's summary, or else the last commit,
  and a visible Reviewed button.
- When the board is taller than the window, the last panels give up
  rows first, charts down to a readable floor, so the inbox keeps its
  rows. A panel that grows to its content now pushes the panels below
  it down instead of overlapping them.
- Sessions list waiting first, then stalled or dead, working, idle and
  finished, newest first within each. An untitled session off the main
  line is named with its branch.
- The tray's session items open that session's pane, and the Find menu
  opens search.
- A failed Chat reply offers Retry, and a send that never reached the
  service gives its text back.
- Inferred states keep the one light badge style with a dashed edge;
  badges keep their text, long titles and branches truncate, and every
  bar is labelled.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Using Chat end to end showed four bugs:

- Chat's own claude session was collected as one of the person's agents
  ("You are driga Chat…" in project "chat"). Chat's prompts open with
  model.ChatPreamble, and the collector now skips sessions that do.
- A panel title kept "$project" as written. Results now carry the title
  with its variables filled in, or "all projects" when one is empty.
- A filter such as occurred_at gt 2026-10-25T00:00:00Z matched nothing,
  since gt and lt compared numbers only; times now compare as times.
  That filter made a Chat card empty, and an empty inbox card said
  "Nothing needs you right now" over the card's own empty text.
- Text from consecutive model messages ran together ("gate.I didn't");
  a new message now starts a new paragraph.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
The Chat column is 400 px wide, and its cards drew whole boards there:
tables scrolled sideways and charts were too small to read.

- A card now says in a few lines what a panel tool showed: each panel's
  title and reading (a stat's value, a chart's summary, a list's row
  count, or why it failed), with Open, Add to Home and Save.
- Open shows the panels in the main area, marked "Preview from Chat",
  with Add to the open dashboard, Save as dashboard and Close.
- When Chat creates, edits, replaces or restores a dashboard, the card
  says so: "Saved Home", its version, chips for what changed ("Added
  Quota", "Removed Memory"), and Open dashboard.
- Chat is told to answer in lists and put tables in panels; a markdown
  table it writes anyway scrolls inside itself.
- Bars grouped by session id are named by the session's title.

Also fixes cards that broke after a restart under capture_text =
"snippets": a reopened conversation cut each card to a snippet, which
is not JSON. Cards are now kept whole when loaded, as when saved.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Chat cards no longer have Add to Home and Save buttons, and neither does
the Chat preview. As in Fanout, a view is kept by asking Chat, and the
card for that write is the record of it.

A panel card now waits for its tool's result. A dashboard write's card
names the dashboard and version it saved, which claude passes as the
tool's structured result, so Open dashboard opens what was saved rather
than racing the save. A rejected draft says why it was not saved.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
As in Fanout, Chat stays where it is and its results open inside the
conversation rather than in the main area.

- A card is collapsed to a few lines, the newest one expanded. Expanded,
  its panels stack full width to fit the Chat column, and Full screen
  shows them at full size in a dialog. The main-area preview is gone.
- Chat can delete a dashboard. Delete is not pre-allowed, so each one
  waits on an approval card; the other writes still run straight away.
- Text panels render GitHub tables (remark-gfm 4.0.1), and cards count
  and describe text panels instead of reporting "0 panels".
- A turn's reply shows after its tool rows and cards, as the answer
  that ends the turn.
- Chat's own claude session is also recognised by its working
  directory, so it stays out of Sessions before its first prompt is read.
- An empty action result, such as delete's "{}", is not shown.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Chat that could act started claude with every built-in tool, so one
Allow could run any shell command Chat wrote, or change any file. It now
gets only Read, Glob, Grep and TodoWrite. Everything Chat changes goes
through Driga's own tools and their approval cards. Chat refuses to start
when claude offers Bash, Edit, Write or any other built-in tool.

A conversation that used Bash before may have its model call it again.
claude refuses a tool it did not offer and the model goes on, so the
turn no longer ends; a refused tool that was offered still ends it.

A card for a dashboard deleted since says so instead of
"dashboard not found".

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Chat was a fixed 400 px column. The board and Chat now share the window
as resizable panes (react-resizable-panels 4.14.4): drag the line
between them, or focus it and use the arrow keys. Chat's width is
remembered, and hiding Chat collapses its pane, which keeps the
conversation and any draft, then shows it again at the same width.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Driga's UI now has a local Storybook, following Canon's design review
convention and monk's setup: `just design` serves it on 6506 for review,
never published, and `just design-check` runs every story, its play,
accessibility (failures are errors) and Canon's layout check in
Chromium, light at desktop width and dark at laptop width. `just check`
and CI run it.

- Components/Panel has one story for each kind of panel, with loading,
  empty, error and narrow states; Home's own panels, the header metrics,
  the board, Chat, Chat cards, search, row menu, status badge, measure,
  chart and brand each have theirs. Pages/Home renders the real app;
  Flows/ walks through answering a prompt, reviewing finished work,
  asking Chat for a dashboard, and finding and resuming a session.
  Embedded/Panels app is the MCP App's view; Theme/Tokens the palette.
- Stories render the shipping components. Their data is Driga's own
  panel engine run over a fictional fleet (internal/panel/stories_test.go;
  DRIGA_WRITE_STORIES=1 regenerates it and the test fails when it is
  stale), and a fake service answers the app's requests; any other
  request fails the story. Time and storage are pinned per story.
- A unit test fails when a component or a kind of panel has no story,
  and when app code imports a fixture.
- App moves out of main.tsx, and the MCP App's view out of its host
  connection, so stories can render them.

The catalog found real bugs, fixed here:

- Truncated text had no way to read it in full: inbox rows, table and
  card cells, panel titles, Chat card lines and the page description
  now carry their full value.
- At narrow widths inbox rows overflowed and clipped their buttons; the
  header metrics clipped their trend, and their row could not scroll.
  Trends and sparklines now show only on wide windows.
- A gauge's "of" line was clipped in its default height; a text panel
  clipped its content instead of scrolling.
- Light status colors missed AA on the inset surface Chat sits on (red
  was 4.16:1). Light text roles now pass on crust, the darkest light
  surface, in the console palette too, and the theme test checks every
  surface.
- Header tiles and the search button were named differently from what
  they show; the tile menu's description broke the menu's roles;
  scrollable panels could not be reached by keyboard; notification
  close buttons had no name; the search dialog had none and now has a
  title; two "Sessions" panels made duplicate landmarks, so panels are
  articles.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Until the open dashboard arrived, the app took it to lack an inbox, so
Chat's brief showed its own copy of the inbox and then removed it when
Home came in; a click in that moment hit a button that was going away.
While the dashboard is unknown the brief now shows no copy. A
Pages/Home story holds Home loading and fails without the fix.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
A catalog test now loads every story file and every app module, and fails
when a story's component is not one the app exports, so a story cannot
show a copy or stand-in of a screen. The theme's token sample is the one
listed exception. Pointing the Brand story at a stand-in fails it.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Adds @xyflow/react 12.12.0 and elkjs 0.12.0 for a draft Fleet view, the
Drafts sidebar group, and a catalog test that keeps app code from
importing drafts.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Two presentational drafts of the 56 px app header under
ui/src/drafts/header, for the owner to compare in Storybook with
today's header:

- ClustersHeader: attention, hygiene, this-machine memory and fleet
  pulse clusters set apart by hairlines; tiles tinted with a rule and a
  dot above zero, one pulsing waiting dot, quiet zeros, one all-clear
  line when nothing needs you, a memory sparkline with a delta chip and
  a pressure bar, one dot per machine, and the keys 1, 2, 3 and g f.
- AttentionHeader: three large counts with their hour faded behind them
  and a delta with per-metric polarity, resources demoted to a quiet
  cluster, one chip per machine with a 30-poll tracker strip, on a bar
  one surface step dimmer than the content.
- DeltaChip, the change chip both share; Header.ts, the contract both
  take; fixtures of a fictional fleet; stories per variant (busy, all
  clear, one stuck, many machines, laptop width, filtering, keyboard
  focus) and a Compare page with both drafts and today's header.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
…o choose between

Two drafts of a Fleet view under ui/src/drafts/fleet, both over one typed
contract (machines, projects, sessions with state, agent, memory, parent,
files, last tool and waiting reason; onSelect, onOpen and onReply) and one
fictional fleet of three machines, five projects and fourteen sessions.

A, Sankey: a machine strip, then an ECharts sankey from machines through
projects to sessions where link width is memory and node colour is state,
with a fixed node order, adjacency on hover, yellow paths for waiting
sessions and leftover pseudo-nodes per machine; a Tree mode as an
accessible table; an inspector beside it.

B, Topology: React Flow with ELK's layered layout, machine boxes holding
project boxes holding session cards with a status ring, agent chip, 30-turn
strip, memory and Reply or Open; spawn edges and red dashed same-file edges;
hover isolates a neighbourhood; colours follow the Mantine scheme through
the --xy-* variables; nodes are keyboard-focusable; the canvas loads lazily
and the minimap is off by default.

Shared parts (header, machine card and strip, inspector, tree, turn strip,
empty state), a Compare page that stacks both, fixtures, unit tests for the
model, and a story file per component with plays, Docs descriptions and the
page stories pinned to the desktop width.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
vishr added 18 commits October 25, 2026 18:12
Sixteen widget drafts under ui/src/drafts/widgets, each a component with
typed props, fixtures beside it and a story file with a play on every
story: KPI tile, bar and ring gauges, chart card with peak, wait and event
annotations, status badge, agent chip, inbox item, session card, machine
card with its 90-minute strip, tracker bar, category bar, bar list, donut,
heatmap, session swimlanes and empty state, plus a Gallery that lays them
on one board. Colors come from the theme; the one palette module holds the
validated series palette and the agent tints. Lint, the unit tests and the
catalog check pass in both schemes.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
… drafts

The chosen drafts become the app: the clusters header replaces the metrics
row inside the shell, the Fleet becomes a page the app opens (fleet pulse,
g f, Search, drigaCommand view fleet) built from what the service already
sends, and fourteen widgets replace today's inbox rows, status badges, empty
states, gauges, bar and timeseries panels and the timeline.

One vocabulary across the three sets: working green, waiting yellow, stuck
red, idle blue, finished neutral with the check; one status badge, one
machine card, one tracker strip, one delta chip in the neutral tint; tint
tokens in the theme and a Latte hover that keeps muted text at AA; the
chart module registers every chart kind and takes an onEvent; the series
slots are re-stepped Catppuccin hues validated for color blindness.

The header B, Fleet B (React Flow and ELK), Donut, Gallery and the merged
duplicates are deleted. The catalog runs in all four scheme and width cells.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Mantine's ScrollArea paints its bar inside the viewport, so the right-aligned
values in a bar list or a table's last column sat under it once the content
overflowed, as in Home's "Memory by project" in the desktop app. Every scroll
area now offsets its content while a scrollbar is present, set once in the
theme.

The catalog gains a check, beside Canon's layout check, that fails a story
when visible text lies under a vertical scrollbar, measuring wide tables at
their scrolled end. A new Panel story overflows a bar list on purpose.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
…g panels

A card's Full screen and Open dashboard were a row of text buttons under its
panels; they are now icon buttons with tooltips at the right of the card's
header, as panel actions are elsewhere in the app. The header no longer says
how many panels a card holds, since the panels below say what it shows; it
says only how many failed and, once saved, the version. A created or replaced
dashboard's note reads Created or Replaced.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Sample CPU busy share and persist disk totals across the service and Fleet.
Serve attention and machine poll history, captured turn outcomes and relative
file paths, project tool activity, and targeted desktop notification clicks.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
The Busy story ended by unhovering the brand mark, which left the pointer
logically over the last button clicked; its tooltip opened 300 ms later, after
the check had passed, and failed the layout check whenever a loaded run was
slow enough to measure it. rest() now unhovers every control and waits out the
tooltip delay before asserting none is open.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Keep retained events and prompt history scoped and bounded, preserve machine
and session identity in panels, and enforce dashboard write contracts. Use
stuck consistently and share attention counts, CPU, disk and session details
between terminal and app views.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Require bound one-use confirmation tokens and trusted request origins, keep
notification targets out of process arguments, and restrict navigation. Repair
startup dispatch, service installation ordering, Chat connections and snapshot
cache cancellation, with regression coverage for forged and stale requests.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Guard stale loads and approvals, preserve unknown readings, and fix chart and
dashboard lifecycle, accessible controls and machine-scoped Fleet selection.
Update story names, selection payloads, overlap cases and the minimum-width
legend to match those contracts. Include rebuilt app and MCP bundles.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Document the separate service lifecycle, editable dashboards, notification
routing and stuck vocabulary. Refresh generated references and add checks for
the corrected public instructions.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Keep generated JSON checkouts on LF and bound the Chromium dependency installation step. Cover checkout conversion and the installation timeout with regressions.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
Give header KPI labels and values room for Linux Plex Mono metrics while keeping the 56 px shell. Keep Fleet inspector headings clear of its scroll viewport, add a tall-font regression story, and rebuild the UI bundles.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
macOS carries drigad inside Driga.app, so the driga archive has one binary
there and two on Linux; allow that. On Windows drigad.exe goes into the zip
as a plain file, like Driga.exe, so Scoop still shims only bin/driga.exe.

Claude-Session: https://claude.ai/code/session_01T2vtcW6ihn1ywy9vxUq9a4
@vishr
vishr merged commit 60f39c4 into main Oct 26, 2026
6 checks passed
@vishr
vishr deleted the feat/panels branch October 26, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant