Jelajahi Sumber

docs: structural UX critique of the web UI (ux-notes.md)

An opinions document on page structure - hierarchy, information order, navigation, form placement, density - with the theme explicitly out of scope. Every claim verified against the templates on the goal branch: the single 64rem column with only .cards and .grid-2 as layout primitives, the 13-section task detail and 11-card goal detail scrolls, the create/edit surfaces hidden behind details expanders, the 8-column tasks table with unranked select filters and no server-side limit, the dashboard opening on the push bell and metrics before the needs-a-human blocks, the client-rendered event log with a non-sticky toolbar, and the absence of breadcrumbs with hand-drawn back links per page.

Per page: what it is for, the structural problem, 2-4 recommendations with before/after ASCII wireframes, effort (S/M/L) and risk. Cross-cutting sections on expander fatigue (history folds, actions surface) and navigation (breadcrumb partial via the existing pstm-fragment mechanism, reverse links, task-to-log flow). Ends with a quick wins list (a day or less), structural bets, and an appendix marking which recommendations run on today's statum.js patterns (stm-for/if/attribute, details, client JS as in task_log/tasks/goal_detail) versus which need a new small pattern (scroll-highlight sub-nav, URL-state filters given the entrypoint fetch carries no query string).
saddle-Clanker-Saddle 1 bulan lalu
induk
melakukan
3c535a9382
1 mengubah file dengan 631 tambahan dan 0 penghapusan
  1. 631 0
      docs/ux-notes.md

+ 631 - 0
docs/ux-notes.md

