| 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 | |
a814db5feat: push and clone private repositories with a token8d | 7 | ## Active: Milestone 5 — Repo browsing |
| 8 | |
| 9 | **Goal:** a repository's contents are readable on the web — tree, blob, commit log — so |
| 10 | a visitor can look at code without cloning it. The first milestone where the profile |
| 11 | starts to look like a portfolio rather than a list of names. |
| 12 | |
| 13 | **Not planned yet.** Steps get laid out at the start of the milestone rather than |
| 14 | guessed at the end of the previous one. Two things are already known and should shape |
| 15 | that plan: |
| 16 | |
| 17 | - **Start with domain value objects** — `ObjectId`, `RefName`, `TreeEntry` — before any |
| 18 | adapter. A query port returning `String`s is an anaemic pass-through that pushes |
| 19 | validation into the page, which is what |
| 20 | [0006](decisions/0006-git-binary-behind-narrow-ports.md) rejected in advance. |
| 21 | - **Measure fork/exec per page view first.** Milestone 3 paid it once per repository |
| 22 | creation and Milestone 4 once per git request; browsing would pay it several times per |
| 23 | page. This is the point where `gix` for the read path gets reconsidered, and 0006 says |
| 24 | to make that call with a measurement rather than an intuition. |
8608574feat: personal access tokens, as a domain type8d | 25 | |
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d | 26 | ### Open |
| 27 | |
a814db5feat: push and clone private repositories with a token8d | 28 | - **Whether writing (Milestone 6) should come first.** The ladder puts browsing next, but |
| 29 | the vision is portfolio-first and writing is the more distinctive feature. Browsing is |
| 30 | the more expected one. Worth a moment's thought before starting rather than after. |
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d | 31 | |
e0856ebfeat: clone a public repository over HTTP8d | 32 | ### Carried over — small, unblocked |
d7b99d9docs: record milestone 0 progress and routing findings1mo | 33 | |
47db238feat: serve the git protocol through http-backend, behind a port8d | 34 | - **A client that disappears mid-request leaves the body-copy task waiting.** The copy |
e0856ebfeat: clone a public repository over HTTP8d | 35 | into git's stdin runs in its own task and nothing cancels it if the connection drops. |
47db238feat: serve the git protocol through http-backend, behind a port8d | 36 | Bounded by the backend exiting and closing the pipe, but not by anything deliberate. |
a814db5feat: push and clone private repositories with a token8d | 37 | - **`REMOTE_USER` is not set on the backend**, so a push is recorded in the repository's |
| 38 | reflog without naming who made it. Steid knows the actor by then; it simply is not |
| 39 | passed through. Small, and worth doing before anything reads reflogs. |
| 40 | - **No rate limiting on token authentication.** A token is 256 bits so guessing is not |
| 41 | the worry; unbounded hashing on an open endpoint is. |
| 42 | - **Tokens have no expiry and no last-used timestamp.** Both deliberate omissions for |
| 43 | now — see [0007](decisions/0007-tokens-over-http-basic.md) — but a token list with no |
| 44 | "last used" makes it hard to know which are safe to revoke. |
e0856ebfeat: clone a public repository over HTTP8d | 45 | - **A subprocess per git request.** Unlike Milestone 3's once-per-creation, this is on a |
| 46 | hot path and has not been measured. Milestone 5 is where that bill comes due. |
| 47 | - **Streaming is by construction, not by measurement.** The response body is never |
| 48 | collected, but no clone large enough to prove it has been run. |
6a2a925docs: close Milestone 3, plan Milestone 4a8d | 49 | - **An orphaned repo directory is possible** if the process dies between the record |
| 50 | write and the filesystem write, and it then blocks re-creating that name. The durable |
| 51 | fix is a reconciliation sweep on boot |
| 52 | ([architecture.md](architecture.md#db-plus-filesystem-writes)); clearing one is a |
| 53 | manual `rm` today, since repo deletion does not exist. |
| 54 | - **The duplicate-name check races.** The loser is caught by `init_bare` or the unique |
| 55 | constraint, but surfaces as an opaque storage error rather than "name taken". |
| 56 | - **Bare repos created on macOS carry `ignorecase = true`.** A migration gotcha if the |
| 57 | data directory ever moves to Linux. |
2eb8681docs: put git next, plan the repo model24d | 58 | - **Fonts are not loaded.** The theme names Geist and IBM Plex Mono; both fall back |
| 59 | today. Topcoat's `font-fontsource` feature handles it. |
| 60 | - **Light mode is untested.** The palette defines it; nobody has looked at it. |
f0444b7docs: plan milestone 2 in two phases1mo | 61 | - **No rate limiting** on `/auth/login` or `/auth/setup`. |
076dbc9docs: close milestone 1, open milestone 21mo | 62 | - **`sweep_expired` is never called**, so expired session rows accumulate. Expiry is |
| 63 | enforced on read, so this is tidiness, not a hole. |
2eb8681docs: put git next, plan the repo model24d | 64 | - **CSRF.** `SameSite=Lax` covers the common case. Forms now exist, so this is decidable |
| 65 | rather than hypothetical. |
| 66 | |
| 67 | ## Backlog |
| 68 | |
| 69 | Ordered. Pull from the top. |
| 70 | |
a814db5feat: push and clone private repositories with a token8d | 71 | 1. **Milestone 6 — Writing.** Posts, markdown, `/{handle}/posts/{slug}`. Still open |
2eb8681docs: put git next, plan the repo model24d | 72 | whether writing or projects/showcases is the better first portfolio feature. |
| 73 | |
| 74 | ## Open questions |
| 75 | |
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo | 76 | - **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected |
| 77 | by its own authors). Expect churn that isn't feature work. |
| 78 | - Topcoat ships Tailwind without Node, which reopens the design system attempt #1 |
| 79 | dropped purely to avoid an npm build step — see [ui.md](ui.md). |
| 80 | |
| 81 | ## Routing findings (Milestone 0) |
| 82 | |
| 83 | - **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat` |
| 84 | silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local |
| 85 | stable is now 1.97.1. |
| 86 | - `Router::builder().discover()` collects `#[page]`-annotated items **at link time**, |
| 87 | so pages can live in any module. Layering is our choice, not the framework's. |
| 88 | - `module_router!` derives each URL from the module tree rather than a path string. |
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo | 89 | Still deferred. Application routes now group cleanly (`auth/login`, `api/me`), but |
| 90 | handles sit at the root ([0004](decisions/0004-root-handles-grouped-routes.md)), so a |
| 91 | parameterised root segment still has to coexist with static ones. Worth checking how |
| 92 | `module_router!` handles that before committing to it. |
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo | 93 | - Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not |
| 94 | injected as handler arguments. Parses are memoized per request. |
| 95 | - Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's |
| 96 | `NotFoundError` to render a branded 404. |
| 97 | - `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone. |
| 98 | - `Body` is a boxed `http_body::Body` used for both requests and responses, with |
| 99 | `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can |
ca76e1bdocs: fix milestone cross-references after the reorder24d | 100 | stream both directions without buffering. This is what makes Milestone 4 viable. |