eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– Signup-first join page β€” design
2
3**Date:** 2026-07-31 Β· **Status:** approved by Paul Β· **Scope:** web join page
4(native follows as a stated follow-up)
5
6## What changes
7
8Today a joiner opening `/join/<name>#<secret>` sees the app/web choice first,
9claims an invite with no account, and is asked to sign in only afterwards β€”
10by Eleven's welcome conversation, then the fragility banner. This flips the
11order: the **first screen is signup/sign-in**, and by the time the joiner
12reaches the continue-on-web / continue-in-the-app choice they already have an
13Eleven account and passkey. This is the golden path
14`docs/onboarding-and-home-server.md` Β§1 designed but never built.
15
16The account gate is **offered first, skippable** β€” a quiet "continue without
17an account" escape preserves the zero-friction join; skippers fall through to
18today's exact flow, including Eleven's ask and the fragility banner.
19
20## 1. The lead card
21
22New first screen for fresh joiners β€” all fresh-person shapes: generic
23invites, DM/user invites, and setup (new-space) invites β€” rendered by
24`join.js` from a new template in `join.html`:
25
26- Branded like today's welcome card (space avatar + name via the existing
27 sealed-branding machinery; plaintext branding fallback).
28- Copy: "You've been invited to *(space)*" plus one explainer line β€” your
29 sign-in makes your access portable to any device.
30- Primary: **Continue with your Eleven sign-in** β†’ same-tab round-trip to the
31 home server (Β§3).
32- Quiet link: **Continue without an account** β†’ reveals today's welcome card
33 unchanged (Open in the app / Get the app / Continue in the browser, with
34 the browser-only caveat).
35
36"All the sign-in options" are satisfied by landing on home's existing doors
37surface (smart single door β€” last-used else platform default β€” plus "Other
38ways to sign in": passkey, Apple, Google, Microsoft, GitHub, email code). The
39lead card links to that surface; it does not rebuild it.
40
41## 2. Who never sees the lead card
42
43These paths fall through to today's behaviour untouched:
44
45- Add-device links (`payload.priv`) β€” the person already has an identity.
46- Already-a-member adopt paths (all shapes).
47- The native embed form path (`window.__lchatEmbed`) and the Mac webview.
48- Dead/consumed invites β€” the existing error card (with its sign-in door).
49- Instances with no home server configured (`__HOME_ORIGIN__` empty β€”
50 self-hosters).
51- πŸ€– Setup links opened from home's create-a-space flow (`?src=home`): that
52 person signed in at home one tap ago, and home has already saved the new
53 space to their account. The sign-in they still owe β€” adding this space's
54 KEY to that account β€” is the setup conversation's `keys-home` step, at the
55 end.
56
57## 3. The home-side pre-join intent
58
59Home's sign-in page (`home/`) grows a lightweight pre-join mode:
60
61- Query params: `?return=<url>` plus the instance host for display (host and
62 group name are plaintext branding by design β€” CLAUDE.md Β§ branding).
63- Any door works. After the account exists **and a passkey is saved**, home
64 redirects to the return URL. An account that already exists in this browser
65 session completes immediately (the shipped session-reuse behaviour,
66 `d2c4092`).
67- **Return-URL guard:** must be `https` and path `/join/…`. Instance hosts
68 are arbitrary custom domains, so no host allowlist is possible; home
69 displays the destination host so the person sees where they're being sent.
70 This is post-auth navigation, not a credential redirect, so the residual
71 open-redirect risk is display-mitigated, not allowlist-mitigated.
72
73## 4. The invite secret never travels
74
75- Before navigating to home, `join.js` stashes the full original `#fragment`
76 in **instance-origin localStorage**, keyed by invite id, with an expiry,
77 cleared on restore. localStorage (not sessionStorage) so a mobile OAuth
78 app-switch that returns in a fresh tab can't orphan the secret. Lingering
79 risk is bounded: entries expire, and the invite is one-time anyway.
80- The `return` URL carries **no fragment**. On return, `join.js` restores the
81 fragment from the stash and cleans the URL (`history.replaceState`).
82- The home origin never sees the invite secret, preserving the invariant
83 that fragments never reach any server and the secret stays on the instance
84 origin.
85
86## 5. Return: the caveat-free choice card
87
88Back on the join page with a signed-in marker (query flag from home, cleaned
89after read):
90
91- The joiner sees the **same web/app choice**, but the "this browser only"
92 caveat is replaced with a positive line: "Your access is saved to your
93 Eleven account β€” you can sign in from any device."
94- **Continue in the browser** claims exactly as today β€” keypair, deferred
95 claim, bind-MAC (the claim-on-button-press invariant is untouched) β€” then,
96 before `location.href = "/"`, `join.js` auto-runs the existing `/enroll`
97 round-trip (`launchEnrol` machinery). Against the live home session this
98 completes instantly with no second ceremony, so the space is genuinely
99 saved to the account before the joiner lands in the chat.
100- **Open in the app** keeps today's `elevenmessenger://add` scheme handoff.
101 The vault-first handoff ("the space is already in your account β€” just sign
102 in in the app") is stated **phase 2**, because it changes invite-consumption
103 timing (claim-at-signup) and touches native sign-in.
104
105## 6. Eleven stands down
106
107- The welcome flow's `access` step (`web/static/welcome.flow.js`) gains an
108 "already saved" skip condition alongside its existing `native` /
109 `canEnrol` skips; likewise the `finish` step's "you haven't signed in yet"
110 warning and chip.
111- The fragility banner never arms for a saved session (existing behaviour β€”
112 verify, don't rebuild).
113
114## 7. Testing
115
116- Pull the which-card-for-which-state decision (link shape Γ— home-origin
117 presence Γ— signed-in marker Γ— embed) out of DOM code into a pure helper
118 (`ui.js` or a new module) with Node tests, per the testable-module
119 convention.
120- Go test on home for the `?return` guard (https-only, `/join/` path only).
121- `node --check` on touched JS; `npm test` + `go test ./...` green.
122- Manual verification per the developer's recorded preference
123 (`CLAUDE.local.md`; ask if unrecorded).
124- Changelog entry (user-visible web change).
125
126## Out of scope (stated)
127
128- Native (iOS/Mac) direct-join restructure β€” follow-up, building on
129 `SignInWithEleven`.
130- Claim-at-signup + vault-first app handoff β€” phase 2.
131- Any change to invite consumption timing or the deferred-claim invariant.
132- The dead `homescreen-tpl` markup in `join.html` may be deleted in passing
133 if touched, but its removal is not a goal of this change.
134