From 365917e195acbce119828f02143b6b93e97205da Mon Sep 17 00:00:00 2001 From: maceip Date: Sun, 6 Sep 2026 09:52:20 -0700 Subject: [PATCH] Add Grove, a component kit grown from the Animal Island design language Grove takes the Animal Island design system (lifeodyssey/animal-island-ui- tailwind, MIT) as a foundation and rebuilds it as our own plain-CSS kit with thin React wrappers: no Tailwind, no Radix, nothing imported at runtime. Kept because they are the point: capsule shapes, the flat game-button press shadow (5px rest, 6px hover with a lift, 1px pressed with a drop), chunky sage borders, the yellow focus ring, organic title radii and the blob clip-path on a native . Ours: the palette (paper, warm charcoal, leaf green, dry-sage shadow), status tags mapped to the workspace's activity states, speech bubbles for turns, a vine divider, toasts. - ui/src/grove: grove.css and Grove.tsx (Button, Input, Textarea, Switch, Checkbox, Tag, Ribbon, Speech, BlobDialog, Vine, GroveDefs) - The "Start a contribution" dialog now uses BlobDialog with Grove controls; the dialog fills the viewport with its own blur so the blob has no un-blurred rectangle behind it - docs/design/grove.html shows every component and the palette mapping; grove.md records what was kept, what is ours and where to apply it next --- THIRD_PARTY_NOTICES.md | 5 + docs/design/grove.html | 362 +++++++++++++++++++++ docs/design/grove.md | 64 ++++ ui/src/App.tsx | 2 + ui/src/WorkspacePanels.tsx | 66 ++-- ui/src/grove/Grove.tsx | 229 +++++++++++++ ui/src/grove/grove.css | 637 +++++++++++++++++++++++++++++++++++++ ui/src/style.css | 11 + 8 files changed, 1349 insertions(+), 27 deletions(-) create mode 100644 docs/design/grove.html create mode 100644 docs/design/grove.md create mode 100644 ui/src/grove/Grove.tsx create mode 100644 ui/src/grove/grove.css diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index b27122f..5a8bd6c 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -17,6 +17,11 @@ - Lucide, Lucide contributors, ISC: https://github.com/lucide-icons/lucide - React Markdown and remark-gfm, unified contributors, MIT: https://github.com/remarkjs/react-markdown and https://github.com/remarkjs/remark-gfm +- Grove (`ui/src/grove`) follows the Animal Island design language documented + by lifeodyssey/animal-island-ui-tailwind (MIT, after guokaigdg/animal-island-ui): + https://github.com/lifeodyssey/animal-island-ui-tailwind. The dialog clip-path + is reproduced from that project; no code or assets are bundled. Not affiliated + with Nintendo. - DM Sans and IBM Plex Mono font packages, SIL Open Font License 1.1: https://fontsource.org/fonts/dm-sans and https://fontsource.org/fonts/ibm-plex-mono diff --git a/docs/design/grove.html b/docs/design/grove.html new file mode 100644 index 0000000..3c55620 --- /dev/null +++ b/docs/design/grove.html @@ -0,0 +1,362 @@ + + + + + + Grove — AXP component kit + + + + + + + +

Grove

+

+ AXP's component kit, grown from the Animal Island design language and + re-based on our palette. Capsules, chunky 2px borders, the flat + game-button press shadow, a yellow focus ring, organic titles and a blob + dialog. Press things. +

+ +

Palette: foundation → ours

+
+
+
+ parchment #f8f8f0 · text #794f27 +
+
+ primary mint #19c8b9 +
+
+ primary bg #e6f9f6 · press shadow #bdaea0 +
+
+ focus yellow #ffcc00 +
+
+
+
+ paper #f6f7f4 · ink #252c28 +
+
+ leaf #58aa72 +
+
+ leaf bg #eaf3e9 · press shadow sand #b9c6b0 +
+
+ focus yellow, kept #ffcc00 +
+
+
+ +

Buttons

+
+ + + + + + +
+
+ + + + + +
+ +

