Repository navigation
Conversation
Replaces the 2021 Next.js / Manifest V2 "URL Notes" app with a vanilla Manifest V3 extension built from skeleton-chrome-extension. - One note per canonical page, with dated entries. Page identity uses the Organize Tabs URL rules (copied for now, see organize-tabs#12), so a note on a PR shows on its Files tab and a Jira issue on its board. - Presence badge with the entry count on the toolbar icon. - Capture selected text as a quote from the context menu. - Reminders (presets or a time) that notify and reopen the page; snooze a tab (close now, reopen later) from the popup, context menu or a keyboard shortcut. - Page snapshot (title, description, leading text) stored locally with the first entry, via the activeTab grant. Nothing is transmitted. - Markdown with YAML frontmatter as the format: zip export, import with merge, and an Obsidian vault folder connection that writes every save. - All notes page with search, reminders view, settings with a rules tester. Notes in chrome.storage.local; settings in sync. - Display name lives only in the manifest; code reads it at runtime. - 31 unit tests (note model, Markdown round-trip, zip writer, URL rules) and a 20-check headless e2e suite; CI with a zip artifact. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Every save or popup open on a noted page captures the page locally: title, core metadata (canonical, OpenGraph, author, published, JSON-LD types) and text. Captures are kept as history (capped, first kept, identical text collapses), so there is a record of what you saw. - Changed-since-last-visit: line diff between the two newest captures, surfaced in the popup and on a new note detail page. - Profile insights: a text-based LinkedIn profile parser (flat and grouped positions, locations, education) and an analysis that asks about overlapping full-time roles, two current roles, backwards dates, short stints and gaps, with years, employers, average tenure and a one-line card grouped by employer. Edits to the history between visits are detected and listed. All phrased as questions, never verdicts; built against fixtures only. - Note detail page with insights, diff, entries and every capture. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- LinkedIn renders most text twice (visible plus a visually hidden copy); consecutive duplicate lines now collapse so parses match clean text. Fixture test doubles every line and expects the same result. - "Show all N experiences" is read as a truncation marker and surfaced: N listed, M captured, open Show all and capture again. - A stated "N+ years of experience" that the visible, complete history does not reach becomes an ask. - "Capture page" in the popup keeps a page's current state without writing an entry; such notes are retained and listed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ettings copy The shared ui.css now forces [hidden] to display:none, which the flex containers in the popup were overriding. Applied to the skeleton and Organize Tabs copies of ui.css too (uncommitted there). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Self-employed and internship rows, a company line with no employment type, lowercase present, en dashes, duplicated lines, education with no degree line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Chrome's HSTS preload upgrades linkedin.com to https on the CI runner, so an exact http URL match found no tab. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Markdown round-trip: entries merge by minute and text, so re-importing a backup over an existing note no longer duplicates every entry. - Imports keep the existing note's reminder; a file's reminder is only honoured for a new note and only if it is still in the future, so a restore cannot fire a burst of stale alarms. - Snoozing without text records a "Snoozed until" entry, so the note survives the reminder firing even with tab-opening off. - Index writes are serialised; concurrent saves no longer drop rows. - Badge passes load settings and the index once and read no notes. - Repeat identical captures within the hour do not rewrite the note. - One captureFor helper; per-parser capture budgets live in profile.js. - Rules editor preserves regex flags. package.json version matches. - el/send/fmt live in src/lib/ui.js, shared by the three pages. - make screenshots renders README images and four store screenshots. - e2e covers re-import merge, past reminders on import, text-less snooze; unit tests cover merge keys, capture throttling, budgets. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Interaction model - The editor is today's entry: it saves as you type (debounced, and on blur, hide and close). No Save button. Reopen the same day and the text is there to keep editing. Earlier entries edit in place on click and save as you type; delete on hover. - Reminders and snooze are one-click chips under the editor; the current reminder shows inline with a clear link. - Opening the popup captures the page automatically, creating a capture-only note when none exists. All notes hides capture-only pages behind an "Include pages you only captured" toggle. - Esc saves and closes. Page favicon in the header. Visual pass - Refined token system in ui.css: indigo-biased neutrals, a type scale, radii, layered shadows, an ease curve, reduced-motion support, and color-scheme set per theme so native controls follow. - Editor as a focused card with an uppercase day label and a quiet saved status; chips as soft pills; earlier entries as a dated timeline with a rail; insight rows with warn/accent treatments. Tests: setToday unit test; e2e covers draft create/update/remove, editor restore on open, autosave status, no Save button, the captures filter. 46 unit, 36 e2e, all green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brave ships with the File System Access API off, so showDirectoryPicker does not exist there. The settings page now disables Connect, names the flag (brave://flags/#file-system-access-api) with a copy button, and notes that export and import work everywhere. e2e checks the state. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
"(3) Jane Doe | LinkedIn" becomes "Jane Doe". Applied when a note is created and on every capture, so a renamed profile renames the note while captures keep the raw title. Also: an undated preamble in an imported file sorts before the first dated section instead of at import time, which made that test date-sensitive. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The site name is part of what the page is and carries into Obsidian filenames and frontmatter titles. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- src/panel.html is the popup view in panel layout (fluid width, full height). It follows its own window's active tab, refreshes when a note changes elsewhere, and saves what is being typed before switching. - "Sidebar" in the popup footer opens it; a setting makes the toolbar icon open the sidebar instead of the popup. The icon click still grants activeTab, so the page is captured on that click. - Uncaptured pages in the panel show a one-line hint, since a capture needs the icon click on that page. - e2e: panel follows tab switches within its window and shows the active tab's entry; the setting flips openPanelOnActionClick. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- src/lib/anchor.js: a sticky remembers its element three ways (css path rooted at the nearest stable id, text quote with context, structural path) and resolves css → quote → path, verified by text similarity; unresolvable stickies are orphans, never guesses. Unit-tested on a fake DOM. - src/content/: an in-page layer in a shadow root with numbered pins, hover ring, click-to-edit popover that saves as you type, and a pick mode to add a sticky to any element. Injected on the user's click via activeTab; per-site "always show" registers it through optional host permissions requested from the popup. - Note model: stickies ride in the note, export to a "## Stickies" section with hidden anchors that round-trip, and merge by id. feedbackMarkdown renders a numbered list with quotes for reviews. - Popup and panel: an "On the page" section with the numbered list, + Sticky, Copy feedback, click to scroll to a pin, and the always-show toggle. Sticky-only notes count as notes. - e2e: stickies through messages, feedback export, and the layer in a real DOM served locally (pins, placement, popover autosave, pick mode anchoring, orphan reporting). Loopback is excluded from the DNS block. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d delay Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…r matching - notes.js: `links` on a note is the list of the other pages' canonical keys, symmetric, set by linkNotes/unlinkNotes. Keys round-trip through the Markdown frontmatter (`links: [...]`) and resolve to ids with noteId(); mergeNotes unions them; indexEntry counts them. Title cleanup strips only leading unread counters and keeps site names. - store.js: a note that only holds a link is kept, not treated as empty. One-time migration cleans unread counters from saved titles and the index together. - settings.js: `launch` is 'popup' | 'sidebar' | 'floating'; the earlier openInSidePanel switch migrates to 'sidebar'. - picker.js: matchRows filters the index on every typed word across title, site, URL and latest note, title hits first, newest first. - Tests for all of the above. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…layer reboot - The toolbar icon opens a popup, toggles the side panel, or (floating mode) reaches onClicked, which captures the page, injects the layer and shows the widget. applyLaunchMode() sets the popup and the panel behaviour: a popup takes the click before anything else sees it, so it is cleared for the other two modes. The mode is cached so the side panel fallback on browser pages can open synchronously inside the click gesture. A failed launch marks the badge. - layer.js: a draggable floating button and a draggable window holding an iframe of the extension's own panel page, inside the shadow root; positions remembered through the `widgetLayout` message. The frame asks the layer to close by postMessage, accepted only from the frame and the extension origin. - annotate.js: a layer orphaned by an extension reload (its chrome.runtime.id is gone) is replaced, not reused; boot() removes stale hosts. - Messages: link, unlink, links (resolved rows, missing pages flagged), widgetLayout; `current` returns the linked rows; deleteNote removes the page from the pages it was linked to. Panel and popup refreshes never capture. - manifest: panel.html and the icon are web-accessible for the widget. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…bed mode - explorer.js/css: a shared Sites > Pages > Page browser for All notes and the sidebar. Navigation is a hierarchy, never a toggle: up() climbs one level, a breadcrumb jumps to any ancestor, the narrow layout shows one level at a time. Across all sites, pages are listed under a heading per site. The preview lists linked pages, navigates to them, and links new ones with the picker. - Sidebar: the header arrow lands on the current site's pages, the library arrow hides at the top, "Current page" in the footer returns; the header names the level. A close control shuts the panel. Saved-note banner and sticky show/hide controls. - popup.js runs three ways: popup, side panel, and embedded in the floating widget (bound to its tab, dismiss() instead of window.close()). Drafts carry the displayed page, saves retry, refreshes are versioned. - "Linked pages" card with the type-to-find picker in popup, sidebar and widget; read-only list on the details page. - Options: three-way launch mode selector; the explorer replaces the old list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- e2e: 144 checks. New coverage: launch modes and the popup/behaviour pair with the legacy migration; the widget layout message; the embedded panel's Esc; the layer's floating button and window including drag by synthetic pointer events and the dead-layer reboot; the sidebar's hierarchical navigation, site groups, level titles and close control; link, unlink, links, markdown export of links, delete cleanup; the picker's filtering and keyboard; link navigation in the library. Panel reliability: passive refreshes, save failure and retry, held responses, out-of-order lookups. - make check covers content scripts and stops on the first error. - README, STORE_LISTING and HANDOFF describe the new behaviour and the verification done in a real headless Brave for floating mode. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- vault.js: syncFolder(folder, store, state) reconciles one profile's notes with the Markdown files in the shared folder. Metadata-only scan; a file is read when its mtime or size changed and compared by FNV hash before parsing; merged with mergeNotes; written only when it differs from the note's serialization, so profiles converge instead of ping-ponging. Local changes made while the vault was unreachable are caught against the recorded hash. Deletions propagate both ways through the per-profile state. A note's file follows its title. Capture-only notes get no file. memoryFolder for tests and the e2e seam, handleFolder over a FileSystemDirectoryHandle. - store.js: vaultFolder() returns the adapter with a permission status, replacing the write-only helper. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
share.js: sharePayload (stickies, entries optional), encodeShare/decodeShare (JSON -> deflate-raw -> optional PBKDF2-SHA256 + AES-256-GCM with fresh salt and iv -> base64 at 76 columns, wrapped in BEGIN/END PAGE NOTES lines with a versioned header), inspectShare, applyShare (stickies by id, entries by minute and text, idempotent), shareFileName. Web APIs only, so it runs in the worker, the pages and node. Tests cover round trip, encryption, wrong or missing passphrase, damaged and foreign input, and merge behaviour. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, docs - Worker: persist writes through the folder adapter and records the file; deleteNote removes it; syncVault is serialised and runs from a per-profile alarm every 5 minutes, at startup, on Sync now, after re-allowing access, and at most once a minute when a page is viewed. vault:status carries the last report; `current` returns it. Messages: syncVault (writeAllToVault kept as an alias), vaultStatus, shareExport, shareImport (page resolved by the recipient's own rules, note created if needed). - Popup/sidebar/widget: once-per-session "Allow" banner for vault access; Share… and Import… in the stickies card with copy, download and paste. - Settings: Sync now with last-sync status, vault copy for synced drives and several profiles, import of a shared file with passphrase. - e2e: vault sync through the in-memory folder (import, write, rename, steady state, deletion from elsewhere, status, alarm); share export/import with passphrase, wrong passphrase, idempotence, foreign text; the sidebar share panel. 164 checks. README, STORE_LISTING and HANDOFF updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… files - vault.js: observeHandle() wraps FileSystemObserver (feature-detected) and reports changed Markdown file names, or "full" when the browser lost track; changeCollector() batches a burst into one flush. syncFolder() takes `only`: it stats just those files, merges them, handles reported deletions, and leaves unrelated notes alone. memoryFolder and handleFolder gain stat(). - The side panel, the floating widget and Settings are long-lived pages and watch the vault folder while open, asking the worker for a scoped sync as files land. The worker carries the same watch, re-armed on every wake, which is a no-op until a browser exposes the observer to service workers (Chromium 155 does not). The 5-minute alarm and the look-at-a-page sync stay as the safety net. - e2e: a scoped sync over the in-memory folder; the real observer over the origin-private file system pins the record shape. 167 checks. Docs updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es today's entry The entry landed from the simulated other profile was dated noon today; in CI's UTC clock that made it the newest entry of the day and the editor showed it instead of the one the later check expects. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both plain names are taken in the Chrome Web Store: a "Page Notes" with 10k+ users, a "Marginalia" study-notes extension, and Marginalia Search. The combined name is deliberate: the brand is Marginalia, and a search for page notes still finds it. manifest `name` is "Marginalia (Page Notes)", `short_name` is "Marginalia"; code reads them at runtime, the short name for compact spots (pins, widget bar, badge tooltip) and the full name for titles and the store. The store summary fits 132 characters. User-visible format tokens are renamed now, before any file exists: share envelope -----BEGIN/END MARGINALIA-----, header marginalia/1, file suffix .marginalia.txt, frontmatter app: marginalia. Internal keys (IndexedDB name, storage keys, the data-page-notes host attribute, pn: messages) stay by rule. The repo name stays notes-chrome-extension. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The index keeps each note's link keys (indexEntry.linkKeys; old rows rebuild once) and the list message resolves them to titles and sites. A page row shows a small mark with the number of linked pages beside its entry and sticky counts; the names are in the tooltip, and the preview still lists them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nned
The author comes from the manifest ("Hacktoolkit (@hacktoolkit)", parenthetical
dropped), so the name rule still holds. The library header in the sidebar is
sticky and the search field focuses without scrolling, so the title no longer
slides out of view inside the widget.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the 2021 Next.js / Manifest V2 "URL Notes" app, which no longer loads in current Chrome, with a vanilla Manifest V3 extension built from skeleton-chrome-extension.
What it does
Naming
The display name is only in
manifest.jsonand read at runtime everywhere else. Currently "Page Notes"; "Marginalia" is the other candidate.Verification
54 unit tests (note model, Markdown round-trip, zip writer, URL rules, profile parser and analysis) and a headless e2e suite covering every feature including re-import merge and reminders; an adversarial review pass found ten issues, all fixed with tests. CI runs everything and attaches the zip.
Closes #16 (delete), #17 (edit), #10 (all notes view). Supersedes #3 (encryption) and #4 (cloud sync), which are explicit non-goals for v1.
🤖 Generated with Claude Code