# Changelog OxygenHub runs at https://oxygenhub.io; each deploy's build is on the About page and at `/api/v1/version`. ## Unreleased ## 0.9.4 (2026-10-25) - **Boards keep the last fortnight of finished work (#293):** by default every column archives its finished cards (closed issues, closed or landed proposals) 14 days after they reached it, hourly, so a Done column stops growing however long a project runs; open work and notes stay. A column can instead archive every card after N days, or never ("Archive automatically" in its menu). Archived cards stay on the board's archive view, searchable and restorable. On the next hourly pass, boards with years of Done cards (Super Mario Land 2's 327) shrink to their last two weeks. - **An issue's references: no line through the chips, each change once (#318):** an opened "referenced by N changes" box drew the proposal timeline's vertical rule through its first column (the events shared its class); and a change was recorded again for every bookmark it was pushed on, and for every mention in its message, so one change filled several chips. Now one reference per change per issue or proposal, and the page shows each change once, including those recorded before. - **Claims say how long a job is expected to take (oxygenflow#76):** `expected_ms`, the median of the job's last five passing runs in the repository (reused passes left out), so a runner leaving with notice finishes a job that fits it and hands back the rest. Pins OxygenFlow's main. - **The hub's CI writes a test report and coverage (#307):** `test` runs under cargo-nextest and keeps a JUnit report (whether or not the tests pass); a new `coverage` job runs the tests instrumented beside it, with its own cached target, and keeps LCOV coverage. `coverage` is not a required check. - **The new proposal form lines up (#315):** as wide as the changes above it; Draft with Claude a secondary button under the editor (it fills the fields, it does not open the proposal); the draft checkbox and Open proposal on one row. - **Draft with Claude proposes a title too (#316):** for a proposal not yet opened, the assistant begins its draft with a title in the style of the changes' own titles; it fills the title field unless you wrote one (the page's own suggestion does not count), and stays out of the description. - **A waiting WASM job no longer says runners lack its image (#317):** a job with no image (the WASM tier) waited saying every runner "does not hold image" with an empty name; the explanation now skips the image check, as the claim always did. - **A draft proposal can be made ready from `oh` and the API (#313):** `oh proposal ready N` (and `--undo` to turn it back into a draft); `PATCH …/proposals/{n}` takes `state: "open"` for a draft and `state: "draft"` for an open one, as the page's buttons do, and `draft: false|true` in the words a proposal is created with. `{"draft": false}` used to answer 200 and change nothing. ## 0.9.3 (2026-10-25) - **Runners hand jobs back, and the queue takes them first (#312, oxygenflow#76):** `POST /api/v1/flow/jobs/{id}/release` {reason} lets a draining runner hand back a job it holds as it leaves (a phone off Wi-Fi, a restart, a spot reclaim). The job is queued again **ahead of new work**, whatever its priority; its log says who handed it back and why, and its next attempt's log follows on; it is not a decline. A job whose runner the hub has not heard from for 3 minutes (claims, 30-second heartbeats, logs) is handed back the same way, rather than running on until its time limit and ending as lost. A job handed back more than 5 times ends as an error saying so. Migration 0094 adds `flow_jobs.handed_back_ms` and `handbacks`. ## 0.9.2 (2026-10-25) - **`GET /api/v1/user/proposals?landable=true` (#311):** also lists the open proposals the caller could land now (in repositories they can write to, with no blockers, as each proposal's `landable` says), for the Android app to say when there is something to land. Only when asked for, since it works out each one's projected state. - **One logo, with its arms (#306):** the Oxygen mark's two arms were never drawn: their gradient was in bounding-box units, and a horizontal line's box has no height, so SVG paints nothing. The gradient is now in user space everywhere (header, footer, favicon, `mark.svg`; the VS Code extensions and the Android app too), and while a page loads the arms slide back and forth in turn beside the turning rings (still with reduced motion). - **Admin › Runners: the Serves column lines up (#309):** the scope sits above its input, so the inputs line up whatever the scope's length. - **A job's RE-RUN THIS JOB button no longer touches its log (#310).** ## 0.9.1 (2026-10-25) - **Pipelines may use the WASM tier:** OxygenFlow's main pinned (oxygenflow#75), so `runtime "wasm"` sandboxes are accepted and their jobs placed on runners that offer the tier (every runner from flow 0.3.1-3; an Android phone from the app). - **`POST /api/v1/user/runner-token` (repo:write):** a one-time registration token for a runner that serves the caller's own repositories and no one else's, as the Android app offers the phone as a runner (oxygenhub-android#8); it lasts a day, like the settings pages' tokens, and is audited. ## 0.9.0 (2026-10-25) - **`GET /api/v1/user/proposals` (repo:read):** the proposals the web dashboard lists, for apps: `review` (open ones by others in repositories the caller belongs to, not yet reviewed by them) and `authored` (their own open and drafts, in repositories they can still see), each with its repository, state, conflicts and web address. The Android app's dashboard shows them (oxygenhub-android#18). - **Board cards can be archived (#293, archive):** off the board but kept, on the board's archive view (`?archived=1`, with the same filter) and restored to the end of their column. A column archives its cards that reached it more than N days ago at once ("Archive cards here more than 14 days"), or on its own once set to (checked hourly, `projects.archiver`), so a Done column stops growing. An issue's or proposal's page says its card is archived. The API has `POST …/cards/archive` (#289's selection plus `older_days`, with `dry_run`), `GET …/cards?archived=true` and `POST …/cards/{id}/restore`; `oh project card archive N --from Done --older 14d`, `oh project card restore N CARD` and `oh project view N --archived`. Migration 0093 adds `project_cards.archived_ms` and `moved_ms` (when a card joined its column) and `project_columns.archive_after_days`. - **Board cards show their labels and assignees, and the board filters by them (#293, filters):** an issue's or proposal's card shows its labels (each a link to the board filtered by it) and an issue's assignees; the filter takes `label:NAME`, `assignee:NAME` and `state:STATE` with words, as the issue list does, every term holding. The board reads what its cards show once per repository rather than once per card (a board of 300 cards made 300 queries). The API's cards and `oh project view` carry the labels and assignees too. ## 0.8.0 (2026-10-25) - **Built on oxy 0.6.0 and OxygenFlow 0.3.1:** tag withdrawals, keyless grants that survive a key rotation, working depth-0 clones of big repositories (oxygen#134, #135, #137; oxygenhub#301). - **The API for apps (#302-#305), found building the Android app:** `GET /api/v1/user/repos` lists the caller's own repositories, their organisations' and those they collaborate on (`?affiliation=owner,organization,collaborator`, paged with `?after=owner/name&limit=`), and `GET /api/v1/user/orgs` their organisations and role (#302); a call with no token now answers 401 (sign in) rather than 403 (#303); `DELETE /api/v1/device/code` withdraws a device sign-in the app gave up on, at once (#304); each notification says its `repository`, `reason` (participating, watching), `thread` and the kind's `label` (#305). - **Boards that keep growing stay readable (#293, first part):** a column of more than 30 cards shows its newest 30 (cards join a column at its end), says how many older ones it leaves out and links to "show all"; a filter above the board finds cards by title or reference, and by state (open, closed, landed, draft), in every column, counting "3 of 312". - **An issue's references come in a box per day (#292):** an issue many changes refer to listed a line per change and ran on down the page. Two or more on one day are now one box, "referenced by 12 changes on 21 Oct 2026", their changes as wrapping chips, collapsible, the newest day's open. - **Move a board's column at once (#289):** `oh project card move N --from Doing -c Done` moves every card of a column, `--except 317,#320` leaves some, `--state closed,landed` takes only cards whose issue or proposal is done, and `--dry-run` lists what would move. One call (`POST …/projects/{owner}/{n}/cards/move`), all or none, one audit entry: tidying a board of 115 cards took 115 commands. - **Expediting a run moves the queue at once (#291):** a run's jobs kept the place they had ("9 of 9 jobs come before") until the next placement pass and some other change on the page. Expediting now places every queued job again straight away, every run whose jobs' notes changed updates live, and a job an expedited run moved down says so ("2 ready jobs come first (after expedited run andy/demo #12)"). - **Create and withdraw tags on the website (#295, #301):** a writer creates a tag on the Tags page (or `oh tag create`, `POST …/tags`), signed by the hub as their delegate, under the policy, and its tag jobs run; a maintainer withdraws one there (or `oh tag delete`, `DELETE …/tags/{tag}`, or `oxy push --delete tags/NAME`) with a signed withdrawal, never while a release uses it. The withdrawal is logged, audited and listed on the page with why, and clones drop the tag on their next fetch. Tags never move, and a withdrawn name is not used again. Pins oxygen with tag withdrawal and with keyless grants that survive their owner's key rotation (oxygen#135). - **An agent's changes verify in a new repository (oxygen#134):** a keyless agent's change in a repository its owner had never pushed to read INVALID ("grant is by an unknown author"). The hub now sends the grant's signer with each certificate, and with oxy's fix the agent keeps it and pushes it beside the certificate, so the repository can check the grant. - **A mistyped address shows the not-found page (#294):** an address the hub could not read, such as a project or issue number that is not a number, answered with a raw parse error ("Invalid URL: Cannot parse value …"); it now shows the site's not-found page, signed-in header and all. - **Embed a drop (#280):** a drop's page offers an `