Skip to content

Fix partial chat display: path encoding, reconnect safety, and JSONL parsing#108

Open
mcintyre94 wants to merge 1 commit intomainfrom
fix-partial-chat-display-issue-918b92c4
Open

Fix partial chat display: path encoding, reconnect safety, and JSONL parsing#108
mcintyre94 wants to merge 1 commit intomainfrom
fix-partial-chat-display-issue-918b92c4

Conversation

@mcintyre94
Copy link
Owner

Summary

  • Root cause found: Claude Code encodes project directories by replacing both / and . with -, but Wisp only replaced /. Worktree chats (under .wisp/) had their JSONL paths computed as -home-sprite-.wisp-... instead of the actual -home-sprite--wisp-..., so loadRemoteHistory silently found nothing and fell back to whatever partial state was in SwiftData — producing the "chat starts at 'Now let me do a quick sanity check...'" symptom.
  • Secondary fix: auto-load the JSONL from disk when local messages are empty but a sessionId is known, so the full chat is always recoverable even after a SwiftData loss.
  • Reconnect hardening: two sub-fixes so partial/truncated service logs can't silently overwrite good content, and retry iterations without UUID tracking don't duplicate events.
  • JSONL parsing: block-format user messages (some Claude Code versions write [{type:text}] arrays instead of plain strings) were being silently dropped.

Changes

claudeProjectPathEncoding helper (ChatViewModel.swift)

  • Replaces both / and . with -, matching Claude Code's actual on-disk encoding
  • Extracted into a named nonisolated static helper used by both fetchRemoteSessions and loadRemoteHistory

reconnectIfNeeded

  • When messages.isEmpty but sessionId != nil, fires loadRemoteHistory to recover the full conversation from the JSONL file on the sprite
  • Guarded by !isLoadingHistory to prevent concurrent duplicate calls (dashboard, detail view, and background resume all call this)

runReconnectLoop

  • Snapshots existing assistant content before replay; restores it if the replay ends without a system event (truncated logs) or produces fewer items than the snapshot
  • Resets replay buffering state on retry iterations when UUID tracking is absent, preventing content from being doubled on each pass

parseSessionJSONL

  • User events with [{type:\"text\", text:\"...\"}] block arrays are now treated as user messages (same as the plain-string case), not silently discarded

Test plan

  • Run unit tests: xcodebuild -scheme Wisp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' test
  • Open a worktree chat that previously showed partial content — should now load the full conversation from the JSONL
  • Open a chat where SwiftData was cleared (delete app data) — should recover history via loadRemoteHistory
  • Verify new claudeProjectPathEncoding tests pass (3 cases: standard path, worktree path with .wisp, repo path)
  • Verify new reconnect loop tests pass (truncated logs restore snapshot, no duplication on retry)
  • Verify new parseSessionJSONL tests pass (block-format user messages, multi-turn, tool-result disambiguation)

🤖 Generated with Claude Code

…parsing

The root cause was a path-encoding mismatch: Claude Code encodes project
directories by replacing both '/' and '.' with '-', but Wisp only replaced
'/'. Worktree chats under .wisp/ had their JSONL paths computed incorrectly
(e.g. -home-sprite-.wisp-... vs the actual -home-sprite--wisp-...), so
loadRemoteHistory silently found nothing and fell back to whatever partial
state was in SwiftData.

Three fixes:

1. Path encoding (claudeProjectPathEncoding helper): replace both '/' and '.'
   with '-' in all Claude project directory lookups (fetchRemoteSessions and
   loadRemoteHistory). Extracted into a named static helper with tests.

2. Auto-load remote history on empty messages: reconnectIfNeeded now triggers
   loadRemoteHistory when messages are empty but a sessionId is known. Guards
   against concurrent calls with isLoadingHistory. This recovers the full chat
   from the JSONL even if SwiftData was wiped or a prior path-encoding failure
   left the chat blank.

3. Reconnect replay safety (two sub-fixes):
   - Truncated service logs: if a replay completes without a system event
     (logs don't start from the beginning), restore the pre-reconnect snapshot
     rather than showing a partial mid-response fragment.
   - No-UUID retry deduplication: on retry iterations without UUID tracking,
     reset replay state before re-fetching so events aren't doubled.

4. parseSessionJSONL block-format user messages: some Claude Code versions
   write user turns as [{type:text, text:...}] arrays instead of plain
   strings. These were silently dropped; now treated as user messages.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
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