# Tasks — tabvana

## Approved


















## Backlog

---

### Trivial — high-confidence bugfixes and syntax improvements

These are 1–5 line changes with obvious correct implementations and low regression risk.

---

### Complex bugfixes

Fixes that carry more risk, require verification, or involve non-obvious behavior.

- [ ] **P1 — `bulkPut` on tags in `ensurePageTracked` needs browser verification** — `pages.ts:191` uses `db.tags.bulkPut(...)` inside a transaction. A prior developer noted this caused excessive `useLiveQuery` re-renders and reverted to individual `put()` calls. Current code uses `bulkPut` again on the assumption Dexie coalesces notifications per transaction commit — unverified in a real extension context. Verify with React DevTools Profiler under rapid tab activation; revert to individual puts if re-render storms appear.

- [ ] **P2 — `backup.ts` unhandled `NotAllowedError` on File System API permission revoke** — `backup.ts:55` has a `// TODO catch permission denied error`. The outer `try/catch` logs the error but the user sees no actionable message. Fix: detect `DOMException` with name `'NotAllowedError'` and surface a specific UI message prompting the user to re-select the backup folder.

- [ ] **P2 — `ensurePageTracked` drops pages with empty titles** — `pages.ts:140-142` returns early when `title` is falsy, so tabs that open before their title loads are never written to IndexedDB. The `fetchMissingTitle` sweep can't backfill pages that were never tracked. Fix: track the page with an empty title; let `fetchMissingTitle` fill it in.

- [ ] **P2 — `TodoRow` Escape key may commit edit via blur-on-unmount** — `TodoView.tsx:56-61` handles Escape by calling `setEditing(false)`, which unmounts the `Input`. Chrome fires a `blur` on unmount, triggering `onBlur={handleTitleCommit}`, which saves if `editTitle` is non-empty — committing the edit the user meant to cancel. Fix: add a `cancelledRef`, set it in the Escape handler, check it in `handleTitleCommit`.

- ~~**P2 — `embeddingProxy.init()` catch swallows non-backoff errors**~~ — Fixed as part of embedding performance work: consolidated init catch block now always re-throws.

- [ ] **P2 — Search index hook silent desync** — The `updating` Dexie hook in `pageSearchIndex.ts` uses `onsuccess` without an `onerror` path. A failed index update silently desynchronises MiniSearch from the DB. Fix: add an `onerror` handler that logs and optionally triggers a full index rebuild.

- [ ] **P2 — Todo due-date timezone mismatch** — `TodoView.tsx:65` and `PageRow.tsx:118` convert stored UTC timestamps to date-input strings via `new Date(ts).toISOString().split('T')[0]`. In UTC− timezones the displayed date appears as the previous calendar day. Fix: offset by `getTimezoneOffset()` for display; inverse for storage.

- [ ] **P2 — No user-facing error feedback for failed todo edits in `PageRow`** — `PageRow.tsx` inline-edit handlers catch errors and call `logger.error` but show nothing to the user. The UI stays in an ambiguous state. Fix: surface a toast or inline error message on failure.

- [ ] **P3 — `TodoRow` stale-closure race on concurrent external edit** — `TodoRow` captures `editTitle` from `todo.title` at double-click time. If a background update changes `todo.title` while the inline edit is active, pressing Enter commits the stale pre-update title. Fix: re-read `todo.title` from the live query on blur/cancel.

- [ ] **P3 — `_similarityWorker` never terminated** — `windowsSlice.ts` holds a module-level `_similarityWorker` singleton that is reassigned but never `.terminate()`d, leaking the old worker thread. Fix: call `.terminate()` before reassigning.

- [ ] **P3 — Unawaited `chrome.storage.session.set()` in `chromeTabsListeners`** — `chromeTabsListeners.ts:48,96,134` fire-and-forget session writes with empty catch blocks. Window access-time state can silently become stale. Fix: await and log errors.

- [ ] **P3 — Unawaited `chrome.windows` calls in `WindowGroupHeader`** — `WindowGroupHeader.tsx:136` calls `chrome.windows.update()` and `chrome.windows.create()` without `await`; errors are silently swallowed. Fix: await and surface errors to the user.

