fix: historical messages rendering, question tools and todos on client#24
Merged
Conversation
Deliver chat history to mobile in ~30-message windows with backward paging as users scroll up, and mirror the desktop AskUserQuestion flow on mobile. - Add loadMoreMessages/moreMessages and questionQueue/questionAnswer sync payloads, plus an activeSessionHasMore flag on the snapshot - Desktop AppState broadcasts the question queue and handles answers and pagination requests from paired mobile devices - MobileAppState tracks paged history and pending questions - New MobileQuestionSheet mirrors the desktop QuestionSheetView (single/multi-select and free-form "Other" answers) - MobileChatView shows a question queue banner above the input field Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ed history - Add byte-budget paging for mobile message snapshots so a sync frame stays under the relay's 10 MiB cap; older messages page in on demand - Wire best-effort APNs fan-out into broadcastNotification so finished threads surface a banner on backgrounded/offline devices - Preserve carried history when a session redirect lands after live messages already accumulated under the new session id - Make the mobile navigation title fully tappable to open the todo + summary sheet even when the thread has no todos Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR upgrades the mobile + desktop sync and UI to better handle large thread histories, mirror desktop tool-driven interactions (AskUserQuestion + todos), and improve notification delivery.
Changes:
- Add message paging (“load older”) to avoid sending/rendering full histories in one snapshot and improve scroll stability when prepending pages.
- Add mobile support for
AskUserQuestion(queue mirroring + question sheet) and surface thread todos/summary in the chat title UI. - Add APNs fan-out on the desktop relay path and extend the sync protocol with new payloads/fields (thinking state, unread completion indicator, model sections, etc.).
Reviewed changes
Copilot reviewed 21 out of 23 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| RxCodeMobile/Views/SessionsList.swift | Adds incremental list paging and a leading status dot for unread completions. |
| RxCodeMobile/Views/RootView.swift | Adjusts navigation to avoid double-push and adds a close callback for chat. |
| RxCodeMobile/Views/NewThreadSheet.swift | Uses structured modelSections (with fallback to flat models) for the model picker. |
| RxCodeMobile/Views/MobileQuestionSheet.swift | New mobile question sheet UI mirroring the desktop AskUserQuestion flow. |
| RxCodeMobile/Views/MobileChatView.swift | Adds message paging, question queue banner/sheet, todos + summary sheet, and thread actions (rename/archive/delete). |
| RxCodeMobile/Views/MobileBriefingView.swift | Adds briefing filters (project + branch) and improved empty state. |
| RxCodeMobile/State/MobileAppState.swift | Implements thinking state sync, message paging state, question queue sync, and thread lifecycle actions. |
| RxCode/Services/MobileSyncService.swift | Adds APNs push fan-out and new inbound event routing for new payload types. |
| RxCode/App/AppState.swift | Implements desktop handlers for thread actions, paging requests, question answers, and snapshot changes (paging + unread completion). |
| RxCode.xcodeproj/.../Package.resolved | Updates Xcode project SwiftPM resolution with new dependencies. |
| Packages/Sources/RxCodeSync/Transport/RelayClient.swift | Raises WebSocket frame size limit for sync payloads. |
| Packages/Sources/RxCodeSync/Protocol/Payload.swift | Extends wire protocol with new payloads/fields (paging, thread actions, questions, model sections, thinking, unread completion). |
| Packages/Sources/RxCodeCore/Models/AgentModel.swift | Adds AgentModelSection for structured model picker grouping. |
| Packages/Sources/RxCodeChatKit/ToolResultView.swift | Fixes layout alignment for tool result rows. |
| Packages/Sources/RxCodeChatKit/MessageBubble.swift | Removes an extra frame modifier to avoid layout side effects. |
| Packages/Sources/RxCodeChatKit/MarkdownView.swift | Replaces custom markdown rendering with Textual-based renderer + preprocessing. |
| Packages/Sources/RxCodeChatKit/ChatTextContentView.swift | Simplifies chat text rendering and ensures link tinting. |
| Packages/Sources/RxCodeChatKit/ChatMessageBubble.swift | Switches assistant markdown rendering to MarkdownContentView. |
| Packages/Package.swift | Adds Textual dependency for RxCodeChatKit. |
| Packages/Package.resolved | Records new SwiftPM dependency graph for Packages workspace. |
| icon.icon/icon.json | Updates icon fill + layer metadata. |
Comments suppressed due to low confidence (1)
RxCodeMobile/State/MobileAppState.swift:820
updateSessionStreamingFlagreconstructsSessionSummarywithouthasUncheckedCompletion, so it will default tofalseand can clear the unread-completion state whenever asession_updatearrives without a full summary (e.g. message diffs carryingisStreaming). Preservecurrent.hasUncheckedCompletionwhen rebuilding the summary to avoid losing the green indicator due to delivery ordering.
private func updateSessionStreamingFlag(sessionID: String, isStreaming: Bool) {
guard let index = sessions.firstIndex(where: { $0.id == sessionID }) else { return }
let current = sessions[index]
sessions[index] = SessionSummary(
id: current.id,
projectId: current.projectId,
title: current.title,
updatedAt: current.updatedAt,
isPinned: current.isPinned,
isArchived: current.isArchived,
isStreaming: isStreaming,
attention: current.attention,
progress: current.progress,
queuedMessages: current.queuedMessages
)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| if loadingMoreSessions.remove(previous) != nil { | ||
| loadingMoreSessions.insert(update.sessionID) | ||
| } |
Comment on lines
+318
to
+333
| func renameThread(sessionID: String, title: String) async { | ||
| let trimmed = title.trimmingCharacters(in: .whitespacesAndNewlines) | ||
| guard !trimmed.isEmpty else { return } | ||
| replaceSession(sessionID: sessionID) { current in | ||
| SessionSummary( | ||
| id: current.id, | ||
| projectId: current.projectId, | ||
| title: trimmed, | ||
| updatedAt: current.updatedAt, | ||
| isPinned: current.isPinned, | ||
| isArchived: current.isArchived, | ||
| isStreaming: current.isStreaming, | ||
| attention: current.attention, | ||
| progress: current.progress, | ||
| queuedMessages: current.queuedMessages | ||
| ) |
Comment on lines
+171
to
+181
| Section("Branches") { | ||
| Button { | ||
| showAllBranches = false | ||
| } label: { | ||
| Label("Current branch", systemImage: showAllBranches ? "" : "checkmark") | ||
| } | ||
| Button { | ||
| showAllBranches = true | ||
| } label: { | ||
| Label("All branches", systemImage: showAllBranches ? "checkmark" : "") | ||
| } |
Comment on lines
+501
to
+526
| var submitEnabled: Bool { | ||
| guard !isSubmitting else { return false } | ||
| if isLastQuestion || allAnswered { return allAnswered } | ||
| return currentAnswerProvided | ||
| } | ||
|
|
||
| var mainButtonLabel: String { | ||
| (isLastQuestion || allAnswered) ? "Submit" : "Next" | ||
| } | ||
|
|
||
| var mainButtonIcon: String { | ||
| (isLastQuestion || allAnswered) ? "paperplane.fill" : "arrow.right" | ||
| } | ||
|
|
||
| func submit() { | ||
| guard allAnswered, !isSubmitting else { return } | ||
| isSubmitting = true | ||
| UIImpactFeedbackGenerator(style: .light).impactOccurred() | ||
| var resolved: [Int: AskUserQuestion.Answer] = [:] | ||
| for (index, question) in questions.enumerated() { | ||
| guard let local = answers[index] else { continue } | ||
| resolved[index] = convert(local: local, multiSelect: question.multiSelect) | ||
| } | ||
| onSubmit(resolved) | ||
| dismiss() | ||
| } |
Comment on lines
89
to
116
| public struct SnapshotPayload: Codable, Sendable { | ||
| public let projects: [Project] | ||
| public let sessions: [SessionSummary] | ||
| public let branchBriefings: [MobileBranchBriefing]? | ||
| public let threadSummaries: [MobileThreadSummary]? | ||
| public let settings: MobileSettingsSnapshot? | ||
| public let activeSessionID: String? | ||
| public let activeSessionMessages: [ChatMessage]? | ||
| /// Whether the active thread has messages older than the window in | ||
| /// `activeSessionMessages`. Mobile uses this to decide whether to offer | ||
| /// "load more" as the user scrolls up. `nil`/`false` means the window is | ||
| /// the whole thread (or the desktop predates message paging). | ||
| public let activeSessionHasMore: Bool? | ||
| /// Current git branch resolved per project on the desktop. Mobile uses this | ||
| /// to surface "you're about to create a thread on branch X" when starting | ||
| /// a new thread. Missing entries mean the project isn't a git repo or the | ||
| /// branch couldn't be resolved. | ||
| public let projectBranches: [ProjectBranchInfo]? | ||
| public init( | ||
| projects: [Project], | ||
| sessions: [SessionSummary], | ||
| branchBriefings: [MobileBranchBriefing]? = nil, | ||
| threadSummaries: [MobileThreadSummary]? = nil, | ||
| settings: MobileSettingsSnapshot? = nil, | ||
| activeSessionID: String? = nil, | ||
| activeSessionMessages: [ChatMessage]? = nil, | ||
| activeSessionHasMore: Bool? = nil, | ||
| projectBranches: [ProjectBranchInfo]? = nil |
Contributor
Author
|
🎉 This PR is included in version 1.6.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
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.
No description provided.