feat(alerts): the whole rule row opens the rule (#5214)
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 1m2s
CI / unit (push) Successful in 1m24s
CI / publish (push) Successful in 1m30s

The name's link stretches over its row (.row-link / .row-target); the
History and Disable buttons sit above it and keep their own clicks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 23:23:02 -04:00
co-authored by Claude Opus 5.5
parent e37cd0a873
commit 16b636b2f0
3 changed files with 16 additions and 5 deletions
+6
View File
@@ -210,6 +210,12 @@ strong, b, th { font-weight: 500; }
/* The Docker page's one-line answer, above its tiles (#5298). */
.page-verdict { margin: 0 0 0.75rem; font-size: 0.95rem; }
.table tr.row-off > td { opacity: 0.55; }
/* A row that opens as a whole: its one .row-target link stretches over the
row, and the actions cell sits above it so its buttons keep their own
clicks. One real link per row, so keyboard and screen readers meet it once. */
.table tr.row-link { position: relative; cursor: pointer; }
.table tr.row-link .row-target::after { content: ""; position: absolute; inset: 0; }
.table tr.row-link .td-actions { position: relative; z-index: 1; cursor: auto; }
.table td.nowrap { white-space: nowrap; }
.cell-note { font-size: 0.78rem; color: var(--text-muted); margin-top: 0.2rem; }
/* An alert episode's own transitions, folded under it (#5316). */
+5 -4
View File
@@ -1,7 +1,8 @@
{# alerts/list.html — the Alerts page (#5315): the verdict and what is active
(alerts/_active.html, polled), then every rule: what it is doing now and
when it last fired, what it fires on, and its History and Disable. A rule's
name opens it; deleting one is on its own page, behind the confirm dialog. #}
when it last fired, what it fires on, and its History and Disable. The
whole row opens the rule (.row-link, its name is the link); deleting one is
on its own page, behind the confirm dialog. #}
{% extends "base.html" %}
{% block title %}Alerts — Steward{% endblock %}
{% from "_macros.html" import page_header, status_badge, empty_state %}
@@ -36,9 +37,9 @@
<tbody>
{% for rule in rules %}
{% set fired = (last_fired or {}).get(rule.id) %}
<tr{% if not rule.enabled %} class="row-off"{% endif %}>
<tr class="row-link{% if not rule.enabled %} row-off{% endif %}">
<td class="cell-name">
<a href="{{ ('/alerts/rules/' ~ rule.id ~ '/edit') if can_edit else ('/alerts/history?rule=' ~ rule.id) }}" title="{{ rule.name }}">{{ rule.name }}</a>
<a class="row-target" href="{{ ('/alerts/rules/' ~ rule.id ~ '/edit') if can_edit else ('/alerts/history?rule=' ~ rule.id) }}" title="{{ rule.name }}">{{ rule.name }}</a>
<div class="cell-sub">{{ source_label(rule.source_module) }}</div>
</td>
<td>
+5 -1
View File
@@ -86,6 +86,10 @@ async def test_an_operator_sees_the_sentence_linked_and_can_acknowledge(app):
assert '<span class="badge badge-crit">Firing</span>' in page
assert 'action="/alerts/r1/acknowledge"' in page
assert 'href="/alerts/rules/new"' in page and 'href="/alerts/rules/r1/edit"' in page
# The whole row opens the rule: one stretched link, the row marked for it.
row = page[page.index('<tr class="row-link'):]
row = row[:row.index("</tr>")]
assert row.count('class="row-target"') == 1 and 'class="row-target" href="/alerts/rules/r1/edit"' in row
assert 'action="/alerts/rules/r1/delete"' not in page # deleting a rule is the admin's
main = page[page.index("<main"):] # the sidebar brand mark carries its own colours
assert "onclick" not in main and not hex_colours(main), hex_colours(main)
@@ -222,7 +226,7 @@ async def test_rules_say_what_each_is_doing_now(app):
rules = page[page.index(">Rules</h2>"):]
assert '<span class="badge badge-crit">Firing</span>' in rules
assert 'title="Within its threshold">quiet</span>' in rules
assert '<span class="tone-dim">off</span>' in rules and 'class="row-off"' in rules
assert '<span class="tone-dim">off</span>' in rules and 'class="row-link row-off"' in rules
assert 'hx-get="/alerts/active"' in page