fix(conversations): broadcast new chat to list live (#155)#255
Open
Ovaculos wants to merge 2 commits into
Open
fix(conversations): broadcast new chat to list live (#155)#255Ovaculos wants to merge 2 commits into
Ovaculos wants to merge 2 commits into
Conversation
Replace the fs.watch + 500ms-debounce model with a pull-on-demand index. Each `list` / `get` / `search` reconciles in-memory state with the directory: stat every conversation file, reuse cached entries when mtime is unchanged, re-read the header for new or modified files, drop entries whose files have disappeared. Removes a class of timing races: `fs.watch` delivery is variable (especially under FSEvents on macOS), the 500ms debounce window let write-then-list refetches return stale data, and the prior `flushPending` shortcut had a concurrent-caller gap (caller B could share caller A's in-flight snapshot and miss a filename queued mid-flush). `get(id)` keeps an `id → filePath` map so the common callers (`handleGet`, `handleFork`, `handleUpdate`) stat one file instead of walking the directory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A brand-new conversation did not appear in the Conversations list until the user refreshed or switched tabs (NimbleBrainInc#155). The runtime emitted the data.changed signal onto the per-request chat sink, which never reaches the /v1/events SSE channel that useDataSync consumes. Route the emit through the runtime's default sink (the one api/server.ts wraps for /v1/events). Fire once right after the user message is persisted so the conversation surfaces with its first-message preview as the label, and once more after auto-title generation settles so the label flips to the generated title. Two adjacent issues surfaced during investigation and are filed separately: - NimbleBrainInc#253: auto-generated titles often contain assistant response content instead of a short summary (pre-existing prompt-shape bug) - NimbleBrainInc#254: mid-turn conversation switch bleeds the streaming response into the destination chat (pre-existing client-side state contamination) Supporting hardening in this commit, in service of the broadcast both reaching its destination and surviving along the way: - FaultIsolatedSink wraps each sink in the defaultEvents fan-out so a logger throw can't abort the SSE-broadcast wrap downstream. Engine-time sink chain stays loud. - generateTitle uses AbortSignal.timeout to hard-cap a hung fast-model call so the title-block .finally always fires. - Dead chat-stream data.changed branch removed from handlers.ts (nothing relays it now; confirmed via grep). Closes NimbleBrainInc#155 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #155 — new conversations now appear in the Conversations list without a manual refresh.
Root cause: the runtime emitted
data.changedonto the per-request chat sink, which never reaches the/v1/eventsSSE channeluseDataSyncconsumes. The fix routes the emit through the runtime's default sink. Two broadcasts per new conversation — once right after the user message is persisted (label = first-message preview) and once after auto-title generation settles (label flips to the generated title).Follow-up issues filed during this work
Two adjacent bugs surfaced while wiring the live-update path. Both are pre-existing and out of scope for #155:
generateTitle; previously invisible because new conversations did not appear in the list without a refresh.useChat.Commits
refactor(conversations): switch ConversationIndex to pull-on-demand— replacesfs.watch + 500ms debouncewith mtime-based reconciliation per call. Removes the timing-race class that the live broadcast would otherwise hit.get(id)keeps anid → filePathmap sohandleGet/handleFork/handleUpdatestat one file instead of walking.fix(conversations): broadcast new chat to list live— the routing fix itself, plus hardening that keeps the broadcast path correct end-to-end:FaultIsolatedSinkso a logger throw can't abort the SSE-broadcast wrap,AbortSignal.timeoutso a hung fast-model call can't leave the title-block.finallypending, dead chat-streamdata.changedbranch removed fromhandlers.ts.Known scope notes
data.changedisscope: "global"inSSE_ROUTES. Pre-existing; a new conversation in workspace A causes every workspace-B iframe on/v1/eventsto refetch (each gets its own dir's data, so no leak — wasted bytes only). Worth a follow-up when the payload growswsId.Test plan
bun run verify— unit / web 249 / integration 482 / smoke 17, 0 fail, 0 errorsConversationIndexreflects new + deleted + modified files immediately (no debounce)🤖 Generated with Claude Code