fix(terminal): bound + harden xterm snapshot persistence

- Skip snapshot save for shell sessions (restore is gated on mode!=='shell',
  so they only burned a serialize() + cache slot + localStorage quota).
- In-memory cache: delete-before-set so eviction is true LRU, not FIFO that
  could drop the most-recently-used session.
- localStorage: extract _persistXtermSnapshot — evict to a fixed key budget
  regardless of session liveness (the old prune only dropped dead keys, so
  >10 live sessions at the 20-session target deadlocked the quota) and
  evict-and-retry on quota errors (the old prune ran only after a successful
  setItem, so a full quota permanently disabled persistence).
- Tighten the OSC-strip regex in _isUsableXtermSnapshot to stop at ST.
- Update the structural test's usability-gate assertion to not depend on a
  fixed byte window.
This commit is contained in:
arkon
2026-06-11 10:16:47 +02:00
parent 5b2da424a1
commit 9893a7f64a
2 changed files with 70 additions and 25 deletions
+4 -2
View File
@@ -17,8 +17,10 @@ describe('xterm snapshot/replay (codex tab-switch)', () => {
const restoreBlock = source.slice(save, restore);
expect(helper).toBeGreaterThan(-1);
// The save is gated on a usability check…
expect(source.slice(save - 250, save)).toContain('this._isUsableXtermSnapshot(snapshot)');
// The save is gated on a usability check immediately above it.
const usabilityGate = source.lastIndexOf('if (this._isUsableXtermSnapshot(snapshot))', save);
expect(usabilityGate).toBeGreaterThan(-1);
expect(usabilityGate).toBeLessThan(save);
// …and so is each restore path (in-memory + persisted).
expect(restoreBlock).toContain('if (snapshot && !this._isUsableXtermSnapshot(snapshot))');
expect(restoreBlock).toContain('persisted && this._isUsableXtermSnapshot(persisted)');