Skip to content

feat(ios): read-only offline cache for chat sessions and transcripts#100219

Merged
steipete merged 6 commits into
mainfrom
feat/ios-chat-offline-cache
Jul 6, 2026
Merged

feat(ios): read-only offline cache for chat sessions and transcripts#100219
steipete merged 6 commits into
mainfrom
feat/ios-chat-offline-cache

Conversation

@steipete

@steipete steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Resolves a problem where opening the chat tab showed a blank transcript until the gateway responded, and showed nothing at all when the gateway was unreachable. All chat state was in memory only, so cold opens and offline periods left users staring at an empty screen even for conversations they had just been reading.

Part of #100194

Why This Change Was Made

Adds a small, disposable, read-only offline cache of recent chat sessions and transcripts, scoped per paired gateway identity. Cold opens paint the last known transcript immediately; authoritative gateway history then replaces it through the existing reconciliation path. Post-send incomplete history cannot cache optimistic echoes or erase the last canonical snapshot.

The store is versioned SQLite with drop-and-rebuild on mismatch, bounded retention, no migrations, and no new dependency. Text rows only; attachment payloads and tool arguments are stripped. Each gateway gets a SHA-256-named database, so bootstrap replacement can retire and physically remove only that gateway's file and sidecars without deleting other paired gateways' caches. iOS opens cache files with Complete file protection and treats locked-device protected-data unavailability as a temporary cache miss.

User Impact

The chat tab renders the last known conversation immediately on launch. Cached sessions remain visible and openable from Home, the full Sessions screen, and iPad Activity while disconnected. Sending stays disabled until the connection is healthy. Switching gateways rebuilds cache consumers immediately; reset/forget physically removes the appropriate cached conversation files.

Evidence

  • Final exact PR head: ec1ee2bd5c468874d23314649ef9e4df2c0d1a85; landed as 5d8293c1fe404f607b3ecddcb9f12d1753a4de51.
  • Exact-head CI run 28759147624 — success, including macos-swift, ios-build, QA Smoke, Android, Node, docs, security, and native i18n lanes.
  • Exact-head Periphery run 28759147630 — success.
  • swiftformat --lint --config config/swiftformat <17 touched Swift files> — pass.
  • swiftlint lint --config apps/ios/.swiftlint.yml <touched Swift files> — no new errors; only pre-existing size/complexity warnings.
  • node --import tsx scripts/native-app-i18n.ts check — pass, 2542 entries, no drift.
  • node scripts/ios-write-swift-filelist.mjs — pass; both new shared cache files are included in the iOS lint input.
  • Blacksmith Testbox through Crabbox tbx_01kwt78aakecg7xbakwev6mxcn: Linux Swift attempts reached existing Apple-only package blockers (AudioToolbox; then CoreFoundation symbols with Talk disabled). Exact-head Apple CI supplied the compile/test proof.
  • Fresh Codex autoreview on the final rebased branch — clean, no accepted/actionable findings (patch is correct, confidence 0.77).

Known proof gap: no live simulator/device visual recording for cold-open, offline navigation, reconnect, and purge behavior.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation app: ios App: ios size: XL maintainer Maintainer-authored PR labels Jul 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 548326ae30

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// is written through so the next cold open pre-paints current rows.
self.hasAppliedLiveHistory = true
self.isShowingCachedTranscript = false
self.persistTranscriptToCache(sessionKey: request.session.key)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid caching optimistic send echoes

When this is reached from refreshHistoryAfterRun(... preservingOptimisticLocalMessages: true), self.messages can still include the pending local user echo inserted before sendMessage and preserved by reconciliation while the gateway history has not durably returned that row yet. Persisting the whole visible transcript here writes non-authoritative UI state into the offline cache; if the run later times out or the app restarts before canonical history catches up, the next cold open ghost-paints a message the gateway never had. Store only canonical live-history rows for cache write-through, not optimistic messages.

Useful? React with 👍 / 👎.

let transportModeID = self.appModel.chatTransportModeID
// Includes the cache gateway identity so switching paired gateways
// rebuilds the view model even while the transport mode stays the same.
let transportModeID = self.appModel.chatViewModelIdentityID

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Observe gateway cache identity changes

