af07314bb6
Uncommitted work from three design runs on this branch, against one design system: docs/design-system.md is updated to match the CSS, not the reverse. Library (Reader-facing): - .chrome sticks at top: 0. Search and the tab row were unreachable three screens into a 300-item library, which is exactly where they earn their keep; everything above them still scrolls away on purpose. - One :focus-visible ring (2px --paper) on the nine controls that defined none and fell back to the UA blue. .searchbar keeps its border recolour as a resting cue but no longer stands in for a ring. - Mono labels lift 10px -> 11px everywhere. The brief names night reading and glare as the usage scene; 10px small-caps was where taste overrode it. - A card in flight past 2s says "Saving..." and carries aria-busy. htmx sets neither, so the wait up to its 15s timeout was silent in both channels. - Titles clamp at 3 lines; .is-new .title takes width: fit-content, or -webkit-box stretches the ember underline past the text it sizes to. /admin: - Overview routes into Lanes when a lane is unhealthy, prefixes each figure with its column word on the phone layout that drops the thead, labels state cells for a screen reader, and has an empty state where the sites table assumed rows. - The admin shell picks up the library's chrome: htmx 15s timeout, the shared #notice slot, #sr-announce, filter.js. admin.css follows the same pass. - admin_render_test.go and card_render_test.go render the templates directly, so markup regressions in either surface fail without a browser. Login: - DISCORD_GUILD_NAME (optional) names the community on the login screen and in the refusal message, so a stranger knows which Discord to ask for an invite. Unset degrades to a generic label; neither form names the guild id. Handlers: - maxChapterNum (9999) bounds both typed-chapter paths. uiChapter and adminSeriesCorrectLatest each parsed a float64 with no ceiling, so a hand-rolled POST stored 1e308 and every later reader of that row inherited it. Matches the max on the card's chapter input. go test ./... green.
64 lines
3.8 KiB
HTML
64 lines
3.8 KiB
HTML
{{/* Poll Lane status: one row per Site's latest durable pass, refreshing
|
|
itself so a run can be watched rather than sampled by reloading. The
|
|
refresh is one attribute on the fragment root and the endpoint answers
|
|
with this same fragment, so the swap replaces the element that asked.
|
|
|
|
Every figure is read from poll_passes, never from a running poller: a
|
|
restart answers from the database the moment it is up (issue #145). The
|
|
browser fact is a deployment-config fact plus a reachability derived from
|
|
the pass log; the cause chips and the state phrase are decided in Go,
|
|
this template only prints them. */}}
|
|
{{define "lanes"}}
|
|
<section class="lanes" id="lanes"
|
|
hx-get="/ui/admin/lanes" hx-trigger="every 30s" hx-swap="outerHTML" hx-sync="this:replace">
|
|
<div class="sechead">
|
|
<h2 class="sec">Poll Lanes</h2>
|
|
<p class="statusline">
|
|
{{if .PollerOff}}Polling: <span class="mark-faint">off</span>
|
|
{{else}}Browser: {{if not .BrowserConfigured}}<span class="mark-faint">not configured</span>{{else if .BrowserReachable}}<span class="mark mark-strong">reachable</span>{{else}}<span class="mark bad">unreachable</span>{{end}}{{end}}
|
|
</p>
|
|
</div>
|
|
<p class="hint">Healthy idle (paused, browser asleep, nothing eligible) needs no action — attention needs the owner.</p>
|
|
{{if .Rows}}
|
|
<div class="tbl lanes">
|
|
<div class="thead">
|
|
<span>Site</span><span>Due</span><span>Checked</span><span>Gap</span>
|
|
<span>Last pass</span><span title="Window outcomes for this Site (last 12h)">Outcomes · status</span><span></span>
|
|
</div>
|
|
{{range .Rows}}
|
|
<div class="trow{{if .Attention}} attention{{end}}">
|
|
<a class="c-site" href="{{.FailingHref}}">{{.Site}}</a>
|
|
{{/* data-label is the column word for the phone layout, which drops the
|
|
thead: three bare numbers in a row say nothing. */}}
|
|
<span data-label="due">{{.Due}}</span>
|
|
<span data-label="checked">{{.Checked}}</span>
|
|
<span data-label="gap">{{.Gap}}</span>
|
|
<span>ran {{.Ran}}</span>
|
|
<span class="c-skip">{{if .HasChips}}{{range $i, $c := .Chips}}{{if $i}}<span class="mark-faint"> · </span>{{end}}<span class="mark">{{$c.Name}} {{$c.Count}}</span>{{end}}{{else}}<span class="mark-faint">none observed</span>{{end}}{{if .StatePhrase}} · <span class="{{if .StateGood}}ok{{else}}bad{{end}}" aria-label="{{if .StateGood}}healthy: {{end}}{{.StatePhrase}}">{{.StatePhrase}}</span>{{end}}</span>
|
|
{{/* The pause control lives in the one slot the design leaves for it:
|
|
a running Lane offers the three durations and Pause; a paused Lane
|
|
offers Resume in the same place. Pause is not destruction — it
|
|
takes nothing away and reverses in one press — so neither wears a
|
|
confirm row or the danger accent. The form wraps the select so the
|
|
offered duration travels with the press. */}}
|
|
<span class="c-ctrl">{{if .Paused}}<span class="pausebar">
|
|
<form hx-post="/admin/lanes/{{.Site}}/resume" hx-target="#lanes" hx-swap="outerHTML" hx-indicator="#lanes" hx-disabled-elt="find button" hx-sync="closest #lanes:replace">
|
|
<button type="submit" class="ghost">Resume</button>
|
|
</form>
|
|
</span>{{else}}<span class="pausebar">
|
|
<form hx-post="/admin/lanes/{{.Site}}/pause" hx-target="#lanes" hx-swap="outerHTML" hx-indicator="#lanes" hx-disabled-elt="find button, select" hx-sync="closest #lanes:replace">
|
|
<select name="duration" aria-label="Pause duration">
|
|
<option>1h</option><option selected>6h</option><option>24h</option>
|
|
</select>
|
|
<button type="submit" class="ghost">Pause</button>
|
|
</form>
|
|
</span>{{end}}</span>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{else}}
|
|
<p class="empty">No data yet — no Lane has recorded a pass.</p>
|
|
{{end}}
|
|
</section>
|
|
{{end}}
|