- [ ] **P3 — `activeQueryCancellers` memory leak** — `pages.ts` `fetchTagsAndPagesForQuery` appends to `activeQueryCancellers` but never clears the array on exception. Fix: clear or splice the entry in a `finally` block.

---

### New features

Changes that add new behaviour, UI, or test coverage.

- [ ] **P2 — `similarityWorker` yield points** — `similarityWorker.ts` loops over all embedding comparisons without yielding. For large libraries this blocks the worker thread. Fix: `await new Promise(r => setTimeout(r, 0))` every ~500 items.

- [ ] **P2 — Tests: `TodoView` React component** — Zero React-level tests for `TodoView.tsx`. Missing: error display when `createTodo` throws, form state reset after successful add, `handleAdd` early-return, Enter/Escape keyboard shortcuts.

- [ ] **P3 — `updateTodoDueDate` should reject `NaN` timestamps** — `pages.ts` stores `dueDate` without checking `isNaN(dueDate)`. `new Date('bad-string').getTime()` returns `NaN`, breaking date rendering. Fix: throw if `!Number.isFinite(dueDate)` when `dueDate` is provided.

- [ ] **P3 — `TodoView` sorted array recomputed on every render** — `TodoView.tsx:162-169` recomputes `sorted` on every render. Wrap in `useMemo(() => ..., [todos])`.

- [ ] **P3 — Embedding failure UI** — `embeddingErrored` is set per-page but never surfaced in the UI. Users have no way to see which pages permanently failed or trigger a retry.

- [ ] **P3 — Database quota monitoring** — Bulk migrations have no `QuotaExceededError` handling. Wrap migration batches in try/catch; add a proactive quota check with a user-visible warning.

- [ ] **P3 — Accessibility: missing aria-labels in todo UI** — Checkboxes, delete buttons, date inputs, and priority selects in `TodoView.tsx` and the `PageRow` edit modal have no `aria-label` attributes.

- [ ] **P3 — Integration tests for DB migration → search index → UI sync** — This path is the highest-risk integration surface and has zero coverage.

---

### Abandon (speculative or not worth the cost)

These were flagged by prior analysis but are either unreachable in practice, already mitigated, pure speculation, or refactoring for its own sake.

- **`makeDomainSortKey` fragile https assumption** — Explicitly noted as unreachable in practice (only called with `forceHttps`-normalised URLs). Not worth guarding.
- **`ensurePageTracked`/`createTodo` silent embedding drop on queue failure** — If the queue write fails, the DB has larger problems; the embedding can be regenerated. Adding try/catch here is noise.
- **`Operation.payload` typed as `any`** — Requires a full discriminated union for all operation types. Type-only change; low real impact.
- **`Embedding not invalidated on `updateTodoCompletion`** — Needs a product decision before it's a bug. Not actionable as written.
- **`updateTodoPriority` accepts out-of-range values** — The UI already validates; direct DB callers don't exist.
- **`updateTodoTitle`/`createTodo` allow empty titles at DB layer** — UI already validates; DB-layer enforcement is redundant for this codebase.
- **`dbLog` uses `any[]` variadic args** — Micro type improvement with no real impact.
- **Break apart `queueProcessor.ts`** — Refactoring for its own sake; file is manageable.
- **Refactor global singletons** — Speculative architectural improvement.
- **Performance monitoring / telemetry** — Overengineered for a personal extension.
- **Downgrade-safe migrations** — Extremely rare scenario; high implementation cost.
- **Re-enable autoTag feature** — Needs a product decision, not a bug fix.
- **Title plugin bypass inconsistency** — Vague, no confirmed user-visible impact.
- **Backup silent-drop on concurrent calls** — Minor UX; not worth the complexity.

---

## Analysis Notes (2026-04-01)

| Metric | Value |
|--------|-------|
| Approx. source LOC | ~11,000 |
| Test files | 6 |
| `any` type uses | ~20 (reduced) |
| Dexie schema versions | 18 |
