···11-# copy improvements
22-33-## the problem
44-55-the original copy throughout @me was too technical and jargon-heavy for people unfamiliar with atproto. terms like "silos," "atproto identity," "repository," and "Personal Data Server" appeared without context or explanation. this created barriers for the primary audience: regular social media users who might be curious about decentralized social but don't yet understand the tech.
66-77-more importantly, the copy focused on **how the technology works** rather than **why it matters** to users. people don't care about protocols - they care about not losing their followers when platforms change.
88-99-## the philosophy
1010-1111-drawing from [overreacted.io/open-social](https://overreacted.io/open-social/), we adopted these principles:
1212-1313-1. **lead with relatable problems** - "built 10k followers? if you leave, you lose them all"
1414-2. **use familiar analogies** - "what if social media worked like email?"
1515-3. **focus on benefits, not technology** - "switch apps anytime, take everything with you"
1616-4. **provide breadcrumbs** - link every technical term to official docs so curious users can learn more
1717-1818-the key insight: if you can't leave without losing something important, the platform has no incentive to respect you. that's the message that resonates with regular users, not "merkle search trees" or "decentralized identity."
1919-2020-## what we changed
2121-2222-### logged-out experience (login page "what is this?")
2323-2424-**before:**
2525-- "visualize your atproto identity"
2626-- "the problem with silos"
2727-- "the atproto solution"
2828-- heavy use of jargon, abstract concepts
2929-3030-**after:**
3131-- "your posts should be yours" - opens with the actual problem people face
3232-- "what if social media worked like email?" - uses an analogy everyone understands
3333-- "see it in action" - simple call to action
3434-- every technical term links to [atproto.com](https://atproto.com) documentation
3535-3636-**why:** logged-out users know nothing about atproto. this is our chance to make them care before introducing any technical concepts.
3737-3838-### logged-in experience (? button modal)
3939-4040-**before:**
4141-- "@me - your repository"
4242-- focused on platform switching
4343-- generic language about ownership
4444-4545-**after:**
4646-- "this is your data" - personal and direct
4747-- explains what they're looking at: "you're looking at your Personal Data Server - where your social data actually lives"
4848-- concrete examples: "bluesky for microblogging. whitewind for long-form posts"
4949-- defines "open social" in plain terms: "if you don't like an app, switch"
5050-- ends with clear instructions on how to use the tool
5151-5252-**why:** once someone is logged in, they're ready for slightly deeper concepts. but we still prioritize clarity over accuracy, using the visualization to teach what a PDS does.
5353-5454-### identity/PDS panel (clicking @ in center)
5555-5656-**before:**
5757-- title: "your repository"
5858-- subtitle: "what you've built"
5959-- comparison boxes about traditional vs atproto platforms
6060-- technical details at bottom
6161-6262-**after:**
6363-- title: "your personal data server"
6464-- subtitle: "where your social data lives"
6565-- **"your pds location"** box - explicitly states where the PDS is hosted and what's stored there
6666-- **"explore your data"** box - links to `pdsls.dev/{pds-domain}` as a next step
6767-- removed redundant platform comparison (already covered in modals)
6868-- kept technical details (DID, handle) at bottom
6969-7070-**why:** this panel should immediately answer "what is this thing in the center?" and "where is my data actually stored?" the pdsls.dev link gives power users an immediate action item.
7171-7272-## the pattern
7373-7474-every piece of copy now follows this structure:
7575-7676-1. **hook** - relatable problem or question
7777-2. **explain** - use familiar analogies
7878-3. **breadcrumb** - link technical terms to docs
7979-4. **action** - give them something to do or explore
8080-8181-examples:
8282-- login page: problem → email analogy → linked "Personal Data Server" → "explore demo"
8383-- info modal: "this is your data" → concrete examples → linked "open social" → "how to explore"
8484-- pds panel: "where your social data lives" → linked PDS location → pdsls.dev tool → technical details
8585-8686-## success metrics
8787-8888-we'll know this worked if:
8989-9090-1. **bounce rate decreases** on login page
9191-2. **demo mode usage increases** (people want to see it work)
9292-3. **pdsls.dev referrals** show users are exploring further
9393-4. **fewer confused questions** from new users
9494-9595-more importantly: can you explain this to your non-technical friend and have them understand why they should care? that's the test.
9696-9797-## files modified
9898-9999-- `src/templates.rs` - login page info section, logged-in info modal
100100-- `static/app.js` - identity/PDS panel on @ click
101101-102102-## resources
103103-104104-- [overreacted.io/open-social](https://overreacted.io/open-social/) - the philosophical foundation
105105-- [atproto.com/guides/data-repos](https://atproto.com/guides/data-repos) - what is a PDS
106106-- [atproto.com](https://atproto.com) - protocol overview
107107-- [pdsls.dev](https://pdsls.dev) - tool for exploring PDS contents
-92
docs/firehose.md
···11-# real-time updates via firehose
22-33-at-me visualizes your atproto activity in real-time using the jetstream firehose.
44-55-## what is the firehose?
66-77-the [atproto firehose](https://docs.bsky.app/docs/advanced-guides/firehose) is a WebSocket stream of all repository events across the network. when you create, update, or delete records in your PDS, these events flow through the firehose.
88-99-we use [jetstream](https://github.com/ericvolp12/jetstream), a more efficient firehose consumer that filters and transforms events.
1010-1111-## architecture
1212-1313-### backend: rust + server-sent events
1414-1515-**firehose manager** (`src/firehose.rs`)
1616-- maintains WebSocket connections to jetstream
1717-- one broadcaster per DID being watched
1818-- smart reconnection with exponential backoff
1919-- thread-safe using `tokio` and `Arc<Mutex>`
2020-2121-**dynamic collection registration**
2222-- when you click "watch live", we fetch your repo's collections via `com.atproto.repo.describeRepo`
2323-- registers event ingesters for ALL collections (not just bluesky)
2424-- this means whitewind, tangled, guestbook, and any future app automatically work
2525-2626-**event broadcasting** (`src/routes.rs:firehose_watch`)
2727-- server-sent events (SSE) endpoint at `/api/firehose/watch?did=<your-did>`
2828-- filters jetstream events to only those matching your DID and collections
2929-- broadcasts as JSON: `{action, collection, namespace, did, rkey}`
3030-3131-### frontend: particles + circles
3232-3333-**WebSocket to SSE bridge** (`static/app.js`)
3434-- `EventSource` connects to SSE endpoint
3535-- parses incoming events
3636-- creates particle animations
3737-- shows toast notifications
3838-3939-**particle system**
4040-- creates colored particles (green=create, blue=update, red=delete)
4141-- animates from app circle → identity (your PDS)
4242-- uses `requestAnimationFrame` for smooth 60fps
4343-- easing with cubic bezier for natural motion
4444-4545-**dynamic circle management**
4646-- new app? → `addAppCircle()` creates it on the fly
4747-- delete event? → `removeAppCircle()` cleans up when particle completes
4848-- circles automatically reposition to maintain even spacing
4949-5050-## event flow
5151-5252-```
5353-1. you create a post in bluesky
5454-2. bluesky writes to your PDS
5555-3. your PDS emits event to firehose
5656-4. jetstream filters and forwards to our backend
5757-5. backend matches your DID + collection
5858-6. SSE pushes event to your browser
5959-7. particle animates from bluesky circle to center
6060-8. identity pulses when particle arrives
6161-9. toast shows "created post: hello world..."
6262-```
6363-6464-## why it works for any app
6565-6666-traditional approaches hardcode collections like `app.bsky.feed.post`. we don't.
6767-6868-instead, we:
6969-1. call `describeRepo` to get YOUR actual collections
7070-2. register ingesters for everything you have
7171-3. dynamically create/remove app circles as events flow
7272-7373-this means if you use:
7474-- whitewind → see blog posts flow in
7575-- tangled → see commits flow in
7676-- at-me guestbook → see signatures flow in
7777-- future apps → automatically supported
7878-7979-## performance notes
8080-8181-- **caching**: DID resolution cached for 1 hour (`constants::CACHE_TTL`)
8282-- **buffer**: broadcast channel with 100-event buffer
8383-- **reconnection**: 5-second delay between retries
8484-- **cleanup**: connections close when SSE client disconnects
8585-8686-## code references
8787-8888-- firehose manager: `src/firehose.rs`
8989-- SSE endpoint: `src/routes.rs:951` (`firehose_watch`)
9090-- dynamic registration: `src/routes.rs:985` (fetch collections via `describeRepo`)
9191-- particle animation: `static/app.js:1037` (`animateFirehoseParticles`)
9292-- circle lifecycle: `static/app.js:1419` (`addAppCircle`), `static/app.js:1646` (`removeAppCircle`)
-51
docs/lexicon.md
···11-# lexicon
22-33-## `app.at-me.visit`
44-55-**status**: unofficial, experimental
66-77-this is the record type created when users opt-in to "sign the guestbook" on at-me.
88-99-### namespace rationale
1010-1111-we use `app.at-me.visit` rather than a domain-based namespace (like `io.zzstoatzz.*`) because:
1212-1313-1. the app is hosted at `at-me.fly.dev`, not under a domain we control
1414-2. using a personal domain namespace would incorrectly suggest this is an official/owned lexicon
1515-3. `app.at-me.*` clearly associates records with this specific application
1616-1717-this is an **unofficial lexicon** - there is no formal schema definition served at a URL. it's a simple, unvalidated record type for analytics/engagement tracking.
1818-1919-### record structure
2020-2121-```json
2222-{
2323- "$type": "app.at-me.visit",
2424- "timestamp": "2025-10-25T22:30:00Z",
2525- "createdAt": "2025-10-25T22:30:00Z",
2626- "text": "optional message from the visitor"
2727-}
2828-```
2929-3030-**fields:**
3131-- `timestamp` (required): ISO 8601 timestamp of when the signature was created
3232-- `createdAt` (required): ISO 8601 timestamp of when the record was created (typically same as timestamp)
3333-- `text` (optional): a message left by the visitor, max 280 characters
3434-3535-### privacy
3636-3737-- users must explicitly authenticate and click "sign guestbook" to create these records
3838-- records are written to the user's own PDS, which they control
3939-- the app does not store or aggregate this data
4040-- users can delete these records at any time through their PDS
4141-4242-### philosophy
4343-4444-this approach aligns with atproto's principles:
4545-- user data sovereignty (records live in user's PDS)
4646-- transparency (users see exactly what's being written)
4747-- opt-in participation (no tracking without explicit consent)
4848-4949-### acknowledgments
5050-5151-thanks to [@thisismissem.social](https://bsky.app/profile/thisismissem.social) for putting [lexicon-guestbook](https://github.com/FujoWebDev/lexicon-guestbook) on our radar! [@essentialrandom.bsky.social](https://bsky.app/profile/essentialrandom.bsky.social)'s work on that project - a more fully-featured implementation with per-user guestbooks, moderation, and an appview - helped inform the addition of optional text messages to our simpler global guestbook.
-27
docs/oauth.md
···11-# oauth
22-33-at-me uses atproto oauth for authentication.
44-55-## flow
66-77-1. user enters handle on landing page
88-2. app resolves handle → DID → authorization server via did document
99-3. authorization server redirects to user's pds for consent
1010-4. user approves, receives redirect back with auth code
1111-5. app exchanges code for access token
1212-6. token stored in session, used for authenticated api calls
1313-1414-## scopes
1515-1616-```rust
1717-Scope::Known(KnownScope::Atproto),
1818-Scope::Unknown("repo:app.at-me.visit".to_string()),
1919-```
2020-2121-the granular scope `repo:app.at-me.visit` limits write access to only guestbook records.
2222-2323-## session management
2424-2525-sessions use actix-web's cookie-based session middleware. authenticated agents cached in-memory by DID for performance (`AGENT_CACHE`).
2626-2727-see `src/oauth.rs` for implementation.