From 98e37bf8955c33041ebb1118ace956a8e272aeaf Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Tue, 18 Aug 2026 16:26:34 +0200 Subject: [PATCH] feat(sidebar): add a rich session sidebar that carries the home screen's row detail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session List Layout gains a third option. The old "Left sidebar" becomes "Left sidebar simple" and is unchanged down to the byte; the new "Left sidebar" puts on each row what the desktop home rail and the phone overview already show: when the session was first created, how long it has been in the state it is in, and a status pill naming that state. A docked column is not a tab strip. It has width to spare and a row per session either way, and "name + folder" is the whole story a TAB can tell, not the whole story there is. This is the information that was missing, and it already existed one surface over. Both sidebar values are the same layout, and both set data-session-list="sidebar"; the row detail rides on a separate data-sidebar-detail attribute. That split is the load-bearing decision here: every one of the ~25 isSessionSidebarActive() call sites and every html[data-session-list="sidebar"] rule in styles.css and mobile.css keeps matching both variants without being touched. A third data-session-list value would have meant auditing and editing all of them. - Stored values: 'header', 'sidebar' (simple), 'sidebar-rich'. Anyone already on 'sidebar' keeps exactly the layout they picked — the rename is label-only. - State classification and the "how long has it been like this" anchor come from _mobileOverviewState() / _mobileOverviewSince(), not re-derived, so the three surfaces cannot disagree about what "working" means. A working pane repaints ~1/s, so its duration is measured from the turn's last Enter: a running turn reads "working 12m", not "0m". - Stamps refresh in place on a 20s clock rather than by re-rendering — a rebuild would restart every load spinner and alert animation in the list, twice a minute. The clock runs only while rich rows are on screen, and is stopped from both render paths and from applySessionListLayout(). - The incremental render path updates the pill, the accent class and the since anchor; a tick alone cannot see a state change, and a new turn re-stamps lastSubmitAt without changing state. - applySessionListLayout() now re-renders on a DETAIL change too. simple <-> rich leaves data-session-list on 'sidebar' both times, and the meta line is emitted by the row template rather than toggled by CSS, so the old layout-only test would have flipped the setting and repainted nothing. - Width: 300px for the extra line. The collapsed 44px rail and the handheld drawer are both explicitly held back from it — the desktop rule is (0,3,1) and would otherwise out-specify mobile.css's (0,2,1) drawer base and pin a 320px phone's drawer to 300px. - Missing/stale mobile-overview.js degrades to a row with no meta line rather than throwing and taking the whole tab strip down. 15 new tests cover the attribute split, the solo-window override, the detail-change re-render, the row model, both render paths, the clock lifecycle and the mobile width guard. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/rich-session-sidebar.md | 13 ++ src/web/public/app.js | 265 +++++++++++++++++++++++++++-- src/web/public/i18n.js | 5 +- src/web/public/index.html | 7 +- src/web/public/mobile.css | 13 ++ src/web/public/settings-ui.js | 4 + src/web/public/styles.css | 134 +++++++++++++++ src/web/schemas.ts | 13 +- test/session-list-layout.test.ts | 252 ++++++++++++++++++++++++++- 9 files changed, 682 insertions(+), 24 deletions(-) create mode 100644 .changeset/rich-session-sidebar.md diff --git a/.changeset/rich-session-sidebar.md b/.changeset/rich-session-sidebar.md new file mode 100644 index 00000000..49276c01 --- /dev/null +++ b/.changeset/rich-session-sidebar.md @@ -0,0 +1,13 @@ +--- +'aicodeman': patch +--- + +Session List Layout gains a third option, "Left sidebar", whose rows carry the same per-session detail the home screen shows. + +The sidebar previously had one row style: a name and a folder. That is the whole story a tab can tell, but a docked column is not a tab strip — it has width to spare and a row per session either way, and the information that was missing is exactly the information the desktop home rail and the phone overview already put on screen. So the new option lifts it onto the rows: when the session was first created, how long it has been in the state it is in, and a status pill naming that state. + +- The old "Left sidebar" is now **"Left sidebar simple"** and is unchanged, down to the byte — the stored value stays `sidebar`, so anyone already using it keeps exactly the layout they chose. The new option is `sidebar-rich`. +- Both sidebar values are the SAME layout and both set `data-session-list="sidebar"`; row detail rides on a separate `data-sidebar-detail` attribute. That is deliberate: every `isSessionSidebarActive()` call site and every `html[data-session-list="sidebar"]` rule in styles.css and mobile.css keeps matching both, untouched. +- Which state a session is in, and which stamp measures it, come from `_mobileOverviewState()` / `_mobileOverviewSince()` rather than being re-derived — the sidebar, the home rail and the phone overview cannot disagree about what "working" means. A working row is measured from the turn's last Enter, not from its last repaint, so a running turn reads `working 12m` instead of `0m`. +- The stamps refresh in place on a 20s clock instead of re-rendering: a rebuild would restart every load spinner and alert animation in the list, twice a minute. The clock only runs while rich rows are on screen. +- The column widens to 300px for the extra line, and the collapsed 44px rail and the handheld drawer are explicitly held back from that width. diff --git a/src/web/public/app.js b/src/web/public/app.js index 49dd6503..7c8297ca 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -481,6 +481,14 @@ const DEFAULT_SHORTCUTS = [ // CodemanApp Class — constructor and global state // ═══════════════════════════════════════════════════════════════ +/** + * How often the rich sidebar rewrites its relative stamps in place. Matches the + * two home screens (mobile-overview.js, home-sessions.js). Deliberately a local + * const and not a constants.js export: an undefined interval would make + * setInterval fire on every frame, and constants.js is cached independently. + */ +const SIDEBAR_RICH_CLOCK_MS = 20000; + class CodemanApp { constructor() { this.sessions = new Map(); @@ -3704,18 +3712,26 @@ class CodemanApp { // ═══════════════════════════════════════════════════════════════ /** - * 'header' | 'sidebar'. Solo (detached single-session) windows are ALWAYS - * 'header': they show exactly one session, so a session list is noise — and - * #sessionTabs must never be parked inside the display:none