fix(booth): put the blur toggle on every item kind, and make it look like a control

The operator asked "no UI option to blur/unblur?" and was right twice over.

MISSING ENTIRELY ON TWO OF THREE BRANCHES. booth.html renders docs, media and
everything-else through separate <figure> blocks. The toggle went into the
media branch only, so inline docs -- the branch that puts readable text straight
on the page, the one that needs blur most -- had no control at all, and `other`
files only got a caption row if they happened to carry a caption.

This is the SECOND time this feature shipped having patched some branches and
not others; the blurred class itself had the same gap one commit ago. So the
toggle is now a single Jinja macro called from all three sites, which makes
"patched two of three" impossible rather than merely unlikely, and
test_every_item_kind_gets_exactly_one_blur_toggle counts toggles against figures
across mixed kinds so a fourth branch cannot quietly skip it.

INVISIBLE WHERE IT DID RENDER. v1 was a bare `◌` at 0.78rem in --muted with no
border, no label and no hover affordance. It now reads `◌ blur` / `◉ blurred`
with a border, matching the other per-item controls. A control nobody can find
is a control that is not there.

Docs get it in the doc bar beside ⤢ ⬇ ✕, with stopPropagation so submitting it
does not collapse the <details> it lives inside.

Verified live on all three kinds: 3 figures, 3 toggles, and the POST round trip
blurs and un-blurs. 167 tests pass.
This commit is contained in:
vh
2026-09-19 23:50:32 -07:00
parent 2e7fd7157e
commit 271cb11b70
4 changed files with 88 additions and 12 deletions
+11 -1
View File
@@ -52,7 +52,17 @@ booth blur <name> <file>... # hide from a glance
booth unblur <name> <file>...
```
Or the ◌ / ◉ toggle in each item's caption row on the booth page.
Or the **`◌ blur` / `◉ blurred`** button on every item in the booth page —
in the caption row for images, video, audio and plain files, and in the doc bar
beside ⤢ ⬇ ✕ for inline docs.
⚠ The toggle is emitted by ONE Jinja macro (`blurtoggle`) called from all three
item branches. booth.html renders docs, media and everything-else through
separate `<figure>` blocks, and this feature was twice shipped having patched
only some of them — first the blur class, then the toggle itself. Add a fourth
branch and you must call the macro from it;
`test_every_item_kind_gets_exactly_one_blur_toggle` counts toggles against
figures across mixed kinds and will fail if you don't.
- **State** is `.blurred` in the booth dir — one booth-relative item path per
line, the same filesystem-is-the-state idiom as `.pins` and `.forever`. An
+11 -4
View File
@@ -270,10 +270,17 @@
font:inherit;font-size:.72rem;line-height:1;padding:.24rem .5rem;cursor:pointer;
border:1px solid var(--line);border-radius:.3rem;background:var(--bg);color:var(--fg)}
.item.blurred .reveal:hover{background:var(--aus-blue);color:var(--fg-on-accent)}
.blurtoggle{display:inline;margin:0}
.blurtoggle button{font:inherit;font-size:.78rem;line-height:1;padding:0 .2rem;
cursor:pointer;border:0;background:none;color:var(--muted)}
.blurtoggle button:hover{color:var(--fg)}
/* Legible on purpose. v1 was a bare muted glyph with no border and no label,
and the operator's reaction to it was "no UI option to blur/unblur?" — a
control nobody can find is a control that is not there. */
.blurtoggle{display:inline-block;margin:0}
.blurtoggle button{font:inherit;font-size:.7rem;line-height:1;padding:.2rem .4rem;
cursor:pointer;border:1px solid var(--line);border-radius:.3rem;
background:var(--bg);color:var(--fg);white-space:nowrap}
.blurtoggle button:hover{background:var(--aus-blue);color:var(--fg-on-accent)}
/* In the doc bar it sits beside ⤢ ⬇ ✕ and should not out-shout them. */
.doc-bar .blurtoggle{margin-left:.35rem}
.doc-bar .blurtoggle button{font-size:.66rem;padding:.14rem .34rem}
/* Cover thumbs on the index inherit the blur so the front page cannot undo it. */
.blurred-thumb{filter:blur(16px)}
/* opaque dark control-scrim + always-light glyph — legible over any thumbnail
+26 -7
View File
@@ -1,4 +1,18 @@
{% extends "base.html" %}
{# The blur toggle, defined ONCE. There are three item branches in this file
(doc / media / other) and the first cut of this feature patched only one of
them, so docs rendered with no control at all. A macro makes "patched two of
three" impossible rather than merely unlikely. #}
{% macro blurtoggle(name_url, it, cls='') -%}
<form class="blurtoggle {{ cls }}" method="post" action="/b/{{ name_url }}/blur">
<input type="hidden" name="f" value="{{ it.name }}">
<input type="hidden" name="on" value="{{ '0' if it.blurred else '1' }}">
<button title="{{ 'un-blur this item' if it.blurred else 'blur this item — cosmetic only, the file is still served' }}"
aria-label="{{ 'un-blur' if it.blurred else 'blur' }} {{ it.name }}"
>{{ '◉ blurred' if it.blurred else '◌ blur' }}</button>
</form>
{%- endmacro %}
{% block title %}{{ name }} · The Booth{% endblock %}
{% block content %}
<div class="boothhead">
@@ -109,6 +123,7 @@
<span class="doc-spacer"></span>
<a class="doc-act" href="view?f={{ it.url }}" title="open full page">⤢</a>
<a class="doc-act" href="{{ it.url }}" download title="download {{ it.name }}">⬇</a>
{{ blurtoggle(name_url, it, 'doc-act') }}
<button type="button" class="doc-act doc-close" title="close (hide for now)" aria-label="close">✕</button>
</summary>
{% if it.rendered_html %}
@@ -142,17 +157,17 @@
<a class="dl" href="{{ it.url }}" download>⬇ {{ it.name }}</a>
{% endif %}
{% if it.kind == 'other' %}
{% if it.caption %}<figcaption><span class="cap-text">{{ it.caption }}</span></figcaption>{% endif %}
{# Always render the caption row now: it carries the blur toggle, and
"no caption" is not a reason to deny an item its controls. #}
<figcaption>
{% if it.caption %}<span class="cap-text">{{ it.caption }}</span>{% endif %}
{{ blurtoggle(name_url, it) }}
</figcaption>
{% else %}
<figcaption>
<a class="dl-link" href="{{ it.url }}" download title="download {{ it.name }}">⬇</a>
<span class="cap-text">{{ it.caption or it.name }}</span>
<form class="blurtoggle" method="post" action="/b/{{ name_url }}/blur">
<input type="hidden" name="f" value="{{ it.name }}">
<input type="hidden" name="on" value="{{ '0' if it.blurred else '1' }}">
<button title="{{ 'un-blur this item' if it.blurred else 'blur this item (cosmetic only — the file is still served)' }}"
aria-label="{{ 'un-blur' if it.blurred else 'blur' }} {{ it.name }}">{{ '◉' if it.blurred else '◌' }}</button>
</form>
{{ blurtoggle(name_url, it) }}
</figcaption>
{% endif %}
</figure>
@@ -200,6 +215,10 @@
"collapse". Collapse stays available via the rest of the summary bar. With
JS off the button is inert and collapse via <details> still works. */
(function () {
/* A form inside <summary> would otherwise collapse the doc on submit. */
document.querySelectorAll('.doc-bar .blurtoggle').forEach(function (f) {
f.addEventListener('click', function (ev) { ev.stopPropagation(); });
});
document.querySelectorAll('.doc-close').forEach(function (btn) {
btn.addEventListener('click', function (ev) {
ev.preventDefault();
+40
View File
@@ -1447,3 +1447,43 @@ def test_blur_applies_to_inline_docs_not_just_images(client):
assert body.count('<figure class="item item-doc blurred"') == 1, (
"exactly the blurred doc, not every doc"
)
def test_every_item_kind_gets_exactly_one_blur_toggle(client):
"""The regression guard for this whole feature's recurring bug.
booth.html has THREE item branches — doc, media, other — and each pass at
this feature patched some and missed others: first the blurred class landed
on media only, then the toggle landed on media only and the operator asked
"no UI option to blur/unblur?". A count assertion across mixed kinds is the
check that actually catches it; a spot check on one item never will.
"""
c, root = client
d = root / "bo"
d.mkdir()
(d / "note.txt").write_text("doc branch") # -> item-doc
_png(d / "pic.png") # -> item-image
(d / "blob.bin").write_bytes(b"\x00\x01binary") # -> item-other
body = c.get("/b/bo/").text
figures = body.count('<figure class="item item-')
toggles = body.count('class="blurtoggle')
assert figures == 3, f"expected all three kinds to render, got {figures}"
assert toggles == figures, (
f"{figures} items but {toggles} blur toggles — a branch was missed again"
)
def test_blur_toggle_posts_the_opposite_state(client):
"""The button must flip, not just set. A toggle hard-coded to on=1 looks
identical in the markup and silently cannot un-blur."""
c, root = client
d = root / "bo"
d.mkdir()
_png(d / "pic.png")
assert 'name="on" value="1"' in c.get("/b/bo/").text
set_blurred(d, "pic.png", True)
body = c.get("/b/bo/").text
assert 'name="on" value="0"' in body, "a blurred item must offer un-blur"
assert "◉ blurred" in body