Stork
Shareable baby-name boards: suggest names, vote, keep origin / meaning / pronunciation in English, Russian, and Hebrew, and optionally record how a name sounds.
Each board has a unique share link (/b/<uid>). Start a board with no password,
add names, share the link. Anyone with the link can edit.
Live read-only demo: stork.levkin.ca demo board.
Quick start (local)
cp .env.example .env # optional STORK_INVITE_TOKEN bootstraps a "Family" board
make install
make test
make run
Open http://127.0.0.1:8094 → Start new board → share /b/….
Visitors open the link and enter a display name.
Features
- Renamable columns (First is fixed;
+adds up to 4; ✕ removes extras) - Drag a name onto another column to move it (or Move to… in the drawer)
- Votes / ranking per board
- Locale notes (en / ru / he) + personal significance (why the name matters) + nickname ideas + voice recordings (re-record overwrites)
- Suggest form stays simple (baby name + nicknames + why); origin/meaning autofill when known — names aren’t translated, other scripts are written forms
- Name generator — draw or remix from a local SQLite world first-name dump (JSON fallback) with filters (region, length, contains letter, …)
- Site ideas — button emails suggestions to
idobkin@gmail.comvia Mailcowalerts@levkine.caSMTP - Paper UI at
/ - Optional read-only boards via
STORK_READONLY_BOARD_IDS
API sketch
| Method | Path | Notes |
|---|---|---|
| GET | /api/health |
Liveness |
| POST | /api/boards |
Start board {title} → {id, url, readonly} (rate-limited) |
| GET | /api/boards/{id} |
Public board title + url + readonly |
| GET | /api/resolve?invite= |
Legacy invite → board url |
| POST | /api/session |
{board_id, display_name} → cookies (rate-limited) |
| DELETE | /api/session |
Clear cookies |
| GET | /api/columns |
Columns for session board |
| POST/PATCH/DELETE | /api/columns… |
Add / rename / remove (not first; blocked if readonly) |
| GET/POST | /api/names |
List / add |
| PATCH | /api/names/{id} |
Locale notes, significance, and/or nicknames |
| POST | /api/ideas |
Email a site idea (SMTP or mailto fallback) |
| GET | /api/nicknames/suggest?spelling= |
Curated nickname ideas for a spelling |
| GET | /api/names/lookup?spelling= |
Origin/meaning/script-form autofill hints |
| POST | /api/names/{id}/move |
Move to another column {kind} |
| POST | /api/names/{id}/vote |
{value: 1|-1|0} |
| POST/GET/DELETE | /api/names/{id}/recording/{lang} |
Voice (magic sniff; max ~5 MiB) |
| GET | /api/generator/meta |
Corpus regions / modes |
| POST | /api/generator/spin |
Draw or remix with filters |
| DELETE | /api/names/{id} |
Admin header X-Stork-Admin |
Empty unused boards (no names) older than STORK_ORPHAN_BOARD_HOURS (default 48)
are removed when someone creates a new board. Readonly + invite Family boards are
kept.
Name corpus attribution: data/ATTRIBUTION.md
(shipped as stork/data/world-names.sqlite3 when present, else world-names.json).
Rebuild: make corpus-db.
Scripts
# Seed starter names onto a board (uses session cookies)
make seed STORK_BOARD_ID=b_…
# Demo set (Mira / Noa / Lior / Elena / Ezra):
STORK_SEED_FILE=data/demo-names.example.json make seed STORK_BOARD_ID=b_…
# Create an empty board and print share URL
.venv/bin/python scripts/create_board.py
Secrets
- Local: gitignored
.env - Deploy host:
.envnext to compose (never commit)
CI gates
Gitea Actions (.gitea/workflows/ci.yml): ruff and pytest are hard gates
(no || true). Gitleaks on PRs. Bandit is advisory.
Production
Public HTTPS at stork.levkin.ca. Homelab deploy lives in a
separate ansible repo (make deploy-stork).