This identity only takes effect when syncChatViewModel() is called, but the view currently calls that on initial task, chat-session changes, fixture toggles, and isOperatorGatewayConnected becoming true. If the user changes the active/saved gateway while the chat tab is mounted and still offline/connecting, chatViewModelIdentityID changes without rebuilding, so the existing view model and transcript cache remain scoped to the previous gateway until connection succeeds and can show stale cached transcripts under the new gateway. Add an onChange for this identity (or the gateway cache identity) so switches rebuild immediately.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 5, 2026, 7:46 PM ET / 23:46 UTC.

Summary
The PR adds an iOS/shared SQLite-backed, per-gateway read-only offline cache for recent chat sessions and transcripts, then wires it into iOS chat, Home/Sessions/iPad Activity, onboarding purge, docs, i18n, and Swift file-list tooling.

PR surface: Docs +2, Other +1303. Total +1305 across 21 files.

Reproducibility: yes. source-reproducible. Start a sessions.list request from Home, Sessions, or iPad Activity for gateway A, switch to gateway B before the response returns, and the current code applies/stores A's response through the now-current cache identity.

Review metrics: 2 noteworthy metrics.

  • New persisted cache surface: 1 per-gateway SQLite cache family added. The PR creates a new on-device store for session/transcript text, so identity scoping, file protection, and purge behavior matter before merge.
  • Session-cache writers: 3 iOS browsing surfaces write cached sessions. Home recent sessions, the full Sessions screen, and iPad Activity can all write the per-gateway session cache and need the same request identity guard.

Stored data model
Persistent data-model change detected: database schema: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTranscriptCache.swift, persistent cache schema: CHANGELOG.md, persistent cache schema: apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatTranscriptCacheStoreTests.swift, persistent cache schema: docs/platforms/ios.md. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #100194
Summary: The canonical remaining product work is the mobile read-only offline session cache tracked by #100194; this PR is the iOS candidate while related Android work already merged and macOS/outbox work is adjacent or stacked.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🦐 gold shrimp
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Guard all session-list apply/store paths with a captured gateway/cache identity before and after await.
  • [P1] Add redacted simulator/device proof for cold open, offline browsing, reconnect replacement, and purge behavior.
  • Remove the release-owned CHANGELOG.md edit.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR provides tests and CI evidence, but the body and latest comment explicitly say there is no fresh real-device/simulator recording for cold-open, offline navigation, reconnect, and purge behavior; add redacted simulator/device proof before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] A stale sessions.list response from one paired gateway can be shown and persisted under a later selected gateway because the browsing surfaces resolve the cache identity after the await.
  • [P1] The PR stores sensitive session/transcript text locally, so identity scoping and purge behavior are security-boundary requirements, not cosmetic polish.
  • [P1] The PR edits release-owned CHANGELOG.md; release generation should own that entry instead of this feature branch.

Maintainer options:

  1. Fix request-scoped session writes before merge (recommended)
    Capture the gateway/cache identity before each sessions.list request and re-check it before assigning rows or writing the cache in Home, Sessions, and iPad Activity.
  2. Narrow the cached browsing rollout
    Disable or defer cached session-list writes outside the guarded chat view model if maintainers want a smaller v1 surface.
  3. Pause for security acceptance
    Pause this PR until the parent issue records the accepted iOS local-storage and purge contract for cached chat text.

Next step before merge

  • [P1] Human follow-up is required because real behavior proof is mock/CI-only and the remaining cache-scoping/security acceptance cannot be completed by ClawSweeper repair without contributor or maintainer simulator/device evidence.

Maintainer decision needed

  • Question: After the cache-scoping bugs are fixed and simulator/device proof is added, should iOS accept a v1 read-only local transcript/session cache protected by Complete file protection?
  • Rationale: This PR creates a new sensitive on-device storage surface for chat/session text, so the permanent storage and purge contract needs maintainer/security acceptance beyond ordinary CI.
  • Likely owner: steipete — He is the PR author/assignee and recent shared chat UI/cache sponsor, so he is best positioned to make or route the storage/security acceptance call.
  • Options:
    • Accept after guards and proof (recommended): Keep the read-only cache direction, but require request-scoped session-list writes, changelog cleanup, and redacted simulator/device proof before merge.
    • Narrow v1 to chat tab: Ship only transcript pre-paint and defer Home/Sessions/iPad Activity offline browsing until a shared guarded session-cache helper exists.
    • Pause under the parent issue: Hold this PR under Mobile apps: offline session cache with instant cold open (iOS + Android) #100194 until mobile cache storage and security direction is explicitly settled.

