# 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 0 — Skeleton

**Goal:** a Topcoat app that boots, serves one page, reads config from env, and opens
a SQLite pool. No domain logic yet. The point is to learn Topcoat's shape before
committing the architecture to it.

### Steps

- [x] Add `topcoat` 0.5 + `tokio` to `Cargo.toml`
- [x] Get the getting-started hello-world page rendering
- [x] Work out how routing wants the source tree laid out — see
      [routing findings](#routing-findings) below
- [x] Config from env via `envy` (`STEID_*`) — see [runbook.md](runbook.md)
- [x] SQLite pool registered as app context; page reads it via `app_context::<T>(cx)`
- [ ] Install `topcoat-cli` and confirm `topcoat dev` watch/reload works
- [ ] Decide the layering question in [architecture.md](architecture.md#open-question-topcoats-data-access-vs-clean-architecture)
      — this is the one genuinely open design question and it blocks Milestone 1

### Done when

`cargo run` serves a page that renders a value read from SQLite, with config supplied
by env. **Met** — renders the SQLite version, and `STEID_DATABASE_URL` redirects it to
a different file.

### Routing findings

- **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. Worth a `rust-toolchain.toml` if this ever builds on another
  machine.
- `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 — the
  home page sits in `infrastructure/web.rs` per `architecture.md`.
- `module_router!` is Topcoat's recommended alternative, deriving each URL from the
  module tree rather than a path string. **Deferred to Milestone 1.** Steid's URL space
  is parameterised at the root (`/{owner}`, `/{owner}/{repo}`, `/{owner}/keys`), which
  means `path_param!` declarations inside route modules; worth designing once there are
  real routes rather than guessing now.
- 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 — that replaces attempt #2's `not_found.html`.
- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` from attempt #2 is
  gone. `runbook.md` reflects this.

## Backlog

Ordered. Pull from the top.

1. **Milestone 1 — Identity.** Domain model (User, Org, Membership, Actor, Role),
   value objects (Email, PasswordHash), typed IDs, repository ports, Argon2 hashing,
   registration policy, session cookies. Reference implementation exists and was
   solid — port the domain layer, rewrite the web layer.
2. **Milestone 2 — Repo model.** `Repository` entity, `Visibility`, `create_repo` use
   case, 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).
3. **Milestone 3 — Git over SSH.** `GitProtocolServer` port, `GitBinary` adapter,
   embedded russh. Channel-splitting is fiddly; the notes in
   [progress.md](progress.md#ssh-channel-bridging) are hard-won.
4. **Milestone 4 — SSH key auth + authz.**

## Open questions

- **Topcoat is nine days old** (v0.5.0, first release 2026-07-22, breaking changes
  expected). Pin the exact version and expect to chase it. Budget time for churn that
  isn't feature work.
- Does Topcoat's asset/CSS pipeline coexist with an embedded russh server in one
  binary, or does the CLI's watch-and-rebuild model push SSH into a separate process?
  Unresolved — affects the embedded-SSH approach and single-binary deploy.
- Topcoat ships Tailwind without Node. That reopens the design system from attempt #1
  that was dropped purely to avoid an npm build step — see [ui.md](ui.md).
