Same-network peer discovery & an ephemeral shared tray (signed-out). Visitors
behind the same router can now find each other and open a temporary shared
"clipboard" that lasts only as long as both sessions are open.
Discovery + signaling ride a Supabase Realtime channel keyed by a salted hash
of the visitor's public IP (app/api/network/route.ts — raw IPs never leave
the server). Two clients on the same hash see each other as throwaway
"Adjective Animal" peers (lib/identity.tsrandomAnonName).
Connecting is explicit: one peer requests, the other accepts/declines
(components/LocalPeersDock.tsx). On accept, the pair upgrade to a direct
WebRTC data channel (lib/webrtc.ts) with chunked, back-pressured file
transfer — so links, text, and any file move peer-to-peer with no storage
bucket.
The shared tray (LocalPeersDock) shows every shared item live to both sides;
drag an item onto the canvas (or click +) to keep it. Closing the tab tears
everything down. Orchestration lives in lib/use-local-peers.ts.
Gated to signed-out users (signed-in users have account-based collaboration)
and requires Supabase to be configured; otherwise it's silently off.
Optional NETWORK_SALT and NEXT_PUBLIC_TURN_* env (see .env.example).
Drag-and-drop onto the canvas (components/Canvas.tsx). Drop image files,
generic files (PDF/zip/doc/…), dragged URLs, or selected text and each lands at
the cursor. A drop overlay highlights the target.
File cards (components/cards/FileCard.tsx, FileCard in lib/canvas-store.ts).
Non-image files become a download chip; bytes inline as a data URL (capped at
8 MB — larger files keep a metadata-only chip).
Tooling
Unit tests (Vitest + jsdom). 39 tests under tests/ cover the new feature
logic: WebRTC item/file wire protocol incl. chunked transfer reassembly
(tests/webrtc.test.ts), the /api/network IP-hash route (tests/network-route.test.ts),
byte formatting + card/edge persistence (tests/canvas-store.test.ts), and the
identity helpers (tests/identity.test.ts). Run pnpm test (watch) or
pnpm test:run.
Logged test runs.pnpm test:log runs the suite once and appends a
timestamped record to test-results.log (gitignored) via scripts/test-with-log.mjs.
Pre-push gate.pnpm verify runs the logged tests then a full next build.
A committed .githooks/pre-push runs it before every push (bypass with
git push --no-verify); pnpm install wires core.hooksPath automatically via
the prepare script (scripts/setup-hooks.mjs) — no husky dependency.
Changed
formatBytes now drops a trailing .0 so sizes read "1 KB" / "5 GB" instead of
"1.0 KB" / "5.0 GB".