# Current

> Keep this file short. One active step, one ordered backlog. Completed work moves to
> [progress.md](progress.md). If this file starts reading like a changelog, it has
> drifted — that's exactly what went wrong last time.

## Active: Milestone 1 — Identity, thin

**Goal:** the app knows who you are. An unclaimed installation is claimed through
`/setup`, and the resulting owner can log in and out. Enough identity to hang a profile
page off, and no more.

**Explicitly out of scope** — these are Milestone 7: multi-user registration,
invite codes, `RegistrationPolicy`, organisation management UI, roles beyond owner.

### Steps

- [x] Domain: typed IDs, `Email`, `PasswordHash`, `User`, `Organization`,
      `Membership`, `Role`, `Actor`, `DomainError`
- [x] Domain: repository traits — `UserRepository`, `OrgRepository`,
      `MembershipRepository`
- [x] Infrastructure: in-memory implementations (these are what make use cases
      testable without a database)
- [x] Application: `PasswordHasher` port + Argon2 adapter, stub hasher for tests
- [x] Domain: `SetupToken` — one-time claim secret, constant-time comparison
- [x] Application: `claim_instance` use case — token-gated, creates org → user →
      owner membership, returns the owner signed in
- [x] Application: `login` use case — verifies credentials, returns an `Actor`
- [ ] Infrastructure: migrations `001`–`003`, SQLite implementations
- [ ] Infrastructure: `sessions` table + session storage
- [ ] Boot: mint and print a `SetupToken` when unclaimed; register it in app context
- [ ] Web: `/setup` claim page; every other route redirects there while unclaimed
- [ ] Web: login page, logout, `current_actor(cx)` helper
- [ ] `/api/me` — first `/api` route, proves the use case layer has two consumers

### Done when

A fresh database prints a setup token at boot; `/setup` with that token creates the
owner and signs them in; logging out and back in works; `/api/me` returns that
identity.

### Watch for

- **`__Host-` cookies need a secure context.** Topcoat's session cookie is
  `__Host-`-prefixed and `Secure`. Browsers treat `http://localhost` as trustworthy so
  dev over plain HTTP *should* work — verify this as soon as the login page exists,
  because if it's wrong, login fails silently and looks like a bug in our code.
- **CSRF.** `SameSite=Lax` blocks cross-site POSTs, which covers the common case.
  Whether forms also want tokens is an open decision, not a default to pick quietly.
- **Claim is TOCTOU.** `is_claimed` then write is not atomic; the `UNIQUE` constraints
  on email and org name are what actually serialise concurrent claims. Integration-test
  this once SQLite lands.
- **Foreign key ordering.** The org must be saved before the user — attempt #2 had to
  fix this in both `bootstrap_owner` and `register_user`. See
  [progress.md](progress.md#identity).
- **Bootstrap must be idempotent.** It runs on every boot, not just the first.
- Never log or `Debug`-print a password. `PasswordHash` is opaque on purpose.

## Backlog

Ordered. Pull from the top.

1. **Milestone 2 — Profile page.** `/{owner}` becomes the real home page, replacing
   the Milestone 0 placeholder. The frame the rest of the product hangs in.
2. **Milestone 3 — Writing.** Posts, markdown rendering, `/{owner}/{slug}`.
   *Open question: is writing actually the first portfolio feature, or is it
   projects/showcases?*
3. **Milestone 4 — Repo model.** `Repository` entity, `Visibility`, `create_repo`,
   bare repo on disk at `{data_dir}/{org}/{repo}.git`. Watch the DB-plus-filesystem
   atomicity problem — see [architecture.md](architecture.md#db-plus-filesystem-writes).
4. **Milestone 5 — Git over HTTP.** `git http-backend` subprocess, PATs over HTTP
   Basic. See [0001](decisions/0001-git-over-http-not-ssh.md).

## Open questions

- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
  by its own authors). Expect churn that isn't feature work.
- Body size limits will reject large pushes at Milestone 5 — `topcoat-router` has a
  `body_limit` layer that needs raising on the git routes. Recorded here because it
  will surface as a confusing failure rather than a clear one.
- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
  dropped purely to avoid an npm build step — see [ui.md](ui.md).

## Routing findings (Milestone 0)

- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
  silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
  stable is now 1.97.1.
- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
  so pages can live in any module. Layering is our choice, not the framework's.
- `module_router!` derives each URL from the module tree rather than a path string.
  Still deferred — Steid's URL space is parameterised at the root (`/{owner}`,
  `/{owner}/{repo}`), which means `path_param!` declarations inside route modules.
  Worth designing at Milestone 2 when the profile page makes it concrete.
- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
  injected as handler arguments. Parses are memoized per request.
- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
  `NotFoundError` to render a branded 404.
- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
- `Body` is a boxed `http_body::Body` used for both requests and responses, with
  `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
  stream both directions without buffering. This is what makes Milestone 5 viable.
