From e97992458837e02c0b945d67d6e173920262f306 Mon Sep 17 00:00:00 2001 From: jessicat <8797119+jess-cat@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:28:38 +0100 Subject: [PATCH 001/357] Add GSV design-system migration showcase (web/) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the GSV design system (authored in Claude Design) into the web client as a standalone, isolated catalog page for review before applying it to the real GSV pages. Showcase only β€” the app entry and built-in packages are untouched. - Vendor design tokens (gsv-tokens.css) as the shared foundation - Self-host Departure Mono (SIL OFL 1.1; LICENSE included) - 22 atom components ported to Preact + CSS in web/src/app/components/ui/ - 16 dot-matrix icons as static SVGs, CSS-mask tintable, no runtime JS - Standalone /design.html catalog: sticky grouped nav + 24 stories - Implementation spec in engineering/design-system-implementation.md - PORTING_GUIDE.md documenting the .dc.html -> Preact conventions Co-Authored-By: Claude Opus 4.8 --- .gitignore | 3 + engineering/design-system-implementation.md | 165 +++++++++++++ web/design.html | 20 ++ web/public/fonts/DepartureMono-LICENSE.txt | 93 +++++++ web/public/fonts/DepartureMono-Regular.woff2 | Bin 0 -> 22496 bytes web/public/icons/INDEX.md | 44 ++++ web/public/icons/bookmark.svg | 1 + web/public/icons/chat.svg | 1 + web/public/icons/cog.svg | 1 + web/public/icons/computer.svg | 1 + web/public/icons/discord.svg | 1 + web/public/icons/folder.svg | 1 + web/public/icons/gmail.svg | 1 + web/public/icons/list.svg | 1 + web/public/icons/pencil.svg | 1 + web/public/icons/plus.svg | 1 + web/public/icons/rss.svg | 1 + web/public/icons/stars.svg | 1 + web/public/icons/tag.svg | 1 + web/public/icons/telegram.svg | 1 + web/public/icons/terminal.svg | 1 + web/public/icons/weblink.svg | 1 + web/public/img/agent-0.png | Bin 0 -> 1416 bytes web/public/img/agent-1.png | Bin 0 -> 1447 bytes web/public/img/agent-2.png | Bin 0 -> 1404 bytes web/src/app/components/ui/AddAction.css | 4 + web/src/app/components/ui/AddAction.tsx | 84 +++++++ web/src/app/components/ui/AgentImage.tsx | 49 ++++ web/src/app/components/ui/Avatar.tsx | 48 ++++ web/src/app/components/ui/Button.css | 151 ++++++++++++ web/src/app/components/ui/Button.tsx | 40 +++ web/src/app/components/ui/Checkbox.css | 144 +++++++++++ web/src/app/components/ui/Checkbox.tsx | 75 ++++++ web/src/app/components/ui/Counter.css | 138 +++++++++++ web/src/app/components/ui/Counter.tsx | 107 ++++++++ web/src/app/components/ui/IconButton.css | 24 ++ web/src/app/components/ui/IconButton.tsx | 89 +++++++ web/src/app/components/ui/ListRow.css | 4 + web/src/app/components/ui/ListRow.tsx | 104 ++++++++ web/src/app/components/ui/PORTING_GUIDE.md | 80 ++++++ web/src/app/components/ui/Progress.css | 64 +++++ web/src/app/components/ui/Progress.tsx | 50 ++++ web/src/app/components/ui/Radio.css | 152 ++++++++++++ web/src/app/components/ui/Radio.tsx | 94 +++++++ web/src/app/components/ui/SectionHeader.tsx | 50 ++++ web/src/app/components/ui/Segmented.css | 121 +++++++++ web/src/app/components/ui/Segmented.tsx | 108 ++++++++ web/src/app/components/ui/Select.css | 137 +++++++++++ web/src/app/components/ui/Select.tsx | 156 ++++++++++++ web/src/app/components/ui/Slider.css | 114 +++++++++ web/src/app/components/ui/Slider.tsx | 122 ++++++++++ web/src/app/components/ui/Spinner.css | 14 ++ web/src/app/components/ui/Spinner.tsx | 11 + web/src/app/components/ui/StatusDot.tsx | 36 +++ web/src/app/components/ui/Stepper.css | 100 ++++++++ web/src/app/components/ui/Stepper.tsx | 70 ++++++ web/src/app/components/ui/Tag.tsx | 79 ++++++ web/src/app/components/ui/TextArea.css | 144 +++++++++++ web/src/app/components/ui/TextArea.tsx | 106 ++++++++ web/src/app/components/ui/TextInput.css | 193 +++++++++++++++ web/src/app/components/ui/TextInput.tsx | 133 ++++++++++ web/src/app/components/ui/Toggle.css | 151 ++++++++++++ web/src/app/components/ui/Toggle.tsx | 71 ++++++ web/src/app/components/ui/Tooltip.css | 105 ++++++++ web/src/app/components/ui/Tooltip.tsx | 34 +++ web/src/design-system/catalog.css | 230 ++++++++++++++++++ web/src/design-system/catalog.tsx | 158 ++++++++++++ web/src/design-system/main.tsx | 12 + .../design-system/stories/AddAction.story.tsx | 26 ++ .../stories/AgentImage.story.tsx | 30 +++ .../design-system/stories/Avatar.story.tsx | 38 +++ .../design-system/stories/Button.story.tsx | 34 +++ .../design-system/stories/Checkbox.story.tsx | 42 ++++ .../design-system/stories/Counter.story.tsx | 47 ++++ .../stories/IconButton.story.tsx | 40 +++ web/src/design-system/stories/Icons.story.tsx | 97 ++++++++ .../design-system/stories/ListRow.story.tsx | 37 +++ .../design-system/stories/Progress.story.tsx | 45 ++++ web/src/design-system/stories/Radio.story.tsx | 40 +++ .../stories/SectionHeader.story.tsx | 32 +++ .../design-system/stories/Segmented.story.tsx | 42 ++++ .../design-system/stories/Select.story.tsx | 42 ++++ .../design-system/stories/Slider.story.tsx | 46 ++++ .../design-system/stories/Spinner.story.tsx | 24 ++ .../design-system/stories/StatusDot.story.tsx | 37 +++ .../design-system/stories/Stepper.story.tsx | 44 ++++ web/src/design-system/stories/Tag.story.tsx | 40 +++ .../design-system/stories/TextArea.story.tsx | 42 ++++ .../design-system/stories/TextInput.story.tsx | 43 ++++ .../design-system/stories/Toggle.story.tsx | 41 ++++ .../design-system/stories/Tokens.story.tsx | 98 ++++++++ .../design-system/stories/Tooltip.story.tsx | 23 ++ web/src/design-system/story.ts | 34 +++ web/src/styles/gsv-fonts.css | 18 ++ web/src/styles/gsv-tokens.css | 54 ++++ 95 files changed, 5433 insertions(+) create mode 100644 engineering/design-system-implementation.md create mode 100644 web/design.html create mode 100644 web/public/fonts/DepartureMono-LICENSE.txt create mode 100644 web/public/fonts/DepartureMono-Regular.woff2 create mode 100644 web/public/icons/INDEX.md create mode 100644 web/public/icons/bookmark.svg create mode 100644 web/public/icons/chat.svg create mode 100644 web/public/icons/cog.svg create mode 100644 web/public/icons/computer.svg create mode 100644 web/public/icons/discord.svg create mode 100644 web/public/icons/folder.svg create mode 100644 web/public/icons/gmail.svg create mode 100644 web/public/icons/list.svg create mode 100644 web/public/icons/pencil.svg create mode 100644 web/public/icons/plus.svg create mode 100644 web/public/icons/rss.svg create mode 100644 web/public/icons/stars.svg create mode 100644 web/public/icons/tag.svg create mode 100644 web/public/icons/telegram.svg create mode 100644 web/public/icons/terminal.svg create mode 100644 web/public/icons/weblink.svg create mode 100644 web/public/img/agent-0.png create mode 100644 web/public/img/agent-1.png create mode 100644 web/public/img/agent-2.png create mode 100644 web/src/app/components/ui/AddAction.css create mode 100644 web/src/app/components/ui/AddAction.tsx create mode 100644 web/src/app/components/ui/AgentImage.tsx create mode 100644 web/src/app/components/ui/Avatar.tsx create mode 100644 web/src/app/components/ui/Button.css create mode 100644 web/src/app/components/ui/Button.tsx create mode 100644 web/src/app/components/ui/Checkbox.css create mode 100644 web/src/app/components/ui/Checkbox.tsx create mode 100644 web/src/app/components/ui/Counter.css create mode 100644 web/src/app/components/ui/Counter.tsx create mode 100644 web/src/app/components/ui/IconButton.css create mode 100644 web/src/app/components/ui/IconButton.tsx create mode 100644 web/src/app/components/ui/ListRow.css create mode 100644 web/src/app/components/ui/ListRow.tsx create mode 100644 web/src/app/components/ui/PORTING_GUIDE.md create mode 100644 web/src/app/components/ui/Progress.css create mode 100644 web/src/app/components/ui/Progress.tsx create mode 100644 web/src/app/components/ui/Radio.css create mode 100644 web/src/app/components/ui/Radio.tsx create mode 100644 web/src/app/components/ui/SectionHeader.tsx create mode 100644 web/src/app/components/ui/Segmented.css create mode 100644 web/src/app/components/ui/Segmented.tsx create mode 100644 web/src/app/components/ui/Select.css create mode 100644 web/src/app/components/ui/Select.tsx create mode 100644 web/src/app/components/ui/Slider.css create mode 100644 web/src/app/components/ui/Slider.tsx create mode 100644 web/src/app/components/ui/Spinner.css create mode 100644 web/src/app/components/ui/Spinner.tsx create mode 100644 web/src/app/components/ui/StatusDot.tsx create mode 100644 web/src/app/components/ui/Stepper.css create mode 100644 web/src/app/components/ui/Stepper.tsx create mode 100644 web/src/app/components/ui/Tag.tsx create mode 100644 web/src/app/components/ui/TextArea.css create mode 100644 web/src/app/components/ui/TextArea.tsx create mode 100644 web/src/app/components/ui/TextInput.css create mode 100644 web/src/app/components/ui/TextInput.tsx create mode 100644 web/src/app/components/ui/Toggle.css create mode 100644 web/src/app/components/ui/Toggle.tsx create mode 100644 web/src/app/components/ui/Tooltip.css create mode 100644 web/src/app/components/ui/Tooltip.tsx create mode 100644 web/src/design-system/catalog.css create mode 100644 web/src/design-system/catalog.tsx create mode 100644 web/src/design-system/main.tsx create mode 100644 web/src/design-system/stories/AddAction.story.tsx create mode 100644 web/src/design-system/stories/AgentImage.story.tsx create mode 100644 web/src/design-system/stories/Avatar.story.tsx create mode 100644 web/src/design-system/stories/Button.story.tsx create mode 100644 web/src/design-system/stories/Checkbox.story.tsx create mode 100644 web/src/design-system/stories/Counter.story.tsx create mode 100644 web/src/design-system/stories/IconButton.story.tsx create mode 100644 web/src/design-system/stories/Icons.story.tsx create mode 100644 web/src/design-system/stories/ListRow.story.tsx create mode 100644 web/src/design-system/stories/Progress.story.tsx create mode 100644 web/src/design-system/stories/Radio.story.tsx create mode 100644 web/src/design-system/stories/SectionHeader.story.tsx create mode 100644 web/src/design-system/stories/Segmented.story.tsx create mode 100644 web/src/design-system/stories/Select.story.tsx create mode 100644 web/src/design-system/stories/Slider.story.tsx create mode 100644 web/src/design-system/stories/Spinner.story.tsx create mode 100644 web/src/design-system/stories/StatusDot.story.tsx create mode 100644 web/src/design-system/stories/Stepper.story.tsx create mode 100644 web/src/design-system/stories/Tag.story.tsx create mode 100644 web/src/design-system/stories/TextArea.story.tsx create mode 100644 web/src/design-system/stories/TextInput.story.tsx create mode 100644 web/src/design-system/stories/Toggle.story.tsx create mode 100644 web/src/design-system/stories/Tokens.story.tsx create mode 100644 web/src/design-system/stories/Tooltip.story.tsx create mode 100644 web/src/design-system/story.ts create mode 100644 web/src/styles/gsv-fonts.css create mode 100644 web/src/styles/gsv-tokens.css diff --git a/.gitignore b/.gitignore index 8b564e95..01023c29 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ docs/.vitepress/cache/ # Alchemy state .alchemy/ + +# OS junk +.DS_Store diff --git a/engineering/design-system-implementation.md b/engineering/design-system-implementation.md new file mode 100644 index 00000000..f2bd3bd8 --- /dev/null +++ b/engineering/design-system-implementation.md @@ -0,0 +1,165 @@ +# Design System Implementation Spec + +> Status: **proposal / for review.** This describes *how* we bring the GSV design +> system (authored in Claude Design) into the GSV codebase. It is scoped to the +> **Web UI (`web/`) only** β€” built-in packages are deliberately left untouched +> (see Β§2). The work is **UI-only**; anything that would reach beyond UI is called +> out under **🚩 STRUCTURAL FLAG**. Nothing here is implemented yet. + +## 1. Goal & constraints + +We are implementing the GSV design system in the **web client**. Ground rules agreed with the design owner: + +- **Source of truth:** the **GSV Live** prototype + the design-system atom set + `gsv-tokens.css`, all authored in Claude Design. +- **Two visual worlds, one universe:** **The Desktop** (deep-space view, periwinkle/cyan, restrained generative art) and **The Control Panel** (dark-purple console, monospace, status dots, hand-drawn/dot-matrix glyphs). +- **Incremental, small chunks.** First chunk = the **desktop shell**. Other web surfaces follow one at a time. +- **One clean design system, shared across all of `web/`** β€” and **only** `web/`. Tokens and ported components live inside the web client and are imported directly. **No sync scripts, no cross-package sharing, no ad-hoc tooling.** +- **Built-in packages are frozen.** We do not restyle them. See Β§2. +- **Atoms (components) are ported on demand**, into a shared `components/` folder in the web UI. +- **UI-only.** No behavioral, data, protocol, or runtime changes. Anything structural is flagged, not silently done. + +## 2. Migration strategy: web absorbs, packages retire + +The clean path that avoids all cross-package plumbing: **the new web UI implements the functionality itself**, rather than sharing a design system into the built-in packages. + +- **During migration:** built-in packages (`builtin-packages/chat`, `files`, `wiki`, `shell`, `gsv`) are **left exactly as they are.** We do not touch their CSS, tokens, or components. +- **As the web UI grows:** we pull as much of that functionality into the web client as possible, styled natively with the design system. +- **When a package's functionality is covered:** disable it during testing, then eventually **remove it from the built-ins.** + +This means the entire design-system effort lives inside `web/`, which is a plain Vite app β€” so it's pure UI work with no platform/assembler involvement. + +### 🚩 STRUCTURAL FLAG β€” this is a product/architecture direction, not just a restyle *(owner-confirmed; tractable)* + +"Web UI implements the same functionality" is a **separate, larger workstream** than applying the design system β€” re-creating chat / files / wiki / shell / console behavior in the web client touches data flow and feature scope, well beyond CSS. **Confirmed by the design owner**, with the path de-risked: + +- **Most logic is reusable from the existing packages.** The package code is the behavioral blueprint β€” we adapt it rather than reinventing it. +- **Syscalls are already usable from the Web UI.** The web client's `GSVClient` / `GatewayProvider` (`web/src/app/services/gateway/`) already exposes the kernel syscall surface, so replicating a package's backend interactions in web is mostly wiring, not new plumbing. +- **The design prototype + packages together are the spec** for both look and behavior. + +So this track is real but not high-risk. Two guardrails remain: + +1. **Keep the tracks distinct.** Styling a screen β‰  porting a package's full behavior into web. The **design-system restyle itself stays UI-only**; the functionality migration rides alongside it. +2. **Track the dependency explicitly.** Maintain a "what functionality must web absorb before package X can be disabled/removed" checklist, so a package isn't retired before web covers it. + +## 3. The format gap (why this is a *port*, not a *copy-paste*) + +The design system is authored in **Claude Design's `.dc.html` runtime**: ``, ``, `{{ }}` value-holes, a `data-props` JSON schema, `support.js`, and `DCLogic` classes. **None of that runtime exists in GSV.** GSV's web UI is **Preact + plain CSS with custom properties** β€” no Tailwind, no CSS-in-JS, no component library. + +So "implement the design system" means **translate each layer into the web client's idioms**: + +| Design-system layer | Source artifact | Web target | Effort | +|---|---|---|---| +| **Tokens** | `gsv-tokens.css` (`:root` custom properties) | CSS custom properties β€” near-verbatim | Mechanical | +| **Atoms** | `Button.dc.html`, `Select.dc.html`, … (~37) | Preact components + CSS in `web/src/.../components/`, ported **on demand** | Per-component | +| **Page templates / screens** | `GSV Live.dc.html`, `Node desktop.dc.html`, … | Restyle the real web screens against ported tokens/atoms | Larger | +| **Icons / illustrations** | `gsv-dot-icons.js`, ASCII `
` planets, raster portraits | Static **SVG/CSS** assets β€” see Β§7 | Mixed |
+
+The atom files are still valuable as **exact visual specs**: each `.dc.html` carries literal hex, paddings, letter-spacing, font sizes, and per-state CSS (hover/active/focus/disabled). Porting = transcribing those into a Preact component, not redesigning.
+
+## 4. The token layer (foundation, ships first)
+
+`gsv-tokens.css` is ~50 custom properties on `:root` β€” surfaces, borders/rules, interaction states, text/accent, status, and action accents. Every other piece references these via `var(--token)`.
+
+**Plan:** vendor `gsv-tokens.css` into the web client (e.g. `web/src/styles/gsv-tokens.css`) as the single foundation file, imported once in `web/src/app/main.tsx`:
+
+```ts
+import "./styles/gsv-tokens.css"; // global design tokens β€” the shared asset for all of web/
+import "../styles.css";
+// …feature CSS
+```
+
+Because `web/` is a single Vite app, this *is* the "global asset shared across the whole UI" β€” every component and stylesheet under `web/` sees the same `var(--token)` values with zero plumbing. Values come straight from `gsv-tokens.css` and are not re-derived.
+
+### 🚩 STRUCTURAL FLAG β€” token name collisions inside the web client
+
+The design tokens use **generic, unprefixed names** that already exist with **different values** in `web/src/styles.css` (scoped to `.desktop-shell`):
+
+| Token | Design system value | Web shell value today |
+|---|---|---|
+| `--accent` | `#b3aeff` | `#8ccdf8` |
+| `--danger` / `--warn` | `#a8324a` / `#e0a64c` | `#ff6f6f` / `#f0ca6f` |
+| `--text*`, surfaces | dark-purple set | navy set |
+
+This is now contained entirely within `web/` (packages are out of scope). Handling β€” **needs a decision (Q1)**:
+
+- **Option A β€” unprefixed, migrate the shell wholesale.** Keep names as authored. The shell's current tokens are scoped to `.desktop-shell` (more specific than `:root`), so old values *shadow* the new ones until each rule is migrated β€” a single chunk converts the shell cleanly. Matches the design files 1:1.
+- **Option B β€” namespace as `--gsv-*`.** Old and new coexist with zero collision risk, allowing piecemeal migration; costs a one-time rename and diverges from the design files' literal `var()` names.
+
+**Recommendation:** Option A β€” we're converting the shell wholesale anyway. Pure UI; "structural" only in CSS blast radius.
+
+## 5. Atoms β€” on-demand Preact port into a shared `components/` folder
+
+No `gsv-ui` package. Atoms are ported **only when a screen being styled needs them**, into a tidy shared folder in the web UI that any web component can import:
+
+```
+web/src/app/components/ui/   # shared design-system primitives (Button, Select, Tag, StatusDot, …)
+```
+
+Each port:
+
+1. Read the atom's `.dc.html` (props in the `data-props` JSON at the bottom; states/sizes in the `` CSS).
+2. Implement a Preact component whose props mirror the `data-props` schema.
+3. Transcribe the per-state CSS into a stylesheet (literal values as authored; `var(--token)` where the design file references tokens).
+4. Preserve handler semantics β€” `onClick`/`onChange` pass-through, `stopPropagation` where present.
+
+This is the existing `web/` convention (feature-organized Preact + CSS), just with a shared `ui/` folder added for cross-feature primitives. No new infrastructure.
+
+## 6. Typography β€” self-hosted
+
+Design fonts: **Departure Mono** (primary, console/HUD) + **JetBrains Mono** (mono fallback). Today the web client loads **Space Grotesk + IBM Plex Mono** from Google Fonts in `web/index.html`.
+
+**Plan (per the design owner):** **self-host** the fonts.
+
+- Add the `Departure Mono` woff2 (and JetBrains Mono if we adopt it) under `web/public/fonts/`.
+- Declare `@font-face` in the token/base CSS pointing at the local files; drop the Google Fonts `` for the replaced families from `web/index.html`.
+- Confirm Departure Mono's license permits redistribution before committing the file. *(This is the one prerequisite to verify β€” Q2.)*
+
+This is an asset + ``/`@font-face` change only.
+
+## 7. Iconography β€” static SVG/CSS, never JS
+
+Per the design owner, **icons must be images/CSS, not a JS runtime.** The prototype's `gsv-dot-icons.js` (which sets `window.GSV_DOT` and draws icons at runtime) is **not** ported as-is.
+
+**Plan:**
+
+- **Pre-render each needed dot-matrix icon to a static `.svg` file** (the `[col,row]` cell data in the export is the spec), committed under `web/public/icons/` (or `web/src/assets/icons/`). These are plain committed assets β€” **not a runtime script and not a sync pipeline.** Generated once from the design data, then they live as files.
+- **Render via image/CSS:** use `` for fixed-color marks, or CSS `mask-image` with `background-color: var(--token)` where an icon must take a theme color. The mask approach gives full token-driven tinting with **no JS**.
+- **Raster agent portraits** (`img/agent-*.png`) are plain images β€” copy into `web/public/`.
+- **ASCII planet illustrations** (`AsciiPlanet.dc.html`) are procedurally JS-painted `
` canvases. Per **D4**, these become **static art** (rendered image/CSS), accepting the loss of the procedural animation β€” no runtime canvas component. Treat as their own later chunk; do not block the shell on them.
+
+## 8. What stays untouched (explicit non-goals)
+
+- **Built-in packages** β€” frozen; not restyled (Β§2).
+- No protocol/syscall, gateway, assembler, CLI, adapter, or device changes.
+- No changes to web data flow, state management, routing, window manager, or providers β€” restyle only.
+- No new dependencies beyond self-hosted font/icon/image assets.
+- Behavior, copy, and interactions stay as-is unless the design explicitly changes them (called out per chunk).
+
+## 9. Decisions (locked 2026-06-22)
+
+| # | Decision |
+|---|---|
+| **D1** | **Token naming: Option A** β€” keep the design tokens unprefixed and migrate the desktop shell wholesale. The shell's `.desktop-shell`-scoped tokens shadow the new `:root` values until each rule is converted. |
+| **D2** | **Self-host fonts.** Departure Mono is **SIL Open Font License 1.1** (Helena Zhang) β€” *corrected from an earlier note that said MIT*. OFL explicitly permits bundling/embedding/redistribution with software, so self-hosting is fine. Vendored the v1.500 woff2 + LICENSE from the canonical GitHub release (not the prototype's third-party CDN re-host) into `web/public/fonts/`. |
+| **D3** | **Chrome first.** Chunk 1 covers the desktop *chrome* (top bar, dock/launcher, windows, command palette, session/login). The **space-view node graph is its own later chunk.** |
+| **D4** | **ASCII planet illustrations β†’ static art.** Render to static image/CSS; no runtime canvas component. |
+
+*(Package questions are resolved by Β§2: packages are left alone β€” no cross-package token delivery, no shared package, no assembler change.)*
+
+## 10. Proposed phasing (each chunk = its own PR, reviewed against GSV Live)
+
+1. **Tokens + fonts + desktop chrome.** Vendor `gsv-tokens.css`, self-host fonts, and restyle the desktop shell chrome (top bar, dock/launcher, windows, command palette, session/login screens) to the design vocabulary. Port only the atoms these screens use into `components/ui/`.
+2. **Desktop space view.** Node network, orbital links, starfield, responsive collapse-to-left-rail, persistent chat panel + resize. (Largest; isolate per Q3.)
+3. **Remaining web surfaces** + absorbing built-in-package functionality into web (per Β§2), one surface at a time, retiring packages as their functionality lands.
+4. **Illustrations/icons** as needed by the above (static icon SVGs early; ASCII planets per Q4).
+
+---
+
+### Appendix β€” key code references
+
+- Web UI entry & CSS imports: `web/src/app/main.tsx`, `web/src/styles.css`
+- Web fonts (to self-host): `web/index.html` (`` to Google Fonts) β†’ `web/public/fonts/`
+- Desktop feature components to restyle: `web/src/app/features/desktop/*`
+- Session/login screens: `web/src/app/features/session/*`
+- Package frontend conventions (for reference; packages are frozen): `engineering/package-frontend-architecture.md`
+- Design tokens (source of truth): `gsv-tokens.css` (from the Claude Design export)
diff --git a/web/design.html b/web/design.html
new file mode 100644
index 00000000..19f7306d
--- /dev/null
+++ b/web/design.html
@@ -0,0 +1,20 @@
+
+
+  
+    
+    
+    
+    GSV Β· Design System
+    
+    
+    
+    
+  
+  
+    
+ + + diff --git a/web/public/fonts/DepartureMono-LICENSE.txt b/web/public/fonts/DepartureMono-LICENSE.txt new file mode 100644 index 00000000..de524761 --- /dev/null +++ b/web/public/fonts/DepartureMono-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2022–2024 Helena Zhang (helenazhang.com). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/web/public/fonts/DepartureMono-Regular.woff2 b/web/public/fonts/DepartureMono-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..b4a23dc1dad1c934005d0862e62c0294636afb08 GIT binary patch literal 22496 zcmV)CK*GOwPew9NR8&s@09W7u3;+NC0ZIS>09TLz0RR9100000000000000000000 z0000DgW3=pg<>0&ZXA{r24Dc{ZUHs|BmCrObD{ZH=hZA*&!MH+zI zVgQwY&Xv7oaDi>piFnp|L%N=YblB`*3>zna;rXqz|NsC0|NsA$%a_OIlC<^Z{4FiW z5Mlt1+f*Rat075p$4JQ#bNd8rcH26dxfJf!f+<%oEMz zWCgC9uTrU$ItyD;yOMObCg-bfw%u*mK7!m2%)d)VRdOn3#+A|-Mt04Ut=LICW2s;* zA|kjunV{LHI~RKYoW#P3Pt@LQT4+s0e=T}E+&xIyQo1A03H+%fHu;|>Y$AJil-e?cEWRiLUXmj*mfu(sZxSrLgc z3UQ4_M8v{EL_{UqAloiX%(DwK`pOJ?kg?Z$B~@2Flv6;G6oRIB0!M0@T;_;4eoB$- zF93o0*DXJR1UWUCyY+QghEC^#Tf~46#1cdapi}R6(v789DI^^mXcHEc%m33a``
S&Y$B6bQNVy_}t=+?^%B}Y(` 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 v7oUo5C)>^E>&CZV$&|CqZZE~UofZ=oq` +- Token tinting: `mask-image: url(/icons/folder.svg); -webkit-mask-image: url(/icons/folder.svg); background-color: var(--accent-bright)` (set `mask-size`/`mask-repeat` as needed). +- The `currentColor` fill also lets you inline the SVG and tint it with `color`. + +## object key β†’ icon name β†’ filename + +| object key | icon name | filename | +| ------------ | --------- | -------------- | +| machines | computer | computer.svg | +| machine | computer | computer.svg | +| messengers | chat | chat.svg | +| discord | discord | discord.svg | +| telegram | telegram | telegram.svg | +| integrations | weblink | weblink.svg | +| mail | gmail | gmail.svg | +| linear | list | list.svg | +| applications | stars | stars.svg | +| game | stars | stars.svg | +| scanner | stars | stars.svg | +| coach | stars | stars.svg | +| files | folder | folder.svg | +| settings | cog | cog.svg | +| cat | tag | tag.svg | +| satellite | rss | rss.svg | +| add | plus | plus.svg | +| library | pencil | pencil.svg | +| terminal | terminal | terminal.svg | +| tabs | bookmark | bookmark.svg | + +`computer`, `plus`, `terminal` are the custom-drawn `added` icons. +`pencil`/`terminal`/`bookmark` double as chrome aliases (library/terminal/tabs). + +## All 16 generated files + +bookmark, chat, cog, computer, discord, folder, gmail, list, pencil, plus, rss, +stars, tag, telegram, terminal, weblink diff --git a/web/public/icons/bookmark.svg b/web/public/icons/bookmark.svg new file mode 100644 index 00000000..7ab8ba83 --- /dev/null +++ b/web/public/icons/bookmark.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/chat.svg b/web/public/icons/chat.svg new file mode 100644 index 00000000..f00e53bb --- /dev/null +++ b/web/public/icons/chat.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/cog.svg b/web/public/icons/cog.svg new file mode 100644 index 00000000..530dba8e --- /dev/null +++ b/web/public/icons/cog.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/computer.svg b/web/public/icons/computer.svg new file mode 100644 index 00000000..6aa93003 --- /dev/null +++ b/web/public/icons/computer.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/discord.svg b/web/public/icons/discord.svg new file mode 100644 index 00000000..b8a7117b --- /dev/null +++ b/web/public/icons/discord.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/folder.svg b/web/public/icons/folder.svg new file mode 100644 index 00000000..d4071ec2 --- /dev/null +++ b/web/public/icons/folder.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/gmail.svg b/web/public/icons/gmail.svg new file mode 100644 index 00000000..026bc574 --- /dev/null +++ b/web/public/icons/gmail.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/list.svg b/web/public/icons/list.svg new file mode 100644 index 00000000..c98a674d --- /dev/null +++ b/web/public/icons/list.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/pencil.svg b/web/public/icons/pencil.svg new file mode 100644 index 00000000..1f0988d9 --- /dev/null +++ b/web/public/icons/pencil.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/plus.svg b/web/public/icons/plus.svg new file mode 100644 index 00000000..da9d9fae --- /dev/null +++ b/web/public/icons/plus.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/rss.svg b/web/public/icons/rss.svg new file mode 100644 index 00000000..836b8040 --- /dev/null +++ b/web/public/icons/rss.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/stars.svg b/web/public/icons/stars.svg new file mode 100644 index 00000000..a1b0079c --- /dev/null +++ b/web/public/icons/stars.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/tag.svg b/web/public/icons/tag.svg new file mode 100644 index 00000000..12f13121 --- /dev/null +++ b/web/public/icons/tag.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/telegram.svg b/web/public/icons/telegram.svg new file mode 100644 index 00000000..dc11a945 --- /dev/null +++ b/web/public/icons/telegram.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/terminal.svg b/web/public/icons/terminal.svg new file mode 100644 index 00000000..fa9b49d4 --- /dev/null +++ b/web/public/icons/terminal.svg @@ -0,0 +1 @@ + diff --git a/web/public/icons/weblink.svg b/web/public/icons/weblink.svg new file mode 100644 index 00000000..33453ed1 --- /dev/null +++ b/web/public/icons/weblink.svg @@ -0,0 +1 @@ + diff --git a/web/public/img/agent-0.png b/web/public/img/agent-0.png new file mode 100644 index 0000000000000000000000000000000000000000..e71dd89e4d87ec04d8613791f869095aedf99a5c GIT binary patch literal 1416 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 KMNUMnLSTZmZKf~) literal 0 HcmV?d00001 diff --git a/web/src/app/components/ui/AddAction.css b/web/src/app/components/ui/AddAction.css new file mode 100644 index 00000000..87b5912f --- /dev/null +++ b/web/src/app/components/ui/AddAction.css @@ -0,0 +1,4 @@ +/* Ported from AddAction.dc.html β€” hover rule transcribed verbatim. */ +.aa:hover { + background: var(--hover) !important; +} diff --git a/web/src/app/components/ui/AddAction.tsx b/web/src/app/components/ui/AddAction.tsx new file mode 100644 index 00000000..ce819098 --- /dev/null +++ b/web/src/app/components/ui/AddAction.tsx @@ -0,0 +1,84 @@ +import "./AddAction.css"; + +export type AddActionVariant = "row" | "tile"; + +export interface AddActionProps { + variant?: AddActionVariant; + label?: string; + onClick?: () => void; +} + +const PlusGlyph = () => ( + + + + + + +); + +/** AddAction β€” ported from AddAction.dc.html. Dashed "add new" affordance in two + * variants: a full-width row (with hover + chevron) and a stacked tile. */ +export function AddAction({ variant = "row", label, onClick }: AddActionProps) { + const text = label ?? (variant === "tile" ? "NEW AGENT" : "CONNECT NEW MACHINE"); + + return ( +
+ {variant === "row" ? ( +
+ + + + {text} + + + + + +
+ ) : ( +
+
+ +
+
{text}
+
+ )} +
+ ); +} diff --git a/web/src/app/components/ui/AgentImage.tsx b/web/src/app/components/ui/AgentImage.tsx new file mode 100644 index 00000000..366a3afe --- /dev/null +++ b/web/src/app/components/ui/AgentImage.tsx @@ -0,0 +1,49 @@ +export interface AgentImageProps { + /** Crew index 0–2 β†’ /img/agent-.png. Ignored if `src` is provided. */ + agent?: number; + /** Explicit image src; wins over `agent`. */ + src?: string; + /** Box diameter in px (28–80). */ + size?: number; +} + +/** AgentImage β€” ported from AgentImage.dc.html. Pixel crew portrait in a + * rounded, glowing tile. Size buckets keep the portrait crisp at any scale. */ +export function AgentImage({ agent, src, size = 50 }: AgentImageProps) { + const sz = Number(size) || 50; + // agent index β†’ raster portrait; explicit src wins + const idx = agent === undefined || agent === null ? null : Number(agent) || 0; + const imgSrc = src ?? (idx === null ? "/img/agent-0.png" : `/img/agent-${idx}.png`); + // size buckets keep the pixel portrait crisp at every usage scale + const fill = sz <= 44 ? 0.74 : sz <= 60 ? 0.68 : 0.62; + const imgH = Math.round(sz * fill); + const radius = sz <= 44 ? 3 : 4; + const glow = sz <= 44 ? 5 : sz <= 60 ? 6 : 8; + + return ( +
+ agent +
+ ); +} diff --git a/web/src/app/components/ui/Avatar.tsx b/web/src/app/components/ui/Avatar.tsx new file mode 100644 index 00000000..4f9ef700 --- /dev/null +++ b/web/src/app/components/ui/Avatar.tsx @@ -0,0 +1,48 @@ +import { AgentImage } from "./AgentImage"; + +export type AvatarStatus = "online" | "idle" | "error" | "live"; + +export interface AvatarProps { + /** Crew index 0–2 β†’ /img/agent-.png. Ignored if `src` is provided. */ + agent?: number; + /** Explicit image src; wins over `agent`. */ + src?: string; + /** Box diameter in px (28–80). */ + size?: number; + /** Corner status dot. */ + status?: AvatarStatus; +} + +const DOT_COLOR: Record = { + online: "var(--online)", + idle: "var(--idle)", + error: "var(--error)", + live: "var(--live)", +}; + +/** Avatar β€” ported from Avatar.dc.html. Wraps AgentImage and overlays a + * status corner-dot. */ +export function Avatar({ agent, src, size = 44, status = "online" }: AvatarProps) { + const dotColor = DOT_COLOR[status] ?? DOT_COLOR.online; + const dotGlow = status === "idle" ? "none" : `0 0 7px ${dotColor}`; + + return ( +
+ + +
+ ); +} diff --git a/web/src/app/components/ui/Button.css b/web/src/app/components/ui/Button.css new file mode 100644 index 00000000..b5710935 --- /dev/null +++ b/web/src/app/components/ui/Button.css @@ -0,0 +1,151 @@ +/* Ported from Button.dc.html β€” values transcribed verbatim. */ +.gsv-btn { + font-family: var(--gsv-font-mono); + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + user-select: none; + border: 1px solid transparent; + cursor: pointer; +} + +/* β€” primary β€” */ +.gsv-btn-primary { + font-size: 11.5px; + letter-spacing: 0.16em; + padding: 11px 30px; + background: #5a52a8; + border-color: #6b62c4; + color: #fff; + box-shadow: 0 0 18px rgba(120, 110, 230, 0.4); + transition: background 0.12s, box-shadow 0.12s, border-color 0.12s; +} +.gsv-btn-primary:hover { + background: #6b62c4; + box-shadow: 0 0 28px rgba(120, 110, 230, 0.7); +} +.gsv-btn-primary:active { + background: #4a43a0; + border-color: #5a52a8; + box-shadow: 0 0 8px rgba(120, 110, 230, 0.2); +} +.gsv-btn-primary.is-disabled { + border-color: #3a3676; + background: #1e1b45; + color: #565199; + box-shadow: none; +} + +/* β€” secondary β€” */ +.gsv-btn-secondary { + font-size: 11px; + letter-spacing: 0.14em; + padding: 10px 22px; + background: transparent; + border-color: #4a449e; + color: #b3aeff; + transition: background 0.12s, color 0.12s, border-color 0.12s; +} +.gsv-btn-secondary:hover { + background: #171441; + color: #e6e4ff; + border-color: #6b62c4; +} +.gsv-btn-secondary:active { + background: #201c4d; + color: #cbc7ff; + border-color: #7a72d4; +} +.gsv-btn-secondary.is-disabled { + border-color: #25224d; + background: transparent; + color: #565199; +} + +/* β€” danger β€” */ +.gsv-btn-danger { + font-size: 11px; + letter-spacing: 0.14em; + padding: 10px 28px; + background: #a8324a; + border-color: #c4445f; + color: #fff; + box-shadow: 0 0 16px rgba(200, 60, 90, 0.35); + transition: background 0.12s, box-shadow 0.12s, border-color 0.12s; +} +.gsv-btn-danger:hover { + background: #c4445f; + box-shadow: 0 0 26px rgba(200, 60, 90, 0.6); +} +.gsv-btn-danger:active { + background: #8c2840; + border-color: #a8324a; + box-shadow: 0 0 8px rgba(200, 60, 90, 0.2); +} +.gsv-btn-danger.is-disabled { + border-color: #3a2030; + background: #1e1018; + color: #565199; + box-shadow: none; +} + +/* β€” dangerGhost β€” */ +.gsv-btn-dghost { + font-size: 11px; + letter-spacing: 0.14em; + padding: 9px 20px; + background: transparent; + border-color: #5a2b3a; + color: #ff8fa3; + transition: background 0.12s, color 0.12s, border-color 0.12s; +} +.gsv-btn-dghost:hover { + background: #2a1420; + color: #ffb3c2; + border-color: #7a3050; +} +.gsv-btn-dghost:active { + background: #3a1a2a; + color: #ffd0dc; + border-color: #9a4060; +} +.gsv-btn-dghost.is-disabled { + border-color: #2a1420; + background: transparent; + color: #565199; +} + +/* β€” link β€” */ +.gsv-btn-link { + font-size: 10px; + letter-spacing: 0.16em; + padding: 0 0 1px; + background: transparent; + border: none; + border-bottom: 1px solid #b3aeff; + color: #b3aeff; + transition: color 0.12s, border-color 0.12s, opacity 0.12s; +} +.gsv-btn-link:hover { + color: #cbc7ff; + border-color: #cbc7ff; +} +.gsv-btn-link:active { + opacity: 0.65; +} +.gsv-btn-link.is-disabled { + color: #565199; + border-color: #25224d; +} +.gsv-btn-link svg { + display: block; + filter: drop-shadow(0 0 3px rgba(150, 140, 255, 0.5)); +} +.gsv-btn-link.is-disabled svg { + filter: none; +} + +.gsv-btn.is-disabled { + cursor: not-allowed; +} diff --git a/web/src/app/components/ui/Button.tsx b/web/src/app/components/ui/Button.tsx new file mode 100644 index 00000000..d08f52ea --- /dev/null +++ b/web/src/app/components/ui/Button.tsx @@ -0,0 +1,40 @@ +import "./Button.css"; + +export type ButtonVariant = "primary" | "secondary" | "danger" | "dangerGhost" | "link"; + +export interface ButtonProps { + variant?: ButtonVariant; + label?: string; + disabled?: boolean; + onClick?: () => void; +} + +const VARIANT_CLASS: Record = { + primary: "gsv-btn-primary", + secondary: "gsv-btn-secondary", + danger: "gsv-btn-danger", + dangerGhost: "gsv-btn-dghost", + link: "gsv-btn-link", +}; + +/** Button β€” ported from Button.dc.html. Rendered as a to match the + * design source exactly (no native ); } return ( -
+
{children}
); diff --git a/web/src/app/features/session/SetupScreen.css b/web/src/app/features/session/SetupScreen.css new file mode 100644 index 00000000..302aff7f --- /dev/null +++ b/web/src/app/features/session/SetupScreen.css @@ -0,0 +1,76 @@ +/* Create-account (register) wizard shell β€” sits in the AuthLayout galaxy + surface. A wide card panel: sidebar (stepper) + workspace (stages + guide). */ +.gsv-setup-panel { + width: min(920px, 94vw); + max-height: calc(100% - 56px); + overflow: auto; + background: color-mix(in srgb, var(--panel) 100%, #ffffff 5%); + border: 1px solid var(--border); + box-shadow: 0 0 40px rgba(10, 6, 30, 0.5), 0 0 0 1px rgba(150, 140, 255, 0.04); + backdrop-filter: blur(3px); +} + +.gsv-setup-form { + display: grid; + grid-template-columns: 236px minmax(0, 1fr); + align-items: stretch; +} +/* Sidebar column (the only direct