Skip to content

Let each person pick a skin: OpenDuet, OpenDots or OpenMuse - #856

Open
davidmckayv wants to merge 44 commits into
mainfrom
david/skins
Open

davidmckayv wants to merge 44 commits into
mainfrom
david/skins

Conversation

@davidmckayv

@davidmckayv davidmckayv commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

Anyone, not just an admin, can change how OpenBot looks for them under Settings → Preferences → Skin. There are three skins, each taking the look of one of the open-source templates and running on OpenBot's own features:

  • OpenDuet:
    • One phone-width column for Chat and Tasks, with a floating header and a dictation orb.
    • A frosted tab bar.
    • A side menu that reaches every OpenBot screen.
    • Tasks shows OpenBot's own lists: what's waiting on you, responsibilities, routines and suggestions.
  • OpenDots:
    • An icon rail and a sidebar of Bots and recent chats.
    • Breadcrumbs and a per-Bot pause.
    • The Bot's computer in a right-hand pane.
    • Routines and Memories wrap OpenBot's own lists.
  • OpenMuse:
    • One Bot at the centre of a column, with a Bot switcher and a bell for what's waiting.
    • Chat, Activity, Ideas, Goals and Apps in a floating tab bar.
    • Each tab is OpenBot's own components under the skin's titles, and Apps opens OpenBot's Connections page.

Skins keep the deployment's identity. Every name a person reads comes from tenant.product_name in brand.yaml. The mark is OpenBot's AgentOrb, every Bot shows its own avatar from its avatar_seed, and the words are OpenBot's (Bots, Approvals, Connections). Only the look changes. The picker shows each skin's description, and the default skin uses the brand's name.

Left out on purpose: template features OpenBot has no server for, such as OpenDots' Spaces, OpenDuet's Make tab and morning brief, and OpenMuse's mail, calendar and page watches. There are no placeholders and no dead buttons.

