(remote): light a pip while a remote session writes - #242
Merged
Conversation
remote-watch.js already parses every transcript-write inotify event but threw the rel path away. Wire a second, uncoalesced callback (onActivity) through it so a per-session last-write timestamp reaches the sidebar in near real time, without paying for an ssh refresh per event the way the existing coalesced onEvent does. main.js tracks last-write times per alias+session (remote-activity.js, throttled to 1 IPC message/s/session) and seeds session.remoteActiveAt on every rebuild so a fresh launch paints correctly. The renderer (remote-activity-ui.js, mirroring the session-activity.js split so it can be evaluated standalone in jsdom) lights a dedicated dot next to the existing status dot and clears it 20s after the last sighting.
The per-session IPC throttle kept one entry per session it had ever seen, for the life of the process, while the sightings map was pruned. Both now age out of the same decay window.
This was referenced Sep 10, 2026
Merged
devsuitup
added a commit
that referenced
this pull request
Sep 10, 2026
…ed decay Two bugs in e1e2187: remote-activity-ui.js wrote sessionBusyState directly instead of going through session-activity.js's setActivity dispatcher, so a decayed remote session never picked up the response-ready transition a local one gets; and buildSessionItem seeded busy=true from remoteActiveAt with no decay timer, so a row rendered within the window and never touched again by a live event stayed busy forever. Move the seed into remote-activity-ui.js's seedRemoteActivity(), called once per render before items are built, arming a timer for the remaining window instead of a fresh one. Also repoints every dangling .ai/contexts/session-cache.md comment at the new Remote hosts - busy spinner (issue #242) subsection.
devsuitup
added a commit
that referenced
this pull request
Sep 10, 2026
* fix(sidebar): make the remote activity indicator the local busy spinner A remote session writing its transcript lit a separate violet dot (#242) while a local session busy generating used the braille spinner on .cli-busy. Same signal, two vocabularies. Route the remote-activity event through sessionBusyState and applyActivityClasses — the single declared writer of .cli-busy in session-activity.js — instead of toggling a dedicated element, and drop that element and its CSS. * fix(sidebar): route remote activity through setActivity, fix stuck-seed decay Two bugs in e1e2187: remote-activity-ui.js wrote sessionBusyState directly instead of going through session-activity.js's setActivity dispatcher, so a decayed remote session never picked up the response-ready transition a local one gets; and buildSessionItem seeded busy=true from remoteActiveAt with no decay timer, so a row rendered within the window and never touched again by a live event stayed busy forever. Move the seed into remote-activity-ui.js's seedRemoteActivity(), called once per render before items are built, arming a timer for the remaining window instead of a fresh one. Also repoints every dangling .ai/contexts/session-cache.md comment at the new Remote hosts - busy spinner (issue #242) subsection. * docs: point the remaining activity comments at the busy-spinner section
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The watch channel from #241 already parses every transcript event into
{ kind: 'project', rel }and threwrelaway. That signal arrives in realtime — measured against the live host, one remote session answering a single
question produced 19 of them over nine seconds — and fed nothing visible.
This puts it on screen: a pip that lights while a remote session is writing its
transcript, and goes out on its own when it stops.
Two callbacks, deliberately unequal
start(alias, onEvent, onActivity).onEventstays coalesced at 15 s. It opens an ssh cycle, so it must be.onActivityfires on every project event, uncoalesced. It costs noconnection and no disk — only a map write and, at most once a second per
session, one small IPC message.
Routing activity through the coalesced path would have pinned the pip to the
15 s refresh rhythm, which is exactly what makes it useless as an activity
indicator.
kind: 'session'never reaches it: a descriptor rewrite is a statuschange, not writing.
Nothing rebuilds the sidebar
The renderer gets
{ alias, sessionId, at }and updates that one row. Noprojects rebuild, no morphdom pass — the whole point is that an event costs
nothing. The pip clears 20 s after the last event for that session, one timer
per session, reset on each event, and dropped when the row goes away.
A rebuild for other reasons still paints the pip correctly:
remoteActiveAtrides along on the session object, so a fresh launch or an unrelated refresh
does not blank an active session.
The decision itself lives in
remote-activity.js— a pure module the mainprocess wires thin, testable directly rather than through a replica.
One defect fixed on top of the original commit
The IPC throttle map kept one entry per session it had ever seen, for the life
of the process, while the sightings map was pruned. Both now age out of the
same window, pinned by a test that fills 50 sessions and asserts both maps
shrink to one. Removing the prune turns it red.
Verification
Mutations, each breaking only its own test: the
onActivitycall dropped; thekind === 'project'guard removed; the session-id check relaxed; the IPCthrottle removed; the decay prune disabled; the renderer's decay delay changed;
the initial-paint comparison inverted; the throttle-map prune removed.
One real bug surfaced by the tests and fixed before it shipped: both the
throttle check and
activeAtusedmap.get(k) || <default>, which collapses alegitimate
0timestamp to "absent". Invisible with a real clock, immediateonce the injected clock started at zero.
task check: 1202 pass, 0 fail, 8 pre-existing skips, lint 0 errors.Not verified: the pip's colour was chosen by inspection against the existing
dot palette, not looked at in a running window.