Skip to content

Planning UI — analysis notes (2026-08-21) ​

Status: the cross-plan ready board is built (same day — "right off the bat"): / lists every ready node across every plan, coloured by its server-assigned plan colour, sorted by leverage, with the not-done junctions beneath; the DAG page draws borrowed nodes as ghost cards. The rest of this note is still analysis, to be revisited after a week of real use.

Premise (Jeremy, 2026-08-21): the primary interactions are through MCP; the Stoat UI is a live visualization of the plan, updated over SignalR. Agents (and people driving agents) create plans, add and bind nodes, claim and report; the dashboard shows what is happening, as it happens. No human editing surface is planned until real use asks for one.

What that means for the UI ​

It is a viewer, and most of it already exists:

  • PlanView draws the DAG from the derived status (PlanStatusView carries each node's dependsOn, so a structural change — a node added by add_plan_node, an edge from link_plan_nodes — re-lays out on the next push with no extra wire); plan_status_updated arrives within ~a second of any coordination event; the 60s poll is only a backstop.
  • Claims (claimedBy, the report_node note) already render on the cards.

What a live, MCP-driven board still wants, roughly in order of value:

  1. A live activity feed — "agent-a added node wolverine-4012", "agent-b bound cw-1064 to JasperFx/CritterWatch#1064", "agent-a claimed cw-999: reproduced; fix is in the parser". The plan's history already exists (GET /api/plans/{slug}/history); making it LIVE is one more StoatEvent (plan_changed, the PlanHistoryEntry shape) enqueued by the same CoordinationChangeHandler that pulses the signal — and the claim events the same way. Per plan on PlanView; across plans on the home page.
  2. Structural changes made visible — a node that just appeared or just got bound should read as new for a moment (a highlight that decays), or the board is a still that happens to be current.
  3. "What is ready, across everything" as the home page — built: ReadyBoardView, derived client-side from the pushed statuses (lib/readyBoard.ts), leverage-sorted, with a "blocked on" section.
  4. Agent presence — which agents hold claims right now, with lease expiry; the session-file poller (#10) will make this much richer, but claims alone already answer "who is working".
  5. History as a page, not just a feed — filter by node, by agent.

What it explicitly does not need (for now) ​

  • Forms for create / add / change / link / bind / archive — those are MCP tools. If a human needs to do one by hand, curl against the REST endpoints is the stopgap, and the decision to add a drawer/dialog waits for evidence that people want it.
  • Any frontend validation vocabulary — the handler's refusals are the parser's messages.

Open questions real use should answer (next week) ​

  • Is the per-plan DAG the page people watch, or the cross-plan "ready + who" page?
  • How much activity is too much — does a GitHub sweep's burst of observations belong in the feed, or only plan/claim changes (the asserted side)? The feed's filter decides the StoatEvent set.
  • Do people want to SEE unrealized nodes being materialized (bind) as a first-class moment?
  • Is any write affordance missed badly enough to build one — the likeliest candidate is "bind this node to the issue I just opened", and even that may stay an agent's job.

If it starts after next week: the minimal first cut ​

  1. plan_changed + claim events on the SignalR batch; an activity feed component on PlanView and on the home page.
  2. "New" highlight on node cards whose dependsOn/binding just changed.
  3. A cross-plan ready board as the landing page.
  4. No write UI.

Everything above is additive; the backend change is one handler enqueuing one more event.


Dogfooding notes (2026-09-04) ​

Real use started answering the open questions above. Jotted as they came; the override question got the analysis it asked for, the rest are captured with just enough shape to build from.

1. Manual override of a node's status — needs analysis (here it is) ​

The ask: every action in the plan visualization needs a way to manually mark it "done" / "ejected" / "aborted" / "cancelled".

The four words are two concepts. "Done" is force-complete: treat the node as satisfied even though no observation says so (the issue is closed as won't-fix-but-actually-handled, the package was pushed by hand, the gate was verified by eyeball). "Ejected/aborted/cancelled" are all not-doing-this: remove the node from the critical path. For readiness derivation both unblock dependents identically; they differ only in how the card should read. Suggested vocabulary: done and cancelled (with the free-text reason carrying "aborted" vs "ejected" nuance) — two verdicts, not four states.

It fits the model without bending it. This is an asserted event, exactly like a claim: NodeOverridden { Verdict, Reason, By, At } (+ OverrideCleared for undo — assertions append, never erase). Provenance stays distinct on the card: the dashboard can still answer "the plan believes this because a human said so", which is the observe-never-own stance, not a violation of it. Stream-wise it belongs on the claim stream's sibling — per plan node — or on the plan stream; per-node is cleaner because PlanStatus.For already folds per-node inputs.

The fold rule is the real decision. Options:

  • (a) Override wins, terminally. Simple, but a GitHub reopen after a human said "done" is silently ignored — the exact "crashed agent renders in-progress forever" failure, mirrored.
  • (b) Override wins until evidence contradicts, then the conflict is surfaced: status renders done (asserted) normally, and grows a badge when the observed state disagrees (issue reopened, package unlisted). Recommended. Nothing is dropped, the card says which source it believed, and a human resolves it by clearing or re-asserting.

Cancellation has a propagation question with no obvious answer. Does cancelling a node satisfy its dependents (skip it and move on) or moot them (the feature isn't happening, so its test gate isn't either)? Default to satisfies — it matches "unblock dependents" — but render cancelled-upstream edges distinctly so a moot subtree is visible, and revisit if real use wants cascade-cancel.

Mechanics: a command through the same pipe as everything else (OverrideNode → PlanHandler or a new OverrideHandler), MCP tool + REST endpoint, license-gated like every mutating surface. UI-wise this is the first write affordance in the viewer — the "no write UI until real use asks" line above has now been crossed by real use asking. A small context menu on the card (done / cancel / clear, reason required) is enough.

2. Contextual icons — built today ​

Kind glyphs (GitHub mark for issues, PR glyph, package for publish, download for consume, beaker for gates) on both the ready-board rows and the DAG cards (NodeKindIcon.vue); where the binding yields a URL the glyph+kind is an out-link (lib/nodeLinks.ts, hoisted from PlanView — GitHub for owner/repo#n, nuget.org for publishes). Ready rows previously had no external link at all.

3. Ready-board filtering — by kind, repository, plan ​

Kind and plan are client-side filters over data the board already holds; the legend chips are halfway to plan toggles already. Repository is the interesting one: it is not in the view model. Today it exists only inside ref (owner/repo#n) — publish/consume/gate nodes have no repository at all unless the model grows one (see §4). Build kind+plan now; repository filtering falls out of §4.

4. A formal Repository concept — model built same day ​

Wanted by §3 (filtering), §7 (swim lanes), and §8 (CI tracking — you can't watch workflows for a repo you can't name). Today "repository" appears as: the repo half of an issue/PR ref, the node-level repository field plans may carry, and the git tree roots the agent roster groups by. A first-class Repository could carry: GitHub slug, the NuGet packages it publishes, its CI workflows (names/expected checks), default branch, local clone/worktree roots seen on this box. Jeremy's instinct: it may live in the memory model — a Repository is exactly the kind of slowly-accreting, cross-plan knowledge memories are for, rather than plan-document config repeated per plan.

Built the same day (see agent-coordination-design.md, "Repositories as first-class memory"): event-sourced RepositoryRecord per repo, license-gated MCP tools, read-only REST, markdown notes as the first formal memory shape. UI (§5's memory view, §3's repo filter, §7's swim lanes) still to come.

5. Memory visualization + human-added memories ​

The "agent session memory" item in the design doc's not-built list, pulled forward: a UI that shows what Stoat remembers, plus the ability for a human to add a memory as embedded markdown in the page. Note this makes the memory surface the second write affordance (after §1), and an editor is a bigger UI commitment than a context menu — but a memory is append-only prose, which is the easiest possible write to model (asserted event, markdown body, no fold conflicts).

6. Archive / evict plans, and seeing archived ones ​

Half exists: archive is already a PlanHandler command — removes the plan from the active set, keeps the stream. Missing: (a) any UI affordance to invoke it; (b) an archived-plans listing — GET /api/plans only knows active plans, so a ?includeArchived or /api/plans/archived reading the kept streams, and a read-only plan page for an archived plan (history exists; the status derivation may want a "frozen at archive time" stance rather than deriving against live observations). "Evict" as distinct from archive — actually deleting a junk plan created during testing — is against the event-store grain; if wanted, it's a tombstone event that hides the stream everywhere, not a delete.

7. Swim-lane view: outstanding work by repository ​

A new view: horizontal lanes per repository, cards = outstanding (not done/cancelled) nodes across every plan, coloured by home plan as everywhere else. This is the cross-plan board re-pivoted: same pushed statuses, a different client-side grouping — buildable today for issue/PR nodes (repo derivable from ref), and honest lanes for everything else need §4. An "(unassigned)" lane for repo-less nodes keeps it shippable before §4 lands.

8. Execution + PR/CI lifecycle tracking — the big one ​

Wanted, in one line each:

  • an at-a-glance "currently executing" indication per step;
  • success/failure icons for NuGet pushes and GitHub CI, linking to the action run (or the PR when a PR's CI fails);
  • the full PR lifecycle tracked: opened → CI running → CI passed/failed (per-workflow, since our repos run several) → merged, with easy colour coding.

What exists: the status grammar already has the colours (running/passed/failed/retrying tokens), pr-open and running statuses, openPrs on issue nodes, and Bobcat-run drill-in links. What doesn't: CI is not observed at all. GitHubPoller watches issues and PRs, not check runs/workflow runs. The shape of the work:

  • Observe: extend the GitHub sweep with the Checks API per open PR head SHA (and workflow runs on the default branch for publish verification) → new observed events, change-detected like everything else. Multiple workflows per repo means the observation is a set of named checks, not one verdict — the per-repo expected-check list is §4 material.
  • Derive: PR nodes get a CI facet (checks: passing m/n, failing, running), issue nodes surface it via their open PRs; "executing" for an issue node is derivable today from claim
    • session (claimHealth: held = an agent is actually on it) — that part needs no new observation, just rendering.
  • Render: icon + colour per state reusing the existing tokens; the failure icon links to the failing run's URL (the Checks API carries it), the PR icon to the PR.
  • NuGet push success/failure: the feed only shows presence; failure of a push isn't observable from nuget.org — a failed push is a CI-run failure (§ the publish workflow), which the Checks work covers.

Sequencing that falls out: §4 (Repository) unlocks §3, §7, and the per-repo check config in §8; §1 (override) and §2 (icons, done) are independent; §8's "who is executing" render-only slice is a quick win before any Checks API work.

Source-available under the Business Source License 1.1.