Security
Needs attention: The diff adds sensitive local transcript/session storage and still has a cross-gateway session-cache write race that needs fixing before merge.

Review findings

  • [P2] Guard session cache writes against gateway switches — apps/ios/Sources/Design/CommandCenterTab.swift:410-412
  • [P2] Guard iPad session writes against gateway switches — apps/ios/Sources/Design/IPadActivityScreen.swift:222-224
  • [P3] Remove the release-owned changelog edit — CHANGELOG.md:9
Review details

Best possible solution:

Land the per-gateway read-only cache only after every session-list browsing surface uses a request-scoped cache identity guard, the release-owned changelog edit is removed, and redacted simulator/device proof shows cold open, offline browsing, reconnect replacement, and purge.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible. Start a sessions.list request from Home, Sessions, or iPad Activity for gateway A, switch to gateway B before the response returns, and the current code applies/stores A's response through the now-current cache identity.

Is this the best way to solve the issue?

No, not yet. The cache belongs near the shared chat/session boundary and the view-model path is well covered, but the visible browsing surfaces need the same request-scoped identity guard before this is the safe implementation.

Full review comments:

  • [P2] Guard session cache writes against gateway switches — apps/ios/Sources/Design/CommandCenterTab.swift:410-412
    This sessions.list result is applied and then stored through appModel's current cache identity. If the request started on gateway A and the user switches to gateway B before it returns, the Home/Recent and full Sessions surfaces can paint and persist A's rows under B; GatewayChannelActor.request does not make SwiftUI task cancellation a sufficient guard. Capture the identity/cache before the await and re-check it before applying or storing.
    Confidence: 0.89
  • [P2] Guard iPad session writes against gateway switches — apps/ios/Sources/Design/IPadActivityScreen.swift:222-224
    Late catch: this code was unchanged from the prior reviewed head, but the same stale-response bug exists here too. A sessions.list response from the previous gateway can still assign self.sessions and write storeCachedChatSessions after the cache identity has changed, so iPad Activity can leak session metadata across paired gateways unless it captures and validates the request identity.
    Confidence: 0.87
    Late finding: first raised on code an earlier review cycle already covered.
  • [P3] Remove the release-owned changelog edit — CHANGELOG.md:9
    Late policy catch: root AGENTS.md says CHANGELOG.md is release-owned and normal PRs should carry release-note context in the PR body, commit, or squash message instead. Please drop this branch edit and leave the release entry to generation.
    Confidence: 0.86
    Late finding: first raised on code an earlier review cycle already covered.

Overall correctness: patch is incorrect
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 2ded26a5d6be.

Label changes

Label changes:

  • add merge-risk: 🚨 session-state: Merging as-is could show or persist session rows from the wrong paired gateway after a gateway switch.
  • add merge-risk: 🚨 security-boundary: The PR introduces local storage for sensitive chat/session text, and the remaining stale-response race can cross the intended per-gateway boundary.
  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR provides tests and CI evidence, but the body and latest comment explicitly say there is no fresh real-device/simulator recording for cold-open, offline navigation, reconnect, and purge behavior; add redacted simulator/device proof before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Label justifications:

  • P2: This is a normal-priority iOS feature/review blocker with limited blast radius, not a core runtime outage.
  • merge-risk: 🚨 session-state: Merging as-is could show or persist session rows from the wrong paired gateway after a gateway switch.
  • merge-risk: 🚨 security-boundary: The PR introduces local storage for sensitive chat/session text, and the remaining stale-response race can cross the intended per-gateway boundary.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR provides tests and CI evidence, but the body and latest comment explicitly say there is no fresh real-device/simulator recording for cold-open, offline navigation, reconnect, and purge behavior; add redacted simulator/device proof before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Docs +2, Other +1303. Total +1305 across 21 files.

