Skip to content

Let every Bot show what it is doing, as a kite instead of an orb - #1065

Draft
jerelvelarde wants to merge 5 commits into
CopilotKit:mainfrom
jerelvelarde:jerel/pixel-kite-mascot
Draft

jerelvelarde wants to merge 5 commits into
CopilotKit:mainfrom
jerelvelarde:jerel/pixel-kite-mascot

Conversation

@jerelvelarde

@jerelvelarde jerelvelarde commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Let every Bot show what it is doing, as a kite instead of an orb

The problem

A Bot in OpenBot is drawn as an orb, and the orb says almost nothing about it. BotOrb can draw three states and the app passes one, so a paused Bot looks like one ready to work and a Bot with a question for you looks like one with nothing to ask. The roster says "Paused" or "1 question" only in words, and the sidebar shows a running turn as three bouncing dots laid over the orb. The orb is also expensive: a blurred mesh with a backdrop filter every frame, which is why avatar sizes swap in a still CSS gradient.

It is no longer OpenBot's mark either. OpenBot 1.0 launches on 9 November with a kite that rests with no face and wakes with pixel eyes when it is in touch with you. #913 and #914 proposed giving the orb a face; the team chose the kite, and this branch replaces both.

The approach

One drawing, a pure function of time. kite/kite-draw.ts draws the mascot to the spec's numbers: a zero-radius kite whose face sits in its incircle, eight brand colours each with a lifted twin for dark grounds, and eight states (Sleeping, Idle, Listening, Working, Answering, Waiting for you, Done, Error). The face shows only at contact, forms from pixels in 0.7 s and scatters in 0.5 s; every change blends over 0.26 s and then holds; each Bot blinks on its own seeded schedule. Status is a square beside the kite in reserved system colours, never in the body. Because the pose depends only on state history, time and seed, the app draws what openbot.co and the launch video draw: a pixel diff against the mascot's reference renderer, over 672 combinations of state, size, colour, sail and theme, differs by at most 4/255 per channel.

Cheap in a list. Kite draws in a layout effect, so it never shows an empty frame, and then asks for frames only while something moves (a change of state, Working's pulse, Waiting's nudge, and above 32 px a blink). It redraws when the screen's pixel ratio changes, so a resting kite stays sharp across a zoom. A kite at 32 px and under draws at most about thirty frames a second. Every moving kite shares one animation-frame loop, the clock from #913, which stops in a hidden tab, so twenty resting Bots in a sidebar request no frames. At 32 px and under only the square moves; under 22 px there is no face; under reduced motion each state is its designed still.

State from what the app already knows. There is no new endpoint. kiteStateOf reads the roster's attention counts: a question, approval or stalled hand-off is Waiting, a failed task is Error, a paused Bot is Sleeping (dimmed, no face, no square), and a reply to read is Done. Waiting outranks Error, which outranks Sleeping, because a paused Bot with a question still needs its person; check-in finds never change the kite, because they never block the Bot. The Bots list and a Bot's page use it, the page letting its pause switch win over counts that refetch later. The Needs you board and the question and approval notices draw Waiting, a conversation's 96 px kite works while the Bot has the turn, and a busy conversation in the sidebar shows Working in its kite instead of the dots badge. A group keeps the badge, since no one kite there is the one working.

Every Bot keeps its place in the palette. The colour slot is the same FNV-1a hash of the avatar seed the orb used, and a test pins ten seeds to their old slots. The palette itself is now the brand's, so most Bots stay in their colour family, but the old teal slot becomes graphite and sky becomes teal. Bits 16 to 18 of that hash pick the sail, which tells apart Bots that share a colour; they are the bits that give all fifteen Bots in the examples a livery of their own, where the bits right after the colour made Release Notes and Research Desk identical. OpenBot's own mark is violet quarters, the kite on openbot.co and in the launch film, and the one Bot in 64 whose hash lands there takes the next sail so no Bot reads as the product. The mark appears on sign-in, onboarding, the New Bot sheet, the skins' frames and the OpenBot for Business icon. The New Bot sheet's "is ready" moment lands that kite in Done, so its separate check badge is gone.

The status square is cut out. The reference paints a ring of the page's colour around the square. A Bot here sits on rows, hovers, cards and selections, so the ring is cut out of the drawing and whatever is behind shows through.

What it costs. Each Bot is a small canvas rather than a CSS gradient, blank until script runs. The drawing reaches a few pixels past its slot so the square and pulse aren't cropped, which an ancestor with overflow: hidden can still clip. botLook: "orb" becomes "kite", a rename for any skin outside this repo. AgentOrb, BotOrb, ai-core, orb-palette and the orb PNG are removed.