@@ -0,0 +1,631 @@
+# ClankerSaddle web UI — structural layout critique
+
+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.
+
+## How the app is built (why the recommendations look the way they do)
+
+- Every page is a Statum template compiled by `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.
+- The whole app renders inside a **single 64rem column** (`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.
+- Client JS is not exotic: `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.
+
+## The five structural themes
+
+1. **Attention is buried under equal-weight sections.** The detail
+   pages are one long scroll of cards that all look equally
+   important. The one thing a human must do (answer a block, review a
+   plan) sits in card 2 or 3 of 11, between the status card and the
+   plan history. Nothing in the page's *shape* says "act here".
+2. **Reference material competes with action.** Rules, capabilities,
+   sessions, briefs, plan revision history — genuinely useful, read
+   maybe once — render at the same weight and in the same column as
+   the action surface, so a page visit for "approve the plan" scrolls
+   past everything else.
+3. **Expander fatigue.** Every create and edit surface hides behind a
+   `<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).
+4. **No "where am I".** No breadcrumbs anywhere. "Back" is a manual
+   anchor each page draws by hand ("← Goals", "All tasks", "All
+   projects"), inconsistently placed (goal page: above the `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.
+5. **Density mismatch.** A five-word status line and a 400-word
+   completion report both get a full-width card in the same column;
+   the tasks table carries eight columns of which three are reference
+   (goal, project, capabilities) that most visits do not need.
+
+---
+
+## Page-by-page
+
+### 1. Home / dashboard (`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.**
+
+- **A. Move "Needs a human" to the top** (S, low risk). Keep the bell
+  but demote it: it is a settings action, not news. It can become a
+  compact strip beside the metric cards, or move to the header's user
+  cluster entirely (arguably where a notification preference belongs).
+- **B. Put the metrics in a right-hand rail next to the attention
+  column** (M, low risk). One `.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`.
+
+### 2. Tasks list (`tasks.html`)
+
+**What it's for.** The queue: find a task, judge the queue's shape,
+create a task.
+
+**Structural problems.**
+
+- The **create form** is the page's only primary action and it is a
+  collapsed expander above the table. New users' first act is to hunt
+  for the form.
+- **Eight columns** (`#`, 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.
+- The three filters (Status, Project, Goal) are bare `<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.
+- The list has **no server-side limit** (`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.
+- It duplicates the create form that project detail doesn't have
+  (project detail links back here instead — the reverse direction is
+  a dead end).
+
+**Recommendations.**
+
+- **A. Default the list to "open work"** (S, low risk): a
+  `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.
+- **B. Replace the three selects with faceted status chips + one
+  "Project / Goal" select** (M, low risk, client JS exists): the
+  goal-detail page already has exactly this pattern (a
+  `goal-strip` of status buttons with counts). Port it here and the
+  two pages stop feeling like different apps.
+- **C. Drop Mode / Priority / Capabilities to a per-row hover/expand
+  or merge into one "meta" cell** (S, low risk); keep the row scannable
+  as `# 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.
+
+### 3. Task detail (`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.**
+
+- **A. Two zones: "Act" then "Understand"** (M, medium risk). Zone 1
+  (top, full width): Progress line, **Blocks** (open ones first,
+  full weight), Completion report. Zone 2: the status/mandate grid,
+  comments, then a reference cluster.
+- **B. An anchored sub-nav strip under the page title**
+  (`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.
+- **C. Cluster the low-traffic reference sections** (Rules, Required
+  capabilities, Sessions) into one "Configuration & history" region —
+  either a single card with internal `h4`s 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.
+
+### 4. Task event log (`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.**
+
+- It is structurally unlike every other page (client-rendered feed in
+  a 72vh scroller), which is *fine for a transcript* — but its
+  toolbar (5 filter buttons + search) sits inside the card above the
+  feed, so it scrolls away. For the page where you spend the most
+  time scrolled deep, the controls should be pinned.
+- "Load older events" is a button *above* the feed — correct, since
+  older history grows upward, but nothing indicates how much history
+  remains or that the feed is a window onto a longer ledger.
+- There is no way back to the task's *blocks/comments* surface except
+  the "Task page" text link at the top — the two pages are one
+  investigation flow split across a page boundary, and the log opens
+  in a **new tab** from the task page, which severs even the back
+  button.
+- The feed and the task page's Progress/Sessions duplicate a job:
+  "what has the agent been doing".
+
+**Recommendations.**
+
+- **A. Sticky toolbar** (S, low risk): `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).
+- **B. Context header, not a link farm** (S): the page opens with
+  "← Tasks" then "Event log" then "Task page · the unified event
+  ledger…". Replace with a single header row: task id + title +
+  status badge (the data is already in `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.
+
+### 5. Goal detail (`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.**
+
+- **A. Make the page's state legible in one line** (S, low risk):
+  a compact "goal strip" at the top — status badge, the one open
+  item ("1 question waiting · plan revision 2 pending"), and the
+  primary lifecycle button (Start / Pause / Retry). The Status card
+  keeps the detail (branches, compare, saddle, timestamps) but the
+  reader gets the verdict without parsing it.
+- **B. Two-column attention-first layout on wide screens** (M,
+  medium risk): left column = Status, Agent progress, Blocks, Plan
+  review, Completion review, Comments (everything a human acts on);
+  right column = Tasks, Initial brief, Involved projects, Rules,
+  Supervision narrative, Runs (everything a human consults). The
+  single 64rem column is the core reason this page reads as "12
+  cards in a trenchcoat"; splitting the reading column from the
+  acting column fixes it without touching the theme.
+- **C. Sub-nav strip** as per task detail (`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.
+
+### 6. Saddles (`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.**
+
+- The card grid is right for this page, but each card is a long
+  stack of paragraphs (status, work, health line, token, SSH key
+  expander, git identity expander, capability chips, add-capability
+  form, Refresh/Remove buttons). "What is this saddle doing?" — the
+  question you ask daily — is the second of nine blocks, and the
+  admin machinery (keys, identity) crowds it.
+- The **Capabilities section at the bottom is a different modality**
+  (table) bolted under a card grid; it reads like a second page
+  appended to the first.
+- Health is expressed as three separate text fragments (health badge,
+  `daemon state`, `up X`, `contacted Y ago`) instead of one verdict.
+
+**Recommendations.**
+
+- **A. Split each card into a "now" head and an "admin" tail** (S,
+  low risk): head = name, status badge, health verdict (one line),
+  current work; tail (already-expanders, keep them) = key, identity,
+  capabilities, token, actions. Visual anchor: the head is the card's
+  first ~3 lines; everything else folds.
+- **B. Surface "unhealthy" at the top of the page** (S): if any
+  saddle is offline/stale/fenced, one summary line above the grid
+  ("1 of 3 saddles stale — dispatcher may be starved"), linking to
+  the card. The dashboard's idle-count metric does not currently
+  tell you this.
+- **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.
+
+---
+
+## Shorter notes
+
+### Projects list (`projects.html`) and project detail
+
+- List: table is fine; "New project" is the expander problem again
+  (give it a button). The Git URL column is long noise — truncate
+  with the branch column into one "repo" cell.
+- Detail: "Details" and "Edit" as two side-by-side cards is odd —
+  the Edit card is a form for exactly what Details displays. Make
+  Details the read view with an in-place **Edit** toggle (the
+  `data-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".
+- Project detail has no create-task surface and links out to
+  `/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 list (`goals.html`)
+
+- Same expander-pattern note for "New goal". The brief is a required
+  6-row textarea — the goal's most important field — hiding behind a
+  collapsed summary; fine once expanded, invisible until then.
+- The Tasks column packs four counts into one muted string
+  ("2 draft / 5 queued / 3 running / 14 done") — unreadable; four
+  tiny badges (the `goal-strip` chip pattern again) or just
+  "19 tasks, 3 running" would carry more signal per pixel.
+- No status filter on the list at all, unlike tasks — the same chip
+  strip would fix both consistency and scanning.
+
+### Rules (`rules.html`)
+
+- Each rule is a full-width card with body, then an Edit expander and
+  a bare Delete button *outside* the expander — a destructive
+  action more prominent than the edit one. Invert: Edit as the
+  in-place editor pattern, Delete inside a "danger" expander or
+  behind the existing confirm alone but visually secondary.
+- No list of where a rule is attached (projects/tasks using it) —
+  the page says rules attach on project/task pages but you cannot
+  see the blast radius of a delete from here. A reference list, not
+  a theme change.
+
+### Users (`users.html`) and profile
+
+- Users: admin-only, table plus expander create form — acceptable,
+  lowest traffic in the app. Same "New user" button note applies.
+- Profile: fine structurally; the change-password form could sit in
+  a card for consistency, and this is the natural home for the
+  notification preference currently occupying the dashboard's first
+  card.
+
+### Login
+
+- No structural complaints: inline error handling, focused field,
+  narrow form. Good.
+
+---
+
+## Expander fatigue — the cross-cutting fix
+
+Counted across the app, `<details>` currently serves three distinct
+jobs with one widget:
+
+1. **History folding** (progress history, answered blocks, plan
+   revisions, submitted markdown) — this is the right tool; keep it.
+2. **Primary create forms** (new task, goal, project, rule, user,
+   capability, register saddle) — wrong tool: it hides the page's
+   main action. Replace with a persistent button (`+ 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).
+3. **Admin/config editing** (SSH key, git identity, edit task, edit
+   rule) — defensible, but prefer the in-place editor (read view +
+   Edit toggle) where the form is short, because it shows the
+   current values in place instead of a cold empty form.
+
+Rule of thumb proposed: **history folds, actions surface.**
+
+## Navigation — the cross-cutting fix
+
+- **Breadcrumbs on detail pages** (S): `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.
+- **Reverse links**: project detail lists its tasks but not its
+  goals; task detail's sessions link out to the log but the log links
+  back only to the task. The dashboard's cross-links are the model to
+  copy: pinned blocks deep-link to the exact task or goal, and saddle
+  cards link to the saddle's current work.
+- **The log-in-a-new-tab decision** (see event log, C) is the one
+  navigation break that costs the most; everything else is polish.
+
+## Quick wins (a day or less each)
+
+1. Dashboard re-order: Needs-a-human first, bell compacted (S).
+2. Faceted status chips with counts on /tasks, defaulting to open
+   work (M but a port of existing `goal-strip` code).
+3. Breadcrumb partial across detail pages (S).
+4. `+ New …` buttons replacing create-form expanders (S, per page).
+5. Sticky toolbar + jump-to-latest on the event log (S).
+6. Task detail: cluster Rules + Capabilities + Sessions into one
+   collapsed "Configuration & history" region (S).
+7. Goal page: one-line state strip above the Status card (S).
+8. Saddles: card head/tail split + fleet health summary line (S).
+9. Goals list: readable task-count badges + status chips (S).
+
+## Structural bets (multi-day)
+
+1. **Act / Consult two-column layout** for goal detail (and
+   optionally task detail) — the biggest single improvement to the
+   app's most-used deep pages; needs a responsive fallback.
+2. **Anchor sub-nav** on the two detail pages (Progress · Blocks ·
+   Plan · …) with in-view highlighting — a new (small) client-side
+   pattern, ~a day including polish.
+3. **Task page ↔ event log unification**: shared context strip or a
+   tabbed task page (`Overview | Log`), turning two URLs and a new
+   tab into one investigation flow.
+4. **Saved views / URL state for the tasks list** (filters in the
+   query string, shareable "my blocked tasks" views) — needs the
+   entrypoint to read the query string or a small client state
+   sync; the current client-side filtering deliberately exists
+   because statum's fetch doesn't carry the page query string, so
+   this is the one item that brushes against that limitation.
+5. **Cross-entity "work" stream**: one feed joining task progress,
+   goal narrative and event-log highlights, replacing three
+   different feeds with different row grammars (activity table,
+   narrative card, event log). Large; only worth it if the dashboard
+   stays the daily driver.
+
+## Capability appendix — what needs what
+
+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`):
+
+- Every quick win above (1–9): chips, breadcrumbs, buttons,
+  sticky toolbar, clustering, strips, head/tail cards.
+- The Act/Consult two-column layouts: pure CSS grid + re-parenting
+  existing cards; `stm-if` gates each card's visibility already.
+- In-place editors replacing expanders: the `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:
+
+- Sub-nav with in-view highlight: a scroll listener + class toggle;
+  no statum change, but it is a new client-side idiom for
+  server-rendered pages (only `task_log` carries this much JS
+  today).
+- URL-state filters / saved views on /tasks: either the entrypoint
+  learns the query string (server change; the template comment in
+  `tasks.html` says the statum entrypoint fetch does not carry it)
+  or the client owns filter state in `location.hash`.
+- Task↔log unification: fragment-include of a client-rendered
+  component inside a server-rendered page, or a shared context
+  strip; both are compositions of existing pieces but need real
+  design care around the SSE lifecycle on tab switches.