Back to the work

Wordship

You choose my word.
I’ll choose yours.

A friend sends you a challenge. You think of a word that might stump them. A few guesses later, there’s something new to compare, laugh about and try again.

I’m building Wordship for those small moments of playing together, with daily puzzles for the time in between.

Play in your browser. Start as a guest; create an account later and your games stay yours.

Illustrative friend matchTwo words, one shared result
Follow a match between two friends

One word in each direction

You choose their wordA puzzle for your friend
They choose yours
A puzzle for you

You know the word you set for your friend. Neither player sees the word they must solve.

Together, with different clues

Your board
Two guesses · keep going
Your friend
3/6guesses made
Still solving · letters hidden

You see your clues and their progress. Their guesses and feedback stay hidden until both players finish.

Compare after both players finish

Your resultSolved in 3 guesses
Your friend's resultSolved in 4 guesses

Now both words and results can be seen together. These sample guesses are invented to explain the exchange.

01 / Deduce

Every guess gives you
something to go on.

Find a five-letter word in six guesses. Each guess tells you which letters fit, which need moving, and which you can leave behind.

Play the shared daily word, keep playing fresh words, or choose words for a friend while they choose yours. You can also challenge each other to the same random word.

DEDUCE / AN ILLUSTRATIVE ROUNDHidden word: CRANE
Follow the guesses

A and E are in the right places. R belongs somewhere else. S and H are out.

Moving R helps. Now C, R, A and E are in place. Only the fourth letter remains.

N completes the word. Each guess narrowed the possibilities; the third solves it.

  • Right place
  • Somewhere else
  • Not in the word
Six guesses give you room to reason. The answer is shown here to explain the clues.

A friendly rivalry, at your pace.

Share an invite link, or say a five-character room code aloud, for one head-to-head game, even without an account. Signed-in friends can also find each other with friend codes and play challenges across one, three or five rounds. Choose Performance to compare results after every round is played, or Race: when one person finishes, the other gets thirty seconds.

02 / Salvo

First find the word.
Then work it out.

Play a daily board or start a fresh solo hunt. With a friend, you each hide five five-letter words on a ten-by-ten grid and take turns hunting for them. Hits tell you which word you’ve touched, but keep its letters secret.

Find every cell of one word and a timed Deduce challenge opens. Your friend watches you try to solve it. A solution sinks the ship; a missed word still scores the letters you found before the hunt resumes.

01 Find its five cells.

02 Figure out the word.

Six guesses. Three minutes.
Your friend watches the attempt.
Illustrative Salvo sequence. Finding the ship earns a word-solving challenge.

Behind the play

The same game
on both screens.

A shared puzzle depends on small promises: whose turn it is, what each person can know, and where the game stands when they come back.

01

The shared result

The server validates guesses and decides when a round ends. Both players’ screens use that saved result, including when their final guesses arrive together.

02

Protecting unsolved answers

A player can read the progress they need without receiving their unsolved answer. Access to a game and its live updates is limited to its participants.

03

When the connection fails

If a guess is accepted but the next read fails, the submitted letters stay on the board and further input pauses. Refresh brings back the saved game, so a momentary connection problem does not make a player guess whether their move counted.

04

Starting as a guest

You can begin without a signup form. Wordship gives the guest a durable player identity and saves their moves. An account with no existing Wordship history can later claim that same player instead of starting over.

Engineering

Game state and access

Two friends on two phones each see a board, but there is one game. It lives in Postgres, and rules in the database decide what each player may write and read. The page sends requests to the app’s server, which works out who is asking. Functions inside the database then check that this player may make the move, take a lock, apply it and return the result.

Authority

SECURITY DEFINER PL/pgSQL · search_path pinned · client writes revoked · FOR UPDATE; room writes: match, then round · one STABLE snapshot · answers null until complete

A move is a call to a function that runs inside Postgres with its own rights, so a modified page cannot write a game row, a placement or a guess.

How it works

The definer functions that make game moves pin their search path. A move locks its game row before it changes anything, and a room write locks the match before its round, always in that order. When two players move at the same moment, one waits for the other, and the fixed order keeps them from deadlocking. One STABLE function builds the whole authorized game view from a single snapshot. Both players’ answers stay null until the game is complete, so the secret word stays on the server until then.

Access

row-level security · anon and authenticated revoked · explicit JSON projection · missing and forbidden look alike

