From 05750c5bd71b9d24f4b805efec3f103b8bd00fdb Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:41:42 -0300 Subject: [PATCH] chore(design): the live browser in the design system; the sidebar runs full height BrowserPanel joins the claude.ai/design design system. BotloftProvider takes the bots' open browsers, opens them in the fake daemon before the app's store connects and paints them with the app's dev page drawer, so the panel shows tabs, the address bar, the page and the bot's cursor. The conventions now say how the app window is built: the Sidebar as the row's direct first child (wrapped, it shrank to its content in a design) and the BrowserPanel as its last. --- .design-sync/NOTES.md | 9 +++- .design-sync/config.json | 9 +++- .design-sync/conventions.md | 23 +++++++--- .design-sync/pkg/index.ts | 3 +- .design-sync/pkg/provider.tsx | 59 ++++++++++++++++++++++++- .design-sync/previews/BrowserPanel.tsx | 60 ++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 .design-sync/previews/BrowserPanel.tsx diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md index 0dce557f..263d18af 100644 --- a/.design-sync/NOTES.md +++ b/.design-sync/NOTES.md @@ -45,6 +45,11 @@ Tailwind and TypeScript). Nothing there reimplements a component. 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. +- `BrowserPanel`: `BotloftProvider` `browsers` opens each bot's browser in `FakeBrowser` and + paints it with the app's dev page drawer (`app/src/dev/seedPage.ts`). It must happen in the + provider's `useState` initializer, before the store connects: the fake answers the store's + first `browser.list` synchronously, so an answer taken before the browsers opened arrives after + their `browser.changed` and wins (the panel then says "Browser closed" over the page). - 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 @@ -71,7 +76,7 @@ Tailwind and TypeScript). Nothing there reimplements a component. - 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. - 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. + explainer video needs (sidebar, crew cards, a bot's turn, messages, requests, the live browser). + Not in it yet: the whole crew view, the composer, the screens panel, 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 03c3e62c..dc6c9279 100644 --- a/.design-sync/config.json +++ b/.design-sync/config.json @@ -26,8 +26,9 @@ "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;" + "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. Put it as the direct first child of the app window's flex row (fixed height); wrapped in anything else it shrinks to its content. */ 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[]; /** Open browsers for BrowserPanel: [{ bot, url, title, tabs?: [{ url, title }], action?: { kind: \"open\" | \"click\" | \"type\" | \"scroll\" | \"back\", x?, y?, label? } }]. A new action object moves the bot's cursor on the page again. */ browsers?: 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;", + "BrowserPanel": "/** The bot whose browser this is; its page comes from BotloftProvider browsers (it shows \"closed\" without one). Place it as the last child of the app window's flex row, right of the chat column: it is a side panel the owner can widen. */ bot: any; /** Counts up each time the owner asks to take the browser into their own hands. */ take?: number; onClose: () => void;" }, "overrides": { "Dialog": { @@ -67,6 +68,10 @@ "BotloftProvider": { "cardMode": "single", "viewport": "1000x560" + }, + "BrowserPanel": { + "cardMode": "single", + "viewport": "1440x620" } }, "readmeHeader": ".design-sync/conventions.md" diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md index 11fc1a3b..72e2cf20 100644 --- a/.design-sync/conventions.md +++ b/.design-sync/conventions.md @@ -74,11 +74,22 @@ const scout = makeBot({ crew, name: "Scout", state: "busy", role: "Finds sources `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 `