A thinking block that says how long and how much
Each block reports its own round now. `reasoning_ms` was the reply's first burst, written once, so on the fifteen-block reply GPT-OSS actually produces only the first could claim a duration and the other fourteen said "Thought" and nothing at all. `Generation.thinking_ms` accumulates per round and `close_step` stamps it cumulatively, so steps.py diffs it exactly as it already diffs the three lengths beside it. The interval between a round's first and last reasoning delta, deliberately, not a sum of gaps between deltas -- that would count the network's latency as the model's thinking. While it runs: "Thinking" with an ellipsis that types itself, and the seconds and tokens climbing beside it. The ellipsis is a `content` keyframe, so there is no timer to start, stop or clean up when the block is swapped away -- it stops existing when the element does. The numbers come from a `think` frame, and `round_thinking_ms` is written by the producer rather than computed by the follower from a start time: a model that has stopped thinking and moved on to a tool should show a settled number, not a clock that keeps running. Tokens read exactly up to 200 and as `0.4k` above it, from one helper shared by the live label and the stored one, so the two cannot drift into two conventions. The live duration is terser than the finished one -- `6s` against `6 seconds` -- because it sits beside an animating word and changes every second, where "less than a second" flickering into "1 second" reads as a glitch. Checked against the real endpoint: fourteen marks carrying 919ms through 14223ms, per-block labels from "less than a second · 111" to "4 seconds · 0.5k", and the live frames resetting each round rather than accumulating. 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 # 1500 tests, ~92s
|
||||
pytest # 1513 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;
|
||||
@@ -276,6 +276,31 @@ sees that flag and re-renders the bubble from the database, so the row has to be
|
||||
authoritative first. The other order silently showed the previous turn's stored
|
||||
metrics.
|
||||
|
||||
**A thinking block reports its own round.** `Message.reasoning_ms` was the
|
||||
reply's *first* burst, written once, so on a forty-round reply only the first
|
||||
block could honestly claim a duration and the rest said "Thought" and nothing.
|
||||
`Generation.thinking_ms` accumulates per round — the interval between a round's
|
||||
first and last reasoning delta, not a sum of per-delta gaps, which would count
|
||||
the network's latency as the model's thinking — and `close_step` stamps it
|
||||
cumulatively, so `steps.py` diffs it exactly as it diffs the three lengths. The
|
||||
last round closes no mark (it is the round that stopped calling tools), so its
|
||||
duration is what the reply spent beyond the last mark, which is why `_persist`
|
||||
stores `thinking_ms` into `reasoning_ms` in preference: it is the same
|
||||
measurement done properly.
|
||||
|
||||
The live block's numbers come from a `think` frame, and `round_thinking_ms` is
|
||||
written by the *producer*. Computing it in the follower from a start time would
|
||||
keep the clock running after the model had stopped thinking and moved on to a
|
||||
tool — a timer rather than a measurement. The animated ellipsis is a `content`
|
||||
keyframe in CSS: no timer to start, stop or clean up when the block is swapped
|
||||
away, and it stops existing when the element does.
|
||||
|
||||
**`include_open` and `live` are two questions, and conflating them put a caret
|
||||
on every finished reply.** One says whether to emit the trailing step, the other
|
||||
whether it is still being written. `for_message` wants the first without the
|
||||
second. The test that existed asserted the caret was on the *right* step and
|
||||
passed; it never asked whether a finished reply should have one.
|
||||
|
||||
**A reply is a sequence of steps, and the marks are what make it one.** The
|
||||
three stores a reply writes into -- `content`, `reasoning`, `tool_events` -- are
|
||||
each append-only and each correct, and none of them records *interleaving*. So a
|
||||
@@ -971,6 +996,21 @@ came from an ancestor — the same family as the trigger bound where the event d
|
||||
not go, and the reason that test asserts the resolved property rather than the
|
||||
attributes.
|
||||
|
||||
**And htmx events bubble, which is the same lesson a second time.** The form
|
||||
also declares `hx-on::after-request` so it can clear itself after sending — and
|
||||
`htmx:afterRequest` bubbles, so every request made by anything inside the form
|
||||
ran that handler. Six things do: the two scope switches, "ask me about these
|
||||
again", the agent mode select, the effort select and the jobs chip. Changing the
|
||||
mode or the effort while typing therefore called `this.reset()` on the composer
|
||||
and dragged the view to the bottom, and had done for as long as those controls
|
||||
existed; the chip only made it periodic, and therefore visible. The guard is
|
||||
`event.target === this`. A form's own handler answers its own request.
|
||||
|
||||
Both bugs had correct markup whose meaning came from an ancestor. That is why
|
||||
both tests assert the *resolved* behaviour — one walks the form and refuses a
|
||||
descendant that fetches without a target, the other drives the handler under a
|
||||
DOM stub and fires the event from a descendant.
|
||||
|
||||
**Background jobs have a chip in the composer row and a panel behind it.** A job
|
||||
runs detached for as long as it takes and the only way to see one used to be
|
||||
asking the model to call `job_list` — something that outlives the reply that
|
||||
|
||||
Reference in New Issue
Block a user