openvibers/openvibe.space Commits

Space: the hosting dashboard passes axe (contrast, file input labels) (#37)

OP OpenVibers committed 12 Oct 2026 · 6f21f28e20 · 2 files, +10 −9

axe-core 4.13 (WCAG 2.1 A/AA, serious and critical, the same rules as Live's browser smoke), run on the hosting dashboard pages from Space#36's design pass, found:

- **Contrast:** the muted grey on table headers, fact labels, row metadata, ids and breadcrumbs. Those now use the secondary text colour, and the faint grey stays only on decoration. The accent badge text and table links (including a deploy id on the highlighted active row) are lightened.
- **Labels:** the folder and archive file inputs had a legend but no accessible name. Each gets an `aria-label`.

After the change axe finds nothing on the dashboard, project, site, deploy and home pages. Live pages checked the same way are clean too: openvibe.video (home, watch, clip, channel), openvibe.space, Rent's housing page, openvibe.host and openvibe.tips. Tests: the full suite, 23 files.

Changes

ddiff --git a/public/css/hosting.css b/public/css/hosting.css
iindex 4dcbe7f..307c4ce 100644
---- a/public/css/hosting.css
++++ b/public/css/hosting.css
@@@ -30,18 +30,18 @@
.hd-head h1 code { font-size: .8em; }
.hd-eyebrow { display: inline-flex; gap: 8px; align-items: center; margin: 0; padding: 4px 12px; border-radius: 999px; border: 1px solid var(--hd-line-strong); color: var(--accent-light, var(--hd-accent)); font-size: 12px; font-weight: 700; letter-spacing: .4px; }
.hd-sub { margin: 0; color: var(--hd-dim); max-width: 640px; line-height: 1.6; }
--.hd-crumbs { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 14px; color: var(--hd-faint); }
++.hd-crumbs { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 14px; color: var(--hd-dim); }
.hd-crumbs a { color: var(--hd-dim); text-decoration: none; }
.hd-crumbs a:hover { color: var(--hd-text); text-decoration: underline; }
.hd-chips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0; color: var(--hd-dim); }
--.hd-chips > code { color: var(--hd-faint); }
++.hd-chips > code { color: var(--hd-dim); }
/* Badges */
.hd-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 700; line-height: 18px; white-space: nowrap; border: 1px solid var(--hd-line-strong); color: var(--hd-dim); background: transparent; }
.hd-badge--ok { color: var(--hd-ok); border-color: color-mix(in srgb, var(--hd-ok) 45%, transparent); background: color-mix(in srgb, var(--hd-ok) 12%, transparent); }
.hd-badge--warn { color: var(--hd-warn); border-color: color-mix(in srgb, var(--hd-warn) 45%, transparent); background: color-mix(in srgb, var(--hd-warn) 12%, transparent); }
.hd-badge--bad { color: var(--hd-bad); border-color: color-mix(in srgb, var(--hd-bad) 45%, transparent); background: color-mix(in srgb, var(--hd-bad) 12%, transparent); }
--.hd-badge--accent { color: var(--accent-light, var(--hd-accent)); border-color: color-mix(in srgb, var(--hd-accent) 50%, transparent); background: color-mix(in srgb, var(--hd-accent) 14%, transparent); }
++.hd-badge--accent { color: color-mix(in srgb, var(--accent-light, #60a5fa) 65%, #fff); border-color: color-mix(in srgb, var(--hd-accent) 50%, transparent); background: color-mix(in srgb, var(--hd-accent) 14%, transparent); }
/* Cards */
.hd-card { min-width: 0; margin: 0 0 20px; padding: 20px 22px; border: 1px solid var(--hd-line); border-radius: 16px; background: var(--hd-card); }
@@@ -102,12 +102,13 @@
/* Tables */
.hd-table-wrap { overflow-x: auto; margin: 0 -4px; }
.hd-table { width: 100%; border-collapse: collapse; font-size: 14px; }
--.hd-table th { padding: 8px 10px; text-align: left; font-size: 12px; font-weight: 700; letter-spacing: .3px; text-transform: uppercase; color: var(--hd-faint); border-bottom: 1px solid var(--hd-line); white-space: nowrap; }
++.hd-table th { padding: 8px 10px; text-align: left; font-size: 12px; font-weight: 700; letter-spacing: .3px; text-transform: uppercase; color: var(--hd-dim); border-bottom: 1px solid var(--hd-line); white-space: nowrap; }
.hd-table td { padding: 10px; border-bottom: 1px solid var(--hd-line); vertical-align: middle; }
.hd-table tbody tr:last-child td { border-bottom: 0; }
.hd-table tbody tr:hover { background: color-mix(in srgb, var(--hd-text) 3%, transparent); }
.hd-table tr.hd-active td { background: color-mix(in srgb, var(--hd-accent) 8%, transparent); }
.hd-table tr.hd-active td:first-child { box-shadow: inset 3px 0 0 var(--hd-accent); }
++.hd-table a { color: color-mix(in srgb, var(--accent-light, #60a5fa) 70%, #fff); }
.hd-actions { text-align: right; white-space: nowrap; }
.hd-actions > * + * { margin-left: 6px; }
.hd-table--dns td { vertical-align: top; }
@@@ -119,7 +120,7 @@
.hd-row__main { display: grid; gap: 2px; min-width: 0; }
.hd-row__title { font-weight: 700; font-size: 16px; color: var(--hd-text) !important; text-decoration: none; }
.hd-row__title:hover { text-decoration: underline; }
--.hd-row__side { display: inline-flex; gap: 8px; align-items: center; color: var(--hd-faint); }
++.hd-row__side { display: inline-flex; gap: 8px; align-items: center; color: var(--hd-dim); }
.hd-url { display: inline-flex; gap: 6px; align-items: center; font-size: 13px; color: var(--hd-dim) !important; text-decoration: none; overflow-wrap: anywhere; }
.hd-url i { font-size: 11px; opacity: .7; }
.hd-url:hover { color: var(--hd-text) !important; text-decoration: underline; }
@@@ -141,7 +142,7 @@
.hd-meter__bar--bad span { background: var(--hd-bad); }
.hd-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--hd-line); }
.hd-facts div { display: grid; gap: 2px; }
--.hd-facts dt { font-size: 12px; color: var(--hd-faint); text-transform: uppercase; letter-spacing: .3px; font-weight: 700; }
++.hd-facts dt { font-size: 12px; color: var(--hd-dim); text-transform: uppercase; letter-spacing: .3px; font-weight: 700; }
.hd-facts dd { margin: 0; font-size: 14px; overflow-wrap: anywhere; }
.hd-facts--wide { margin-top: 18px; border: 1px solid var(--hd-line); border-radius: 14px; padding: 14px 16px; background: var(--hd-card); }
@@@ -158,7 +159,7 @@
.hd-details[open] summary { margin-bottom: 8px; }
.hd-history { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; font-size: 14px; }
.hd-history li { display: flex; gap: 10px; flex-wrap: wrap; }
--.hd-history time { color: var(--hd-faint); font-variant-numeric: tabular-nums; }
++.hd-history time { color: var(--hd-dim); font-variant-numeric: tabular-nums; }
.hd-log { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }
.hd-log__line { display: flex; gap: 10px; align-items: baseline; padding: 6px 8px; border-radius: 8px; }
.hd-log__line > span:last-child { overflow-wrap: anywhere; }
ddiff --git a/server/render/hosting-pages.js b/server/render/hosting-pages.js
iindex 7f5ac2d..5f19511 100644
---- a/server/render/hosting-pages.js
++++ b/server/render/hosting-pages.js
@@@ -127,8 +127,8 @@ function site({ site: s, project: p, role, deploys, activations, domains, siteCo
<h1>${esc(s.name)}</h1>
<p class="hd-chips"><a class="hd-url hd-url--big" href="${esc(url)}" rel="noopener">${esc(url.replace(/^https?:\/\//, ''))} ${icon('fa-arrow-up-right-from-square')}</a> ${status} <code>${esc(s.id)}</code></p></header>
${canDeploy ? card('Upload a deploy', form(`/sites/${s.id}/deploys`, csrf, `<div class="hd-sources">
--<fieldset class="hd-source"><legend>${icon('fa-folder-open')} A folder</legend><input type="file" name="files" multiple webkitdirectory><input type="hidden" name="strip" value="folder"></fieldset>
--<fieldset class="hd-source"><legend>${icon('fa-file-zipper')} …or an archive (.tar.gz / .tar)</legend><input type="file" name="archive" accept=".tar,.tgz,.gz,application/gzip,application/x-tar"></fieldset>
++<fieldset class="hd-source"><legend>${icon('fa-folder-open')} A folder</legend><input type="file" name="files" multiple webkitdirectory aria-label="A folder to upload"><input type="hidden" name="strip" value="folder"></fieldset>
++<fieldset class="hd-source"><legend>${icon('fa-file-zipper')} …or an archive (.tar.gz / .tar)</legend><input type="file" name="archive" accept=".tar,.tgz,.gz,application/gzip,application/x-tar" aria-label="An archive to upload (.tar.gz or .tar)"></fieldset>
</div>
<label class="hd-field hd-field--narrow">Deploy from subfolder (optional) <input name="root" placeholder="dist"></label>
<fieldset class="hd-choices"><legend>When it is ready</legend>