fix(switch): drop duplicate tab refs that accumulate across switches - #79
Merged
Merged
Conversation
Symptom: switching between Spaces accumulates phantom rows in the side panel that all point at the same chrome tabId. Closing the tab leaves the ghost rows behind; clicking them throws "No tab with id"; closing and reopening the side panel clears them (because reconcile runs on mount). The race that seeds the duplicates: switchTo's empty-Space starter path calls chrome.tabs.create, which fires chrome.tabs.onCreated → registerTab in parallel with switchTo's own updateStore. Since the SW just wrote activeSpaceByWindow = target, registerTab picks the same Space and pushes the new tabId into root.items. switchTo's updateStore then pushes again with no guard, leaving two entries — repeated for every starter creation. Defenses: - switchTo: guard the starter push with a `.some(...)` check so a concurrent registerTab can't produce a duplicate. - pruneDeadTabs: dedupe folder.items by tabId on top of the existing dead-tab cull, so any duplicates that slipped through earlier (or through a race we haven't yet identified) self-heal on the next reconcile. - reconcile: also trigger when items have duplicate live tabIds (not only when refs are dead), so the dedup pass actually runs. - activateTab handler: when chrome.tabs.update throws "No tab with id", drop the stale ref before surfacing the error so the ghost row goes away on the next refresh instead of sitting there until the user reopens the panel. Test coverage: reconcile dedupes duplicate live tab refs without any tabs being dead. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
Switching between Spaces accumulates phantom rows in the side panel that all point at the same chrome tabId. Reported by the user:
No tab with id: …が出るreconcileが走るため)Root cause
switchToの empty-Space starter パスがchrome.tabs.createを呼ぶと、chrome.tabs.onCreated→handlers.onTabCreated→registerTabが並走switchToは直前にactiveSpaceByWindow = targetを書いているため、registerTabも同じ Space を active と判定しroot.itemsに pushswitchTo自身のupdateStoreもガードなしで push結果、starter 作成のたびに同じ tabId が 2 重に items に入り、reconcile されるまで残る。
Fixes
switchTostarter push をガード —root.items.some(...)でチェックしてから push (空 Space → starter の race を解消)。pruneDeadTabsを dedup 対応 — dead tab の除去に加え、各フォルダのitemsで同 tabId が複数あれば 1 件に圧縮。過去ログから残った重複を reconcile sweep が自己修復する。reconcileの判定を拡張 — dead tab 0 件でも live tab に重複があればpruneDeadTabsを走らせる(panel mount / visibility change で発火)。activateTabハンドラに self-heal —chrome.tabs.updateがNo tab with idで落ちたら、その場でdropTabしてから error を投げる(ゴースト行が次の refresh で消える)。Test plan
bun run build通過bunx vitest run src/background/145 件全 passreconcile dedupes duplicate live tab refs without any tabs being dead) が passchrome://extensionsで拡張 reload して以下が解消するか確認:🤖 Generated with Claude Code