OXYGENHUB
# e5e73109dbf1 oxygen / docs / backlog-overnight-2.md
33.5 KiB · Markdown · last changed Backlog: repository overview changes (oxygenhub#18) 1 month ago renderedsource blame history raw
1
# Overnight backlog 2 (2026-09-28 to 2026-09-30)
2
3
Andy's brief: usability and features on Oxygenhub, as many features as
4
possible in the VS Code extensions, and a stable, mature `oxy` client.
5
Work top to bottom, but interleave the three tracks: after each hub or
6
extension item, take the next oxy stability item if one is open. Each
7
item is done when its tests pass, the gate is clean, it is committed with
8
`oxy` as Andy and pushed to Earth. Log each iteration at the bottom.
9
10
## Hub (oxygenhub): usability and features
11
12
- [x] H1 Dashboard for signed-in users: my repositories, proposals needing me, issues assigned to me, recent activity across repositories I can see (pushes, proposals, issues, releases).
13
- [x] H2 Empty-repository quickstart: exact commands for this repository (remote add, token, CA download, push) and for starting from nothing.
14
- [x] H3 Theme switch (dark, light, auto) in the header and under Settings, remembered in the cookie; check every page in light mode.
15
- [x] H4 Profile and organisation pages: generated identicon avatar, display name, bio, linked keys, repositories, recent activity, organisation members and teams.
16
- [x] H5 Diff view: split (side by side) toggle remembered per user, collapse per file, hide whitespace changes, intra-line word highlighting.
17
- [x] H6 Forks: fork button, fork creation (store copy, forked_from), fork list, "propose to upstream" from a fork.
18
- [x] H7 Stars and watch counts, explore sorting (activity, stars, name) and filtering, repository topics.
19
- [x] H8 Proposal and issue lists: filters (author, label, target, assignee), sort orders, pagination, counts in tabs.
20
- [x] H9 File view: file tree sidebar, `y` for a permalink to the state, line range copy, blame age shading and "blame the state before this change".
21
- [x] H10 Compare page with bookmark and tag pickers and a "propose this" button.
22
- [x] H11 Help pages: getting started (install oxy, clone, push, tokens, CA, delegating to the hub), keyboard shortcuts, glossary for people coming from GitHub.
23
- [x] H12 Password reset and email verification (email when SMTP is set; admin-issued reset link otherwise).
24
- [x] H13b Uploaded profile pictures and organisation logos (Andy, oxygenhub#11): upload, validate by content, re-encode and crop, identicon fallback, remove.
25
- [x] H13 Notifications: per-thread mark read, filter by kind and repository, "participating" versus "watching".
26
- [x] H14 Accessibility and mobile pass: focus styles, skip link, landmarks, responsive tables and tabs, contrast in both themes.
27
- [x] H15 Organisation webhooks page; admin storage and repository maintenance page (sizes, oxy gc).
28
- [x] H16 Performance: cache rendered Markdown and highlighted files by object hash; measure the heaviest pages on Earth before and after.
29
- [x] H17 Issue API: labels, assignees and milestone on create/edit; create labels (oxygenhub#3).
30
- [x] H18 `refs #N` in change messages appears on the issue's timeline (oxygenhub#4).
31
- [x] H19 Projects: boards of issues and proposals across an owner's repositories (oxygenhub#5).
32
- [x] H21 Projects API: list, create, board, cards and columns over /api/v1 (oxygenhub#15).
33
- [x] H20 Issue Markdown resolves against the default bookmark, not `main` (oxygenhub#6).
34
35
## VS Code (oxygen-vscode, oxygenhub-vscode)
36
37
- [x] V1 oxygenhub-vscode: proposal files in native diff editors (target state against projected state through a content provider), with review threads in them.
38
- [x] V2 oxygenhub-vscode: notifications count in the status bar and an inbox quick pick that opens the item.
39
- [x] V3 oxygenhub-vscode: completion of `#N` and `@user` in the Oxygen commit box and in comments; hover on `#N` shows the title.
40
- [x] V4 oxygenhub-vscode: create an issue from the editor selection or a TODO line, with a permalink.
41
- [x] V5 oxygenhub-vscode: releases view; draft a release from a tag.
42
- [ ] V6 oxygen-vscode: history graph actions for split, squash and rebase (oxy history commands), with confirmation and undo hint.
43
- [ ] V7 oxygen-vscode: commit box polish: amend toggle, message history, signed/unsigned indicator, warning on conflict markers.
44
- [ ] V8 oxygen-vscode: Open on Remote in editor gutter context (line permalinks), copy permalink.
45
- [ ] V9 Both: README screenshots-free walkthroughs, settings documentation, CHANGELOG per release.
46
47
## oxy: stability and maturity
48
49
- [x] O1 Property tests (proptest) that applying the same changes in any order gives the same state, over random edit sequences on several files.
50
- [x] O2 `oxy fsck`: verify every object's hash, every change's references and signatures, the op log chain, and report precisely.
51
- [x] O3 Crash safety audit: every write goes through write_atomic or the store's batch; a test that interrupts commit and push at each step and proves the repository still opens and fscks.
52
- [x] O4 Benchmarks with thresholds: 10 000 files, 5 000 changes; status, commit, log, blame, push, clone; record numbers in docs.
53
- [x] O10a Heads at scale (first part of O10): `State::maximal` compared every pair of heads (quadratic; about 1.5 s per call at 4 500 heads, called three times per commit); now one walk. Commit reuses the snapshot's base state instead of loading it again.
54
- [x] O10b Parallel snapshot work: rendering the base state's files, building file graphs from a checkpoint, and reading working-copy files now run on every core (rayon).
55
- [x] O12 `log_for_path_in` and `blame_in` rebuild and re-render the whole state on every call, and walk every change: take an already loaded state, stop at the first change touching the path when only the newest is wanted, and benchmark it (found in H16).
56
- [ ] O10c Snapshot without the whole base (rest of O10): load only the file graphs a snapshot needs (a checkpoint split per file or indexed), skip reading and rendering files whose stat matches a manifest written for the same base, cache materialized states. Needs a spec change to the checkpoint format; target spec §25.
57
- [x] O5 Concurrency: two processes committing, snapshotting and pushing on one repository; locks hold, nothing is lost.
58
- [x] O6 Error messages audit: every user-facing error says what to do next; `oxy doctor` checks identity, config, remotes, CA files, hub reachability.
59
- [x] O7 Shell completions (`oxy completions bash|zsh|fish`) and man pages generated from clap.
60
- [x] O8 Git round trip on real history: import a scratch mirror of tetriblox (never the live repo), export it back, compare trees at every commit.
61
- [x] O11 Line placement found by O8: in 5 of 539 tetriblox commits (all non-merge; e.g. 43de61fac554 src/banked/code_bank_07.c, 021ad78791a2 code_bank_03.c, 2721670b49af code_bank_02.c, 435fbe1d5fac scripts/x16_unchanged.py, d17c19b22748 test/test_memory_guards.py) one line lands elsewhere than in Git with no conflict reported, so an insertion is anchored between atoms that are not adjacent in the base. Reproduce with `oxy git verify` on a scratch mirror; find which edit of the importer's diff (build_from_entries/text_edits) anchors non-adjacently, add a debug assertion for anchor adjacency, fix, re-verify.
62
- [x] O9 Portability review for macOS and Windows: path separators, executable bits, case-insensitive file systems, CRLF; tests where it can be exercised on Linux.
63
64
## Log
65
66
- 2026-09-28 H1 done: signed-in dashboard at `/` (review queue, own proposals, assigned issues, repositories, activity feed filtered to visible repositories); test `dashboard_shows_what_needs_the_viewer`; deployed to Earth.
67
- 2026-09-28 O1 done: `crates/oxy-core/tests/commutativity.rs` (proptest, 32 cases): diverging clones with random edits, rebuilt in shuffled and random dependency orders, checkpoint round trip, and push in random order to a fresh clone; all give identical checkpoints, heads and trees. About 80% of generated cases conflict. No defects found. Workspace tests all green (oxy-core 129 + new).
68
- 2026-09-28 H2 done: empty repositories show a quickstart (remote named after the host, e.g. `earth`; CA download and `remote.X.ca` when self-signed; token placeholder; push; start from nothing; `oxy git import`); readers get a plain notice. Tests: `empty_repository_shows_a_quickstart` and a remote-naming unit test; hub spec §6 row updated; deployed.
69
- 2026-09-28 O2 done: `oxy fsck [--json]` (objects, op log through all parents, references and change closure with identities/blobs/chunks/signatures, bookmark states, checkpoints re-derived, ref and tag attestations verified); spec Draft 0.8h §21.1, guide updated. Test `fsck_passes_a_sound_repository_and_names_each_fault` (corrupt object, missing change, missing blob, broken op head). Clean on oxygen, oxygenhub, oxygen-vscode, oxygenhub-vscode, hellooxy. Release binary rebuilt.
70
- 2026-09-28 V1 done: hub API `GET /proposals/{n}/files` and `/proposals/{n}/content/{base|head}/{path}`, and anchored comments now carry their current `line` (deployed). oxygenhub-vscode 0.2.0: proposals expand to files, native diff editor per file, View Changes multi-diff, review threads on the projected side; installed in Default and Rust profiles (Developer: Reload Window to pick it up). Tests: hub api test extended; extension pass 6 fail 0 against a real hub.
71
- 2026-09-28 O3 done: new `oxy_core::durable` (unique temp, fsync, rename, directory fsync) for every durable write; **real bug fixed**: inside a write batch the op head moved before the batch (including the new op object) was on disk, so a crash there left an unopenable repository; `Store::flush` now runs before the head moves. Test stops commit (20 steps) and push (32 steps) at every step: both repos open, fsck clean, command retries. Spec §13.6. Hub rebuilt and redeployed with the fix. Not covered: working-copy file writes during checkout are still plain writes (user files, recoverable by snapshot).
72
- 2026-09-28 H3 done: header theme switch (dark/light/auto) and Settings → Appearance, theme cookie; auto follows the system live. Light-mode pass by headless Chrome screenshots of dashboard, overview, file, proposal, issue, settings, changes: found and fixed highlighted code drawing boxes in both themes (syntect scope `comment` colliding with the page `.comment` class; classes now `sy-` prefixed) and the band ADMIN link unreadable in light. Test `theme_is_chosen_in_settings_and_remembered_in_a_cookie`; hub spec §6.1; deployed. The code editor picks its theme at load, not live.
73
- 2026-09-28 O4 done: harness `crates/oxy-core/tests/bench.rs` (ignored; run by hand) and `docs/benchmarks.md`. Full run on disk took 5 h 14 min. **Andy: status and commit are slow at scale**: at 10 000 files / 5 000 changes a commit averages 3.8 s, a 10-file commit 9.1 s, a clean status 3.0 s, blame 2.9 s, peak memory 1.4 GiB; push 0.8 s, clone 3.4 s, log 0.4 s are fine. Cause: every snapshot loads and materializes the whole base state. New item O10 (moved ahead of O5) fixes it.
74
- 2026-09-28 V2 done: hub API `GET /notifications`, `POST /notifications/{id}/read`, `POST /notifications/read` (scope user:read; deployed). oxygenhub-vscode 0.3.0: status-bar bell with unread count across hubs, new-notification messages, Open Inbox quick pick that marks read and opens proposals/issues in the editor. Installed in both profiles (Reload Window). Tests: hub api test extended; extension pass 7 fail 0.
75
- 2026-09-28 O10a done: `State::maximal` was quadratic in heads (4 500 heads in the benchmark); now one walk, with a property test against the old definition. Commit reuses the snapshot's base. At 10 000 files / 5 000 changes: commit 3.8 s to 0.86 s, 10-file commit 9.1 s to 0.86 s, status 3.0 s to 0.80 s, blame 2.9 s to 0.81 s, memory 1.4 to 1.1 GiB; benchmark run 5 h 14 min to 1 h 12 min. Hub redeployed with it. O10b (manifest fast path, lazy base) remains for the per-file cost.
76
- 2026-09-28 V3 done: oxygenhub-vscode 0.4.0: `#` and `@` completion in the Oxygen commit box and comment editors, hover on `#N`; repository from the SCM root. Parsing tested in Node (pass 10, fail 0). Installed in both profiles.
77
- 2026-09-28 O10b done: rayon for materialize, checkpoint file graphs and working-copy reads. At 10 000 files: clean status about 800 to 460-580 ms, commit 861 to 550-605 ms. Remaining cost is loading the whole base (O10c, needs a checkpoint format change in the spec, so left for Andy to agree). Hub redeployed.
78
- 2026-09-28 H4 done: `/{owner}` profile (identicon, display name, bio, location, website, keys, organisations, visible repositories, activity by that person; never the email) and organisation page (description, members and roles, teams with sizes, repositories, activity); `/avatar/{name}` identicons also in the header and feeds; migration 0008 (bio, website, location) and Settings fields (http(s) websites only); `avatar` and `help` reserved. Test `profile_and_organisation_pages`; screenshot checked; deployed (andymccall profile 200 on Earth).
79
- 2026-09-28 O5 done. **Real bug fixed**: concurrent commands overwrote `.oxy/ops/head`, so the loser's operation (and its commit on the bookmark) vanished; a two-workspace test lost about half its commits every run. The head now moves by compare-and-swap under a short `.oxy/ops/lock`, and a mover that lost the race records a merge op (two parents, three-way merged view; bookmarks keep both sides' heads), as spec §12.1 described; undo refuses on a moved head. Tests: concurrent two-workspace commits with pushes lose nothing (5/5 runs), deterministic merge of two ops from one parent; two real oxy processes x 25 commits keep all 50 (39 merges). Spec §12.1 updated. Hub redeployed.
80
- 2026-09-28 H5 done: word-level marks within paired changed lines, `?w=1` hides whitespace-only changes (change, compare, proposal files), collapsible files (over 400 rows start closed), unified/split toggle remembered in a `diff` cookie, collapse/expand all. Tests: two renderer unit tests and `diffs_mark_words_hide_whitespace_and_collapse`; split view checked by screenshot; hub spec §6.8.1; deployed.
81
- 2026-09-28 O6 done: `oxy doctor [--offline] [--json]` (op log and stale locks, identity object/secret/expiry, author defaults, each remote: plain HTTP, token, CA file, reachability and repository id) with a next step per problem; every failed remote request now carries a hint (untrusted certificate, 401, 403, 404, refused, DNS, timeout) and transport errors no longer repeat the URL; clearer messages for different-repository remotes, pull-first pushes (still contains "pull first", which oxygen-vscode matches), unauthorized keys, and no repository. Tests: hint table and doctor scenarios. Clean on oxygen and oxygenhub checkouts. Spec §21.2, guide. Hub redeployed.
82
- 2026-09-28 H6 done: fork button and form, `Repos::fork` (new id, hard-linked objects, bookmarks copied, no policy), migration 0009 `forked_from`, forks list, "forked from" and fork count on the overview, "propose to upstream" link. Test `fork_and_propose_upstream` (fork, fsck, clone the fork, push a bookmark, propose upstream, files tab shows it). Hub spec §6.3 row; deployed.
83
- 2026-09-28 O7 done: `oxy completions SHELL` (clap_complete) and `oxy man [--dir]` (clap_mangen 0.3, 104 pages); help text written for 112 bare commands and arguments with a test that every one has help; oh-my-bash plugin loads bash completions; guide section. Tests `cli_docs` (every shell, pages, argument help on the page). Andy: `oxy completions bash > ~/.local/share/bash-completion/completions/oxy` to enable.
84
- 2026-09-28 O7 note: the O7 commit went in before clippy finished (two lints in the new help test); fixed in the next commit. The gate now runs to completion before every commit.
85
- 2026-09-28 H7 done: stars (toggle, counts), watcher counts on the overview, repository topics in settings (validated) shown as chips, Explore filter by text and topic and sort by activity/stars/name with per-row stars and topics; migration 0010. Tests: topic parsing unit test and `stars_topics_and_explore`. Gate fully green before commit; deployed.
86
- 2026-09-28 O8 done. `oxy git verify GIT_DIR` checks an import commit by commit (content, exec bits, symlinks, pins; conflicts named with their changes; first differing line). On a scratch mirror of tetriblox (539 commits, 208 merges, 880 gitlinks; the live repo was only read by `git clone --mirror`): import 36 s, tip tree round-trips exactly, and verification found **real core bugs that also affect ordinary commits**, now fixed: (1) insert after a line, then delete that line, left a delete/insert conflict (deletions now depend on the inserters of the lines right after them); (2) a binary or submodule reverted to an earlier blob lost it, and replacements had no dependency (now depend on the change that set their old blob; causal supersession, legacy records keep the old rule); (3) merges of independently replaced binaries/submodules stayed conflicted (importer resolves to the merge's blob). Mismatching commits went from 50+ (verification stopped early) to 5, now O11. Tests: 3 regressions (sequential insert/delete, binary revert, Git binary merge + verify). Spec Draft 0.8i §8.1, §9.3, §23. Hub redeployed.
87
- 2026-09-28 H8 done: open counts on the Proposals and Issues tabs (from the repository lookup), issue `author:` filter and sorts (newest/oldest/updated/comments), proposal filter (`author:`, `label:`, `target:`, words) with labels shown and sorts (updated/newest/oldest), 25-per-page pager keeping filter and sort. Test `lists_filter_sort_page_and_count`; spec §6.3 rows; deployed.
88
- 2026-09-28 O9 done: `portable::Platform` (detected, overridable in tests); no exec bits: snapshot keeps the recorded bit (it was recording a mode change on every Windows snapshot); no symlinks: a plain file holding the target stays the symlink (spec §7.7 required it, the code converted it); checkout lists case collisions and Windows-invalid names on affected platforms and `oxy doctor` does everywhere; CRLF already handled by the eol attribute; separators verified (components joined with /). Tests simulate Windows and macOS. Follow-up: normalise `\` in paths typed on Windows command lines. Spec §7.7. Hub redeployed.
89
- 2026-09-28 V4 done: oxygenhub-vscode 0.5.0 "New Issue from Selection" (context menu): quotes the selection under a permalink (head-pinned when single-headed and saved), TODO/FIXME lines suggest the title and can be annotated `TODO(#N)`, then the issue opens; line-range links. Tests pass 13 fail 0. Installed in both profiles.
90
- 2026-09-28 O11 done: root cause in `text_edits`: similar's capture_diff_slices runs a Compact hook that slides deletions along equal lines and can leave an insertion with an earlier old index, anchoring a block one line early (a brace on the wrong side). Now uses the diff with only the Replace hook. Randomized replay test (2 000 repetitive files; fails on the old code). tetriblox scratch import verifies all 539 commits, 95 585 files. Note: that commit also picked up Andy's new docs/xenon_spec.md and fix.py, fix2.py, format_xenon.py from the working copy; left for Andy to decide.
91
- 2026-09-28 UI request from Andy (between items): repository page centred with the sidebar, README as wide as the file table, clone command in the top bar; repository, organisation and account settings get a sticky list of sections on the left and equal-width boxes. Screenshot-checked, deployed.
92
- 2026-09-28 V5 done: oxygenhub-vscode 0.6.0 Releases view (releases per repository with latest/pre-release, assets with size and downloads, source archives) and Draft Release…: pick an unreleased tag, title from the tag message, notes generated from the changes since the newest full release's tag, pre-release suggested from the tag name. End-to-end test tags with oxy and releases through the client. Tests pass 15 fail 0; extension spec §8.2. Installed in both profiles.
93
- 2026-09-28 H9 done: file page tree sidebar (path expanded, 200 per directory then "N more", hidden under 900 px); state addresses `h1+h2` (sorted full hashes, at most 32) so any state has a permalink; `y` on file and tree pages switches to it; a line-range bar with Copy lines and Copy permalink; blame shaded in ten age steps and each run links "blame before this change" (the state of its dependencies). Tests: view unit tests (sidebar order and limit, state addresses) and browse end to end. Hub spec §6.3, §6.6, §6.13. Deployed; health ok.
94
- 2026-09-28 Requests from Andy (between items):
95
  - **OxygenHub naming.** The name is written OxygenHub everywhere people read it (hub, both extensions: oxygenhub-vscode 0.6.1, oxygen-vscode 0.7.1).
96
  - **OxygenFlow M0, built and running on Earth.** Xenon was renamed to OxygenFlow, in the separate oxygenflow repository. It has:
97
    - flow-core: KDL pipeline, validation with lines, canonical CBOR, receipts, runner API types
98
    - flow-init, as the guest's PID 1
99
    - the Firecracker microVM tier
100
    - `flow check/run/rootfs/runner`
101
    - a microVM conformance test
102
  - **Hub side.** Pushes start runs, a runner API with repo-scoped read-only job tokens, jobs mirrored as `flow/<job>` checks, and a Flow tab with the SVG job graph and live logs. Hub spec §13.2; migration 0011; tests/flow.rs.
103
  - **Earth.** Firecracker v1.17.0, kernel 6.1.155 and the Alpine 3.24.2 rootfs, pinned in ~claude/flow. `flow-runner.service` is hardened. `hub.toml` gained `[flow]` limited to `andymccall/*` (backed up as hub.toml.before-flow).
104
  - **Result.** oxygenflow's own pipeline passes (runs 1 and 2). Andy's spec is committed, renamed to OxygenFlow, with a README.
105
  - **oxy-core.** `tls_client_config` is public.
106
- 2026-09-28 Flow state stamps instead of CI badges (oxygenhub#1, closed by the change):
107
  - `/flow/stamp/{ref}` is an SVG of the newest run of exactly that state, with receipt hashes in its metadata. For bookmarks and tags it redirects to the stamp of the current state.
108
  - In READMEs, `[![flow](.flow/stamp)](.flow/run)` resolves against the state being viewed.
109
  - Fixed oxygenhub#2 on the way: README images pointed at the HTML file page. There is now a `/raw/{ref}/{path}` route.
110
  - The oxygenflow README shows its stamp (oxygenflow#1).
111
  - From now on every item gets an issue on Earth and its commits say closes/refs #N.
112
  - Gaps the workflow exposed became H17 to H20 (oxygenhub#3 to #6).
113
- 2026-09-28 H10 done (oxygenhub#7, closed by the change):
114
  - The compare page has base and head pickers (bookmarks and tags in a datalist; any change or state can be typed), a swap link, `/compare?base=&head=` as the form target, and `/compare` alone comparing the newest other bookmark against the default.
115
  - **Propose this** shows for writers when both sides are bookmarks and head has changes; it becomes "proposal #N is open" when one exists.
116
  - Tests in browse and proposals. Hub spec §6.3. Deployed.
117
- 2026-09-28 H11 done (oxygenhub#8, closed by the change):
118
  - `/help` is a sectioned page with getting started, identity and key linking, pushing, cloning, proposals, delegating to the hub, pipelines and state stamps, the keyboard, and a GitHub glossary.
119
  - Every command carries this hub's address, remote name and certificate step.
120
  - It is linked from the band, the footer and the `?` sheet.
121
  - Browse test; hub spec §6.3. Deployed.
122
- 2026-09-28 H12 done (oxygenhub#9, closed by the change). Spec §5.1.1 first.
123
  - **Account links.** 32 random bytes, stored as a BLAKE3 hash, single use and expiring. Migration 0012.
124
  - **Verification.** Sent on registration and on every address change (48 h). It only verifies the address it was sent to. Settings shows the status and can resend: one every 2 minutes per address, 10 an hour per account.
125
  - **Reset by email.** By username or verified email (1 h, one every 2 minutes). The answer is identical whether or not anything matched, and mail goes out in the background so timing leaks nothing.
126
  - **Reset by an administrator.** From /admin (24 h, shown once, audited).
127
  - **Using a reset link.** It rotates the session salt (every session ends) and voids other links. TOTP stays on; banned accounts are refused.
128
  - **Mail.** A shared `mail` module with `file://` for development. Notification mail only goes to verified addresses.
129
  - **Tests.** tests/recovery.rs covers the whole flow through a file mailbox.
130
  - **Earth.** Earth has no SMTP, so resets there go through the admin link. Existing addresses count as unverified until verified.
131
- 2026-09-28 H13 done (oxygenhub#10, closed by the change):
132
  - Notifications record their thread, repository and reason (participating or watching) and can be marked done. Migration 0013 backfills threads and repositories from the URLs.
133
  - The inbox shows one row per thread, with updates and new counts.
134
  - Filters: inbox, unread and done views, reason, kind family and repository, with counts.
135
  - Per-thread read, unread and done. Done threads return when a new event arrives.
136
  - Opening a row marks its thread read.
137
  - `notify::add` takes a struct.
138
  - Tests extended in tests/issues.rs. Hub spec §10.
139
  - The API keeps per-item read; per-thread actions in the API are a follow-up for the VS Code inbox.
140
- 2026-09-28 Andy asked for uploaded profile pictures and organisation logos: added as H13b (oxygenhub#11), taken next.
141
- 2026-09-28 H13b done (oxygenhub#11, closed by the change):
142
  - Upload a picture under Settings › Profile › Picture, or an organisation logo under its settings (owners only).
143
  - PNG, JPEG or WebP, recognised by content; at most 5 MiB and 8 000 px a side.
144
  - Every upload is decoded, centre-cropped, scaled to 400×400 and re-encoded (PNG if it has transparency, JPEG otherwise), so uploaded bytes are never served.
145
  - `/avatar/{name}` serves the picture with an ETag, falling back to the identicon; Remove restores the identicon.
146
  - Avatar CSS no longer pixelates photos.
147
  - Unit tests for processing and refusals; end-to-end test in tests/accounts.rs. Hub spec §6.3. Deployed.
148
  - This machine's disk filled up (0 bytes free) during the build. I removed the regenerable `target/debug/incremental` caches of oxygenhub and oxygen (33 GB) and build with CARGO_INCREMENTAL=0 from now on.
149
- 2026-09-28 H14 done (oxygenhub#12, closed by the change):
150
  - Keyboard and screen readers: a focus ring on everything focusable; a skip link to `<main id="main">`; named landmarks (Account, Site, Repository and Settings navigation); `aria-current` on the current tab.
151
  - Contrast: WCAG AA in both themes, enforced by tests/contrast.rs over the CSS variables.
152
    - Light theme: mute, acc, ok, now and the string, function and comment syntax colours adjusted.
153
    - Dark theme: comment colour adjusted.
154
    - Text no longer uses `--dim`.
155
  - Under 760 px: tab bars scroll on one line, the tree drops its message column, wide content scrolls inside its panel, sectioned pages stack, and the header search hides.
156
  - Checked at 390 px on Earth. Hub spec §6.13.
157
- 2026-09-28 H15 done (oxygenhub#13, closed by the change):
158
  - **Organisation webhooks.** A Webhooks section in organisation settings: add, ping, enable or disable, delete, deliveries and redeliver.
159
    - Repository and organisation webhooks now share one module (`web/webhooks.rs`) and one template macro.
160
    - Every action checks that the hook belongs to the scope it is managed from.
161
  - **Storage.** `/admin/storage` lists each repository's size, loose objects and packs. Maintenance runs `oxy gc` (grace period kept, write lock held) on one repository or all, and is audited.
162
  - Tested in tests/accounts.rs. Hub spec §6.3, §12.2. Deployed.
163
  - Earth's repositories can be maintained from /admin/storage now; I have not run it on Andy's data.
164
- 2026-09-28 H16 done (oxygenhub#14, closed by the change). Measured on Earth first (oxygenhub/docs/performance.md).
165
  - The raw bytes of heavy files took 5 ms, but their pages took 196 to 428 ms: syntax highlighting, Markdown rendering, and above all the file's "last change", found by `log_for_path_in` rebuilding the state and reading every change on each view.
166
  - Now cached, in memory-bounded LRUs keyed by content or by (state, path): rendered Markdown, highlighted files, last change and blame. Labels are still computed fresh.
167
  - `?plain=1` takes the lean path.
168
  - Warm views: spec.md 196 → 8 ms, repo/mod.rs 400 → 18 ms, state.rs 317 → 14 ms, blame 428 → 23 ms.
169
  - Unit test for the LRU; browse test checks the second view hits the cache. Hub spec §16.3.
170
  - Follow-up for oxy-core: `log_for_path_in` should reuse a loaded state instead of rebuilding it.
171
- 2026-09-28 O12 done (oxygen#1, closed by the change; the first issue in the oxygen repository):
172
  - oxy-core has `log_of(state)`, `log_for_path_of(state, path, limit)` and `blame_of(state, tree, path)`. They find the path in the tree view alone, with no full render, and the walk stops at `limit`.
173
  - `log_for_path_in` and `blame_in` wrap them and load once instead of twice.
174
  - Test `history_and_blame_of_a_loaded_state_match_the_full_walk`; gate green; release binary rebuilt.
175
  - The hub uses them for last change, blame and tree rows.
176
  - Cold file pages on Earth: 196/400/317/428 ms (before any caching) → 146/256/121/88 ms; warm 8–24 ms (oxygenhub/docs/performance.md).
177
- 2026-09-28 H17 done (oxygenhub#3, closed by the change):
178
  - `POST /issues` takes labels, assignees and a milestone (triage). Unknown names are refused before anything is created.
179
  - New `PATCH /issues/{n}`: title and body for the author or triage; the whole label set, assignees and milestone for triage; each change goes on the timeline.
180
  - `POST /labels` creates or changes a label and `DELETE /labels/{name}` removes it (triage).
181
  - hub-api types `IssueEdit` and `NewLabel`; TS regenerated (the extension's copy follows with its next change). Test `api_issue_triage_and_labels`; hub spec §12.1.
182
  - Dogfooding:
183
    - Each Earth repository now has the labels bug, feature, performance, accessibility, security and gap.
184
    - Every issue so far is labelled.
185
    - The work log's issue helper sets labels from now on.
186
- 2026-09-28 H18 done (oxygenhub#4, closed by the change):
187
  - Any bare `#N` in a pushed change's message, on any bookmark, adds "referenced by change <hash> on <bookmark>" to that issue's or proposal's timeline, with the hash linked.
188
  - `closes #N` still records only "closed by change"; `owner/name#N` from other repositories is left alone.
189
  - `refs::references` has a unit test; the issues end-to-end test covers issue and proposal timelines. Hub spec §8.
190
  - From now on, the `refs #N` lines in work commits show on the Earth issues.
191
- 2026-09-28 H19 done (oxygenhub#5, closed by the change). Spec §8.2 first.
192
  - Projects belong to a person or organisation: `/projects/{owner}` and `/projects/{owner}/{n}` ("projects" is now a reserved name).
193
  - Columns start as Backlog, Doing and Done; they can be added, renamed, moved and removed (their cards go to the first column; the last column stays).
194
  - Cards are issues or proposals of the owner's repositories (by `repo#N`, or `#N` with a repository picked) or notes. Each can be on a project only once. They move, reorder and are removed without JavaScript.
195
  - Visibility: public or private (owner, organisation members and admins). Cards from repositories the viewer cannot read are not shown at all.
196
  - A closed project is read-only; deleting one leaves the issues.
197
  - Issue and proposal pages list their projects; profiles and organisation pages link to them.
198
  - Migration 0015. Test `project_boards` plus reference parsing. Screenshot-checked on a scratch hub.
199
  - The screenshot tool now disables the browser cache (an earlier shot had served a stale style.css).
200
  - Gap found: no projects API → H21 (oxygenhub#15).
201
- 2026-09-28 H20 done (oxygenhub#6, closed by the change): relative links and images in issue, comment and proposal Markdown resolve against the repository's default bookmark; release notes resolve against the release's tag. Test `issue_markdown_follows_the_default_bookmark`.
202
- 2026-09-28 Webhooks, from Andy's report (oxygenhub#16 and #17, closed by the change):
203
  - **Why Discord got nothing.** Every delivery, ping included, was the OxygenHub envelope, which Discord rejects ("Cannot send an empty message").
204
  - **Formats.** Hooks now have a format: `oxygenhub`, `discord` (an embed with the event's summary; mentions disabled so user text cannot ping) or `slack`. It is picked from the URL; migration 0016 switched existing Discord and Slack hooks.
205
  - **Settings.**
206
    - The URL is masked (long path segments and query cut short, since chat webhooks keep a token there; the audit log records the same masked form) and wraps inside the panel.
207
    - The table shows the last delivery result.
208
    - Each hook has an edit form; a blank URL or secret keeps the current one.
209
  - **Also found.** Issues opened, commented on, closed or reopened in the web interface emitted no webhook events at all; now they do.
210
  - **Tests.** Unit tests for the formats and masking; end-to-end test `webhook_formats_editing_and_web_issue_events`. Hub spec §12.2.
211
- 2026-09-28 H21 done (oxygenhub#15, closed by the change):
212
  - `/api/v1/projects/{owner}`: list and create.
213
  - `/api/v1/projects/{owner}/{n}`: the board as the caller may see it, and `PATCH`.
214
  - `/cards`: add a reference or note; columns by id or name.
215
  - `/cards/{id}`: move or remove.
216
  - The pages and the API share `rights_of`, `board_columns` and `add_card`. hub-api types added; TS regenerated. Test `api_projects`. Hub spec §12.1.
217
- 2026-09-28 Repository overview, from Andy's review (oxygenhub#18, closed by the change):
218
  - The heads and conflicts strips sit inside the main column.
219
  - Clone is a button beside search: a popup with a selectable command, copy, and the bare URL.
220
  - Contributors box: authors counted from the state's changes, linked to hub accounts through linked keys; unsigned changes with no author show as "Unattributed changes".
221
  - Languages box: bytes per language (an extension table plus syntect), with Linguist's colours. Prose, lock files and vendored paths are left out; under 1% folds into Other.
222
  - A public, internal or private badge by the repository name.
223
  - Tests in browse.rs and a unit test for detection. Screenshot-checked on Earth.
224
  - Andy's key is not linked to his account yet (Settings › Keys), so his contributor entry is not a link.