OxygenHub 0.6.1 a549ddd5941f
Drops (#279), one settings page per setting (#278), secret scanning over history (#277), keyless release tags (oxygen#129) and released oxy can clone a repository with an agent's tag again (oxygen#131); pins oxy 0.5.1. Tidier forms and headers: the New drop page is centred like every other page, a form's Cancel lines up with its buttons, and + NEW DROP no longer touches the text above. Details in CHANGELOG.md under 0.6.1.
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key 85c5ddd6ebc5
- When
- · 2026-10-20 16:51 UTC
- Signature
- ◈ sealed by agent:claude@andymccall
- Hash
a549ddd5941ffe53cfa2efc116cf480e103dcf71b7ff693631ee529601089d74- Depends on
- 145306044896 166f6e9bf4f1 1f78655a0466 380996db84e5 4c77a1c8aad8 5c6e92fd8d71 9c04c779f032 a7e6e8e6094c b5668108487f d23464c0ac60
- Depended on by
- 69e4f27c5633 ab2d8b40f9f4 787632570bf9
- In bookmarks
- api-unknown-fields diffusion-pin-bookmark draft-ready main proposal-list-fast
- In tags
- v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
- Edits
- 18 atoms, 0 tree operations, 8 files
Browse the tree at this change
modified CHANGELOG.md +3 −0
| … | 3 unchanged lines |
||
| 4 | 4 | ||
| 5 | 5 | ## Unreleased |
|
| 6 | 6 | ||
| 7 | + | ## 0.6.1 (2026-10-20) |
|
| 8 | + | ||
| 9 | + | - **Tidier forms and headers:** the New drop page is laid out like every other page (it ran the full width when its notice did), a form's Cancel lines up with its buttons (drops and web edits), and the Drops page's + NEW DROP sits beside its title instead of against the text above. |
|
| 7 | 10 | - **Released oxy can clone repositories with an agent's release tag again (oxygen#131):** since keyless agents could tag releases with ten-minute certified keys, oxy 0.5.0 and older refused such a tag when cloning or fetching a repository with a trust policy (`key … may not create tags/v0.5.0 under this repository's policy`), and with it the whole clone, so andymccall/oxygen could not be cloned by any released client. The hub now leaves those tags out of its answer to a client that does not declare `certified-tags` in the new `Oxy-Features` request header (oxygen#131), which sees the repository as if the tag did not exist; current clients get it as before. Needs the oxygen pin with oxygen#131. |
|
| 8 | 11 | - **Drops: snippets of files, each its own small repository (#279):** a drop is one or more files, as a gist is, kept as an Oxygen repository of its own at `/{owner}/drops/{id}` (a 24-hex-digit id), so every edit is a signed change with history and diffs, and `oxy clone` and `oxy push` take its address. Make one at `/drops/new` (title, description, files, public or secret), with `POST /api/v1/drops`, or with `oh drop create FILE… [--secret] [-d TEXT] [-t TITLE]`; the hub commits it as your delegate, as web edits are, so you delegate to the hub's key first. Your Drops page (`/drops`, under your profile and in the site band) lists and manages them; someone else's shows their public drops only. A secret drop is listed nowhere but readable by anyone with its address. Drops never appear among repositories (profiles, the dashboard, Explore, search, `GET /api/v1/repos`); Admin › Storage labels them. Only the owner edits a drop; the owner or a site administrator deletes it. Push protection covers drop pushes and what the web and the API commit. API: `GET`/`POST /drops`, `GET /users/{name}/drops`, `GET`/`PATCH`/`DELETE /drops/{id}`; `oh drop list|view|delete`. `drops` is now a reserved name, for owners and for repositories. Not yet: forks, stars, comments, embedding, and drops in backups. |
|
| 9 | 12 | - **Repository settings are one page per setting (#278):** a repository's Settings was one long page; it is now a sidebar of grouped pages (General, Code, Flow, Integrations, Data, Danger zone) with the page shown beside it, the current one marked, each at its own address (`/{owner}/{name}/settings/push-protection`, `/settings/runs`, …; `/settings` is General). Pages a role cannot use are not listed and are not found. Every form keeps its address and now returns to its own page with what happened, so reloading does not send it again. Links elsewhere point at the new pages (the push protection refusal names `/settings/push-protection`), and a kept `/settings#section` link opens the page that has it. On a narrow screen the sidebar becomes a compact list above the page. An archived repository's settings open for its admins again, so they can bring it back. |
|
| … | 259 unchanged lines |
modified Cargo.lock +6 −6
| … | 1457 unchanged lines |
||
| 1458 | 1458 | ||
| 1459 | 1459 | [[package]] |
|
| 1460 | 1460 | name = "hub-api" |
|
| 1461 | - | version = "0.6.0" |
|
| 1461 | + | version = "0.6.1" |
|
| 1462 | 1462 | dependencies = [ |
|
| 1463 | 1463 | "schemars", |
|
| 1464 | 1464 | "serde", |
|
| … | 797 unchanged lines |
||
| 2262 | 2262 | ||
| 2263 | 2263 | [[package]] |
|
| 2264 | 2264 | name = "oh" |
|
| 2265 | - | version = "0.6.0" |
|
| 2265 | + | version = "0.6.1" |
|
| 2266 | 2266 | dependencies = [ |
|
| 2267 | 2267 | "anyhow", |
|
| 2268 | 2268 | "axum", |
|
| … | 33 unchanged lines |
||
| 2302 | 2302 | ||
| 2303 | 2303 | [[package]] |
|
| 2304 | 2304 | name = "oxy-core" |
|
| 2305 | - | version = "0.5.0" |
|
| 2305 | + | version = "0.5.1" |
|
| 2306 | 2306 | dependencies = [ |
|
| 2307 | 2307 | "blake3", |
|
| 2308 | 2308 | "ed25519-dalek", |
|
| … | 18 unchanged lines |
||
| 2327 | 2327 | ||
| 2328 | 2328 | [[package]] |
|
| 2329 | 2329 | name = "oxy-server" |
|
| 2330 | - | version = "0.5.0" |
|
| 2330 | + | version = "0.5.1" |
|
| 2331 | 2331 | dependencies = [ |
|
| 2332 | 2332 | "axum", |
|
| 2333 | 2333 | "axum-server", |
|
| … | 4 unchanged lines |
||
| 2338 | 2338 | ||
| 2339 | 2339 | [[package]] |
|
| 2340 | 2340 | name = "oxy-strata" |
|
| 2341 | - | version = "0.5.0" |
|
| 2341 | + | version = "0.5.1" |
|
| 2342 | 2342 | dependencies = [ |
|
| 2343 | 2343 | "async-trait", |
|
| 2344 | 2344 | "bytes", |
|
| … | 10 unchanged lines |
||
| 2355 | 2355 | ||
| 2356 | 2356 | [[package]] |
|
| 2357 | 2357 | name = "oxygenhub" |
|
| 2358 | - | version = "0.6.0" |
|
| 2358 | + | version = "0.6.1" |
|
| 2359 | 2359 | dependencies = [ |
|
| 2360 | 2360 | "ammonia", |
|
| 2361 | 2361 | "anyhow", |
|
| … | 2513 unchanged lines |
modified Cargo.toml +1 −1
| … | 6 unchanged lines |
||
| 7 | 7 | resolver = "2" |
|
| 8 | 8 | ||
| 9 | 9 | [workspace.package] |
|
| 10 | - | version = "0.6.0" |
|
| 10 | + | version = "0.6.1" |
|
| 11 | 11 | edition = "2021" |
|
| 12 | 12 | license = "MIT OR Apache-2.0" |
|
| 13 | 13 | authors = ["Andy McCall <[email protected]>"] |
|
| … | 60 unchanged lines |
replaced deps/oxygenflow +0 −0
No line changes.
modified templates/drop_form.html +1 −2
| 1 | 1 | {% extends "base.html" %} |
|
| 2 | 2 | {% block title %}{% if editing %}Edit drop{% else %}New drop{% endif %}{% endblock %} |
|
| 3 | - | {% block mainclass %} class="wide"{% endblock %} |
|
| 4 | 3 | {% block content %} |
|
| 5 | 4 | <div class="head"><h1>{% if editing %}Edit drop{% else %}New drop{% endif %} <small>{% if editing %}saved as one signed change{% else %}a small repository of its own, never listed among your repositories{% endif %}</small></h1></div> |
|
| 6 | 5 | {% if let Some(d) = delegation %}<div class="strip warn">{{ d }}</div>{% endif %} |
|
| … | 19 unchanged lines |
||
| 26 | 25 | {% if !kept.is_empty() %}<p class="small mute">Kept as they are (not text, or pushed with oxy): {% for k in kept %}<code>{{ k }}</code>{% if !loop.last %}, {% endif %}{% endfor %}.</p>{% endif %} |
|
| 27 | 26 | {% if editing %}<label for="message">Change message</label><input type="text" id="message" name="message" value="{{ message }}" placeholder="Edit {{ title }}">{% endif %} |
|
| 28 | 27 | <p class="small mute">The change is authored by your linked key and signed by this hub's key as your delegate. Push protection refuses anything that looks like a credential.</p> |
|
| 29 | - | <div class="choice"> |
|
| 28 | + | <div class="actions"> |
|
| 30 | 29 | <button type="submit"{% if delegation.is_some() %} disabled{% endif %}>{% if editing %}SAVE{% else %}CREATE DROP{% endif %}</button> |
|
| 31 | 30 | <a class="go" href="{{ cancel }}">CANCEL</a> |
|
| 32 | 31 | </div> |
|
| … | 2 unchanged lines |
modified templates/drops.html +1 −1
| 1 | 1 | {% extends "base.html" %} |
|
| 2 | 2 | {% block title %}Drops · {{ owner }}{% endblock %} |
|
| 3 | 3 | {% block content %} |
|
| 4 | - | <div class="head"><h1><a href="/{{ owner }}">{{ owner }}</a> / Drops <small>{% if is_me %}snippets of one or more files, each its own repository{% else %}public drops{% endif %}</small></h1>{% if is_me %}<a class="go primary" href="/drops/new">+ NEW DROP</a>{% endif %}</div> |
|
| 4 | + | <div class="head with-action"><h1><a href="/{{ owner }}">{{ owner }}</a> / Drops <small>{% if is_me %}snippets of one or more files, each its own repository{% else %}public drops{% endif %}</small></h1>{% if is_me %}<a class="go primary" href="/drops/new">+ NEW DROP</a>{% endif %}</div> |
|
| 5 | 5 | {% if let Some(n) = notice %}<div class="strip">{{ n }}</div>{% endif %} |
|
| 6 | 6 | {% if is_me %}<p class="small mute">A drop is a small Oxygen repository: every edit is a signed change with history, and <code>oxy clone</code> takes its address. Drops never appear among your repositories. A secret drop is listed nowhere and readable by anyone with its address.</p>{% endif %} |
|
| 7 | 7 | <section class="card-box"><div class="card-head"><h2>Drops</h2><span class="count-chip">{{ drops.len() }}</span></div> |
|
| … | 19 unchanged lines |
modified templates/edit.html +1 −1
| … | 19 unchanged lines |
||
| 20 | 20 | <label><input type="radio" name="mode" value="propose"{% if !is_bookmark %} checked{% endif %}> new bookmark <input type="text" name="new_bookmark" placeholder="name (optional)" size="18"> and propose it</label> |
|
| 21 | 21 | </div> |
|
| 22 | 22 | <p class="small mute">The change is authored by your linked key and signed by this hub's key as your delegate; the repository's policy decides whether it may land.</p> |
|
| 23 | - | <div class="choice"> |
|
| 23 | + | <div class="actions"> |
|
| 24 | 24 | <button type="submit"{% if delegation.is_some() %} disabled{% endif %}>SAVE</button> |
|
| 25 | 25 | {% if !new_file %}<button type="submit" name="delete" value="1" class="danger-btn"{% if delegation.is_some() %} disabled{% endif %} data-confirm="Delete {{ path }}?">DELETE FILE</button>{% endif %} |
|
| 26 | 26 | <a class="go" href="{{ repo.url }}/{% if new_file %}tree/{{ r }}{% else %}file/{{ r }}/{{ path }}{% endif %}">CANCEL</a> |
|
| … | 3 unchanged lines |
modified templates/style.css +8 −0
| … | 121 unchanged lines |
||
| 122 | 122 | button, a.go, button.go, summary.go { font: inherit; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; background: var(--panel); color: var(--acc); border: 1px solid var(--acc); padding: 5px 10px; cursor: pointer; } |
|
| 123 | 123 | button:hover, a.go:hover { background: var(--acc); color: var(--now-ink); text-decoration: none; } |
|
| 124 | 124 | button.small, a.go.small { padding: 2px 8px; font-size: 11px; } |
|
| 125 | + | a.go { display: inline-block; line-height: normal; } |
|
| 126 | + | /* A form's buttons in one aligned row: a CANCEL link the same height as |
|
| 127 | + | the buttons beside it. */ |
|
| 128 | + | form .actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 12px 0 0; } |
|
| 129 | + | /* A page title with an action (+ NEW DROP): title left, action right, a |
|
| 130 | + | gap between them; on a phone the action wraps below with room. */ |
|
| 131 | + | .head.with-action { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px 20px; flex-wrap: wrap; } |
|
| 132 | + | .head.with-action h1 { margin: 0; } |
|
| 125 | 133 | .tag { display: inline-block; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--acc); background: var(--soft); padding: 1px 6px; vertical-align: middle; } |
|
| 126 | 134 | .tag.bad { color: var(--bad); } |
|
| 127 | 135 | .tag.warn { color: var(--warn); } |
|
| … | 1260 unchanged lines |