Skip to content

Stop the person-facing screens saying things that are not true - #463

Merged
guidovizoso merged 13 commits into
mainfrom
guido/agents-roster-grid
Sep 9, 2026
Merged

Stop the person-facing screens saying things that are not true#463
guidovizoso merged 13 commits into
mainfrom
guido/agents-roster-grid

Conversation

@guidovizoso

Copy link
Copy Markdown
Collaborator

Four defects found in a UI/UX pass over the app, plus the regressions that review then found in the fixes for them. The thread running through all of it: a screen should not assert something it has not established.

What changes

A routine's firing no longer reads as a message the person wrote. A scheduled routine arrives as role: "user" carrying a model-facing frame — three sentences of scaffolding — which the transcript drew in an ordinary bubble, and the channel titler used to name the conversation. The frame is now declared once in shared/routine-firing.ts and read back on both sides, so the server still persists exactly what it persisted before. The transcript draws a firing as a scheduled event, and the scroll machinery stops treating it as the person having just sent something.

A tool line stops printing its plugin's name twice. mcp__notes__search_notes read "Search notes notes". The server is dropped when the label already names it as the thing acted upon — matched word by word, singular-aware, and excluding the label's leading verb, so mcp__posts__post_message keeps its "posts" attribution rather than losing it to a coincidence with "Post".

Both agent screens say what is going on. Empty states where there was a heading over nothing; loading states that hold their own height; error states styled as the empty state with destructive text and border. isError is gated on the query never having returned, so a failed background refetch shows the roster it still has rather than blanking it — TanStack Query keeps data across a failed refetch, so error-with-data is an ordinary state, not a contradiction.

The home screen's shared Bots scroll instead of squeezing. A fifth public Bot used to compress all five; they are now a carousel with controls on the heading's baseline.

Also here: PageShell gained a scroller, because _authed/_app is deliberately a non-scrolling viewport and Skills was clipping 246px below the fold with no way to reach it. And the sidebar toggle stands down while the sidebar is already on screen, never while it is the way back.

Review

The branch went through a full review-fix loop. Round 2 found that three of round 1's own fixes were regressions, each corroborated by more than one reviewer and each reproduced before being accepted:

  • readFiring had started returning null for a frame wrapping a blank instruction — but null means "not a firing" to every caller, so that drew the scaffolding as the person's own words. The exact defect the branch exists to remove.
  • The new error arm outranked cached data, so a transient refetch failure blanked a working roster and, on /, printed "there's no one to send this to yet" beside a still-enabled composer.
  • singular() matched the label's verb rather than its object, dropping the server for mcp__posts__post_message and mcp__lists__list_files — attribution main had kept.

A later round found a fourth: the empty-slice case, where one section rendering real cards proves the sibling's emptiness is a fact rather than a failure.

Every state was then checked in a browser against the real app rather than only in tests — the destructive states measure oklch(0.577 0.245 27.325) for both text and border, matching the --destructive token exactly, at the same 180px the other arms occupy.

Notes for the reviewer

  • The branch is behind main; happy to rebase.
  • Repo-wide bun test has pre-existing failures in agent-handoff, agent-computer and agent-bot/agent-langgraph (the last two are missing-module errors, reproduced on main). None are in files this branch touches. bun test app is 370 pass / 0 fail.
  • One product gap was found and deliberately not fixed: on /, a query that succeeds and returns zero agents leaves the composer disabled with no explanation. It previously said "couldn't be loaded", which was false; removing that lie exposed the silence underneath. New copy is a product decision.

…ex wrapper collapsing auto-fill to a single column
…ular, so a routine call stops reading Create routine routines
…ognise it and the server still stores exactly what it stored before
…aiming emptiness before the list has arrived
…nated key stops printing beside the words it already said
A channel a routine opens carries the firing frame around its first message, so the
auto-generated title was drawn from three sentences of model-facing scaffolding rather
than from what was asked. `readFiring` now runs before the excerpt is built, the same
unwrapping the transcript already does before a person reads it.

`readFiring` returns null if and only if the text is not a framed firing. A frame
wrapping a blank instruction is still a firing — the schedule ran and the frame is
intact — so it returns that (empty) instruction. Overloading null to also mean "firing
with nothing in it" would tell both callers the opposite of the truth: the transcript
reads null as "a person wrote this" and would draw the scaffolding as their own words,
which is the defect this branch exists to remove.
…ht while it arrives, and name the shared-with-you rule once
… below the fold

`_authed/_app` is deliberately a single non-scrolling viewport — "one viewport, never
scrolls: panes scroll inside it" — and its `main` is `overflow-hidden`. PageShell rendered a
plain auto-height div with no scroller of its own, which is fine under `_authed`, where the
document scrolls, and silently clipping under `_app`, where nothing does.

Measured on Skills: `main` was 800px tall against 1046px of content, so 246px sat below the
fold with no way to reach it by mouse or keyboard. `/routines` had the same zero scrollable
descendants and merely happened to fit.

`min-h-0` is the load-bearing half of `min-h-0 flex-1`: a flex item's default `min-height:
auto` refuses to shrink below its content, so `flex-1` alone would have grown the pane and
clipped exactly as before. Under `_authed` both are inert and `overflow-y-auto` on an
auto-height element shows no scrollbar, so the eighteen screens there are untouched —
verified: `/settings` and `/admin` still scroll the document and gain no inner scroller.

The scroller wraps the centred column rather than being it, so the scrollbar rides the
pane's edge instead of appearing inside a 630px measure with content either side of it.
On a desktop-width window with the roster showing, a button whose whole offer is to take it
away is clutter beside the screen's own controls. It now draws only where it has something to
offer: on mobile, where the sidebar is a Sheet that starts closed and this is the only way to
open it, and on desktop once the sidebar is collapsed.

Deliberately NOT hidden in both desktop states. Collapsing is remembered across reloads
(`lib/sidebar.ts` stores `collapsed`) and ⌘B still works, so a window that hid this whenever
it was desktop-width would leave somebody who pressed ⌘B once with every channel behind a
shortcut nobody told them about — which is the defect this component was written to fix. The
rule is that it may stand down while the sidebar is visible, never while it is the way back.

The visibility test is exported as `useSidebarToggleVisible` rather than re-derived, because
PageShell reserves a 56px bar to hold this control. Deciding that on "is there a sidebar"
while the toggle decides on something narrower left an empty band on every configuration
screen — measured on /skills, /settings and /admin — which is the layout bug that file's own
comment warns against. One predicate keeps the two from drifting apart.

Also corrects the docstring, which claimed the toggle is drawn in both states.
@guidovizoso
guidovizoso force-pushed the guido/agents-roster-grid branch from 010ada2 to 5508bc1 Compare September 9, 2026 14:33
@guidovizoso
guidovizoso merged commit d12300b into main Sep 9, 2026
14 checks passed
@guidovizoso
guidovizoso deleted the guido/agents-roster-grid branch September 9, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants