Skip to content

Mobile chat resume testing

Test on https://yumina-testing.up.railway.app after the testing deployment finishes. Reload once to obtain the new frontend. Use an existing test story with enough messages to scroll; the long-history case needs more than 200 messages. Test Android Chrome and iPhone Safari on real devices where available, especially the device that originally showed the problem.

Expected behavior

If you leave while viewing the latest message, returning should reveal the latest message again. If you leave while reading an older message, returning should preserve that message and approximately the same position within it. A deliberate scroll should immediately take control, including while restoration or history loading is in progress.

Test cases

  1. Original app-switch report: Go to the bottom of a built-in story chat. Switch to YouTube or another app for 10–30 seconds, then return. Repeat five times, including one longer absence of several minutes. Expect the last message to remain visible without manually scrolling down.
  2. Older reading position: Scroll halfway up and note a distinctive sentence near the top of the viewport. Switch apps and return. Expect the same message near the same offset, rather than a jump to the latest message or an unrelated section.
  3. Long history: In a conversation with more than 200 messages, load earlier messages until reading outside the newest 200. Switch apps and return, then reload the same browser tab. Expect the older message to remain reachable and the saved reading position to return after any necessary history requests finish. Test again with a slower connection; manually scrolling during recovery should cancel automatic repositioning.
  4. Reply completed in the background: At the bottom, send a test message and switch apps while the reply is generating. Return after it has had time to finish. Expect the latest available response, with no duplicate or missing messages. If the connection is interrupted, existing generation recovery still applies; this change does not guarantee background network execution. Repeat while intentionally reading older history: returning should preserve that reading position.
  5. Keyboard and orientation: Leave and return with the composer keyboard open, then close it. Repeat while reading older history and after rotating the phone. Expect no jump into the middle of an unrelated message and no persistent inability to scroll. Viewport changes may expose more or less text.
  6. User takes control: Immediately after returning, swipe upward before layout/history recovery finishes. Expect your new reading position to win. Tap the composer and type/send; the old resume operation must not pull the transcript back afterward.
  7. Route and session isolation: Leave the story for the library and reopen it. Then open another story and return. Each story should retain its own reading position, with no jump to a checkpoint from another session. Repeat a same-tab reload at the bottom and while reading older messages.
  8. Custom world: Repeat app switching in a world with its own handwritten scrollable transcript. Warm resume should restore the observed scroll container. For a custom UI without built-in message IDs, older positions use a pixel fallback and reload restoration is not guaranteed.

For a failed case, record the phone/browser version, story interface type, approximate message count, whether the keyboard was open or a reply was streaming, and the exact steps. A short screen recording showing the position before and after the app switch is useful.

Engineering notes

The resume path previously refreshed only the newest message page, which could remove loaded older history. Sandbox scroll protection could also classify browser-driven scrolling as reader intent. The built-in transcript now distinguishes latest/reading intent, saves a message ID and offset through the validated iframe bridge, and restores after visibility/route changes and layout updates. Foreground refresh reconciles the loaded history range atomically in bounded server pages, including edits and deletions.

Built-in checkpoints are account/session-scoped in parent sessionStorage, with bounded in-memory fallback when storage is unavailable. They contain message IDs and geometry, not message text. Storage is best effort: a closed tab, cleared browser data, or a browser that does not preserve sessionStorage cannot be promised to restore. A deleted or unavailable anchor falls back to the saved pixel position after bounded history recovery.

Automated regressions cover initial layout, resume geometry, cancellation and stale requests, delayed React history commits, refresh edits/deletes, storage isolation, bridge validation, and custom warm resume. Run from packages/app:

sh
pnpm exec tsx --tsconfig tsconfig.app.json --test src/features/chat/initial-message-scroll.test.ts src/features/chat/transcript-position.test.ts src/features/chat/transcript-position-bridge.test.ts src/features/chat/refresh-message-window.test.ts src/lib/transcript-position-storage.test.ts

Real-device checks above remain necessary: simulated DOM tests cannot reproduce mobile operating-system suspension, browser tab eviction, or the actual software keyboard.