Stop the person-facing screens saying things that are not true - #463
Merged
Conversation
guidovizoso
requested review from
MikeRyanDev,
davidmckayv and
tylerslaton
as code owners
September 9, 2026 14:08
…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
…on appears to have written
…aiming emptiness before the list has arrived
…ops squeezing the other four
…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.
…in already type-checks the file
…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
force-pushed
the
guido/agents-roster-grid
branch
from
September 9, 2026 14:33
010ada2 to
5508bc1
Compare
davidmckayv
approved these changes
Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 inshared/routine-firing.tsand 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_notesread "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, somcp__posts__post_messagekeeps 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.
isErroris gated on the query never having returned, so a failed background refetch shows the roster it still has rather than blanking it — TanStack Query keepsdataacross 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:
PageShellgained a scroller, because_authed/_appis 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:
readFiringhad started returningnullfor a frame wrapping a blank instruction — butnullmeans "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./, 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 formcp__posts__post_messageandmcp__lists__list_files— attributionmainhad 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--destructivetoken exactly, at the same 180px the other arms occupy.Notes for the reviewer
main; happy to rebase.bun testhas pre-existing failures inagent-handoff,agent-computerandagent-bot/agent-langgraph(the last two are missing-module errors, reproduced onmain). None are in files this branch touches.bun test appis 370 pass / 0 fail./, 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.