Discarding a freshly-created tab while its navigation is still pending
cancels the navigation and resets the tab to about:blank in Edge — the
recorded URL is lost and activating the tab later reloads a blank page.
discardRestoredTabAfterCommit polls tabs.get until the url is committed
(early in the load, page never fully rendered) and only then discards,
with a 15s timeout degrading to a normally-loading tab.
background's duplicate-blank-tab cleanup now exempts freshly-created
tabs for a 5s grace period (a restore batch's tabs have empty urls until
their navigation commits, which previously looked like a pile of blank
new-tab pages and got closed — the cause of 'No tab with id' during
group restore) and always exempts discarded tabs.
Tests: grace-period exemption probe, discard-after-commit probes for both
restore modes (background +2, restore probes updated).
Background tabs created during session restore are discarded right after
creation so a large session does not load every page at once (the first
active tab stays loaded; Chrome reloads a discarded tab on activation and
natively refuses to discard an active tab, so no active-guard IPC is
needed). The restore runs under the refresh-suppression window so the
tabs.create/discard event echo does not cause a redundant post-restore
refresh (the explicit renderDashboard call is unaffected).
Adds behavioral probes asserting both restore modes discard every
background tab, keep the first active tab loaded, and leave the restored
id list intact (removing either discard call turns them red).
queryUserChromeGroups, collapseChromeTabGroupsInWindow and
syncChromeTabGroupExpansionForTab pulled every window's groups and
filtered in JS; pass the windowId to tabGroups.query instead. The full
sync path (syncChromeTabGroups) intentionally keeps the unfiltered query
— it manages mirrors across every window in desired and cleans stale
mappings in other windows. Tests stub tabGroups.query with real
windowId-filtering semantics via a shared helper.
chrome.tabs.group creates a NEW group in the CALLER's window by default,
so a new-window session restore dragged the chrome-group tabs out of the
fresh window into the dashboard window (observed: the session split across
two windows, chrome-group tabs flashing into the dashboard window). Pass
createProperties.windowId so the group is created directly in the restore
target window — tabs never move, no flash. The same-title conflict check
now lets tabGroups.query filter by the target window instead of pulling
every window's groups. Adds regression assertions for the pinned-window
creation path.
- config-sync: serialize unset storage keys as explicit null under real
chrome.storage.get(keys[]) semantics (unset keys resolve as undefined,
so the old 'key in data' check silently dropped 15 keys from exports);
reset the import file input after a successful import
- applyChromeTabGroupsToggle(off): clear stale imported-group metadata
without deleting the session groups it once pointed to (they are now
ordinary manual groups — deletion was an upgrade-residue data-loss path)
- card-level actions (close/sleep/merge/dedup per domain or section
header) get a shared cardActionInFlight re-entrancy guard, matching the
batch bar's batchActionInFlight; merge also clears the lingering handle
selection like the batch path does
- discard-tab: stale (ghost) branch prunes dangling assignments up front
like the success branch; success branch restores back-to-top visibility
- popup: loadPopupView writes the resolved view back to the localStorage
first-frame mirror so config imports can no longer leave the two stores
permanently diverged (stale first frame on every open)
- tests: real-semantics exportConfig discriminant case, hardened
toggleGuardHit no-keydown-bypass regex, stale-branch shape sync
normalizeSessionTab/normalizeSessionGroup/buildSessionGroupsFromTabs and
the savedTab snapshot now carry chromeGroupColor through the whole
save->storage->reload->restore round-trip (previously stripped by the
double normalize, so restored native groups always fell back to grey).
createRestoredSessionGroups consumes every restored tab matching a
recorded url instead of one, so duplicate-URL group members no longer
drift out of the restored Chrome group. Adds round-trip, rebuild-color
and same-URL coverage tests with real normalize paths.
Follow-up fixes from the helpers-unify audit plus user-confirmed UX decisions.
Product:
- Batch sleep/dedup keep the ORIGINAL selection on chips that could not be
resolved to a numeric tab id (they were not part of the operation), so a
batch action never silently deselects untouched rows.
- closeTabOutDupes now closes through closeTabsSafely: the shared
window-last-tab protection applies, so closing duplicate Tab Harbor pages
never closes a window whose only tab would be removed.
- Removed three dead delegating wrappers (closeTabsByUrls/closeTabsExact/
closeDuplicateTabs) that no longer had callers.
- Sleep failure UX: the per-chip sleep button is hidden on already-discarded
rows (!tab.discarded), and group/global sleep filter out discarded tabs —
an all-slept group is a silent no-op instead of a misleading
'Failed to sleep' toast. Real discard failures still surface.
- Chrome-group card names use the PURE native group color again so they
visually match the group chip in the browser tab strip (accepted contrast
tradeoff on light palettes); hover/focus feedback is now an underline so
keyboard focus stays visible without relying on color contrast.
Tests (406 -> 409):
- Behavioral tests for closeTabsByUrlsSafely (exact/file:// exact/hostname
exact, unparseable skipped), closeDuplicatesByUrls (keepOne keeps the
active copy) and the REAL ensureWindowsKeepLastTab implementation (was
only mocked before).
- Hardened the shouldImportChromeGroupsIntoSessionState assertion (unconditional
false anchored with doesNotMatch return true) and the sleepAllTabsButton
i18n pair; updated the chip-discard button and color assertions.
Audit-fix round (C1-C23) plus follow-up fixes and comment polish.
Product:
- Native Chrome group writes are the source of truth: drag into/out of
Chrome groups aborts with a toast on API failure instead of silently
clearing session state (C1); stale-id retries via
groupTabsWithStaleRetry(IntoGroup)/ungroupTabsWithStaleRetry return the
actual merged ids so counts/cleanup never use the pre-retry list (C2/C5).
- Batch merge now merges tabs already inside Chrome groups (pinned stays
outside); count-based group titles resolve from the ACTUAL merged ids.
- Batch handlers are re-entrancy safe: batchActionInFlight guard + finally
resets the refresh-suppression window on every path (C22).
- Chrome-group card close/dedup are scoped to the card's own tabs; URL-wide
matching never touches tabs outside the group (C12). Placeholder rows
(lagging snapshot) render no save/close controls and never count as
duplicates (C13).
- Unmanaged native-group tabs are never pulled into dashboard mirrors:
buildDomainGroups snapshot fallback is gated on queryUserChromeGroups
failing or partially failing (C7), partial failures keep their diagnostic
without misreporting a global toast (C6).
- Ambiguous title+color fingerprints no longer churn mirrors on reload:
the in-session mapping is kept when still among the candidates, new
mirrors pick a non-colliding color (C4), including legacy flat meta keys.
- Disabling the Chrome-group toggle tears down managed mirrors (C16);
the retired import pipeline short-circuits before any fetch work (C17);
Chrome-group card row order is never persisted into the durable order (C18).
- closeTabsSafely/sleepTabsByIds run in parallel (Promise.allSettled/all)
with the same window-last-tab protection.
- Chrome-group card names use a 35% ink mix (AA >= 4.5:1 on light palettes)
with a 55% hover/focus step so keyboard focus stays visible.
Tests (386 -> 406):
- Behavioral tests for all three stale-retry helpers, the C15 superset
filter, and the C4 keeper/collision-avoidance paths; discriminating
assertions for C12/C13/C16/C17/C22 and the dedup suppression ordering;
the C6 partial-failure diagnostic; split the dedup alternation regex into
two discriminating assertions; storage mock gained remove() so
resetChromeGroupState clears persisted meta between tests.
Comments:
- Full-branch comment review (3 read-only reviewers, ~1200 lines): fixed
26 stale/misleading comments across all six files and removed an inert
column-span rule; no audit-internal jargon remains.