COD-139 add session pinning (float pinned sessions to top of session manager list)

Pin/unpin a session via POST /api/sessions/:id/pin {pinned}; pinned sessions
sort above unpinned in the unified session manager list (COD-121), ordered by
pinnedAt descending. Pin state lives on SessionState, persists to state.json,
and survives reload/reconnect/restart (persisted-input carries pinned; the
merge skips undefined so a recovered live session can't clobber it). New SSE
event session:pinned re-sorts the open list live across clients. Pin/Unpin
affordance in the session-row kebab menu with a 📌 glyph + amber highlight.

(cherry picked from commit 82749747039afcd4a3104f6a97ce7d3c2ddd048d)
This commit is contained in:
Aamer Akhter
2026-07-17 16:25:29 -04:00
parent 8807b3ff6d
commit 5792c2d62e
13 changed files with 413 additions and 3 deletions
+27
View File
@@ -336,6 +336,11 @@ export class Session extends EventEmitter {
// Image watcher setting (per-session toggle)
private _imageWatcherEnabled: boolean = false;
// Pin state (COD-139) — pinned sessions float to the top of the session
// manager list, ordered by pinnedAt descending (most-recently-pinned first).
private _pinned: boolean = false;
private _pinnedAt: number | null = null;
// Flicker filter setting (per-session toggle, applied on frontend)
private _flickerFilterEnabled: boolean = false;
@@ -957,6 +962,26 @@ export class Session extends EventEmitter {
this._imageWatcherEnabled = enabled;
}
/** Whether this session is pinned to the top of the session manager (COD-139). */
get pinned(): boolean {
return this._pinned;
}
/** When the session was pinned (epoch ms), or null when unpinned. */
get pinnedAt(): number | null {
return this._pinnedAt;
}
/**
* Set pin state (COD-139). Pinning stamps pinnedAt with now so the pinned
* group orders most-recently-pinned first; unpinning clears it. Idempotent:
* re-pinning an already-pinned session refreshes its pinnedAt.
*/
setPinned(pinned: boolean): void {
this._pinned = pinned;
this._pinnedAt = pinned ? Date.now() : null;
}
get flickerFilterEnabled(): boolean {
return this._flickerFilterEnabled;
}
@@ -1021,6 +1046,8 @@ export class Session extends EventEmitter {
autoResumeEnabled: this._autoOps.autoResumeEnabled,
autoResumeAt: this._autoOps.autoResumeAt ?? undefined,
imageWatcherEnabled: this._imageWatcherEnabled,
pinned: this._pinned || undefined,
pinnedAt: this._pinned ? (this._pinnedAt ?? undefined) : undefined,
totalCost: this._totalCost,
inputTokens: this._totalInputTokens,
outputTokens: this._totalOutputTokens,