From b4618bb85307d29cb738f3fc8f3607dcc83f379f Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Tue, 6 Oct 2026 17:55:22 +0200 Subject: [PATCH] feat(tiles): Tiles header button with a session picker, and a tile's + menu A Tiles button beside Split in the header, opt-in through the per-device showTileGridButton setting (read in applyHeaderVisibilitySettings; the settings checkbox, displayKeys membership and schema exclusion follow with persistence) and hard-gated like Split: hidden by its --hidden marker, a JS width check with a live media listener, a @media (max-width: 1179px) backstop and never in a solo window. While the grid is open the button closes it and reads as pressed. Closed, it opens a picker: a checkbox per open session in tab order (never one popped out to its own window; one with no PTY is offered, its tile shows the Attach overlay), names as text, preselected with the grid this tab last left, else the active session and an open split's two. Boxes past what the window can fit are disabled with the count shown, and Open opens the grid on the checked sessions, focusing the active one if checked. Escape and an outside click close it; its close method is idempotent and the global Escape handler calls it. A tile's + lists the open sessions not yet tiled; picking one adds it and focuses it (a human selection). A grid that already holds what the window fits disables the entries. "New session in this case" waits for auto-join. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/web/public/app.js | 2 + src/web/public/constants.js | 24 ++++ src/web/public/index.html | 1 + src/web/public/settings-ui.js | 4 + src/web/public/styles.css | 96 +++++++++++++- src/web/public/tile-grid.js | 240 +++++++++++++++++++++++++++++++++ test/mocks/tile-grid-vm.ts | 9 +- test/tile-grid-picker.test.ts | 241 ++++++++++++++++++++++++++++++++++ 8 files changed, 615 insertions(+), 2 deletions(-) create mode 100644 test/tile-grid-picker.test.ts diff --git a/src/web/public/app.js b/src/web/public/app.js index 5965a181..b9ffd575 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -1369,6 +1369,8 @@ class CodemanApp { if (this.attachmentHistoryDrawerOpen) this.closeAttachmentHistory(); this.closeSessionManager(); this.closeCommandPalette?.(); + this.closeTilePicker?.(); + this.closeTileAddMenu?.(); this.closeShortcutOverlay?.(); // Overlay layouts only: below 1024px the sidebar is a modal off-canvas // drawer over the terminal, so Escape must close it. The docked desktop diff --git a/src/web/public/constants.js b/src/web/public/constants.js index efcb996f..0b0ed6dd 100644 --- a/src/web/public/constants.js +++ b/src/web/public/constants.js @@ -1741,6 +1741,29 @@ function sanitizeTileGridState(raw, liveSessions, detachedIds) { }; } +/** + * The sessions the Tiles picker (and a tile's + menu) offers, in tab order: + * live ones only, never a session popped out to its own window (that window + * owns its PTY size). A session with no PTY attached IS offered: its tile shows + * the Attach overlay. + * + * @param {Map} sessions + * @param {string[]} sessionOrder + * @param {{has(id: string): boolean}} [detachedIds] + * @param {{has(id: string): boolean}} [exclude] - ids not to offer (already tiled) + * @returns {Array<{id: string, label: string}>} + */ +function buildTilePickerSessions(sessions, sessionOrder, detachedIds, exclude) { + const result = []; + for (const id of sessionOrder) { + if (detachedIds?.has?.(id) || exclude?.has?.(id)) continue; + const session = sessions.get(id); + if (!session) continue; + result.push({ id, label: session.name || 'Session' }); + } + return result; +} + /** * Which tile takes focus when `id` leaves the grid: the next one in grid * order, else the previous one, else null. @@ -2050,6 +2073,7 @@ if (typeof window !== 'undefined') { computeTileLayout, tileGridCapacity, sanitizeTileGridState, + buildTilePickerSessions, tileNeighbor, tileInDirection, cycleTile, diff --git a/src/web/public/index.html b/src/web/public/index.html index 2e83db29..0cbbebec 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -192,6 +192,7 @@ +
—