fuzzy find my records ken.waow.tech
embeddings pds search
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add design notes + TODO for multi-account, settings, profile record

notes/multi-account-and-settings.md has the full design writeup
with reference implementations (pdsls scope mgmt, plyr.fm group_id
session linking). TODO.md at repo root is the statement of work for
the next engineer picking this up.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+144
+33
TODO.md
··· 1 + # ken — next up 2 + 3 + ## multi-account + settings + profile record 4 + 5 + design notes: [notes/multi-account-and-settings.md](notes/multi-account-and-settings.md) 6 + 7 + ### what 8 + 9 + - write a `tech.waow.ken.profile/self` record on first sign-in so indexers can discover ken users 10 + - consolidate all PDS record management (profile, pack, scopes) into a settings panel 11 + - multi-account support: link accounts, switch without re-auth, per-account indexes 12 + - scope management: user-selectable at login, progressive escalation (pdsls pattern) 13 + 14 + ### reference 15 + 16 + - **pdsls** (tangled.org/pds.ls/pdsls): scope selector UI, PermissionButton pattern, account manager modal 17 + - **plyr.fm** (sibling repo): group_id session linking, server-side account switching, logout modal 18 + 19 + ### order of work 20 + 21 + 1. lexicons: `tech.waow.ken.profile`, `tech.waow.ken.optout` 22 + 2. backend: profile read/write on sign-in, settings endpoints, scope storage 23 + 3. frontend: settings panel, move pack actions out of meta line 24 + 4. backend: group_id on sessions, add-account/switch-account endpoints 25 + 5. frontend: account menu in nav, multi-account switching 26 + 27 + ### context the next engineer needs 28 + 29 + - ken is a zig HTTP server (0.16-dev) with a vanilla JS SPA (single main.js, no framework) 30 + - all state is in-memory (state.zig) — no database, PDS is the only persistence 31 + - session cookies are now random tokens mapped to DIDs (shipped this session, `921fefe`) 32 + - OAuth uses zat for atproto primitives, DPoP-bound tokens, PKCE 33 + - the session token fix also needs to be propagated to pollz (same vulnerability)
+111
notes/multi-account-and-settings.md
··· 1 + # multi-account support + settings panel 2 + 3 + status: design notes. not started. 4 + 5 + ## what we want 6 + 7 + ken currently has single-account OAuth with a fixed scope, inline pack 8 + management in the meta line, and no profile record. this should become: 9 + 10 + 1. **profile record** — `tech.waow.ken.profile/self`, written on first 11 + sign-in (idempotent). lets indexers discover who has used ken. users 12 + can delete it from settings; ken respects the delete by checking for 13 + an opt-out record (`tech.waow.ken.optout/self`) before rewriting. 14 + 15 + 2. **settings panel** — replaces the inline disclosure in the meta line. 16 + one place to manage all ken-related records on the user's PDS: 17 + - profile record: exists / doesn't, toggle create/delete 18 + - search index (pack): saved / not saved, save / delete / view on PDS 19 + - oauth scopes: what was granted, option to re-auth with different scopes 20 + 21 + 3. **multi-account** — link multiple atproto accounts, switch between 22 + them without re-authing. each account gets its own index/pack. 23 + 24 + 4. **scope management** — user-selectable scopes at login (like pdsls), 25 + progressive permission escalation (PermissionButton pattern wraps 26 + actions and prompts for re-auth if the scope wasn't granted). 27 + 28 + ## reference implementations 29 + 30 + ### pdsls (tangled.org/pds.ls/pdsls) 31 + 32 + - scopes: user-selectable checkboxes at login. 4 granular scopes 33 + (create, update, delete, blob) on top of base `atproto` scope. 34 + `PermissionButton` component wraps every write action — if scope 35 + wasn't granted, shows a modal offering to re-auth with the needed 36 + scope. progressive escalation, no silent failures. 37 + - multi-account: sessions keyed by DID in localStorage (client-side). 38 + account manager modal with avatar + handle, click to switch via 39 + `resumeSession(did)`. expired sessions prompt re-auth automatically. 40 + - settings: minimal — PLC directory URL, theme, version. 41 + - stack: vite + typescript SPA, cloudflare pages. 42 + 43 + ### plyr.fm (/Users/nate/tangled.sh/@zzstoatzz.io/plyr.fm/) 44 + 45 + - multi-account: `group_id` column on `UserSession` table links sessions 46 + across accounts server-side. active account = which session_id the 47 + cookie points to. switching = `POST /auth/switch-account` → server 48 + validates both sessions share a group_id → swaps the cookie. 49 + - add account: dedicated `/auth/add-account/start` endpoint. initiates 50 + OAuth with `prompt=login`, links new session to existing group_id. 51 + `PendingAddAccount` record ties the OAuth state to the group. 52 + - logout: modal when multi-account — "switch to @handle" or "sign out 53 + of all." single-account just logs out. 54 + - scope upgrade: `PendingScopeUpgrade` flow — re-auth with new scopes, 55 + update existing session rather than creating a new one. 56 + - session storage: postgres + redis (60s cache TTL) + fernet encryption 57 + on OAuth tokens at rest. random session_id (secrets.token_urlsafe(32)) 58 + as cookie, never the DID. 59 + - stack: fastapi + sveltekit, postgres, redis. 60 + 61 + ## proposed design for ken 62 + 63 + **backend (zig, in-memory):** 64 + - add `group_id: ?[]u8` to `StoredSession` in state.zig 65 + - `createSessionToken` already generates random tokens (just shipped) 66 + - new endpoints: 67 + - `POST /oauth/add-account` — initiates OAuth, links to existing group 68 + - `POST /oauth/switch-account` — validates group membership, swaps cookie 69 + - `GET /api/me` returns `linked_accounts: [{did, handle}]` for the group 70 + - profile record: check for `tech.waow.ken.profile/self` on sign-in, 71 + write if absent and no `tech.waow.ken.optout/self` exists 72 + - scope: store granted scopes in session, expose via `/api/me` 73 + 74 + **frontend (vanilla JS, single file):** 75 + - account menu in nav (avatar + handle button → dropdown): 76 + - list linked accounts with handles 77 + - click to switch (POST to switch-account) 78 + - "add account" button 79 + - per-account: remove, edit permissions, view repo on pdsls 80 + - settings panel (gear icon or link from account menu): 81 + - profile record: status + toggle 82 + - pack: status + save/delete/view 83 + - scopes: what was granted + re-auth button 84 + - PermissionButton pattern: wrap save/delete with scope check, 85 + show prompt if scope wasn't granted 86 + 87 + **lexicons needed:** 88 + - `tech.waow.ken.profile` — marker record, rkey `self` 89 + - `tech.waow.ken.optout` — opt-out marker, rkey `self` 90 + - `tech.waow.ken.pack` — already exists 91 + 92 + **migration from current state:** 93 + - the inline "saved ▾" disclosure in the meta line moves to settings 94 + - meta line becomes pure stats again 95 + - existing single-account sessions continue to work (group_id = null 96 + means "no linked accounts") 97 + 98 + ## open questions 99 + 100 + - should the profile record carry any data beyond a marker? handle, 101 + sign-in timestamp, ken version? or keep it empty? 102 + - should we write the profile on first sign-in automatically, or 103 + require the user to opt in via settings? auto-write is more useful 104 + for indexers but less respectful of user agency. 105 + - ken is a vanilla JS SPA — the PermissionButton pattern assumes 106 + components. may need a simpler wrapper (function that checks scope 107 + before calling the action, shows a confirm dialog if scope is missing). 108 + - in-memory sessions mean group_id doesn't survive restarts. is that 109 + acceptable? users re-auth on restart anyway, but they'd lose their 110 + linked-account associations. could persist group mappings to... the 111 + PDS? a separate record? or just accept the UX cost.