3 Commits
Author SHA1 Message Date
av be69bec17f Make the panel position and the label width settings
Two things that were constants and had no business being constants.

Placement: the box (left, centre, right) and the index within it. The
default is unchanged -- centre, index 1, immediately right of the clock --
and an index past the end of a box lands at the end, so a large one means
"last". Applied at once, no reload.

Moving is done by rebuilding the indicator rather than by moving the actor.
addToStatusArea is what registers it under the uuid and there is no
documented call to move one between boxes; everything else reaches into
Main.panel's private boxes. It costs one re-read of a few small state files
and only when the setting is touched.

Label width: three characters by default, settable up to ten, not down.
Three is enough to keep initials apart and narrow enough not to shove the
clock about; below three, distinct projects start sharing a label. A wider
label takes more initials rather than a longer prefix -- a prefix collapses
dev-skills and dev-conventions at any width -- so dev-skills stays "ds"
however wide the setting, while claude-code-gnome-extension becomes "ccge"
at four. A disambiguating digit still eats into the label instead of
extending past the width, so a chip that gains one does not push the row.

Sticky labels work against that setting: kept labels are kept whatever
width they were cut at, so widening would leave every session on screen at
its old width until it ended. The width is therefore the one thing that
discards the map -- a relabelling that was asked for is not a label moving
under your hand.

The combo row is bound by hand: Gio.Settings.bind maps a boolean to
'active' and an int to 'value', but a string to a selected index needs
bind_with_mapping, which is not introspectable. Only the write direction is
wired up, and the test covers it, because a row that stores its index
instead of its value looks fine until the shell reads the key.

Fixed on the way, found by watching the centre box grow 2 -> 3 -> 4 -> 5
across four moves: PanelMenu.ButtonBox connects `this._onDestroy.bind(this)`
in its _init, and its _onDestroy is what destroys the container -- the
St.Bin the panel box actually holds. The name resolves through the
prototype chain, so this extension's own _onDestroy had been silently
replacing the shell's since the beginning, leaving an empty container in
the panel on every teardown. Renamed to _teardown; the box now stays at two
children across moves and across enable/disable cycles.

Verified in a nested shell on a copy of the extension carrying temporary
logging, since the shell refuses screenshots to non-portal callers: every
box, indices 0, 1 and 9, and widths 3, 5, 8, 10 and back, with no JS errors
and no leftover actors.
2026-08-09 21:56:10 +03:00
av 2565d45bb5 Act on four reviews: state machine, resource bounds, teardown
Four agents reviewed this in parallel -- correctness, GNOME integration,
edge cases, security. Everything below was reproduced before being fixed;
several findings that survived the first reading did not survive a probe
and are not here.

State machine, the two that mattered most. A pending permission prompt was
erased by any subagent bookkeeping event: SubagentStop or the next
PreToolUse recomputed the state from scratch, so a session sat at "working"
with a dialog open and nothing ever raised it again. Blocked now outlives
everything except evidence the question was answered. Separately, the
stale-event guard refused whole events, including the subagent counter's
increments and decrements -- but those are deltas and deltas commute, so a
"+1" that lost a timestamp race left the count short and the batch freed
the session while a subagent was still running. The guard now gates the
state decision only.

Corrupt or hostile state files could wedge the panel or take the hook down
for every session: a non-numeric pid raised inside sweep_dead before the
hook wrote its own file, so one bad byte stopped new sessions appearing at
all. Numbers read back from disk are coerced, one unreadable file no longer
aborts the sweep, and a stored timestamp far in the future -- corruption, or
a clock stepped backwards by NTP -- no longer refuses every later event
forever.

Resource bounds, all in the compositor process. A state file was read whole
with no size check: a symlink to /dev/zero took a test process past 4 GB in
three seconds, which in gnome-shell ends the session. Sizes are checked
before the read, sessions and zellij subprocesses are capped, labels
ellipsize, and cwd and messages are truncated at the hook.

Teardown hung off an overridden destroy(), which only runs when JS calls
it. An actor destroyed any other way -- another extension rebuilding the
panel boxes -- left the timer and the file monitor running against a
disposed actor. It is a destroy signal now. The zellij child is killed
rather than merely abandoned.

The glyph was pinned to physical pixels and rendered half-size on HiDPI;
size comes from the stylesheet, and the foreground colour is normalised by
inspection rather than assuming which colour struct the shell hands back.

Chip labels: non-Latin names all collapsed to "?", because the split
treated every Cyrillic letter as a separator -- notable for a tool whose
own README is Russian. Seniority also ranked by time-in-state rather than
session age, so after a shell restart the older session could take the
digit; the hook now records when the session began.

zellij: a dump ends with new_tab_template and swap_tiled_layout blocks
whose tab lines carry no name, and their panes were being attached to the
last real tab -- which then answered for every unmatched directory,
confidently and wrongly.

install.py no longer widens the mode of a settings.json someone narrowed to
0600, no longer overwrites the pristine .bak on a second run, no longer
replaces a symlink out of a dotfiles repository with a regular file, and
quotes the hook path. The debug log is capped and README now says plainly
that it records prompts verbatim.

Not fixed, deliberately: the panel does push the clock about 70 px left
with three labelled chips, which is inherent to putting them in the centre
box; two different projects abbreviating alike still read as one project
with a digit; GNOME 48 remains unverified for the colour struct and for
St.BoxLayout's vertical property, both flagged rather than guessed at.
2026-08-09 20:20:45 +03:00
av a4e5d653de Show one chip per session, right of the clock
The panel showed a single aggregate: the most urgent session, plus a "+N"
for the others. That answers "what is the worst thing happening", which
is not the question with five projects open -- "what is each of them
doing" needs each of them on screen.

Each session now gets a chip: state glyph plus a three-character project
label, in the centre box just right of the clock. Time in state stays on
the first chip only; five counters side by side are a row of numbers,
not an answer.

Labels are initials for multi-segment names, first letters otherwise.
Initials rather than a prefix, because a prefix collapses dev-skills and
dev-conventions onto the same "dev" -- exactly the pair that has to stay
apart. Collisions, including two sessions in one project where the cwd
is identical, take a digit: ds, ds2, ds3.

Two rules keep a label still, and the feature is worthless without them.
Assignment runs oldest-session-first, so a session starting now takes
the suffix instead of displacing one already on screen; and a label
belongs to its session until it ends, even after whatever forced the
digit has closed. A label that moves under your hand is worse than one
carrying a digit that no longer looks necessary.

The zellij tab name was considered as the label source and dropped. It
arrives asynchronously from dump-layout, so a sticky label would freeze
whatever the project name produced first and never adopt it. Digits
already separate same-project sessions, so the tab name buys nothing
here and stays where it is useful, in the menu.

Menu rows now lead with the same label, so the mapping from "ds" to
dev-skills is read rather than guessed.
2026-08-09 18:45:44 +03:00