Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .docket/ledger.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,6 @@
{"schema":3,"kind":"question","id":"q14","text":"Does cmd.exe /c start leave the config TUI holding process.run's pipes on Windows, so /glowup config waits 30 s and prints the fallback while the window is open?","state":"open","ts":"2026-10-08T10:46:42+00:00","author":"claude-code","session":"","branch":"feat/config-tui","scope":["hooks/launch.ts"],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false}
{"schema":3,"kind":"claim","id":"c15","text":"The config TUI's Linux launch works live: tmux window, Konsole, $TERMINAL=xterm, hot reload, /clear mid-run, q and Esc","state":"accepted","ts":"2026-10-08T10:46:43+00:00","author":"claude-code","session":"","branch":"feat/config-tui","scope":["hooks/launch.ts","hooks/register.tsx"],"rationale":"macOS and Windows launches are unit-tested only","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"Task 11 live check 2026-10-08, task-11-report.md"}],"revisit":"","cost_if_wrong":"","pinned":false}
{"schema":3,"kind":"decision","id":"d100","text":"The config TUI draws the session's built-in pet playing its idle animation from frames baked into packs.json","state":"adopted","ts":"2026-10-08T12:30:13+00:00","author":"claude-code","session":"","branch":"fix/config-tui-pets","scope":["hooks/packexport.ts","installer/internal/tui/clawd.go","installer/internal/configtui/view.go"],"rationale":"The installer already embeds packs.json; built-ins cover the options list, and it adds about 350 KB to a 5.7 MB binary. The egg shows its uncracked stage because the installer has no session store.","supports":[["d96"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"User pets appear in the config TUI's pet options, or the egg's stage matters there","cost_if_wrong":"","pinned":false,"choice":"packexport.ts exports every BUILTIN_SHEETS pet's idle frames as half-block rows; the TUI picks the frame by tick time, holds frame 0 under reduced motion, and widens the column to the pet","alternatives":["Have the mod write the pet's frames into state.json each run, which would also cover user pets and the egg's crack stage"],"decided_by":""}
{"schema":3,"kind":"decision","id":"d101","text":"The landing page and studio share one token layer and control set in docs/web/app/ui, with Base UI supplying behavior for toggle groups, selects and popovers","state":"adopted","ts":"2026-10-08T13:02:49+00:00","author":"claude-code","session":"","branch":"feat/site-design-system","scope":["docs/web/app/ui/","docs/web/app/landing/","docs/web/app/studio/"],"rationale":"Both pages are plain CSS on runtime pack variables; shadcn's components are Tailwind-classed and would force those pages onto utility classes. Base UI is unstyled, so the existing CSS styles it and live pack theming keeps working.","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"the landing or studio pages move to Tailwind utilities","cost_if_wrong":"","pinned":false,"choice":"app/ui/system.css holds tokens derived from the pack's custom properties plus .btn/.seg/.chip/.field/.pop classes; Segmented, Select and the studio share popover wrap @base-ui/react and portal into the .landing root","alternatives":["shadcn/ui components themed through their CSS variables","Tokens and CSS only, keeping the hand-built popover and native selects"],"decided_by":"owner"}
{"schema":3,"kind":"decision","id":"d101","text":"Setups are written as format 2; parseSetup maps the old needs-you,fail,done default to all seven moments only when the stored setup is not format 2","state":"adopted","ts":"2026-10-08T15:15:57+00:00","author":"claude-code","session":"","branch":"feat/pet-lines","scope":["hooks/setup.ts"],"rationale":"the ungated migration turned a deliberate three-mood choice from /glowup setup, the config TUI or the studio back into all seven; glowup 0.12 parseSetup never reads format, so it still loads format 2","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Setup.format is 2; migration gated on raw.format !== 2","alternatives":["read the exact old list as the default on every read and tell users to reorder it"],"decided_by":"team-lead"}
{"schema":3,"kind":"decision","id":"d102","text":"Beside the pet, the strip's row grows to the bubble's rows when the pet is shorter than the bubble","state":"adopted","ts":"2026-10-08T15:15:57+00:00","author":"claude-code","session":"","branch":"feat/pet-lines","scope":["hooks/pane.tsx"],"rationale":"a row Box clips content taller than its height; a 2x2 px pet has a one-row strip and its three-row bubble vanished; main 9911949 has the same layout","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"stripHeight() in hooks/pane.tsx: max(pet rows, bubble rows) beside, used by petStrip and footerRows","alternatives":["leave the clipping and change the docs example to a taller pet"],"decided_by":""}
{"schema":3,"kind":"decision","id":"d103","text":"The landing page and studio share one token layer and control set in docs/web/app/ui, with Base UI supplying behavior for toggle groups, selects and popovers","state":"adopted","ts":"2026-10-08T13:02:49+00:00","author":"claude-code","session":"","branch":"feat/site-design-system","scope":["docs/web/app/ui/","docs/web/app/landing/","docs/web/app/studio/"],"rationale":"Both pages are plain CSS on runtime pack variables; shadcn's components are Tailwind-classed and would force those pages onto utility classes. Base UI is unstyled, so the existing CSS styles it and live pack theming keeps working.","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"the landing or studio pages move to Tailwind utilities","cost_if_wrong":"","pinned":false,"choice":"app/ui/system.css holds tokens derived from the pack's custom properties plus .btn/.seg/.chip/.field/.pop classes; Segmented, Select and the studio share popover wrap @base-ui/react and portal into the .landing root","alternatives":["shadcn/ui components themed through their CSS variables","Tokens and CSS only, keeping the hand-built popover and native selects"],"decided_by":"owner"}
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Clawd, the robot and the egg each speak in their own voice, with about six lines per moment.
- Four new bubble moments: `green` (a passing test run after a failed one), `hello` (when the pane first opens in a session unless another bubble spoke first, and when a turn starts after 30 minutes away), `long-done` (a turn of 5 minutes or more) and `compact`.
- Lines that join the pool by time of day, on Fridays, and at Christmas, Halloween and your install anniversary.
- Pet files with `"format": 2` can carry `lines` and a Haiku `voice`. glowup 0.12 and earlier refuse them.

### Changed

- The default `bubbles.moods` includes all seven moments. A setup saved before this release that still holds the old default, `needs-you,fail,done`, gets all seven.
- Haiku bubble lines speak in each pet's own voice instead of always as Clawd.
- The website's landing page and studio share one set of buttons, toggles, selects and popovers, and they all follow the pack you pick. The studio's selects open a themed list instead of the browser's own, and its Send to my Claude popover takes keyboard focus when it opens.

## [0.12.1] - 2026-10-08
Expand Down
28 changes: 28 additions & 0 deletions docs/pet-sprites.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,31 @@ Each `px` row is a string with one palette key per pixel, and `.` is a transpare
- `ms` is a whole number from 80 to 10000.
- A row has at most 32 frames, and the animation names are the ones in the table above.
- A frame can also set `dx`, a whole number from -4 to 4 for how many pixels the pet travels right when the frame shows, and `exit: true` to mark a neutral frame where glowup may cut away to the next animation. The last frame of a row that plays once counts as one. An optional `description` is at most 80 characters.

### Lines and voice

A pet can say its own lines and describe itself to Haiku in a file with `"format": 2`. The file then takes two more optional keys:

- `lines` maps a moment (`done`, `fail`, `needs-you`, `green`, `hello`, `long-done`, `compact`) or a moment and a flavour (`done@night`, `hello@christmas`) to a list of 1 to 12 lines. Each line is printable text of at most 40 characters. The flavours are `morning`, `afternoon`, `evening`, `night`, `friday`, `christmas`, `halloween` and `birthday`. A flavour key adds its lines to the moment's pool while that flavour is active. See [Pets](pets.md#what-pets-say) for when each moment speaks.
- `voice` is printable text of at most 120 characters. When bubbles are set to haiku (`/glowup bubbles haiku`), it describes the pet to the model that writes the line, in place of the default "a small pixel pet. Friendly and brief."

A line can use only the variables its moment fills: `{file}` in `done` and `long-done`, `{n}` in `fail`, `{command}` in `needs-you`. `hello`, `green` and `compact` take none. An unknown moment, flavour or variable, a line over the limit, or more than 12 lines to a key is rejected with the key named.

A moment with no lines in the file speaks a neutral default line, never another pet's. Using `lines` or `voice` in a `"format": 1` file is an error.

```json
{
"format": 2,
"name": "blob",
"voice": "a sleepy blob. Slow, soft, a bit confused.",
"lines": {
"done": ["mm. done", "{file}… better"],
"hello@morning": ["too early"],
"green": ["oh nice"]
},
"palette": { "a": "#88ccff" },
"animations": { "idle": [{ "px": ["aa", "aa"], "ms": 400 }] }
}
```

glowup 0.12 and earlier refuse a format 2 file with a "made for a newer glowup" error. The studio opens format 2 files but drops `lines` and `voice` when you export, because it writes format 1.
32 changes: 30 additions & 2 deletions docs/pets.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,49 @@ It reacts to the same moments as Clawd, without his outfits.

## Speech bubbles

With bubbles on, Clawd says a short line (40 characters at most) by default when a turn ends, when a test fails and when Claude needs you, and the bubble stays for 3 seconds by default. Lines come from a small set of templates per mood, never the same one twice in a row, and some fill in details such as the failure count or the command. In the narrow drawer the line appears beside the one-row Clawd. Choose which moments get a bubble and how long it stays with /glowup setup bubbles.moods and /glowup setup bubbles.ms.
With bubbles on, the pet says a short line (40 characters at most) at the moments listed under [What pets say](#what-pets-say), and the bubble stays for 3 seconds by default. Lines come from a set of templates per moment, never the same one twice in a row, and some fill in details such as the failure count or the command. In the narrow drawer the line appears beside the one-row pet. Choose which moments get a bubble and how long it stays with /glowup setup bubbles.moods and /glowup setup bubbles.ms.

```text title="claude code"
/glowup bubbles off
/glowup bubbles on
/glowup bubbles haiku
```

### What pets say

A bubble is about one moment. There are seven, and all are on by default.

| Moment | When it speaks |
| --- | --- |
| `done` | A turn ends with an answer. |
| `fail` | A test run fails. |
| `needs-you` | A permission dialog opens. |
| `green` | A test run passes after a failed one in the same session. |
| `hello` | The pane is first drawn in a session, unless another bubble spoke first, and when a turn starts after 30 minutes away. |
| `long-done` | A turn that ran 5 minutes or more ends with an answer. |
| `compact` | The context is compacted. |

Clawd, the robot and the egg each have their own lines, about six per moment: Clawd is dry and warm, the robot reports in capitals, and the egg mostly makes small sounds. `clawd-shiny` uses Clawd's. A pet you draw yourself speaks a neutral default line unless its file carries [its own lines](pet-sprites.md#lines-and-voice).

Lines also change with the clock. Mornings, afternoons, evenings and nights add their own lines to the pool, and so do Fridays and the holiday outfits (Christmas, Halloween and your install anniversary).

To silence a moment, list the ones you want to keep:

```text title="claude code"
/glowup setup bubbles.moods done,fail,needs-you,green
```

With `long-done` off, a long turn speaks a plain `done` line. A setup saved before these moments existed that still holds the old default, `needs-you,fail,done`, gets all seven.

### Lines written by Haiku

With `/glowup bubbles haiku`, glowup sometimes asks Claude Haiku for the line. The template line shows first, and Haiku's replaces it only if the reply arrives while that bubble is still up, so the bubble is drawn without waiting for the call.

**Cost.** Each line is a small Haiku call billed to your account, made with your session's credentials.

What it sends: the mood, the pose, the short label glowup already shows for the current tool, a test summary such as `failed 3`, and the time of day. It never sends your prompts, file contents, code or secrets. The reply is cleaned to one plain line of at most 40 characters; an empty reply falls back to the template.
`hello` and `compact` lines never use Haiku. Haiku speaks as the pet, using the pet's voice: built in for Clawd, the robot and the egg, and the `voice` key in a [custom pet file](pet-sprites.md#lines-and-voice).

What it sends: the moment, the pose, the short label glowup already shows for the current tool, a test summary such as `failed 3`, and the time of day. It never sends your prompts, file contents, code or secrets. The reply is cleaned to one plain line of at most 40 characters; an empty reply falls back to the template.

glowup makes one call at a time, at most one per turn, at least 90 seconds apart, and abandons a call after 4 seconds. After an error or a timeout, and always in a `-p` run, under reduced motion or with the pet off, the template line stays and nothing is retried. Errors go to the debug log only.

Expand Down
4 changes: 2 additions & 2 deletions docs/web/app/landing/Terminal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Fragment, useEffect, useId, useRef, useState } from 'react'
import { CLAWD_SAY, DEFAULT_SETUP, renderFields, spinnerWordSpans, toneFor, type BandItem, type Model, type OrbState, type PetSheet, type Setup, type StatusFieldId, type TabId } from './data.ts'
import { BUILTIN_LINES, DEFAULT_SETUP, renderFields, spinnerWordSpans, toneFor, type BandItem, type Model, type OrbState, type PetSheet, type Setup, type StatusFieldId, type TabId } from './data.ts'
import { useReducedMotion, useVisible } from './motion.ts'
import { PaneField } from './PaneField.tsx'
import { Pet } from './Pet.tsx'
Expand Down Expand Up @@ -167,7 +167,7 @@ export function Terminal({ setup = DEFAULT_SETUP, interactive = false, scale = 1

const marks = marksOf(look, interactive)
const { band } = state
const lines = CLAWD_SAY[bub.kind]
const lines = BUILTIN_LINES.clawd[bub.kind]!.filter(l => !l.includes('{'))
const agents = band?.agents ?? 0
const showWord = state.bandOn && band !== null && band.kind !== 'pass'
const gw = glowupWord(state)
Expand Down
2 changes: 1 addition & 1 deletion docs/web/app/landing/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export { FIELD_IDS as STATUS_FIELD_IDS, DEFAULT_FIELDS, renderFields, type Field
export { fieldFrame, fieldTickMs } from '../../../../hooks/effects.ts'
export type { Model } from '../../../../hooks/model.ts'
export type { Seg } from '../../../../hooks/segs.ts'
export { CLAWD_SAY } from '../../../../hooks/bubbles.ts'
export { BUILTIN_LINES } from '../../../../hooks/lines.ts'
export { encodeLink, decodeLink, STUDIO_URL, PET_LINK_MAX } from '../../../../hooks/link.ts'
export { PET_ANIMS, FRAME_W, FRAME_H, MAX_COLORS, MAX_FRAMES, ANIM_MS, WALKS, MIN_MS, validatePetFile, petSheet, petNameProblem, type PetFile, type PetAnimName } from '../../../../hooks/petfile.ts'
export { parseSetup, toneFor, DEFAULT_SETUP, BAND_ITEMS, TAB_IDS, SETUP_MOODS, type Setup, type BandItem, type TabId } from '../../../../hooks/setup.ts'
5 changes: 4 additions & 1 deletion docs/web/app/studio/SetupControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { BAND_ITEMS, SETUP_MOODS, TAB_IDS } from '../landing/data.ts'
import { editSetup, toggle, type StudioSetup } from './model.ts'
import { Order } from './ui'

const MOOD_LABEL: Record<string, string> = { 'needs-you': 'needs you', fail: 'failed test', done: 'turn done' }
const MOOD_LABEL: Record<string, string> = {
'needs-you': 'needs you', fail: 'failed test', done: 'turn done',
green: 'tests green again', hello: 'hello', 'long-done': 'long turn done', compact: 'compacted',
}

// Keeps what the user typed until it parses, so "6" on the way to "60" is not snapped back by a rejected edit.
function NumField({ label, value, min, max, step = 1, onCommit }: { label: string; value: number; min?: number; max?: number; step?: number; onCommit(n: number): void }) {
Expand Down
Loading
Loading