diff --git a/README.md b/README.md
index f700f85..7435ffc 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
A collaborative, real-time trip map and journal. Anyone can explore a trip with
no account — filter days, browse route segments, and open notes and photos
-pinned to the map. Signed-in friends auto-join as members to drop their own
-notes and geotagged photos, and edits stream to everyone live.
+pinned to the map. Invited friends can sign in to drop their own notes and
+geotagged photos, and edits stream to everyone live.
▶ Live demo
@@ -39,7 +39,7 @@ The interesting problems this project solves:
- **Real authorization, not a toy.** Postgres row-level security enforces
*public reads, member contributions, and owner-or-admin writes* at the
database — the client can't bypass it. Members self-serve admin requests that
- existing admins approve in-app.
+ existing admins approve in-app. Signing in alone never grants edit access.
- **Live collaboration.** Supabase Realtime streams inserts/updates for photos,
notes, places, and routes, so a second browser sees changes appear instantly.
- **Zero-config demo mode.** With no backend keys set, the app boots from
@@ -177,9 +177,10 @@ Keep the Supabase variables empty to stay in demo mode. To enable shared mode:
5. Edit `supabase/grant-member.sql` with your email and run it to make your
first account an admin.
6. Reload — you should see the seeded trip and admin controls.
-7. Friends can view the trip without signing in. When they sign in, the app
- auto-joins them as members so they can contribute notes/photos and request
- admin access in-app.
+7. Friends can view the trip without signing in. To contribute, a friend first
+ signs in once so Supabase creates their Auth account; an admin then adds that
+ email from the in-app Members panel. Their next reload enables notes/photos
+ and admin-access requests.
### What the schema sets up
@@ -190,6 +191,9 @@ collaborative tables, and RLS policies:
- **Reads are public** — `select` is granted to `anon` with `using (true)`, so
anyone can view the trip without an account.
+- **Contribution is invite-only** — signing in does not create a membership.
+ An existing admin must add the account email from the Members panel after the
+ person has signed in once (or use `grant-member.sql` for initial setup).
- **Notes and photos** can be created by any signed-in member; each row is
updatable and deletable by its owner or a trip admin.
- **Trips, days, routes, places, and membership** are admin-scoped.
@@ -274,7 +278,9 @@ automatically, and CI validates a production build on every PR.
- Signed-out visitors see the seeded trip (reads are public); the "Sign in"
button opens the optional sign-in panel.
- Your admin account shows contribute/admin controls; a guest does not.
- - A newly signed-in friend can contribute notes/photos and request admin.
+ - A newly signed-in, uninvited account remains view-only.
+ - After an admin adds that account's email in Members and it reloads, the
+ friend can contribute notes/photos and request admin access.
- A note saves and survives reload.
- A small photo uploads, renders on the map, and survives reload.
- An admin can approve/deny admin requests and adjust member roles.
diff --git a/app/page.tsx b/app/page.tsx
index a99fb35..835012d 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -766,7 +766,7 @@ export default function Home() {
{tripTitle}
-
{supabase ? (user ? `Signed in ${user.email ?? ""}` : "Viewing as guest") : "Local demo mode"}
+
{supabase ? (user ? (currentMember ? `Signed in ${user.email ?? ""}` : "Signed in · view only") : "Viewing as guest") : "Local demo mode"}