perf: lazy-load chat messages on scroll-to-top#1108
Open
chaehyun2 wants to merge 10 commits intoslopus:mainfrom
Open
perf: lazy-load chat messages on scroll-to-top#1108chaehyun2 wants to merge 10 commits intoslopus:mainfrom
chaehyun2 wants to merge 10 commits intoslopus:mainfrom
Conversation
c1d3479 to
bb10bbb
Compare
- Add "Restart Session" action to web UI popover (forks session with --resume, preserving full conversation history) - Fix markdown table row misalignment by switching from column-first to row-first layout - Add resumeSession RPC handler in CLI - Add translations for all 10 languages Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Users can paste images from clipboard in the web chat input. Images flow through the full pipeline: web UI → sync → CLI → Claude SDK as base64 content blocks. - Web UI: onPaste handler, preview thumbnails, remove button - Sync: images field in message schema and sendMessage - CLI: images in MessageQueue2, claudeRemote buildContent, launcher passthrough - Fix base64 encoding stack overflow for large image buffers Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Images are now sent as separate encrypted messages with a TTL marker (expiresIn) outside the encryption layer. Server stores expiresAt and a cleanup job deletes expired messages every 5 minutes. Server: expiresAt field on SessionMessage, TTL cleanup in main.ts, v3 POST accepts expiresIn per message Web: sendMessage splits images into separate ephemeral message with groupId (inside encryption) and expiresIn: 300 (outside encryption) CLI: routeIncomingMessage buffers image messages by groupId, attaches to matching text message before passing to UserMessageSchema Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Show image thumbnails (120x120) in user message bubbles. Click thumbnail to expand full-size with dark overlay. Images stored in ephemeral messageImageStore keyed by localId. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Reducer assigns new internal IDs, losing the original localId needed to look up images in messageImageStore. Pass realID as localId in user messages, and add id-based fallback lookup in MessageView. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The schema only accepted 'text' type for user messages, causing TypeScript errors when sending image-only messages. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Node 25's ESM loader no longer synthesizes a default export for signal-exit@4 (named-only ESM), so ink@6.6's transitive restore-cursor@4 crashes at startup with "does not provide an export named 'default'". restore-cursor@5 uses the named onExit import and is API-compatible, so we pin cli-cursor@4's restore-cursor to ^5.1.0 via pnpm overrides. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `latest=true` query parameter to GET /v3/sessions/:id/messages that fetches messages in reverse order (most recent first). Web app uses this on first load to avoid fetching entire chat history. Subsequent real-time messages arrive via WebSocket. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
- Initial load fetches latest 50 messages (was 100) - Server adds `before_seq` query parameter to fetch older messages - ChatList triggers `loadOlderMessages` via onEndReached (inverted FlatList) - Loading spinner shown while fetching older batch Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
bb10bbb to
fa99eaf
Compare
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
before_seqquery parameter to fetch older messagesloadOlderMessagesviaonEndReached(inverted FlatList) when the user scrolls to the oldest visible messagelatest=trueparameter returns messages in descending order, then they're reversed to ASC for the clientWhy
For long sessions (thousands of messages), the existing flow fetched the entire history on every page refresh, causing multi-second loading delays. With lazy loading, the first paint is fast and older messages load only when the user actually scrolls back.
Changes
v3SessionRoutes.ts): Addbefore_seqandlatestquery params; reuse thelatest=truereverse-order path whenbefore_seqis providedsync.ts): TracksessionOldestSeqandsessionHasMoreOlderper session; addloadOlderMessages(sessionId)that fetches a 50-message page older than the current oldestChatList.tsx): WireonEndReachedtosync.loadOlderMessages; render anActivityIndicatorwhile loadingTest plan
loadOlderMessagesno-ops without an extra request🤖 Generated with Claude Code
via Happy