ux-notes.md 31 KB

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 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.

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.