Tabvana: handle Chrome memory-saver discarded tabs

You're working in the Tabvana repo (Chrome MV3 extension for tab management; see CLAUDE.md for architecture). There's a bug where tabs that Chrome has discarded via the "memory saver" feature show up as blank rows in our views. Investigate and fix it.

Background on Chrome's discard lifecycle:

chrome.tabs.Tab has a discarded: boolean property — true means Chrome has unloaded the renderer but kept the tab strip entry.
chrome.tabs.onUpdated fires with changeInfo.discarded when the state flips.
Discarded tabs report status: "unloaded" and should preserve url, title, favIconUrl per the Chrome API docs.
When the user clicks a discarded tab, Chrome reactivates it and may assign a new tab ID (verify this — important for our keying).
Hypotheses for the blank-row symptom — investigate each before fixing:

Pre-discard URL transition. Chrome may briefly transition the URL to about:blank or similar right before discard. If our shouldIgnore() (src/chrome/normalizeUrl.ts) drops it, we'd lose the row's content. Add temporary logging in src/background/chromeTabsListeners.ts and src/db/dbSync.ts to capture changeInfo and tab.url/tab.discarded/tab.status around discard events. Reproduce by enabling memory saver in Chrome and leaving tabs idle (or use chrome://discards/ to force-discard a tab).
status: "unloaded" gating. Search the codebase for any place that gates rendering, syncing, or DB updates on status === "complete" or filters by status. A discarded tab reports unloaded and might fall through.
Tab ID reassignment on reactivation. Check whether UI rows in src/main/ and src/store/windowsSlice.ts are keyed by Chrome tab ID. If so, a reactivated tab gets a new ID and the old row may persist as a ghost while the new one shows up empty until the next sync.
Reproduce first, then fix:

Open chrome://discards/ and use the "Discard" action on a real tab in a Tabvana-tracked window. Observe what Tabvana shows.
Capture the chrome.tabs.onUpdated events, chrome.tabs.onRemoved events, and the resulting DB state (db.pages, db.windows tables) before/after.
Confirm which hypothesis matches.
Fix scope:

Track discarded state on DBPage if useful for surfacing it in the UI (optional — only if needed to fix the blank row).
In dbSync.ts handleTabUpdated, treat changeInfo.discarded === true as a state change, not a URL/title change — do NOT clear the URL or trigger renormalize/embedding-regen.
Ensure that whatever transient URL state Chrome reports during discard doesn't cause ensurePageTracked to overwrite a valid URL with about:blank.
If tab ID reassignment is the issue, ensure that the row keyed by the normalized URL survives the ID change cleanly, and that chrome.tabs.onRemoved followed by onCreated for the same URL doesn't produce a flicker of empty state.
Don't add a "discarded" indicator badge in the UI unless trivially easy — focus on eliminating the blank row.
Verification:

Manually reproduce with chrome://discards/ after the fix; confirm the row stays populated through discard → reactivation.
Add a unit test in src/db/__tests__/dbSync.test.ts that simulates an onUpdated event with changeInfo: { discarded: true } and asserts the existing DBPage row is unchanged (URL, title preserved).
Run yarn build and yarn test. Note that Content.spec.tsx, Options.spec.tsx, and Welcome.spec.tsx have known pre-existing failures — your changes shouldn't make them worse, but you don't need to fix them.