Operator: the form failed when a question was left blank. Refusing the whole submission over one blank threw away the picks that were made, and the HTML `required` on the radios blocked it in the browser before the server saw it. - answered questions recorded; blank ones land in `unanswered`; `complete` says whether the set is finished; a blank question carrying a note keeps the note - `required` dropped from both templates so the browser cannot block a partial - refused only when there is no pick anywhere AND no notes (a 400 — that would flip an open ask to answered with no decision recorded); a choice outside the option list is still an error - new ◐ partial state with an n/N count; skipped questions render as skipped - README + global CLAUDE.md tell reading sessions to check `complete` - 154 tests; v0.1.15
82 lines
5.0 KiB
HTML
82 lines
5.0 KiB
HTML
{# Shared asks panel — included by booth.html (auto-gallery view) and by
|
|
asks.html (the standalone page a VERBATIM index.html booth links to, since
|
|
a verbatim page is served as-is and can never render this inline). #}
|
|
{# ASKS. A session left multiple-choice questions here for the operator
|
|
(`<stem>.ask.json`). Open ones render as a radio form; answering POSTs to
|
|
/answer, which writes `<stem>.answer.json` for the session to read. Works
|
|
with JS off — plain form POST. Answered asks show the recorded answer and a
|
|
collapsed "change" form, since the sidecar is the CURRENT answer. #}
|
|
<section class="asks">
|
|
{% for a in asks %}
|
|
<article class="ask{% if a.answer and a.answer.complete %} is-answered{% elif a.answer %} is-partial{% elif a.error %} is-broken{% endif %}" id="ask-{{ a.stem }}">
|
|
<header class="ask-head">
|
|
<span class="ask-state">{% if a.error %}⚠ broken{% elif a.answer and a.answer.complete %}✓ answered{% elif a.answer %}◐ partial{% else %}? open{% endif %}</span>
|
|
<span class="ask-stem"><code>{{ a.stem }}.ask.json</code>{% if a.multi %} · {{ a.questions|length }} questions{% endif %}</span>
|
|
<span class="board-spacer"></span>
|
|
{% if a.answer and not a.answer.complete %}<span class="ask-part">{{ (a.questions|length) - (a.answer.unanswered|length) }}/{{ a.questions|length }}</span>{% endif %}
|
|
{% if a.answer %}<span class="ask-when">{{ a.answer.answered_at }}{% if a.answer.answered_by %} · {{ a.answer.answered_by }}{% endif %}</span>{% endif %}
|
|
</header>
|
|
{% if a.error %}
|
|
<p class="ask-error">This ask could not be read: {{ a.error }}</p>
|
|
{% else %}
|
|
{% if a.title and not a.multi %}<p class="ask-title">{{ a.title }}</p>{% endif %}
|
|
<p class="ask-prompt">{{ a.prompt }}</p>
|
|
{% if a.answer %}
|
|
<div class="ask-answer">
|
|
{% if a.multi %}
|
|
{% for q in a.questions %}{% set qa = a.answer.answers.get(q.key) %}
|
|
<div class="ask-answer-q">
|
|
<span class="ask-answer-qprompt">{{ q.prompt }}</span>
|
|
<div class="ask-answer-choice{% if not (qa and qa.choice is not none) %} is-skipped{% endif %}">{{ qa.label if (qa and qa.choice is not none) else 'left blank' }}</div>
|
|
{% if qa and qa.notes %}<pre class="ask-answer-notes">{{ qa.notes }}</pre>{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="ask-answer-choice">{{ a.answer.label }}</div>
|
|
{% endif %}
|
|
{% if a.answer.notes %}<pre class="ask-answer-notes">{{ a.answer.notes }}</pre>{% endif %}
|
|
<span class="ask-answer-file">→ <a href="{{ a.stem }}.answer.json">{{ a.stem }}.answer.json</a></span>
|
|
</div>
|
|
{% endif %}
|
|
<details class="ask-formwrap"{% if not a.answer %} open{% endif %}>
|
|
<summary class="ask-change">{% if a.answer %}change answer{% else %}answer{% endif %}</summary>
|
|
<form class="ask-form" method="post" action="/b/{{ name_url }}/answer">
|
|
<input type="hidden" name="ask" value="{{ a.stem }}">
|
|
{# On the standalone page, come back HERE — the booth's own page is a
|
|
verbatim report that cannot show the recorded answer. #}
|
|
{% if asks_page %}<input type="hidden" name="back" value="asks">{% endif %}
|
|
{% for q in a.questions %}
|
|
{% set field = 'choice.' ~ q.key if a.multi else 'choice' %}
|
|
{% set qa = a.answer.answers.get(q.key) if (a.answer and a.multi) else a.answer %}
|
|
<fieldset class="ask-q">
|
|
{% if a.multi %}<legend class="ask-q-prompt">{{ loop.index }}. {{ q.prompt }}</legend>{% endif %}
|
|
<div class="ask-options">
|
|
{% for o in q.options %}
|
|
<label class="ask-opt{% if qa and qa.choice == o.id %} is-current{% endif %}">
|
|
<input type="radio" name="{{ field }}" value="{{ o.id }}"
|
|
{% if qa and qa.choice == o.id %}checked{% endif %}>
|
|
<span class="ask-opt-main">
|
|
<span class="ask-opt-label">{{ o.label }}</span>
|
|
{% if o.detail %}<span class="ask-opt-detail">{{ o.detail }}</span>{% endif %}
|
|
</span>
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
{% if q.notes %}
|
|
<textarea class="ask-notes ask-qnotes" name="notes.{{ q.key }}" rows="2" placeholder="notes on this one (optional)">{{ qa.notes if qa else '' }}</textarea>
|
|
{% endif %}
|
|
</fieldset>
|
|
{% endfor %}
|
|
{% if a.notes %}
|
|
<textarea class="ask-notes" name="notes" rows="3" placeholder="{{ a.notes_label }} (optional)">{{ a.answer.notes if a.answer else '' }}</textarea>
|
|
{% endif %}
|
|
<div class="ask-actions">
|
|
<button type="submit" class="ask-submit">{% if a.answer %}Update answer{% else %}Submit answer{% endif %}</button>
|
|
</div>
|
|
</form>
|
|
</details>
|
|
{% endif %}
|
|
</article>
|
|
{% endfor %}
|
|
</section>
|