diff --git a/README.md b/README.md index 226df88..4f692e8 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Dark mode: add the `dark` class to `` (`document.documentElement.classList | [FindBar](docs/components/find-bar.md) | agentsview `SessionFindBar` | In-content find bar with N-of-M counter and match navigation | | [FitStages](docs/components/fit-stages.md) | new (TopBar collapse pattern) | Multi-breakpoint wrapper: renders the richest of N stage snippets that fits, by measurement | | [FlashBanner](docs/components/flash-banner.md) | consumer app | Transient toast banner driven by the `showFlash` store | -| [HarnessMark](docs/components/harness-mark.md) | agentsview docs site | Coding-agent harness wordmarks (Claude Code, Codex, Gemini, …), themeable and mono-capable | +| [HarnessIcon](docs/components/harness-icon.md) | agentsview docs site | Coding-agent harness glyphs (Claude, OpenAI, Gemini, …), square and monochrome | | [IconButton](docs/components/icon-button.md) | both (hand-rolled `.icon-btn`) | Square ghost button for a lone icon, required aria-label | | [KbdBadge](docs/components/kbd-badge.md) | consumer app | Keyboard-shortcut badge, hidden on touch devices | | [Markdown](docs/components/code-block.md) | both (consolidated) | Sanitized GFM rendering with highlighted fences and app-extension hooks | diff --git a/docs/checking.md b/docs/checking.md index 4fccac0..f6d1378 100644 --- a/docs/checking.md +++ b/docs/checking.md @@ -121,7 +121,7 @@ everything and aren't repeated per row. | Button | no rule — generic button markup has no reliable signature; state-recipe rules catch disabled and pressed drift | | ChipStack | no rule — generic badge markup has no reliable signature; found in review | | ProviderBrandMark, ProviderButton | no rule — provider images and action buttons overlap ordinary image/button markup; class or label heuristics would false-positive | -| HarnessMark | no rule — inline logos overlap ordinary ``/`` markup; found in review | +| HarnessIcon | no rule — inline logos overlap ordinary ``/`` markup; found in review | | Chip | `chip-label-override` (CSS reaching into the internal label span); hand-rolled badge markup itself has no reliable signature | | StatusDot | `hand-rolled-status-dot` | | Checkbox | `hand-rolled-checkbox` (bare native checkboxes and `accent-color` styling) | diff --git a/docs/components/harness-icon.md b/docs/components/harness-icon.md new file mode 100644 index 0000000..0bff7a3 --- /dev/null +++ b/docs/components/harness-icon.md @@ -0,0 +1,103 @@ +# HarnessIcon + +`HarnessIcon` renders the glyph of a coding-agent harness (Claude, Codex via +the OpenAI mark, Gemini, …) as a square, monochrome icon. The artwork is the +icon sprite from the AgentsView documentation landing page, bundled into the +component so an icon never costs an extra asset request. + +```svelte + + + + + +{#if isHarnessIconId(session.agent)} + +{/if} +{session.agentLabel} +``` + +## Props + +| Prop | Type | Default | Notes | +| ------------ | --------------- | ---------- | --------------------------------------------------------------------- | +| `harness` | `HarnessIconId` | required | Which glyph to draw | +| `label` | `string` | brand name | Accessible name (`role="img"`) | +| `decorative` | `boolean` | `false` | Hides the icon from assistive tech when nearby text names the harness | +| `size` | `number` | `16` | Width and height in px | +| `class` | `string` | — | Additional class on the icon root | + +The root is an inline-flex square sized by `--harness-icon-size`, so icons sit +on the text baseline like a lucide icon and inherit the surrounding `color`. A +muted row dims its icons along with its text. Every glyph paints in +`currentColor`; there are no brand colours to flatten. + +## Harnesses + +`HARNESS_ICONS` lists every id with its brand name and the agent products the +AgentsView docs draw with it, in the order the docs present them. +`isHarnessIconId(value)` narrows an arbitrary string to `HarnessIconId`, and +`harnessIconInfo(id)` returns the entry. Ids are brand glyphs, not agent +products: Codex draws `openai`, Cortex Code draws `snowflake`, and Positron +draws `posit`. Map product names on the consumer side. + +| Id | Brand | Agents | +| -------------- | ------------- | -------------------------------------- | +| `claude` | Claude | Claude Code, OpenClaude, Claude Cowork | +| `openai` | OpenAI | Codex | +| `gemini` | Gemini | Gemini | +| `copilot` | Copilot | Copilot | +| `vscode` | VS Code | VS Code Copilot | +| `visualstudio` | Visual Studio | Visual Studio Copilot | +| `cursor` | Cursor | Cursor, Cursor IDE | +| `warp` | Warp | Warp | +| `qwen` | Qwen | Qwen Code, QwenPaw | +| `deepseek` | DeepSeek | DeepSeek TUI, DeepSeek Harness | +| `mistral` | Mistral | Mistral Vibe | +| `zed` | Zed | Zed | +| `posit` | Posit | Positron, Posit Assistant | +| `sourcegraph` | Sourcegraph | Amp (historical) | +| `opencode` | OpenCode | OpenCode | +| `antigravity` | Antigravity | Antigravity | +| `kilo` | Kilo | Kilo, Kilo (legacy) | +| `kimi` | Kimi | Kimi, Kimi Work | +| `kiro` | Kiro | Kiro | +| `openhands` | OpenHands | OpenHands | +| `zencoder` | Zencoder | Zencoder | +| `snowflake` | Snowflake | Cortex Code | +| `goose` | Goose | Goose | +| `grok` | Grok | Grok | +| `hermes` | Hermes | Hermes | +| `xiaomi` | Xiaomi | MiMoCode | +| `openclaw` | OpenClaw | OpenClaw | +| `pi` | Pi | Pi | +| `poolside` | Poolside | Poolside | +| `qoder` | Qoder | Qoder | +| `roocode` | RooCode | RooCode | +| `trae` | Trae | Trae, TraeX | +| `aider` | Aider | Aider | +| `gptme` | gptme | gptme | +| `omnigent` | Omnigent | Omnigent | +| `zai` | Z.ai | ZCode | +| `commandcode` | Command Code | Command Code | +| `codebuff` | Codebuff | Codebuff, Freebuff | +| `forge` | Forge | Forge | +| `iflow` | iFlow | iFlow | +| `omp` | OhMyPi | OhMyPi | +| `prime` | Prime Agent | Prime Agent | +| `piebald` | Piebald | Piebald | +| `tencent` | Tencent | QClaw | +| `reasonix` | Reasonix | Reasonix | +| `shelley` | Shelley | Shelley | +| `workbuddy` | WorkBuddy | WorkBuddy | +| `windsurf` | Windsurf | Windsurf | +| `devin` | Devin | Devin CLI | + +## Provenance + +Sources are the `` definitions in the AgentsView documentation landing +page sprite, split into one 24×24 file each with the geometry unchanged. Each +logo remains a trademark of its owner. Use the icons to identify the harness a +session ran in, not to imply endorsement. diff --git a/docs/components/harness-mark.md b/docs/components/harness-mark.md deleted file mode 100644 index f61ec22..0000000 --- a/docs/components/harness-mark.md +++ /dev/null @@ -1,88 +0,0 @@ -# HarnessMark - -`HarnessMark` renders the wordmark of a coding-agent harness (Claude Code, -Codex, Gemini, …) at an icon-like height. The artwork was consolidated from -the agentsview docs landing page and is bundled into the component, so a mark -never costs an extra asset request. - -```svelte - - - - - -{#if isHarnessId(session.agent)} - -{/if} -{session.agentLabel} -``` - -## Props - -| Prop | Type | Default | Notes | -| ------------ | ----------- | ------------ | --------------------------------------------------------------------- | -| `harness` | `HarnessId` | required | Which mark to draw | -| `label` | `string` | display name | Accessible name (`role="img"`) | -| `decorative` | `boolean` | `false` | Hides the mark from assistive tech when nearby text names the harness | -| `size` | `number` | `20` | Root height in px; width follows the artwork's aspect ratio | -| `mono` | `boolean` | `false` | Flattens brand colours to `currentColor` | -| `class` | `string` | — | Additional class on the mark root | - -The root is an inline-flex span sized by `--harness-mark-size`, so marks sit -on the text baseline and inherit the surrounding `color`. A muted row dims its -marks along with its text. - -Two marks carry an optical correction (`HarnessInfo.opticalScale`): Gemini -and Forge are drawn taller than `size` and centred on the root box, mirroring -the agentsview docs matrix where they are shown at 30 and 28 px against a 22 px -baseline. The root box itself stays at `size`, so line boxes and row heights do -not change. The docs matrix also doubles Codex, which suits its wide grid cells -but overwhelms inline text; kit-ui leaves that mark at 1×. - -## Harnesses - -`HARNESSES` lists every id with its display name, in the order the agentsview -docs present them. `isHarnessId(value)` narrows an arbitrary string (a -session's agent name, say) to `HarnessId`, and `harnessInfo(id)` returns the -entry. - -| Id | Artwork | Colour | -| ---------------- | ---------------------------------------------------- | --------------------------------------- | -| `claude-code` | vector | orange asterisk, text in `currentColor` | -| `codex` | vector (the OpenAI wordmark, as agentsview ships it) | `currentColor` | -| `gemini` | vector | brand gradient | -| `copilot` | vector | `currentColor` | -| `cursor` | vector | `currentColor` | -| `opencode` | vector | `currentColor` at three opacities | -| `forge` | raster (PNG, transparent) | orange; `mono` has no effect | -| `openhands` | typeset | brand colour | -| `amp` | typeset | brand colour | -| `vscode-copilot` | typeset | brand colour | -| `positron` | typeset | brand colour | -| `openclaw` | typeset | brand colour | -| `pi` | typeset | brand colour | -| `iflow` | typeset | brand colour | -| `zencoder` | typeset | brand colour | -| `kimi` | typeset | brand colour | -| `warp` | typeset | brand colour | -| `hermes` | typeset | brand colour | -| `cortex-code` | typeset | brand colour | -| `kiro` | typeset | brand colour | - -Typeset marks are bold text in the inherited font at the mark height. The -agentsview docs ship these as `` SVGs in the system font; rendering them -as real text keeps them crisp and lets the font fall back cleanly. - -## Provenance and theming - -Sources are the agent wordmarks on the AgentsView documentation site's landing page. The vector marks -were adjusted for a themeable library: `width`/`height` attributes dropped so -CSS sizes them, `fill="white"` and Claude's cream text changed to -`currentColor`, OpenCode's three greys mapped to `currentColor` at 100 / 72 / -30 % opacity, and no-op clip paths and masks removed. Gemini keeps its gradient -and mask under namespaced ids. Brand geometry is unchanged. - -Each logo remains a trademark of its owner. Use the marks to identify the -harness a session ran in, not to imply endorsement. diff --git a/src/demo/App.svelte b/src/demo/App.svelte index e44960a..8ac0699 100644 --- a/src/demo/App.svelte +++ b/src/demo/App.svelte @@ -32,7 +32,7 @@ import FitStagesDemo from "./pages/FitStagesDemo.svelte"; import FlashBannerDemo from "./pages/FlashBannerDemo.svelte"; import FormFieldDemo from "./pages/FormFieldDemo.svelte"; - import HarnessMarkDemo from "./pages/HarnessMarkDemo.svelte"; + import HarnessIconDemo from "./pages/HarnessIconDemo.svelte"; import IconButtonDemo from "./pages/IconButtonDemo.svelte"; import ImagePreviewDemo from "./pages/ImagePreviewDemo.svelte"; import KbdBadgeDemo from "./pages/KbdBadgeDemo.svelte"; @@ -113,7 +113,7 @@ { id: "fit-stages", label: "FitStages", component: FitStagesDemo }, { id: "flash-banner", label: "FlashBanner", component: FlashBannerDemo }, { id: "form-field", label: "FormField", component: FormFieldDemo }, - { id: "harness-mark", label: "HarnessMark", component: HarnessMarkDemo }, + { id: "harness-icon", label: "HarnessIcon", component: HarnessIconDemo }, { id: "icon-button", label: "IconButton", component: IconButtonDemo }, { id: "image-preview", label: "ImagePreview", component: ImagePreviewDemo }, { id: "kbd-badge", label: "KbdBadge", component: KbdBadgeDemo }, diff --git a/src/demo/pages/HarnessIconDemo.svelte b/src/demo/pages/HarnessIconDemo.svelte new file mode 100644 index 0000000..c7a56af --- /dev/null +++ b/src/demo/pages/HarnessIconDemo.svelte @@ -0,0 +1,131 @@ + + + + + +{#each HARNESS_ICONS as { id } (id)} + +{/each}`} +> + + + +`} +> +
+ +
+
+ + + + + + +
+
+ + + Session ran in Cursor +`} +> +

+ Session ran in Cursor, then handed off to + Codex. +

+

+ · + · + · + +

+
+ + diff --git a/src/demo/pages/HarnessMarkDemo.svelte b/src/demo/pages/HarnessMarkDemo.svelte deleted file mode 100644 index bbf6e31..0000000 --- a/src/demo/pages/HarnessMarkDemo.svelte +++ /dev/null @@ -1,129 +0,0 @@ - - - - - -{#each HARNESSES as { id } (id)} - -{/each}`} -> - - - - -`} -> -
- - - - -
-
- - - - - - -
-
- - - Session ran in Cursor -`} -> -

