steid

@jamesgill /

steid/plans/current.md
5.3 KBCode·Blame·Raw
ab7fea9chore: plans setup1mo
1# Current
2
3> Keep this file short. One active step, one ordered backlog. Completed work moves to
4> [progress.md]progress.md. If this file starts reading like a changelog, it has
5> drifted — that's exactly what went wrong last time.
6
076dbc9docs: close milestone 1, open milestone 21mo
7## Active: Milestone 2 — Profile page
ab7fea9chore: plans setup1mo
8
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
9**Goal:** `/{handle}` becomes the real profile page, replacing the Milestone 0
076dbc9docs: close milestone 1, open milestone 21mo
10placeholder. This is the frame the rest of the product hangs in — repos, writing, and
11projects all appear on it later.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
12
076dbc9docs: close milestone 1, open milestone 21mo
13**Explicitly out of scope:** repos and posts don't exist yet, so the page shows
14identity plus empty sections. Editing display name / bio is Milestone 2 only if it
15stays small.
ab7fea9chore: plans setup1mo
16
17### Steps
18
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
19- [x] Settle URL shape — handles at the root, routes grouped under prefixes, see
20 [0004]decisions/0004-root-handles-grouped-routes.md
21- [x] Reserved-handle denylist in `OrgName::new`; auth routes moved under `/auth/`
7addd53docs: scope every URL under an explicit prefix1mo
22- [ ] Settle routing mechanics: `module_router!` vs explicit `#[page]` paths, and
23 `path_param!` for `{handle}` — deferred from Milestone 0
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
24- [ ] `/{handle}` — public page, resolves the org by handle, 404 when unknown
076dbc9docs: close milestone 1, open milestone 21mo
25- [ ] `/` redirects to the owner's profile once claimed
26- [ ] Owner-only affordances visible when the viewer is the owner
7addd53docs: scope every URL under an explicit prefix1mo
27- [ ] `/api/users/{handle}` — the API surface for the same read model
ab7fea9chore: plans setup1mo
28
29### Done when
30
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
31`/{handle}` renders for a signed-out visitor, shows extra affordances to the
7addd53docs: scope every URL under an explicit prefix1mo
32owner, and an unknown handle 404s rather than erroring.
88583f2docs: bring tracking docs up to date with milestone 11mo
33
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
34### Watch for
d7b99d9docs: record milestone 0 progress and routing findings1mo
35
076dbc9docs: close milestone 1, open milestone 21mo
36- **Handle lookups are case-insensitive** in storage (`collate nocase`) and lowercased
37 by `OrgName::new`. A URL with different casing must resolve, not 404.
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
38- **Reserve handles early.** Adding to the denylist later is a breaking change for
39 whoever holds that handle. It is free while unclaimed, so reserve an area before it
40 exists — see [0004]decisions/0004-root-handles-grouped-routes.md.
076dbc9docs: close milestone 1, open milestone 21mo
41- **A profile is public.** It is the first page rendering for anonymous visitors by
42 design, so anything private must be gated explicitly rather than by assuming a
43 session exists.
44
45### Carried over — small, unblocked, worth doing alongside
46
47- **Flash messages.** A wrong password or setup token bounces with no explanation.
48 Deliberate on the security side, but indistinguishable from a broken form. Every
49 form added from here inherits the problem.
50- **No rate limiting** on `/login` or `/setup`.
51- **`sweep_expired` is never called**, so expired session rows accumulate. Expiry is
52 enforced on read, so this is tidiness, not a hole.
53- **CSRF.** `SameSite=Lax` covers the common case; whether forms also want tokens is
54 still undecided.
55- **Styling.** Everything is unstyled HTML. Topcoat ships Tailwind without Node, and
56 `steid-backup/AGENTS/UI.md` has a full OKLCH system to mine. Cheaper at two pages
57 than at ten — see [ui.md]ui.md.
ab7fea9chore: plans setup1mo
58
59## Backlog
60
61Ordered. Pull from the top.
62
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
631. **Milestone 3 — Writing.** Posts, markdown rendering, `/{handle}/posts/{slug}`.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
64 *Open question: is writing actually the first portfolio feature, or is it
65 projects/showcases?*
076dbc9docs: close milestone 1, open milestone 21mo
662. **Milestone 4 — Repo model.** `Repository` entity, `Visibility`, `create_repo`,
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
67 bare repo on disk at `{data_dir}/{org}/{repo}.git`. Watch the DB-plus-filesystem
68 atomicity problem — see [architecture.md]architecture.md#db-plus-filesystem-writes.
076dbc9docs: close milestone 1, open milestone 21mo
693. **Milestone 5 — Git over HTTP.** `git http-backend` subprocess, PATs over HTTP
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
70 Basic. See [0001]decisions/0001-git-over-http-not-ssh.md.
ab7fea9chore: plans setup1mo
71
72## Open questions
73
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
74- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
75 by its own authors). Expect churn that isn't feature work.
76- Body size limits will reject large pushes at Milestone 5 — `topcoat-router` has a
77 `body_limit` layer that needs raising on the git routes. Recorded here because it
78 will surface as a confusing failure rather than a clear one.
79- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
80 dropped purely to avoid an npm build step — see [ui.md]ui.md.
81
82## Routing findings (Milestone 0)
83
84- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
85 silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
86 stable is now 1.97.1.
87- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
88 so pages can live in any module. Layering is our choice, not the framework's.
89- `module_router!` derives each URL from the module tree rather than a path string.
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
90 Still deferred. Application routes now group cleanly (`auth/login`, `api/me`), but
91 handles sit at the root ([0004]decisions/0004-root-handles-grouped-routes.md), so a
92 parameterised root segment still has to coexist with static ones. Worth checking how
93 `module_router!` handles that before committing to it.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
94- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
95 injected as handler arguments. Parses are memoized per request.
96- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
97 `NotFoundError` to render a branded 404.
98- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
99- `Body` is a boxed `http_body::Body` used for both requests and responses, with
100 `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
101 stream both directions without buffering. This is what makes Milestone 5 viable.