feat: order the session list by attention, not by when you created it - #300
Merged
Conversation
The sidebar sorted by createdAt — under a comment that read "Sorted list — most recent activity first". The intent was always recency; the code delivered creation order. A session made last week but driven all morning sat at the bottom, which is why the order looked arbitrary. It could not be fixed client-side. The daemon has always tracked `#lastActivityAt` (it orders the resumed session list by it) but never put it on the wire, and `SessionInfo` carried only createdAt. web/src/state/messages.ts has a `lastActivityAt`, but it is derived from messages THIS client received, so it is 0 for every session you have not opened — a trap for the obvious fix. Adds `lastActivityAt` to SessionInfo (optional; clients fall back to createdAt so an older daemon does not sink every session to epoch 0) and bands the list: NEEDS YOU waiting_approval, error WORKING thinking, tool_running IDLE everything else, most recently active first Bands rather than one flat multi-key sort, for three reasons. It is already this project's answer — conductor-frontends-design.md §4 locks a state-grouped list as the fleet view's primary, and a second ordering vocabulary in the same client would be a bug in itself. It absorbs thrash: thinking and tool_running alternate several times a second and both are WORKING, so nothing moves, where a flat sort keyed on status would jitter continuously. And it makes movement legible — a row crossing under a NEEDS YOU header reads as a state change rather than a glitch. Two details that matter more than the sort: - A fleet bands and dates as ONE unit, by the most urgent state and the most recent activity anywhere in it. An orchestrator sits idle while its children work, so banding on the lead alone would file a fleet whose child is blocked on an approval under IDLE — exactly the case NEEDS YOU exists to surface. - Ordering is held while the pointer is over the list and applied on leave. A row that moves between aiming and clicking opens the wrong session. Membership is deliberately NOT held: a destroyed session must disappear, since leaving it clickable trades a misclick for a worse one. Ordering stays client-side. The daemon owns the state and exposes the field; each client decides how to render it, which is what lets the TUI and mobile band differently later. Mirrored in the Rust crate. 13 new tests over the pure ordering functions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rsharath
approved these changes
Aug 23, 2026
akhiljavelin
approved these changes
Aug 23, 2026
saucam
added a commit
that referenced
this pull request
Aug 29, 2026
#306) Both timestamps were stamped unconditionally in the Session constructor, so every restart re-dated every resumed session to the restart moment: this.createdAt = new Date().toISOString(); this.#lastActivityAt = this.createdAt; Two consequences. A weeks-old session reported as brand new. And because the session list's recency key is `lastActivityAt ?? createdAt`, every session tied on the same instant — so the attention ordering added in #300 collapsed back to insertion order on the first restart, which is exactly what it was built to replace. The values were already persisted in TranscriptMeta (createdAt, lastActivityAt) and already read: SessionManager's `resumeSortKey` sorts the resume pass by `meta.lastActivityAt`. So the manager iterated in the right order and each Session then overwrote the timestamps a moment later — the correct data was on disk, read, and discarded. SessionCreateOptions now carries both as optional fields, the constructor prefers them over `now`, and the resume call site passes the meta values. A new session is unaffected (no opts -> stamped fresh, activity falls back to creation), and meta written before `lastActivityAt` existed degrades to `createdAt` rather than to `now` — an ancient session must not sort as the most recently active one. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Fixes the "session panel order seems mostly random" report.
Why it looked random
web/src/state/sessions.tssorted bycreatedAt— under a comment reading "Sorted list — most recent activity first." The intent was always recency; the code delivered creation order. A session made last week but driven all morning sat at the bottom.It could not be fixed client-side. The daemon has always tracked
#lastActivityAt— it already orders the resumed session list by it — but never put it on the wire;SessionInfocarried onlycreatedAt. There is alastActivityAtinweb/src/state/messages.ts, but it's derived from messages this client received, so it's0for every session you haven't opened. That's a trap for the obvious fix, so it's worth knowing it's there.The design
Adds
lastActivityAttoSessionInfo(optional — clients fall back tocreatedAt, so an older daemon doesn't sink every session to epoch 0) and bands the list:Bands rather than one flat multi-key sort, for three reasons:
conductor-frontends-design.md§4 locks a state-grouped list as the fleet view's primary, partly because it renders identically in Solid and ratatui. A second ordering vocabulary in the same client would be a bug in itself.thinkingandtool_runningalternate several times a second; both are WORKING, so nothing moves. A flat sort keyed on status would jitter continuously.errorbands as NEEDS YOU, not IDLE — a failed session is the other thing wanting a human, and burying it under idle sessions is how a failure goes unnoticed for an hour.The two details that matter more than the sort
idlewhile its children work, so banding on the lead alone would file a fleet whose child is blocked on an approval under IDLE: exactly the case NEEDS YOU exists to surface.Ordering stays client-side: the daemon owns state and exposes the field, each client decides how to render it. That's what lets the TUI and mobile band differently later.
Companion
highflame-ai/codeoid-ui — mirrors the field. The TUI doesn't order by it yet; that lands with P5.1 so web and TUI adopt one vocabulary rather than drifting.
Verification
web/src/lib/session-order.test.ts), covering band assignment, fleet roll-up, the createdAt fallback, an unparseable timestamp, determinism for same-millisecond sessions, and hold-under-pointer including the membership carve-outcargo fmt/ clippy clean; web build cleanFollow-ups I did not fold in
Pinning (a manual override for long-lived sessions) and a collapsed "N idle" roll-up at fleet scale are both natural next steps, but they're UI features rather than fixes to the reported problem.