OverviewFilesCommitsIssuesDocsReleasesPeers

favela-surface commits / 9a510ee

README: reference, not essay
Config and JavaScript as tables, auth as bullets, and the full route list — which was missing entirely. Dropped the paragraphs arguing why the boundary exists; the rule and the test that enforces it are the informational part. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9a510ee2df83f9231225ad0036f7699c3b7836a3
by plankatron <fleeky@gmail.com> · 2026-09-09T21:34:34.000Z

Changed files (1) +49 -46

README.md +49 -46
……@@ -1,9 +1,8 @@
11# favela-surface
22
3**Read your favela in a browser.** `favela-peer` owns the data; this turns it into pages.
3The favela renderer. Server-side HTML over `favela-peer`'s socket.
44
5It is a renderer and only a renderer. Everything it knows it asked the daemon over a unix socket,
6and everything it shows, `favela-markdown` rendered. Close it and you lose nothing but the window.
5No storage, no keys, no swarm — it holds a socket and nothing else.
76
87## Install
98
……@@ -19,70 +18,74 @@ favela-peer & # the daemon owns the data
1918favela-surface # http://127.0.0.1:4042
2019```
2120
22**If every page says the daemon is down, it is almost always the socket.** `FAVELA_SOCK` has to
23point at the *daemon's* storage, not the surface's. The two apps have separate defaults, so a
24surface left on its own looks for a socket nobody created. This package's pgh defaults set it to
25`${HOME}/.config/favela-peer/favela.sock`; set it yourself if you run the daemon elsewhere.
21## Config
2622
27`FAVELA_SURFACE_PORT` and `FAVELA_SURFACE_HOST` move the listener. It binds `127.0.0.1` — this is
28your own daemon's data with no authentication in front of it, so exposing it is a decision.
23| var | default | |
24|---|---|---|
25| `FAVELA_SOCK` | `${HOME}/.config/favela-peer/favela.sock` | the DAEMON's socket, not the surface's |
26| `FAVELA_SURFACE_PORT` | `4042` | |
27| `FAVELA_SURFACE_HOST` | `127.0.0.1` | no authentication in front of it |
2928
30## Signing in
29Every page reporting "daemon down" almost always means `FAVELA_SOCK` points at the surface's own
30storage default rather than the daemon's. The two apps have separate defaults.
3131
32Every route is gated except `/login`. A browser gets in with a **one-time invite**, minted on the
33machine running the daemon:
32## Auth
3433
35```
36favela invite # prints a single-use token
34Every route is gated except `/login`.
35
36```sh
37favela invite # one-time token, minted on the daemon's machine
3738favela browsers # who is admitted
3839favela revoke <pubkey> # retire one
3940```
4041
41Your browser makes an Ed25519 keypair, keeps it in `localStorage`, and answers a challenge with it.
42There is **no expiry and no password** — the key is the credential, so revoking is the only way out.
42- The browser generates an Ed25519 keypair, keeps it in `localStorage`, answers a challenge with it.
43- No expiry, no password. The key is the credential; revocation is the only way out.
44- A revoke lands on that browser's **next request** — the daemon is asked on every page load.
45- Clearing site data means asking for a new invite.
46- Sessions are in memory: restarting the surface signs everyone out.
4347
44Two consequences worth knowing before they surprise you:
48## JavaScript
4549
46- A revoke lands on that browser's **next request**, not whenever a session would have lapsed. The
47 daemon is asked whether the key is still allowed on every page load.
48- Clearing site data means asking for a new invite. Nothing on the server remembers you.
49- Restarting the surface signs everyone out, because sessions live in memory. That is a restart,
50 not an expiry, and it follows from the surface holding no storage.
50Reading needs none. Three pages load a script; two of them require it.
5151
52## JavaScript
52| page | loads | JS off |
53|---|---|---|
54| `/login` | inline module + vendored signer (22 KB) | breaks — no form post makes an Ed25519 signature |
55| `/` timeline | `composer.js` (2.6 KB), the `@`-picker | works — posts a real multipart form |
56| `/explore` | `explore.js` + three.js (733 KB) | breaks — rotating 3D view |
5357
54Reading needs none. Timelines, profiles, posts, search, settings, the graph — all server-rendered
55HTML, and they work with JavaScript switched off.
58- `/explore` is the only page that loads three.js. Its legend is server-rendered.
59- Everything under `/static/` is vendored, never a CDN: a page loading its signer from someone
60 else's server would let that server mint signatures with the user's key.
61- See `static/README.md` for what is vendored and from where.
5662
57Three pages load a script, for three different reasons — and only two of them need one:
63## Routes
5864
59| page | what it loads | without JavaScript |
60|---|---|---|
61| `/login` | an inline module + the vendored signer (21K) | **doesn't work** — a form post cannot produce an Ed25519 signature |
62| `/` (the timeline) | `composer.js` (2.6K), the `@`-picker | **works** — it posts a real multipart form; the picker only saves you typing |
63| `/explore` | `explore.js` + three.js (~730K) | **doesn't work** — it is a rotating 3D view |
65`/` `/u/<key>` `/r/<key>` `/t/<tag>` `/search` `/settings` `/explore` `/graph`
66`/following` `/notifications` `/mentions` `/knocks` `/rooms` `/directory`
67
68Actions (POST): `/post` `/follow` `/unfollow` `/like` `/unlike` `/react` `/unreact` `/repost`
69`/unrepost` `/reply` `/block` `/unblock` `/knock` `/admit` `/peek` `/reach` `/handle` `/theme`
70
71JSON: `/api/graph` `/api/records` `/api/mentions` `/api/node`
72
73Auth: `/login` `/logout` `/auth/challenge` `/auth/login` `/auth/redeem` `/auth/claim` `/auth/handoff`
6474
65The composer is the pattern to copy. It inserts `@alice` — ordinary text you could have typed —
66and the daemon resolves mentions on the write path either way. With the script missing the composer
67still works; with it present it cannot produce a mention the server would not have produced anyway.
75Blobs: `/blob/<oid>` `/avatar/<key>` `/static/<file>`
6876
69`/explore` is the one place a browser pays for a megabyte, and only that page loads it. Its legend
70is server-rendered on purpose — an explanation of what the dots mean is no use if it arrives after
71the WebGL.
77## Boundary
7278
73**Everything under `/static/` is vendored, never a CDN.** A page that loaded its signer from
74someone else's server would let that server mint signatures with your key. See `static/README.md`.
79⛔ No `corestore`, `hypercore`, `autobee`, `autobase`, `hyperswarm`, `hyper-overlay`, `atproto-*`
80or `ap-bridge` — in any source file or dependency. `test/boundary.test.js` enforces it.
7581
76## The boundary
82A renderer that grows a Corestore has become a second peer implementation.
7783
78⛔ **No storage, no keys, no swarm.** This process holds a socket and nothing else. A surface that
79grows a Corestore has quietly become a second peer implementation — the fork risk
80`favela-markdown/spec/library.md` §1 names for the codec, which applies here identically.
84## Status
8185
82`test/boundary.test.js` enforces it: no source file and no dependency may name `corestore`,
83`hypercore`, `autobee`, `autobase`, `hyperswarm`, `hyper-overlay`, `atproto-*` or `ap-bridge`.
86Working. 0.0.47, running on tabertron, ashatron and stortron.
8487
85`npm test` — 175 tests, 565 asserts, all passing.
88`npm test` — 175 tests, 565 asserts, passing.
8689
8790## License
8891