steid

@jamesgill /

steid/plans/current.md
8.1 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
f0444b7docs: plan milestone 2 in two phases1mo
9**Goal:** `/{handle}` is the real profile page — public, working signed out, and the
10frame that repos, writing, and projects hang off later. It replaces the Milestone 0
11placeholder.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
12
f0444b7docs: plan milestone 2 in two phases1mo
13**Out of scope:** repos, posts, and projects don't exist yet, so there is nothing to
14list. Avatars, following, and anything social. Organisation profiles beyond what falls
15out for free.
ab7fea9chore: plans setup1mo
16
f0444b7docs: plan milestone 2 in two phases1mo
17### Phase 1 — the page
ab7fea9chore: plans setup1mo
18
f0444b7docs: plan milestone 2 in two phases1mo
19Shippable on its own: a public profile that renders signed out.
20
21- [x] Settle URL shape — handles at the root, routes grouped under prefixes
22 ([0004]decisions/0004-root-handles-grouped-routes.md)
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
23- [x] Reserved-handle denylist in `OrgName::new`; auth routes moved under `/auth/`
3954b45docs: record milestone 2 phase 125d
24- [x] Explicit `#[page]` paths for now; `module_router!` still unexamined, and four
25 routes is too few to judge it against
26- [x] `PublicProfile` read model + `view_profile` use case — no email field, by design
27- [x] `/{handle}` page: label, handle, bio, and the section frame; 404 on unknown,
87edb56docs: settle milestone 2 open questions1mo
28 case-insensitive
3954b45docs: record milestone 2 phase 125d
29- [x] Asserted `/auth/login` and `/api/me` still route with `/{handle}` at the root —
30 static beats parameterised
31- [x] `/` redirects to the owner's profile once claimed, retiring the placeholder
32- [x] `/api/users/{handle}` — same read model, public JSON
33- [x] Migration `orgs.bio`, pulled forward so the page had a field to render
f0444b7docs: plan milestone 2 in two phases1mo
34
35### Phase 2 — make it yours
36
37A profile you can't change is a stub. This is what makes it a portfolio page.
38
af126c7feat: tailwind theme and the first components25d
39- [x] Styling: Tailwind via Topcoat, theme retuned, primitives copied in, `flash`
40 written by hand ([0005]decisions/0005-tailwind-and-copied-components.md)
f0444b7docs: plan milestone 2 in two phases1mo
41- [ ] Flash messages — the first edit form needs success and failure feedback, and
42 every form after it inherits whatever we build here
43- [ ] `/{handle}/settings` — edit display name and bio, owner only, enforced in the use
87edb56docs: settle milestone 2 open questions1mo
44 case (settings belong to the org, and this scales to organisations)
f0444b7docs: plan milestone 2 in two phases1mo
45- [ ] Owner-only affordances on the profile (edit link)
ab7fea9chore: plans setup1mo
46
47### Done when
48
f0444b7docs: plan milestone 2 in two phases1mo
49Signed out, `/{handle}` renders the owner's display name and handle and nothing
50private. An unknown handle 404s. The owner can set a display name and bio and see them
51on the page. `/api/users/{handle}` returns the same public view.
88583f2docs: bring tracking docs up to date with milestone 11mo
52
3954b45docs: record milestone 2 phase 125d
53### Findings — phase 1
54
55- **`path_param` is an attribute in Topcoat 0.5**, applied to a tuple struct
56 (`#[path_param] struct Handle(str);`), not the function-like `path_param!(handle)`
57 that the docs on `main` describe. **Read the vendored crate, not GitHub `main`**
58 the framework is two weeks old and the two have already diverged.
59- A `str` inner type yields the raw percent-decoded segment with no parsing, which
60 suits validating through `OrgName` and 404ing what fails.
61- **Static routes beat parameterised ones**, so `/auth/login` and `/api/me` still work
62 with `/{handle}` registered at the root. Verified, not assumed.
63- Topcoat serves bundled assets from `/_topcoat/assets/…` with content-hashed URLs.
64
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
65### Watch for
d7b99d9docs: record milestone 0 progress and routing findings1mo
66
f0444b7docs: plan milestone 2 in two phases1mo
67- **Do not leak email.** `describe_identity` carries email, and `/api/me` returns it —
68 correctly, because that endpoint describes the caller to themselves. The public
69 profile needs its **own** read model; reusing `Identity` would publish the owner's
70 email address to anonymous visitors. This is the single most likely mistake in this
71 milestone.
87edb56docs: settle milestone 2 open questions1mo
72- **Route precedence** between static routes and `/{handle}`. The reserved list stops a
73 user *owning* `auth`, but it does not stop the router matching `/api/me` against
74 `/{handle}/{x}` and shadowing the real route. Static-over-parameterised is near
75 universal, so this is an assertion when the route lands, not a blocking spike.
f0444b7docs: plan milestone 2 in two phases1mo
76- **Case-insensitive handles.** Storage is `collate nocase` and `OrgName::new`
77 lowercases, so `/JamesGill` must resolve rather than 404.
78- **Authorization on settings** belongs in the use case, taking an `Actor` — not in the
79 page. Otherwise `/api` gets a different answer from the web form.
80- **A profile is public.** This is the first page rendering for anonymous visitors by
076dbc9docs: close milestone 1, open milestone 21mo
81 design, so anything private must be gated explicitly rather than by assuming a
82 session exists.
f0444b7docs: plan milestone 2 in two phases1mo
83- **Reserve handles early.** Adding to the denylist later is a breaking change for
84 whoever holds that handle ([0004]decisions/0004-root-handles-grouped-routes.md).
85
87edb56docs: settle milestone 2 open questions1mo
86### Settled
f0444b7docs: plan milestone 2 in two phases1mo
87
87edb56docs: settle milestone 2 open questions1mo
88- **Settings live at `/{handle}/settings`.** They belong to the organisation, which
89 scales to real organisations in Milestone 7 without moving.
90- **Render the full frame from the start**, including sections with nothing in them.
91 A profile page that renders almost nothing is a poor start for a portfolio-first
92 product; the shape of the page is part of what is being built, not scaffolding for
93 it.
076dbc9docs: close milestone 1, open milestone 21mo
94
f0444b7docs: plan milestone 2 in two phases1mo
95### Carried over — small, unblocked
076dbc9docs: close milestone 1, open milestone 21mo
96
f0444b7docs: plan milestone 2 in two phases1mo
97- **No rate limiting** on `/auth/login` or `/auth/setup`.
076dbc9docs: close milestone 1, open milestone 21mo
98- **`sweep_expired` is never called**, so expired session rows accumulate. Expiry is
99 enforced on read, so this is tidiness, not a hole.
100- **CSRF.** `SameSite=Lax` covers the common case; whether forms also want tokens is
f0444b7docs: plan milestone 2 in two phases1mo
101 still undecided. Phase 2 adds a form, so this is the natural time to settle it.
af126c7feat: tailwind theme and the first components25d
102- **Light mode is untested.** The palette defines it, but every page has been looked at
103 dark-only. There is no toggle yet either.
104- **Fonts are not loaded.** The theme names Geist and IBM Plex Mono; neither is
105 installed, so both fall back. `topcoat`'s `font-fontsource` feature handles this.
ab7fea9chore: plans setup1mo
106
107## Backlog
108
109Ordered. Pull from the top.
110
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
1111. **Milestone 3 — Writing.** Posts, markdown rendering, `/{handle}/posts/{slug}`.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
112 *Open question: is writing actually the first portfolio feature, or is it
113 projects/showcases?*
076dbc9docs: close milestone 1, open milestone 21mo
1142. **Milestone 4 — Repo model.** `Repository` entity, `Visibility`, `create_repo`,
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
115 bare repo on disk at `{data_dir}/{org}/{repo}.git`. Watch the DB-plus-filesystem
116 atomicity problem — see [architecture.md]architecture.md#db-plus-filesystem-writes.
076dbc9docs: close milestone 1, open milestone 21mo
1173. **Milestone 5 — Git over HTTP.** `git http-backend` subprocess, PATs over HTTP
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
118 Basic. See [0001]decisions/0001-git-over-http-not-ssh.md.
ab7fea9chore: plans setup1mo
119
120## Open questions
121
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
122- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
123 by its own authors). Expect churn that isn't feature work.
124- Body size limits will reject large pushes at Milestone 5 — `topcoat-router` has a
125 `body_limit` layer that needs raising on the git routes. Recorded here because it
126 will surface as a confusing failure rather than a clear one.
127- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
128 dropped purely to avoid an npm build step — see [ui.md]ui.md.
129
130## Routing findings (Milestone 0)
131
132- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
133 silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
134 stable is now 1.97.1.
135- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
136 so pages can live in any module. Layering is our choice, not the framework's.
137- `module_router!` derives each URL from the module tree rather than a path string.
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
138 Still deferred. Application routes now group cleanly (`auth/login`, `api/me`), but
139 handles sit at the root ([0004]decisions/0004-root-handles-grouped-routes.md), so a
140 parameterised root segment still has to coexist with static ones. Worth checking how
141 `module_router!` handles that before committing to it.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
142- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
143 injected as handler arguments. Parses are memoized per request.
144- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
145 `NotFoundError` to render a branded 404.
146- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
147- `Body` is a boxed `http_body::Body` used for both requests and responses, with
148 `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
149 stream both directions without buffering. This is what makes Milestone 5 viable.