Files
terdut-server/internal/web/static/index.html
T
Niklas Ye 56b8191a78
CI / chart (pull_request) Successful in 2s
CI / security (pull_request) Successful in 17s
CI / test (pull_request) Successful in 2m33s
Let the tab bar fit however many tabs there are
The bottom tab bar was grid-template-columns: repeat(4, 1fr), written when
there were four tabs. Team and Admin arrived in the last two releases and
nothing updated that number, so six items were being laid into four
columns -- which on a phone is the reported symptom, tabs that do not fit
the width.

grid-auto-flow: column with grid-auto-columns: 1fr makes the count follow
the markup instead. That also handles a case a fixed number cannot: Admin
is only rendered for an administrator, so the tab count genuinely differs
between two people looking at the same install.

Then the compactness. Each link gets min-width: 0 so a column may shrink
below its label's natural width, and the label itself ellipsises rather
than widening the bar. Under 420px the font drops to 10px, the icons to
21px and the badge shrinks to match.

No icon-only breakpoint. The arithmetic says the labels fit: six tabs on
a 320px phone give about 53px each, and the widest label, "On-call", is
about 38px at 10px. A media query that never fires is dead code, and the
ellipsis is the backstop if a future tab is named something longer.

The links gained aria-labels regardless. The icons are aria-hidden, so
the visible text was the accessible name, and it should not be the only
one.

The desktop sidebar is unaffected: it overrides display, padding and
font-size itself, so none of the phone rules reach it.

Not verified on a phone -- I cannot open a browser here, so this is the
cause identified from the CSS and the widths worked out on paper.

Claude-Session: https://claude.ai/code/session_01RHPj4ggeFdEjKKfm4SHbD7
2026-09-21 11:18:30 +02:00

134 lines
6.2 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#f5f6f8" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0f1115" media="(prefers-color-scheme: dark)">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="terdut">
<title>terdut</title>
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="stylesheet" href="/app.css">
<script type="module" src="/js/app.js"></script>
</head>
<body>
<div id="boot" class="boot" aria-busy="true"><span class="spinner"></span></div>
<main id="login" class="login" hidden>
<form id="login-form" class="login-card" autocomplete="on">
<div class="login-brand">
<img src="/icon.svg" alt="" width="40" height="40">
<h1>terdut</h1>
</div>
<label>
<span>Username</span>
<input name="username" autocomplete="username" autocapitalize="none" spellcheck="false" required>
</label>
<label>
<span>Password</span>
<input name="password" type="password" autocomplete="current-password" required>
</label>
<p class="form-error" role="alert" hidden></p>
<button class="btn btn-primary btn-block" type="submit">Sign in</button>
<p class="login-hint">No password yet? Ask an admin to set one, or run
<code>PUT /api/users/{id}/password</code> with your API key.</p>
<p class="login-hint" id="signup-link" hidden>
No account? <a href="/signup">Create one</a>.</p>
</form>
<!-- Sign-up. Shown instead of the login card at /signup, and only offers
what the server allows: an invite link, or open sign-up. -->
<form id="signup-form" class="login-card" autocomplete="on" hidden>
<div class="login-brand">
<img src="/icon.svg" alt="" width="40" height="40">
<h1>terdut</h1>
</div>
<p class="signup-intro" id="signup-intro"></p>
<label>
<span>Username</span>
<input name="username" autocomplete="username" autocapitalize="none" spellcheck="false" required>
</label>
<label>
<span>Email</span>
<input name="email" type="email" autocomplete="email" required>
</label>
<label>
<span>Password</span>
<input name="password" type="password" autocomplete="new-password" minlength="10" required>
</label>
<label id="signup-team-label" hidden>
<span>Team name</span>
<input name="team_name" autocomplete="off">
</label>
<p class="form-error" role="alert" hidden></p>
<button class="btn btn-primary btn-block" type="submit">Create account</button>
<p class="login-hint">Already have one? <a href="/">Sign in</a>.</p>
</form>
</main>
<div id="app" class="app" hidden>
<nav class="nav" aria-label="Sections">
<a class="nav-brand" href="/">
<img src="/icon.svg" alt="" width="28" height="28">
<span>terdut</span>
</a>
<a class="nav-link" href="/" data-section="queue" aria-label="Queue">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h10"/></svg>
<span class="nav-label">Queue</span>
<span class="nav-badge" data-badge hidden></span>
</a>
<a class="nav-link" href="/oncall" data-section="oncall" aria-label="On-call">
<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15" rx="2"/><path d="M3.5 10h17M8 3v4M16 3v4"/></svg>
<span class="nav-label">On-call</span>
</a>
<a class="nav-link" href="/alerts" data-section="alerts" aria-label="Alerts">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 16V11a6 6 0 0 1 12 0v5l1.5 2h-15z"/><path d="M10 20.5a2 2 0 0 0 4 0"/></svg>
<span class="nav-label">Alerts</span>
</a>
<a class="nav-link" href="/team" data-section="team" aria-label="Team">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="9" cy="8" r="3"/><circle cx="17" cy="9" r="2.5"/><path d="M3 19a6 6 0 0 1 12 0M15 19a5 5 0 0 1 6-4"/></svg>
<span class="nav-label">Team</span>
</a>
<!-- Hidden unless the signed-in user is a system administrator; app.js
unhides it once /api/me says so. The server refuses every admin
endpoint regardless, so this is a courtesy and not a gate. -->
<a class="nav-link" href="/admin" data-section="admin" aria-label="Admin" id="nav-admin" hidden>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3l7 3v6c0 4-3 7-7 9-4-2-7-5-7-9V6z"/></svg>
<span class="nav-label">Admin</span>
</a>
<a class="nav-link" href="/more" data-section="more" aria-label="Account">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="3.5"/><path d="M5 20a7 7 0 0 1 14 0"/></svg>
<span class="nav-label">Account</span>
</a>
</nav>
<header class="topbar">
<h1 class="topbar-title" id="topbar-title">Queue</h1>
<span class="open-pill" id="open-pill" hidden></span>
</header>
<section id="view-queue" class="view view-queue" data-view="queue">
<div class="pane pane-list">
<div class="chips" id="queue-filters" role="tablist" aria-label="Filter"></div>
<div id="queue-list" class="list"></div>
</div>
<div class="pane pane-detail" id="detail" aria-live="polite"></div>
</section>
<section id="view-oncall" class="view view-page" data-view="oncall" hidden></section>
<section id="view-alerts" class="view view-page" data-view="alerts" hidden></section>
<section id="view-team" class="view view-page" data-view="team" hidden></section>
<section id="view-admin" class="view view-page" data-view="admin" hidden></section>
<section id="view-more" class="view view-page" data-view="more" hidden></section>
</div>
<dialog id="sheet" class="sheet"></dialog>
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
</body>
</html>