{% extends "base.html" %} {% from "_provenance.html" import provenance %} {# 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='') -%}
{%- endmacro %} {# The per-item MARK controls: flag (the operator pointing at this one) and a note field. Same macro discipline as blurtoggle above — three item branches, one definition. `marks` here is THIS item's marks, from item_marks. #} {% macro markcontrols(name_url, it, marks, cls='') -%} {% set flagged = marks | selectattr('shape', 'equalto', 'flag') | list | length > 0 %}
{%- endmacro %} {# An item's notes, rendered BESIDE the artifact — the 2026-09-09 ruling that a judgment belongs with the thing it is about, applied to notes as well as picks. The add-field is a
so 270 tiles do not each carry an open textarea. #} {% macro marknotes(name_url, it, marks) -%} {% for m in marks if m.shape == 'note' %}
{{ m.text }}
{% endfor %}
+ note
{%- endmacro %} {% block title %}{{ name }} · The Booth{% endblock %} {% block content %}
‹ all booths {# The manifest's TITLE is the display name; the directory name stays visible beside it because that is the identity the operator navigates by and refers to positionally, and losing it would be losing the thing the URL says. Index cards keep the directory name alone for the same reason. #} {% if manifest and not manifest.error and manifest.title and manifest.title != name %}

{{ manifest.title }} {{ name }}

{% else %}

{{ name }}

{% endif %} {% if uploaded %}⬆ pickup {% endif %}{% if board %}{{ board|length }} link{{ '' if board|length == 1 else 's' }}{% if items %} · {{ items|length }} file{{ '' if items|length == 1 else 's' }}{% endif %}{% else %}{% if marks_open %}{{ marks_open }} open · {% endif %}{{ items|length }} item{{ '' if items|length == 1 else 's' }} · expires in {{ expires_in|dur }}{% endif %} {% if items %}⬇ zip{% endif %} {{ provenance(manifest) }} {# A durable multi-writer board gets no one-click wipe — same rule as the kept lane on the index. Remove rows with the per-row ×, or release the board from the index and wipe it from there. #} {# Promote or release without going back to the index. `next` keeps you on this page instead of bouncing you to /. #} {% if kept %}
{% else %}
{% endif %} {% if not board %}
{% endif %}
{% if uploaded %}
📦 Pickup {{ name }} — download files below, or on nh3-dev grab ~/booth-data/{{ name }}/
{% endif %} {# The marks panel: the session's questions, the operator's notes, and the way back to the flagged items. Always rendered on a gallery booth — the add-note field is a control, not a result, so it has to be there before the first mark exists. #} {# `marks or not board`: the standing link board renders as a board rather than a gallery, and the add-note control would be noise on it — but the suppression was unconditional, so a pick declared on a booth that happens to carry a links.md had no form to answer it and nothing said so. #} {% if marks or not board %} {% include "_marks.html" %} {% endif %} {% if board %} {# THE STANDING LINK BOARD. Every agent session on the fleet appends here, so this is the one booth where the useful granularity is the ROW, not the folder. Rendered as real UI rather than a markdown blob so a dead link can be removed without hand-editing the file — and so provenance (who posted it, when) is readable at a glance, which is the whole reason a bare URL three days old is useless. ORDER: pinned rows first, then newest-first (order_for_display). Pin a row with the ★ so the ones you care about stop scrolling off the bottom. ONE
, not one-per-row: checkboxes drive the bulk delete, while the per-row × and ★ are submit buttons with their own `formaction`. That keeps all three actions in a single form (nested forms are invalid HTML) AND lets every one work with JS off — JS only adds select-all and the live count. Every action posts a CONTENT ID, never a row number: another session can append between this page rendering and a click, and an index would then hit a neighbour. An id matches the row the operator saw, or nothing. #} {% set pinned_n = board | selectattr('pinned') | list | length %}
{{ board|length }} link{{ '' if board|length == 1 else 's' }}{% if pinned_n %} · {{ pinned_n }} pinned{% endif %} pinned first · newest on top · ★ pins a row · tick rows to delete
{% for e in board %}
{{ e.desc }}
{{ e.url }}
{% if e.who %}{{ e.who }}{% endif %} {% if e.when %}{{ e.when }}{% endif %}
{% endfor %}
{% endif %} {% if not items and not board and not marks %}
This booth is empty.
{% elif items %} {# `elif items` and not a bare `else`: a board booth has NO gallery items (its links.md is rendered as the board above and filtered out), so a plain else would emit an empty