Commit Graph
3 Commits
Author SHA1 Message Date
av 626c2b6d2c Merge "idle" into "waiting"
"Idle" was set by SessionStart and by nothing else, and there was no path
back into it. So it never meant "sitting unused" -- it meant "opened and
never asked anything yet", a state a few seconds long that you would
almost never catch. Meanwhile a session that finished an hour ago and was
forgotten showed as waiting, which is correct but leaves the fourth state
with nothing to describe.

A session that has just opened is waiting for your first prompt exactly
as one that finished a turn is waiting for your next. They are the same
thing, and now they are the same state. Three glyphs instead of four,
which also gives the remaining three more room to be told apart in a
monochrome panel.

Files written by the previous hook still say "idle", and a session open
across the upgrade must not disappear, so unrecognised states now read as
waiting rather than being treated as unknown. Covered by a test that
feeds an "idle" file to the store and asserts it comes back as waiting,
sorted by age among the others.

The "hide when nothing is running" setting goes with it. Its condition
was "no sessions, or all of them idle"; with idle gone the second half is
unreachable and the first was already unconditional, so the switch could
no longer change anything. A control that does nothing is worse than no
control.

The compaction test also got stronger in passing: it now checks that a
mid-turn SessionStart leaves a *busy* session alone, which is the case
that matters. It used to assert from waiting, where the state it was
guarding against happened to be the state already stored.
2026-08-09 19:14:34 +03:00
av 839c6f6b52 Drop colour from the panel, and the message that said nothing
Two changes that turned out to be related.

The chips painted their own red, amber and green. An indicator that picks
its own colours competes with the shell's accents and stops following the
theme, so the panel now draws everything in the panel's text colour and
lets shape carry the state. Only alpha still varies, to push idle sessions
back.

That put real weight on the four shapes being distinguishable at 14 px,
which is not something to guess at, so the drawing moved to lib/glyph.js
-- free of St, Clutter and shell imports precisely so the glyphs can be
rendered to a file and looked at. They were, and the first attempt was
wrong twice: a disc filled to the ring's centreline reads smaller than the
ring beside it, and the inner disc of "blocked" was too small to tell from
a plain "working" ring. Radii are now matched at the outer edge and the
inner disc sits at 0.55, both settled by comparing renders.

Separately, hooking a live session showed the Notification that accompanies
a permission prompt carries only "Claude needs your permission" -- no tool,
no command -- and arrives identically when the session is putting a question
to the user rather than asking to run something. The menu was giving that
string the line, pushing out the tab and the path: strictly less information
in more space. It is gone, and the comment claiming it "says what is about
to run" is corrected. Recovering the real command means reading the tail of
the transcript when the prompt fires; it is not in the event.

The same finding settles how far the states can split. "Blocked" cannot be
divided into "asking permission" and "asking a question" from this data.
2026-08-09 19:02:43 +03:00
av 7fc7f63842 Show Claude Code session status in the GNOME panel
Answers one question at a glance: is any session waiting for me, and
which one. With several sessions open the cost is not knowing what each
is doing, it is noticing that one stopped an hour ago.

Claude Code hooks write one JSON file per session under
~/.local/state/claude-code-status; the extension watches the directory
with Gio.FileMonitor, so nothing polls and there is no daemon.

Two distinctions carry the design:

  * blocked (permission prompt) is kept apart from waiting (turn done).
    Merged, a finished task looks as urgent as a stuck one, which is
    exactly the judgement the indicator exists to make.

  * the panel names the oldest session in the top state, not the latest.
    The session you forget is the one that has been waiting longest.

PostToolUse is registered although it looks redundant: it is the only
event that fires after a permission is granted, so without it a session
stays blocked in the panel for the rest of the turn. It writes only on
an actual state change, so the usual case costs no I/O.

Stop and SessionEnd are synchronous, unlike the rest. Both fire as the
process is about to go quiet, and an async hook racing that exit gets
killed before it writes -- claude -p left a session pinned at busy.
Concurrent hooks for one session serialise on an flock plus a timestamp
guard; tests/test-hook.sh covers each separately, because the burst test
passes on the timestamp guard alone.

Sessions running in zellij are located by tab name rather than by path,
matched through dump-layout on the working directory. The dump carries
no pane ids, so ZELLIJ_PANE_ID cannot be used; rows that do not resolve
stay inert instead of pretending a click does something.

lib/sessions.js deliberately imports nothing from the shell resource
namespace, which lets the riskiest logic -- liveness, ordering, partial
reads, monitoring -- run under plain gjs in tests/test-sessions.js.
2026-08-09 18:11:27 +03:00