Skip to content

Revamp layout, spacing and empty states, and rework Boundaries, credentials and the Bot page - #838

Merged
guidovizoso merged 13 commits into
mainfrom
guido/ui
Oct 26, 2026
Merged

guidovizoso merged 13 commits into
mainfrom
guido/ui

Conversation

@guidovizoso

Copy link
Copy Markdown
Collaborator

What this changes

A pass on how the app is laid out, aiming at the calm, even feel of Linear and Vercel. Colours are left alone, since each deployment sets its own; the work is spacing, sizes, borders and the shape of a few screens that had outgrown themselves. One commit per change.

Units and rhythm

  • The root font size is the browser's 16px again. html { font-size: 15px } made every Tailwind size 6.25% off its documented value (max-w-3xl was 720px, not 768). Text stays on its 15px scale: each --text-* is Tailwind's default ×15/16.
  • PageShell sets the page's rhythm, so screens stop choosing margins: 32px from the title to the first content, 48px between sections, 64px at the bottom. PageRows holds every row to at least 56px with dividers inset 16px. Connections drops a heading that repeated the page title and stays a grid of outline tiles, documented as the one exception to divided rows.
  • Item rows are drawn like Linear's settings rows: 16px in, an icon as a 16px glyph on a 32px muted tile, the description a size smaller.
  • PageEmpty is shadcn's empty state (icon, title, one sentence, and the page's Add button where there is one) on every screen that lists something.
  • .claude/skills/openbot-screen-layout documents the rhythm table, the row anatomy, the empty state and the new "add something from a list" procedure.

Screens

  • Add credential is a screen with a back button (/admin/credentials/new) instead of a dialog, in a new PageCard. Its kind select shows "Model" rather than the raw model.
  • Boundaries was unreadable: the rule language explained in a paragraph of attribute names, rules shown as raw CEL. It is now a list of named rules under "Bots may never". Names and descriptions are stored on the server beside each rule (action_policies.labels). Adding a rule (/admin/boundaries/new) starts from a template or a blank rule and can be tested on recent activity first. Each rule opens to what it has stopped lately (/admin/boundaries/$index) and can be removed there.
  • A Bot's page ran to almost 10,000px. Its In progress, Scheduled and Completed lists move to /bots/$agentId/activity. The page keeps up to three running or scheduled items, running first with a pulsing dot (amber when it needs you), and a row to all activity. Suggested next steps show the newest three, with the rest behind "Show N more".
  • A conversation's gear panel ends with "This conversation": Pin (outline) and Delete (destructive), sharing the sidebar's delete dialog.

Fixes

  • Suggested next steps piled up. One Bot had 60 open, with duplicates and tool names in the text. A new superseded status lets each research run replace the open suggestions from the one before. Titles already open, or dismissed in the last 30 days, are refused. A title already announced is not notified twice. The research instruction now lists both kinds of title, asks for nothing when nothing needs doing, and asks it not to name tools.
  • The @ menu opened off the composer on the home screen. The message box's wrapper carried translate-y-0 at rest. In Tailwind v4 that sets translate: 0px, which makes the wrapper the containing block for position: fixed descendants, so the caret-placed menu was offset twice. The wrapper now has no translate at rest. Its transition also named transform, so the dictation lift never animated; it now names translate. The menu is also 320px wide and shows each Bot's avatar.

Where it runs

  • New state that outlives a request? In Postgres: a superseded suggestion status (migration 0061) and action_policies.labels (migration 0062). The research engine's in-process running map, which already held a run's setting, now also holds the titles that run was shown. It lives only for the length of a run in the process running it, as before.
  • What happens on the second replica? A research run is claimed by a conditional update that moves nextRunAt forward, so one process runs a given setting at a time. The process that finishes a run supersedes the previous run's open suggestions in one UPDATE … WHERE status = 'open' AND run_id <> $run. Rule labels travel inside the policy row and reach other processes through the policy store's existing refresh.
  • Anything serialised? Superseding is a single conditional update. Saving the policy with its labels is the same single write as before.
  • Anything fanned out to a browser? No.
  • New listener, port, or schedule? No.

Boundary and audit

  • Every acting call still goes through the gateway. Boundary rules are evaluated exactly as before; labels are display only and never read by the evaluator.
  • New refusals and new failures each write a row. No new refusal paths.
  • Nothing new is trusted from the client that the server can resolve itself. Labels are validated on save (names 1–80 characters, descriptions up to 300), and labels for rules that aren't in the policy are dropped.

