A question that offers real choices, and says how many you may take
Three things about `ask_user`, all of them about the card being answerable rather than about the tool being callable. Options are required now, and they are objects: a label, and a line of description where the label alone does not say what choosing it would mean. "Rewrite it" and "Patch it" are two words that do not tell you which one loses your uncommitted work. They stack one per line, because a row of chips has nowhere to put the second line and no room to read the first. The model says whether they are exclusive. Only it knows whether its options are alternatives or a set, and the card has to show which -- a radio group offered where checkboxes were meant loses every answer but one. Exclusive is the default, being the cheaper mistake. A `multiple` question posts the same field name once per ticked box, so the endpoint gathers choices into a list; the `setdefault` it did before kept the first and dropped the rest, which is an answer that says something the reader did not. And "Something else" is added here, on every question, with the box behind it revealed by `:has()` and no JavaScript at all. The model is told never to write an "other" option of its own, because its version would be a choice with no box behind it -- a word submitted that means nothing. It carries a sentinel rather than an answer, and the endpoint swaps in what was typed beside it, or drops it when the box was left empty rather than telling the model the answer is "__other__". Typing no longer beats picking. That rule belonged to a box that was always visible next to the options; this one only exists once its own option is chosen, so picking is the answer and the box is one of the things you can pick. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ lembas info # paths + counts, useful when confused
|
||||
lembas secret-key # generate LEMBAS_SECRET_KEY
|
||||
lembas create-admin # create or promote an admin
|
||||
|
||||
pytest # 1483 tests, ~90s
|
||||
pytest # 1499 tests, ~92s
|
||||
# PLAN.md tracks what is and is not built
|
||||
ruff check . # lint (line length 100)
|
||||
python scripts/build_artwork.py # regenerate artwork (SVG + PWA icons;
|
||||
@@ -375,9 +375,37 @@ Each becomes an `Item` with its own `key`; several items can share an `index`
|
||||
because they belong to one call, and one tool turn answers them all with each
|
||||
answer quoted beside its question. Asking one at a time would cost a round trip
|
||||
and an interruption each, and answering the third would mean having forgotten
|
||||
the first. `_questions_in` reads the singular form and bare strings too: a small
|
||||
model sends something close to the schema rather than the schema, and getting it
|
||||
wrong costs a whole round trip to show a card that says nothing.
|
||||
the first. `_questions_in` reads the singular form and bare strings too, and
|
||||
`_options_in` reads an option written as a bare string as well as one written as
|
||||
`{label, description}`: a small model sends something close to the schema rather
|
||||
than the schema, and getting it wrong costs a whole round trip to show a card
|
||||
that says nothing.
|
||||
|
||||
**"Something else" is added by the template, never by the model, and that is why
|
||||
the model is told not to offer one.** Every question gets it, with a text box
|
||||
behind it revealed by `:has()` — no JavaScript, and nothing that can fall out of
|
||||
step with the control. An "Other" the model wrote would be an option with no box
|
||||
behind it: a choice that submits a word and means nothing. It carries
|
||||
`interaction.OTHER` as its value rather than an answer, and the endpoint replaces
|
||||
it with whatever was typed beside it — or drops it entirely when the box is
|
||||
empty, because telling the model the answer is `__other__` is exactly the shape
|
||||
of thing it would try to act on.
|
||||
|
||||
Options are **required** now, and stacked one per line rather than in a row: an
|
||||
option carries an optional `description`, and a row of chips has nowhere to put
|
||||
the second and no room to read the first. A question with no options is a blank
|
||||
box, which asks the reader to do the thinking the model was meant to do.
|
||||
|
||||
**The model says whether its options are exclusive**, because only it knows
|
||||
whether they are alternatives or a set — `multiple` picks radios or checkboxes,
|
||||
and the default is exclusive because that is the cheaper mistake: a radio group
|
||||
where checkboxes were meant costs one clarifying round, while checkboxes for
|
||||
alternatives invite an answer that contradicts itself. A `multiple` question
|
||||
posts the same field name once per ticked box, so the endpoint gathers `choice.`
|
||||
fields into a **list** and joins them; the `setdefault` it used to do kept the
|
||||
first and lost the rest, which is an answer that says something the reader did
|
||||
not. Typing no longer beats picking — that rule belonged to a box that was always
|
||||
visible, and this one only exists when its own option is chosen.
|
||||
|
||||
**A paused reply is deliberately not `done`.** That is what lets a page reload
|
||||
reattach to it. Its *timeout* is what stops it lingering, not `_prune`, which
|
||||
|
||||
Reference in New Issue
Block a user