side-notes/openspec/changes/archive/2026-08-03-add-notes-extension/tasks.md

4 KiB

1. Extension scaffold

  • 1.1 Create Manifest V3 manifest.json with sidePanel, tabs, webNavigation, unlimitedStorage permissions, action icon, and keyboard command binding to open the side panel
  • 1.2 Set up the build/dev tooling for a background service worker, a side panel page, and a notes-list page as separate entry points
  • 1.3 Wire chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }) in the background service worker

2. Shared storage & key module

  • 2.1 Implement buildNoteKey(url, config) with the default rule (scheme://domain/path?search, hash excluded) and a swappable config
  • 2.2 Define the IndexedDB schema: notes object store keyed by id, non-unique index on the derived URL key, index on updatedAt
  • 2.3 Implement getNoteByKey(key), saveNote(key, content, pageMeta), listNotes() (excludes soft-deleted), filtering deletedAt == null in retrieval
  • 2.4 Implement soft-delete support at the storage layer (markDeleted(id)), even though no UI triggers it in this change

3. In-page navigation detection

  • 3.1 Listen for chrome.webNavigation.onHistoryStateUpdated and onReferenceFragmentUpdated in the background service worker
  • 3.2 On either event, re-resolve the note key for the affected tab and notify that tab's open side panel (if any) to reload

4. Side panel note editor

  • 4.1 Build the side panel page shell with a dark theme applied by default
  • 4.2 On panel load/tab activation, resolve the active tab's URL to a key via the shared module and load (or initialize) that note
  • 4.3 Render the note content in a plain <textarea> with no other interactive text-editing controls
  • 4.4 Implement debounced autosave on keystroke, writing through the shared storage module and capturing pageTitle/favIconUrl from the active tab at save time
  • 4.5 Implement in-panel find: intercept Ctrl+F/Cmd+F while the panel document has focus, prevent the browser's page-level find, render a find bar, search textarea.value, and move the native selection (setSelectionRange + scroll into view) to each match
  • 4.6 Handle tab switching: when the panel's hosting tab changes its active URL (activation or navigation), reload the note for the new key

5. Cross-panel consistency

  • 5.1 Open a shared BroadcastChannel from every side panel instance
  • 5.2 On save, post { key, updatedAt } on the channel
  • 5.3 On receiving a message for the currently displayed key, reload from storage unless the textarea currently has focus; if focused, defer the reload until blur

6. Notes-list page

  • 6.1 Build the notes-list page shell (own tab, history-page-inspired layout)
  • 6.2 Query notes via the updatedAt index in descending order and group them into day sections (Today / Yesterday / date) in local time
  • 6.3 Render each entry with favicon + page title (falling back to URL when no title was captured) and a formatted time
  • 6.4 On entry click: query chrome.tabs.query({}), compute each tab's key via the shared module, and compare to the clicked note's key
  • 6.5 If a matching tab is found: focus its window (chrome.windows.update) and activate it (chrome.tabs.update), then call chrome.sidePanel.open({ tabId }) as the first synchronous action in the click handler
  • 6.6 If no matching tab is found: navigate the tab hosting the notes-list page to the note's URL, then call chrome.sidePanel.open({ tabId }) on it

7. Cross-browser verification

  • 7.1 Load the unpacked extension in Chrome and manually verify: per-tab side panel isolation, autosave/reload across restart, in-panel find, cross-panel refresh, notes-list grouping and click behavior (both matched-tab and no-match paths)
  • 7.2 Repeat the same manual verification in Brave, specifically checking chrome.sidePanel per-tab setOptions/open() gesture behavior parity with Chrome
  • 7.3 Note any Brave-specific discrepancies found and decide whether a workaround is needed before considering this change complete