steid

@jamesgill /

steid/plans/current.md
8.7 KBCode·Blame·Raw
ab7fea9chore: plans setup1mo
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
2eb8681docs: put git next, plan the repo model24d
7## Active: Milestone 3 — Repo model
8
9**Goal:** repositories exist as records and as bare git repos on disk, and they appear
10on the profile. No git protocol yet — that is milestone 4. This milestone fills the
11Repositories section and gets the storage layout right before anything serves it.
12
13**Out of scope:** clone, push, browsing a tree, README rendering, forks, stars.
14Deleting a repo — worth having, but it makes the filesystem/database consistency
15problem twice as interesting, so not in the first pass.
16
17### Steps
18
c6d74a8docs: record the repo model decisions24d
19- [x] Domain: `RepoId`, `RepoName`, `Visibility` (Public/Private), `Repository`
c5b3ff5feat: repository persistence24d
20- [x] Domain: `RepoRepository` port — `find_by_id`, `find_by_org_and_name`,
2eb8681docs: put git next, plan the repo model24d
21 `list_by_org`, `save`
c5b3ff5feat: repository persistence24d
22- [x] Infrastructure: in-memory + SQLite implementations, migration
02eb2e4feat: GitStorage port and DiskGitStorage24d
23- [x] Application: `GitStorage` port — `init_bare`, `remove`, `repo_path`
24- [x] Infrastructure: `DiskGitStorage`, shelling out to `git init --bare`
11e7a39feat: create_repo use case24d
25- [x] Application: `create_repo` use case — owner only, validates, creates record and
2eb8681docs: put git next, plan the repo model24d
26 bare repo
27- [ ] Application: `list_repos` / `view_repo` read models — visibility-aware
28- [ ] Web: `/{handle}/repos/new` form, `/{handle}/repos/{name}` page
29- [ ] Web: the profile's Repositories section lists what the viewer may see
30- [ ] `/api/users/{handle}/repos`
ab7fea9chore: plans setup1mo
31
32### Done when
33
2eb8681docs: put git next, plan the repo model24d
34The owner creates a repo through the UI, a bare repo appears at
35`{data_dir}/{handle}/{name}.git`, and it is listed on the profile. A private repo is
36invisible to a signed-out visitor. `git clone` does **not** work yet — that is
37milestone 4.
38
c6d74a8docs: record the repo model decisions24d
39### Settled
2eb8681docs: put git next, plan the repo model24d
40
c6d74a8docs: record the repo model decisions24d
41- **Repo name rules:** `OrgName`'s, plus `.` and `_` for names like `.github` and
42 `foo.js`. Lowercased, max 100. Also rejects a name of nothing but dots and any name
43 ending `.git` — the first is traversal, the second would live at `foo.git.git`.
44- **Reserved repo names:** `import`, `new`, `search`. Only names directly under
45 `/{handle}/repos/` can collide.
46- **Visibility defaults to public**, matching a portfolio-first product.
47- **Repositories carry an optional description**, capped at 300 characters — a sentence
02eb2e4feat: GitStorage port and DiskGitStorage24d
48 for the profile listing, not a README. Kept deliberately: portfolio-first is the
49 tie-break, and this milestone's own "Done when" puts repositories on the profile, so
50 the consumer is inside the milestone rather than hypothetical. The per-repo analogue
51 of `Organization::bio`.
c5b3ff5feat: repository persistence24d
52- **`list_by_org` returns every repository regardless of visibility.** Filtering is an
53 authorization decision and belongs to the use case, so the page and `/api` cannot end
54 up applying different rules. The cost is that a private repo is briefly in memory
55 before being filtered, which is fine in-process.
3954b45docs: record milestone 2 phase 125d
56
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
57### Open
58
02eb2e4feat: GitStorage port and DiskGitStorage24d
59Nothing open. `GitStorage`'s shape and how git is invoked are recorded in
60[0006]decisions/0006-git-binary-behind-narrow-ports.md.
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
61
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
62### Watch for
d7b99d9docs: record milestone 0 progress and routing findings1mo
63
11e7a39feat: create_repo use case24d
64- **An orphaned directory is indistinguishable from a duplicate to the visitor.**
65 `create_repo` maps `GitStorageError::AlreadyExists` to "that name is taken", which is
66 true from outside but hides the inconsistency from the operator. There is no logging
67 story yet for it to surface in. The durable fix is the reconciliation sweep in
68 [architecture.md]architecture.md#db-plus-filesystem-writes.
69- **The duplicate check races.** Two concurrent creates of the same name can both pass
70 `find_by_org_and_name`; the loser is then stopped by `init_bare` or, failing that, by
71 the `unique (org_id, name)` constraint — which surfaces as an opaque storage error
72 rather than "name taken". Correct, just ugly, and single-user for now.
73
2eb8681docs: put git next, plan the repo model24d
74- **The database and the filesystem cannot share a transaction.** Creating a repo
75 writes a row and a directory. Neither previous attempt solved this properly — see
76 [architecture.md]architecture.md#db-plus-filesystem-writes. A compensating delete is
77 good enough to ship, but write down that an orphaned directory is possible if the
78 process dies between the two, rather than rediscovering it.
79- **Path traversal.** `{data_dir}/{handle}/{name}.git` is built from user input. A name
80 containing `..` or `/` must be impossible before it reaches the filesystem, and
81 `RepoName` is the place to make it impossible rather than sanitising at the call site.
82- **Visibility is an authorization decision**, so it belongs in the use case. A private
83 repo must be absent from listings, not merely unlinked — and `/api` must agree with
84 the page.
02eb2e4feat: GitStorage port and DiskGitStorage24d
85- **`git` is a dependency of the test suite too**, not only of the runtime —
86 `DiskGitStorage`'s tests run real `git init`. A machine without `git` fails
87 `cargo test`, not just the app.
88- **A handle rename is a directory move.** The layout is keyed by handle for
89 legibility, so whenever renaming arrives it has to move the tree; it cannot be a row
90 update. Nothing renames handles today.
91- **Bare repos created on macOS carry `ignorecase = true`** in their config, because
92 git probes the filesystem at init. Correct where it was created, wrong if the data
93 directory is ever moved to Linux. A migration gotcha, not a bug.
94- **An orphaned directory blocks re-creating that name.** `init_bare` refuses rather
95 than adopting what is already there, and repo deletion is out of scope this
96 milestone, so clearing one is a manual `rm` for now.
076dbc9docs: close milestone 1, open milestone 21mo
97
f0444b7docs: plan milestone 2 in two phases1mo
98### Carried over — small, unblocked
076dbc9docs: close milestone 1, open milestone 21mo
99
2eb8681docs: put git next, plan the repo model24d
100- **Fonts are not loaded.** The theme names Geist and IBM Plex Mono; both fall back
101 today. Topcoat's `font-fontsource` feature handles it.
102- **Light mode is untested.** The palette defines it; nobody has looked at it.
f0444b7docs: plan milestone 2 in two phases1mo
103- **No rate limiting** on `/auth/login` or `/auth/setup`.
076dbc9docs: close milestone 1, open milestone 21mo
104- **`sweep_expired` is never called**, so expired session rows accumulate. Expiry is
105 enforced on read, so this is tidiness, not a hole.
2eb8681docs: put git next, plan the repo model24d
106- **CSRF.** `SameSite=Lax` covers the common case. Forms now exist, so this is decidable
107 rather than hypothetical.
ab7fea9chore: plans setup1mo
108
109## Backlog
110
111Ordered. Pull from the top.
112
2eb8681docs: put git next, plan the repo model24d
1131. **Milestone 4 — Git over HTTP.** `git http-backend` subprocess, PATs over HTTP
114 Basic. See [0001]decisions/0001-git-over-http-not-ssh.md. The `body_limit` cap will
115 reject large pushes until raised.
02eb2e4feat: GitStorage port and DiskGitStorage24d
1162. **Milestone 5 — Repo browsing.** Tree, blob, commit log. **Start with domain value
117 objects**`ObjectId`, `RefName`, `TreeEntry` — before any adapter. A query port
118 returning `String`s is an anaemic pass-through that pushes validation into the page.
119 Also the point to measure fork/exec cost per page view, and to reconsider `gix` for
120 the read path ([0006]decisions/0006-git-binary-behind-narrow-ports.md).
2eb8681docs: put git next, plan the repo model24d
1213. **Milestone 6 — Writing.** Posts, markdown, `/{handle}/posts/{slug}`. Still open
122 whether writing or projects/showcases is the better first portfolio feature.
ab7fea9chore: plans setup1mo
123
124## Open questions
125
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
126- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
127 by its own authors). Expect churn that isn't feature work.
ca76e1bdocs: fix milestone cross-references after the reorder24d
128- Body size limits will reject large pushes at Milestone 4 — `topcoat-router` has a
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
129 `body_limit` layer that needs raising on the git routes. Recorded here because it
130 will surface as a confusing failure rather than a clear one.
131- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
132 dropped purely to avoid an npm build step — see [ui.md]ui.md.
133
134## Routing findings (Milestone 0)
135
136- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
137 silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
138 stable is now 1.97.1.
139- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
140 so pages can live in any module. Layering is our choice, not the framework's.
141- `module_router!` derives each URL from the module tree rather than a path string.
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
142 Still deferred. Application routes now group cleanly (`auth/login`, `api/me`), but
143 handles sit at the root ([0004]decisions/0004-root-handles-grouped-routes.md), so a
144 parameterised root segment still has to coexist with static ones. Worth checking how
145 `module_router!` handles that before committing to it.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
146- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
147 injected as handler arguments. Parses are memoized per request.
148- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
149 `NotFoundError` to render a branded 404.
150- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
151- `Body` is a boxed `http_body::Body` used for both requests and responses, with
152 `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
ca76e1bdocs: fix milestone cross-references after the reorder24d
153 stream both directions without buffering. This is what makes Milestone 4 viable.