From 2dad0f8d92c7a914e369751c7e888424b45113aa Mon Sep 17 00:00:00 2001 From: ionfwsrijan Date: Tue, 30 Jun 2026 22:07:58 +0530 Subject: [PATCH 1/2] fix(core): invalidate overlay layers and reset screen state on resize Closes #1911 --- .../core/src/terminal/LayerManager.test.ts | 27 +++++++++++++++---- packages/core/src/terminal/LayerManager.ts | 2 +- packages/core/src/terminal/Screen.test.ts | 22 +++++++++++++++ packages/core/src/terminal/Screen.ts | 8 ++++++ 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/packages/core/src/terminal/LayerManager.test.ts b/packages/core/src/terminal/LayerManager.test.ts index 1dc6e553..e7507366 100644 --- a/packages/core/src/terminal/LayerManager.test.ts +++ b/packages/core/src/terminal/LayerManager.test.ts @@ -313,19 +313,36 @@ describe('LayerManager - Resize', () => { expect(layer.cells[0].length).toBe(20); }); - it('clears dirty regions on resize', () => { + it('marks entire layer as dirty on resize so it is recomposited', () => { const lm = new LayerManager(10, 10); const layer = lm.createLayer('base', 0); - lm.setCell('base', 1, 1, { - char: 'A', + lm.resize(20, 15); + + expect(layer.dirtyRegion).toEqual({ + x: 0, + y: 0, + width: 20, + height: 15, }); + }); - expect(layer.dirtyRegion).not.toBeNull(); + it('composites layer content after resize', () => { + const lm = new LayerManager(10, 10); + const layer = lm.createLayer('overlay', 100); + lm.setCell('overlay', 1, 1, { char: 'X' }); + // Resize should set dirtyRegion to full area lm.resize(20, 15); - expect(layer.dirtyRegion).toBeNull(); + // Write to the new size + lm.setCell('overlay', 10, 5, { char: 'Y' }); + + const screen = new (require('./Screen.js').Screen)(20, 15); + lm.composite(screen); + + // Content written after resize should appear + expect(screen.back[5][10].char).toBe('Y'); }); }); diff --git a/packages/core/src/terminal/LayerManager.ts b/packages/core/src/terminal/LayerManager.ts index 44a21dfe..b44c9bb8 100644 --- a/packages/core/src/terminal/LayerManager.ts +++ b/packages/core/src/terminal/LayerManager.ts @@ -257,7 +257,7 @@ export class LayerManager { for (const layer of this._layers.values()) { layer.cells = this._createGrid(); - layer.dirtyRegion = null; + layer.dirtyRegion = { x: 0, y: 0, width: cols, height: rows }; } this._allocateHitGrids(); diff --git a/packages/core/src/terminal/Screen.test.ts b/packages/core/src/terminal/Screen.test.ts index 650236f3..ceca0662 100644 --- a/packages/core/src/terminal/Screen.test.ts +++ b/packages/core/src/terminal/Screen.test.ts @@ -70,6 +70,28 @@ describe('Screen', () => { expect(screen.front[0][0].char).toBe('\0'); }); + it('resize resets all ancillary state', () => { + const screen = new Screen(10, 5); + + // Set up some state + screen.pushClip({ x: 0, y: 0, width: 5, height: 3 }); + screen.pushTranslateY(2); + screen.writeAnsi('\x1b[31m'); + screen.applyBackdropFilter({ x: 0, y: 0, width: 3, height: 3 }); + + // Resize + screen.resize(20, 10); + + expect(screen.cols).toBe(20); + expect(screen.rows).toBe(10); + expect(screen.activeClip).toBeNull(); + expect(screen.drainAnsiQueue()).toBe(''); + expect(screen.getPreviousLine(0)).toBe(''); + // Write after resize and verify it lands correctly (no stale clip/translate) + screen.setCell(15, 8, { char: 'Z' }); + expect(screen.back[8][15].char).toBe('Z'); + }); + it('writeString applies style attributes (bold, fg)', () => { const screen = new Screen(10, 5); screen.writeString(0, 0, 'Hi', { bold: true, fg: { type: 'named', name: 'red' } }); diff --git a/packages/core/src/terminal/Screen.ts b/packages/core/src/terminal/Screen.ts index adc05e6c..568cf4ac 100644 --- a/packages/core/src/terminal/Screen.ts +++ b/packages/core/src/terminal/Screen.ts @@ -440,6 +440,14 @@ export class Screen { this.front = this._createGrid(cols, rows); this.back = this._createGrid(cols, rows); this._previousLines = []; + this._previousStyleLines = []; + this._clipStack = []; + this._translateYStack = []; + this._translateY = 0; + this._ansiQueue = []; + this._flushEpoch = -1; + this._swapping = false; + this._backdropFilters = []; } /** From 9423c851b3ba3777fe877d71f2efc9c22573407f Mon Sep 17 00:00:00 2001 From: ionfwsrijan Date: Tue, 30 Jun 2026 22:15:28 +0530 Subject: [PATCH 2/2] fix(test): use import instead of require in LayerManager.test.ts --- packages/core/src/terminal/LayerManager.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/terminal/LayerManager.test.ts b/packages/core/src/terminal/LayerManager.test.ts index e7507366..21685108 100644 --- a/packages/core/src/terminal/LayerManager.test.ts +++ b/packages/core/src/terminal/LayerManager.test.ts @@ -339,7 +339,7 @@ describe('LayerManager - Resize', () => { // Write to the new size lm.setCell('overlay', 10, 5, { char: 'Y' }); - const screen = new (require('./Screen.js').Screen)(20, 15); + const screen = new Screen(20, 15); lm.composite(screen); // Content written after resize should appear