| | @@ -6,7 +6,7 @@ |
| 6 | 6 | |
| 7 | 7 | ## Active: Milestone 2 — Profile page |
| 8 | 8 | |
| 9 | | −**Goal:** `/user/{handle}` becomes the real profile page, replacing the Milestone 0 |
| 9 | +**Goal:** `/{handle}` becomes the real profile page, replacing the Milestone 0 |
| 10 | 10 | placeholder. This is the frame the rest of the product hangs in — repos, writing, and |
| 11 | 11 | projects all appear on it later. |
| 12 | 12 | |
| | @@ -16,28 +16,28 @@ stays small. |
| 16 | 16 | |
| 17 | 17 | ### Steps |
| 18 | 18 | |
| 19 | | −- [x] Settle URL shape — scoped under `/user/{handle}`, see |
| 20 | | − [0003](decisions/0003-scoped-urls.md) |
| 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/` |
| 21 | 22 | - [ ] Settle routing mechanics: `module_router!` vs explicit `#[page]` paths, and |
| 22 | 23 | `path_param!` for `{handle}` — deferred from Milestone 0 |
| 23 | | −- [ ] Migration: `orgs.kind` (`personal` | `shared`), backfilled `personal` |
| 24 | | −- [ ] `/user/{handle}` — public page, resolves the org by handle, 404 when unknown |
| 24 | +- [ ] `/{handle}` — public page, resolves the org by handle, 404 when unknown |
| 25 | 25 | - [ ] `/` redirects to the owner's profile once claimed |
| 26 | 26 | - [ ] Owner-only affordances visible when the viewer is the owner |
| 27 | 27 | - [ ] `/api/users/{handle}` — the API surface for the same read model |
| 28 | 28 | |
| 29 | 29 | ### Done when |
| 30 | 30 | |
| 31 | | −`/user/{handle}` renders for a signed-out visitor, shows extra affordances to the |
| 31 | +`/{handle}` renders for a signed-out visitor, shows extra affordances to the |
| 32 | 32 | owner, and an unknown handle 404s rather than erroring. |
| 33 | 33 | |
| 34 | 34 | ### Watch for |
| 35 | 35 | |
| 36 | 36 | - **Handle lookups are case-insensitive** in storage (`collate nocase`) and lowercased |
| 37 | 37 | by `OrgName::new`. A URL with different casing must resolve, not 404. |
| 38 | | −- ~~**Reserved handles.**~~ Resolved by [0003](decisions/0003-scoped-urls.md): handles |
| 39 | | − are scoped under `/user/`, so they cannot collide with application routes and no |
| 40 | | − denylist is needed. |
| 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). |
| 41 | 41 | - **A profile is public.** It is the first page rendering for anonymous visitors by |
| 42 | 42 | design, so anything private must be gated explicitly rather than by assuming a |
| 43 | 43 | session exists. |
| | @@ -60,7 +60,7 @@ owner, and an unknown handle 404s rather than erroring. |
| 60 | 60 | |
| 61 | 61 | Ordered. Pull from the top. |
| 62 | 62 | |
| 63 | | −1. **Milestone 3 — Writing.** Posts, markdown rendering, `/user/{handle}/posts/{slug}`. |
| 63 | +1. **Milestone 3 — Writing.** Posts, markdown rendering, `/{handle}/posts/{slug}`. |
| 64 | 64 | *Open question: is writing actually the first portfolio feature, or is it |
| 65 | 65 | projects/showcases?* |
| 66 | 66 | 2. **Milestone 4 — Repo model.** `Repository` entity, `Visibility`, `create_repo`, |
| | @@ -87,9 +87,10 @@ Ordered. Pull from the top. |
| 87 | 87 | - `Router::builder().discover()` collects `#[page]`-annotated items **at link time**, |
| 88 | 88 | so pages can live in any module. Layering is our choice, not the framework's. |
| 89 | 89 | - `module_router!` derives each URL from the module tree rather than a path string. |
| 90 | | − Still deferred. Now that URLs are scoped ([0003](decisions/0003-scoped-urls.md)) the |
| 91 | | − module tree and the URL tree line up — `user/handle/repos/name` — which makes |
| 92 | | − `module_router!` a much better fit than it was under root-level handles. |
| 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. |
| 93 | 94 | - Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not |
| 94 | 95 | injected as handler arguments. Parses are memoized per request. |
| 95 | 96 | - Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's |