Inputs

+
+
+ + + + +

+ The reference becomes the session's task ID, so keep it unique. +

+
+
+ + +
+ + + +
+
+
+ +

Tags (the workspace's activity states)

+
+ In progress + Needs your input + Ready for review + Checkpoint ready + Open for contribution + Archived +
+ +

Titles, tabs, vine

+
+ Contributions + People +
+ + + +
+
+ + +

Cards and speech

+
+
+ Checkpoint ready +

Explain parser errors

+

+ Explain why parsing failed and how to fix the input. Keep the existing + parser API. +

+
+
+
+ MaintainerReproduce issue 42 and fix it + without changing the API. +
+
+ AgentThe parser rejects empty input with a + generic error. The patch explains how to fix the input without + changing the return type. +
+
+
+
+ +
+ Checkpoint savedbbbbbbb on axp/parser-errors +
+
+
+ ! +
+ Permission neededThe agent wants to edit the + welcome screen. +
+
+
+
+ +

Blob dialog

+
+
+
+

+ Start a contribution +

+

+ Describe the work. Contributors can connect an agent to this + session. +

+ + + + +
+ + +
+
+
+
+ + + diff --git a/docs/design/grove.md b/docs/design/grove.md new file mode 100644 index 0000000..b2eb5f2 --- /dev/null +++ b/docs/design/grove.md @@ -0,0 +1,64 @@ +# Grove + +## What it is + +Grove is AXP's component kit. It starts from the Animal Island design language +([lifeodyssey/animal-island-ui-tailwind](https://github.com/lifeodyssey/animal-island-ui-tailwind), +MIT, after guokaigdg/animal-island-ui) and re-bases it on our palette. Nothing +is imported at runtime: no Tailwind, no Radix, no component code. What we took +is the design system, written down in `DESIGN_PROMPT.md` upstream, and what we +built is our own set of plain CSS classes with thin React wrappers. + +Open `docs/design/grove.html` to see every component, and the palette mapping +from the foundation to ours. + +## What we kept, and why + +- **Capsules everywhere.** Buttons and inputs are full pills; nothing + interactive has a corner sharper than 8px. This is the single strongest + signal of the style. +- **The game-button press.** Depth is a flat bottom shadow, not a blur: 5px at + rest, 6px with a 1px lift on hover, 1px with a 2px drop when pressed. Every + button, switch handle and checkbox uses it. It is why the kit feels + physical. +- **Chunky borders.** 2px on controls, 2.5px on inputs, in a soft sage. +- **A yellow focus ring.** `#ffcc00`, kept from the foundation on purpose: on + a green page it is unmistakable and a little joyful. +- **Organic titles and the blob dialog.** The `Ribbon` uses the foundation's + asymmetric radii; `BlobDialog` uses its exact clip-path on a native + ``. + +## What is ours + +- **Palette.** Parchment and brown became paper (`#f6f7f4`) and warm charcoal + (`#252c28`); mint teal became leaf (`#58aa72`); the sand press shadow became + a dry sage (`#b9c6b0`). Status tags map directly to the workspace's activity + states (working, permission, review, ready, waiting, archived). +- **Speech bubbles** for turns: maintainer prompts on the left, agent replies + on the right, each with a name tag on its shoulder. The transcript is a + conversation; this lets it look like one. +- **A vine divider**, a repeating leaf wave in SVG. +- **Toasts** in the same paper-and-shadow language. + +## Where it is applied + +The "Start a contribution" dialog is now a `BlobDialog` with Grove inputs and +buttons (`ui/src/WorkspacePanels.tsx`). Everything else keeps the current +chrome so the two can be compared. Natural next surfaces: the permission +buttons on tool calls, the composer, the status chips on cards, and the +transcript (speech bubbles). + +## Implementation notes + +- `ui/src/grove/grove.css` is the kit; `ui/src/grove/Grove.tsx` wraps it + (`Button`, `Input`, `Textarea`, `Switch`, `Checkbox`, `Tag`, `Ribbon`, + `Speech`, `BlobDialog`, `Vine`, and `GroveDefs` which mounts the clip-path + once). +- Switch and checkbox are CSS-only over a real ``, using `:has()`; + keyboard and screen-reader behaviour is the native one. +- The blob dialog fills the viewport and carries its own tint and blur. A + transparent dialog box over a blurred `::backdrop` leaves a sharp + un-blurred rectangle around the blob; this avoids it. The drop shadow sits + on a wrapper so it follows the clipped silhouette. +- Reduced motion turns off the press transitions and the striped loading + animation. diff --git a/ui/src/App.tsx b/ui/src/App.tsx index d3254ac..03efb9a 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -24,6 +24,7 @@ import { people, } from "./components.js"; import { ContributionPage } from "./Contribution.js"; +import { GroveDefs } from "./grove/Grove.js"; type Page = "overview" | "contributions" | "people" | "activity"; function currentPage(): Page { @@ -113,6 +114,7 @@ export default function App() { }; return (
+ Skip to workspace diff --git a/ui/src/WorkspacePanels.tsx b/ui/src/WorkspacePanels.tsx index d6405f6..10704f3 100644 --- a/ui/src/WorkspacePanels.tsx +++ b/ui/src/WorkspacePanels.tsx @@ -8,7 +8,8 @@ import { } from "lucide-react"; import type { Contribution } from "../../src/workspace-contract.js"; import { useCommand } from "./api.js"; -import { Avatar, Dialog, Empty, Mark, relativeTime } from "./components.js"; +import { Avatar, Empty, Mark, relativeTime } from "./components.js"; +import { BlobDialog, Button, Input } from "./grove/Grove.js"; export function Nav({ icon, @@ -89,24 +90,40 @@ export function Create({ const [task, setTask] = useState(""); const [session] = useState(() => crypto.randomUUID()); const command = useCommand(refresh); + const submit = () => { + void command.send(session, { kind: "create", title, task }).then((sent) => { + if (sent) created(session); + }); + }; return ( - + + + + + } + >
{ event.preventDefault(); - void command - .send(session, { kind: "create", title, task }) - .then((sent) => { - if (sent) created(session); - }); + submit(); }} > -

- Describe the work. Contributors can connect an agent to this session. -

- - + What do you want to work on? + + - - + Task or issue reference + + setTask(event.target.value)} @@ -124,24 +143,17 @@ export function Create({ maxLength={512} required /> +

+ The reference becomes the session's task ID, so keep it unique. +

{command.error && (
{command.error}
)} -
- - -
+
+ ); } export function Activity({ diff --git a/ui/src/grove/Grove.tsx b/ui/src/grove/Grove.tsx new file mode 100644 index 0000000..cd3f0b4 --- /dev/null +++ b/ui/src/grove/Grove.tsx @@ -0,0 +1,229 @@ +import { useEffect, useRef } from "react"; +import type { + ButtonHTMLAttributes, + InputHTMLAttributes, + ReactNode, + TextareaHTMLAttributes, +} from "react"; +import "./grove.css"; + +/* Grove: thin React wrappers over the classes in grove.css. Anything a plain + * element can do, the class does; these exist so call sites read as + * components and so the dialog blob clip-path is mounted once. */ + +export function GroveDefs() { + return ( + + ); +} + +type Variant = "default" | "primary" | "danger" | "ghost"; +type Size = "sm" | "md" | "lg"; + +export function Button({ + variant = "default", + size = "md", + block, + icon, + loading, + className = "", + ...rest +}: ButtonHTMLAttributes & { + variant?: Variant; + size?: Size; + block?: boolean; + icon?: boolean; + loading?: boolean; +}) { + const classes = [ + "grove-btn", + variant !== "default" ? `grove-btn-${variant}` : "", + size !== "md" ? `grove-btn-${size}` : "", + block ? "grove-btn-block" : "", + icon ? "grove-btn-icon" : "", + loading ? "is-loading" : "", + className, + ] + .filter(Boolean) + .join(" "); + return