style(web): move the row-count select into the card header

It was sitting in a strip below the heading with a "Show" label and a "newest
first" hint — three lines of chrome for one control. The select now lives at the
right end of the card's own header bar (`margin-left:auto` inside the flex h2),
compact and case-normal so it reads as a control and does not grow the bar.
Label and hint are gone; the header text names the card, the values speak for
themselves, and screen readers keep the `aria-label`.
This commit is contained in:
2026-07-25 15:43:17 +02:00
parent fe4d749a90
commit 61c4972a88
4 changed files with 30 additions and 23 deletions
+3 -3
View File
@@ -295,9 +295,9 @@ Gitea at most one call per 10s window; the VM columns are re-read on every
refresh.
Rows are ordered **newest first** (a queued run, which has no start time yet,
sorts above everything; ties break on Gitea's run id). The **Show** selector in
the card's header picks how many to list — 10 / 25 / 50 / 100, default from
`[web].executions_limit` (10). The choice rides on the card's own polling URL
sorts above everything; ties break on Gitea's run id). The row-count dropdown at
the right end of the card's header bar picks how many to list — 10 / 25 / 50 /
100, default from `[web].executions_limit` (10). The choice rides on the card's own polling URL
(`/executions?limit=N`), so it survives every 5s refresh with no client-side
state; a value set in the config outside those four is offered as an extra
option rather than dropped.
+12 -10
View File
@@ -281,17 +281,19 @@ dd code, dd .mono { overflow-wrap: anywhere; }
/* A row of buttons (e.g. Purge / Empty all) that wraps on narrow cards. */
.btn-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* Small control strip under a card's heading (Executions row count): right
* aligned so it reads as a view option, not as part of the data below. */
.card-toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
margin: -0.35rem 0 0.7rem;
/* A control living inside a card's header bar (Executions row count), pushed to
* the bar's right end. Compact and case-normal so it reads as a control rather
* than part of the uppercase heading text, and short enough not to grow the
* bar's height. */
.head-select {
margin-left: auto;
padding: 0.1rem 0.35rem;
font-size: 0.8rem;
line-height: 1.2;
text-transform: none;
letter-spacing: normal;
color: var(--text);
}
.card-toolbar select { padding: 0.2rem 0.4rem; }
/* Per-template expandable backup list (Templates panel). */
.backups-details > summary {
@@ -11,15 +11,18 @@
scrolled out of the window (or that no run ever claimed) keeps its row and
its Remove button.
The row-count selector re-fetches the card with ``?limit=N``, and the card it
renders carries that same limit on its own polling URL -- that is what makes
the choice survive the 5s refresh without any client-side state.
The row-count selector sits in the card's own header bar (right end) and
re-fetches the card with ``?limit=N``; the card it renders carries that same
limit on its polling URL, which is what makes the choice survive the 5s
refresh without any client-side state. It carries no visible label -- the
header text names the card and the values are self-evident -- so screen
readers get ``aria-label`` instead.
#}
<section class="card card--wide" id="executions-card" hx-get="/executions?limit={{ limit }}" hx-trigger="every 5s, ci:refresh from:body" hx-swap="outerHTML">
<h2><svg class="icon" aria-hidden="true"><use href="#i-play"></use></svg>Executions</h2>
<div class="card-toolbar">
<label for="executions-limit">Show</label>
<select id="executions-limit" name="limit" aria-label="Number of executions to show"
<h2>
<svg class="icon" aria-hidden="true"><use href="#i-play"></use></svg>Executions
<select id="executions-limit" name="limit" class="head-select"
aria-label="Number of executions to show"
hx-get="/executions" hx-target="#executions-card" hx-swap="outerHTML" hx-trigger="change">
{% for choice in limit_choices %}
<option value="{{ choice }}" {{ 'selected' if choice == limit else '' }}>{{ choice }}</option>
@@ -28,8 +31,7 @@
<option value="{{ limit }}" selected>{{ limit }}</option>
{% endif %}
</select>
<span class="field-hint">newest first</span>
</div>
</h2>
{% if not backend_available %}
<p class="notice notice-warn" role="alert">Backend unavailable (vmrun not found on this host).</p>
{% endif %}
+4 -1
View File
@@ -385,7 +385,10 @@ def test_default_limit_is_ten(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -
assert limits == [10]
assert '<option value="10" selected>10</option>' in resp.text
assert "newest first" in resp.text
# The selector lives inside the card's header bar, not in a strip below it.
head = resp.text.split("</h2>", 1)[0]
assert "head-select" in head
assert "<label" not in resp.text
def test_selector_offers_the_choices_and_marks_the_current_one(