Row-level security and restricted writes separate application tables from server-owned records, with the database checking who may read or change game state.

How it works

Tables only the server should touch, such as guest sessions, receipts, budgets and account links, also revoke every privilege from the anonymous and signed-in roles. The read functions return only an explicit JSON projection of the data. A missing game and a game the caller is not in get the same answer, so game ids cannot be probed for existence.

Live updates

Realtime Broadcast, private channels · RLS on realtime.messages · topic parsed per subscription · send only on own typing channel · RESTRICTIVE beside permissive · guests reconcile over HTTP

The database authorizes each subscription, where policies on realtime.messages parse the topic name and admit only that game’s participants.

How it works

Live updates travel on private Broadcast channels. A client may send only on its own typing channel, and an opponent’s draft becomes visible only once the reader has finished their own solve. Wordship’s permissive policies are paired with restrictive ones, so a broader policy on the same table cannot widen access to a Wordship topic. Guests are not on Broadcast. Their page reconciles over HTTP while it is visible.

Retries

expected slot number · unique (game, guesser, guess_number) · 64-hex operation key · advisory locks: key, then player · OPERATION_KEY_CONFLICT · replayed: true

If the reply is lost and the client sends the same word for the same slot again, the function returns the row it already committed, without a second write, feedback calculation or broadcast.

How it works

A guess names the slot it expects to fill and is written under the game’s row lock and a unique index on game, guesser and guess number. Only the same word replays, and a different word for a filled slot is refused. Creating a room takes a client-made 64-hex operation key under two advisory locks, one for the key and one for the player, and records a receipt. If the same player retries, they get the same room back, marked as replayed. The same key from a different player is refused with a conflict, so it cannot fetch someone else’s room. Practice guesses, friend-room secrets, Salvo shots and challenge guesses use the same expected-slot pattern.

Identity

256-bit token, SHA-256 digest stored · HttpOnly, SameSite=Lax, Secure · 365-day expiry, revocable · account to player link table · one atomic claim · erasure core + tombstone

The server mints a 256-bit token and keeps it in a host-only HttpOnly cookie that page scripts cannot read, and Postgres stores only its SHA-256 digest.

How it works

A friend can play from a link without signing up. The server mints a 256-bit token and keeps it in a host-only HttpOnly cookie that page scripts cannot read. Postgres stores only its SHA-256 digest, so the database holds no usable credential. The session expires after 365 days and can be revoked. A player’s lasting identity is a profile kept separate from any sign-in account and joined to it through a private one-to-one link table. Naming a guest and attaching an account is therefore one atomic claim, and it rewrites no game row. Deleting a guest or an account runs the same erasure core. It removes the profile, notifications and every game the player took part in, both seats’ turns included. Opponents’ profiles stay intact. Erasure also writes a digest-only tombstone so a late request cannot recreate an erased guest, and a daily job erases expired guests in bounded batches.

Abuse

HMAC-SHA256 of the requester IP · INSERT ... ON CONFLICT budget · fails closed without a client IP · 5 symbols, 32-character alphabet · CHECK on the code format · oracle gate

Anonymous room creates and code joins draw on fixed-window budgets kept in Postgres, so the bound holds across cookie resets and concurrent Worker instances.

How it works

Anonymous room creates and code joins draw on fixed-window budgets kept in Postgres and updated by one atomic upsert, so the bound holds across cookie resets and concurrent Worker instances. The bucket key is an HMAC-SHA256 digest of the requester’s address and the operation. The raw address is never the key. The API refuses the request when no trusted client address is present. A spoken room code is 5 symbols from a 32-character alphabet without I, O, 0 or 1, enforced by a CHECK constraint in the database and the same pattern at the API. Replaying a submitted secret word is permitted only in friend rooms where the players chose the words. On Daily and practice games the same path would be an answer-checking oracle, so it is refused there.

Delivery

forward-only migrations · pg_get_functiondef assertion · Vitest checks · test, typecheck, lint, then build · artifact secret scan before deploy · Cloudflare Workers via OpenNext

The schema has moved through forward-only migrations, and every web push to main runs the Vitest suite, typecheck and lint before a guarded OpenNext build.

How it works

The schema has moved through forward-only migrations. A migration that reworks identity reads the deployed function body with pg_get_functiondef and asserts that each expected fragment appears exactly once. It then rewrites the body around an explicit player and raises if any auth.uid() dependency remains, so drift in production aborts the migration before behaviour can change unnoticed. Every web push to main runs the Vitest suite, typecheck and lint before a guarded OpenNext build. That build refuses a checkout with local env files or server secrets in its environment, scans the compiled Worker for secret names and the client chunks for localhost URLs, and re-runs that scan right before wrangler deploy. Releases are serialized and each is tagged with the commit it came from. The app is served from a Cloudflare Worker on its own domain.

A lost reply, retried. Each lane follows one request whose reply was lost and which the page sends again: a guess that names its slot, and a room created with an operation key. The room lane ends with another player trying the same key. The word and its slot are this page’s teaching example. A mechanism illustration drawn from the migration logic, not a captured trace.

A guess, retried The same player sending the same word for the same slot gets the committed row back

  1. Pagesubmit CRATE as slot 2The guess names the slot it expects to fill.
  2. Postgreslock the game rowUnder the lock, the game row’s guess count says whether slot 2 is already filled. A unique index on game, guesser and guess number stops a second write to the same slot.
  3. Postgreswrite the guess onceThe row is committed, the feedback is calculated and the participants are notified, each exactly once.Committed
  4. Networkthe reply never arrivesThe client cannot tell whether the move counted.Reply lost
  5. Pagesubmit CRATE as slot 2 againThe same word, with the same expected slot.
  6. Postgresslot 2 is already committedThe saved word matches, so the function returns that row without a second write, feedback calculation or broadcast.Replayed

A room, retried The owner gets the same room back; another player with the key gets a conflict

  1. Pagecreate room with key KA 64-hex operation key the client makes before its first attempt.
  2. Postgresadvisory lock K, then the playerTwo transaction-scoped locks, taken in that order, so concurrent attempts with the same key queue behind one another.
  3. Postgrescreate the room, write a receiptThe receipt binds key K to this room and this player. The key is globally unique.Committed
  4. Networkthe reply never arrivesThe client holds a key and no room.Reply lost
  5. Pagecreate room with key K againThe same key, from the same player.
  6. Postgresreceipt found for K, same playerThe same room comes back, marked as replayed. There is still only one room.Replayed
  7. Pageanother player sends key KThe receipt names a different player.
  8. PostgresOPERATION_KEY_CONFLICTRefused. This player cannot use the key to reach the other player’s room or to create a second one.Refused

Limits. This description comes from source inspection; the live database catalog was not queried and the product’s tests were not rerun for this page. The TypeScript game engines and the PL/pgSQL functions implement the same rules in parallel. The unit tests exercise the TypeScript side. Keeping the SQL in step is a manual discipline, and no automated check compares the two. Guests receive no live Broadcast. No latency, scale, concurrency or player number has been measured. A native client shares the server boundary in code, with Android in closed testing rather than a public store release. Erasure deletes the player’s Wordship data. Wordship now has its own sign-in. Deleting an account removes that sign-in as well as its Wordship play; guests can erase their play from the device they used. The source is private and is not linked from this page.

Changelog

What shipped, newest first.

12 releases since 27 Apr 2026, the latest on 26 Sep 2026. From Wordship’s changelog.

  1. Wordship has its own home, and deleting your account deletes your sign-in.

    • Improved Wordship now runs on its own servers. Your games, record and friends came along; sign in once more on each device.
    • Improved Deleting your account removes your sign-in too, in the app or on the web, even if you never saved a game.
  2. Deletion says exactly what it removes.

    • Improved The deletion page separates your Wordship play from the sign-in shared with other Mainthread apps, and says which one each action removes.
    • New If you created a sign-in here, you can request its removal as well, even if you never saved a game.
  3. Steadier turns, and your data in your hands.

    • Improved The Deduce board no longer jumps after you press Enter, and your letters stay put until their colors turn.
    • Improved A finished solo game has one row of navigation, with Next word just below Share.
    • New Delete your Wordship play from your profile, or from the deletion page on the web.
    • New Guests can delete their play too, from the device they played on.
    • New A privacy page explains what Wordship keeps and why.
  4. Deduce plays like the word game you know, and keeps your record.

    • New Press Play Deduce for a fresh word, then Next word when you finish. An unfinished word waits for you.
    • New Results show your record across Daily and fresh words: games played, win rate, best and average guesses, and how many guesses each win took. Your profile shows the same record.
    • New Guests can look back at their earlier Daily boards.
    • Improved The board is larger on phones, with a filled keyboard like the one you know. Tiles flip one after another, and the keyboard colors after the last one.
    • Improved Share is one button. Preview & options holds the preview and the choice of text or image.
    • Improved Answers come from familiar words, skip words you have already seen in Deduce, and leave out a few offensive ones.
    • Improved Screen readers announce each guess's clues once the tiles settle.
    • Fixed How to play closes when a round ends, so it no longer covers your result.
Earlier releases (8)
  1. Both games open with their own art, and Salvo is easier to follow on a phone.

    • New Deduce and Salvo each open with artwork that shows how every mode plays.
    • New Guests can play Deduce and Salvo practice games without an account.
    • Improved Practice starts on the first press, and opening a shared replay link no longer starts a game until you press Play.
    • Improved Salvo's timed word challenge, its keyboard and the latest clue fit on smaller phones.
    • Improved Coming back to a friend Salvo match shows the guesses behind each ship's score.
  2. Daily Salvo and friend matches, open to everyone.

    • New Play Daily Salvo, the fleet-hunting word game, without an account.
    • New Start or join a Salvo friend match with a short code, as a guest or signed in.
    • Improved Home puts Daily Deduce and friend play first. Start makes a room in one tap, and Join opens a code field right there.
    • Improved Deduce keeps the board and keyboard in view on smaller phones, with a compact line for your opponent's progress.
    • Improved Salvo asks for your word before showing the board, and while you wait you see your own fleet with misses marked.
    • Improved The defending friend watches the challenge live: their word, each guess and the countdown. Finished matches offer a new one.
    • Fixed A guess sent on a weak connection is checked against the game, never lost or counted twice.
  3. Play first. Sign in later, if you want to.

    • New Play Daily Deduce without an account. Your progress stays with this device.
    • New Start a Deduce friend room and share a short code. Your friend joins without an account.
    • New Keep what you played by saving it to a new account, right from the game you are in.
    • New Choose your look: Chart Room, Deep Signal or Lantern, each in light or dark.
    • Improved Daily Deduce opens straight onto the board, and a finished match shows the result first, then what to do next.
    • Improved After you finish a duel, watch your friend's hunt live.
    • Fixed Only the players in a game can see its answers and results.
    • Fixed Multi-round matches finish when their last round does, and live updates between friends work again.
  4. A new look: Chart Room, and clearer Salvo hits.

    • New Wordship takes on Chart Room, a navigator's chart-paper look, in light and dark.
    • Improved A Salvo hit tells you which ship it struck.
  5. Salvo, tuned from the first games between friends.

    • New Hits, sinks and a finished fleet each have their own motion, which stays still when your device asks for reduced motion.
    • New Share a Salvo result as text or an image, with a link that invites friends to play too.
    • Improved Daily and Practice Salvo play the new hunt, then deduce, and a found ship shows its letters on the board.
    • Improved Daily puzzles use familiar, well-known words.
    • Improved Tapping a match invite joins you to it, and the defender sees their own word while their friend guesses.
    • Fixed Ships place with one tap, practice words are always available, and the challenge timer no longer runs out right away.
  6. 0.3.0

    Salvo rebuilt: hunt the fleet, then deduce each word.

    • New Friend matches play Salvo in three phases: hunt ships by coordinate, solve each located ship's word in a timed six-guess challenge, and score by how close you came.
    • Improved Every fleet word has five letters, the same as Deduce.
    • Improved Phones and desktops each get a layout made for them.
    • New Share a finished multi-round Deduce match as text or an image.
    • Fixed Watching a friend's Deduce round shows every letter as it is typed.
  7. 0.2.0

    Streaks, stats and head-to-head records.

    • New Daily Deduce tracks your current and longest streak, and your profile shows your full record.
    • New See your head-to-head record with any friend.
    • New The board-and-word game has its own name and home: Salvo.
    • Improved Daily puzzles are published a week ahead, so today's is always ready.
    • Fixed Pages no longer scroll sideways on phones.
  8. 0.1.0

    Wordship opens with Daily Deduce, practice and friend matches.

    • New Daily Deduce: one word a day, six guesses, the same word for everyone.
    • New Practice with random words as often as you like.
    • New Challenge a friend to one, three or five rounds, and watch their board live when you finish first.
    • New Share a result with a link that lets a friend play the same word without seeing it.