Skip to content

A customer's question is drawn once, however long the answer takes - #35

Merged
thankywal merged 1 commit into
mainfrom
a-question-is-drawn-once
Sep 3, 2026
Merged

thankywal merged 1 commit into
mainfrom
a-question-is-drawn-once

Conversation

@thankywal

Copy link
Copy Markdown
Owner

What a visitor saw

On the demo shop's widget, one question typed → two identical bubbles → one answer.

The mechanism

The widget draws the question straight away and calls /send. While the model thinks (seconds), the six-second poll — there to collect what a person on the shop's side may type — asked for every row after the last it knew. The Worker had already stored the question, handed it back, and the widget drew it again. Two writers to one transcript.

It cannot happen on the very first message (no session to poll for until the Worker has answered once); from the second message on it happens whenever the reply outlasts the time to the next tick — which is most replies.

The rule now (widget.ts, 24 lines)

  • A turn that is out is drawn by its own answer. While a send is in flight the poll does not go out, and one already out that returns mid-send is discarded — the send's answer carries the reply and the seq covering both rows.
  • Every row is drawn once, by seq. A late poll brings nothing new.

Tests — 960 passing (+4)

a-question-is-drawn-once.test.ts runs the real widget script against a stub of the DOM it touches, with fetch under the test's control so the poll lands mid-send:

  • drawn once when the tick fires during the send;
  • drawn once when a poll asked before the send is answered during it;
  • a person's handover reply from the shop is drawn once and not again on the next poll;
  • after a send, the next poll asks from the reply's seq.

With the bug put back (git stash the widget), 3 of 4 fail. The existing new Function(script) syntax check still passes.

After merge

bash scripts/first-sync.sh thankywal/muxel-demo → Cloudflare rebuilds the demo Worker (~3 min) → the widget at /w/… serves the new script.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NDkiHs276KQhSWxQnHQdLg

Seen on the demo shop the night the film went in: a visitor typed one question
and saw it twice, one bubble under the other, and then one answer.

The widget draws what was typed straight away and asks the Worker for the
reply. While the model thinks, the six-second poll — there to collect what a
person on the shop's side may type — asked for every row after the last one it
knew, and the Worker, which had already stored the question, handed it straight
back, so the widget drew it again. Two writers to one transcript. It cannot
happen on a visitor's first message, because there is no session to poll for
until the Worker has answered once; from the second message on it happened
whenever the reply took longer than the time to the next tick.

The rule now:

- A turn that is out is drawn by its own answer. While a send is in flight the
  poll does not go out, and a poll that was out already and comes back during
  the send is discarded: the send's answer carries the reply and the seq that
  covers both rows.
- Every row is drawn once, by seq. A poll answered late, after a send has moved
  lastSeen past its rows, brings nothing new.

`a-question-is-drawn-once.test.ts` runs the real widget script against a stub
of the little DOM it touches, with fetch under the test's control so the poll
can be made to land in the middle of a send. With the bug put back, three of
its four cases fail. 960 tests, lint and typecheck clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDkiHs276KQhSWxQnHQdLg
@thankywal
thankywal merged commit 890b10d into main Sep 3, 2026
2 checks passed
@thankywal
thankywal deleted the a-question-is-drawn-once branch September 3, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant