steid

@jamesgill /

steid/plans/handover-refs.md
6.1 KBCode·Blame·Raw
265e887fix: the rows line up, and a phone keeps the names rather than the extras17h
1# Handover — `refs` (branches and tags pages)
2
3Written for the orchestrator to fold into `progress.md`, `current.md` and `ui.md` at
4merge time. Branch `feat/refs-pages`.
5
6## What shipped
7
8`GET /{handle}/repos/{name}/branches` and `GET /{handle}/repos/{name}/tags`, plus the
9wiring that turns the counts beside the revision switcher and the About sidebar's
10Branches and Tags values into links to them.
11
12- `domain::{BranchRow, TagRow}` — a ref plus what one row of a page shows.
13- `GitQuery::branches()` / `GitQuery::tags()`, in `DiskGitQuery` and `InMemoryGitQuery`.
14 `list_refs` is untouched.
15- `application::browse::{list_branches, list_tags}` returning `RefPage<Row>`, and the
16 private `pin_default`.
17- `infrastructure::web::refs` — the two pages, `branches_url`, `tags_url`, `compare_url`.
18
19## Decisions worth not rediscovering (progress.md)
20
21- **`%(HEAD)` is why the page is one process.** `for-each-ref` marks the branch `HEAD`
22 names with `*`, so the default branch arrives in the same output as the branches
23 themselves. Nothing calls `symbolic-ref` or `default_branch` on the happy path, and
24 the compare link's base ref is read out of the pinned row.
25- **Empty fields must be kept when splitting `for-each-ref` output.** `parse_latest_tag`
26 can afford to filter empty fields because it reads two required ones; a multi-field
27 record cannot. A lightweight tag's `%(*objectname)` is empty, and filtering shifts its
28 date into the commit-id slot. `ref_fields` keeps them and drops only the trailing
29 remainder holding git's own record-ending newline. There is a test for exactly this.
30- **Ordering is git's, pinning is the use case's.** `--sort=-committerdate` /
31 `--sort=-creatordate` cost nothing extra in the process that was being run anyway,
32 and re-sorting in Rust would give the same answer. Pinning the default branch is a
33 display decision — it is the branch a visitor came for and it is *not* reliably the
34 most recently pushed — so it lives in `list_branches` as a stable partition, which is
35 what keeps the date order beneath it intact.
36- **A lightweight tag's `%(contents:subject)` is the commit's subject, not the tag's.**
37 It is only read when `%(objecttype)` says `tag`; otherwise the page would attribute
38 the commit's words to a tag that has none. `annotated` is its own field rather than
39 `message.is_some()`, because an annotated tag may carry an empty message.
40- **An annotated tag's `objectname` is the tag object.** The row reports the peeled
41 `%(*objectname)` so the sha it shows is something a visitor can browse.
42- **`RefPage.repo_is_empty` costs one extra process, only when the list is empty.**
43 "No tags yet" and "nothing pushed yet" are different sentences and only the second
44 wants the push snippet, and a tag list alone cannot tell them apart. `default_branch`
45 answers it, and is asked nowhere else on these pages.
46- **A timeout renders as a page state, not a 500.** `refs.rs::timed_out` matches
47 `Error::GitQuery(_)` with `is_timeout()`. This is the **first** place in the web layer
48 that distinguishes a timeout from a fault; every other page still maps both to
49 `server_error`. If a second page wants it, the helper should move to `web::context`.
50
51## Holes and shortcuts (current.md)
52
53- **No ahead/behind counts on the branches page**, deliberately and by brief. It is a
54 `rev-list` per branch — twenty branches would fork twenty extra processes to decorate
55 one page. It stays out until something keeps git alive between questions. Commented in
56 both `port.rs` and `refs.rs`.
57- **The Compare link points at a page this branch does not contain.** The contract is
58 `/{handle}/repos/{name}/compare/{base}...{head}`, each ref percent-encoded whole so a
59 slash in a name stays inside its segment (`main...feature%2Flogin`). It is rendered on
60 every non-default branch row and there are unit tests on the shape. **If the commits
61 agent's compare route differs, `compare_url` in `refs.rs` is the one place to change.**
62- **The tags page leaves the right-hand side short on purpose** for the archive agent's
63 two download links; nothing is rendered there.
64- **`web::browse::{encode, timestamp}` were widened to `pub(super)`.** No other change to
65 those functions.
66- **`repo_stats` in `repo.rs` gained `handle` and `name` parameters** so the sidebar's
67 counts can be links. One call site.
68- **A repository with commits but no branches** (tags pushed alone) says "No branches",
69 and its tags page still works. Real but exotic; not tested against a live fixture.
70- **Neither page is paginated.** A repository with a thousand branches renders a thousand
71 rows. One process still, but a large page.
72
73## UX notes (ui.md)
74
75- The entry-point table's "Branches, Tags → the counts beside the switcher become links"
76 row is **done**, and so is the note under *The repository page* saying the counts are
77 plain text because the pages do not exist. Both need updating.
78- **Both pages sit under the Code tab.** They are ways into the code rather than places
79 of their own; a tab each for two lists would make the strip advertise plumbing. The
80 cost is that the strip cannot say which of them you are on, which is why each page's
81 heading carries a cross-link to the other (`Tags →` / `Branches →`).
82- **The metadata columns are fixed-width and right-aligned** from `sm` up — a `w-24` date
83 and a `w-16` compare slot, the latter kept empty on the default branch's row. Without
84 them the sha zig-zags down the list, because relative dates vary in width and the one
85 row with no Compare link pulls its whole group right. This is worth keeping in mind for
86 any other list of rows with trailing metadata.
87- **Below `sm` the commit subject and the Compare link are dropped**, not wrapped. The row
88 is a scanning surface and a two-line row destroys the column of names that makes it
89 scannable; at 375px the reserved widths were what pushed the row off the screen.
90- Three empty states, deliberately distinct: an empty repository gets the landing page's
91 push snippet, a repository with history but no tags gets a one-line "No tags yet" with
92 `git push --tags` in mono, and a timeout gets its own panel saying the repository is
93 fine.