The 720px breakpoint contained three rules:
.admin-sidebar { display: none; }
.rail-link:not(.rail-link--active) { display: none; }
.topbar-admin-link { display: none; }
Every one hides navigation rather than relocating it. On a phone the console showed the
page you were on and no way to reach any other: the admin sidebar was gone, every rail
link except the current one was gone, and the route from the workspace to the control
panel was gone. There was no hamburger to find because nothing had ever been built for a
narrow screen - the breakpoint assumed the sidebar could simply go away, which is only true
when nothing else is left to navigate with.
A toggle appears below the breakpoint and reveals the navigation as a drawer. The drawer is
positioned rather than shown inline, because an inline nav pushes the page down by its own
height every time it opens, so on a short viewport the page the operator asked for ends up
below the fold. Escape closes it, a click on the page behind it closes it, following a link
closes it, and crossing the breakpoint settles the state - without which the drawer class
survives into the wide layout and the bug only reappears when the phone is rotated back.
Above the breakpoint the toggle is hidden, because a control that opens what is already
open misreports the state of the page.
nodes.html is deliberately not given a drawer. Its navigation is inline in <main> rather
than in a sidebar, so there is nothing to put in a drawer and a hamburger there would
toggle nothing; its nav was a single horizontal scroll strip instead, which hides links off
the right edge with no sign that there are more, so it now wraps.
test_navigation.py states the rule - navigation hidden at a width must still be reachable
at that width - and reads the breakpoint with comments stripped. Two things that check got
wrong on the way, both recorded in the file:
- It flagged the drawer's own "display: none" as a violation. Hiding a closed drawer is
correct, or it would push the page down on every load; what is wrong is hiding navigation
with no way to open it. The test now requires a "body.nav-open" rule alongside any hide.
- It only required that *some* page carry a toggle, so deleting it from the admin console
- the page that was reported broken - passed, because the workspace still had one. Now
per page.
Mutation-verified: restoring the original defect fails 1, removing the toggle from the
admin console fails 1.
695 passed, 4 skipped.
186 lines
9.7 KiB
HTML
186 lines
9.7 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="description" content="UmbraTrans project workspace">
|
|
<title>Workspace · UmbraTrans</title>
|
|
<link rel="icon" type="image/png" href="assets/umbratrans-emblem.png">
|
|
<link rel="stylesheet" href="assets/styles.css">
|
|
</head>
|
|
<body class="workspace-page">
|
|
<div class="workspace-shell">
|
|
<aside class="workspace-rail" id="workspace-rail" aria-label="Workspace navigation">
|
|
<a class="brand-lockup brand-lockup--rail" href="index.html">
|
|
<img class="brand-image" src="assets/umbratrans-emblem.png" alt="" aria-hidden="true">
|
|
<span>UmbraTrans</span>
|
|
</a>
|
|
<div class="rail-section-label">Workspace</div>
|
|
<nav class="rail-nav">
|
|
<a class="rail-link rail-link--active" href="#overview"><span class="rail-icon" aria-hidden="true">⌂</span>Overview</a>
|
|
<a class="rail-link" href="#translations"><span class="rail-icon" aria-hidden="true">文</span>Translations</a>
|
|
<a class="rail-link" href="#voices"><span class="rail-icon" aria-hidden="true">◉</span>Voices</a>
|
|
<a class="rail-link" href="#audio"><span class="rail-icon" aria-hidden="true">▶</span>Audio runs</a>
|
|
<a class="rail-link" href="#artifacts"><span class="rail-icon" aria-hidden="true">◇</span>Deliverables</a>
|
|
</nav>
|
|
<div class="rail-section-label rail-section-label--spaced">Manage</div>
|
|
<nav class="rail-nav">
|
|
<a class="rail-link" href="#activity"><span class="rail-icon" aria-hidden="true">≡</span>Activity</a>
|
|
<a id="workspace-admin-link" class="rail-link" href="uadmin/" hidden><span class="rail-icon" aria-hidden="true">⚙</span>Admin</a>
|
|
</nav>
|
|
<div class="rail-footer">
|
|
<div class="rail-user">
|
|
<span class="avatar" id="rail-avatar" aria-hidden="true">··</span>
|
|
<span>
|
|
<strong id="rail-display-name">Loading…</strong>
|
|
<small id="rail-role">Checking session</small>
|
|
</span>
|
|
</div>
|
|
<a class="text-button" href="settings.html">Your account</a>
|
|
<button class="text-button" type="button" data-signout>Sign out</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="workspace-main" id="overview">
|
|
<header class="workspace-topbar">
|
|
<!-- The only way to reach the other sections on a narrow screen. Above 720px the
|
|
rail is already on screen and nav.js sets this hidden, because a control that
|
|
opens what is already open misreports the state of the page. -->
|
|
<button class="text-button" type="button" data-nav-toggle aria-expanded="false"
|
|
aria-controls="workspace-rail" aria-label="Open navigation" hidden>☰</button>
|
|
<div class="breadcrumb"><span>Workspace</span><span aria-hidden="true">/</span><strong id="breadcrumb-identity">Loading…</strong></div>
|
|
<div class="topbar-actions">
|
|
<span class="status-pill status-pill--info" id="workspace-session-pill">Session active</span>
|
|
<a id="workspace-admin-topbar" class="topbar-admin-link" href="uadmin/" hidden>Control panel <span aria-hidden="true">↗</span></a>
|
|
<a class="text-button topbar-signout" href="settings.html">Your account</a>
|
|
<button class="text-button topbar-signout" type="button" data-signout>Sign out</button>
|
|
</div>
|
|
</header>
|
|
|
|
<section class="workspace-hero">
|
|
<div>
|
|
<p class="eyebrow eyebrow--teal">Translation production workspace</p>
|
|
<h1 id="workspace-title">Workspace</h1>
|
|
<p class="hero-subtitle" id="workspace-subtitle">Loading your session…</p>
|
|
</div>
|
|
<div class="hero-actions">
|
|
<a class="button button--secondary" id="workspace-admin-hero" href="uadmin/" hidden>Open control panel</a>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="pipeline-card" aria-labelledby="projects-title">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="eyebrow eyebrow--teal" id="projects-eyebrow">Projects</p>
|
|
<h2 id="projects-title">Work in this organization</h2>
|
|
</div>
|
|
<div class="section-heading__actions" role="group" aria-label="Filter projects">
|
|
<button class="chip chip--active" type="button" id="project-filter-all" aria-pressed="true">All</button>
|
|
<button class="chip" type="button" id="project-filter-mine" aria-pressed="false">Assigned to me</button>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
One list, rendered from one response. The filter above narrows what is
|
|
already in the page rather than refetching, so switching cannot show a
|
|
half-updated mix of two organizations' projects, and the count in the
|
|
heading always describes the same list the filter applies to.
|
|
-->
|
|
<p class="list-summary" id="projects-summary" role="status" aria-live="polite">Loading projects…</p>
|
|
<div id="projects-list" class="project-list"></div>
|
|
<div class="empty-state" id="projects-empty" hidden>
|
|
<span class="empty-state__icon" aria-hidden="true">◇</span>
|
|
<p class="empty-state__title" id="projects-empty-title">No projects yet</p>
|
|
<p class="empty-state__body" id="projects-empty-body">An organization administrator can create the first one from the control panel.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="metric-grid" aria-label="Project statistics">
|
|
<article class="metric-card">
|
|
<span class="metric-label">Projects</span>
|
|
<strong id="metric-projects" aria-hidden="true">—</strong>
|
|
<span class="metric-note" id="metric-projects-note">In this organization</span>
|
|
</article>
|
|
<article class="metric-card">
|
|
<span class="metric-label">Assigned to you</span>
|
|
<strong id="metric-assigned" aria-hidden="true">—</strong>
|
|
<span class="metric-note" id="metric-assigned-note">Projects you are working on</span>
|
|
</article>
|
|
<article class="metric-card">
|
|
<span class="metric-label">In progress</span>
|
|
<strong id="metric-active" aria-hidden="true">—</strong>
|
|
<span class="metric-note">Active and not on hold</span>
|
|
</article>
|
|
<article class="metric-card">
|
|
<span class="metric-label">Needs a writer</span>
|
|
<strong id="metric-unclaimed" aria-hidden="true">—</strong>
|
|
<span class="metric-note metric-note--warn" id="metric-unclaimed-note">Nobody assigned yet</span>
|
|
</article>
|
|
</section>
|
|
|
|
<div class="workspace-columns">
|
|
<section class="workspace-panel" id="translations">
|
|
<div class="section-heading">
|
|
<div><p class="eyebrow eyebrow--teal">Language lanes</p><h2>Translations</h2></div>
|
|
</div>
|
|
<div class="empty-state">
|
|
<span class="empty-state__icon" aria-hidden="true">文</span>
|
|
<p class="empty-state__title">No translation data</p>
|
|
<p class="empty-state__body">There is no project or translation endpoint in the backend yet, so this panel shows nothing rather than sample data. It will populate once the project and translation services land.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="workspace-panel" id="voices">
|
|
<div class="section-heading">
|
|
<div><p class="eyebrow eyebrow--teal">Identity</p><h2>Voices</h2></div>
|
|
</div>
|
|
<div class="empty-state">
|
|
<span class="empty-state__icon" aria-hidden="true">◉</span>
|
|
<p class="empty-state__title">No voice data</p>
|
|
<p class="empty-state__body">Speaker, canonical voice, and voice-cast records have no backend yet. Nothing is displayed here until those endpoints exist.</p>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<section class="workspace-panel" id="audio">
|
|
<div class="section-heading">
|
|
<div><p class="eyebrow eyebrow--teal">Operations</p><h2>Audio runs</h2></div>
|
|
</div>
|
|
<div class="empty-state">
|
|
<span class="empty-state__icon" aria-hidden="true">▶</span>
|
|
<p class="empty-state__title">No audio run data</p>
|
|
<p class="empty-state__body">Audio workers, queues, and storage have no backend endpoints yet. Maintenance and job state will appear here once they do.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="workspace-columns">
|
|
<section class="workspace-panel" id="artifacts">
|
|
<div class="section-heading">
|
|
<div><p class="eyebrow eyebrow--teal">Delivery</p><h2>Deliverables</h2></div>
|
|
</div>
|
|
<div class="empty-state">
|
|
<span class="empty-state__icon" aria-hidden="true">◇</span>
|
|
<p class="empty-state__title">No deliverables</p>
|
|
<p class="empty-state__body">Artifact download authorization and release state are not implemented in the backend, so no files or counts are shown.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="workspace-panel" id="activity">
|
|
<div class="section-heading">
|
|
<div><p class="eyebrow eyebrow--teal">Traceability</p><h2>Activity</h2></div>
|
|
</div>
|
|
<div class="empty-state">
|
|
<span class="empty-state__icon" aria-hidden="true">≡</span>
|
|
<p class="empty-state__title">No project activity</p>
|
|
<p class="empty-state__body">Only identity events are recorded so far, and those are visible to administrators in the control panel audit log.</p>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
|
<script src="assets/nav.js" defer></script>
|
|
<script src="assets/api.js" defer></script>
|
|
<script src="assets/app.js" defer></script>
|
|
</body>
|
|
</html>
|