steid

@jamesgill /

steid/plans/current.md
6.2 KBCode·Blame·Raw
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
7## Active: Milestone 6 — Writing
8
9**Goal:** posts, written in markdown, at `/{handle}/posts/{slug}`, appearing on the
10profile. The second portfolio feature, and the one that makes Steid something other than
11a git host.
12
13**Not planned yet.** Steps get laid out at the start of the milestone.
14
15### Open
16
17- **Which markdown crate**, and whether rendering is trusted. `pulldown-cmark` is the
18 obvious choice and is not currently a dependency. Raw HTML in markdown is the decision
19 inside it: a single-author instance can trust its own input, but the moment Milestone 7
20 adds a second user that assumption is a stored-XSS hole. Deciding now is cheaper than
21 retrofitting a sanitiser.
22- **Whether a repository's README renders on its page.** It is the feature that makes a
23 repo page look like a portfolio piece rather than a file list, and it falls out of the
24 markdown pipeline this milestone builds — so it belongs here rather than back in 5.
25
26### Carried over — small, unblocked
27
28- **A client that disappears mid-request leaves the body-copy task waiting.** The copy
29 into git's stdin runs in its own task and nothing cancels it if the connection drops.
30 Bounded by the backend exiting and closing the pipe, but not by anything deliberate.
31- **`REMOTE_USER` is not set on the backend**, so a push is recorded in the repository's
32 reflog without naming who made it. Steid knows the actor by then; it simply is not
33 passed through. Small, and worth doing before anything reads reflogs.
34- **No rate limiting on token authentication.** A token is 256 bits so guessing is not
35 the worry; unbounded hashing on an open endpoint is.
36- **Tokens have no expiry and no last-used timestamp.** Both deliberate omissions for
37 now — see [0007]decisions/0007-tokens-over-http-basic.md — but a token list with no
38 "last used" makes it hard to know which are safe to revoke.
39- **A subprocess per git request.** Unlike Milestone 3's once-per-creation, this is on a
40 hot path and has not been measured. Milestone 5 is where that bill comes due.
41- **Streaming is by construction, not by measurement.** The response body is never
42 collected, but no clone large enough to prove it has been run.
43- **An orphaned repo directory is possible** if the process dies between the record
44 write and the filesystem write, and it then blocks re-creating that name. The durable
45 fix is a reconciliation sweep on boot
46 ([architecture.md]architecture.md#db-plus-filesystem-writes); clearing one is a
47 manual `rm` today, since repo deletion does not exist.
48- **The duplicate-name check races.** The loser is caught by `init_bare` or the unique
49 constraint, but surfaces as an opaque storage error rather than "name taken".
50- **Bare repos created on macOS carry `ignorecase = true`.** A migration gotcha if the
51 data directory ever moves to Linux.
52- **Light mode is still untested**, and now there is much more surface to get it wrong
53 on — the file tree, the blob view and the log all shipped without anyone looking at
54 them in light mode.
55- **Submodule rendering was never seen**, only compiled: no fixture contained one.
56- **The `/log` page shows no branch indicator** when no revision is given, because
57 `repo_log` does not return the revision it resolved. A second query or a small
58 application change, neither urgent.
59- **A per-file last-commit column is still absent**, deliberately — see
60 [0006]decisions/0006-git-binary-behind-narrow-ports.md#amendment--20260829-the-milestone-5-read-path.
61 Wanting it is the trigger to move to a kept-alive `cat-file --batch`, not to reopen
62 `gix`.
63- **Fonts are not loaded.** The theme names Geist and IBM Plex Mono; both fall back
64 today. Topcoat's `font-fontsource` feature handles it.
65- **Light mode is untested.** The palette defines it; nobody has looked at it.
66- **No rate limiting** on `/auth/login` or `/auth/setup`.
67- **`sweep_expired` is never called**, so expired session rows accumulate. Expiry is
68 enforced on read, so this is tidiness, not a hole.
69- **CSRF.** `SameSite=Lax` covers the common case. Forms now exist, so this is decidable
70 rather than hypothetical.
71
72## Backlog
73
74Ordered. Pull from the top.
75
761. **Milestone 6 — Writing.** Posts, markdown, `/{handle}/posts/{slug}`. Still open
77 whether writing or projects/showcases is the better first portfolio feature.
78
79## Open questions
80
81- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
82 by its own authors). Expect churn that isn't feature work.
83- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
84 dropped purely to avoid an npm build step — see [ui.md]ui.md.
85
86## Routing findings (Milestone 0)
87
88- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
89 silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
90 stable is now 1.97.1.
91- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
92 so pages can live in any module. Layering is our choice, not the framework's.
93- `module_router!` derives each URL from the module tree rather than a path string.
94 Still deferred. Application routes now group cleanly (`auth/login`, `api/me`), but
95 handles sit at the root ([0004]decisions/0004-root-handles-grouped-routes.md), so a
96 parameterised root segment still has to coexist with static ones. Worth checking how
97 `module_router!` handles that before committing to it.
98- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
99 injected as handler arguments. Parses are memoized per request.
100- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
101 `NotFoundError` to render a branded 404.
102- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
103- `Body` is a boxed `http_body::Body` used for both requests and responses, with
104 `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
105 stream both directions without buffering. This is what makes Milestone 4 viable.