mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
feat(tiles): the grid controller, and the main terminal parked while it is open
tile-grid.js (load order 7.6) adds the grid to CodemanApp: openTileGrid, closeTileGrid, addTile, removeTile and _selectTiledSession, over a <section class="tile-grid"> that is a SIBLING of .terminal-wrap and takes its place under .main.tiles-active. Every tile is a TerminalTile with the grid's one load queue, TILE_SCROLLBACK, a bounded load and its own per-device font size (codeman-tile-font-size; Ctrl +/- sizes the tiles while the grid is open). Layout comes from computeTileLayout; one ResizeObserver on the section refits each tile (xterm and PTY together) on the trailing edge. Opening parks the main terminal: _cleanupPreviousSession runs once (its snapshot is right at that moment, and it closes the main socket), and activeSessionId always names the focused tile's session, so the panels follow focus. With the main socket closed, every main-terminal path that would write the focused tile's output into the hidden xterm, fetch a capture for it, resize it or reopen its socket now stands aside through _tilesOwnTerminal(): the SSE terminal, clear and refresh handlers, the dropped-output recovery, the completion/error writelns, retryConnection and handleInit (both re-arm the tiles instead; handleInit keeps live tiles and drops dead ones), sendResize, throttledResize, the history re-pull, and the WebGL long-task observer, which watches the whole page and must not count tile renders toward the main terminal's sticky WebGL disable. The header connection state comes from the tile sockets. Closing destroys every tile, invalidates the main terminal's cached content (snapshot, codeman-xs key, buffer cache) for every tiled id, since it predates the grid, and replays the focused session fresh in the single view. _focusedPane() answers with the focused tile and _forEachTile reaches every grid tile. A tile whose socket stops for good is removed (4003, 4004, 4010) or keeps its "session ended" marker (4009). No entry point yet: the grid is opened from the shortcut registry in a later commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1328,6 +1328,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
// Same yield as sendResize: never resize a PTY whose session is showing
|
||||
// in its own window. Dragging the dashboard's border must not reshape it.
|
||||
const detachedElsewhere = !this.isSoloWindow && this.detachedSessions?.has(this.activeSessionId);
|
||||
// The tile grid parks the main terminal: its session is sized by its
|
||||
// tile, which this same timer refits below (_forEachTile).
|
||||
const tilesOwnTerminal = this._tilesOwnTerminal?.();
|
||||
// ⚠️ Whether to fit is the SAME question as whether to send (issue #464).
|
||||
// This block used to fit unconditionally and skip only the SIGWINCH,
|
||||
// which is the one combination that cannot be right: it moves xterm to
|
||||
@@ -1335,7 +1338,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
// repaints from the shape it was told. Withhold both, or neither —
|
||||
// a reflow nothing is rendering for buys nothing and costs correctness.
|
||||
const dims =
|
||||
this.activeSessionId && !keyboardUp && !detachedElsewhere ? this._geometryForResizeRequest() : null;
|
||||
this.activeSessionId && !keyboardUp && !detachedElsewhere && !tilesOwnTerminal
|
||||
? this._geometryForResizeRequest()
|
||||
: null;
|
||||
// ⚠️ A null measurement is NOT a reason to report the floor. It used to
|
||||
// fall back to a bare 40x10, which tells the PTY a shape nothing measured
|
||||
// and xterm does not hold — the write-only guess this whole change exists
|
||||
@@ -4512,7 +4517,12 @@ Object.assign(CodemanApp.prototype, {
|
||||
* it from its own terminal's focus; the primary terminal's focus gives it back.
|
||||
*/
|
||||
_focusedPane() {
|
||||
const tile = this._focusedTile;
|
||||
let tile = this._focusedTile;
|
||||
// With the tile grid open the main terminal is parked, so the pane is the
|
||||
// focused tile even when DOM focus sits on a button or a panel.
|
||||
if ((!tile || tile._destroyed || !tile.terminal) && this._tilesOwnTerminal?.()) {
|
||||
tile = this._tileFor(this.activeSessionId);
|
||||
}
|
||||
if (tile && !tile._destroyed && tile.terminal) {
|
||||
return { terminal: tile.terminal, sessionId: tile.sessionId, isPrimary: false, tile };
|
||||
}
|
||||
@@ -4525,13 +4535,19 @@ Object.assign(CodemanApp.prototype, {
|
||||
},
|
||||
|
||||
/**
|
||||
* Run `fn(tile)` for every secondary terminal pane on screen: today the split
|
||||
* pane's second terminal, when one is open. Font, weight, family and skin
|
||||
* changes go through here so they reach every pane without a special case
|
||||
* per pane kind. Agent Teams terminals size themselves and are not tiles.
|
||||
* Run `fn(tile)` for every secondary terminal pane on screen: the split
|
||||
* pane's second terminal and every tile of the tile grid (never both: the two
|
||||
* modes are not open together). Font, weight, family and skin changes go
|
||||
* through here so they reach every pane without a special case per pane kind.
|
||||
* `{ grid: false }` skips grid tiles (they keep their own font size).
|
||||
* Agent Teams terminals size themselves and are not tiles.
|
||||
*/
|
||||
_forEachTile(fn) {
|
||||
_forEachTile(fn, { grid = true } = {}) {
|
||||
if (this._splitPane?.terminal) fn(this._splitPane);
|
||||
if (!grid || !this._tileGrid?.open) return;
|
||||
for (const { tile } of this._tileGrid.tiles.values()) {
|
||||
if (tile.terminal) fn(tile);
|
||||
}
|
||||
},
|
||||
|
||||
// Clears the pane the keyboard is in. The chord itself also reaches that
|
||||
@@ -4614,6 +4630,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
this.showToast('No active session', 'warning');
|
||||
return;
|
||||
}
|
||||
// Backstop: _focusedPane() answers with the focused tile while the grid is
|
||||
// open, so this is reached only if that tile is gone mid-call.
|
||||
if (this._tilesOwnTerminal?.()) return;
|
||||
|
||||
// The pane belongs to the popup showing it, so this window has nothing to
|
||||
// restore. Say so rather than reporting a size that was never sent — the
|
||||
@@ -5742,11 +5761,20 @@ Object.assign(CodemanApp.prototype, {
|
||||
},
|
||||
|
||||
increaseFontSize() {
|
||||
// With the tile grid open, Ctrl +/- sizes the tiles (their own font size).
|
||||
if (this._tilesOwnTerminal?.()) {
|
||||
this.setTileFontSize(Math.min(this._tileGridFontSize() + 2, 24));
|
||||
return;
|
||||
}
|
||||
const current = this.terminal.options.fontSize || 14;
|
||||
this.setFontSize(Math.min(current + 2, 24));
|
||||
},
|
||||
|
||||
decreaseFontSize() {
|
||||
if (this._tilesOwnTerminal?.()) {
|
||||
this.setTileFontSize(Math.max(this._tileGridFontSize() - 2, 10));
|
||||
return;
|
||||
}
|
||||
const current = this.terminal.options.fontSize || 14;
|
||||
this.setFontSize(Math.max(current - 2, 10));
|
||||
},
|
||||
@@ -5759,10 +5787,13 @@ Object.assign(CodemanApp.prototype, {
|
||||
// Update overlay font cache and re-render at new cell dimensions
|
||||
this._localEchoOverlay?.refreshFont();
|
||||
this._predictiveEcho?.refreshFont();
|
||||
this._forEachTile?.((tile) => {
|
||||
tile.terminal.options.fontSize = size;
|
||||
tile.fit(); // a font change is a size change: tell its PTY too (#464)
|
||||
});
|
||||
this._forEachTile?.(
|
||||
(tile) => {
|
||||
tile.terminal.options.fontSize = size;
|
||||
tile.fit(); // a font change is a size change: tell its PTY too (#464)
|
||||
},
|
||||
{ grid: false }
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -6066,6 +6097,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
// settle-time refit through this call and has no fallback, which is correct:
|
||||
// a pane it does not own is not its to refit either.
|
||||
if (!this.isSoloWindow && this.detachedSessions?.has(sessionId)) return false;
|
||||
// Backstop: while the tile grid owns the terminal, a tile sizes this PTY and
|
||||
// the parked main terminal measures nothing worth sending.
|
||||
if (this._tilesOwnTerminal?.()) return false;
|
||||
// Fit, floor, and apply in one step so the numbers below are the numbers
|
||||
// xterm is actually holding (or, while another device holds the width,
|
||||
// the numbers this container would hold if the PTY followed).
|
||||
|
||||
Reference in New Issue
Block a user