Skip to content

Design uplift: Bots as orbs, an inset frame, and a finished dark mode - #903

Open
samjulien wants to merge 37 commits into
mainfrom
claude/openbot-design-inspiration-da568c
Open

samjulien wants to merge 37 commits into
mainfrom
claude/openbot-design-inspiration-da568c

Conversation

@samjulien

@samjulien samjulien commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A design uplift for OpenBot's default look, combined with Guido's inset panel from #912. It changes how the app looks, not what it does.

From #912 (merged in, Guido's commits kept as they are):

  • Each page sits in a panel inset from the window. There are three surfaces, darkest to lightest: the sidebar frame, the off-white page and the white cards.
  • Stock shadcn components, the dot-matrix pattern behind empty states, and Guido's per-page fixes. These include compact Bot cards on Home, app logos on rows, the Connections tiles and the Composio loading rows.

The uplift's look on top:

  • The palette: CopilotKit's pale lavender for the frame, lilac accents and a periwinkle ring in light, and a plum ground in dark.
  • Atkinson Hyperlegible type, bold page titles and 18px section titles.
  • Each Bot appears as its own orb, in a color from the Bot's existing seed.
  • Rows and cards have a firmer edge and a soft lift.
  • The sidebar marks a conversation Waiting for you when something there needs you, and its times are short.
  • Home has a subheader above the composer, and the composer cycles through example jobs taken from your own Bots.
  • A conversation opens with the Bot's orb, name and role, in a centered 720px column. The orb sits on the same dot pattern as the empty states.
  • Orange means one thing: waits for you. The approval card is orange, and the audit's "Did not happen" is not.

How the look is built

  • Colors, edges and shadows are tokens in theme.css, as plain shadcn tokens. A customer restyles the default look from that one file.
  • New tokens:
    • --warning and --warning-foreground for orange.
    • --card-border, --card-shadow and --card-shadow-hover for the card edge and lift, registered as border-card-border, shadow-card and shadow-card-hover.
    • --empty-border for the edge of empty states.
  • The composer has a raised variant, which Home uses.
  • openbot.css keeps only what is not a color: the typeface, the orb's edge and tone, and the conversation's centered column.

What stays the same

  • Behavior: no flow, control, route or copy meaning changes. There are no server changes and no new stored data.
  • Skins:
    • OpenDuet, OpenDots and OpenMuse keep their own look for everything this PR adds. skins.css resets the card and empty-state tokens for them, as it already does for the empty-state pattern.
    • The approval card, tool lines, audit marks, sidebar gutters, Home's composer and the greeting check the skin, so a skin keeps the version it was designed with.
  • The extension: its notice copies the new theme.css tokens, and a test keeps the copy in step.

Changed from #912

These are reverted on purpose. Each revert is its own commit, so it is easy to discuss or drop:

  • "Lighten page and section headings". Titles are bold again, and section titles are 18px.
  • "Bot page: describe the Bot, not the page". The role sits beside the orb again, instead of a paragraph under the title.
  • "Bot page: fold an idle Activity section into For you". Activity is its own section again.

New client-side behavior

  • Waiting for you: the sidebar joins the approval inbox, which it already loads, with each conversation's threadId. Approvals and questions both carry it.
  • Compact times: sidebar times read 2m, 3h, 1d. This uses a new relativeTimeShort(), so the other screens keep their longer format.
  • Home examples: one short line per Bot you can reach. It stops while the composer has focus or text, and under reduced motion. The routing sentence still shows only when there is a Bot to route to.

Testing

Only the touched tests were run, plus typecheck, lint and the build:

cd app
bun test tests/design-tokens.test.ts tests/page-empty.test.tsx tests/skins.test.ts tests/conversation-intro.test.tsx tests/bot-orb.test.tsx tests/avatar-skin.test.tsx tests/home-examples.test.ts tests/rotating-placeholder.test.tsx tests/home-fallback-routing.test.tsx tests/agent-roster-error.test.tsx tests/channel-item-waiting.test.tsx tests/channel-pagination.test.tsx tests/channel-work-row.test.tsx tests/skins-openduet-composer.test.ts tests/skins-opendots-frame.test.tsx tests/approval-tools.test.tsx tests/audit-table.test.tsx tests/audit-outcome.test.ts tests/sidebar-shortcut-layouts.test.tsx tests/user-preferences.test.tsx tests/bot-page-length.test.tsx tests/composer-collapse.test.tsx tests/transcript-open.test.tsx ../extension/tests/content-notice.test.ts
bun run typecheck
bun run build
  • Typecheck, lint and the build pass.
  • These tests fail locally, and each fails the same way without this PR's latest changes:
    • "the Outcome button narrows the rows to the outcomes ticked". It also fails on main.
    • "on a phone the sidebar is a drawer" (OpenDots frame) and three tests in approvals-rules.test.tsx. Each times out at about one second on a local machine. CI is the check for these.
  • Checked by hand at 1440×900 in light and dark: Home, a conversation, the Bot picker, Bots and a Bot's page.

Follow-up

Screenshots

Taken from a local OpenBot with real data, at 1440×900.

Home: before and after

Before After
Home before, light Home after, light
Home before, dark Home after, dark

Light and dark

Light Dark
Conversation, light Conversation, dark
Bot picker, light Bot picker, dark
Bots, light Bots, dark
A Bot's page, light A Bot's page, dark

Who OpenBot is for, what it must keep, and the brand inputs for the design work.
…ult skin

New tokens live in app/src/openbot.css, scoped to :root:not([data-skin]), so OpenDuet, OpenDots and OpenMuse keep their look. theme.css is unchanged because the extension's pages import it. Inter stays for OpenDuet.
Eight palettes from the CopilotKit color family, picked by hashing the Bot's avatarSeed, so a rename never changes a Bot's color and no data is added.
Small idle orbs are drawn still, so a long sidebar does not animate dozens of blurred layers. Larger or working orbs use AgentOrb, toned so light palettes do not wash out to white.
AbstractAvatar and ChannelAvatar render BotOrb when no skin is set, so every avatar call site follows without changes. Skins keep their generated faces.
A conversation row says Waiting for you when an approval or question for its thread is pending, joined on the client from the approval inbox. Busy rows show the typing dots only. Times read 2m, 3h, 1d. The sidebar shares one gutter.
The placeholder cycles through one short line per Bot the person can reach, and holds while the composer has focus or text, or under reduced motion. The routing sentence shows only while there is a Bot to route to, as the old hint did.
The Bot's orb, name, role and visibility open the transcript and scroll away with it, without disturbing open-at-bottom, paging or the rail of turns. The inline approval reads as a card. The transcript rail still needs its own design pass.
…Audit

Rows, cards and empty states get a firmer edge in the default skin. In Audit, Did not happen is a hollow mark instead of amber, so orange only ever means waiting for you.
Dark mode's --accent matched the menu surface, so the highlight in menus, pickers and comboboxes painted plum on plum. A lighter plum shows on both the menu and the page, and a test keeps the accent apart from both surfaces in light and dark.
A Bot's role can run to a paragraph, which filled half the conversation before the first message. The intro shows two lines and keeps the whole role on hover, on the Bot's page and in the detail pane.
samjulien and others added 13 commits October 30, 2026 08:31
- The composer column keeps its literal class, so OpenDuet's hook still finds it. openbot.css sets the default skin's centered 720px column.
- The work row test looks for the Bot's orb, not an svg.
- The OpenDots frame test sets the OpenDots skin, so its avatars are the OpenDots ones.
- The pagination test answers the approval inbox, so it counts only channel requests.
Every grey token in the default theme moves to the matching slate step, in light and dark. The
three greys that sat between palette steps keep their lightness and take slate's tint from the
steps either side, so contrast does not shift. The onboarding illustration and the component
preview's neutral-* classes become slate-* to match. Skins set their own colours and are
unchanged.
The sidebar and the window's edge now share one tint, and the page sits in a panel inset 8px with
rounded corners and a hairline, using the sidebar's own inset variant. Light mode runs darkest to
lightest the way Linear does: the frame, a slate-50 panel, then white cards on it; dark mode was
already in that order once the frame became its darkest surface.

Admin and settings now scroll inside the panel like the app does, so its corners stay on screen;
the audit table and the playground fill the panel instead of the window. Fields fill with the
card's white so they read as fields on a card or the panel, and row icon tiles take the panel's
grey with a hairline so they still show on a white card.
The conversation's main line was 14.4px, sized by hand; it is now text-sm like the rest of the
sidebar. The tests that named the old size class follow.
Page titles are semibold with slightly tighter tracking instead of bold. Section titles drop from
18px bold to 15px semibold, close to body size, so a long page reads as one title and its parts
rather than a row of titles competing with each other.
Plugins showed one plug icon on every row, so the icons told one vendor from another no better than
none. Rows now show the app's logo, its brand icon, or its initial; Connections and the app
directory fall back to the initial too. The More apps row, a directory rather than a vendor, gets
an apps icon. In the admin sidebar Skills and Automatic learning shared a file icon; they are now a
slash and a bulb.
The small uppercase OPENBOT over "Start a conversation" repeated what the sidebar already says
and gave the page two headings where one does the job. The greeting is now the page's only title,
in the same semibold as other page titles.
The carousel's tall cards left about 50px of nothing between each avatar and its name, and its
arrows showed even when every card fit. Bots are now rows (avatar beside name and one line of what
the Bot is for) two to a line, up to four, with an All Bots link when there are more. The skeleton,
empty and error states reserve the same two rows so the composer does not move.
"Review this Bot's activity and settings." could head any page. The description under the title
is now the Bot's own one-liner, which used to sit just below in the avatar block; that block keeps
the avatar and the Bot's title.
With nothing running or scheduled, Activity was a whole section holding one row, "All activity ·
Nothing yet". That row now sits at the end of For you, titled Activity. The section still shows,
with its rows to running and scheduled work, whenever there is any.
…button

