Add shared navigation to every page
The pages were only reachable by typing URLs, and the digest had no navigation at all. A single _nav.html partial now appears on all four, marking the current page, so Triage / Digest / Tune / Config are one click apart. Parameterised test asserts every page links to the others and marks itself active, so a page added later cannot ship stranded.
This commit is contained in:
1 parent
5f4bc8d890
commit
327d6a69ae
7 files changed
+69
-15
No files matched your search
@@ -0,0 +1,7 @@
|
||||
{#- Shared navigation. `current` marks the active page. -#}
|
||||
<nav class="sitenav">
|
||||
<a href="/" class="{{ 'here' if current == 'triage' }}">Triage</a>
|
||||
<a href="/digest" class="{{ 'here' if current == 'digest' }}">Digest</a>
|
||||
<a href="/tune" class="{{ 'here' if current == 'tune' }}">Tune</a>
|
||||
<a href="/config" class="{{ 'here' if current == 'config' }}">Config</a>
|
||||
</nav>
|
||||
@@ -47,14 +47,21 @@
|
||||
.nearmiss { border-left:2px solid var(--warn); padding-left:.7rem; margin-top:.6rem; }
|
||||
.ok { color:var(--pos); } .err { color:var(--neg); }
|
||||
.gate { color:var(--warn); }
|
||||
.sitenav { display:flex; gap:.15rem; flex-wrap:wrap; }
|
||||
.sitenav a { color:var(--muted); text-decoration:none; font-size:.85rem;
|
||||
padding:.2rem .6rem; border-radius:6px; border:1px solid transparent; }
|
||||
.sitenav a:hover { color:var(--fg); background:var(--rule); }
|
||||
.sitenav a.here { color:var(--accent); border-color:var(--rule); font-weight:600; }
|
||||
|
||||
</style>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Config</h1>
|
||||
<div class="note"><a href="/">triage</a> · <a href="/tune">tune</a> ·
|
||||
<a href="/digest">digest</a> · {{ clusters }} clusters scored</div>
|
||||
{% include "_nav.html" %}
|
||||
</header>
|
||||
|
||||
<p class="note" style="margin:-.5rem 0 1rem">{{ clusters }} clusters scored</p>
|
||||
|
||||
<h2>Try a filter</h2>
|
||||
<div class="probe">
|
||||
<p class="note" style="margin-top:0">
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
||||
-webkit-text-size-adjust:100%; }
|
||||
main { max-width:38rem; margin:0 auto; padding:1.5rem 1.15rem 4rem; }
|
||||
header { border-bottom:1px solid var(--rule); padding-bottom:.75rem; margin-bottom:1.5rem; }
|
||||
header { border-bottom:1px solid var(--rule); padding-bottom:.75rem; margin-bottom:1.5rem;
|
||||
display:flex; gap:1rem; flex-wrap:wrap; align-items:baseline;
|
||||
justify-content:space-between; }
|
||||
h1 { font-size:1.3rem; margin:0 0 .15rem; letter-spacing:-.01em; }
|
||||
.date { color:var(--muted); font-size:.85rem; }
|
||||
.briefing p { margin:0 0 1rem; }
|
||||
@@ -30,11 +32,20 @@
|
||||
nav { display:flex; justify-content:space-between; margin-top:2rem; font-size:.9rem; }
|
||||
nav a { color:var(--accent); text-decoration:none; }
|
||||
.empty { color:var(--muted); font-style:italic; }
|
||||
.sitenav { display:flex; gap:.15rem; flex-wrap:wrap; }
|
||||
.sitenav a { color:var(--muted); text-decoration:none; font-size:.85rem;
|
||||
padding:.2rem .6rem; border-radius:6px; border:1px solid transparent; }
|
||||
.sitenav a:hover { color:var(--fg); background:var(--rule); }
|
||||
.sitenav a.here { color:var(--accent); border-color:var(--rule); font-weight:600; }
|
||||
|
||||
</style>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Daily briefing</h1>
|
||||
<div class="date">{{ day.strftime('%A, %-d %B %Y') }}</div>
|
||||
<div>
|
||||
<h1>Daily briefing</h1>
|
||||
<div class="date">{{ day.strftime('%A, %-d %B %Y') }}</div>
|
||||
</div>
|
||||
{% include "_nav.html" %}
|
||||
</header>
|
||||
|
||||
{% if briefing %}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
h1 { font-size:1.15rem; margin:0; letter-spacing:-.01em; }
|
||||
.stats, .filters { color:var(--muted); font-size:.83rem; }
|
||||
.filters a { color:var(--accent); text-decoration:none; margin-left:.6rem; }
|
||||
.subbar { display:flex; gap:1rem; flex-wrap:wrap; justify-content:space-between;
|
||||
margin:-.4rem 0 1rem; }
|
||||
.filters a.on { text-decoration:underline; }
|
||||
|
||||
article { background:var(--card); border:1px solid var(--rule); border-radius:9px;
|
||||
@@ -54,18 +56,26 @@
|
||||
border-top:1px solid var(--rule); padding:.45rem 1rem; font-size:.78rem;
|
||||
color:var(--muted); display:flex; gap:1rem; justify-content:center; }
|
||||
.empty { color:var(--muted); font-style:italic; padding:2rem 0; }
|
||||
.sitenav { display:flex; gap:.15rem; flex-wrap:wrap; }
|
||||
.sitenav a { color:var(--muted); text-decoration:none; font-size:.85rem;
|
||||
padding:.2rem .6rem; border-radius:6px; border:1px solid transparent; }
|
||||
.sitenav a:hover { color:var(--fg); background:var(--rule); }
|
||||
.sitenav a.here { color:var(--accent); border-color:var(--rule); font-weight:600; }
|
||||
|
||||
@media (max-width:34rem) { .help { display:none; } }
|
||||
</style>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Triage</h1>
|
||||
<div class="stats">{{ rows|length }} shown · {{ labelled }} labelled · {{ total }} clusters</div>
|
||||
<div class="filters">show
|
||||
{% include "_nav.html" %}
|
||||
</header>
|
||||
<div class="subbar">
|
||||
<span class="stats">{{ rows|length }} shown · {{ labelled }} labelled · {{ total }} clusters</span>
|
||||
<span class="filters">show
|
||||
<a class="{{ 'on' if show == 'unlabelled' }}" href="?show=unlabelled">unlabelled</a>
|
||||
<a class="{{ 'on' if show == 'all' }}" href="?show=all">all</a>
|
||||
· <a href="/digest">digest</a>
|
||||
</div>
|
||||
</header>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% for r in rows %}
|
||||
<article id="c{{ loop.index0 }}" data-id="{{ r.id }}"
|
||||
|
||||
@@ -39,11 +39,17 @@
|
||||
letter-spacing:.04em; min-width:5.5rem; }
|
||||
.lane.imp { color:var(--neg); }
|
||||
.note { color:var(--muted); font-size:.82rem; }
|
||||
.sitenav { display:flex; gap:.15rem; flex-wrap:wrap; }
|
||||
.sitenav a { color:var(--muted); text-decoration:none; font-size:.85rem;
|
||||
padding:.2rem .6rem; border-radius:6px; border:1px solid transparent; }
|
||||
.sitenav a:hover { color:var(--fg); background:var(--rule); }
|
||||
.sitenav a.here { color:var(--accent); border-color:var(--rule); font-weight:600; }
|
||||
|
||||
</style>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Tune</h1>
|
||||
<div class="note"><a href="/">triage</a> · <a href="/digest">digest</a></div>
|
||||
{% include "_nav.html" %}
|
||||
</header>
|
||||
|
||||
<form method="get">
|
||||
|
||||
+6
-4
@@ -136,7 +136,7 @@ async def triage(request):
|
||||
return TEMPLATES.TemplateResponse(
|
||||
request, "triage.html",
|
||||
{"rows": rows, "show": show, "total": len(scores), "labelled": len(labelled),
|
||||
"signed": bool(secret)})
|
||||
"signed": bool(secret), "current": "triage"})
|
||||
|
||||
|
||||
async def tune(request):
|
||||
@@ -196,7 +196,7 @@ async def tune(request):
|
||||
request, "tune.html",
|
||||
{"routing": routing, "counts": counts, "alerts": alerts, "digest": digest,
|
||||
"per_day": (counts[IMPORTANT] + counts[INTEREST]) / days, "days": days,
|
||||
"defaults": cfg.routing})
|
||||
"defaults": cfg.routing, "current": "tune"})
|
||||
|
||||
|
||||
def _centroids(con):
|
||||
@@ -254,7 +254,8 @@ async def config_page(request):
|
||||
{"interests": summarise("interest", "___", 0.60),
|
||||
"vetoes": summarise("veto", "threshold", 0.60),
|
||||
"demotes": summarise("demote", "___", 0.55),
|
||||
"clusters": len(rows), "scoring": cfg.scoring, "routing": cfg.routing})
|
||||
"clusters": len(rows), "scoring": cfg.scoring, "routing": cfg.routing,
|
||||
"current": "config"})
|
||||
|
||||
|
||||
async def config_preview(request):
|
||||
@@ -383,7 +384,8 @@ async def digest_page(request):
|
||||
request, "digest.html",
|
||||
{"day": when, "stories": stories,
|
||||
"briefing": prose["briefing"] if prose else None,
|
||||
"prev": when - timedelta(days=1), "next": when + timedelta(days=1)},
|
||||
"prev": when - timedelta(days=1), "next": when + timedelta(days=1),
|
||||
"current": "digest"},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -232,3 +232,14 @@ def test_labelling_is_disabled_when_no_secret_is_configured(tmp_path, monkeypatc
|
||||
from news import web
|
||||
web._VECTORS = None
|
||||
assert "labelling is disabled" in TestClient(web.app).get("/").text
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path,active", [
|
||||
("/", "Triage"), ("/tune", "Tune"), ("/config", "Config"), ("/digest", "Digest")])
|
||||
def test_every_page_carries_the_nav(client, path, active):
|
||||
"""Navigation must not require knowing the URLs."""
|
||||
c, _ = client
|
||||
html = c.get(path).text
|
||||
for target in ("/tune", "/config", "/digest"):
|
||||
assert f'href="{target}"' in html, f"{path} is missing a link to {target}"
|
||||
assert f'class="here">{active}<' in html, f"{path} does not mark itself active"
|
||||
Reference in new issue
Block a user