Skip to content

About

Sticky notes that stay stuck to the page, not to your browser.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Chevalet Note

Sticky notes that stay stuck to the page, not to your browser.

Put a note anywhere on any web page. It stays at that exact spot in the document — scroll, and it scrolls with the content like part of the site. Close the tab, close the browser, come back next week and open the same URL: the note is still there, in the same place.

Firefox extension. Offline, private, and yours to export.

Status: 0.0.16, live on addons.mozilla.org.

This line said "awaiting review" for five releases and was wrong for all of them. The listing was approved at 0.0.8, and 0.0.9 to 0.0.15 all went to the unlisted channel -- the first six from the signing script's default, 0.0.15 from a radio button on the upload form -- so no store user received any of them. pnpm amo:status now prints the channel of every version straight from AMO, which is the check that was missing. CHANGELOG.md opens with the whole story.

A signed build is also installable by hand from the releases page. The de-risking spikes are all runnable — see docs/spikes.md.


What makes it different

It barely touches the page. One element is added to <body>, with a closed shadow root and all: initial. Every pixel not covered by a note hit-tests straight through, so the site stays completely usable. There is no pointermove listener on the page, no whole-document MutationObserver, and no timers running when nothing is animating — an idle tab with notes on it costs 0% CPU.

The page-level listeners, counted rather than remembered — grep -n "addEventListener(" src/cs/renderer.ts src/cs/guard.ts is the whole list, and none of them calls preventDefault:

Added What
Whenever the content script runs, which is only on an origin you granted pageshow, pagehide, visibilitychange — saving, and the bfcache
Once the site is enabled and the notes for the URL are known dblclick — Alt + double-click makes a note, so it has to work before the first one exists
Once a note exists on the page, and not before keydown, keyup, pointerdown, blur, visibilitychange — hold-Alt
Only while a note has unsaved text beforeunload — removed the moment it saves, so the page stays eligible for the bfcache

That paragraph used to say "no keydown listeners", which stopped being true the day hold-Alt landed; then it said all of them arrived "once a note exists", which was never true of the four that have to be there before the first note. A sentence in a README does not notice when the code moves under it, which is why this one is now a table anyone can check in one command.

That element used to be appended to <html>, on the reasoning that a child of the root is the hardest place for a page to disturb. It cost three releases: Gecko will not run an editing command for an editing host outside <body>, so Backspace inside a note did nothing while typing worked perfectly. spikes/firefox-where.mjs proves it in a real Firefox, and tests/host.test.ts makes sure it cannot come back.

Assets are bytes, never URLs. Fonts go in through new FontFace(arrayBuffer), styles through adoptedStyleSheets, images through createImageBitmap into a canvas. The page's Content-Security-Policy has nothing to block, because the in-page layer makes no network request of any kind — and it is the only technique that can render a font you uploaded, or a picture you pasted, on a site with a strict img-src.

Notes are anchored, not just positioned. Every note records three things when you drop it: the element under the cursor, a text quote with its surrounding context (W3C Web Annotation selectors), and document coordinates. On reload they are resolved through a scored fallback chain, so a note survives responsive reflow, dynamic content and SPA routing instead of ending up 40,000px down an empty page. When an anchor genuinely cannot be found, the note says so and offers to reattach — it is never silently misplaced and never deleted.

Your notes are not in the cache. They live in the extension's own IndexedDB, which "Clear cookies and site data" does not touch, and unlimitedStorage keeps exempt from eviction. The two things that would lose them are Refresh Firefox and uninstalling the extension, and the answer to both is Export in the cabinet, which needs no permission at all and writes every note, its position, its style and its images to one archive — as a ZIP you can import back, or as Markdown or a single self-contained HTML page for reading elsewhere.

There is also a scheduled backup: an alarm, a ZIP into your Downloads folder, a ring of three files. It asks Firefox for permission to save files only when you switch it on, and the downloads permission is optional for exactly that reason.

This paragraph has been wrong in both directions. It once claimed scheduled backups that did not exist; then it was corrected to say there were none; then 0.0.11 built them and the correction went stale in its turn. Both versions were confidently phrased.

The durability table below is hand-written too, and nothing generates it from the code — so it is worth reading with the same suspicion. What the table says is checked against docs/STATUS.md on every release, which is a person's habit rather than a mechanism, and the honest statement is that this file is documentation and only the tests are proof.

A note belongs somewhere, and you choose where. This page, this section, this whole site, or every page — set in the note's own settings. "This section" is every page whose address starts the same way, worked out from the page you are on and shown in the option, so This section (/blog/) leaves nothing to guess. A note on the whole site appears on every page of it, in the same place.

It feels like paper. Dragging a note runs a critically-damped spring on position and underdamped springs on rotation, tilt, skew and corner curl, driven by pointer velocity and by where on the note you grabbed it. Only transform and opacity ever change per frame; the release settle is baked into a Web Animations keyframe list so it runs on the compositor and stays smooth even when the page's own JavaScript is blocking.


Keyboard

Every shortcut is matched on the physical key, so they work on any keyboard layout. Until 0.0.10 they were matched on the character the layout produces, which meant that on a Persian, Arabic, Cyrillic or Greek layout none of the letter shortcuts worked at all — while Backspace, Delete and the arrows did, because those do not depend on the layout. A Latin layout still wins where it has an opinion, so Dvorak behaves the way a Dvorak user expects.

Anywhere in Firefox — rebindable in about:addons → the gear → Manage Extension Shortcuts. The Keys pane in the cabinet reads the current binding from the browser, so it never disagrees with what you set.

Alt+Shift+A Stick a note in the middle of the page
Alt+Shift+S Turn notes off, or back on, for this tab
Alt+Shift+K Move focus to the next note
unbound Open the cabinet

On a page — Alt+double-click makes a note where you clicked. Right-click offers to add one, or to quote the text you have selected. Hold Alt and the notes go translucent and stop taking the pointer, so you can read and click the page underneath — a link under a note is clickable while the key is held.

On a selected note — grabbing the header, or the sliders button in it, selects the note.

S This note's settings: its name, where it shows, colour, type, size, direction, paper
C D P E Next colour · draw · pen · eraser
Z Undo the last brush stroke
L M Lock · collapse
Enter / F2 Start writing
Delete Send it to the trash
arrows Nudge — Shift ×10, Ctrl ×25. Alt+arrows resizes

While writing — the body of a note is markdown source, so Ctrl+B wraps the selection in ** rather than styling it. Pressing it again unwraps. Every keystroke is saved; you do not have to click away first.

Ctrl+B Ctrl+I Bold · italic
Ctrl+Shift+X Ctrl+E Strikethrough · code
Ctrl+K Make a link
Ctrl+Shift+. Quote
Ctrl+Shift+8 7 9 Bullet · numbered · task list
Ctrl+Shift+Enter Tick or untick this line's checkbox
Ctrl+Shift+1 Heading — again for smaller, again for none
Ctrl+Shift+D Insert today's date
Ctrl+Space Clear formatting
Ctrl+Enter Finish writing and select the note

There is deliberately no Ctrl+U: markdown has no underline, so there is nothing for it to produce that a note could render. Ctrl+Shift+M and Ctrl+Shift+K are left alone because Firefox owns them.

Anywhere in a note — Ctrl+Z and Ctrl+Y undo and redo everything, in the order you did it: typing, colour, moving, resizing, drawing, deleting. One stack for the whole page, so undo does not depend on finding the right note first.


Durability

Action Your notes
Clear cache / cookies / site data survive — the sanitizer excludes moz-extension://
Forget About This Site survive
Disk pressure / quota eviction survive — unlimitedStorage
Refresh Firefox lost — restore from a ZIP backup
Uninstall the extension lost — restore from a ZIP backup

Manual export and import is always available, needs no permission, and produces a single ZIP containing every note, its position and anchor, its images and ink, plus a human-readable Markdown mirror. Import reads it back: it checks the archive's checksum, shows you a merge plan before writing anything, and keeps the previous text of every note it overwrites as a version.

Scheduled backup writes the same ZIP into your Downloads folder on an alarm, on a ring of three filenames. It cannot choose the folder — the browser decides that — it cannot run while Firefox is closed, and a file on the same disk is not an off-site backup. It survives Refresh Firefox and uninstalling the extension; it does not survive the disk failing.

Privacy

No telemetry. No analytics. No accounts. No sync. Nothing about you leaves your machine, which is why the manifest declares data_collection_permissions: { required: ["none"] }.

There is exactly one network call in the shipped code, and it is worth being precise about rather than rounding down to zero: an optional check for a new release, off by default, which asks for its own host permission the first time you press the button and then reads one public version number with no cookies, no referrer and no identifier. Leave it off and the extension makes no network request whatsoever. PRIVACY.md has the full account.

Host permissions are not requested at install. You grant access per site, per domain, or for all sites, from a button in the popup, whenever you decide to.


Development

Requires Node 24+ and pnpm 11+.

pnpm install
pnpm dev

pnpm dev builds and launches a scratch Firefox profile with the extension loaded.

Command What it does
pnpm build Production build into dist/
pnpm watch Incremental rebuild
pnpm typecheck tsc --noEmit, strict
pnpm lint Biome check
pnpm test Vitest unit tests
pnpm lint:ext web-ext lint — AMO's own validator
pnpm check All of the above, the CI gate
pnpm package Zips an installable build into artifacts/

The build is a plain, readable build.ts rather than a framework, deliberately: AMO reviewers must be able to reproduce dist/ byte-for-byte from source. It is fully deterministic — no timestamps, no randomness, no network. Even the randomised shadow-host tag name is derived from name@version by SHA-256 so it is stable across machines.

Bundle budgets are enforced by the build and will fail it:

Bundle Budget Why
cs/guard.js 1 kB gz runs at document_start on every annotated page
cs/renderer.js 38 kB gz parsed on every page load that has notes
ui/manager.js 64 kB gz the cabinet, opened deliberately
ui/options.js, ui/popup.js 24 kB gz small pages, generous ceilings
bg/main.js 32 kB gz an event page parses its whole bundle on every wake

Every number has an argument behind it in build.config.ts, and raising one costs writing that argument down with a measurement. The renderer's has been raised six times and cut once — the i18n split took 11 kB out of it by moving the string catalogue behind a table a note actually uses.

Layout

src/bg/     background event page — the only writer to the database
   scope/   URL normalization and scope matching (pure, heavily tested)
   msg/     the typed protocol shared by all four contexts
src/cs/     the in-page layer — host, anchoring, physics, note UI
src/ui/     popup, options, and the note manager
spikes/     throwaway harnesses that answer design questions against a real Firefox
docs/       spike runbook, performance numbers, QA checklist

License

MPL-2.0

About

Sticky notes that stay stuck to the page, not to your browser.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages