feat(booth): asks render INLINE in a verbatim report, placed by the author

Operator verdict on the separate /asks page: the question belongs with the
artifact it is about. A four-voice audition wants each voice's radio group
under that voice's audio, and one submit for the lot.

- booth/inline.py: data-booth-ask="stem" | "stem:key" | data-booth-ask-submit,
  plus <!-- booth:ask ... --> comments; unknown stem left alone, not blanked
- _ask_inline.html: self-contained fragments (own scoped styles, no JS), per-question
  groups bound to one form via the HTML5 form= attribute so a scattered
  multi-question ask still POSTs once
- unplaced questions and a missing submit block are appended, so a partially
  marked-up page can never produce an unsubmittable 400
- chip becomes a jump link to the first open ask; /asks page kept as a fallback
- 6 tests (one caught the partial-placement drop); v0.1.14
This commit is contained in:
vh
2026-09-09 14:16:03 -07:00
parent f99faabb80
commit d7361e8b44
6 changed files with 420 additions and 21 deletions
+26 -6
View File
@@ -161,12 +161,32 @@ Rules of the primitive:
- **A broken ask is shown as broken**, not hidden: if the JSON does not
validate, the page says why, so a session never thinks it posted a question
the operator cannot see.
- **A booth with its own `index.html` shows a chip, not the panel.** That page is
served verbatim by design, so the inline panel cannot appear on it: the Booth
injects an amber `? N open asks` chip (next to the back chip) linking to
**`/b/<name>/asks`**, a standalone page carrying the real forms. Answering
there returns there. Put the media in the booth and the ask beside it either
way — the ask is never lost, whichever shape the booth takes.
- **A booth with its own `index.html` gets the ask INLINE, where you put it.**
That page is served verbatim, so the Booth substitutes placeholders in your
markup rather than rendering the panel above a gallery it does not have:
```html
<div data-booth-ask="anchors"></div> <!-- the whole ask: every question + submit -->
<div data-booth-ask="anchors:lawson"></div> <!-- just that one question's radios -->
<div data-booth-ask-submit="anchors"></div> <!-- the notes field + submit button -->
<!-- booth:ask anchors:lawson --> <!-- comment form, same thing -->
```
Per-question fragments bind to **one** form with the HTML5 `form=` attribute,
so a four-voice audition can put each radio group under that voice's audio and
still submit all four picks in a single POST — which is what a multi-question
ask requires. The fragments ship their own scoped styles and inherit nothing
from your page. No JavaScript.
⚠ Put the placeholder **outside** any CSS grid or flex container, or it
becomes a cell in it. A sibling of the block it belongs to is right.
Placement is optional: a page with no placeholders gets the whole ask appended
at the end, so an ask is never invisible — markup only moves it somewhere
better. Mark up some questions and not others and the rest are appended too,
because a multi-question form that is missing a question is a 400 the operator
would only meet after filling it in. The amber chip stays as a jump link to the
first open ask, and `/b/<name>/asks` still renders every ask on its own page.
- Ask/answer files are not gallery items and do not count toward the booth's
item count; they render as the panel above the gallery. Answering bumps the
booth's mtime, so it lives another TTL — the session has 24h to read it.