GitHub's tile line was a full sentence cut off mid-word; tiles now carry a short version that fits
("Repository events can start Bots."), and the full sentence stays on GitHub's own page. An app
with no summary of its own no longer shows "Connect your Todoist account.", which only repeated
the Connect button beside it; the tile shows just its name.
… its count

The full directory takes a couple of seconds to arrive, and until then the page was a search box
over blank space. It now shows placeholder rows while loading, then how many apps there are
("1,605 apps. Search by name to narrow them.") or how many match a search.
Sam's square-dot matrix (from the design-uplift branch) now sits faintly behind every PageEmpty,
fading out from the top-right corner. It is built to be changed without touching a component:

- the tile is app/src/assets/patterns/dot-matrix.svg, used as a mask, so replacing the file
  changes the pattern;
- --empty-pattern, --empty-pattern-color and --empty-pattern-opacity in theme.css point at the
  file and set its colour and strength, per theme;
- --empty-pattern-opacity: 0 turns it off. Skins start with it off, since it is drawn for
  OpenBot's own palette.
guidovizoso and others added 12 commits October 30, 2026 17:52
The sidebar draws Bots as a lightning bolt, but empty states about Bots (no Bots yet, no other Bots
to hand off to, Bot not found) drew a robot. They now use the same bolt, so the two read as one
thing.
Conflicts:
- page-parts.tsx: PageEmpty takes Guido's empty-state pattern.
- Home: Guido's layout and Bot cards. The default skin keeps its subheader above the composer and the rotating examples. The composer keeps its stock frame.
The default look's colors move into theme.css as plain shadcn tokens, on Guido's hierarchy: the
sidebar frame darkest, the off-white panel, then white cards. The grays lean towards CopilotKit's
lilac, the ring is its periwinkle, and dark mode keeps the plum ground.

openbot.css keeps only the default skin's typeface and the orb's edge and tone. The extension's
notice copies the new tokens, and the token test pins the three surfaces in both themes.
- Rows, cards and empty states use Guido's page parts: no custom edge, shadow or wash.
- The sidebar uses Guido's inset frame, and its rows keep the stock padding.
- The approval card keeps its orange tint, the color for "waits for you", in the stock card shape.
- The composer loses the height prop that only Home's old frame used.
The dots behind a Bot's orb read Guido's --empty-pattern tokens, so swapping or turning off the
pattern changes both places, and skins start without it.
Guido's inset frame, three surfaces, shadcn components and empty-state pattern stay. What comes back
is the uplift's look, which the merge had dropped:

- The palette's exact values, the lilac selected row and the 0.625rem radius. The frame is
  CopilotKit's pale lavender in light and the plum #1f1c2c in dark.
- The firmer edge and soft lift on rows, cards and Home's Bot cards, and the faint wash and edge on
  empty states, behind the pattern.
- Home's composer frame and the 32px bold greeting, on Guido's centered layout.
- The orange approval card, the sidebar's shared left edge, and the composer height prop.
- Home's Explore Bots error and empty states, which the heading revert's conflict had dropped.

The extension's notice copies the restored tokens and radius.
So a customer restyles the default look from theme.css alone:

- Card edge and lift are --card-border, --card-shadow and --card-shadow-hover, registered as
  border-card-border, shadow-card and shadow-card-hover. Page rows, cards, empty states and Home's
  Bot cards use them. skins.css sets them back to each skin's plain hairline, as it does for the
  empty-state pattern.
- Orange is --warning and --warning-foreground, used by the approval card and "Waiting for you".
- Home's composer uses a raised variant on the composer instead of styling its form from outside.
- The transcript's centered column is set in openbot.css through a transcript-column slot, as the
  composer's already was.
- The approval card, tool-line cards, the audit's hollow "Did not happen" mark and the sidebar's
  gutters no longer check the skin.

Two fixes for Guido's surface order: empty states lose their wash, which sat darker than the page,
and dark secondary and muted fills are now lighter than cards rather than the frame's color.
The move to theme tokens had also changed OpenDuet, OpenDots and OpenMuse. Each is back to what it
drew before:

- The approval card, tool lines, the audit's "Did not happen" mark and the sidebar's gutters keep
  their skin check. OpenBot's own versions still take orange from the --warning tokens.
- Home's raised composer and bolder greeting are OpenBot's own; a skin keeps the default composer.
- Empty states take their edge from a new --empty-border token, so a skin keeps its hairline in dark
  mode, where rows and cards have none.
@samjulien samjulien changed the title Design uplift: Bots as orbs, a calmer frame, and a finished dark mode Design uplift: Bots as orbs, an inset frame, and a finished dark mode Oct 30, 2026
The transcript column's data-slot replaced the scroller content's own, which the paging tests and
the scroller look for. The column is marked with data-transcript-column instead.

This branch has not been deployed

No deployments
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