How it is built

  • Skins reuse OpenBot's components. Approvals, responsibilities, routines, memory and suggestions are OpenBot's own components, wrapped in each skin's layout. No skin re-implements them, so a new approval choice or routine field appears in every skin.

  • One navigation list. app/src/lib/navigation.ts (navigationFor(role) / useNavigationItems()) drives OpenBot's sidebar and every skin's menu. Each skin has a test checking that its menu matches the list.

  • data-slot hooks. Skins style OpenBot's own elements only through these explicit hooks:

    • the composer: composer, composer-input, composer-attach, composer-send, composer-stop and composer-dictate(-cancel/-stop);
    • the conversation: composer-column and conversation-header;
    • the right-hand pane: detail-pane(-header/-title);
    • pages: page-header and page-title.

    Tests pin each hook to its element, and fail if any skin stylesheet names a Tailwind class or an aria-label.

  • The choice is saved on the account, as skin in users.preferences. That column is jsonb, so there's no migration.

    • The picker draws the skin at once and saves it optimistically, reverting with a message if the save fails.
    • A sync under _authed applies the account's skin whenever preferences load.
    • localStorage is only the pre-paint cache that app/index.html reads so a reload never flashes the default, and sign-out clears it.
    • OpenMuse's chosen Bot is remembered per person.
  • Tokens stay scoped. Each skin overrides shadcn's variables only under :root[data-skin="<id>"] and its .dark. Tests hold every stylesheet to its own scope and to defining dark for every light token. Every skin meets WCAG AA contrast and respects reduced motion.

  • The chat itself is OpenBot's under every skin, so the browser-side computer, approval and component tools keep working.

  • Layout. Only each skin's own screens use its narrow column. Bot pages, Admin, Settings and the computer panel get OpenBot's normal width.

  • Other existing code touched:

    • the display hooks: the index.html pre-paint, the styles.css import, the frame in _authed/_app.tsx, the Skin row in Settings and /s/$screen;
    • the sidebar, which now renders the shared list;
    • the data-slot attributes;
    • the preferences type and route;
    • sign-out clearing the cache;
    • the Connections helpers, which moved from the route file into lib/plugins.
  • The AG2 test change (Anthropic's 2.x SDK sends the key as a Bearer token) is identical to Read the Anthropic key from either header in the AG2 test #859 and drops out once that merges.

Checked:

  • bun test app/tests (1764 pass) and the server preference tests (18 pass).
  • typecheck, typecheck:tests, lint, format:check and the app build.
  • Live in Chrome on a local stack with real Bots, under every skin in light and dark:
    • the skin is saved to the account and survives a reload with the cache wiped;
    • every OpenBot route renders, and OpenBot's pages keep OpenBot's width;
    • a live conversation where the Bot used its computer works;
    • no template name appears in any text, label or alt text.

Where it runs

  • New state that outlives a request? The skin, in users.preferences jsonb, merged in Postgres the way the other preferences are.
  • What happens on the second replica? Nothing new. The preference lives in Postgres, so every replica reads the same value.
  • Anything serialised? The skin is a single-key jsonb merge (preferences || patch), the same as the existing preferences, so concurrent changes to different preferences are kept.
  • Anything fanned out to a browser? No server fan-out. Other tabs in the same browser follow through the storage event, and other devices pick up the skin on their next preferences load.
  • New listener, port, or schedule? No.

Boundary and audit

  • Every acting call still goes through the gateway. Skins add no acting call, and the chat and its tools are OpenBot's.
  • New refusals and new failures each write a row. None are new.
  • Nothing new is trusted from the client. The preference is validated against the known skin ids on the server.

Changelog

  • A line in CHANGELOG.md under Unreleased.

Adds app/src/skins: a registry of skins, a per-browser choice stored like
the dark theme and applied before first paint as data-skin, a Skin row in
Preferences, and /s/$screen for screens only a skin has. A skin may replace
the frame around the app's pages and scope its tokens to its own data-skin;
tests hold every skin's stylesheet to its own scope and to defining dark for
every light token. OpenDuet, OpenDots and OpenMuse are registered as stubs.
…er pane around OpenBot's pages

The OpenDots stub becomes OpenDots' own frame drawn over OpenBot's data. A 48px rail
(home, sidebar toggle, new chat, Bots, activity, skills, connections, admin, settings,
account) sits beside a 220px sidebar whose DOTS are the Bots from the agent list, each
wearing one of OpenDots' four plush avatars chosen by its id, and whose RECENT CHATS are
OpenBot's conversations with search, pin and delete. A Dot reopens its latest chat or starts
one; its "..." opens the Bot's page. The top bar carries OpenDots' breadcrumbs, a pause and
resume for the Bot on screen, and the computer toggle: on a conversation it drives the
conversation's own computer pane, elsewhere it opens OpenBot's computer panel in OpenDots'
right pane. At phone width the sidebar becomes OpenDots' hamburger drawer.

Every page inside is OpenBot's own, chat included, in OpenDots' tokens for light and dark
(the dark set derived with OpenDots' own lightness flip). Scheduled & activity and Memories
are skin screens that put OpenBot's routines and memory lists under OpenDots' page heading.
Spaces, pages, the Brief and the pause-everything switch are left out: OpenBot has nothing
behind them.

Tests draw the frame against OpenBot's own agent routes and hold it to: every OpenBot
surface reachable, Settings reachable on every page even when nothing loads, DOTS listing
the agent list, a Dot opening a chat with its Bot, the computer toggle driving the
conversation's pane, and per-Bot pause reaching the server.
OpenDots opens a Dot on its persona: the plush, the name, what it does and a way to
edit it, above an empty conversation. OpenBot's new-conversation page leaves that space
empty, so the frame draws the persona there from the Bot's profile and hides it as soon
as the page shows a message. Also keeps the composer and its notices off the pane's edges
where the column is no wider than the composer, drops the rule under a conversation's
header so it reads as one band with the top bar, and gives the disabled send button
OpenDots' pale disc.
The top bar's computer toggle opens the conversation's own Computer pane, so it now wears
the same grey ground, ruled header and selected-tab title as the frame's pane elsewhere.
…Bot's data

OpenDuet's look, rebuilt from OpenBot's primitives, Tabler icons and
OpenDuet's tokens, so a person can pick it in Settings and keep everything
OpenBot does.

The frame is one column at most 430px wide. On Chat and Tasks a header
floats at the top with the menu disc, OpenDuet's mic orb and the person's
initial (their Settings), and a frosted tab bar floats at the foot. The orb
presses the composer's own dictation rather than running a second one, so
recording, transcription and where the words land stay OpenBot's; on a
screen with no composer it opens Chat and starts there. Every other page
opens over the app with a back disc and its own heading in a title pill.
The side menu lists Chat, every conversation and group (pinned first, pin
and delete on right-click), New conversation, and under Manage: Bots,
Connections, Skills, Memory, Routines, Responsibilities, Approvals,
Notifications and, for administrators, Admin. Settings is never folded
away. The frame keeps OpenBot's channel socket and the browser notification
the sidebar's Bots badge sent.

Tasks, at /s/tasks, shows only what OpenBot keeps: pending approvals and
Bots' questions, decided and answered on their cards with OpenBot's own
mutations; active responsibilities and switched-on routines; and, under Up
next, background research's suggestions with Start and Hide.

Left out because OpenBot has no backend for them: Make, the morning brief,
triage, the setup checklist, first-day onboarding, reply chips, channel
icons and colours, Archived, the "Match phone" theme and adding tasks.
…rding

The orb watched the whole conversation for the composer's recording
controls and set its state on every change to the page, which during a
streamed reply is every word. It now sets state only when recording
actually starts or stops.
…dark

OpenBot's composer sits on the card colour, which in this skin's dark set is a step above
the canvas; OpenDots' composer is the canvas with a hairline around it.
…ph, the composer's edge

Driving the skin against a real OpenBot found three things.

The orb rested while a Bot browsed with its computer: it read only the
roster's busy flag, which arrives over the socket and missed the person's
own run. It now also reads the composer's Stop control, which OpenBot
draws for exactly as long as a turn runs, and waits 700ms before thinking
so the moment Stop shows while a conversation opens is not a flash of
"Thinking" on every open.

A waiting computer_run_command was drawn with the browser's globe,
because the card asked whether the tool was the computer's before asking
whether it was a command. The kind is now worked out once, commands and
files first.

A conversation's composer ran flush to the 430px column's edges with its
focus ring on the screen's edge. The skin insets OpenBot's composer
wrapper by 12px, as OpenDuet floats its own.
OpenMuse is a personal agent shaped like a phone. This rebuilds it on
OpenBot's own components, Tabler icons and tokens so a person can pick it
in Settings and keep everything OpenBot does.

The frame is a single 760px column: a menu button, the centre Bot as
OpenMuse's capybara with its name and a live status line, a bell for the
approvals and questions waiting on the person, and a floating pill of
Chat, Activity, Ideas, Goals and Apps. The centre Bot follows the
conversation on screen, then the person's choice, then OpenBot's default
Bot; a sheet behind the name switches it.

Chat is OpenBot's own chat route under the frame, restyled through the
skin's tokens and scoped CSS, so the computer, approval and component
tools keep working. Activity, Ideas, Goals and Apps read OpenBot's Bot
activity, approval inbox, suggestions, routines, responsibilities,
connections and memory through the existing query and mutation
factories. The menu reaches every OpenBot surface, Settings and Admin
included. Mail, calendar, page watches, PDF filling and task plans have
no OpenBot backend and are left out.

Light is OpenMuse's palette; dark is a night version in its spirit. The
tests render the frame against OpenBot's API shapes and check that every
surface is reachable and each tab reads and writes the right data.
index.html applies the stored skin before first paint without knowing which
skins exist, so a removed skin's name stayed on <html> for the whole visit
while the app drew the default. The store now settles the attribute when it
loads.
…nBot's orb and avatars

A skin keeps OpenDuet's look and takes its identity from the OpenBot
deployment. The menu header and the orb's "Talk to …" now read the
product name from brand.yaml (appConfig.brand.productName), as OpenBot's
own sidebar does, and nothing a person reads says OpenDuet; the skin's
picker name stays OpenDuet, since that names the look.

The orb's face is OpenBot's AgentOrb, as sign-in and onboarding draw it,
in place of OpenDuet's mic-orb robot, which is removed. Its behaviour is
unchanged: it presses the composer's dictation, listens while recording
or typing, and thinks while the Bot works.

Conversation avatars are each Bot's own, drawn from its avatarSeed in
OpenBot's ChannelAvatar, and Tasks uses OpenBot's words for decisions:
Allow once, Always allow here, Deny, Start as task, Dismiss.
…ts' look

A skin changes how OpenBot looks, never whose product it is. The wordmark and the home
links now name the product from brand.yaml (appConfig.brand.productName), the rail's
monogram is OpenBot's AgentOrb, and every Bot row and the coworker home draw OpenBot's own
avatar from the Bot's avatarSeed. The plush avatars and their images are gone. The words
follow OpenBot: BOTS, "Bots" in the breadcrumb, "Edit Bot". The rail, sidebar layout, top
bar, computer pane, density, type and palette stay OpenDots'. The skin's own name in the
picker is still OpenDots, since that names the look.

New tests render the frame and hold it to the product name, to never showing "OpenDots" or
"Dots", and to drawing each Bot with OpenBot's avatar from its seed; both fail on the
previous skin.
Skins keep their look but take their identity from the OpenBot deployment.
The OpenMuse skin now names itself with the brand.yaml product name, the
same one OpenBot's sidebar shows, and never says "OpenMuse" to a person;
only the picker still calls the look OpenMuse.

The capybara is gone. The top bar, the Bot switcher, the Your Bot card,
approval cards and conversation rows draw each Bot with its own OpenBot
avatar, and the deployment's AgentOrb stands in when there is no Bot yet.
Copy uses OpenBot's words: Bots, Connections, Approvals.

A regression test renders the frame on every tab and checks that it shows
the product name, never shows "OpenMuse" in text, labels or alt text, and
draws no capybara.
Anthropic's SDK 2.3.0, which CI now installs for the AG2 harness, sends the
key as Authorization: Bearer instead of x-api-key. The agent's key still
reaches Anthropic; the test only looked for the old header, so it failed on
every pull request. It now accepts either, and passes with Anthropic's SDK
1.17.0 and 2.3.0 alike.

(cherry picked from commit a1391e7)

@guidovizoso guidovizoso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plumbing is solid:

  • Data access goes through the lib/* factories.
  • Every skin selector is scoped under data-skin, and a test enforces it.
  • Every light token has a dark one.
  • The pre-paint script means no flash.
  • An unknown stored skin falls back to the default.
  • Settings, Sign out and Admin are reachable in every skin.

Requesting changes, mainly for the first point.

Blocker

The skins re-implement OpenBot's screens instead of reusing them.

  • Approvals now exist three times: components/approvals/waiting.tsx, skins/openmuse/approvals.tsx and skins/openduet/tasks-screen.tsx. The copies have already drifted:
    • there are three different tool-name formatters;
    • OpenMuse hides "What it will send" behind a toggle, where OpenBot always shows it.
  • OpenMuse rebuilds routines, memory and suggestions (screens/goals.tsx, screens/apps.tsx, screens/ideas.tsx). Its routine delete is "Stop this routine" then "Remove for good", instead of the existing Delete confirmation dialog.
  • Navigation lives in four lists: app-sidebar, openduet/places.ts, openmuse/menu-sheet.tsx and opendots/frame.tsx/sidebar.tsx.

Any new approval choice, routine field or route will silently be missing from two or three skins, and nothing fails. OpenDots already shows the right pattern: opendots/screens.tsx wraps RoutinesList/MemoryList in its own heading. Please build the skins from the existing components throughout, and read navigation from one shared list.

Should fix

  • Skin CSS targets OpenBot's Tailwind classes and copy. For example form.rounded-2xl, .sticky.top-0.border-b and .bg-sidebar.border-l > .h-12.sticky > div:first-child (opendots/skin.css), and [aria-label="Message"] (openmuse/skin.css). openduet/frame.tsx:42 also writes an attribute onto a React-owned heading. Any composer restyle breaks the skins without a test failing. Please hook skins onto explicit data-* slots instead.
  • "Each person" is per browser (skins/store.ts). On a shared machine the next person inherits the skin, and also the Bot chosen in OpenMuse (openmuse/chosen-bot.ts). User preferences already exist (userPreferencesQueryOptions). Keep localStorage only as the pre-paint cache, or retitle.
  • A skin imports helpers from a route file. openmuse/screens/apps.tsx imports from routes/_authed/_app/connections.index, which works against autoCodeSplitting. Move the helpers to lib/plugins/.
  • Contrast. OpenDots' "YOUR WORKSPACE" label (8px, 2.7:1) and its description (12px, 3.0:1) in opendots/screens.tsx fail WCAG AA.
  • OpenMuse highlights the wrong tab. tabFor (openmuse/model.ts) maps everything else to "apps", so /routines, /approvals and /bots/x/setup mark Apps as the current page.
  • Copy against openbot-copy:
    • "Always-on coworkers" (opendots/index.ts);
    • "run on the server" (opendots/screens.tsx);
    • "The beginning of something lighter" as an empty state (openmuse/screens/activity.tsx);
    • "Personality & memory" under a tab called "Apps" (apps.tsx).
  • The picker shows only names. Each skin's description is never displayed, and the default is hardcoded "OpenBot" even when brand.yaml renames the product.
  • Wide screens (please check live): OpenDuet's 430px column and OpenMuse's 760px column hold every page, including Bot pages and the computer panel.

Nits

  • The agent-ag2/tests/test_main.py change (an anthropic 2.x header change) belongs in its own PR.
  • Add motion-reduce to OpenMuse's animate-pulse; OpenMuse and OpenDots have no reduced-motion handling at all.
  • Every OpenDuet approval card is a region with the same name.
  • The \nNext run line break collapses inside a <p> (goals.tsx).
  • /s/$screen with another skin's key silently goes home, and activity means a different screen in each skin. Nothing tests switching skins while on a skin screen.

…ot's width

From Guido's review of the skins.

Tasks re-implemented OpenBot's approvals, suggestions and routines, and
its copies had drifted: its own tool-name formatter, its own decision
buttons, approval cards that were all regions with the same name. Tasks
now draws OpenBot's components unchanged (WaitingForYou for approvals
and questions, BotResponsibilities, RoutinesList and SuggestionsInbox)
under OpenDuet's Active and Up next switch and section headings. What an
action will send, every choice and every confirmation are OpenBot's. The
skin keeps only the badge's count, and a test renders WaitingForYou and
holds the badge to the rows it draws.

The frame no longer writes an attribute onto a page's own heading. A
page opened over the app has a back disc and keeps its heading as drawn.

Only Chat and Tasks are phone-shaped now. Every other page keeps
OpenBot's width on a wide screen, and a conversation with its computer
or its Bot's details open grows by OpenBot's 400px pane, so the computer
sits beside the conversation as it does in OpenBot.

Every animated control in the frame says what reduced motion does, and a
test holds new ones to it; the orb is AgentOrb, which reads the same
preference. The skin's description no longer calls a Bot an agent.
…d reuse OpenBot's parts

Review on #856 found the skin's page label and description below WCAG AA, house-style copy
problems, no reduced-motion handling, and pieces rebuilt that OpenBot already has.

- Contrast: every text colour now reaches 4.5:1 and every icon 3:1 on the surface it sits
  on, in light and dark. Greys that fell short are darkened along their own hue; the
  YOUR WORKSPACE label grows from 8px to 10px.
- Copy: the picker description no longer says "coworkers"; the schedule screen is
  Routines, as OpenBot names it, at /s/routines, without "run on the server"; empty states
  and errors say what will appear and what to do next.
- Reduced motion: the frame's chrome and the menus, tooltips, sheets and dialogs it opens
  stop transitioning and animating under prefers-reduced-motion.
- Reuse: the phone drawer is OpenBot's Sheet (focus trap, Escape, scrim) at OpenBot's own
  768px breakpoint, and older conversations load through OpenBot's ChannelPagination.

New tests measure every colour pair against WCAG in both themes, hold the reduced-motion
rule and the chrome markers in place, and open the phone drawer to reach every surface.
…ns data-slot hooks

The skin was a per-browser choice, so the next person on a shared machine
inherited it. It is now a user preference (`skin`, default "openbot") that
the server validates against SKIN_IDS, which move to shared/. The picker
draws a skin at once and saves it through an optimistic preferences
mutation that puts the skin back and says so when the save fails.
SkinAccountSync, mounted in _authed, applies the account's skin whenever
the preferences load or change, so the account wins over the browser's
cache. localStorage stays only as the pre-paint cache, and the central
sign-out clears it.

The picker names the default skin after brand.yaml's product name and
shows each skin's description.

OpenBot's destinations (Bots, Capabilities, Connections, Admin for admins,
Settings) live in lib/navigation.ts. The sidebar renders its footer and
user menu from that list, so skins can read the same one.

Skins styled OpenBot through Tailwind classes and ARIA copy. The composer,
its input and buttons, the conversation header, the composer column, the
detail pane and the page title now carry data-slot hooks for them to use.

The Connections listing rules move from the route file to
lib/plugins/listed.ts, so OpenMuse's Apps screen stops importing a route.
The OpenMuse skin rebuilt approvals, routines, responsibilities,
suggestions, memory and connections, and its copies had already drifted
from OpenBot: a different tool-name formatter, "What it will send" hidden
behind a toggle, and its own routine delete flow. A new approval choice or
routine field would have been missing from the skin with nothing failing.

Each tab now puts OpenBot's own sections for the centre Bot under
OpenMuse's titles. The bell and Activity use WaitingForYou and BotNeedsYou;
Activity adds the Bot's activity sections; Ideas uses SuggestionsInbox and
ProactiveResearchSettings; Goals uses RoutinesList and BotResponsibilities,
with OpenBot's Delete dialog. Apps is OpenBot's Connections page. The
skin's copies and its formatter are gone.

A tab is lit only on its own screen, not on every other page. Pages that
are not a tab, and a conversation with its computer or settings open, take
OpenBot's own width on wide screens; the top bar and tab bar stay centred.
The chosen Bot is remembered per signed-in person, so the next person on a
shared browser starts from OpenBot's default Bot. Buttons, tabs and sheets
respect reduced motion, and copy follows OpenBot's style.
# Conflicts:
#	app/src/skins/openmuse/screens/apps.tsx
…y their hooks

The menu's destinations are OpenBot's one navigation list now
(useNavigationItems: Bots, Capabilities, Connections, Admin for admins,
Settings), so a destination added to OpenBot reaches this menu too. Chat,
the conversations and Tasks stay the skin's own. The skin's copy of the
list is gone, and the avatar disc takes Settings from the same list.

Every selector that reached OpenBot's markup by its classes or its words
now uses the data-slot hooks: the composer column for the inset, the
composer's stop control for "working", its input for the caret. Only
the three dictation controls, which have no hook, are still found by the
names OpenBot gives them, and a test holds those to the real composer.

A page opened over the app has its title back in the pill, read from its
page-title hook and never written to; the title stays for screen readers.
Where the page draws its own back link, the chrome's back disc goes where
that link goes and the page's own bar is hidden, so there is one way back.
A skin that restyles or drives dictation found the three buttons by their
aria-labels, so rewording one would break it without a test failing. They
carry composer-dictate, composer-dictate-cancel and composer-dictate-stop.
… pages through their hooks

The rail and sidebar now draw OpenBot's destinations from useNavigationItems(), the same list
OpenBot's own sidebar renders, so a destination added there reaches this skin too. The skin
keeps only what is its own: BOTS, RECENT CHATS, and its Routines and Memories screens. Bots
sits under the list of Bots; Capabilities and Connections are in the rail and the phone
drawer; Admin and Settings close the sidebar. OpenBot's Bots badge, which also sends the
browser notification, is mounted once: on the rail's Bots button, or on the menu button on a
phone.

skin.css no longer names OpenBot's Tailwind classes. The composer, send button, conversation
header, composer column and detail pane are styled through the data-slot hooks those pages
publish for skins.

Tests hold the rail and sidebar to navigationFor() for a member and an admin, and fail any
selector in skin.css that names a class other than .dark.
… orb

The orb found the composer's dictate, cancel and stop controls by their
accessible names, so rewording one for a screen reader would have broken
it silently. It uses the composer-dictate, composer-dictate-cancel and
composer-dictate-stop hooks now, and a test fails if any selector in the
skin, in its stylesheet or its code, targets an aria-label.
The menu sheet kept its own list of OpenBot destinations, which would miss
any page OpenBot adds. Its destinations now come from the shared
navigation list OpenBot's sidebar draws, filtered for the signed-in
person; conversations and the skin's tabs stay as they were.

The stylesheet reached OpenBot's components through Tailwind classes and
ARIA labels, so restyling the composer or the conversation header in
OpenBot would have broken the skin without a test failing. It now uses
only the data-slot hooks those components publish: composer,
composer-input, composer-attach/send/stop, conversation-header and
page-title. The system font is set through the font-sans token rather
than by overriding OpenBot's font classes.
…raph AG-UI test

anthropic 2.x sends the key as Authorization: Bearer instead of x-api-key and
dates its Messages API version later than 2023-06-01. The provider-boundary
test asserted both by their 1.x values, so it failed once the unpinned
requirements resolved 2.x. It now reads the key from either header and checks
the version is a date; the endpoint, key, model and body it asserts are
unchanged.

(cherry picked from commit 99685ee)
CI installs Chromium with --no-shell since #829, and enter-text launched
Playwright's default, the separate headless shell, so it failed to start.
It now launches channel "chromium", the build the computer itself launches.

(cherry picked from commit f220198)
The Connections page's helpers moved from the route file into
lib/plugins/listed.ts, and the guard's list of known brokered-provenance
sites still named the route file.

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