Skip to content

Feat/claude code status crown - #24

Open
yedhu123-pixel wants to merge 12 commits into
KeyValueSoftwareSystems:mainfrom
yedhu123-pixel:feat/claude-code-status-crown
Open

Feat/claude code status crown#24
yedhu123-pixel wants to merge 12 commits into
KeyValueSoftwareSystems:mainfrom
yedhu123-pixel:feat/claude-code-status-crown

Conversation

@yedhu123-pixel

Copy link
Copy Markdown

No description provided.

yedhu123-pixel and others added 12 commits September 4, 2026 10:23
Argos gains a coloured square on the pet's head driven by Claude Code's
state: red when Claude is waiting for confirmation, amber while it runs,
green when the task is done.

The square rather than cap art is deliberate. It occupies the slot the
real cap will occupy, so the transport, the frame seam and the head
anchoring are all proven before anything is drawn.

Scope is one session, macOS, local only. Multi-session aggregation is
iteration 2 and touches only the state source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five tasks, each ending in a green test run and a commit: the state
source, the frame seam and the square, the controller and shape region,
the app wiring, then the hooks and hand verification.

Records one deliberate gap - the app-shell wiring has no unit test,
because no PetWindow or DisplayManager fake exists and building one for
four lines of wiring is not worth it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One word in ~/.argos/claude-state, written by Claude Code hooks. The
hook is 'echo running > file' so it cannot fail in an interesting way.

The file's mtime is the staleness guard, which is why the payload is a
bare word: a session killed with Ctrl+C never fires SessionEnd, and the
timestamp the guard needs already exists in the filesystem.

Watches the directory rather than the file, because a writer that
replaces the file swaps the inode and leaves a file-level watch pointed
at nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds PetFrame.claudeState and a coloured square anchored to the pet's
head through --body-cx / --body-top, the same properties the sleep Z's
use, so it tracks the head through every pose and every pet size.

A separate field from overlay because the two are independent axes: a
sleeping pet must still be able to wear the cap.

The shape region now follows the cap as well as the sleep overlay,
through frameNeedsCellRegion(). That is a function rather than an
expression in pet-window so it can be tested without Electron, and it
matters because setShape governs painting and not just hit-testing --
the cap sits over the hair, in mask cells that are transparent, so
without it the cap would be invisible on Linux and the screenshot
harness would not catch it, capturePage() never consulting the shape.

getClaudeState is injected like getMovementEnabled, keeping the
controller ignorant of where the state came from.

Combines what the plan listed as tasks 2 and 3: the required frame field
leaves pet-controller failing to typecheck until its call site is
updated, so splitting them would have committed a broken tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A state change calls tickNow() rather than waiting for the next tick, so
the cap changes colour when the terminal does. tickNow is the existing
seam for out-of-phase updates and does not reset the interval.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Includes the settings.json hook block and, more usefully, how to drive
the cap with echo so the transport can be proven before the hooks are
the variable under test.

Records a limit found while verifying with the screenshot harness: the
cap hangs off --body-cx, which is the centre of the character's bounding
box rather than of its head, so it sits visibly left of the head in the
jumping pose. The alpha mask publishes headTopByState but no matching
head centre; generating one is the fix, and is worth doing when the real
cap art lands rather than before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PreToolUse and PostToolUse carry no matcher rather than "*". An omitted
matcher matches every tool and is the shape the settings schema
guarantees; "*" is only a convention.

Verified live: with these hooks installed, the state file went to
"running" on the next tool call, with no restart -- the settings watcher
reloads the file on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cap sat still while the pet breathed. Root cause: it was anchored to
--body-top, and the pet's bounce is entirely background-position
stepping, so the sprite element never moves and nothing anchored to a
per-state constant can follow it.

--body-top comes from headTopByState, which is the minimum across a
state's frames. That is deliberate -- mask.mjs says so in as many words,
because a speech bubble that bobs with the pet's breathing is worse than
one that hangs still. Correct for a bubble, exactly wrong for a hat: the
cap parked at the highest point the head ever reaches and never came
down.

Measured from the art: idle's two frames sit 7px apart, so on the second
frame the cap floated a gap wider than half its own 13px height.

The cap now runs its own generated animation on the sprite's clock, one
step-end stop per frame holding that frame's real head anchor. step-end
rather than a two-endpoint ramp because the head tops are not a ramp --
jumping runs 44,42,42,42,50 -- so interpolating would miss on most
frames.

Per-frame anchors carry a head centre as well as a top, which also fixes
the sideways drift documented earlier: --body-cx was the body's bounding
box centre, and the head swings 9px against it in jumping.

Verified by capturing both idle frames: cap top moved 289->303 and pet
top moved 287->301, both 14px, in lockstep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Red, gold and green crowns for waiting, running and idle, floating just
above the head rather than seated on it.

The art is generated, not hand-placed. pet/crowns-source/ holds the
full-size PNGs and `pnpm generate` box-downsamples them to the 28x22 they
are worn at. Resampling once here rather than letting CSS shrink the big
PNG matters: the browser would otherwise resample by a non-integer factor
against image-rendering: pixelated, and the result is mush at exactly the
sizes the pet is usually shown at.

The filter averages premultiplied alpha. Averaging straight RGBA pulls
the transparent pixels' colour into the edges, which on this art is
black, and the crown comes out with a dark fringe all the way round.

Size, art and the 4px gap live in scripts/lib/crowns.mjs and reach the
stylesheet through the generator, so pet.css carries no crown geometry --
only the cell anchoring, which is the one thing that file knows about.
The per-frame head-tracking from the previous commit is unchanged, so the
crown still bobs with the pet.

Renamed cap -> crown throughout, the earlier name having become wrong.

Also teaches the smoke harness about it. A crown floats in the ring A2
requires to be transparent, so A2 was failing on an assertion it is not
the subject of -- it asks whether the *window* is see-through, and the
crown is app-painted content. Main now reports crownVisible and A2 skips
with a printed reason when one is worn. Skipped rather than narrowed
because the crown's exact band is per animation frame and lives in the
generated stylesheet, so main cannot report a bound worth trusting.
Ordinary runs still check the full ring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Loudest wins across one state file per session, keyed by the hook's
$PPID -- verified by probe to be the session's own claude process, so
the hook stays a one-liner with no jq and no JSON parsing.

The pid is also a liveness handle, which fixes a real defect in the
shipped version: staleness is currently a 15-minute mtime TTL, and a
session sitting at a permission prompt never touches its file, so the
red crown silently vanishes after 15 minutes of waiting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four tasks, only one of which touches the app: the state source reads a
directory of per-session files and reduces them by loudest wins. Task 2
is recorded as an expected no-op, verified rather than assumed -- the
app-shell call site passes no option this changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrites docs/CLAUDE-CODE.md from a setup snippet into something someone
can pick up cold: what it does, what it needs (nothing beyond a shell --
no jq, no daemon, no port), how the hook-to-file-to-crown mechanism
works, how to integrate it, how to verify the transport before blaming
the hooks, a troubleshooting table, and where the code lives.

States the current limits plainly rather than leaving them to be
discovered: one session only, a red crown that expires after 15 minutes
of waiting, and green that never expires -- each linked to the spec and
plan that fix it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants