No updates detected yet. GitHub releases will appear here.
A map of public restrooms that says whether one will work for you — step-free, grab bars, adult changing, and who has corroborated it
翻訳待ち
Loading the full README. The saved excerpt is shown below.
A crowdsourced map of public restrooms, built around the question the other
maps do not answer: will it work for me?
Google Maps knows a restroom exists. It does not know whether you can get in
without steps, whether the accessible stall is kept locked and the key is
behind a counter, whether there is an adult changing bench, whether the sink is
inside the cubicle, whether there is anywhere to put anything down. For a lot
of people those are not details — they decide whether the journey is worth
making at all.Read the full README ↗→ minormending.github.io/restroom-map
A crowdsourced map of public restrooms, built around the question the other maps do not answer: will it work for me?
Google Maps knows a restroom exists. It does not know whether you can get in without steps, whether the accessible stall is kept locked and the key is behind a counter, whether there is an adult changing bench, whether the sink is inside the cubicle, whether there is anywhere to put anything down. For a lot of people those are not details — they decide whether the journey is worth making at all.
So the map is useful to anybody looking for a toilet, and it is built for the people for whom the wrong answer costs a wasted trip they may not be able to repeat.
Nothing else records most of this, which is the reason it is worth contributing to and the reason most fields start empty. An accessibility answer needs two people who agree before it is shown as fact; until then the map says how thin the evidence is, and says so when people disagree. Guessing is worse than not knowing here.
Static frontend on GitHub Pages, Postgres + PostGIS behind it. The browser is treated as hostile throughout: it can read public data and propose writes, but it never decides whether a write is legitimate.
pnpm install
pnpm dev
Opens on the bundled Lower Manhattan sample data — no Supabase project needed.
Set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY in .env and the same code
switches to live reads.
Testing something location-dependent? In dev only,
?at=<lat>,<lng>overrides the browser's position — e.g.http://localhost:5173/restroom-map/?at=40.7115,-74.0111. The branch is behindimport.meta.env.DEV, a compile-time constant, so it is eliminated from production builds entirely.
If the dev server serves stale code after an edit, its watcher has missed the change — restart it. This bites often enough to be worth knowing before you spend an hour debugging a fix that was never actually served.
Seeing stale content after a rebuild or deploy? The PWA service worker precaches the bundle, so a fresh build can keep serving the old one. The SW now ships
skipWaiting+clientsClaim, so updates apply on the next reload — but if you're ever unsure what you're looking at, run this in the browser console and reload:for (const r of await navigator.serviceWorker.getRegistrations()) await r.unregister() for (const k of await caches.keys()) await caches.delete(k)To confirm which bundle is live, compare the hash in the page source against
dist/index.html.
SQL in supabase/migrations/ runs in order — paste into the Supabase SQL editor
or supabase db push if you have the CLI.
| File | What it does |
|---|---|
20260911000001_init.sql | Tables, enums, indexes |
20260911000002_rls.sql | Row-level security and its guard helpers |
20260911000003_rpc.sql | Confidence view, viewport query, code gate |
20260911000004_grants.sql | Data API grants (create the project with auto-expose OFF) |
seed.sql | 30 Lower Manhattan starter rows (generated) |
scripts/db.mjs connects directly to Postgres, so migrations and one-off
queries don't have to go through the dashboard SQL editor.
pnpm db:status # applied vs pending migrations
pnpm db:migrate # apply pending ones, each in a transaction
pnpm db:query "select count(*) from bathrooms"
node scripts/db.mjs file supabase/seed.sql
It needs one line in .env:
SUPABASE_DB_PASSWORD=your-database-password
Host, port and user are derived from VITE_SUPABASE_URL, and the password is
passed to the driver as a field rather than interpolated into a URL, so
characters like @ and : need no escaping. Set SUPABASE_DB_URL instead if
you prefer a full connection string, or SUPABASE_DB_HOST if the derived
pooler host is wrong.
Unlike the publishable key, this password is a genuine secret: full database
access, bypassing RLS. .env is gitignored so it stays on your machine.
Note the direct-connection host (db.<ref>.supabase.co) is IPv6-only, which is
why this uses the pooler.
Migrations are tracked in a schema_migrations table by filename and checksum.
If the schema was built by hand before this tool existed, record the existing
files as already applied instead of re-running them:
node scripts/db.mjs migrate --baseline
Regenerate the seed after editing src/data/seed.json:
node scripts/seed-to-sql.mjs
Two design points worth not undoing:
can_view_code() is the single gate. As of migration 011 a code costs
unlock_cost() credits, with three carve-outs: a place you submitted, a code
you submitted, and anything you have already unlocked are always free to you.
To turn gating off again, that is still one function:
create or replace function can_view_code(p_user uuid, p_bathroom uuid)
returns boolean language sql stable as $$ select true; $$;
Nothing else changes — no data migration, no client rewrite. Prices live in
unlock_cost().
pnpm test # every suite
pnpm test credits # suites matching a name
node scripts/test.mjs --verbose # list passing tests too
Eight suites, 76 tests — harness, credits, codes, access claims,
hours, feedback, flags and write grants — over the places where a
silent bug costs somebody money, hands out a code they did not pay for, turns
one person's opinion into a fact, or lets a write in through a door that was
supposed to be shut.
This is the beginning of the README. Read the rest at the original. Read the full README ↗