mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
feat(tiles): the grid survives a reload (per device, ids only)
The grid is stored in localStorage `codeman:tile-grid` as
{ v: 1, open, ids, focused, zoomed, colFr, rowFr }: ids, focus, a zoom the
user chose (an automatic one is worked out again from the window) and the
divider fractions, never content. It is written as it changes (layout, focus,
zoom, divider drags); closing the grid keeps it remembered as open: false for
one-click return, and the last tile leaving forgets it. That stored state is
now the only "remembered" grid, so the Tiles toggle, the picker's preselection
and Ctrl/Cmd+click all bring back the grid this device last had, across
reloads. Never written or read in a solo window.
The restore runs INSIDE handleInit, in place of its single-view
selectSession(restoreId, { auto: true }), so with a stored open grid the main
terminal never loads on that page load (its first select would pull a
whole-history capture only to be parked). The stored ids are sanitized against
the session list (deleted, detached and duplicate ids dropped, anything that
is not a v1 object ignored), and the fractions and zoom go back on. A window
too narrow for the grid keeps the single view and the stored grid waits; a
#session= link on load wins and leaves the grid remembered but closed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4667,6 +4667,9 @@ class CodemanApp {
|
||||
|
||||
// A `#session=<id>` link wins over restoring the last active tab.
|
||||
if (this._urlSessionId && this.sessions.has(this._urlSessionId)) {
|
||||
// And over a tile grid stored open: it stays remembered, closed, rather
|
||||
// than reappearing unexplained on the next reload.
|
||||
if (!tilesOpen) this._closeStoredTileGrid?.();
|
||||
this.activeSessionId = null;
|
||||
this._selectUrlSession();
|
||||
return;
|
||||
@@ -4702,6 +4705,11 @@ class CodemanApp {
|
||||
this._splitPane?.reconnectNow?.();
|
||||
} else {
|
||||
this.activeSessionId = null;
|
||||
// A tile grid stored open on this device (tile-grid.js) comes back IN
|
||||
// PLACE of the single-view restore below, so the main terminal never
|
||||
// loads (its first select would pull a whole-history capture only to
|
||||
// be parked a moment later).
|
||||
if (this._restoreTileGrid?.()) return;
|
||||
// `auto`: the app is restoring a session on load, not a human opening
|
||||
// one, so a pending idle alert on that tab stays armed until it is
|
||||
// actually tapped (see the userInitiated note in selectSession).
|
||||
|
||||
+116
-9
@@ -23,6 +23,10 @@
|
||||
|
||||
// Per-device tile font size (a tile is a fraction of the screen).
|
||||
const TILE_GRID_FONT_KEY = 'codeman-tile-font-size';
|
||||
// The grid this device last had, ids only (sanitizeTileGridState, constants.js):
|
||||
// `{ v: 1, open, ids, focused, zoomed, colFr, rowFr }`. `open: false` keeps it
|
||||
// remembered for one-click return; restored on reload inside handleInit.
|
||||
const TILE_GRID_STORAGE_KEY = 'codeman:tile-grid';
|
||||
// Trailing debounce for refitting tiles after the grid area changes size, so a
|
||||
// window drag sends each tile's PTY one resize, not one per frame.
|
||||
const TILE_GRID_REFIT_MS = 150;
|
||||
@@ -238,7 +242,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (!grid?.open) return;
|
||||
const focusedId = grid.focusedId;
|
||||
const ids = grid.ids.slice();
|
||||
this._tileGridRemembered = keepStored ? { ids, focusedId } : null;
|
||||
// Remembered (closed) for one-click return, or forgotten.
|
||||
if (keepStored) this._persistTileGrid({ open: false });
|
||||
else this._forgetStoredTileGrid();
|
||||
// A divider drag in progress ends with the grid.
|
||||
this._tileDividerDragTeardown?.();
|
||||
// Closed BEFORE the tiles go: each destroy() updates the header's connection
|
||||
@@ -355,7 +361,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
const T = window.CodemanTileGrid;
|
||||
const capacity = Math.max(1, Math.min(this._tileGridCapacityNow(), T.TILE_GRID_MAX));
|
||||
const candidates = T.buildTilePickerSessions(this.sessions, this.sessionOrder, this.detachedSessions);
|
||||
const remembered = (this._tileGridRemembered?.ids || []).filter((id) => candidates.some((c) => c.id === id));
|
||||
const remembered = (this._readStoredTileGrid()?.ids || []).filter((id) => candidates.some((c) => c.id === id));
|
||||
const seed = remembered.length
|
||||
? remembered
|
||||
: [this.activeSessionId, this._splitPane ? this._splitSessionId : null].filter(Boolean);
|
||||
@@ -590,10 +596,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
return;
|
||||
}
|
||||
if (!this.canOpenTileGrid()) return;
|
||||
const remembered = this._tileGridRemembered;
|
||||
const ids = (remembered?.ids || []).filter((id) => this.sessions.has(id) && !this.detachedSessions?.has(id));
|
||||
if (ids.length > 0) {
|
||||
this.openTileGrid(ids, { focusedId: remembered.focusedId, auto: true });
|
||||
const remembered = this._readStoredTileGrid();
|
||||
if (remembered?.ids.length) {
|
||||
this._openStoredTileGrid(remembered);
|
||||
return;
|
||||
}
|
||||
if (this.activeSessionId) this.openTileGrid([this.activeSessionId], { focusedId: this.activeSessionId });
|
||||
@@ -844,9 +849,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
this.selectSession(sessionId);
|
||||
return true;
|
||||
}
|
||||
const remembered = (this._tileGridRemembered?.ids || []).filter(
|
||||
(id) => this.sessions.has(id) && !this.detachedSessions?.has(id)
|
||||
);
|
||||
const remembered = this._readStoredTileGrid()?.ids || [];
|
||||
const base = remembered.length ? remembered : [this.activeSessionId].filter(Boolean);
|
||||
const ids = [...base.filter((id) => id !== sessionId).slice(0, capacity - 1), sessionId];
|
||||
this.openTileGrid(ids, { focusedId: sessionId, auto: false });
|
||||
@@ -1250,6 +1253,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
});
|
||||
this._syncTileDividers(zoomed ? 0 : cols, zoomed ? 0 : rows);
|
||||
this._syncTileSlots(zoomed ? 0 : cols * rows - grid.ids.length, cols);
|
||||
this._persistTileGrid();
|
||||
},
|
||||
|
||||
// The empty cells of a layout that is not full (3 tiles in a 2x2, 5 in a
|
||||
@@ -1387,6 +1391,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
for (const tile of affected) {
|
||||
if (!tile._destroyed) tile.fit();
|
||||
}
|
||||
this._persistTileGrid();
|
||||
};
|
||||
divider.classList.add('dragging');
|
||||
document.body.classList.add('tile-grid-resizing', `tile-grid-resizing--${axis}`);
|
||||
@@ -1514,6 +1519,108 @@ Object.assign(CodemanApp.prototype, {
|
||||
// The keyboard follows focus: shortcuts, voice and paste act on this tile.
|
||||
this._noteFocusedTile(entry.tile);
|
||||
if (options.focus !== false) entry.tile.terminal?.focus();
|
||||
this._persistTileGrid();
|
||||
},
|
||||
|
||||
// ── Persistence (codeman:tile-grid, per device, ids only) ────────────────
|
||||
|
||||
/**
|
||||
* Writes the open grid: ids, focus, a zoom the user chose (an automatic one
|
||||
* is worked out again from the window) and the divider fractions. Never
|
||||
* content. `open: false` is the closed-but-remembered state. Never in a solo
|
||||
* window; a storage failure only costs the convenience.
|
||||
*/
|
||||
_persistTileGrid({ open = true } = {}) {
|
||||
const grid = this._tileGrid;
|
||||
if (this.isSoloWindow || !grid || grid.ids.length === 0) return;
|
||||
if (open && !grid.open) return;
|
||||
const state = {
|
||||
v: 1,
|
||||
open,
|
||||
ids: grid.ids.slice(),
|
||||
focused: grid.focusedId,
|
||||
zoomed: grid.autoZoom ? null : grid.zoomedId,
|
||||
colFr: grid.colFr.slice(),
|
||||
rowFr: grid.rowFr.slice(),
|
||||
};
|
||||
try {
|
||||
localStorage.setItem(TILE_GRID_STORAGE_KEY, JSON.stringify(state));
|
||||
} catch {
|
||||
/* Per-device convenience only. */
|
||||
}
|
||||
},
|
||||
|
||||
_forgetStoredTileGrid() {
|
||||
try {
|
||||
localStorage.removeItem(TILE_GRID_STORAGE_KEY);
|
||||
} catch {
|
||||
/* Nothing stored. */
|
||||
}
|
||||
},
|
||||
|
||||
/** The stored grid, sanitized against the sessions this page knows now, or null. */
|
||||
_readStoredTileGrid() {
|
||||
if (this.isSoloWindow) return null;
|
||||
let raw = null;
|
||||
try {
|
||||
raw = localStorage.getItem(TILE_GRID_STORAGE_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (!raw) return null;
|
||||
return window.CodemanTileGrid.sanitizeTileGridState(raw, this.sessions, this.detachedSessions);
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens a stored grid: its tiles and focus, then the fractions it had (only
|
||||
* if they still match the layout) and a zoom the user chose. `auto`: the app
|
||||
* is putting it back, so no idle alert is spent.
|
||||
*/
|
||||
_openStoredTileGrid(stored) {
|
||||
const focus = stored.zoomed || stored.focused;
|
||||
if (!this.openTileGrid(stored.ids, { focusedId: focus, auto: true })) return false;
|
||||
const grid = this._tileGrid;
|
||||
// openTileGrid laid the grid out with equal tracks. The stored ones go back
|
||||
// on; _applyTileLayout drops them again if they do not match the column or
|
||||
// row count (the window may have changed the layout since).
|
||||
if (stored.colFr) grid.colFr = stored.colFr.slice();
|
||||
if (stored.rowFr) grid.rowFr = stored.rowFr.slice();
|
||||
if (stored.zoomed && grid.tiles.has(stored.zoomed)) {
|
||||
grid.zoomedId = stored.zoomed;
|
||||
grid.autoZoom = false;
|
||||
}
|
||||
this._applyTileLayout();
|
||||
this._scheduleTileGridRefit();
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Page load (handleInit, in place of selecting the session to restore): a
|
||||
* grid stored OPEN on this device comes back, ids sanitized against the
|
||||
* session list (deleted, detached and duplicate ids dropped). The main
|
||||
* terminal then never loads on this page load, so no `full=1` capture is
|
||||
* paid for a terminal about to be parked. Not in a solo window (nothing is
|
||||
* read there), nor on a window too narrow for the grid (openTileGrid
|
||||
* refuses; the stored grid waits for a wide one).
|
||||
*
|
||||
* @returns {boolean} whether the grid was restored
|
||||
*/
|
||||
_restoreTileGrid() {
|
||||
if (this._tilesOwnTerminal()) return false;
|
||||
const stored = this._readStoredTileGrid();
|
||||
if (!stored?.open || stored.ids.length === 0) return false;
|
||||
return this._openStoredTileGrid(stored);
|
||||
},
|
||||
|
||||
/** A followed `#session=` link took the screen on load: the stored grid stays remembered, closed. */
|
||||
_closeStoredTileGrid() {
|
||||
const stored = this._readStoredTileGrid();
|
||||
if (!stored?.open) return;
|
||||
try {
|
||||
localStorage.setItem(TILE_GRID_STORAGE_KEY, JSON.stringify({ ...stored, open: false }));
|
||||
} catch {
|
||||
/* Per-device convenience only. */
|
||||
}
|
||||
},
|
||||
|
||||
/** Header connection state while tiles own the terminal: every live tile socket open, or not. */
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* @fileoverview The grid survives a page reload, per device (`codeman:tile-grid`).
|
||||
*
|
||||
* - What is stored: ids, focus, a zoom the user chose and the divider
|
||||
* fractions, never content. Closing the grid (Tiles, a pick outside it)
|
||||
* keeps it remembered as `open: false` for one-click return; the last tile
|
||||
* leaving forgets it. Never written or read in a solo window.
|
||||
* - The restore runs INSIDE handleInit, in place of its single-view
|
||||
* `selectSession(restoreId, { auto: true })`: with a stored open grid the
|
||||
* main terminal never loads on that page load (no select, no socket, no
|
||||
* capture), deleted / detached / duplicate ids are dropped, and the stored
|
||||
* fractions and zoom come back. A narrow window keeps the single view.
|
||||
* - A `#session=` link on load wins, and leaves the stored grid remembered
|
||||
* but closed.
|
||||
* - Leaving the grid invalidates the main terminal's cached content for every
|
||||
* tiled id (snapshot, its localStorage copy, buffer cache).
|
||||
*
|
||||
* Real code via the shared vm harness (test/mocks/tile-grid-vm.ts). Port: N/A.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { FakeTile, localStore, makeGridApp, resetGridHarness, windowStub, type GridApp } from './mocks/tile-grid-vm.js';
|
||||
|
||||
const IDS = ['s-a', 's-b', 's-c', 's-d'];
|
||||
const KEY = 'codeman:tile-grid';
|
||||
const stored = () => JSON.parse(localStore.get(KEY) ?? 'null');
|
||||
|
||||
/** A fresh page: the app as handleInit leaves it on its FIRST run (gen 1). */
|
||||
function pageLoad(liveIds: string[], setup: (app: GridApp) => void = () => {}) {
|
||||
const app = makeGridApp(IDS);
|
||||
app._initGeneration = 0;
|
||||
app.activeSessionId = null;
|
||||
app.selectSession = vi.fn();
|
||||
app._fetchTerminalCapture = vi.fn();
|
||||
app._resetAllAppState = vi.fn(() => app.sessions.clear());
|
||||
for (const name of [
|
||||
'_clearTimer',
|
||||
'_updateCjkInputState',
|
||||
'syncSessionOrder',
|
||||
'_loadTabLayout',
|
||||
'cleanupAllFloatingWindows',
|
||||
'startSystemStatsPolling',
|
||||
'stopSystemStatsPolling',
|
||||
'updateCost',
|
||||
]) {
|
||||
app[name] = vi.fn();
|
||||
}
|
||||
app.$ = () => null;
|
||||
setup(app);
|
||||
app.handleInit({
|
||||
sessions: liveIds.map((id) => ({ id, name: id, mode: 'claude', pid: 1 })),
|
||||
scheduledRuns: [],
|
||||
});
|
||||
return app;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
resetGridHarness();
|
||||
});
|
||||
|
||||
describe('what is stored', () => {
|
||||
it('opening the grid stores ids, focus and fractions, nothing else', () => {
|
||||
const app = makeGridApp(IDS);
|
||||
app.openTileGrid(IDS, { focusedId: 's-b' });
|
||||
expect(stored()).toEqual({
|
||||
v: 1,
|
||||
open: true,
|
||||
ids: IDS,
|
||||
focused: 's-b',
|
||||
zoomed: null,
|
||||
colFr: [1, 1],
|
||||
rowFr: [1, 1],
|
||||
});
|
||||
});
|
||||
|
||||
it('focus, a zoom by hand and a divider drag are written as they happen', () => {
|
||||
const app = makeGridApp(IDS);
|
||||
app.openTileGrid(IDS);
|
||||
app.selectSession('s-c');
|
||||
expect(stored().focused).toBe('s-c');
|
||||
app.zoomTile('s-c');
|
||||
expect(stored().zoomed).toBe('s-c');
|
||||
app.zoomTile('s-c');
|
||||
app._tileGrid.colFr = [2, 1];
|
||||
app._persistTileGrid();
|
||||
expect(stored().colFr).toEqual([2, 1]);
|
||||
});
|
||||
|
||||
it('an automatic zoom (window too small) is not stored: it is worked out again', () => {
|
||||
const app = makeGridApp(IDS);
|
||||
app.openTileGrid(IDS);
|
||||
app._tileGrid.zoomedId = 's-a';
|
||||
app._tileGrid.autoZoom = true;
|
||||
app._persistTileGrid();
|
||||
expect(stored().zoomed).toBeNull();
|
||||
});
|
||||
|
||||
it('closing keeps it remembered (open: false); the last tile leaving forgets it', () => {
|
||||
const app = makeGridApp(IDS);
|
||||
app.selectSession = vi.fn();
|
||||
app.openTileGrid(IDS);
|
||||
app.closeTileGrid({ reselect: false });
|
||||
expect(stored()).toMatchObject({ open: false, ids: IDS });
|
||||
|
||||
app.openTileGrid(['s-a']);
|
||||
app.removeTile('s-a');
|
||||
expect(localStore.has(KEY)).toBe(false);
|
||||
});
|
||||
|
||||
it('a solo window never writes', () => {
|
||||
const app = makeGridApp(IDS);
|
||||
app.openTileGrid(IDS);
|
||||
localStore.delete(KEY);
|
||||
app.isSoloWindow = true;
|
||||
app._persistTileGrid();
|
||||
expect(localStore.has(KEY)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('page load with a stored open grid', () => {
|
||||
const storeGrid = (state: Record<string, unknown>) =>
|
||||
localStore.set(KEY, JSON.stringify({ v: 1, open: true, zoomed: null, ...state }));
|
||||
|
||||
it('restores the grid IN PLACE of the single view: the main terminal never loads', () => {
|
||||
storeGrid({ ids: IDS, focused: 's-c' });
|
||||
const app = pageLoad([...IDS, 's-other']);
|
||||
|
||||
expect(app._tilesOwnTerminal()).toBe(true);
|
||||
expect(app._tileGrid.ids).toEqual(IDS);
|
||||
expect(app.activeSessionId).toBe('s-c');
|
||||
expect(app.selectSession).not.toHaveBeenCalled();
|
||||
expect(app._connectWs).not.toHaveBeenCalled();
|
||||
expect(app._fetchTerminalCapture).not.toHaveBeenCalled();
|
||||
// Restoring is the app's choice of focus: no idle alert spent.
|
||||
expect(app.markIdleAlertSeen).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('drops a session that no longer exists, and stores the cleaned list', () => {
|
||||
storeGrid({ ids: ['s-a', 'gone', 's-b'], focused: 'gone' });
|
||||
const app = pageLoad(IDS);
|
||||
expect(app._tileGrid.ids).toEqual(['s-a', 's-b']);
|
||||
expect(app.activeSessionId).toBe('s-a');
|
||||
expect(stored().ids).toEqual(['s-a', 's-b']);
|
||||
});
|
||||
|
||||
it('brings back the fractions (same layout only) and a zoom the user chose', () => {
|
||||
storeGrid({ ids: IDS, focused: 's-a', zoomed: 's-b', colFr: [2, 1], rowFr: [1, 1, 1] });
|
||||
const app = pageLoad(IDS);
|
||||
expect(app._tileGrid.colFr).toEqual([2, 1]);
|
||||
// Three row fractions do not fit a 2x2: equal rows.
|
||||
expect(app._tileGrid.rowFr).toEqual([1, 1]);
|
||||
expect(app._tileGrid.zoomedId).toBe('s-b');
|
||||
expect(app.activeSessionId).toBe('s-b');
|
||||
});
|
||||
|
||||
it('a stored closed grid leaves the single view, and the Tiles toggle brings it back', () => {
|
||||
localStore.set(KEY, JSON.stringify({ v: 1, open: false, ids: ['s-b', 's-c'], focused: 's-c' }));
|
||||
const app = pageLoad(IDS, (a) => localStore.set('codeman-active-session', 's-a'));
|
||||
expect(app._tilesOwnTerminal()).toBe(false);
|
||||
expect(app.selectSession).toHaveBeenCalledWith('s-a', { auto: true });
|
||||
|
||||
app.activeSessionId = 's-a';
|
||||
app.toggleTileGrid();
|
||||
expect(app._tileGrid.ids).toEqual(['s-b', 's-c']);
|
||||
expect(app.activeSessionId).toBe('s-c');
|
||||
});
|
||||
|
||||
it('a window too narrow for the grid keeps the single view (the stored grid waits)', () => {
|
||||
storeGrid({ ids: IDS, focused: 's-a' });
|
||||
windowStub.innerWidth = 1100;
|
||||
const app = pageLoad(IDS);
|
||||
expect(app._tileGrid?.open ?? false).toBe(false);
|
||||
expect(app.selectSession).toHaveBeenCalled();
|
||||
expect(stored().open).toBe(true);
|
||||
});
|
||||
|
||||
it('a solo window never restores it', () => {
|
||||
storeGrid({ ids: IDS, focused: 's-a' });
|
||||
const app = pageLoad(IDS, (a) => {
|
||||
a.isSoloWindow = true;
|
||||
a._applySoloMode = vi.fn();
|
||||
});
|
||||
expect(app._tileGrid?.open ?? false).toBe(false);
|
||||
expect(FakeTile.all).toHaveLength(0);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['another version', JSON.stringify({ v: 2, open: true, ids: IDS })],
|
||||
['not JSON', '{oops'],
|
||||
['not an object', '[1,2]'],
|
||||
])('ignores a stored value that is %s', (_label, raw) => {
|
||||
localStore.set(KEY, raw);
|
||||
const app = pageLoad(IDS);
|
||||
expect(app._tileGrid?.open ?? false).toBe(false);
|
||||
expect(app.selectSession).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('a #session= link on load wins, and the grid stays remembered, closed', () => {
|
||||
storeGrid({ ids: IDS, focused: 's-a' });
|
||||
const app = pageLoad(IDS, (a) => {
|
||||
a._urlSessionId = 's-d';
|
||||
});
|
||||
expect(app._tileGrid?.open ?? false).toBe(false);
|
||||
expect(app.selectSession).toHaveBeenCalledWith('s-d', { auto: true, leaveTiles: true });
|
||||
expect(stored()).toMatchObject({ open: false, ids: IDS });
|
||||
});
|
||||
});
|
||||
|
||||
describe('leaving the grid', () => {
|
||||
it("invalidates the main terminal's cached content for every tiled id", () => {
|
||||
const app = makeGridApp(IDS);
|
||||
app.selectSession = vi.fn();
|
||||
for (const id of [...IDS, 's-other']) {
|
||||
app._xtermSnapshots.set(id, 'old');
|
||||
app.terminalBufferCache.set(id, 'old');
|
||||
localStore.set(`codeman-xs-${id}`, 'old');
|
||||
}
|
||||
app.openTileGrid(['s-a', 's-b']);
|
||||
app.closeTileGrid();
|
||||
for (const id of ['s-a', 's-b']) {
|
||||
expect(app._xtermSnapshots.has(id)).toBe(false);
|
||||
expect(app.terminalBufferCache.has(id)).toBe(false);
|
||||
expect(localStore.has(`codeman-xs-${id}`)).toBe(false);
|
||||
}
|
||||
expect(app._xtermSnapshots.get('s-c')).toBe('old');
|
||||
});
|
||||
});
|
||||
@@ -18,7 +18,15 @@
|
||||
* vm harness in test/mocks/tile-grid-vm.ts). Port: N/A.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { FakeTile, idleCallbacks, makeGridApp, resetGridHarness, section, type GridApp } from './mocks/tile-grid-vm.js';
|
||||
import {
|
||||
FakeTile,
|
||||
idleCallbacks,
|
||||
localStore,
|
||||
makeGridApp,
|
||||
resetGridHarness,
|
||||
section,
|
||||
type GridApp,
|
||||
} from './mocks/tile-grid-vm.js';
|
||||
|
||||
const IDS = ['s-a', 's-b', 's-c'];
|
||||
|
||||
@@ -99,7 +107,7 @@ describe('selectSession with the grid open', () => {
|
||||
|
||||
expect(app._tilesOwnTerminal()).toBe(false);
|
||||
expect(FakeTile.all.every((t) => t.destroy.mock.calls.length === 1)).toBe(true);
|
||||
expect(app._tileGridRemembered).toEqual({ ids: IDS, focusedId: 's-a' });
|
||||
expect(JSON.parse(localStore.get('codeman:tile-grid')!)).toMatchObject({ open: false, ids: IDS, focused: 's-a' });
|
||||
// The parked terminal's stale content must not be saved as s-a's snapshot.
|
||||
expect(activeAtCleanup).toBeNull();
|
||||
expect(app.activeSessionId).toBe('s-other');
|
||||
@@ -169,7 +177,7 @@ describe('moving focus between tiles', () => {
|
||||
const app = openGrid();
|
||||
app.goHome();
|
||||
expect(app._tilesOwnTerminal()).toBe(false);
|
||||
expect(app._tileGridRemembered?.ids).toEqual(IDS);
|
||||
expect(JSON.parse(localStore.get('codeman:tile-grid')!)).toMatchObject({ open: false, ids: IDS });
|
||||
expect(app.activeSessionId).toBeNull();
|
||||
expect(app.showWelcome).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user