Changelog

  • Under Unreleased: the two migrations under "Before upgrading", and an entry for each change a person would notice.

Proof

  • bun run format:check, lint, typecheck and typecheck:tests pass.
  • The full suite (bun run test) passes against a fresh pgvector database migrated from zero, 0061 and 0062 included: 8,416 pass, 80 skip, 0 fail across 726 files.
  • Before rebasing onto main, each of the first nine commits typechecked and passed the app suite on its own.
  • New tests:
    • proactive.test.ts: superseding, refusing dismissed and duplicate titles, no second notification.
    • proactive-store.integration.test.ts: supersede and history against Postgres.
    • computer-policy.test.ts and policy-durability.integration.test.ts: labels are validated, stored and reloaded.
    • boundaries-pages.test.tsx: the rule list, adding from a template, removing.
    • conversation-actions.test.tsx: the Pin and Delete buttons in the gear panel.
    • dictation-surface.test.tsx: no translate at rest. It fails without the fix.
    • bot-page-length.test.tsx: the activity glance and the suggestions fold.
    • Six existing tests were updated for the new empty-state wording.
  • Checked in a browser against a local stack:
    • On Market Scout's page, 9,919px down to 2,212px.
    • The @ menu opening at the caret on the home screen and above the composer in a conversation.
    • The activity glance, with dots, using a stubbed reply, since no Bot had anything running.
    • The Boundaries, credentials and empty-state screens, measured against the rhythm table.

The 15px root made every rem-based size a multiple of 3.75px, so padding,
heights and radii never landed on whole pixels. The root is now the
browser's 16px, and the text sizes are set to Tailwind's defaults times
15/16, so type reads exactly as before while spacing snaps to the grid.

The radius rounds to 8px, the a2ui card text keeps its 14px and 16px,
and two comments that quoted 15px measurements are brought up to date.
PageShell spaces the title block 32px from the first content and pads the
page 64px at the bottom, PageRows holds every row to at least 56px with
dividers inset 16px, and Connections drops its repeated section heading
and lays its apps out as outline tiles, the one accepted exception to
divided rows. The layout skill documents the rhythm.
A row is 16px in and 14px tall on either side, its icon a 16px glyph on a
32px muted tile, and its description a size smaller and 2px under the
title. Admin's rows pass their icons through the icon variant.
PageEmpty becomes a bordered Empty with an icon for the kind of thing, a
title saying there is none, a sentence on how the first one arrives, and
the page's own Add button where there is one to offer. Every screen that
lists something now says so this way.
The gear panel in a conversation ends with a "This conversation" section:
Pin as an outline button and Delete as a destructive one, stacked. Delete
confirms through the same dialog the sidebar uses, now shared.
A research run's new suggestions supersede the open ones from its last
run, through a new superseded status. The instruction lists what is open
and what was dismissed in the last 30 days so neither is suggested again,
asks for nothing when nothing needs doing, and keeps tool names out of the
text. A title already announced is not notified twice.
Add credential opens /admin/credentials/new, a page with a back button to
the list and the form in a PageCard, the new card for page-sized forms.
The kind select shows its label rather than the raw value. The layout
skill gains the procedure for adding something from a list.
Rules are read as "Bots may never" rows with a name and a sentence, now
stored beside each rule on the server. Adding one is its own screen that
starts from a template or a blank rule and can be tested on recent
activity first; a rule opens to what it has stopped lately and can be
removed from there. Trial mode, Audit and the allow rules sit under How
rules apply.
The message box's surface carried translate-y-0 at rest, which in
Tailwind v4 sets translate: 0px and makes it the containing block for
fixed descendants, so the menu was placed twice over, off the composer.
It now names no translate at rest, and its transition names translate,
so the lift as dictation starts animates.
Each option leads with the Bot's avatar, drawn from its id where a group's
roster has no seed, and the menu takes prompt-area's full 320px so a Bot's
title reads in full.
Each suggestion is a paragraph with two buttons, and a Bot with sixty open
ones made its own page 8,000px of suggestions with its settings far below.
The rest open in place from a "Show N more" row.
In progress, Scheduled and Completed move to /bots/$agentId/activity. The
Bot page keeps an Activity section with up to three running or scheduled
items, running first with a pulsing dot (amber when it needs you), and a
row to all activity with a count.
@guidovizoso
guidovizoso merged commit 2b65797 into main Oct 26, 2026
19 checks passed
@guidovizoso
guidovizoso deleted the guido/ui branch October 26, 2026 14:39
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.

1 participant