Skip to content

Commit e67570d

Browse files
committed
fix(diff): order inline size updates with pane dismissal
Serialize dialog fits with placements, wait for earlier pane work before closing, and coalesce size requests by session and pane revision. Invalidate uncertain size replies so returning to the list cannot trust a stale detail size. Preserve all cumulative lifecycle regressions and add fit/hide, retained-clear and rejected-reply controls. The full native diff suite passes 351 tests; four unchanged controls fail on the published baseline. Verified a real stock 2.1.284 BEFORE/AFTER held-fit comparison, with client hook-budget handling disclosed.
1 parent 1b148c8 commit e67570d

2 files changed

Lines changed: 298 additions & 28 deletions

File tree

‎mods/diff/hooks/register.ts‎

Lines changed: 98 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,19 @@ export function register(on: On) {
6565
let isPaneOpen = false
6666
let isPaneUnknown = false
6767
let openingPane: Promise<boolean> | null = null
68+
type DialogFit = {
69+
engine: Host
70+
epoch: number
71+
revision: number
72+
focus: boolean
73+
}
74+
const paneOperations = {
75+
tail: null as Promise<unknown> | null,
76+
closes: new Set<Promise<boolean>>(),
77+
revision: 0,
78+
fit: null as DialogFit | null,
79+
fitting: null as Promise<void> | null,
80+
}
6881
let paneCloses = 0
6982
let dialogRows: number | null = null
7083
let hasAutoOpened = false
@@ -228,18 +241,69 @@ export function register(on: On) {
228241
}
229242
}
230243

231-
function fitDialog(engine: Host) {
244+
function queuePane<T>(task: () => Promise<T>): Promise<T> {
245+
const queued = (paneOperations.tail ?? Promise.resolve())
246+
.catch(() => undefined)
247+
.then(task)
248+
paneOperations.tail = queued
249+
return queued.finally(() => {
250+
if (paneOperations.tail === queued) paneOperations.tail = null
251+
})
252+
}
253+
254+
function fitDialog(engine: Host, focus = false) {
232255
const rows = Views.dialogRowsOf(model)
233256

234257
const isStale =
235-
isPaneOpen && model.isFullscreen === false && rows !== dialogRows
258+
isPaneOpen && !isPaneUnknown && model.isFullscreen === false &&
259+
(focus || rows !== dialogRows || paneOperations.fitting !== null)
236260

237261
if (isStale) {
238-
dialogRows = rows
239-
void engine.openPane(dialogPane()).catch(() => undefined)
262+
const previous = paneOperations.fit
263+
paneOperations.fit = {
264+
engine,
265+
epoch: pin.epoch,
266+
revision: paneOperations.revision,
267+
focus: focus || (previous?.epoch === pin.epoch &&
268+
previous.revision === paneOperations.revision && previous.focus),
269+
}
270+
drainDialogFit()
240271
}
241272
}
242273

274+
function drainDialogFit() {
275+
if (paneOperations.fitting !== null || paneOperations.fit === null) return
276+
const queued = paneOperations.fit
277+
const closes = [...paneOperations.closes]
278+
const fitting = queuePane(async () => {
279+
const request = paneOperations.fit
280+
if (request?.epoch !== queued.epoch || request.revision !== queued.revision) return
281+
paneOperations.fit = null
282+
if (!request || request.epoch !== pin.epoch ||
283+
request.revision !== paneOperations.revision ||
284+
!isPaneOpen || isPaneUnknown || model.isFullscreen !== false) return
285+
286+
await Promise.allSettled(closes)
287+
if (request.epoch !== pin.epoch || request.revision !== paneOperations.revision ||
288+
!isPaneOpen || isPaneUnknown || model.isFullscreen !== false) return
289+
290+
const pane = dialogPane()
291+
if (!request.focus && pane.rows === dialogRows) return
292+
dialogRows = null
293+
await request.engine.openPane({ ...pane, ...(request.focus && { focus: true }) })
294+
if (request.epoch === pin.epoch && request.revision === paneOperations.revision && isPaneOpen) {
295+
dialogRows = pane.rows ?? null
296+
} else {
297+
dialogRows = null
298+
}
299+
})
300+
paneOperations.fitting = fitting
301+
void fitting.catch(() => undefined).finally(() => {
302+
if (paneOperations.fitting === fitting) paneOperations.fitting = null
303+
drainDialogFit()
304+
})
305+
}
306+
243307
function redraw(engine: Host) {
244308
fitDialog(engine)
245309

@@ -483,20 +547,20 @@ export function register(on: On) {
483547
read: PaneState.Fetched | null = null,
484548
): Promise<boolean> {
485549
const { epoch } = pin
550+
const closes = [...paneOperations.closes]
486551
opens += 1
487-
if (openingPane !== null) {
552+
paneOperations.revision += 1
553+
if (paneOperations.tail !== null || closes.length > 0) {
488554
engine.uiLog('The diff panel is waiting for an earlier pane operation to finish.')
489555
}
490556

491-
const opening = (openingPane ?? Promise.resolve(false))
492-
.catch(() => false)
493-
.then(() => {
494-
if (epoch !== pin.epoch || isPaneUnknown) {
495-
return false
496-
}
557+
const opening = queuePane(async () => {
558+
if (epoch !== pin.epoch || isPaneUnknown) {
559+
return false
560+
}
497561

498-
return isPaneOpen || placePane(engine, trigger, read)
499-
})
562+
return isPaneOpen || placePane(engine, trigger, read, closes)
563+
})
500564

501565
openingPane = opening
502566

@@ -511,6 +575,7 @@ export function register(on: On) {
511575
engine: Host,
512576
trigger: (typeof Record.SHOWN_TRIGGERS)[number],
513577
read: PaneState.Fetched | null,
578+
closes: readonly Promise<boolean>[],
514579
): Promise<boolean> {
515580
const { epoch } = pin
516581
const owner = lifecycle.owner()
@@ -533,7 +598,8 @@ export function register(on: On) {
533598
await refresh(engine).catch(() => undefined)
534599
}
535600

536-
if (!(await lifecycle.waitForPaneCloses(owner)) || epoch !== pin.epoch) {
601+
await Promise.allSettled(closes)
602+
if (epoch !== pin.epoch) {
537603
return false
538604
}
539605

@@ -727,12 +793,23 @@ export function register(on: On) {
727793

728794
async function closePane(engine: Host, isCleanup = false): Promise<boolean> {
729795
const owner = lifecycle.owner()
730-
return (await lifecycle.run(isCleanup ? 'pane-cleanup' : 'pane-close', owner, async () => {
731-
await engine.closePane({ id: Names.PANE_ID })
732-
isPaneOpen = false
733-
isPaneUnknown = false
734-
return true
735-
}, true)) ?? false
796+
const placements = paneOperations.tail
797+
if (paneOperations.tail !== null) {
798+
engine.uiLog('The diff panel is waiting for an earlier pane operation to finish.')
799+
}
800+
const closing = lifecycle.run(
801+
isCleanup ? 'pane-cleanup' : 'pane-close', owner, async () => {
802+
await placements?.catch(() => undefined)
803+
if (!lifecycle.isSession(owner)) return false
804+
await engine.closePane({ id: Names.PANE_ID })
805+
paneOperations.revision += 1
806+
isPaneOpen = false
807+
isPaneUnknown = false
808+
return true
809+
}, true,
810+
).then(result => result ?? false)
811+
paneOperations.closes.add(closing)
812+
return closing.finally(() => { paneOperations.closes.delete(closing) })
736813
}
737814

738815
function markTabSwitch(engine: Host, tab: (typeof Record.TABS)[number]) {
@@ -1201,11 +1278,7 @@ export function register(on: On) {
12011278
model = { ...model, dialogView: 'list' }
12021279
redraw(host)
12031280

1204-
dialogRows = Views.dialogRowsOf(model)
1205-
1206-
void host
1207-
.openPane({ ...dialogPane(), focus: true })
1208-
.catch(() => undefined)
1281+
fitDialog(host, true)
12091282

12101283
return { deny: 'back to the file list' }
12111284
}

0 commit comments

Comments
 (0)