View PR surface stats
Area Files Added Removed Net
Source 0 0 0 0
Tests 0 0 0 0
Docs 2 2 0 +2
Config 0 0 0 0
Generated 0 0 0 0
Other 19 1403 100 +1303
Total 21 1405 100 +1305

Security concerns:

  • [medium] Cross-gateway session metadata cache race — apps/ios/Sources/Design/IPadActivityScreen.swift:224
    Session-list responses can be applied and stored after the selected gateway changes, which can place one gateway's session metadata into another gateway's cache despite the PR's per-gateway isolation contract.
    Confidence: 0.88

What I checked:

Likely related people:

  • vincentkoc: Commit 1408dee introduced the current CommandCenterTab, IPadActivityScreen, and NodeAppModel iOS surfaces that this PR now wires for cached session browsing. (role: introduced current iOS surfaces; confidence: high; commits: 1408deede7cb; files: apps/ios/Sources/Design/CommandCenterTab.swift, apps/ios/Sources/Design/IPadActivityScreen.swift, apps/ios/Sources/Model/NodeAppModel.swift)
  • steipete: Peter Steinberger recently changed the shared ChatViewModel path on main and authored the related merged Android offline-cache work plus this iOS implementation, so he is the clearest sponsor for the cache contract. (role: recent shared chat UI contributor and feature sponsor; confidence: medium; commits: bf156386aa5c, 09f9a85145c0; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTranscriptCache.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (3 earlier review cycles)
  • reviewed 2026-07-05T08:13:57.902Z sha 548326a :: needs real behavior proof before merge. :: [P2] Store only canonical history rows in the cache | [P2] Rebuild when the cache gateway identity changes | [P2] Add a purge path for forgotten gateway transcripts
  • reviewed 2026-07-05T10:02:52.804Z sha 165063f :: needs real behavior proof before merge. :: [P2] Store only canonical history rows in the cache | [P2] Rebuild when the cache gateway identity changes | [P2] Add a purge path for forgotten gateway transcripts
  • reviewed 2026-07-05T10:59:28.182Z sha b1c7f92 :: needs real behavior proof before merge. :: [P2] Wire cached sessions into visible browsing UI

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 5, 2026
@steipete
steipete force-pushed the feat/ios-chat-offline-cache branch from 165063f to b1c7f92 Compare July 5, 2026 10:42
@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@openclaw-mantis visual task: verify iOS chat cold-opens from cached transcript while offline, disables sending, then live gateway history replaces the cache on reconnect. Use current head b1c7f92 and publish the redacted simulator artifact manifest here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1c7f922a9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// A live sessions response (even an empty one) is authoritative;
// a slow cache read must never repaint over it.
guard self.sessions.isEmpty, !self.hasAppliedLiveSessions else { return }
self.sessions = cached

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wire cached sessions into the visible sessions UI

When the app is offline, this only assigns cached session rows to OpenClawChatViewModel.sessions, but the iOS chat tab constructs OpenClawChatView with showsSessionSwitcher: false (apps/ios/Sources/Design/ChatProTab.swift:94), while the visible Recent sessions/Sessions surfaces use CommandCenterTab.refreshSessions() and immediately clear their rows when isCommandSessionListAvailable is false (apps/ios/Sources/Design/CommandCenterTab.swift:760-764). As a result, after a cold offline launch the cached transcript can paint for the current chat, but users still cannot browse cached recent sessions as promised; route those surfaces through the same cache or enable a cache-backed switcher.

Useful? React with 👍 / 👎.

@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

The dead-code cleanup in apps/ios/Sources/Terminal/TerminalHubScreen.swift now overlaps merged #100328 (9ba14b80ab9b970ea4ba98dc551f3deb1f9cd1d8), which removed the unused webContentIdentity(config:) overload to keep the iOS Periphery gate green.

Please drop that duplicate hunk when rebasing this PR. The offline cache work remains otherwise distinct from #100328.

@steipete steipete self-assigned this Jul 5, 2026
@steipete
steipete force-pushed the feat/ios-chat-offline-cache branch from b1c7f92 to b6ae8e1 Compare July 5, 2026 23:19
@openclaw-barnacle openclaw-barnacle Bot added the scripts Repository scripts label Jul 5, 2026
@steipete
steipete force-pushed the feat/ios-chat-offline-cache branch from b6ae8e1 to 4d38e7f Compare July 5, 2026 23:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d38e7f3b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 409 to +412
let transport = self.appModel.makeChatTransport()
let response = try await transport.listSessions(limit: Self.recentSessionsFetchLimit)
self.defaultChatSessionEntry = response.sessions.first {
$0.key == self.appModel.defaultChatSessionKey
}
self.recentChatSessions = Self.sessionChoices(
response.sessions,
currentSessionKey: self.appModel.chatSessionKey,
defaultSessionKey: self.appModel.defaultChatSessionKey)
self.applySessions(response.sessions)
await self.appModel.storeCachedChatSessions(response.sessions)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep session cache writes scoped to the request gateway

If this sessions.list call starts against gateway A and the user switches/reconnects to gateway B before it returns, the response is still applied and storeCachedChatSessions resolves the current cache identity, so A's session rows can be written into B's offline cache and later shown while B is offline. The refreshID change cancels the old SwiftUI task, but this non-throwing await path never checks cancellation or that chatViewModelIdentityID is unchanged before applying/storing the result; capture the identity/cache for the request and guard it after the await.

Useful? React with 👍 / 👎.

@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Land-ready rewrite complete on exact head 4d38e7f3b5c2f4b862e4760b7117e40a29163ad1.

What changed:

  • rebased the cache work onto current main and removed the obsolete TerminalHub/stale inventory churn;
  • completed offline session browsing across Home, Sessions, and iPad Activity;
  • isolated transcript storage into one protected SQLite file per gateway so targeted reset physically removes that gateway's database and sidecars;
  • added both new shared cache files to the iOS Swift lint input.

Verification:

Known proof gap: no fresh real-device/simulator recording for cold-open/offline navigation/reconnect/purge. The focused cache/view-model regression suite remains in the PR, and exact-head Apple build/test gates are green.

@steipete
steipete force-pushed the feat/ios-chat-offline-cache branch from 4d38e7f to 7551c63 Compare July 5, 2026 23:36
@clawsweeper clawsweeper Bot removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 5, 2026
@clawsweeper clawsweeper Bot removed the status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. label Jul 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7551c63991

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 223 to +224
self.sessions = response.sessions
await self.appModel.storeCachedChatSessions(response.sessions)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard iPad session writes against gateway switches

When the iPad Activity screen starts sessions.list for gateway A and the user switches/forgets to gateway B before the request returns, this path still applies the old response and storeCachedChatSessions resolves the current cache identity. I checked the request path (apps/ios/Sources/Chat/IOSGatewayChatTransport.swift:186-189apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayNodeSession.swift:378-398), and there is no cancellation or route/identity check after the await, so A's sessions can be painted and persisted into B's offline cache. Capture the chatViewModelIdentityID/cache identity before the await and re-check it before assigning or storing.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 5, 2026
steipete added 6 commits July 5, 2026 19:49
Cache-first cold open for the iOS chat tab: the last known transcript and
session list render immediately from a local SQLite cache, then live gateway
history replaces them wholesale through the existing reconciliation path.
When the gateway is unreachable, recent sessions and transcripts stay
browsable read-only; sending remains gated by the existing connection state.

- New OpenClawChatTranscriptCache protocol seam plus SQLite-backed store in
  OpenClawChatUI (raw SQLite3, no new dependencies), scoped per gateway
  stableID so transcripts never leak across paired gateways.
- Bounds: 50 sessions, 50 transcripts, 200 messages per session; text rows
  only (attachment/binary payloads and tool arguments are stripped).
- Disposable cache: schema versioned via user_version; any open/schema/decode
  mismatch drops and rebuilds silently. File protection
  completeUntilFirstUserAuthentication on iOS.
- View model pre-paints from cache only until a live response applies
  (hasAppliedLiveHistory/hasAppliedLiveSessions guards); write-through is
  chained and off the render path.
- iOS wiring keys the chat view model identity on transport mode plus cache
  gateway ID so switching paired gateways rebuilds the view model.

Part of #100194
@steipete
steipete force-pushed the feat/ios-chat-offline-cache branch from 7551c63 to ec1ee2b Compare July 5, 2026 23:49

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec1ee2bd5c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

self.viewModel = OpenClawChatViewModel(
sessionKey: sessionKey,
transport: self.appModel.makeChatTransport(),
transcriptCache: self.appModel.makeChatTranscriptCache(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bind transcript cache to the transport route

When a user switches from gateway A to gateway B, connectToGateway updates the active config before the old operator GatewayNodeSession is necessarily disconnected (I checked prepareForGatewayConnect: it cancels the old operator task but does not immediately clear the session channel), so this rebuilt chat model can pair makeChatTransport() for A's still-installed channel with makeChatTranscriptCache() scoped from the new active B identity. If ChatView.onAppear loads during that window, A's history can be written into B's per-gateway database and later shown offline under B; create/use the cache only for the actual connected route or make the transport route-checked before assigning the new cache identity.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 5d8293c into main Jul 6, 2026
85 checks passed
@steipete
steipete deleted the feat/ios-chat-offline-cache branch July 6, 2026 00:02
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 6, 2026
…penclaw#100219)

* feat(ios): read-only offline cache for chat sessions and transcripts

Cache-first cold open for the iOS chat tab: the last known transcript and
session list render immediately from a local SQLite cache, then live gateway
history replaces them wholesale through the existing reconciliation path.
When the gateway is unreachable, recent sessions and transcripts stay
browsable read-only; sending remains gated by the existing connection state.

- New OpenClawChatTranscriptCache protocol seam plus SQLite-backed store in
  OpenClawChatUI (raw SQLite3, no new dependencies), scoped per gateway
  stableID so transcripts never leak across paired gateways.
- Bounds: 50 sessions, 50 transcripts, 200 messages per session; text rows
  only (attachment/binary payloads and tool arguments are stripped).
- Disposable cache: schema versioned via user_version; any open/schema/decode
  mismatch drops and rebuilds silently. File protection
  completeUntilFirstUserAuthentication on iOS.
- View model pre-paints from cache only until a live response applies
  (hasAppliedLiveHistory/hasAppliedLiveSessions guards); write-through is
  chained and off the render path.
- iOS wiring keys the chat view model identity on transport mode plus cache
  gateway ID so switching paired gateways rebuilds the view model.

Part of openclaw#100194

* fix(ios): harden offline transcript cache

* fix(ios): expose offline cached sessions

* fix(ios): isolate transcript caches by gateway

* chore(i18n): sync native inventory

* fix(ios): allow cache extension to replace messages
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
…penclaw#100219)

* feat(ios): read-only offline cache for chat sessions and transcripts

Cache-first cold open for the iOS chat tab: the last known transcript and
session list render immediately from a local SQLite cache, then live gateway
history replaces them wholesale through the existing reconciliation path.
When the gateway is unreachable, recent sessions and transcripts stay
browsable read-only; sending remains gated by the existing connection state.

- New OpenClawChatTranscriptCache protocol seam plus SQLite-backed store in
  OpenClawChatUI (raw SQLite3, no new dependencies), scoped per gateway
  stableID so transcripts never leak across paired gateways.
- Bounds: 50 sessions, 50 transcripts, 200 messages per session; text rows
  only (attachment/binary payloads and tool arguments are stripped).
- Disposable cache: schema versioned via user_version; any open/schema/decode
  mismatch drops and rebuilds silently. File protection
  completeUntilFirstUserAuthentication on iOS.
- View model pre-paints from cache only until a live response applies
  (hasAppliedLiveHistory/hasAppliedLiveSessions guards); write-through is
  chained and off the render path.
- iOS wiring keys the chat view model identity on transport mode plus cache
  gateway ID so switching paired gateways rebuilds the view model.

Part of openclaw#100194

* fix(ios): harden offline transcript cache

* fix(ios): expose offline cached sessions

* fix(ios): isolate transcript caches by gateway

* chore(i18n): sync native inventory

* fix(ios): allow cache extension to replace messages
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: ios App: ios docs Improvements or additions to documentation maintainer Maintainer-authored PR merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. scripts Repository scripts size: XL status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant