This is an opinions document about structure, not looks: the theme,
colours and typography stay as they are. "Structure" here means page
hierarchy (what leads a page, what folds away), information order,
navigation between pages, where forms live, and density. Every claim
below was read out of the templates in src/pages/ on the goal branch
(after the SSE and HTML-escaping fixes landed), so the wireframes
describe the app as it ships today.
statum-mkpstm:
stm-text / stm-if / stm-for / stm-attribute / stm-markdown
bind server state, stm-action submits sealed actions, and the page
refreshes in place. The shared chrome lives in main.html: a header
nav (Dashboard, Projects, Tasks, Goals, Saddles, Rules, Users, then
the user cluster), one <main>, a footer.body {
max-width: 64rem } in app.css). There are exactly two layout
primitives beyond that: .cards (an auto-fill card grid) and
.grid-2 (the task page's 50/50 status/mandate grid, stacking below
40rem). No page uses a persistent two-column layout, a sticky
sub-nav, or a sidebar.task_log.html is the only page whose body is built by client
JavaScript (createElement/textContent, filters, search, paging,
SSE). Everything else is server-rendered bindings.details/summary is the app's only progressive-disclosure device,
and it is doing a lot of work it was not meant for (see the
"expander fatigue" section).Two capabilities notes used throughout:
stm-attribute can set any attribute (stm-attribute.href,
.class, .value, .data-* already appear), so a section can be
collapsed by binding hidden or a data-collapsed class to state,
without client JS.task_log.html, tasks.html
(window.taskRowVisible + window.setTaskFilter) and
goal_detail.html (window.goalSetFilter) already run page scripts
that cooperate with statum's render cycle. A "sticky sub-nav that
highlights the section in view" is a scroll listener plus one
class toggle — the pattern already exists on this codebase.<details> expander: New task, New goal, New project, New rule,
New user, New capability, Register saddle, Edit task, Link a
project, SSH key, Git identity, Edit rule, answered blocks, plan
revisions, progress history. Disclosure is the right instinct for
history; it is the wrong instinct for primary actions, where it
inverts affordance (the most important button on the page is the
least visible control).h2; task
page: a context line under the h2 carrying project + enqueue
chain). Cross-links exist (task→project, task→goal, goal→project)
but the reverse links (project→its goals, task→its sessions' logs)
are partial, so navigation is a one-way tree.home.html)What it's for. The "is anything on fire / does anything need me" page: push bell, queue and saddle metrics, pinned needs-a-human blocks, active tasks with latest progress, saddle cards, recent activity.
Structural problem. The page opens with a full-width card whose entire content is one button pair ("Enable notifications — be told when an agent asks a question…"). Then two metric cards (Queue, Saddles). Only then comes the section the page exists for: "Needs a human". On a fresh install the attention content is below the fold; on a busy day the pinned cards — the actionable items — are squeezed between metrics above and two more full sections below (Saddles, Activity), so comparing a block against the task it came from means scrolling past a saddle grid.
Recommendations.
.grid-2-style row: left 2/3 pinned
blocks + active tasks, right 1/3 Queue/Saddles metrics, the bell
strip, and "Saddles" as a compact status list rather than cards.C. Fold Activity under a collapsed expander with a "new since your last visit" count (S, low risk), or cap it at ~8 rows with "view all" — it is the page's only pure-read section.
BEFORE (linear) AFTER (attention-first + rail)
+----------------------------+ +----------------------------+
| [Enable notifications ...] | | NEEDS A HUMAN (3) |
+----------------------------+ | [block] [block] [block] |
| [Queue] [Saddles] | | ACTIVE TASKS |
+----------------------------+ | ...table... |
| NEEDS A HUMAN (3) | +------------+---------------+
| [block] [block] [block] | | Queue | bell (compact)|
+----------------------------+ | Saddles | saddle list |
| ACTIVE TASKS | +------------+---------------+
| ...table... | | ACTIVITY (collapsed, 3 new)|
+----------------------------+ +----------------------------+
| SADDLES (cards) |
+----------------------------+
| ACTIVITY (full table) |
+----------------------------+
statum notes. A and C need nothing new. B needs one CSS grid row
plus the existing card markup; stm-if already gates the pinned
section on dashboard.pinned.length.
tasks.html)What it's for. The queue: find a task, judge the queue's shape, create a task.
Structural problems.
#, Title, Status, Mode, Goal, Priority,
Project, Capabilities), three of which (Goal, Project,
Capabilities) are reference attributes. On the 64rem column this is
dense, and Priority is usually "5" noise.<select>s with
an inline hint ("— filters apply instantly") — functional but
undiscoverable as filters: they read as form fields. They also
carry no counts, so you cannot see the queue's shape without
filtering.TaskService.list_tasks
returns every task newest-first) and terminal tasks accumulate
forever. Done tasks from months ago share the page with the thing
you came for.Recommendations.
draft/queued/assigned/working/blocked default with an explicit
"show done/failed (N)" toggle. The filters exist already; this is a
default-value change plus a chip.goal-strip of status buttons with counts). Port it here and the
two pages stop feeling like different apps.# title — status — project.D. Give "New task" a real button (S, low risk) — see the
expander-fatigue section: an always-visible + New task button that
opens the same form inline.
BEFORE AFTER
+------------------------------+ +------------------------------+
| [New task >] (collapsed) | | Tasks [+ New task] |
+------------------------------+ +------------------------------+
| Status[v] Project[v] Goal[v] | | (draft 2)(queued 5)(working 3)|
| — filters apply instantly | | (blocked 1)(done 214) |
+------------------------------+ | Project/Goal [all v] search |
| # | Title | St | Mode | Goal | +------------------------------+
| Pri | Proj | Caps | | # Title Status |
| ...8 columns... | | 42 Fix the SSE feed working |
+------------------------------+ | 41 Escape entities blocked |
| ...project folded into title |
+------------------------------+
statum notes. B is the client-side taskRowVisible filter with
counts derived from tasks.tasks (already shipped to the client) —
no server change. A could also be server-side (default status filter
in the entrypoint) if the "all" count needs to stay cheap.
task_detail.html)What it's for. The human's control surface for one task: watch progress, answer blocks, read the completion report, comment, tweak rules/capabilities, inspect sessions.
Structural problem. Thirteen h3 sections in one 64rem column,
half of them full-width cards, in this order: Progress → Status
card / Goal card / Mandate card (the one .grid-2 on the page) →
Completion report → Edit task expander → Blocks → Comments →
Rules → Required capabilities → Sessions. The page's actual verb —
answer the block, approve the review — is the sixth thing you
scroll to, and it sits between an edit expander and the comments
thread. Meanwhile the Status card (five muted metadata lines plus
buttons) takes half a screen width while the Mandate — the single
most useful read — gets the other half at the same visual weight as
"Created 3 days ago".
Recommendations.
Progress · Blocks · Report · Comments · Rules · Sessions) that
scroll-jumps and highlights — a dozen lines of client JS over
data-section ids. This is the cheap version of "restructure the
page": it keeps the DOM order but gives the reader a map and a
one-click path.h4s or one collapsed expander
with a summary line ("2 rules · 1 capability · 3 sessions").D. The Sessions table's "Event log" link is the page's only per-session affordance and it is a link in a table row — surface it once at the top level ("View event log" beside Progress) since it is per-task, not per-session, anyway.
BEFORE (13 sections, one column) AFTER (Act / Understand / Reference)
+------------------------------+ +------------------------------+
| Progress | | Progress [Event log] |
+------------------------------+ +------------------------------+
| Status | Goal | Mandate | | BLOCKS (1 open) |
+------------------------------+ | [question card + answer] |
| Completion report | +------------------------------+
+------------------------------+ | COMPLETION REPORT |
| [Edit task >] | +------------------------------+
+------------------------------+ | Status | Mandate | Goal |
| BLOCKS (1 open) <- the verb| +------------------------------+
+------------------------------+ | Comments |
| Comments | +------------------------------+
+------------------------------+ | Configuration & history (2) |
| Rules | | rules - caps - sessions |
+------------------------------+ +------------------------------+
| Required capabilities |
+------------------------------+ (sub-nav strip pinned under
| Sessions | the title: Progress / Blocks /
+------------------------------+ Report / Comments / Config)
statum notes. B and C need no new patterns (stm-if, anchors,
existing client-JS style). A is a re-ordering of existing cards; the
risk is only that regulars re-learn where things are, so ship it with
the sub-nav.
task_log.html)What it's for. The live transcript of one task: turn cards, commands, results, comments, transitions, usage — the forensic view when something went wrong.
Structural problems.
Recommendations.
position: sticky; top: 0
on .log-toolbar inside the scroller, so filters/search survive
deep scrolling. Add a "paused / live" affordance to the
bottom-stick behaviour (the feed already knows whether it is
pinned; expose it as a "jump to latest" button when not).task_log), a back-chevron to
the task, and the window note ("showing most recent 30") folded
into the load-older control ("Load older events — 412 more").C. Consider the log a tab of the task page rather than a
separate URL (L, medium risk — a decision more than a build):
either mount the feed component under a ?view=log tab on
/tasks/{id} or keep the URL but add a persistent task-context
strip so both pages share their sub-nav. This is the single
highest-leverage navigation fix in the app: today the natural
investigation loop (task → log → task) costs a tab and a reload.
BEFORE AFTER
+----------------------------+ +----------------------------+
| <- Tasks | | <- Task #42 "Fix SSE" [wg] |
| Event log | +----------------------------+
| Task page - unified ledger | | [cmds][res][cmts][tr][use] |
+----------------------------+ | [search____________] live |
| [Show: cmds res cmts ...] | +----------------------------+
| [search] | | ^ Load older (412 more) |
+----------------------------+ +----------------------------+
| [Load older events] | | 14:02 turn 51 |
+----------------------------+ | ...feed (sticky filters) |
| 14:02 turn 51 | | |
| ...feed scrolls... | | [jump to end] |
+----------------------------+ +----------------------------+
statum notes. A and B are pure client CSS/markup inside the page
that already owns its rendering. C needs either the feed lifted into
a fragment included by the task page (partials already exist — see
progress_feed.html, goal-branches) or a shared context strip;
both are proven patterns.
goal_detail.html)What it's for. The supervision surface for one goal: status and lifecycle actions, agent progress, plan review, block answering, the task fan-out table, the brief, rules, narrative, comments.
Structural problem. Eleven sequential cards — Status, Agent progress, Goal agent blocks, Completion review, Plan, Tasks, Initial brief, Involved projects, Rules, Supervision narrative, Comments, (Goal agent runs) — in one column, all at the same weight. The good news is the order is already attention-first (blocks and the pending plan review sit above tasks and brief — the comments in the template say so). The problem is depth and differentiation: to check "has the plan been approved yet?" you scroll past status, progress, blocks, and completion review, then find the Plan card whose content is a collapsed expander ("Approved plan & revisions (N)"). The Tasks card embeds a 6-column table with its own client-side status-filter strip — a second, different filtering UI on the same site (see tasks list). Reference cards (Rules, Involved projects) sit between the action surface and Comments, so a conversation with the agent is a long scroll from the question you are answering.
Recommendations.
Status · Blocks · Plan ·
Tasks · Brief · Narrative · Comments) — pairs well with B, and
makes B optional if you want the smaller change first.D. Unify the tasks-filtering pattern with the tasks list (the
goal page's goal-strip is the better UI — port it up to
/tasks, not the other way).
BEFORE (11 cards, 1 column) AFTER (act | consult)
+----------------------------+ +--------------+---------------+
| Status | | ACT | CONSULT |
+----------------------------+ | Status (cmpct)| Tasks |
| Agent progress | | Progress | (filter |
+----------------------------+ | BLOCKS (1) | strip) |
| Goal agent blocks | | Plan review | Initial brief|
+----------------------------+ | Completion | Projects |
| Completion review | | Comments | Rules |
+----------------------------+ | | Narrative |
| Plan (usually collapsed) | | | Runs |
+----------------------------+ +--------------+---------------+
| Tasks (own filter strip) |
+----------------------------+ sub-nav: Status / Blocks / Plan /
| Initial brief | Tasks / Brief / Narrative / Comments
+----------------------------+
| Involved projects |
+----------------------------+
| Rules / Narrative / |
| Comments / Runs |
+----------------------------+
statum notes. A is stm-if + expression text over data the
entrypoint already publishes (open_blocks, plan_pending_block).
B is one CSS grid + card re-parenting; the risk is mobile (stack
Act-then-Consult, which the existing 40rem breakpoint pattern
already handles). C is the same client JS as task detail.
saddles.html)What it's for. Fleet health and configuration: per-saddle status, current work, SSH keys, git identity, capabilities; the capability tag catalogue at the bottom.
Structural problems.
daemon state, up X, contacted Y ago) instead of one verdict.Recommendations.
C. Keep the capabilities table but visually separate it (S) —
its own h2-level section ("Routing tags") rather than an h3
after the grid, so the page reads as two deliberate sections.
BEFORE (one 9-block card) AFTER (now head / admin tail)
+--------------------------+ +--------------------------+
| saddle-1 | | saddle-1 [busy] |
| [busy] localhost:7100 | | healthy - up 3d - 12s ago|
| Supervising goal: X | | Supervising goal X (2h) |
| since 2h | +--------------------------+
| [healthy] daemon idle ...| | [Keys & admin v] |
| API token: ... | | key / identity / caps /|
| [SSH public key v] | | token / refresh/remove |
| [Git identity v] | +--------------------------+
| Capabilities: [gpu] [x] |
| [Add capability v] |
| [Refresh] [Remove] |
+--------------------------+
statum notes. All three are markup re-orders inside existing
patterns (stm-if, details, badges); B is an expression over
saddles.saddles the page already iterates.
projects.html) and project detaildata-long-form in-place editor pattern from task/goal briefs
already exists in main.html and is a better fit here), freeing
the second column for "Tasks in this project"./tasks; with a ?project= pre-fill that becomes a flow instead
of a detour. It also lists tasks but not the project's goals —
the reverse cross-link gap.goals.html)goal-strip chip pattern again) or just
"19 tasks, 3 running" would carry more signal per pixel.rules.html)users.html) and profileCounted across the app, <details> currently serves three distinct
jobs with one widget:
+ New task)
that opens the form inline (a data-* toggle + hidden, exactly
the data-long-form / data-lf-toggle pattern in main.html),
or render the form directly on low-traffic pages where there is
room (rules, projects).Rule of thumb proposed: history folds, actions surface.
Dashboard / Tasks / #42 (or
Goals / #7 🎯), replacing the hand-drawn "← Goals" / "All tasks"
anchors that each page currently invents. One shared partial
(pstm-fragment already powers progress-feed, goal-branches)
makes it consistent for free.goal-strip code).+ New … buttons replacing create-form expanders (S, per page).Overview | Log), turning two URLs and a new
tab into one investigation flow.Already possible with today's statum.js patterns (stm-for,
stm-if, stm-attribute, stm-markdown, details, pstm-fragment
partials, per-page client JS as in task_log / tasks /
goal_detail):
stm-if gates each card's visibility already.data-long-form
machinery in main.html is generic — reuse it for project edit
and rule edit.Needs a new (small) pattern or a server change:
task_log carries this much JS
today).tasks.html says the statum entrypoint fetch does not carry it)
or the client owns filter state in location.hash.