Skip to content

Scan resilience + live progress + actionable sign-in + version footer (v1.0.4)#1

Merged
simaonogueira101 merged 4 commits into
mainfrom
fix/extension-scan-resilience
Jun 24, 2026
Merged

Scan resilience + live progress + actionable sign-in + version footer (v1.0.4)#1
simaonogueira101 merged 4 commits into
mainfrom
fix/extension-scan-resilience

Conversation

@simaonogueira101

Copy link
Copy Markdown
Member

Fixes the extension issues from Filipe's June-23 testing (scan thread + screenshots).

E1 — scan survives popup close / SW teardown

runScan previously paginated in memory with setTimeout sleeps and only persisted at the end, so MV3 killed the service worker mid-scan (closing the popup removed its keepalive) and lost everything → "failed silently". Now the scan is checkpoint-and-resume: progress is written to chrome.storage after every page, a short scan-tick keepalive alarm re-drives it, and an interrupted non-stale scan auto-resumes on SW startup. A torn-down SW loses at most the in-flight page. Re-entrancy guard + 1h stale cutoff.

E2 — live progress

getStatus now returns scanning + scannedCount; the popup polls and shows "scanned N…" (survives closing/reopening the popup mid-scan), replacing the bare "scanning…" spinner.

E3 — completeness

The resume + the popup state-reconcile (E5) fix the "stuck on 2367" report (that account was under the cap; the scan had died partial and the popup showed "no sync yet"). The per-session page cap (maxPagesPerSession) is server-side and is raised separately in the monorepo (60→250, 2.4k→10k) for >2,400-connection networks.

E4 — actionable Sign-in

The red "sign in to noticed to finish syncing" used to render as text while the clickable button was gated on a different signal, so it could show with no button. Now a single source of truth reveals + wires the #signin-cta button whenever a noticed sign-in is needed (from either signal); the text never dangles alone.

E5 — reconcile with the server

The popup showed "no sync yet" while "recent syncs" listed a recorded sync (the syncConfirmed message can be lost if the SW dies). Now both the SW (getSyncHistory) and the popup reconcile local state from /api/sync/runs: stamp lastScanAt/count from the newest extension run and clear a stale noticed-signin.

E6 — version + update footer

A persistent installed-version indicator (v1.0.4) plus the "update available" notice, both moved to a bottom footer.

Verification

  • vitest run: 80 passed (7 files; +new tests for resume/checkpoint/progress/reconcile/sign-in/footer, TDD).
  • tsc --noEmit: clean.
  • Reproducible build: rebuild → identical sha256 (991a5658…).
  • No platform-name literals in shipped code (purity guard green).

Pre-release Helium smoke test pending. Tag v1.0.4 triggers the release workflow once merged.

🤖 Generated with Claude Code

simaonogueira101 and others added 4 commits June 24, 2026 10:23
…ss + history reconcile

Make the MV3 connection scan survive the service worker being torn down
mid-scan (closing the popup was its only keepalive), surface progress, and
stop diverging from the server's recorded syncs.

E1/E3 — checkpoint-and-resume:
- fetch-engine `scanConnections` gains `startAt`/`initialItems` (resume) and an
  `onPage(items, nextStart)` checkpoint hook fired after EACH page (incl. the
  cap-reached page). Stays pure/injectable; passes a fresh snapshot per tick.
- service-worker: new resumable `continueScan()` reads the persisted checkpoint,
  fetches from the cursor, checkpoints to storage per page, and on the last/short
  page or the per-session cap finalizes (pendingScan, clears scan state, clears
  the keepalive tick, opens the handoff tab). Module-level re-entrancy guard so a
  tick-triggered resume can't double-fetch. Lost-session → needs:network-signin
  + clears scan state. `runScan` is now a thin init+continue wrapper (monthly
  alarm + scanNow paths preserved).
- new State fields: scanInProgress, scanCursor, scanItems, scanStartedAt (+ KEYS).

Keepalive + auto-resume:
- scanNow arms a 0.5-min `scan-tick` alarm then runs continueScan inline. The
  tick resumes an in-progress, non-stale scan (drops a >1h-old zombie). On SW
  startup, an interrupted non-stale scan re-arms the tick so it resumes.

E2 — progress:
- getStatus returns scanning + scannedCount. The popup polls getStatus (~1.5s,
  injectable) and shows "scanned N…" while scanning; spinner kept.

E5 — reconcile:
- getSyncHistory reconciles local state from the newest extension run (stamps
  lastScanAt/lastScanCount when local lags, clears a stale noticed-signin). The
  popup derives the last-sync line from the newest run so it never shows
  "no sync yet" while a recorded sync is listed.

Recipe-driven; no platform-name literals in shipped code (purity guard green).
Test infra: chrome mock + chrome.d.ts get alarms.clear; add @types/node so the
pre-existing popup test's node:fs/path imports typecheck. 73/73 tests pass,
tsc --noEmit clean. (Run under Node 22, matching CI; Node 20 hits an unrelated
jsdom ERR_REQUIRE_ESM.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZEx7hFyqqrzeptpN85TLc
E4 — make the Sign-in warning actionable. The popup had two disagreeing
sign-in signals: render() set red "finish syncing" text from status.needs,
but the #signin-cta button was only revealed when getSyncHistory returned
history.needs === "noticed-signin" (a 401). A user could see the red text
with no button (status.needs was noticed-signin while the history fetch
returned 200). Unify into a single source of truth: applyNoticedSigninState
reveals + wires the button whenever EITHER signal needs a noticed sign-in,
and clears the dangling red text so it never appears without the button.
network-signin keeps its informative (non-actionable) text.

E6 — add a persistent version footer at the bottom of the panel that ALWAYS
shows the installed manifest version (e.g. "v1.0.3"), and move the
"update available" notice into that footer (revealed by checkForUpdate when
behind, linking to LATEST_DOWNLOAD_URL). setVersion() is called in init()
alongside checkForUpdate. No platform-name literals — purity guard stays green.

TDD: extended popup.test.ts (E4 button from either signal + network-signin
path + E6 always-on version + bottom update notice). All 80 tests pass;
tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZEx7hFyqqrzeptpN85TLc
Scan resilience (checkpoint-and-resume + keepalive), live progress, unified
actionable Sign-in, server-history reconcile, and a version/update footer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MVRtK6RQbp12pHMV9dgA6Q
…d tab, close on syncConfirmed

The post-scan handoff tab now opens with active:false so it never steals
focus on the monthly auto-scan, and its tab id is persisted (syncTabId).
The syncConfirmed handler closes that background tab (guarded + best-effort
via chrome.tabs.remove(...).catch(()=>{})) once the POST + recipe refresh
have run, then clears syncTabId so a later scan starts clean.

No new permission needed — tabs.create/tabs.remove work without "tabs".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZEx7hFyqqrzeptpN85TLc
@simaonogueira101 simaonogueira101 merged commit d818384 into main Jun 24, 2026
4 checks passed
@simaonogueira101 simaonogueira101 deleted the fix/extension-scan-resilience branch June 24, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant