refactor(web/ui): migrate chat messages to fine-grained createStore #121

Merged
salmaelsoly merged 1 commit from integration_streaming_store_finegrained into integration 2026-06-15 10:31:38 +00:00
Member

Summary

Migrates the chat thread's messages from a coarse createSignal<Msg[]> (whole-array, by-reference replacement) to a fine-grained SolidJS createStore<Msg[]>. Per-token, per-tool, and finalize updates now mutate message fields in place via produce, so SolidJS <For> no longer remounts per-message subtrees (LiveJobPanel, MessageInsights, ThinkingPane) on every update. This removes the remount-by-reference root cause behind the "card keeps refreshing" flicker, on top of the band-aids already present.

This is the smallest high-value change that fixes the root cause. It intentionally does NOT build the broader "per-session typed event channel / SPMC wire" described in the issue, nor consolidate currentStreamingId + jobStreamJobId. The issue should not be auto-closed on this alone.

Refs #37

Changes

  • store.ts: messages is now createStore<Msg[]>; all 13 setMessages mutation sites rewritten to in-place produce mutations; conversation load uses reconcile(mapped, { key: "id" }) to preserve row identity; appends use produce(list => list.push(...)); 3 internal messages() reads converted to proxy access.
  • ChatThread.tsx: messages() -> messages; <For each={messages}>; autoscroll memo tracks messages.length.
  • ConversationWork.tsx, TopBar.tsx, LibraryPage.tsx: messages() -> messages.
  • Regenerated static/ bundle.

Hot-path conversions preserve existing semantics exactly: flushStreamBuffer keeps the x.jobId skip + inline-think re-derivation + idempotent set-from-full-buffer; turn:end keeps the streaming/jobId skip and the finalized -> drainQueue flag; tool activity updates mutate .activities in place.

Test Results

  • TypeScript typecheck: 14 errors before, 14 after; 0 in changed files (pre-existing errors are in unrelated components). Zero new type errors.
  • Production build (vite build): PASS — 101 modules transformed, bundle emitted.
  • Grep: zero messages() call-style reads and zero signal-updater forms remain.
## Summary Migrates the chat thread's `messages` from a coarse `createSignal<Msg[]>` (whole-array, by-reference replacement) to a fine-grained SolidJS `createStore<Msg[]>`. Per-token, per-tool, and finalize updates now mutate message fields in place via `produce`, so SolidJS `<For>` no longer remounts per-message subtrees (LiveJobPanel, MessageInsights, ThinkingPane) on every update. This removes the remount-by-reference root cause behind the "card keeps refreshing" flicker, on top of the band-aids already present. This is the smallest high-value change that fixes the root cause. It intentionally does NOT build the broader "per-session typed event channel / SPMC wire" described in the issue, nor consolidate `currentStreamingId` + `jobStreamJobId`. The issue should not be auto-closed on this alone. ## Related Issue Refs https://forge.ourworld.tf/lhumina_code/hero_shrimp/issues/37 ## Changes - `store.ts`: `messages` is now `createStore<Msg[]>`; all 13 `setMessages` mutation sites rewritten to in-place `produce` mutations; conversation load uses `reconcile(mapped, { key: "id" })` to preserve row identity; appends use `produce(list => list.push(...))`; 3 internal `messages()` reads converted to proxy access. - `ChatThread.tsx`: `messages()` -> `messages`; `<For each={messages}>`; autoscroll memo tracks `messages.length`. - `ConversationWork.tsx`, `TopBar.tsx`, `LibraryPage.tsx`: `messages()` -> `messages`. - Regenerated `static/` bundle. Hot-path conversions preserve existing semantics exactly: `flushStreamBuffer` keeps the `x.jobId` skip + inline-think re-derivation + idempotent set-from-full-buffer; `turn:end` keeps the streaming/jobId skip and the `finalized` -> `drainQueue` flag; tool activity updates mutate `.activities` in place. ## Test Results - TypeScript typecheck: 14 errors before, 14 after; 0 in changed files (pre-existing errors are in unrelated components). Zero new type errors. - Production build (`vite build`): PASS — 101 modules transformed, bundle emitted. - Grep: zero `messages()` call-style reads and zero signal-updater forms remain.
Replace createSignal<Msg[]> with createStore<Msg[]> so per-message
updates mutate fields in place via produce instead of replacing message
objects. Eliminates the remount-by-reference fragility behind the
"card keeps refreshing" flicker. Conversation reload uses reconcile by
id to preserve row identity. Frontend-only; no backend changes.

#37
salmaelsoly force-pushed integration_streaming_store_finegrained from 7869d84bbc to 095f4592f7 2026-06-15 10:31:07 +00:00 Compare
salmaelsoly merged commit ff29b48a70 into integration 2026-06-15 10:31:38 +00:00
salmaelsoly deleted branch integration_streaming_store_finegrained 2026-06-15 10:31:43 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_shrimp!121
No description provided.