- Session ran in Cursor, then handed off to - Codex. -

-

- · - · - · - -

-
- - diff --git a/src/lib/assets/harness-icons/aider.svg b/src/lib/assets/harness-icons/aider.svg new file mode 100644 index 0000000..c0f37fc --- /dev/null +++ b/src/lib/assets/harness-icons/aider.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/antigravity.svg b/src/lib/assets/harness-icons/antigravity.svg new file mode 100644 index 0000000..fb7f0d3 --- /dev/null +++ b/src/lib/assets/harness-icons/antigravity.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/claude.svg b/src/lib/assets/harness-icons/claude.svg new file mode 100644 index 0000000..30a26eb --- /dev/null +++ b/src/lib/assets/harness-icons/claude.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/codebuff.svg b/src/lib/assets/harness-icons/codebuff.svg new file mode 100644 index 0000000..259e642 --- /dev/null +++ b/src/lib/assets/harness-icons/codebuff.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/commandcode.svg b/src/lib/assets/harness-icons/commandcode.svg new file mode 100644 index 0000000..ada9018 --- /dev/null +++ b/src/lib/assets/harness-icons/commandcode.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/copilot.svg b/src/lib/assets/harness-icons/copilot.svg new file mode 100644 index 0000000..f3e9abc --- /dev/null +++ b/src/lib/assets/harness-icons/copilot.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/cursor.svg b/src/lib/assets/harness-icons/cursor.svg new file mode 100644 index 0000000..369a695 --- /dev/null +++ b/src/lib/assets/harness-icons/cursor.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/deepseek.svg b/src/lib/assets/harness-icons/deepseek.svg new file mode 100644 index 0000000..8b10bef --- /dev/null +++ b/src/lib/assets/harness-icons/deepseek.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/devin.svg b/src/lib/assets/harness-icons/devin.svg new file mode 100644 index 0000000..abe018f --- /dev/null +++ b/src/lib/assets/harness-icons/devin.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/forge.svg b/src/lib/assets/harness-icons/forge.svg new file mode 100644 index 0000000..876c183 --- /dev/null +++ b/src/lib/assets/harness-icons/forge.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/gemini.svg b/src/lib/assets/harness-icons/gemini.svg new file mode 100644 index 0000000..a9222e5 --- /dev/null +++ b/src/lib/assets/harness-icons/gemini.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/goose.svg b/src/lib/assets/harness-icons/goose.svg new file mode 100644 index 0000000..81ce04e --- /dev/null +++ b/src/lib/assets/harness-icons/goose.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/gptme.svg b/src/lib/assets/harness-icons/gptme.svg new file mode 100644 index 0000000..d0b985e --- /dev/null +++ b/src/lib/assets/harness-icons/gptme.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/grok.svg b/src/lib/assets/harness-icons/grok.svg new file mode 100644 index 0000000..cdc11b1 --- /dev/null +++ b/src/lib/assets/harness-icons/grok.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/hermes.svg b/src/lib/assets/harness-icons/hermes.svg new file mode 100644 index 0000000..2c42383 --- /dev/null +++ b/src/lib/assets/harness-icons/hermes.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/iflow.svg b/src/lib/assets/harness-icons/iflow.svg new file mode 100644 index 0000000..75febb1 --- /dev/null +++ b/src/lib/assets/harness-icons/iflow.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/kilo.svg b/src/lib/assets/harness-icons/kilo.svg new file mode 100644 index 0000000..732644b --- /dev/null +++ b/src/lib/assets/harness-icons/kilo.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/kimi.svg b/src/lib/assets/harness-icons/kimi.svg new file mode 100644 index 0000000..76f19f5 --- /dev/null +++ b/src/lib/assets/harness-icons/kimi.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/kiro.svg b/src/lib/assets/harness-icons/kiro.svg new file mode 100644 index 0000000..f2d97dd --- /dev/null +++ b/src/lib/assets/harness-icons/kiro.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/mistral.svg b/src/lib/assets/harness-icons/mistral.svg new file mode 100644 index 0000000..ab7690b --- /dev/null +++ b/src/lib/assets/harness-icons/mistral.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/omnigent.svg b/src/lib/assets/harness-icons/omnigent.svg new file mode 100644 index 0000000..b26348f --- /dev/null +++ b/src/lib/assets/harness-icons/omnigent.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/omp.svg b/src/lib/assets/harness-icons/omp.svg new file mode 100644 index 0000000..a64994e --- /dev/null +++ b/src/lib/assets/harness-icons/omp.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/openai.svg b/src/lib/assets/harness-icons/openai.svg new file mode 100644 index 0000000..ccafc8c --- /dev/null +++ b/src/lib/assets/harness-icons/openai.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/openclaw.svg b/src/lib/assets/harness-icons/openclaw.svg new file mode 100644 index 0000000..5e4815b --- /dev/null +++ b/src/lib/assets/harness-icons/openclaw.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/opencode.svg b/src/lib/assets/harness-icons/opencode.svg new file mode 100644 index 0000000..daae80a --- /dev/null +++ b/src/lib/assets/harness-icons/opencode.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/openhands.svg b/src/lib/assets/harness-icons/openhands.svg new file mode 100644 index 0000000..6c9514f --- /dev/null +++ b/src/lib/assets/harness-icons/openhands.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/pi.svg b/src/lib/assets/harness-icons/pi.svg new file mode 100644 index 0000000..fe68dc4 --- /dev/null +++ b/src/lib/assets/harness-icons/pi.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/piebald.svg b/src/lib/assets/harness-icons/piebald.svg new file mode 100644 index 0000000..83ec0ea --- /dev/null +++ b/src/lib/assets/harness-icons/piebald.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/poolside.svg b/src/lib/assets/harness-icons/poolside.svg new file mode 100644 index 0000000..05985f6 --- /dev/null +++ b/src/lib/assets/harness-icons/poolside.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/posit.svg b/src/lib/assets/harness-icons/posit.svg new file mode 100644 index 0000000..3844be6 --- /dev/null +++ b/src/lib/assets/harness-icons/posit.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/prime.svg b/src/lib/assets/harness-icons/prime.svg new file mode 100644 index 0000000..49ee7e2 --- /dev/null +++ b/src/lib/assets/harness-icons/prime.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/qoder.svg b/src/lib/assets/harness-icons/qoder.svg new file mode 100644 index 0000000..d82cc1b --- /dev/null +++ b/src/lib/assets/harness-icons/qoder.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/qwen.svg b/src/lib/assets/harness-icons/qwen.svg new file mode 100644 index 0000000..9b91539 --- /dev/null +++ b/src/lib/assets/harness-icons/qwen.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/reasonix.svg b/src/lib/assets/harness-icons/reasonix.svg new file mode 100644 index 0000000..772bc2c --- /dev/null +++ b/src/lib/assets/harness-icons/reasonix.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/roocode.svg b/src/lib/assets/harness-icons/roocode.svg new file mode 100644 index 0000000..cc7a1c1 --- /dev/null +++ b/src/lib/assets/harness-icons/roocode.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/shelley.svg b/src/lib/assets/harness-icons/shelley.svg new file mode 100644 index 0000000..f9f80f6 --- /dev/null +++ b/src/lib/assets/harness-icons/shelley.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/snowflake.svg b/src/lib/assets/harness-icons/snowflake.svg new file mode 100644 index 0000000..f55d66a --- /dev/null +++ b/src/lib/assets/harness-icons/snowflake.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/sourcegraph.svg b/src/lib/assets/harness-icons/sourcegraph.svg new file mode 100644 index 0000000..e17669d --- /dev/null +++ b/src/lib/assets/harness-icons/sourcegraph.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/tencent.svg b/src/lib/assets/harness-icons/tencent.svg new file mode 100644 index 0000000..fbf8327 --- /dev/null +++ b/src/lib/assets/harness-icons/tencent.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/trae.svg b/src/lib/assets/harness-icons/trae.svg new file mode 100644 index 0000000..fbea2b6 --- /dev/null +++ b/src/lib/assets/harness-icons/trae.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/visualstudio.svg b/src/lib/assets/harness-icons/visualstudio.svg new file mode 100644 index 0000000..45278cb --- /dev/null +++ b/src/lib/assets/harness-icons/visualstudio.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/vscode.svg b/src/lib/assets/harness-icons/vscode.svg new file mode 100644 index 0000000..9e2ef54 --- /dev/null +++ b/src/lib/assets/harness-icons/vscode.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/warp.svg b/src/lib/assets/harness-icons/warp.svg new file mode 100644 index 0000000..40ce883 --- /dev/null +++ b/src/lib/assets/harness-icons/warp.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/windsurf.svg b/src/lib/assets/harness-icons/windsurf.svg new file mode 100644 index 0000000..4f518ab --- /dev/null +++ b/src/lib/assets/harness-icons/windsurf.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/workbuddy.svg b/src/lib/assets/harness-icons/workbuddy.svg new file mode 100644 index 0000000..76ab763 --- /dev/null +++ b/src/lib/assets/harness-icons/workbuddy.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/xiaomi.svg b/src/lib/assets/harness-icons/xiaomi.svg new file mode 100644 index 0000000..59cf68f --- /dev/null +++ b/src/lib/assets/harness-icons/xiaomi.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/zai.svg b/src/lib/assets/harness-icons/zai.svg new file mode 100644 index 0000000..c575da1 --- /dev/null +++ b/src/lib/assets/harness-icons/zai.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/zed.svg b/src/lib/assets/harness-icons/zed.svg new file mode 100644 index 0000000..9d2cfc1 --- /dev/null +++ b/src/lib/assets/harness-icons/zed.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness-icons/zencoder.svg b/src/lib/assets/harness-icons/zencoder.svg new file mode 100644 index 0000000..2db5fa0 --- /dev/null +++ b/src/lib/assets/harness-icons/zencoder.svg @@ -0,0 +1 @@ + diff --git a/src/lib/assets/harness/claude-code.svg b/src/lib/assets/harness/claude-code.svg deleted file mode 100644 index 166692e..0000000 --- a/src/lib/assets/harness/claude-code.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/lib/assets/harness/codex.svg b/src/lib/assets/harness/codex.svg deleted file mode 100644 index 3f25c51..0000000 --- a/src/lib/assets/harness/codex.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/lib/assets/harness/copilot.svg b/src/lib/assets/harness/copilot.svg deleted file mode 100644 index 078815c..0000000 --- a/src/lib/assets/harness/copilot.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/lib/assets/harness/cursor.svg b/src/lib/assets/harness/cursor.svg deleted file mode 100644 index f18635a..0000000 --- a/src/lib/assets/harness/cursor.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/lib/assets/harness/forge.png b/src/lib/assets/harness/forge.png deleted file mode 100644 index 934144d..0000000 Binary files a/src/lib/assets/harness/forge.png and /dev/null differ diff --git a/src/lib/assets/harness/gemini.svg b/src/lib/assets/harness/gemini.svg deleted file mode 100644 index 06306a6..0000000 --- a/src/lib/assets/harness/gemini.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/lib/assets/harness/opencode.svg b/src/lib/assets/harness/opencode.svg deleted file mode 100644 index 5386e14..0000000 --- a/src/lib/assets/harness/opencode.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib/components/HarnessIcon.svelte b/src/lib/components/HarnessIcon.svelte new file mode 100644 index 0000000..417ca84 --- /dev/null +++ b/src/lib/components/HarnessIcon.svelte @@ -0,0 +1,165 @@ + + + + {@html svg} + + + diff --git a/src/lib/components/HarnessMark.svelte b/src/lib/components/HarnessMark.svelte deleted file mode 100644 index 7ff0181..0000000 --- a/src/lib/components/HarnessMark.svelte +++ /dev/null @@ -1,122 +0,0 @@ - - - - {#if svg} - {@html svg} - {:else if image} - - {:else} - - {info.label} - - {/if} - - - diff --git a/src/lib/components/harness-icon.ts b/src/lib/components/harness-icon.ts new file mode 100644 index 0000000..d362bef --- /dev/null +++ b/src/lib/components/harness-icon.ts @@ -0,0 +1,131 @@ +/** + * Coding-agent harnesses kit-ui ships a glyph for. Ids and artwork mirror the + * icon sprite on the AgentsView documentation landing page, which draws one + * 24×24 monochrome glyph per harness. + */ +export type HarnessIconId = + | "claude" + | "openai" + | "gemini" + | "copilot" + | "vscode" + | "visualstudio" + | "cursor" + | "warp" + | "qwen" + | "deepseek" + | "mistral" + | "zed" + | "posit" + | "sourcegraph" + | "opencode" + | "antigravity" + | "kilo" + | "kimi" + | "kiro" + | "openhands" + | "zencoder" + | "snowflake" + | "goose" + | "grok" + | "hermes" + | "xiaomi" + | "openclaw" + | "pi" + | "poolside" + | "qoder" + | "roocode" + | "trae" + | "aider" + | "gptme" + | "omnigent" + | "zai" + | "commandcode" + | "codebuff" + | "forge" + | "iflow" + | "omp" + | "prime" + | "piebald" + | "tencent" + | "reasonix" + | "shelley" + | "workbuddy" + | "windsurf" + | "devin"; + +export interface HarnessIconInfo { + id: HarnessIconId; + /** Brand name; also the default accessible name of the icon. */ + label: string; + /** Agent products the AgentsView docs draw with this glyph. */ + agents: readonly string[]; +} + +/** Every harness with a glyph, in the order the AgentsView docs present them. */ +export const HARNESS_ICONS: readonly HarnessIconInfo[] = [ + { id: "claude", label: "Claude", agents: ["Claude Code", "OpenClaude", "Claude Cowork"] }, + { id: "openai", label: "OpenAI", agents: ["Codex"] }, + { id: "gemini", label: "Gemini", agents: ["Gemini"] }, + { id: "copilot", label: "Copilot", agents: ["Copilot"] }, + { id: "vscode", label: "VS Code", agents: ["VS Code Copilot"] }, + { id: "visualstudio", label: "Visual Studio", agents: ["Visual Studio Copilot"] }, + { id: "cursor", label: "Cursor", agents: ["Cursor", "Cursor IDE"] }, + { id: "warp", label: "Warp", agents: ["Warp"] }, + { id: "qwen", label: "Qwen", agents: ["Qwen Code", "QwenPaw"] }, + { id: "deepseek", label: "DeepSeek", agents: ["DeepSeek TUI", "DeepSeek Harness"] }, + { id: "mistral", label: "Mistral", agents: ["Mistral Vibe"] }, + { id: "zed", label: "Zed", agents: ["Zed"] }, + { id: "posit", label: "Posit", agents: ["Positron", "Posit Assistant"] }, + { id: "sourcegraph", label: "Sourcegraph", agents: ["Amp (historical)"] }, + { id: "opencode", label: "OpenCode", agents: ["OpenCode"] }, + { id: "antigravity", label: "Antigravity", agents: ["Antigravity"] }, + { id: "kilo", label: "Kilo", agents: ["Kilo", "Kilo (legacy)"] }, + { id: "kimi", label: "Kimi", agents: ["Kimi", "Kimi Work"] }, + { id: "kiro", label: "Kiro", agents: ["Kiro"] }, + { id: "openhands", label: "OpenHands", agents: ["OpenHands"] }, + { id: "zencoder", label: "Zencoder", agents: ["Zencoder"] }, + { id: "snowflake", label: "Snowflake", agents: ["Cortex Code"] }, + { id: "goose", label: "Goose", agents: ["Goose"] }, + { id: "grok", label: "Grok", agents: ["Grok"] }, + { id: "hermes", label: "Hermes", agents: ["Hermes"] }, + { id: "xiaomi", label: "Xiaomi", agents: ["MiMoCode"] }, + { id: "openclaw", label: "OpenClaw", agents: ["OpenClaw"] }, + { id: "pi", label: "Pi", agents: ["Pi"] }, + { id: "poolside", label: "Poolside", agents: ["Poolside"] }, + { id: "qoder", label: "Qoder", agents: ["Qoder"] }, + { id: "roocode", label: "RooCode", agents: ["RooCode"] }, + { id: "trae", label: "Trae", agents: ["Trae", "TraeX"] }, + { id: "aider", label: "Aider", agents: ["Aider"] }, + { id: "gptme", label: "gptme", agents: ["gptme"] }, + { id: "omnigent", label: "Omnigent", agents: ["Omnigent"] }, + { id: "zai", label: "Z.ai", agents: ["ZCode"] }, + { id: "commandcode", label: "Command Code", agents: ["Command Code"] }, + { id: "codebuff", label: "Codebuff", agents: ["Codebuff", "Freebuff"] }, + { id: "forge", label: "Forge", agents: ["Forge"] }, + { id: "iflow", label: "iFlow", agents: ["iFlow"] }, + { id: "omp", label: "OhMyPi", agents: ["OhMyPi"] }, + { id: "prime", label: "Prime Agent", agents: ["Prime Agent"] }, + { id: "piebald", label: "Piebald", agents: ["Piebald"] }, + { id: "tencent", label: "Tencent", agents: ["QClaw"] }, + { id: "reasonix", label: "Reasonix", agents: ["Reasonix"] }, + { id: "shelley", label: "Shelley", agents: ["Shelley"] }, + { id: "workbuddy", label: "WorkBuddy", agents: ["WorkBuddy"] }, + { id: "windsurf", label: "Windsurf", agents: ["Windsurf"] }, + { id: "devin", label: "Devin", agents: ["Devin CLI"] }, +]; + +const BY_ID: ReadonlyMap = new Map( + HARNESS_ICONS.map((h) => [h.id, h]), +); + +/** Narrow an arbitrary string (a session's agent name, say) to a known harness glyph. */ +export function isHarnessIconId(value: string): value is HarnessIconId { + return BY_ID.has(value as HarnessIconId); +} + +export function harnessIconInfo(id: HarnessIconId): HarnessIconInfo { + const info = BY_ID.get(id); + if (!info) throw new Error(`unknown harness "${id}"`); + return info; +} diff --git a/src/lib/components/harness-mark.ts b/src/lib/components/harness-mark.ts deleted file mode 100644 index 2c0ea4d..0000000 --- a/src/lib/components/harness-mark.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Coding-agent harnesses kit-ui ships wordmarks for. Ids mirror the asset - * names on the AgentsView documentation site, where the artwork was - * consolidated from. - */ -export type HarnessId = - | "claude-code" - | "codex" - | "gemini" - | "copilot" - | "cursor" - | "opencode" - | "openhands" - | "amp" - | "vscode-copilot" - | "positron" - | "openclaw" - | "pi" - | "iflow" - | "zencoder" - | "kimi" - | "warp" - | "hermes" - | "cortex-code" - | "kiro" - | "forge"; - -export interface HarnessInfo { - id: HarnessId; - /** Display name; also the default accessible name of the mark. */ - label: string; - /** - * Brand colour of the typeset wordmark. Only harnesses without vector or - * raster artwork carry one — the others keep their colour in the asset. - */ - color?: string; - /** - * Optical correction so every mark reads at the same visual weight for a - * given `size`. Mirrors the agentsview docs matrix, which draws Gemini and - * Forge taller than the rest (30 / 28 px against a 22 px baseline). The - * docs also double Codex, but that only suits its wide grid cells; inline - * the OpenAI wordmark already sits level with the others at 1×. - */ - opticalScale?: number; -} - -/** Every harness with a mark, in the order the agentsview docs present them. */ -export const HARNESSES: readonly HarnessInfo[] = [ - { id: "claude-code", label: "Claude Code" }, - { id: "codex", label: "Codex" }, - { id: "gemini", label: "Gemini", opticalScale: 30 / 22 }, - { id: "copilot", label: "Copilot" }, - { id: "cursor", label: "Cursor" }, - { id: "opencode", label: "OpenCode" }, - { id: "openhands", label: "OpenHands", color: "#E9590C" }, - { id: "amp", label: "amp", color: "#E8604C" }, - { id: "vscode-copilot", label: "VS Code Copilot", color: "#0098B8" }, - { id: "positron", label: "Positron", color: "#447099" }, - { id: "openclaw", label: "OpenClaw", color: "#E68A2E" }, - { id: "pi", label: "Pi", color: "#6366f1" }, - { id: "iflow", label: "iFlow", color: "#0ea5e9" }, - { id: "zencoder", label: "Zencoder", color: "#f87171" }, - { id: "kimi", label: "Kimi", color: "#ec4899" }, - { id: "warp", label: "Warp", color: "#01A4A4" }, - { id: "hermes", label: "Hermes", color: "#7c3aed" }, - { id: "cortex-code", label: "Cortex Code", color: "#29B5E8" }, - { id: "kiro", label: "Kiro", color: "#8845f4" }, - { id: "forge", label: "Forge", opticalScale: 28 / 22 }, -]; - -const BY_ID: ReadonlyMap = new Map(HARNESSES.map((h) => [h.id, h])); - -/** Narrow an arbitrary string (a session's agent name, say) to a known harness. */ -export function isHarnessId(value: string): value is HarnessId { - return BY_ID.has(value as HarnessId); -} - -export function harnessInfo(id: HarnessId): HarnessInfo { - const info = BY_ID.get(id); - if (!info) throw new Error(`unknown harness "${id}"`); - return info; -} diff --git a/src/lib/index.ts b/src/lib/index.ts index 2e82076..68ed0dc 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -39,14 +39,14 @@ export type { FilterDropdownItem, FilterDropdownSection } from "./components/Fil export { default as FindBar } from "./components/FindBar.svelte"; export { default as FitStages } from "./components/FitStages.svelte"; export { default as FlashBanner } from "./components/FlashBanner.svelte"; -export { default as HarnessMark } from "./components/HarnessMark.svelte"; +export { default as HarnessIcon } from "./components/HarnessIcon.svelte"; export { - HARNESSES, - harnessInfo, - isHarnessId, - type HarnessId, - type HarnessInfo, -} from "./components/harness-mark.js"; + HARNESS_ICONS, + harnessIconInfo, + isHarnessIconId, + type HarnessIconId, + type HarnessIconInfo, +} from "./components/harness-icon.js"; export { default as IconButton } from "./components/IconButton.svelte"; export type { IconButtonSize, IconButtonTone } from "./components/IconButton.svelte"; export { default as ImagePreview } from "./components/ImagePreview.svelte"; diff --git a/tests/browser/harness-icon.spec.ts b/tests/browser/harness-icon.spec.ts new file mode 100644 index 0000000..e8ee490 --- /dev/null +++ b/tests/browser/harness-icon.spec.ts @@ -0,0 +1,55 @@ +import { expect, test } from "@playwright/test"; +import { gotoPage, setSlider, setTheme } from "./helpers"; + +const GALLERY = '[data-demo="harness-icon-gallery"]'; + +test("every registered harness renders a glyph", async ({ page }) => { + await gotoPage(page, "harness-icon"); + const gallery = page.locator(GALLERY); + + await expect(gallery.locator(".kit-harness-icon")).toHaveCount(49); + await expect(gallery.locator(".kit-harness-icon > svg")).toHaveCount(49); + + await expect(gallery.getByRole("img", { name: "Claude" })).toBeVisible(); + await expect(gallery.getByRole("img", { name: "OpenAI" })).toBeVisible(); + await expect(gallery.getByRole("img", { name: "Forge" })).toBeVisible(); +}); + +test("glyphs follow the current text colour in both themes", async ({ page }) => { + await gotoPage(page, "harness-icon"); + const openai = page.locator(`${GALLERY} .kit-harness-icon--openai`); + + for (const dark of [false, true]) { + await setTheme(page, { dark }); + const { fill, color } = await openai.evaluate((el) => ({ + fill: getComputedStyle(el.querySelector("path")!).fill, + color: getComputedStyle(el).color, + })); + expect(fill).toBe(color); + } +}); + +test("size sets a square box", async ({ page }) => { + await gotoPage(page, "harness-icon"); + const claude = page.locator('[data-demo="harness-icon-sized"] .kit-harness-icon--claude'); + const hermes = page.locator('[data-demo="harness-icon-sized"] .kit-harness-icon--hermes'); + + await setSlider(page.locator('[data-demo="harness-icon-size"]'), 32); + + for (const icon of [claude, hermes]) { + const box = (await icon.boundingBox())!; + expect(Math.round(box.height)).toBe(32); + expect(Math.round(box.width)).toBe(32); + } +}); + +test("decorative icons are hidden from assistive tech", async ({ page }) => { + await gotoPage(page, "harness-icon"); + const inline = page.locator('[data-demo="harness-icon-inline"] .kit-harness-icon'); + + await expect(inline).toHaveCount(2); + for (const icon of await inline.all()) { + await expect(icon).toHaveAttribute("aria-hidden", "true"); + await expect(icon).not.toHaveAttribute("role", "img"); + } +}); diff --git a/tests/browser/harness-mark.spec.ts b/tests/browser/harness-mark.spec.ts deleted file mode 100644 index c337241..0000000 --- a/tests/browser/harness-mark.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { gotoPage, setSlider, setTheme } from "./helpers"; - -const GALLERY = '[data-demo="harness-gallery"]'; - -test("every registered harness renders a mark", async ({ page }) => { - await gotoPage(page, "harness-mark"); - const gallery = page.locator(GALLERY); - - await expect(gallery.locator(".kit-harness-mark")).toHaveCount(20); - await expect(gallery.locator(".kit-harness-mark > svg")).toHaveCount(6); - await expect(gallery.locator(".kit-harness-mark > img")).toHaveCount(1); - await expect(gallery.locator(".kit-harness-mark__text")).toHaveCount(13); - - await expect(gallery.getByRole("img", { name: "Claude Code" })).toBeVisible(); - await expect(gallery.getByRole("img", { name: "Forge" }).locator("img")).toHaveAttribute( - "src", - /^data:image\/png/, - ); - await expect(gallery.getByRole("img", { name: "Hermes" })).toHaveText("Hermes"); -}); - -test("vector marks follow the current text colour in both themes", async ({ page }) => { - await gotoPage(page, "harness-mark"); - const codex = page.locator(`${GALLERY} .kit-harness-mark--codex`); - - for (const dark of [false, true]) { - await setTheme(page, { dark }); - const { fill, color } = await codex.evaluate((el) => ({ - fill: getComputedStyle(el.querySelector("path")!).fill, - color: getComputedStyle(el).color, - })); - expect(fill).toBe(color); - } -}); - -test("size sets the height and width follows the artwork", async ({ page }) => { - await gotoPage(page, "harness-mark"); - const codex = page.locator('[data-demo="harness-sized"] .kit-harness-mark--codex'); - const hermes = page.locator('[data-demo="harness-sized"] .kit-harness-mark--hermes'); - - await setSlider(page.locator('[data-demo="harness-size"]'), 32); - - for (const mark of [codex, hermes]) { - const box = (await mark.boundingBox())!; - expect(Math.round(box.height)).toBe(32); - expect(box.width).toBeGreaterThan(box.height); - } -}); - -test("mono flattens brand colours to currentColor", async ({ page }) => { - await gotoPage(page, "harness-mark"); - const claude = page.locator('[data-demo="harness-sized"] .kit-harness-mark--claude-code'); - const hermes = page.locator('[data-demo="harness-sized"] .kit-harness-mark--hermes'); - - const asteriskFill = (mark: typeof claude) => - mark.evaluate((el) => getComputedStyle(el.querySelector("path")!).fill); - const textColor = (mark: typeof hermes) => - mark.evaluate((el) => getComputedStyle(el.querySelector("span")!).color); - const rootColor = (mark: typeof claude) => mark.evaluate((el) => getComputedStyle(el).color); - - expect(await asteriskFill(claude)).not.toBe(await rootColor(claude)); - expect(await textColor(hermes)).not.toBe(await rootColor(hermes)); - - await page.locator('[data-demo="harness-mono"] input').check(); - - expect(await asteriskFill(claude)).toBe(await rootColor(claude)); - expect(await textColor(hermes)).toBe(await rootColor(hermes)); -}); - -test("decorative marks are hidden from assistive tech", async ({ page }) => { - await gotoPage(page, "harness-mark"); - const inline = page.locator('[data-demo="harness-inline"] .kit-harness-mark'); - - await expect(inline).toHaveCount(2); - for (const mark of await inline.all()) { - await expect(mark).toHaveAttribute("aria-hidden", "true"); - await expect(mark).not.toHaveAttribute("role", "img"); - } -});