steid

@jamesgill /

steid/plans/current.md
4.6 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 1 — Identity, thin
8
9**Goal:** the app knows who you are. One owner, bootstrapped from config, who can log
10in and out. Enough identity to hang a profile page off, and no more.
11
12**Explicitly out of scope** — these are Milestone 7: multi-user registration,
13invite codes, `RegistrationPolicy`, organisation management UI, roles beyond owner.
14
15### Steps
16
17- [ ] Domain: typed IDs, `Email`, `PasswordHash`, `User`, `Organization`,
18 `Membership`, `Role`, `Actor`, `DomainError`
19- [ ] Domain: repository traits — `UserRepository`, `OrgRepository`,
20 `MembershipRepository`
21- [ ] Infrastructure: in-memory implementations (these are what make use cases
22 testable without a database)
23- [ ] Infrastructure: migrations `001``003`, SQLite implementations
24- [ ] Application: `PasswordHasher` port + Argon2 adapter, stub hasher for tests
25- [ ] Application: `bootstrap_owner` use case — creates org, then user, then
26 membership, idempotent on reboot
27- [ ] Application: `login` use case — verifies credentials, returns an `Actor`
28- [ ] Web: login page, logout, session cookie, `current_actor(cx)` helper
29- [ ] `/api/me` — first `/api` route, proves the use case layer has two consumers
30
31### Done when
32
33A fresh database boots into an owner account from `STEID_OWNER_*`; logging in through
34the web UI sets a session; `/api/me` returns that identity; logging out clears it.
35
36### Watch for
37
38- **Foreign key ordering.** The org must be saved before the user — attempt #2 had to
39 fix this in both `bootstrap_owner` and `register_user`. See
40 [progress.md]progress.md#identity.
41- **Bootstrap must be idempotent.** It runs on every boot, not just the first.
42- Never log or `Debug`-print a password. `PasswordHash` is opaque on purpose.
43
44## Backlog
45
46Ordered. Pull from the top.
47
481. **Milestone 2 — Profile page.** `/{owner}` becomes the real home page, replacing
49 the Milestone 0 placeholder. The frame the rest of the product hangs in.
502. **Milestone 3 — Writing.** Posts, markdown rendering, `/{owner}/{slug}`.
51 *Open question: is writing actually the first portfolio feature, or is it
52 projects/showcases?*
533. **Milestone 4 — Repo model.** `Repository` entity, `Visibility`, `create_repo`,
54 bare repo on disk at `{data_dir}/{org}/{repo}.git`. Watch the DB-plus-filesystem
55 atomicity problem — see [architecture.md]architecture.md#db-plus-filesystem-writes.
564. **Milestone 5 — Git over HTTP.** `git http-backend` subprocess, PATs over HTTP
57 Basic. See [0001]decisions/0001-git-over-http-not-ssh.md.
58
59## Open questions
60
61- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
62 by its own authors). Expect churn that isn't feature work.
63- Body size limits will reject large pushes at Milestone 5 — `topcoat-router` has a
64 `body_limit` layer that needs raising on the git routes. Recorded here because it
65 will surface as a confusing failure rather than a clear one.
66- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
67 dropped purely to avoid an npm build step — see [ui.md]ui.md.
68
69## Routing findings (Milestone 0)
70
71- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
72 silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
73 stable is now 1.97.1.
74- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
75 so pages can live in any module. Layering is our choice, not the framework's.
76- `module_router!` derives each URL from the module tree rather than a path string.
77 Still deferred — Steid's URL space is parameterised at the root (`/{owner}`,
78 `/{owner}/{repo}`), which means `path_param!` declarations inside route modules.
79 Worth designing at Milestone 2 when the profile page makes it concrete.
80- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
81 injected as handler arguments. Parses are memoized per request.
82- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
83 `NotFoundError` to render a branded 404.
84- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
85- `Body` is a boxed `http_body::Body` used for both requests and responses, with
86 `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
87 stream both directions without buffering. This is what makes Milestone 5 viable.