What is not covered

  • No per-Bot "running now" signal reaches the Bots list, so Working shows in a Bot's conversation and the sidebar only. A reply beside a streaming answer doesn't pass Answering to its avatar yet; Listening and Answering appear where the app knows them (the New Bot sheet, OpenDuet's talk button, onboarding).
  • With faces only at contact, Error is the red square with no face. The flat-line mouth belongs to the always-awake mode, which isn't shipped.
  • Let a Bot's owner choose its colour and draw it a new face #914's colour picker isn't carried over: it needs its own edit route, and its palette leaves out violet, which this one keeps as a Bot colour (Risk Analyst, in the examples, is violet). Its server half would sit on top of this unchanged.
  • OpenBot Desktop's app and tray icons, the extension's icon and desktop/src/Welcome.tsx still draw the orb; they are separate packages with their own assets. The other skins still draw Bots as faces; only their frames' OpenBot mark changes.

Verification

  • format:check, lint and typecheck across all seven packages: clean.
  • App suite: 2,706 pass, 0 fail across 341 files at the head (2d924ef), against 2,663 across 340 on main (47cc61d).
  • New tests:
    • app/tests/kite-draw.test.ts (25) holds the drawing to the spec: palette and dark twins, status colours, face only at contact, no face under 22 px, eye shapes, whole-pixel gaze from 48 px, never a glance up and to the right, the blend, wake and sleep timings, seeded blinks that keep going after 400 s, stillness at 32 px and under, reduced motion, which states ask for frames, and the eye pixels, dimmed sleep and cut-out square as drawn.
    • app/tests/kite-livery.test.ts (13): ten seeds keep their orb colour slot, every shipped Bot has a livery of its own, no Bot wears OpenBot's violet quarters, and kiteStateOf maps each case in order.
    • app/tests/kite.test.tsx (9): a Bot is named once, OpenBot's kite is decorative violet quarters, twenty resting Bots request no frames, working Bots share one loop, a change of state stops asking for frames after about 0.75 s, a held kite never animates, dark mode redraws, a pixel-ratio change redraws a resting kite sharp, and enterFrom plays the landing.
  • An independent read-only review of the branch found no high-severity defect. Its fixes are in 2d924ef: sharpness after a zoom, OpenBot's livery reserved, no empty first frame, small kites throttled. A change of state in the middle of another's blend starts from the outgoing state's rest, as the reference renderer does, so it can snap within 0.7 s; that is kept to match it.
  • Extended: bot-roster (each roster state), bot-profile-active (a paused Bot's kite sleeps), avatar-skin, conversation-intro, onboarding-name-step, skins-openduet-frame, app-navigation and start-placeholder.

The recording is this branch on a local stack with its own Postgres: Bots created, Finance Bot paused and Ops Bot's conversation marked busy, all through the API. The question, failed task and reply to read are patched into the attention response, since real ones need a model run, and the stack's placeholder-licence banner is hidden.

Light Dark
Bots list, light Bots list, dark
A conversation, light A conversation, dark
Every state and size, light Every state and size, dark

One Bot through every state at 176 px, beside a sidebar at 24 px, light then dark

Merge notes

This supersedes #913 and #914, which are left open. #914 and #916 both change app/src/components/channels/avatar.tsx. Whichever of them lands after this one keeps the kites, and takes #916's state into KiteFace. #1066, stacked on this, writes the 1.0.0 changelog from the code; the version bump comes from the Create release PR action, run with major.

The Pro pixel kite picked for OpenBot 1.0, drawn by kite-draw.ts to the mascot spec's numbers
(Filled, pixel eyes, a face only at contact): a pure function of time and a per-Bot seed, put on
canvas by Kite, BotKite and OpenBotKite. Every kite that animates shares one frame loop; a resting
kite draws once. It honours reduced motion and dark mode, holds still at 32 px and under (only the
status square moves), and shows no face under 22 px.

A Bot's state comes from what main already knows: the roster's attention counts (waiting, error,
paused as sleeping, a reply to read as done), a turn in flight for the conversation's kite, and a
busy conversation in the sidebar. Each Bot keeps its orb's colour slot (the same FNV-1a hash) and
gets a sail pattern from the hash's next bits. OpenBot's own mark everywhere is the violet kite,
including the skins' frames and the OpenBot for Business icon.

Removes AgentOrb, BotOrb, ai-core, orb-palette and the orb logo PNG; renames botLook "orb" to
"kite".
…r state

A Bot's page drew its kite idle until the attention counts arrived, even when its lifecycle
already said paused. The roster and the page now each have a test that reads the state every
kite is drawn in.
…ilm use

The product's kite is violet quarters on the site, in the film and in the favicon. The app drew
it with a plain sail; the sign-in, onboarding, New Bot, skins' frames and the OpenBot for Business
icon now match.
…inct

Release Notes and Research Desk share the orb's plum, and the bits after the colour gave them the
same top sail too. Bits 16 to 18 give all fifteen Bots in the examples a livery of their own, and
a test now holds that.
…t's own kite

From review of this branch:
- A resting kite asks for no frames, so a zoom or a move to another screen left it at the old
  resolution. It now redraws when devicePixelRatio changes.
- One Bot in 64 landed on violet quarters, OpenBot's own kite. That Bot takes the next sail and
  keeps its colour; none of the examples' Bots change.
- Kites draw in a layout effect, so a newly mounted one is never shown as an empty frame.
- A kite at 32 px and under draws at most about thirty frames a second while its square moves.
- The changelog said every Bot keeps its colour. It keeps its place in the palette; the palette is
  the brand's, so the teal slot becomes graphite and the sky slot teal.

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.

1 participant