The logo's arms are drawn and slide while a page loads; the Serves column lines up; the job re-run button has room (closes #306, closes #309, closes #310) 03bfb945f5c1
The mark's gradient was in bounding-box units, and a horizontal line's box has no height, so the arms were never painted. It is in user space now in the header, footer, favicon and mark.svg, and the wait animation slides the arms opposite ways beside the turning rings.
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key 10ff523123bf
- When
- · 2026-10-25 16:31 UTC
- Signature
- ◈ sealed by agent:claude@andymccall
- Hash
03bfb945f5c1802fccb5e2b81155f569b5ea76039bf4f747cf3be08a8e27d458- Depends on
- 16626c0d69c0 19355e2283ab 19bb31ff1f5c 5c6eb1feaa71 74be97962ed8 a7e6e8e6094c b5668108487f b6b7e4cf6b24 d23464c0ac60 de4467cb1eca de505df5cf8d e9b9619cd857
- Depended on by
- 5d818ec7d5fc
- In bookmarks
- api-unknown-fields diffusion-pin-bookmark draft-ready main proposal-list-fast
- In tags
- 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
- 16 atoms, 0 tree operations, 6 files
Browse the tree at this change
modified CHANGELOG.md +4 −0
| … | 3 unchanged lines |
||
| 4 | 4 | ||
| 5 | 5 | ## Unreleased |
|
| 6 | 6 | ||
| 7 | + | - **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). |
|
| 8 | + | - **Admin › Runners: the Serves column lines up (#309):** the scope sits above its input, so the inputs line up whatever the scope's length. |
|
| 9 | + | - **A job's RE-RUN THIS JOB button no longer touches its log (#310).** |
|
| 10 | + | ||
| 7 | 11 | ## 0.9.1 (2026-10-25) |
|
| 8 | 12 | ||
| 9 | 13 | - **`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. |
|
| … | 304 unchanged lines |
modified assets/icons/mark.svg +1 −1
| 1 | 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="-24 -12 48 24" width="48" height="24" aria-hidden="true"> |
|
| 2 | 2 | <defs> |
|
| 3 | - | <linearGradient id="oxyGrad" x1="0%" y1="0%" x2="100%" y2="100%"> |
|
| 3 | + | <linearGradient id="oxyGrad" gradientUnits="userSpaceOnUse" x1="-22" y1="-11" x2="22" y2="11"> |
|
| 4 | 4 | <stop offset="0%" stop-color="#00F0FF"/> |
|
| 5 | 5 | <stop offset="100%" stop-color="#0072FF"/> |
|
| 6 | 6 | </linearGradient> |
|
| … | 8 unchanged lines |
modified assets/icons/oxygenhub.svg +1 −1
| 1 | 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="100%" height="100%"> |
|
| 2 | 2 | <defs> |
|
| 3 | - | <linearGradient id="oxyGrad" x1="0%" y1="0%" x2="100%" y2="100%"> |
|
| 3 | + | <linearGradient id="oxyGrad" gradientUnits="userSpaceOnUse" x1="-22" y1="-11" x2="22" y2="11"> |
|
| 4 | 4 | <stop offset="0%" stop-color="#00F0FF" /> |
|
| 5 | 5 | <stop offset="100%" stop-color="#0072FF" /> |
|
| 6 | 6 | </linearGradient> |
|
| … | 14 unchanged lines |
modified templates/admin_runners.html +1 −1
| … | 27 unchanged lines |
||
| 28 | 28 | <td><a href="/admin/runners/{{ r.id }}"><strong>{{ r.name }}</strong></a>{% if r.source == "config" %} <span class="tag" title="Configured with a hub.toml token">hub.toml</span>{% endif %}</td> |
|
| 29 | 29 | <td><span class="runner-status st-{{ r.connectivity() }}">{{ r.connectivity() }}</span></td> |
|
| 30 | 30 | <td class="small"><a href="/admin/runners?runner={{ r.name|urlencode }}#queue" title="{% if row.jobs.is_empty() %}Its jobs in the queue{% else %}{{ row.jobs_line() }}{% endif %}">{{ row.doing() }}</a></td> |
|
| 31 | - | <td class="small">{{ row.scope }} |
|
| 31 | + | <td class="small runner-serves"><div>{{ row.scope }}</div> |
|
| 32 | 32 | <form class="tiny" method="post" action="/admin/runners/{{ r.id }}/scope" title="Empty for the whole site, an owner, or owner/repository"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="text" name="scope" size="14" placeholder="owner or owner/repo"> <button>set</button></form></td> |
|
| 33 | 33 | <td>{% if r.key.is_empty() %}<span class="mute" title="Bound on its first signed receipt">not yet</span>{% else %}<code title="{{ r.key }} · {{ r.key_name }}">{{ r.key|short }}</code>{% endif %}</td> |
|
| 34 | 34 | <td class="small">{% if r.arch.is_empty() %}<span class="mute">unknown</span>{% else %}{{ r.arch }} · {{ r.os }} · flow {{ r.version }}{% endif %}{% let f = r.facts_line() %}{% if !f.is_empty() %}<br><span class="mute" title="Measured by the runner at its last claim{% if !r.kernel.is_empty() %}; kernel {{ r.kernel }}{% endif %}">{{ f }}</span>{% endif %}{% if !row.swarm.is_empty() %}<br><span class="mute" title="Where other runners fetch image chunks from it">swarm {{ row.swarm }}</span>{% endif %}</td> |
|
| … | 41 unchanged lines |
modified templates/base.html +2 −2
| … | 11 unchanged lines |
||
| 12 | 12 | <body{% block bodyattrs %}{% endblock %}> |
|
| 13 | 13 | <a class="skip" href="#main">Skip to content</a> |
|
| 14 | 14 | <header> |
|
| 15 | - | <a class="brand" href="/"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-24 -12 48 24" width="48" height="24" aria-hidden="true"><defs><linearGradient id="oxyGradH" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00F0FF"/><stop offset="100%" stop-color="#0072FF"/></linearGradient></defs><path d="M -11 -6 L 11 -6" stroke="url(#oxyGradH)" stroke-width="3" stroke-linecap="round"/><path d="M -11 6 L 11 6" stroke="url(#oxyGradH)" stroke-width="3" stroke-linecap="round"/><circle cx="-11" cy="0" r="10" fill="none" stroke="url(#oxyGradH)" stroke-width="3"/><circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/><circle cx="11" cy="0" r="10" fill="none" stroke="url(#oxyGradH)" stroke-width="3"/><circle cx="11" cy="0" r="3.5" fill="#FFFFFF"/></svg><span class="px">OXYGEN<em>HUB</em></span></a> |
|
| 15 | + | <a class="brand" href="/"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-24 -12 48 24" width="48" height="24" aria-hidden="true"><defs><linearGradient id="oxyGradH" gradientUnits="userSpaceOnUse" x1="-22" y1="-11" x2="22" y2="11"><stop offset="0%" stop-color="#00F0FF"/><stop offset="100%" stop-color="#0072FF"/></linearGradient></defs><path class="arm" d="M -11 -6 L 11 -6" stroke="url(#oxyGradH)" stroke-width="3" stroke-linecap="round"/><path class="arm" d="M -11 6 L 11 6" stroke="url(#oxyGradH)" stroke-width="3" stroke-linecap="round"/><circle cx="-11" cy="0" r="10" fill="none" stroke="url(#oxyGradH)" stroke-width="3"/><circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/><circle cx="11" cy="0" r="10" fill="none" stroke="url(#oxyGradH)" stroke-width="3"/><circle cx="11" cy="0" r="3.5" fill="#FFFFFF"/></svg><span class="px">OXYGEN<em>HUB</em></span></a> |
|
| 16 | 16 | <form class="find" action="/search" method="get" role="search"><input type="search" name="q" placeholder="Search…" aria-label="Search" autocomplete="off"></form> |
|
| 17 | 17 | <nav aria-label="Account"> |
|
| 18 | 18 | <button type="button" class="theme-toggle" data-theme-toggle aria-label="Theme" title="Theme"><span aria-hidden="true">◐</span></button> |
|
| … | 24 unchanged lines |
||
| 43 | 43 | <footer> |
|
| 44 | 44 | <div class="foot"> |
|
| 45 | 45 | <div> |
|
| 46 | - | <a class="brand" href="/"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-24 -12 48 24" width="48" height="24" aria-hidden="true"><defs><linearGradient id="oxyGradF" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00F0FF"/><stop offset="100%" stop-color="#0072FF"/></linearGradient></defs><path d="M -11 -6 L 11 -6" stroke="url(#oxyGradF)" stroke-width="3" stroke-linecap="round"/><path d="M -11 6 L 11 6" stroke="url(#oxyGradF)" stroke-width="3" stroke-linecap="round"/><circle cx="-11" cy="0" r="10" fill="none" stroke="url(#oxyGradF)" stroke-width="3"/><circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/><circle cx="11" cy="0" r="10" fill="none" stroke="url(#oxyGradF)" stroke-width="3"/><circle cx="11" cy="0" r="3.5" fill="#FFFFFF"/></svg><span class="px">OXYGEN<em>HUB</em></span></a> |
|
| 46 | + | <a class="brand" href="/"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-24 -12 48 24" width="48" height="24" aria-hidden="true"><defs><linearGradient id="oxyGradF" gradientUnits="userSpaceOnUse" x1="-22" y1="-11" x2="22" y2="11"><stop offset="0%" stop-color="#00F0FF"/><stop offset="100%" stop-color="#0072FF"/></linearGradient></defs><path class="arm" d="M -11 -6 L 11 -6" stroke="url(#oxyGradF)" stroke-width="3" stroke-linecap="round"/><path class="arm" d="M -11 6 L 11 6" stroke="url(#oxyGradF)" stroke-width="3" stroke-linecap="round"/><circle cx="-11" cy="0" r="10" fill="none" stroke="url(#oxyGradF)" stroke-width="3"/><circle cx="-11" cy="0" r="3.5" fill="#00F0FF"/><circle cx="11" cy="0" r="10" fill="none" stroke="url(#oxyGradF)" stroke-width="3"/><circle cx="11" cy="0" r="3.5" fill="#FFFFFF"/></svg><span class="px">OXYGEN<em>HUB</em></span></a> |
|
| 47 | 47 | <p class="small">{{ shell.site }} · a forge for Oxygen repositories. Nothing here is inferred: what a page says, the object store says.</p> |
|
| 48 | 48 | </div> |
|
| 49 | 49 | <div class="cols"> |
|
| … | 13 unchanged lines |
modified templates/style.css +10 −2
| … | 356 unchanged lines |
||
| 357 | 357 | table.small td, table.small th { padding: 5px 8px; font-size: 13px; } |
|
| 358 | 358 | form.tiny { display: inline; margin: 0; } |
|
| 359 | 359 | form.tiny button { padding: 1px 6px; font-size: 10px; } |
|
| 360 | + | .flow-job > form.job-rerun { display: block; margin: 0 0 8px; } |
|
| 361 | + | /* The runners table's Serves cell (#309): the scope above its input, so the inputs line up whatever the scope's length. */ |
|
| 362 | + | .runner-serves form.tiny { display: flex; gap: 4px; margin-top: 4px; white-space: nowrap; } |
|
| 360 | 363 | ||
| 361 | 364 | /* proposals */ |
|
| 362 | 365 | .comment { background: var(--panel); border: 1px solid var(--line); padding: 10px 14px; margin: 0 0 10px; } |
|
| … | 755 unchanged lines |
||
| 1118 | 1121 | .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); } |
|
| 1119 | 1122 | ||
| 1120 | 1123 | /* Working (#142): a pressed button's spinner, and the Oxygen mark's rings |
|
| 1121 | - | spinning while a slow page loads (hub.js adds .oxy-wait after 300 ms). The |
|
| 1122 | - | pointer keeps its own shape: the mark beside it is the only sign. */ |
|
| 1124 | + | spinning and its arms sliding back and forth in turn while a slow page |
|
| 1125 | + | loads (hub.js adds .oxy-wait after 300 ms; #306). The pointer keeps its own |
|
| 1126 | + | shape: the mark beside it is the only sign. */ |
|
| 1123 | 1127 | button[aria-busy=true] { opacity: 1; } |
|
| 1124 | 1128 | .oxy-spin { display: inline-block; width: 1em; text-align: center; } |
|
| 1125 | 1129 | .oxy-wait-mark { display: none; position: fixed; z-index: 1000; pointer-events: none; padding: 4px 6px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, .18); line-height: 0; } |
|
| … | 2 unchanged lines |
||
| 1128 | 1132 | .oxy-wait .oxy-wait-mark { display: block; } |
|
| 1129 | 1133 | .oxy-wait header .brand svg circle[r="10"], .oxy-wait-mark svg circle[r="10"] { stroke-dasharray: 44 18.8; animation: oxy-ring .9s linear infinite; } |
|
| 1130 | 1134 | .oxy-wait header .brand svg circle[cx="11"][r="10"], .oxy-wait-mark svg circle[cx="11"][r="10"] { animation-direction: reverse; } |
|
| 1135 | + | .oxy-wait header .brand svg .arm, .oxy-wait-mark svg .arm { animation: oxy-arm .9s ease-in-out infinite alternate; } |
|
| 1136 | + | .oxy-wait header .brand svg .arm + .arm, .oxy-wait-mark svg .arm + .arm { animation-direction: alternate-reverse; } |
|
| 1131 | 1137 | @keyframes oxy-ring { to { stroke-dashoffset: -62.8; } } |
|
| 1138 | + | @keyframes oxy-arm { from { transform: translateX(-3px); } to { transform: translateX(3px); } } |
|
| 1132 | 1139 | @media (prefers-reduced-motion: reduce) { |
|
| 1133 | 1140 | .oxy-wait header .brand svg circle[r="10"], .oxy-wait-mark svg circle[r="10"] { animation: none; stroke-dasharray: none; } |
|
| 1141 | + | .oxy-wait header .brand svg .arm, .oxy-wait-mark svg .arm { animation: none; } |
|
| 1134 | 1142 | } |
|
| 1135 | 1143 | ||
| 1136 | 1144 | /* Admin › Runners › Images (C0, docs/cylinder.md): where each image is. */ |
|
| … | 316 unchanged lines |