Skip to content

Sidebar: fix scrollbar artifacts, keep sessions with pending work "running" - #322

Merged
pufit merged 3 commits into
mainfrom
pufit/sidebar-parked-sessions
Aug 20, 2026
Merged

Sidebar: fix scrollbar artifacts, keep sessions with pending work "running"#322
pufit merged 3 commits into
mainfrom
pufit/sidebar-parked-sessions

Conversation

@pufit

@pufit pufit commented Aug 19, 2026

Copy link
Copy Markdown
Member

Three sidebar fixes from one report: a stray white dot, a horizontal scrollbar, and sessions dropping out of "Running" the moment their turn ended.

1. The white dot in the panel corner

index.css styles the scrollbar, its track and its thumb — but never ::-webkit-scrollbar-corner. Where a horizontal and a vertical scrollbar meet, WebKit painted that square with its own near-white default: a bright dot on a dark panel. Now transparent, like the track.

2. The horizontal scrollbar

Not cosmetic — a sizing bug. The ... load-more row was w-full plus mx-1, i.e. 100% + 8px, so the list overflowed by exactly those 8px (measured live: clientWidth 233, scrollWidth 241). Sized to calc(100% - 0.5rem), and the list is pinned to overflow-x-hidden: it only ever scrolls vertically and rows truncate, so nothing was reachable only by scrolling sideways.

3. Sessions with pending work stay "running" — and stay put

A session that scheduled a wakeup, or left a background job in flight, is not idle: it will pick itself back up. Two problems today:

  • the moment its turn ended it fell out of the pinned Running group, and
  • when the continuation landed, the assistant message bumped updated_at and yanked the session to the top of the list — reordering the panel for work the user never triggered.

Each row (and each session_running event) now carries two live bits:

field source meaning
pending_wakeup_at session_wakeups, status='pending' fire time of the one wakeup a session can have pending
has_background_tasks the engine's per-session background-task registry run_in_background Bash/Agent work still live in its CLI

A row with either is parked: it stays in the Running group and shows a violet pulse instead of the emerald one, so "working right now" still reads apart from "will resume on its own". Violet was the only free hue — blue is awaiting your input, orange review loop needs you, emerald running, red error, yellow stopped, accent unread. A tooltip names what it is waiting on (Background job running / Scheduled wake-up at 03:24 PM). Ranked below the error dot on purpose: a failed turn must not be masked by the background work it left behind.

Shipping both bits with the transition (not just on the list route) matters because the client deliberately skips the list refetch for the active session.

updated_at is now frozen for turns nobody asked for — internal=True runs (fired wakeup, restart continuation, star hook) and autonomous drains (a background task settling). The message row is still recorded and message_count still advances; only the sidebar's sort key is left alone. A turn the user actually sent bumps it as before.

Notes

  • GET /api/sessions/search was duplicating _decorate inline; it now calls it, so search rows get the same treatment (including review-loop state) for free.
  • Non-active sessions' background-task state is eventually consistent: background_tasks_update is broadcast on the session channel only, so a task that settles without triggering a continuation turn leaves the dot up until the next list refresh. Making that event global would close it — deliberately out of scope here.

Testing

  • pytest tests/ — 3351 passed (15 new in tests/test_parked_sessions.py: the updated_at freeze at the store level and through feed ordering, the engine's pending-work lookup + broadcast payload, and the two fields on every sidebar list route).
  • npm test — 114 passed (5 new for the client handler that applies the fields).
  • npm run lint — unchanged (148 pre-existing problems before and after).
  • Verified in the browser: the 8px overflow and the corner dot are gone; parked rows render violet inside Running, clearly distinct from the green ones.

Generated by Nerve

@pufit
pufit requested a review from alex-clickhouse August 19, 2026 20:24
Comment thread nerve/agent/engine.py Outdated
# wakeup, a restart continuation, a star hook): they resume work the
# session was already doing, so they must not re-sort the sidebar.
await self._finalize_turn(
session_id, st, channel, bump_updated_at=not internal,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to add bump_updated_at as a param to run() instead of overloading internal here. A couple of things that won't be bumped, when I think they should:

Both of those are user-requested actions that should bump updated_at.

Alternatively, we could just explicitly bump updated_at at both of those sites and leave the internal check as it is?

pufit added 3 commits August 20, 2026 11:49
The '...' load-more row was sized `w-full` *plus* `mx-1`, i.e. 100% + 8px:
the session list overflowed by exactly those 8px and grew a horizontal
scrollbar. Where that scrollbar met the vertical one, WebKit painted
::-webkit-scrollbar-corner with its own near-white default — the bright dot
in the panel's bottom-right corner.

Size the row to `calc(100% - 0.5rem)`, paint the corner transparent like the
track, and pin the list to overflow-x-hidden: it only ever scrolls
vertically, and rows truncate, so nothing is reachable only by scrolling
sideways.
A session that schedules a wakeup or leaves a background job in flight is
not idle: it will pick itself back up. But the moment its turn ended the
sidebar dropped it out of the Running group, and when the continuation
landed the assistant row bumped updated_at and yanked the session to the
top of the list — reordering the panel for work the user never triggered.

Sessions now carry two live bits on every row and every session_running
transition: pending_wakeup_at (the fire time of the one pending wakeup a
session can have) and has_background_tasks (live run_in_background work in
its CLI). A row with either is "parked": it stays in the Running group and
shows a violet pulse instead of the emerald one, so "working right now"
still reads apart from "will resume on its own". A tooltip names which.

updated_at is now frozen for turns nobody asked for — internal runs (fired
wakeup, restart continuation, star hook) and autonomous drains (a
background task settling). The message row is still recorded and still
counts; only the sidebar's sort key is left alone. A turn the user actually
sent bumps it as before.

Tests: updated_at freeze at the store level and through the feed ordering,
the engine's pending-work lookup and broadcast payload, the two fields on
every sidebar list route, and the client handler that applies them.
…ernal`

Review catch: `internal=True` only means "don't persist the trigger
message" — it does not mean "the user didn't ask for this". Deriving the
freeze from it quietly stopped two user-requested turns from surfacing:

* the star-project hook, fired because the user just starred the session;
* a run-later deferral, delivering a message the user wrote and scheduled.

`run()` / `_run_inner()` now take `bump_updated_at` (default True), so a
freeze is something a call site asks for rather than a side effect of an
unrelated flag. Only two callers ask: the wakeup sweep for model-scheduled
ScheduleWakeup ticks (run-later deferrals keep bumping), and the autonomous
drain when a background task settles. Everything else — the star hook,
restart continuations, expired-question injections — bumps exactly as it
did before this branch.
@pufit
pufit force-pushed the pufit/sidebar-parked-sessions branch from af4b74c to e2d3126 Compare August 20, 2026 15:51
@pufit
pufit merged commit 1985e37 into main Aug 20, 2026
2 checks passed
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