From b5651dc0daf3161a895dbb6e659b883003250469 Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:09:19 -0300 Subject: [PATCH] chore(design): app screens in the design system: sidebar, crew, chat, requests Adds Sidebar, CrewBots, BotRun, InboundRow and ApprovalCard to the claude.ai/design design system, so designs show Botloft's real screens. They read the app's store and API: BotloftProvider wires the app's own providers to the fake daemon, and scene.ts builds crews, bots, messages and chat items in the protocol's shape, with request inputs and summaries as the daemon makes them. Bot colors now come from AVATAR_PALETTE. Types are emitted for the whole package. --- .design-sync/NOTES.md | 25 ++- .design-sync/config.json | 31 ++- .design-sync/conventions.md | 71 +++++-- .design-sync/pkg/build.mjs | 45 ++--- .design-sync/pkg/index.ts | 14 +- .design-sync/pkg/provider.tsx | 81 ++++++++ .design-sync/pkg/scene.ts | 229 ++++++++++++++++++++++ .design-sync/previews/ApprovalCard.tsx | 72 +++++++ .design-sync/previews/BotAvatar.tsx | 12 +- .design-sync/previews/BotRun.tsx | 78 ++++++++ .design-sync/previews/BotloftProvider.tsx | 64 ++++++ .design-sync/previews/CrewBots.tsx | 43 ++++ .design-sync/previews/InboundRow.tsx | 64 ++++++ .design-sync/previews/Menu.tsx | 2 +- .design-sync/previews/SidePanel.tsx | 2 +- .design-sync/previews/Sidebar.tsx | 46 +++++ 16 files changed, 818 insertions(+), 61 deletions(-) create mode 100644 .design-sync/pkg/provider.tsx create mode 100644 .design-sync/pkg/scene.ts create mode 100644 .design-sync/previews/ApprovalCard.tsx create mode 100644 .design-sync/previews/BotRun.tsx create mode 100644 .design-sync/previews/BotloftProvider.tsx create mode 100644 .design-sync/previews/CrewBots.tsx create mode 100644 .design-sync/previews/InboundRow.tsx create mode 100644 .design-sync/previews/Sidebar.tsx diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md index e3a3880b..0dce557f 100644 --- a/.design-sync/NOTES.md +++ b/.design-sync/NOTES.md @@ -37,7 +37,19 @@ Tailwind and TypeScript). Nothing there reimplements a component. - Fixed overlays (`Dialog`, `Confirm`, `Toaster`) render inside a box with `transform: translateZ(0)`, which makes it their containing block; without it the per-story capture is 0 px tall. -- `ListAvatar` is excluded (cfg.componentSrcMap): a wrapper over `BotAvatar` that needs a bot object. +- The app screens (`Sidebar`, `CrewBots`, `BotRun`, `InboundRow`, `ApprovalCard`) read the app's + store and API. `pkg/provider.tsx` (`BotloftProvider`) wires the app's own `DaemonProvider` and + `HostProvider` to `FakeBotloft` and `FakeHost`, seeded with the crews and bots it is given, and + turns `approvals.answer` into an `onAnswer` callback. `pkg/scene.ts` builds protocol objects + (`makeCrew`, `makeBot`, `makeMessage`, `chat.*`) with the fake daemon's defaults; request + inputs and summaries follow the daemon (`crates/botloft-core/src/chat.rs` `tool_summary`, + `crates/botloftd/src/tools/browser_sites.rs`): a site request is `{site, url}` with the site as + its summary. +- Type emission covers the whole package with the repo as root; `.design-sync` is renamed to `ds` + in `dist/types/emit` (ts-morph skips dot folders), and `paths` points bare imports at + `app/node_modules` (`@types` first, or React resolves to its JS). `vite/client.d.ts` is added + for the fake daemon's `?raw` import. +- Bot colors come from `AVATAR_PALETTE` (protocol.gen.ts); previews and conventions use it. ## Known render warns @@ -45,6 +57,10 @@ Tailwind and TypeScript). Nothing there reimplements a component. Mono ships. Accepted. - `Menu` shows only its closed button: opening takes a click. - `SelectField` Disabled looks like the enabled one: the app's own control does not style disabled. +- `[RENDER_ERRORS] AbortError: Transition was skipped` on the app screens: each preview cell has + its own `BotloftProvider`, and each opens its first crew with the app's view transition + (`ui/motion.ts`); the transitions of several cells on one page cut each other off. Renders fine; + a design uses one provider. ## Re-sync risks @@ -54,5 +70,8 @@ Tailwind and TypeScript). Nothing there reimplements a component. ranges. Change both together. - The icon set follows the app's imports: an icon the app stops using disappears from `icons`. - Bot colors in previews and conventions are sample values, not tokens. -- The scope is the base pieces plus the bot badges (owner's choice on 2026-10-02). Chat rows, - approval cards and the sidebar are not in the package. +- Scope (owner's choices on 2026-10-02): the base pieces, the bot badges, and the app screens the + explainer video needs (sidebar, crew cards, a bot's turn, messages, requests). Not in it yet: + the whole crew view, the composer, the browser and screens panels, routines, settings. +- `scene.ts` mirrors protocol shapes and daemon summaries by hand; a protocol change + (`protocol.gen.ts`) or a new request kind needs it updated, or the tsc step fails. diff --git a/.design-sync/config.json b/.design-sync/config.json index 009509e0..03c3e62c 100644 --- a/.design-sync/config.json +++ b/.design-sync/config.json @@ -21,10 +21,13 @@ "SelectField": "label: string; hint?: string; options: { value: string; label: string }[]; value?: string; defaultValue?: string; onChange?: React.ChangeEventHandler; name?: string; className?: string; id?: string; style?: React.CSSProperties; disabled?: boolean; autoFocus?: boolean; ", "BotStateBadge": "/** The bot: its state and whether the owner paused it. */ bot: { state: \"offline\" | \"launching\" | \"idle\" | \"busy\" | \"needs_approval\" | \"rate_limited\" | \"auth_error\" | \"backoff\" | \"archived\"; paused: boolean }; /** A paused crew shows its stopped bots as paused. */ crewPaused?: boolean; compact?: boolean;", "ChiefBadge": "/** The crew this bot leads; only its name is read, for the tooltip. */ crew: { name: string }; /** Just the crown, for tight rows. */ compact?: boolean;", - "Menu": "/** Accessible name of the button that opens the menu. */ label: string; /** An icon from window.Botloft.icons, e.g. icons.Ellipsis. */ icon: React.ComponentType<{ size?: number; strokeWidth?: number }>; items: { label: string; icon?: React.ComponentType<{ size?: number; strokeWidth?: number }>; danger?: boolean; disabled?: boolean; /** Makes the item one of a set of choices, marked when chosen. */ checked?: boolean; onSelect: () => void }[];" - }, - "componentSrcMap": { - "ListAvatar": null + "Menu": "/** Accessible name of the button that opens the menu. */ label: string; /** An icon from window.Botloft.icons, e.g. icons.Ellipsis. */ icon: React.ComponentType<{ size?: number; strokeWidth?: number }>; items: { label: string; icon?: React.ComponentType<{ size?: number; strokeWidth?: number }>; danger?: boolean; disabled?: boolean; /** Makes the item one of a set of choices, marked when chosen. */ checked?: boolean; onSelect: () => void }[];", + "ApprovalCard": "/** A request: Botloft.chat.askCommand / askSite / suggestBot / askPlan(...).body. The card follows its toolName (command, website, suggested bot, plan) and its status (pending, allowed, denied, expired). Needs BotloftProvider around it. */ approval: any; /** A bot from Botloft.makeBot(...). */ bot: any;", + "BotRun": "/** One turn of the bot, in order: items from Botloft.chat.reply / tool / askCommand / askSite / suggestBot / askPlan / turn(bot, ...). Needs BotloftProvider around it. */ items: any[]; /** A bot from Botloft.makeBot(...). */ bot: any; /** The turn in progress: the reply being written (draft) or the working dots (working). Only a live run moves the avatar. */ live?: { draft: string; working: boolean };", + "InboundRow": "/** A message from Botloft.makeMessage({ from, to, body }). From \"owner\" it is your bubble on the right; from a bot or \"system\" it sits on the left with the sender's avatar. The sender must be among BotloftProvider bots. */ message: any; /** The bot whose chat this is: the message's recipient. */ /** A bot from Botloft.makeBot(...). */ bot: any;", + "CrewBots": "/** A crew from Botloft.makeCrew(...). */ crew: any; /** Its bots, from Botloft.makeBot({ crew, ... }); the Chief (chief: true) gets the crown. Pass the same ones to BotloftProvider. */ bots: any[]; onNewBot: () => void;", + "Sidebar": "/** No props: it lists the crews and bots given to BotloftProvider, with each bot's state and last activity, and the account at the bottom. Give it a fixed height. */ className?: never;", + "BotloftProvider": "/** Crews from Botloft.makeCrew(...). */ crews?: any[]; /** Bots from Botloft.makeBot(...). A new array updates them, e.g. to change a state over time. */ bots?: any[]; /** The bot whose chat is open: highlighted in the sidebar. */ selectedBotId?: string; /** Called when Allow or Deny is clicked on a request card. */ onAnswer?: (answer: { approvalId: string; allow: boolean; note?: string }) => void; children: React.ReactNode;" }, "overrides": { "Dialog": { @@ -44,6 +47,26 @@ "Toaster": { "cardMode": "single", "primaryStory": "ErrorNotice" + }, + "Sidebar": { + "cardMode": "single", + "viewport": "320x620" + }, + "BotRun": { + "cardMode": "column" + }, + "InboundRow": { + "cardMode": "column" + }, + "ApprovalCard": { + "cardMode": "column" + }, + "CrewBots": { + "cardMode": "column" + }, + "BotloftProvider": { + "cardMode": "single", + "viewport": "1000x560" } }, "readmeHeader": ".design-sync/conventions.md" diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md index 59a9d64b..11fc1a3b 100644 --- a/.design-sync/conventions.md +++ b/.design-sync/conventions.md @@ -5,7 +5,13 @@ mascot in its own color. The look is quiet and warm: neutral surfaces, one orang ## Setup -- No provider is needed. Link `styles.css` and load `_ds_bundle.js`; components are on `window.Botloft`. +- Link `styles.css` and load `_ds_bundle.js`; components are on `window.Botloft`. The basic pieces + (Button, fields, Dialog, Callout, BotAvatar, BotStateBadge...) need nothing around them. +- **App screens need `BotloftProvider`.** `Sidebar`, `CrewBots`, `BotRun`, `InboundRow` and + `ApprovalCard` are the app's real screens and read the app's data: wrap them in one + `` per design (not one per piece). Build the data + with `makeCrew`, `makeBot`, `makeMessage` and `chat.*` (below); never write a bot object by hand. + A new `bots` array updates every screen, which is how a design animates states. - **Theme:** light by default. Put `data-theme="dark"` on a wrapper (or ``) and every token switches. Give the page `bg-canvas text-ink` so the background follows the theme. - **Language:** the components' own words (state labels, Close, Cancel) follow the browser's @@ -34,8 +40,29 @@ use `style={{ height: 360 }}` for one-off sizes. The tokens behind them are CSS variables: `--canvas`, `--panel`, `--sunken`, `--line`, `--line-strong`, `--ink`, `--ink-soft`, `--muted`, `--accent`, `--ok`, `--work`, `--warn`, `--danger`. Use `var(--accent)` in inline styles; never hard-code hex for UI colors. -Bot colors are the exception: each bot has its own (`#FF7A59` Botloft orange, `#4FC382`, `#6EA6FF`, -`#F0B24A`, `#C084FC`), passed to `BotAvatar`. +Bot colors are the exception: each bot has its own from `Botloft.AVATAR_PALETTE` (`#FF7A59` +Botloft orange, `#5EC8FF`, `#A48BFF`, `#9BE564`, `#FFC857`, `#FF7EB6`, `#3DD9C1`, `#E8D5B0`); +`makeBot` hands them out in that order. + +## Data for the app screens + +```jsx +const { makeCrew, makeBot, makeMessage, chat } = window.Botloft; +const crew = makeCrew({ name: "Research" }); +const chief = makeBot({ crew, name: "Chief", chief: true, state: "idle" }); +const scout = makeBot({ crew, name: "Scout", state: "busy", role: "Finds sources.", + activity: { kind: "tool", text: "on-device AI news", tool: "WebSearch" } }); // sidebar line +``` + +- `state`: `idle`, `busy` (working), `needs_approval`, `launching`, `rate_limited`, `backoff`, + `auth_error`, `offline` (with `paused: true` it reads Paused). +- A bot's turn is a list for `BotRun`: `chat.reply(bot, markdown)`, `chat.tool(bot, { name: + "WebSearch" | "WebFetch" | "Read" | "Write" | "Bash", summary })`, `chat.askSite(bot, url)`, + `chat.askCommand(bot, command, { explanation })`, `chat.suggestBot(chief, { name, role, model, + instructions, reason })`, `chat.askPlan(bot, markdown)`, `chat.turn(bot, seconds)`. Each takes + `{ status: "allowed" | "denied" }` to show it answered. `ApprovalCard` takes one of them's `.body`. +- `makeMessage({ from: bot | "owner" | "system", to: bot, body, kind: "note" | "task" })` for + `InboundRow`: yours on the right, a bot's or Botloft's on the left. ## Components and their roles @@ -47,6 +74,11 @@ Bot colors are the exception: each bot has its own (`#FF7A59` Botloft orange, `# `Toaster` + `notifyError(what, error)` for failures away from a form. - Fields: `TextField`, `TextArea`, `SelectField` (labeled), `Select` (custom dropdown), `Choices` (2-6 options side by side), `Switch`, `Tabs`, `Menu`, `Details`, `SidePanel`. +- App screens: `Sidebar` (crews and bots, 288px wide, give it a height), `CrewBots` (a crew's bot + cards), `BotRun` (one turn of a bot in its chat, inside a `