Skip to content

fix(viewer): make the feedback loop trustworthy#8

Merged
benvinegar merged 3 commits into
mainfrom
feat/viewer-loop-trust
Jun 12, 2026
Merged

fix(viewer): make the feedback loop trustworthy#8
benvinegar merged 3 commits into
mainfrom
feat/viewer-loop-trust

Conversation

@benvinegar

Copy link
Copy Markdown
Member

What

Four fixes from a UI/UX pass of the viewer, all centered on the product's hardest invariant — feedback is never silently lost — and on awareness when the user isn't staring at the tab.

  1. A failed comment send no longer destroys the message. Previously the composer cleared the input before the POST and a failure showed nothing — the user's words were just gone, with no sign anything went wrong (verified by simulating a network failure). Comments now echo into the thread immediately (dimmed pending style until the POST confirms, deduped against the SSE refetch by comment id), and on failure the text is restored to the input with an error toast. Both composers (snippet thread and session thread) now share one wireComposer().
  2. New snippets stop yanking the scroll position. scrollIntoView fired unconditionally on every published snippet, stealing the viewport mid-read. The stream now only follows when the user is already near the bottom; otherwise a "new snippet ↓" pill appears, which jumps to the card on click and clears itself when the user scrolls down on their own.
  3. Background activity badges the tab title. Activity the user isn't looking at — another session, or any session while the tab is hidden — adds to the unread set, and the title shows (n) sideshow. Returning to the tab (or selecting the session) clears it. Previously a backgrounded tab gave zero signal that the agent had published or replied.
  4. SSE reconnects now resync. onopen only recolored the live dot, so events that fired during a connection gap were permanently missing from a live-looking board. On reconnect the viewer refetches the session list and reconciles the selected session's snippets (pruning deleted cards) and comments — both paths dedupe, so a resync with nothing missed is a no-op.

Tests

Four new e2e tests (chromium + webkit): failure-restores-input then retry succeeds, optimistic echo with a held-open POST settles into exactly one confirmed comment, pill-instead-of-yank with reading position asserted, and the title badge lifecycle. 18/18 e2e pass; unit tests, both typecheck programs, lint, and format all green. Reconnect resync is code-reviewed but not e2e-covered (would need to kill the SSE stream mid-test).

Also verified live in a browser against seeded demo data: simulated failed send, pill appearance/click, and title badge from a cross-session publish.

🤖 Generated with Claude Code

benvinegar and others added 3 commits June 11, 2026 16:51
Four fixes from a UI/UX pass, all guarding the publish-comment loop:

- A comment that failed to send was silently lost: the input cleared
  before the POST and a failure showed nothing. Comments now echo
  immediately (pending until confirmed; deduped against the SSE
  refetch by comment id) and on failure the text returns to the input
  with an error toast.
- New snippets always scrollIntoView, yanking the user mid-read. The
  stream now only follows when already near the bottom; otherwise a
  "new snippet" pill offers the jump and clears on click or on
  scrolling to the bottom.
- Activity the user isn't looking at (another session, or any session
  while the tab is hidden) badges the tab title with the unread count;
  returning to the tab clears the selected session.
- SSE reconnects only recolored the live dot, so events from the gap
  were silently missing from a live-looking board. onopen now refetches
  the session list and reconciles the selected session's snippets and
  comments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conflict in viewer/index.html: both sides added elements after #app —
kept main's aria-live toast attributes and this branch's new-snippet
pill. Verified the auto-merged JS keeps both feature sets (focus
restore + title badge in renderSidebar, iframe.title + scroll pill in
upsertCard). Full suite green: 49 unit, typecheck, lint, 18/18 e2e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@benvinegar benvinegar merged commit f9d7d43 into main Jun 12, 2026
6 checks passed
benvinegar pushed a commit that referenced this pull request Jun 12, 2026
… the Solid viewer

main changed the old inline-JS viewer this branch replaced; the conflict
resolution re-implements both changes in viewer/src/:

- optimistic comment echo (pending until POST confirms, deduped against
  the SSE refetch by id), failed sends restore the input with a toast
- new snippets only auto-scroll near the bottom; otherwise the
  "new snippet" pill offers the jump
- unread activity badges the tab title; returning to the tab clears the
  selected session; hidden-tab activity counts as away
- SSE reconnect resyncs the session list, snippets, and comments
- sidebar collapses into an off-canvas drawer behind a top bar below
  700px; hover-only actions stay visible on narrow/touch screens

All 10 e2e tests (5 new from main) pass on chromium.

https://claude.ai/code/session_01ApwZm1DNZoCQTJHha19thS
benvinegar added a commit that referenced this pull request Jun 12, 2026
…ild (#11)

Rewrites the viewer from inline vanilla JS to Solid components in
viewer/src/, typed against server/types.ts. Vite + vite-plugin-singlefile
builds one self-contained viewer/dist/index.html, so both runtimes keep
serving the viewer as a single in-memory document — no static-asset
routes; auth and withOrigin rewriting unchanged.

Includes the re-implementation of main's feedback-loop fixes (#8) and
mobile drawer (#9) in the Solid viewer; the full e2e suite covers both.
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