Moving a paragraph from your phone to your laptop is still annoying in 2026. The options are an app on both machines, an account to tie them together, or emailing yourself — and if the two devices don't share an ecosystem badge, the good native option disappears entirely.
We wanted the browser version of that. What we did not want was to build a clipboard product next to the notes product.
The idea: the room code is the address
ShareNotes already had everything a shared clipboard needs. A note has a short
memorable slug, lives at sharenotes.dev/<slug>, and syncs to every
browser viewing it over a WebSocket. Two people on one link already see each other
type.
So a "clipboard room" doesn't need to be a new kind of object. It needs to be a new way of arriving at the existing one. A normal note is created for you and you share the URL that comes back. A room inverts that: you pick the address first, type it on both devices, and they meet there.
The whole feature in one sentence: the room code is the note slug. Everything else follows from that, including the parts we didn't have to write.
Which means /clip is not a real-time system. It is a form with one
input that resolves a code to a note and then hands off to the note view — the same
note view, with the same WebSocket, that has been running since launch.
What the code screen actually does
There are only two paths, and the interesting part is that they converge:
const { available } = await checkSlugAvailability(slug);
if (available) {
// Nobody has this code yet, so this device opens the room.
await saveNoteToCloud(' ', slug);
}
onEnterRoom(slug);
If the code is free, you just created the room. If it's taken, you just joined it. The UI says "Join or create room" on one button because, from the user's side, those are the same action — and from our side the difference is one conditional.
That single space in saveNoteToCloud(' ', slug) is not a typo. The note
endpoint rejects empty content, and a room has no content until someone types. A space
is the smallest thing that satisfies the validator and gets replaced by the first
keystroke.
Choosing the alphabet
Generated codes are six characters, and the alphabet is deliberately not the obvious one:
const CODE_ALPHABET = 'abcdefghjkmnpqrstuvwxyz23456789';
Count them: 31, not 36. Missing are i, l, o,
0 and 1. These codes get squinted at on a phone screen and
retyped on a laptop, and every one of those glyphs is a coin flip in some font. Losing
five characters costs about 12% of the keyspace and removes the single most likely
reason for a room to silently fail — you typing a code that is almost the one
on the other screen and landing in an empty room you just created by accident.
Six characters over 31 symbols is about 900 million combinations. Codes come from
crypto.getRandomValues, not Math.random, because the room's
only protection is that nobody guesses its address.
What reusing the note stack bought us
Because a room is a note, it inherited a feature list we never wrote for it:
- Real-time sync — the same WebSocket, so a room is exactly as live as a shared note
- PIN protection — lock a room from inside once you're there
- Self-destruct timers — 1 hour, 24 hours, 7 days
- View-once mode — the room burns after the other device reads it
- QR codes — if retyping a code is still too much
- Expiry and cleanup — rooms age out on the same schedule as notes, handled by a job that already existed
None of that is clipboard-specific code. It is the note feature set, arrived at through a different door. The alternative — a standalone clipboard service — would have meant a second sync implementation, a second expiry policy, and a second set of bugs that behave almost but not quite like the first set.
The two things it cost
This is not free, and pretending otherwise would be the boring version of this post.
Rooms are unlisted, not private. A note's security model was always
"the URL is the secret." Rooms inherit that, but they weaken it: a code you chose to be
memorable is, by construction, more guessable than a random slug. If you use
notes or test as your daily room code, someone will eventually
be in there with you. The generated codes exist precisely because the memorable ones are
a trap, and the PIN is there for when the room holds something you'd mind sharing.
Creating on join makes typos cheap and invisible. Because an unclaimed code is created rather than rejected, a mistyped code doesn't error — it silently opens an empty room. That's the correct behaviour (there's no account, so we can't know which rooms are "yours"), but it means the failure mode for a wrong code is confusion rather than an error message. Dropping the ambiguous glyphs was the cheapest available fix.
When to use which
Rooms are better when both devices are yours and in front of you: no link to send anywhere, and the code is faster to retype than a URL is to transcribe. Regular notes are better when the other person is elsewhere — a memorable URL travels through chat, and a QR code beats reading six characters aloud over a call.
Same object underneath, either way.
Try it: open sharenotes.dev/clip on your phone, tap "Give me a code", and type it on your laptop. About fifteen seconds, no account, nothing installed.