refactor(desktop): mint browser tab ids at random rather than by slot

Browser tabs took the lowest free slot, so an id was reused once its tab
closed. That is only safe while every store keyed by the id is wiped on
close — true today, but a discipline rather than a guarantee, and stale
state would resurface under an unrelated tab the day it lapses.

Mint like a terminal does instead: no id is ever handed out twice.
This commit is contained in:
Brooklyn Nicholson
2026-08-24 14:47:26 -05:00
committed by brooklyn!
parent 84758ed19d
commit cd297653fa
2 changed files with 18 additions and 17 deletions
+7 -2
View File
@@ -109,7 +109,9 @@ describe('preview store', () => {
expect($previewTabs.get()).toHaveLength(2)
})
it('reuses a closed slot instead of counting Browser ids up forever', () => {
// A Browser id is minted rather than derived, so it must never be handed out
// twice: per-tab state keyed by it would resurface under an unrelated tab.
it('never reuses a Browser id, even after one is closed', () => {
newBrowserTab()
const first = $previewTabs.get()[0].id
@@ -117,7 +119,10 @@ describe('preview store', () => {
closeRightRailTab(first)
newBrowserTab()
expect($previewTabs.get().map(tab => tab.id)).toContain(first)
const ids = $previewTabs.get().map(tab => tab.id)
expect(ids).not.toContain(first)
expect(new Set(ids).size).toBe(ids.length)
})
it('re-fronts an existing tab instead of duplicating it, refreshing its target', () => {
+11 -15
View File
@@ -220,18 +220,15 @@ export function previewTabId(target: PreviewTarget): RightRailTabId {
const isBrowserTab = (tab: PreviewTab): boolean => tab.target.kind === 'url'
/** The lowest free Browser slot, so ids stay short and readable across a
* restore instead of accreting one per page ever opened. */
function mintBrowserTabId(tabs: PreviewTab[]): RightRailTabId {
const used = new Set(tabs.map(tab => tab.id))
/** A Browser tab's id, minted the way a terminal's is — there is no identity to
* derive one from. Random rather than the lowest free slot: an id is never
* handed out twice, so per-tab state keyed by it (`$browserPages`, the console
* buffer) cannot resurface under a later tab if a close ever fails to wipe it. */
function mintBrowserTabId(): RightRailTabId {
const unique =
globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
for (let n = 1; ; n += 1) {
const id: RightRailTabId = `url:browser-${n}`
if (!used.has(id)) {
return id
}
}
return `url:browser-${unique}`
}
/** The Browser a URL should open in: the one you're looking at, else the one
@@ -245,7 +242,7 @@ function browserTabId(tabs: PreviewTab[]): RightRailTabId {
return active.id
}
return tabs.findLast(isBrowserTab)?.id ?? mintBrowserTabId(tabs)
return tabs.findLast(isBrowserTab)?.id ?? mintBrowserTabId()
}
// Browsing files is "peek at the source"; a tool or an explicit link handing
@@ -291,10 +288,9 @@ export function openBrowserTab() {
/** Another Browser, always — the strip's "+". */
export function newBrowserTab() {
const tabs = $previewTabs.get()
const id = mintBrowserTabId(tabs)
const id = mintBrowserTabId()
$previewTabs.set([...tabs, { id, target: blankPage() }])
$previewTabs.set([...$previewTabs.get(), { id, target: blankPage() }])
selectRightRailTab(id)
}