Skip to content

wip(web): mark novel findings in the rendered answer - #115

Draft
adityak74 wants to merge 2 commits into
mainfrom
feat/novelty-marking
Draft

wip(web): mark novel findings in the rendered answer#115
adityak74 wants to merge 2 commits into
mainfrom
feat/novelty-marking

Conversation

@adityak74

Copy link
Copy Markdown
Contributor

Parked work recovered from an abandoned agent worktree. Draft on
purpose: this is here to be triaged, not reviewed.

Adds web/src/finding.ts and tests, threaded through markdown.ts and streamed-message.ts. Both of those render model output, so anything here has to build DOM nodes and go through textContent; that needs checking before this lands.

The worktree it came from was 56 or more commits behind main, so treat
the base as stale. Nothing here has been built or tested against current
main.

https://claude.ai/code/session_01KGPVQ8wUG7h36zashWYCp4

The chat streamed tool activity but not the answer. `complete_with_options`
is a blocking POST, so the browser got one `assistant` event carrying the
finished text. On a hosted model that is a couple of seconds. On
qwen3.8:27b-mlx on a laptop it is a minute of spinner and then a wall of
text, so this had to start at the model layer.

`Model::complete_streaming` is now the agent loop's call. Its default body
runs the buffered path and reports the answer as a single delta, so every
existing Model keeps working and there is one code path in the loop rather
than a streaming branch and a buffered branch that drift. `Renderer` gains
`assistant_delta`, empty by default, so the CLI is untouched. Anthropic
keeps the buffered path; its event protocol is its own work.

The part that makes a naive version worse than none: `extract_think_tags`
strips `<think>...</think>` from content, so a qwen-family model's chain of
thought never reaches the browser today. Streaming raw deltas would put it
on screen as the answer. `ThinkGate` filters it and withholds text that
could still be a tag opening, because tags arrive split across chunks. The
accumulator then rebuilds a buffered-shaped response and reuses
`parse_assistant_completion`, so the two paths cannot disagree.

Four defects found by running it, not by reading it. Two were invisible to
a green suite:

- `requestAnimationFrame` stored as a bare default parameter loses its
  receiver, so the browser threw "Illegal invocation" and nothing ever
  painted. Every test injected its own scheduler, so the default had no
  coverage until it broke in a real browser.
- Ending the streamed message on `working_done` looked right and was
  wrong: it fires before the finished answer is sent, so every answer
  rendered twice.
- Pruning fragments from the SSE backlog panicked the streaming task and
  poisoned the session mutex. That code holds an index into the vector
  across polls and says so in a comment. The backlog is append-only.
- A provider that ignores `stream` and returns a JSON body decoded as an
  empty stream: no error, no text. Now handled and reported as one delta.

All four are mutation-checked: restoring each defect fails the test written
for it. Verified in a browser against a real local model, 25 progressive
renders growing 18 to 377 characters, one message, no console errors, and
the injection suite still yields xssRan false with zero scripts, images,
iframes, handlers or anchors.

Claude-Session: https://claude.ai/code/session_01RntquUN2UV37oo4C4EUBYd
Adds web/src/finding.ts and its tests, and threads it through
markdown.ts and streamed-message.ts, which are the two paths onto the
page.

Parked from an abandoned agent worktree, 57 commits behind main
at the time it was committed. Opened as a draft so it can be triaged
rather than reviewed.

Claude-Session: https://claude.ai/code/session_01KGPVQ8wUG7h36zashWYCp4
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