Note It Down 📝
A note-taking Chrome Extension leveraging the modern Document Picture-in-Picture (PiP) API to create floating, borderless, always-on-top text editors.
The Backstory
"I know, the world didn't need another notepad. We have 4 billion of them, half VC-funded, all asking 'Sign up to continue' before you've typed your grocery list.
I just wanted notes to sync between my work laptop and personal laptop without using my personal account at work, my work account at home, or making Yet Another Account on Yet Another SaaS tool that'll eventually pivot to 'AI-powered productivity' and start emailing me.
So I built Note It Down. It's a notepad. Not reinventing anything, it just syncs without logging in, which felt rare enough to post about."
Specs & Info
- Environment:Chrome Sandbox (Sidebar)
- PiP Library:@pip-it-up/react
- Security Layer:AES-GCM-256 E2EE
- Relay DB:Cloudflare Workers + KV
Core Implementation
Floating PiP Window
Pops out any active note into a native, always-on-top Document Picture-in-Picture canvas. This strips away default browser margins, headers, and footers, creating a clean full-bleed editor pane to prevent alt-tabbing.
Shadow DOM Isolation
The sidebar launcher script mounts the React editor container within an isolated Shadow DOM context on host pages (like Reddit or GitHub), completely preventing host CSS sheets from breaking styles or leaking resets.
Zero-Knowledge Storage
A secure system using derived AES-GCM-256 keys. Notes are encrypted client-side using WebCrypto APIs before sync transmission. The worker server only receives encrypted blobs, keeping credentials private.
Cloudflare Edge KV Relay
Notes sync directly to your self-hosted Cloudflare Worker. Cloudflare's free tier provides 1GB storage and 100,000 requests/day, making cross-device relays fast, private, and free to host.
🧩 Powered by pip-it-up
This extension was designed to demonstrate how effortlessly you can wrap any React component in a native PiP frame using the `@pip-it-up/react` library.
Here is the exact controlled integration from the `EditorOverlay.tsx` file inside our codebase:
import { PipWrapper } from '@pip-it-up/react'
// ...inside the component...
<PipWrapper
width={380}
height={360}
open={activeNoteId !== null}
onOpenChange={(openState) => {
if (!openState) {
setActiveNoteId(null)
}
}}
placeholder={<div style={{ display: 'none' }} />}
>
{activeNoteId && (
<NoteEditor
key={activeNoteId}
noteId={activeNoteId}
onClose={() => setActiveNoteId(null)}
theme={theme}
/>
)}
</PipWrapper>Zero-Knowledge Cryptography
Server-blind database read/write validation using WebCrypto
To achieve complete privacy, the extension employs a zero-knowledge structure using WebCrypto APIs:
- Deterministic Key Derivation (HKDF): From a single sync token, the extension derives three independent cryptographic values via HKDF-SHA-256 (using a fixed 32-byte zero salt):
- `address`: The database lookup key (used as the URL segment on the worker).
- `encryptionKey`: An AES-GCM 256-bit key used locally to encrypt/decrypt note database payloads.
- `verifyKey`: Stored on the worker during the first sync to authenticate future writes.
- Write Token Derivation (HMAC): A `writeToken` is derived by computing an HMAC-SHA256 signature of `"nid-write-auth"` using the derived `verifyKey` bytes as the key.
- Zero-Knowledge Boundary: The client only transmits the `address`, the `writeToken`, and the encrypted `blob`. The `encryptionKey` and the raw `token` **never leave your device**.
- Worker Verification: The worker verifies subsequent writes by re-computing the HMAC signature using the stored `verifyKey` and comparing it to the incoming `writeToken`. The worker is content-blind (cannot read notes) and token-blind (does not know the raw token).
Technical Challenges & Solutions
| Challenge | Browser Policy | Problem | Solution |
|---|---|---|---|
| 1. Sandboxed Extension Frame | Extensions block documentPictureInPicture in popup/sidebar frames. | requestWindow() throws security exceptions in default extension overlays. | Migrated UI mount targets to a webpage-injected sidebar drawer element. |
| 2. Gesture Token Expiration | Document PiP requires active user click tokens. | Async messages (popup to script) expire user click context. | Mounted UI directly in host page DOM so clicks act as native events. |
| 3. Content Security Policy (CSP) | Strict host pages block lazy scripts. | Lazy load chunk scripts trigger CSP exceptions on GitHub/Google. | Configured Vite packaging configurations to bundle build into a single IIFE script. |
| 4. CSS Isolation | Injected scripts are isolated from style namespaces. | Copying document.styleSheets into PiP results in blank components. | Injected the absolute CSS link of the extension to PiP head on instantiation. |
| 5. Host Style Bleeding | Host global resets leak onto injected elements. | Sites like Reddit overwrite margins/font alignments in our sidebar launcher. | Encapsulated React app mounting container root inside an isolated Shadow DOM. |