From b14d71ef8e923d9c242d1f7093e1298eca42301d Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:52:09 -0300 Subject: [PATCH] chore(design): sync Botloft's interface to claude.ai/design A small package in .design-sync/pkg builds the app's own components, stylesheet, fonts and icons into a library the design-sync converter turns into a claude.ai/design design system: the base pieces (Button, Dialog, fields, Callout...) plus BotAvatar, BotStateBadge and ChiefBadge. Previews, the conventions read by the design agent and the notes for the next sync live next to the config. Build output and machine state are gitignored. --- .design-sync/NOTES.md | 58 ++++++++++ .design-sync/config.json | 50 +++++++++ .design-sync/conventions.md | 81 ++++++++++++++ .design-sync/pkg/build.mjs | 137 ++++++++++++++++++++++++ .design-sync/pkg/index.ts | 25 +++++ .design-sync/pkg/package.json | 9 ++ .design-sync/pkg/styles.css | 28 +++++ .design-sync/previews/BotAvatar.tsx | 71 ++++++++++++ .design-sync/previews/BotStateBadge.tsx | 59 ++++++++++ .design-sync/previews/Button.tsx | 66 ++++++++++++ .design-sync/previews/Callout.tsx | 50 +++++++++ .design-sync/previews/ChiefBadge.tsx | 39 +++++++ .design-sync/previews/Choices.tsx | 46 ++++++++ .design-sync/previews/Confirm.tsx | 24 +++++ .design-sync/previews/Details.tsx | 35 ++++++ .design-sync/previews/Dialog.tsx | 54 ++++++++++ .design-sync/previews/Menu.tsx | 26 +++++ .design-sync/previews/Select.tsx | 44 ++++++++ .design-sync/previews/SelectField.tsx | 34 ++++++ .design-sync/previews/SidePanel.tsx | 35 ++++++ .design-sync/previews/Switch.tsx | 46 ++++++++ .design-sync/previews/Tabs.tsx | 44 ++++++++ .design-sync/previews/TextArea.tsx | 35 ++++++ .design-sync/previews/TextField.tsx | 41 +++++++ .design-sync/previews/Toaster.tsx | 26 +++++ .gitignore | 9 ++ 26 files changed, 1172 insertions(+) create mode 100644 .design-sync/NOTES.md create mode 100644 .design-sync/config.json create mode 100644 .design-sync/conventions.md create mode 100644 .design-sync/pkg/build.mjs create mode 100644 .design-sync/pkg/index.ts create mode 100644 .design-sync/pkg/package.json create mode 100644 .design-sync/pkg/styles.css create mode 100644 .design-sync/previews/BotAvatar.tsx create mode 100644 .design-sync/previews/BotStateBadge.tsx create mode 100644 .design-sync/previews/Button.tsx create mode 100644 .design-sync/previews/Callout.tsx create mode 100644 .design-sync/previews/ChiefBadge.tsx create mode 100644 .design-sync/previews/Choices.tsx create mode 100644 .design-sync/previews/Confirm.tsx create mode 100644 .design-sync/previews/Details.tsx create mode 100644 .design-sync/previews/Dialog.tsx create mode 100644 .design-sync/previews/Menu.tsx create mode 100644 .design-sync/previews/Select.tsx create mode 100644 .design-sync/previews/SelectField.tsx create mode 100644 .design-sync/previews/SidePanel.tsx create mode 100644 .design-sync/previews/Switch.tsx create mode 100644 .design-sync/previews/Tabs.tsx create mode 100644 .design-sync/previews/TextArea.tsx create mode 100644 .design-sync/previews/TextField.tsx create mode 100644 .design-sync/previews/Toaster.tsx diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md new file mode 100644 index 00000000..e3a3880b --- /dev/null +++ b/.design-sync/NOTES.md @@ -0,0 +1,58 @@ +# Design sync notes + +Botloft is an app, not a component library, so the design system is a small package built from the +app's own source: `.design-sync/pkg/` (`index.ts` re-exports the components, `styles.css` adds a +layout vocabulary to the app's stylesheet, `build.mjs` builds `dist/` with the app's Vite, +Tailwind and TypeScript). Nothing there reimplements a component. + +## Running it + +- `node .design-sync/pkg/build.mjs` first (cfg.buildCmd), then the converter with + `--node-modules ./app/node_modules --entry ./.design-sync/pkg/dist/index.js`. +- `app/node_modules` must be installed (`pnpm install` in `app/`). +- `.design-sync/node_modules` is a junction to `.ds-sync/node_modules` (gitignored, recreate per + clone). Without it `@types/react` is not found from `.design-sync/pkg` and React props collapse + ([DTS_REACT]). On Windows: `node -e "require('fs').symlinkSync(require('path').resolve('.ds-sync/node_modules'), '.design-sync/node_modules', 'junction')"`. +- Render check and capture use `playwright@1.60.0` in `.ds-sync/` (matches the cached chromium-1223). + +## Fixes made in this repo's setup + +- Vite library mode inlines every asset, so the fonts would land in the CSS as base64 (1.1 MB, every + alphabet). `build.mjs` aliases `@fontsource/*.css` to an empty file and the Latin subsets ship as + files through cfg.extraFonts. +- `icons.gen.ts` is generated from the app's `lucide-react` imports and exported as `icons` + (a namespace, so the 94 icons do not become component cards). `lucide-react` is aliased to + `app/node_modules` because the file sits outside the app. +- `.d.ts` emit: tsc from `app/node_modules/typescript` (TypeScript 7; `bin/tsc` is not in its + exports map, so the path is built from `typescript/package.json`). +- Props that extend HTML attributes (`Button`, `TextField`, `TextArea`, `SelectField`) lose + `onClick`/`value`/`onChange` in extraction, and `BotStateBadge`, `ChiefBadge`, `Menu` reference + app types (`Bot`, `Crew`, `LucideIcon`). All six have hand-written bodies in cfg.dtsPropsFor. + Keep them in step with the app's source. +- Only Tailwind classes compiled into the CSS exist. The app's own set missed common layout + utilities (`max-w-sm`, arbitrary sizes), so `pkg/styles.css` adds a fixed vocabulary with + `@source inline(...)` and scans `.design-sync/previews`. +- Components speak the browser's language. Previews call `setLocaleChoice("en")` (exported from + the package) so cards are English on any machine. +- 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. + +## Known render warns + +- `[FONT_MISSING] "Cascadia Mono"`: a system fallback after IBM Plex Mono in `--font-mono`; Plex + 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. + +## Re-sync risks + +- `dtsPropsFor` bodies are copies of the app's props; a prop added in the app does not reach the + contract until the body is updated. +- The layout vocabulary in `pkg/styles.css` is a fixed list; the conventions header names its + 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. diff --git a/.design-sync/config.json b/.design-sync/config.json new file mode 100644 index 00000000..009509e0 --- /dev/null +++ b/.design-sync/config.json @@ -0,0 +1,50 @@ +{ + "projectId": "1f0e61c2-d3f4-4621-a902-66ef64e8cc5f", + "shape": "package", + "pkg": "@botloft/ui", + "globalName": "Botloft", + "buildCmd": "node .design-sync/pkg/build.mjs", + "entry": ".design-sync/pkg/dist/index.js", + "srcDir": "../../app/src", + "cssEntry": "dist/styles.css", + "extraFonts": [ + "../../app/node_modules/@fontsource/ibm-plex-sans/latin-400.css", + "../../app/node_modules/@fontsource/ibm-plex-sans/latin-500.css", + "../../app/node_modules/@fontsource/ibm-plex-sans/latin-600.css", + "../../app/node_modules/@fontsource/ibm-plex-mono/latin-400.css", + "../../app/node_modules/@fontsource/ibm-plex-mono/latin-500.css" + ], + "dtsPropsFor": { + "Button": "variant?: \"primary\" | \"secondary\" | \"ghost\" | \"danger\"; size?: \"sm\" | \"md\"; /** An icon from window.Botloft.icons, e.g. icons.Plus. */ icon?: React.ComponentType<{ size?: number; strokeWidth?: number }>; /** Shows only the icon; label becomes its accessible name and tooltip. */ label?: string; onClick?: React.MouseEventHandler; type?: \"button\" | \"submit\"; title?: string; className?: string; id?: string; style?: React.CSSProperties; disabled?: boolean; autoFocus?: boolean; children?: React.ReactNode;", + "TextField": "label: string; hint?: string; /** Shows used / max and warns past it. */ max?: number; value?: string; defaultValue?: string; placeholder?: string; onChange?: React.ChangeEventHandler; type?: string; name?: string; className?: string; id?: string; style?: React.CSSProperties; disabled?: boolean; autoFocus?: boolean; ", + "TextArea": "label: string; hint?: string; /** Shows used / max and warns past it. */ max?: number; value?: string; defaultValue?: string; placeholder?: string; rows?: number; onChange?: React.ChangeEventHandler; name?: string; className?: string; id?: string; style?: React.CSSProperties; disabled?: boolean; autoFocus?: boolean; ", + "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 + }, + "overrides": { + "Dialog": { + "cardMode": "single", + "viewport": "720x520" + }, + "Confirm": { + "cardMode": "single", + "viewport": "720x480" + }, + "BotAvatar": { + "cardMode": "column" + }, + "SidePanel": { + "cardMode": "column" + }, + "Toaster": { + "cardMode": "single", + "primaryStory": "ErrorNotice" + } + }, + "readmeHeader": ".design-sync/conventions.md" +} diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md new file mode 100644 index 00000000..59a9d64b --- /dev/null +++ b/.design-sync/conventions.md @@ -0,0 +1,81 @@ +# Building with Botloft + +Botloft is a desktop app where a crew of Claude Code bots works together. Each bot is a small flame +mascot in its own color. The look is quiet and warm: neutral surfaces, one orange accent, IBM Plex. + +## Setup + +- No provider is needed. Link `styles.css` and load `_ds_bundle.js`; components are on `window.Botloft`. +- **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 + language. Call `Botloft.setLocaleChoice("en")` once before rendering to pin English + (`"pt-BR"` and `"es"` also exist). +- **Icons:** `Botloft.icons.` holds the lucide icons the app uses (`Plus`, `Pause`, `Settings`, + `Ellipsis`, `X`, `Archive`, `Trash2`, `RotateCw`, `FileText`, `Folder`, `Users`, `Crown`...). Pass one + as `icon` to `Button` and `Menu`, or render it: ``. + +## Styling: Tailwind utilities on the design tokens + +Style your own layout with Tailwind classes. **Only classes compiled into `_ds_bundle.css` exist**: +the app's own plus a fixed layout set. Arbitrary values like `h-[360px]` or `w-[37rem]` do NOT exist; +use `style={{ height: 360 }}` for one-off sizes. + +| Family | Classes | +|---|---| +| Surfaces | `bg-canvas` (page), `bg-panel` (cards, dialogs), `bg-sunken` (wells, hover) | +| Text | `text-ink`, `text-ink-soft` (body), `text-muted` (hints), `text-accent` | +| Lines | `border border-line`, `border-line-strong` (controls), `divide-y divide-line` | +| State | `text-ok` ready, `text-work` working, `text-warn` waiting, `text-danger` error, `text-quiet` off; tints like `bg-accent/10`, `bg-danger/10` | +| Type | `text-xs` 12px, `text-sm` 13px, `text-base` 14px (body), `text-lg`...`text-2xl` for titles; `font-medium`, `font-semibold`; `font-mono` for paths and logs | +| Shape | `rounded-lg` (controls), `rounded-xl` (cards, callouts), `rounded-2xl` (dialogs), `rounded-full` (pills); `shadow-lift` for floating surfaces | +| Layout | `flex`, `grid grid-cols-1`...`grid-cols-12`, `gap-*`, `p-*`, `px-*`, `py-*`, `m*-*` on the 0.5-24 scale, `w-*`/`h-*`/`size-*` 0-96, `max-w-xs`...`max-w-7xl` | + +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`. + +## Components and their roles + +- `BotAvatar` is the mascot: `color`, `size`, `mood` (idle, working, waiting, tired, sleeping; + it animates), `framed` for Botloft itself on its black square. +- `BotStateBadge` shows a bot's state with icon and word; `ChiefBadge` marks a crew's lead bot. +- `Button` variants: `primary` (one per view), `secondary`, `ghost` (toolbars), `danger`. +- `Dialog`, `Confirm` are modal (fixed, covering the window). `Callout` for notices in a page, + `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`. + +Read `components///.prompt.md` for each one's props. + +## Voice + +Plain words for people who are not programmers. Name bots by their name ("Scout is waiting for +you"), never "the agent" or "Claude". Never say "daemon"; say "Botloft" or "in the background". + +## Example + +```jsx +const { BotAvatar, BotStateBadge, Button, Callout, icons, setLocaleChoice } = window.Botloft; +setLocaleChoice("en"); + +function CrewCard() { + return ( +
+
+ +
+

Scout

+ +
+
+ + It wants to open arxiv.org for the first time. + +
+ ); +} +``` diff --git a/.design-sync/pkg/build.mjs b/.design-sync/pkg/build.mjs new file mode 100644 index 00000000..fb4c34d0 --- /dev/null +++ b/.design-sync/pkg/build.mjs @@ -0,0 +1,137 @@ +// Builds @botloft/ui into dist/: an ES module with the components (React +// left external), the app's compiled stylesheet with its fonts, and .d.ts +// files for the converter. Uses the app's own Vite, Tailwind and TypeScript +// from app/node_modules, so the output matches what the app ships. +// +// node .design-sync/pkg/build.mjs + +import { execFileSync } from "node:child_process"; +import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const here = dirname(fileURLToPath(import.meta.url)); +const app = resolve(here, "../../app"); +const dist = join(here, "dist"); +const require = createRequire(join(app, "package.json")); +const load = async (name) => import(pathToFileURL(require.resolve(name)).href); + +rmSync(dist, { recursive: true, force: true }); + +// The icons the app uses, as `icons.` (Button takes one as `icon`). +// Read from the app's imports so the set follows the app. +const iconNames = new Set(); +const walk = (dir) => { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = join(dir, entry.name); + if (entry.isDirectory()) walk(path); + else if (/\.tsx?$/.test(entry.name) && !/\.test\./.test(entry.name)) { + for (const match of readFileSync(path, "utf8").matchAll(/import \{([^}]*)\} from "lucide-react"/g)) { + for (const name of match[1].split(",")) { + const bare = name.replace(/^\s*type\s+/, "").trim(); + if (/^[A-Z]\w*$/.test(bare) && !/^Lucide/.test(bare) && !/^\s*type\s/.test(name)) { + iconNames.add(bare); + } + } + } + } + } +}; +walk(join(app, "src")); +const iconsModule = `// Generated by build.mjs from the app's lucide-react imports.\nexport { ${[...iconNames].sort().join(", ")} } from "lucide-react";\n`; +writeFileSync(join(here, "icons.gen.ts"), iconsModule); + +const { build } = await load("vite"); +const tailwindcss = (await load("@tailwindcss/vite")).default; +const react = (await load("@vitejs/plugin-react")).default; + +// Library mode inlines every asset, so the fonts would land in the CSS as +// base64, every alphabet included. They are left out here and ship as +// files instead (cfg.extraFonts: the Latin subsets the app needs). +const noFonts = join(here, ".no-fonts.css"); +writeFileSync(noFonts, ""); + +// The app is the root so Tailwind scans app/src and emits every utility +// the app uses: the design agent can then use the same class vocabulary. +try { +await build({ + root: app, + configFile: false, + logLevel: "warn", + plugins: [react(), tailwindcss()], + resolve: { + alias: [ + { find: /^@fontsource\/.*\.css$/, replacement: noFonts }, + // icons.gen.ts sits outside the app, beyond the reach of its node_modules. + { find: /^lucide-react$/, replacement: join(app, "node_modules", "lucide-react") }, + ], + }, + build: { + target: "chrome120", + outDir: dist, + emptyOutDir: true, + minify: false, + lib: { + entry: join(here, "index.ts"), + formats: ["es"], + fileName: () => "index.js", + cssFileName: "styles", + }, + rollupOptions: { + external: (id) => /^react(-dom)?(\/|$)/.test(id), + }, + }, +}); +} finally { + rmSync(noFonts, { force: true }); +} + +// Types: declarations for the app files the entry re-exports, then an +// index.d.ts that is the entry with its paths pointed at them. +const entry = readFileSync(join(here, "index.ts"), "utf8"); +const sources = [...entry.matchAll(/from "\.\.\/\.\.\/app\/src\/([^"]+)"/g)].map( + (match) => + existsSync(join(app, `src/${match[1]}.tsx`)) ? `src/${match[1]}.tsx` : `src/${match[1]}/index.ts`, +); +const tsconfig = join(here, "tsconfig.types.json"); +writeFileSync( + tsconfig, + `${JSON.stringify( + { + extends: join(app, "tsconfig.json").replaceAll("\\", "/"), + compilerOptions: { + noEmit: false, + declaration: true, + emitDeclarationOnly: true, + rootDir: join(app, "src").replaceAll("\\", "/"), + outDir: join(dist, "types", "app").replaceAll("\\", "/"), + types: [], + typeRoots: [join(app, "node_modules", "@types").replaceAll("\\", "/")], + }, + files: sources.map((source) => join(app, source).replaceAll("\\", "/")), + include: [], + }, + null, + 2, + )}\n`, +); +try { + const tsc = join(dirname(require.resolve("typescript/package.json")), "bin", "tsc"); + execFileSync(process.execPath, [tsc, "-p", tsconfig], { + stdio: "inherit", + }); +} finally { + rmSync(tsconfig, { force: true }); +} +writeFileSync( + join(dist, "types", "index.d.ts"), + entry + .split("\n") + .filter((line) => !line.startsWith("import ") && !line.startsWith("//")) + .join("\n") + .replaceAll("../../app/src/", "./app/") + .trimStart(), +); +writeFileSync(join(dist, "types", "icons.gen.d.ts"), iconsModule); +console.log(`built ${dist}`); diff --git a/.design-sync/pkg/index.ts b/.design-sync/pkg/index.ts new file mode 100644 index 00000000..b5b75d2a --- /dev/null +++ b/.design-sync/pkg/index.ts @@ -0,0 +1,25 @@ +// The pieces of Botloft's interface that the design system shares with +// claude.ai/design: the app's own components, re-exported as they are, and +// its stylesheet (tokens, fonts and the Tailwind utilities the app uses). +// Built by build.mjs; nothing here reimplements a component. + +import "./styles.css"; + +export { BOTLOFT_COLOR, BotAvatar, ListAvatar, type Mood } from "../../app/src/features/bots/BotAvatar"; +export { BotStateBadge } from "../../app/src/features/bots/BotStateBadge"; +export { ChiefBadge } from "../../app/src/features/bots/ChiefBadge"; +export { Button, type ButtonVariant } from "../../app/src/ui/Button"; +export { Callout } from "../../app/src/ui/Callout"; +export { Choices } from "../../app/src/ui/Choices"; +export { Confirm } from "../../app/src/ui/Confirm"; +export { Details } from "../../app/src/ui/Details"; +export { Dialog } from "../../app/src/ui/Dialog"; +export { SelectField, TextArea, TextField } from "../../app/src/ui/Field"; +export { Menu, type MenuItem } from "../../app/src/ui/Menu"; +export { Select } from "../../app/src/ui/Select"; +export { SidePanel } from "../../app/src/ui/SidePanel"; +export { Switch } from "../../app/src/ui/Switch"; +export { type Tab, Tabs } from "../../app/src/ui/Tabs"; +export { notifyError, Toaster } from "../../app/src/ui/toast"; +export { setLocaleChoice } from "../../app/src/i18n"; +export * as icons from "./icons.gen"; diff --git a/.design-sync/pkg/package.json b/.design-sync/pkg/package.json new file mode 100644 index 00000000..60755dd0 --- /dev/null +++ b/.design-sync/pkg/package.json @@ -0,0 +1,9 @@ +{ + "name": "@botloft/ui", + "version": "0.8.0", + "private": true, + "description": "Botloft's interface pieces, built from the app's own source for claude.ai/design. Not published.", + "type": "module", + "module": "dist/index.js", + "types": "dist/types/index.d.ts" +} diff --git a/.design-sync/pkg/styles.css b/.design-sync/pkg/styles.css new file mode 100644 index 00000000..c3da96ea --- /dev/null +++ b/.design-sync/pkg/styles.css @@ -0,0 +1,28 @@ +/* The design system's stylesheet: the app's own (tokens, fonts, motion and + every utility the app uses) plus a fixed layout vocabulary, so a design + built with these components can count on common utilities that the app + itself happens not to use. */ +@import "../../app/src/index.css"; + +/* The previews' own layout. */ +@source "../previews"; + +/* Spacing, sizing and layout. */ +@source inline("{p,px,py,pt,pr,pb,pl,m,mx,my,mt,mr,mb,ml,gap,gap-x,gap-y,space-x,space-y}-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,12,14,16,20,24}"); +@source inline("{w,h,min-w,min-h,size}-{0,1,2,3,4,5,6,7,8,9,10,11,12,14,16,20,24,28,32,36,40,44,48,52,56,60,64,72,80,96,full,screen,auto,fit,min,max}"); +@source inline("{max-w,w}-{3xs,2xs,xs,sm,md,lg,xl,2xl,3xl,4xl,5xl,6xl,7xl,full,none,prose}"); +@source inline("{grid-cols,grid-rows,col-span,row-span}-{1,2,3,4,5,6,7,8,9,10,11,12,full}"); +@source inline("{flex,flex-row,flex-col,flex-wrap,flex-1,flex-none,grow,shrink-0,grid,block,inline-flex,inline-block,hidden,contents}"); +@source inline("{items,self}-{start,center,end,stretch,baseline}"); +@source inline("{justify,content}-{start,center,end,between,around,evenly}"); +@source inline("{relative,absolute,fixed,sticky,inset-0,top-0,right-0,bottom-0,left-0,overflow-hidden,overflow-auto,overflow-y-auto,truncate,whitespace-nowrap,break-words}"); + +/* Color, border and type on the design tokens. */ +@source inline("{bg,text,border,divide,ring,outline,fill,stroke}-{canvas,panel,sunken,line,line-strong,ink,ink-soft,muted,accent,ok,work,warn,danger,quiet,read,transparent,white,black}"); +@source inline("{bg,text,border}-{accent,ok,work,warn,danger}/{5,10,15,20,30,40,50,60}"); +@source inline("{border,border-t,border-r,border-b,border-l,border-0,border-2,divide-x,divide-y}"); +@source inline("rounded{,-none,-sm,-md,-lg,-xl,-2xl,-3xl,-full}"); +@source inline("{shadow-lift,shadow-sm,shadow-none}"); +@source inline("text-{xs,sm,base,lg,xl,2xl,3xl,4xl,5xl,left,center,right}"); +@source inline("{font-sans,font-mono,font-normal,font-medium,font-semibold,font-bold,tracking-tight,leading-tight,leading-snug,leading-relaxed,uppercase,italic,underline}"); +@source inline("{opacity-0,opacity-50,opacity-75,opacity-100,transition,transition-colors,duration-150,duration-200,duration-300}"); diff --git a/.design-sync/previews/BotAvatar.tsx b/.design-sync/previews/BotAvatar.tsx new file mode 100644 index 00000000..64427a2e --- /dev/null +++ b/.design-sync/previews/BotAvatar.tsx @@ -0,0 +1,71 @@ +import { BotAvatar, setLocaleChoice } from "@botloft/ui"; + +// The app follows the browser's language; the cards are in English. +setLocaleChoice("en"); + +const crew = [ + { name: "Chief", color: "#FF7A59" }, + { name: "Scout", color: "#4FC382" }, + { name: "Writer", color: "#6EA6FF" }, + { name: "Editor", color: "#F0B24A" }, + { name: "Ops", color: "#C084FC" }, +]; + +export function Colors() { + return ( +
+ {crew.map((bot) => ( +
+ + {bot.name} +
+ ))} +
+ ); +} + +export function Moods() { + const moods = ["idle", "working", "waiting", "tired", "sleeping"] as const; + return ( +
+ {moods.map((mood) => ( +
+ + {mood} +
+ ))} +
+ ); +} + +export function Sizes() { + return ( +
+ {[20, 28, 40, 64].map((size) => ( + + ))} +
+ ); +} + +export function Framed() { + return ( +
+ +
+

Botloft

+

The app's own mark, on the black square of its icon.

+
+
+ ); +} + +export function DarkTheme() { + return ( +
+ {crew.map((bot) => ( + + ))} +
+ ); +} diff --git a/.design-sync/previews/BotStateBadge.tsx b/.design-sync/previews/BotStateBadge.tsx new file mode 100644 index 00000000..ec8c01a4 --- /dev/null +++ b/.design-sync/previews/BotStateBadge.tsx @@ -0,0 +1,59 @@ +import { BotStateBadge, setLocaleChoice } from "@botloft/ui"; + +// The app follows the browser's language; the cards are in English. +setLocaleChoice("en"); + +type State = + | "offline" + | "launching" + | "idle" + | "busy" + | "needs_approval" + | "rate_limited" + | "auth_error" + | "backoff" + | "archived"; + +const states: State[] = [ + "idle", + "busy", + "needs_approval", + "launching", + "rate_limited", + "backoff", + "auth_error", + "offline", +]; + +export function States() { + return ( +
+ {states.map((state) => ( + + ))} + +
+ ); +} + +export function Compact() { + return ( +
+ + + + +
+ ); +} + +export function DarkTheme() { + return ( +
+ + + + +
+ ); +} diff --git a/.design-sync/previews/Button.tsx b/.design-sync/previews/Button.tsx new file mode 100644 index 00000000..f356fc71 --- /dev/null +++ b/.design-sync/previews/Button.tsx @@ -0,0 +1,66 @@ +import { Button, icons, setLocaleChoice } from "@botloft/ui"; + +// The app follows the browser's language; the cards are in English. +setLocaleChoice("en"); + +export function Variants() { + return ( +
+ + + + +
+ ); +} + +export function WithIcons() { + return ( +
+ + +
+ ); +} + +export function Sizes() { + return ( +
+ + +
+ ); +} + +export function Disabled() { + return ( +
+ + +
+ ); +} + +export function DarkTheme() { + return ( +
+ + + + +
+ ); +} diff --git a/.design-sync/previews/Callout.tsx b/.design-sync/previews/Callout.tsx new file mode 100644 index 00000000..921db5d1 --- /dev/null +++ b/.design-sync/previews/Callout.tsx @@ -0,0 +1,50 @@ +import { Button, Callout, setLocaleChoice } from "@botloft/ui"; + +// The app follows the browser's language; the cards are in English. +setLocaleChoice("en"); + +export function Info() { + return ( +
+ + They pick up where they left off after a restart, too. + +
+ ); +} + +export function Warn() { + return ( +
+ + It continues on its own at 4:10 PM. + +
+ ); +} + +export function DangerWithAction() { + return ( +
+ + Sign in + + } + > + Your bots stopped until you sign in again. + +
+ ); +} + +export function TitleOnly() { + return ( +
+ +
+ ); +} diff --git a/.design-sync/previews/ChiefBadge.tsx b/.design-sync/previews/ChiefBadge.tsx new file mode 100644 index 00000000..64885d1c --- /dev/null +++ b/.design-sync/previews/ChiefBadge.tsx @@ -0,0 +1,39 @@ +import { BotAvatar, BotStateBadge, ChiefBadge, setLocaleChoice } from "@botloft/ui"; + +// The app follows the browser's language; the cards are in English. +setLocaleChoice("en"); + +const crew = { name: "Research" }; + +export function Badge() { + return ( +
+ +
+ ); +} + +export function InBotHeader() { + return ( +
+ +
+
+ Chief + +
+ +
+
+ ); +} + +export function Compact() { + return ( +
+ + Chief + +
+ ); +} diff --git a/.design-sync/previews/Choices.tsx b/.design-sync/previews/Choices.tsx new file mode 100644 index 00000000..c887b7e2 --- /dev/null +++ b/.design-sync/previews/Choices.tsx @@ -0,0 +1,46 @@ +import { Choices, setLocaleChoice } from "@botloft/ui"; +import { useState } from "react"; + +// The app follows the browser's language; the cards are in English. +setLocaleChoice("en"); + +export function Model() { + const [model, setModel] = useState("sonnet"); + return ( +
+ Model + +
+ ); +} + +export function WaitTime() { + const [minutes, setMinutes] = useState(60); + return ( +
+ How long a request waits for you + +
+ ); +} diff --git a/.design-sync/previews/Confirm.tsx b/.design-sync/previews/Confirm.tsx new file mode 100644 index 00000000..d29d1c38 --- /dev/null +++ b/.design-sync/previews/Confirm.tsx @@ -0,0 +1,24 @@ +import { Confirm, setLocaleChoice } from "@botloft/ui"; + +// The app follows the browser's language; the cards are in English. +setLocaleChoice("en"); + +// Confirm opens a Dialog, which covers the window (position: fixed). The +// transform makes this box its containing block. +const stage = { position: "relative", height: 400, transform: "translateZ(0)" } as const; + +export function ArchiveBot() { + return ( +
+ {}} + onClose={() => {}} + > + Scout stops now and leaves the sidebar. Botloft keeps its conversation, and you can delete it + later in Settings > Archived. + +
+ ); +} diff --git a/.design-sync/previews/Details.tsx b/.design-sync/previews/Details.tsx new file mode 100644 index 00000000..cd8dec28 --- /dev/null +++ b/.design-sync/previews/Details.tsx @@ -0,0 +1,35 @@ +import { Callout, Details, setLocaleChoice } from "@botloft/ui"; + +// The app follows the browser's language; the cards are in English. +setLocaleChoice("en"); + +export function Closed() { + return ( +
+

Scout could not start.

+
claude exited with code 1 after 2.3 s
+
+ ); +} + +export function Open() { + return ( +
+

Scout could not start.

+
+ {"claude exited with code 1 after 2.3 s\nNo conversation found with session ID 4f2c…"} +
+
+ ); +} + +export function InCallout() { + return ( +
+ + Botloft keeps working on the current version. +
signature check failed for Botloft_0.8.1_x64-setup.exe
+
+
+ ); +} diff --git a/.design-sync/previews/Dialog.tsx b/.design-sync/previews/Dialog.tsx new file mode 100644 index 00000000..e7950726 --- /dev/null +++ b/.design-sync/previews/Dialog.tsx @@ -0,0 +1,54 @@ +import { Button, Dialog, setLocaleChoice, TextArea, TextField } from "@botloft/ui"; + +// The app follows the browser's language; the cards are in English. +setLocaleChoice("en"); + +// The dialog covers the window (position: fixed). The transform makes this +// box its containing block, so the card shows it at a real size. +const stage = { position: "relative", height: 460, transform: "translateZ(0)" } as const; + +export function EditBot() { + return ( +
+ {}} + footer={ + <> + + + + } + > +
+ +