Send a browser tab's window times to main with its open (dark, layout-owner build only) (#29988)

* fix(browser): keep slept workspace browser tabs off the reopen-closed list

Sleeping a workspace closed its browser tabs as if the user had closed them, so
Reopen Closed Tab could bring back a tab that was only slept. Sleep now closes
them with a 'sleep' reason that skips the reopen stack while still releasing
host pages, doc grants and webviews as before.

* feat(layout): send a browser tab's window timestamps with its open (dark)

A layout-owner build's openBrowserTab carried no createdAt, so main stamped the
tab-bar entry and the browser record with its own apply time and the window's
next save diverged on createdAt for both. The window now sends both times, as
createTerminalTab does; only switch-on builds send them.

* Revert "fix(browser): keep slept workspace browser tabs off the reopen-closed list"

This reverts commit 6798c3c701.

* test: drop the slept-tab close test that went with the held sleep fix
This commit is contained in:
Jinwoo Hong
2026-10-16 23:15:43 -07:00
committed by GitHub
parent de1f6e3d81
commit 37c314bcc9
7 changed files with 24 additions and 3 deletions
@@ -65,6 +65,8 @@ export type WindowLayoutCommand =
groupId?: string
afterTabId?: string
index?: number
createdAt: number
browserCreatedAt: number
split?: NewGroupSplit
}
| { type: 'openBrowserPage' | 'closeBrowserPage'; browserTabId: string; pageId: string }
@@ -59,7 +59,9 @@ describe('browser tabs in a layout-owner build', () => {
profileId: 'p1',
sessionPartition: null,
groupId: firstEntry.groupId,
index: 0
index: 0,
createdAt: firstEntry.createdAt,
browserCreatedAt: first.createdAt
}
},
{
@@ -32,6 +32,8 @@ export function recordBrowserTabOpen(state: BrowserCommandState, tab: BrowserWor
sessionPartition: tab.sessionPartition ?? null,
groupId: entry.groupId,
...(afterTabId ? { afterTabId } : position === 0 ? { index: 0 } : {}),
createdAt: entry.createdAt,
browserCreatedAt: tab.createdAt,
// Open to the side: main makes the window's new split with the tab.
...newGroupSplitOf(state, tab.worktreeId, entry.groupId, entry.id)
})
@@ -222,6 +222,8 @@ const OpenBrowserTabCommand = z.object({
groupId: z.string().min(1).optional(),
afterTabId: z.string().min(1).optional(),
index: z.number().int().nonnegative().optional(),
createdAt: z.number().int().nonnegative().optional(),
browserCreatedAt: z.number().int().nonnegative().optional(),
split: NewGroupSplit
})
@@ -50,6 +50,15 @@ describe('openBrowserTab under the window’s ids', () => {
expect(saved.browserTabsByWorktree?.[WS]?.at(-1)).toMatchObject({ pageIds: ['page-1'] })
})
// Why: the window's next save carries its own timestamps; main's copy must hold the same ones.
it('keeps the times the window drew the entry and the record at', () => {
const { model } = build(testContext(), [
{ ...WINDOW_OPEN, createdAt: 1_700_000_000_001, browserCreatedAt: 1_700_000_000_000 }
])
expect(model.workspaces[WS]!.tabs.at(-1)?.createdAt).toBe(1_700_000_000_001)
expect(records(model)?.at(-1)?.createdAt).toBe(1_700_000_000_000)
})
it('places the tab where the window drew it', () => {
const { ctx, model, hostTab } = setup()
const placed = build(
@@ -150,6 +150,9 @@ export type LayoutCommand =
/** The window's own ids for a tab it drew: its tab-bar entry and its browser record. */
tabId?: string
browserTabId?: string
/** When the window drew the tab-bar entry and the browser record, so its later save matches. */
createdAt?: number
browserCreatedAt?: number
split?: NewGroupSplit
})
| (On & { type: 'openBrowserPage'; browserTabId: string; pageId: string })
@@ -134,7 +134,8 @@ export function openBrowserTab(
const tab = contentTab(context, {
kind: 'browser',
id: tabId,
entityId: browserTabId
entityId: browserTabId,
...(command.createdAt !== undefined ? { createdAt: command.createdAt } : {})
})
const next = placeContentTab(model, key, tab, command, context).model
const workspace = next.workspaces[key]!
@@ -143,7 +144,7 @@ export function openBrowserTab(
sessionProfileId: command.profileId ?? null,
...(command.sessionPartition ? { sessionPartition: command.sessionPartition } : {}),
pageIds: [pageId],
createdAt: tab.createdAt
createdAt: command.browserCreatedAt ?? tab.createdAt
}
return applied(
withWorkspace(next, key, {