OXYGENHUB

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