Skip to content

fix(session): freeze hidden session screens to stop cumulative slowdown - #386

Open
RonenMars wants to merge 89 commits into
mainfrom
perf/freeze-hidden-session-screens
Open

fix(session): freeze hidden session screens to stop cumulative slowdown#386
RonenMars wants to merge 89 commits into
mainfrom
perf/freeze-hidden-session-screens

Conversation

@RonenMars

Copy link
Copy Markdown
Owner

Problem

The app slows down progressively after opening 5-6 sessions in one run.

Sessions open with router.push, and the native session Stack (app/session/_layout.tsx) had no freezeOnBlur.
In a native stack a pushed-under screen stays mounted, so every opened session kept its useTerminalStream WS handlers firing on every frame (including the '*' wildcard) and its VirtualTerminal buffer growing.
Both scale linearly with the number of sessions opened, which matches the "slow after 5-6 sessions" report.

Fix

Add freezeOnBlur: true to the session Stack so a hidden screen's render and effects are suspended until it is focused again.

Compatibility

Mobile-only, native-navigation config flag.
No server, API, or WS-contract changes.

Note on this PR's diff

This branch is cut from the integration branch, so the diff against main carries the in-flight integration history plus this fix.
It is stacked ahead of #(cap-virtualterminal-scrollback) and is intended to merge first, in integration-branch order.

RonenMars and others added 30 commits July 20, 2026 21:12
Add typed cache integrity alert contracts across REST, WebSocket, and per-server Zustand state. Sync pending alerts through websocket broadcasts and foreground-aware REST queries so clients converge after reconnects or missed unicasts.

Present low-severity alerts in a banner and auto-open high-severity alerts in a localized resolution modal. Support prune-all, prune-selected, ignore, and reset-rescan actions; require confirmation for destructive choices; refetch on fingerprint conflicts; and surface backup paths on success.

Cover store lifecycle, websocket dispatch, banner and modal interactions, locale parity, backward-compatible 404 handling, successful resolution, and fingerprint conflicts with unit and mocked-transport integration tests.
Mark Features 35/36 and Bugs 5/16 as in-flight; freeze the 2026-07-18
analysis as a snapshot with an updated release verdict.

Co-authored-by: Cursor <cursoragent@cursor.com>

[skip-ci]
Discard empty PTYs the user never typed into so they do not linger in
the hub after abandoning a new session from browse.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add typed cache integrity alert contracts across REST, WebSocket, and per-server Zustand state. Sync pending alerts through websocket broadcasts and foreground-aware REST queries so clients converge after reconnects or missed unicasts.

Present low-severity alerts in a banner and auto-open high-severity alerts in a localized resolution modal. Support prune-all, prune-selected, ignore, and reset-rescan actions; require confirmation for destructive choices; refetch on fingerprint conflicts; and surface backup paths on success.

Cover store lifecycle, websocket dispatch, banner and modal interactions, locale parity, backward-compatible 404 handling, successful resolution, and fingerprint conflicts with unit and mocked-transport integration tests.
Explicit Help & Feedback submissions self-init Sentry like reportOneShot,
show delivery method in the success UI, and keep standing reporting opt-in.

Co-authored-by: Cursor <cursoragent@cursor.com>
Legacy uploads with spaces in filenames broke Claude Code path parsing;
escape spaces so multi-attachment sends remain valid single-token refs.

Co-authored-by: Cursor <cursoragent@cursor.com>
…orktree-merge-open-prs

# Conflicts:
#	__tests__/unit/components/servers/CacheAlertModal.test.tsx
#	components/servers/CacheAlertModal.tsx
#	services/api-client.ts
#	services/query-client.ts
#	stores/servers.ts
#	types/api.ts
…19' into worktree-merge-open-prs [skip-ci]

[skip-ci]
Adds missing viaSentry/viaEmail keys to feedback.json for ar, he, and ru.
The English keys were added in this branch but the other locales were never updated, which i18n-completeness.test.ts catches.
Bumps the npm_and_yarn group with 1 update in the / directory: [shell-quote](https://github.com/ljharb/shell-quote).


Updates `shell-quote` from 1.8.4 to 1.10.0
- [Changelog](https://github.com/ljharb/shell-quote/blob/main/CHANGELOG.md)
- [Commits](ljharb/shell-quote@v1.8.4...v1.10.0)

---
updated-dependencies:
- dependency-name: shell-quote
  dependency-version: 1.10.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
The conversation screen now polls its delta drain while focused and consumes streamed conversation_events, so an externally launched session reads as a live transcript without resuming it.
Resuming a conversation that may still be open elsewhere shows a confirmation naming what the server detected, and offers Take over only when a real process was matched.
External sessions route to the read-only conversation view instead of the PTY screen, and the discovered-session overtake path is gated off for them.
Session rows show a distinct external-alive indicator derived from the additive liveness fields, falling back to pid for older servers.
Pass Sentry credentials through to the Ship iOS workflow step so the source-map upload in the Xcode bundle phase can authenticate.
Automated version bump after successful TestFlight upload (build 165).

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…search (#351)

## What

Adds a "Rollup / Snapshot Tag Naming" section to `CLAUDE.md` documenting
the `test-<env>/v<version>-<sha>-<date>` convention for throwaway
pre-merge verification snapshots across the `dev` / `pre-release` /
`production` environments.
Updates the deploy workflow's `deploy_ref` input description and both
non-main warning messages to state that a tag or commit SHA is accepted,
not just a branch.
Adds a research note under `docs/research/` comparing the common
industry best practices for building one combined verification artifact
from several open PRs, ranked by relevance to this repo.

## Why

We routinely combine several open PRs into one build to verify them
together before merging each individually, and the naming and deploy
story for that snapshot was undocumented.
The research note records the patterns behind the choice — immutable
snapshot tag, build-once/promote-many with a release-candidate
identifier, and Expo per-PR update channels — so the convention has a
rationale on record.

## Notes

`deploy_ref` already accepted tags via `actions/checkout`, so this is a
wording clarification only with no change to workflow behavior.
This branch bundles two commits: the research note and the convention
change.
The conversation screen now polls its delta drain while focused and consumes streamed conversation_events, so an externally launched session reads as a live transcript without resuming it.
Resuming a conversation that may still be open elsewhere shows a confirmation naming what the server detected, and offers Take over only when a real process was matched.
External sessions route to the read-only conversation view instead of the PTY screen, and the discovered-session overtake path is gated off for them.
Session rows show a distinct external-alive indicator derived from the additive liveness fields, falling back to pid for older servers.
Adds a dead-key test asserting every English key is referenced somewhere in the source, complementing the existing key-parity gate in i18n-completeness.test.ts.
The scanner reads `t('ns:key')` and bare `t('key')` calls, keeps the static prefix of dynamic keys as a prefix match, and picks up keys hoisted into consts before reaching t().
Arrays are treated as leaves since they are fetched whole via `returnObjects: true`.

Removes the 161 keys it flagged across all four locales — 654 removals, since ar and ru carry extra CLDR plural forms.
Every flagged key was verified against a raw grep to rule out false positives.

Repoints the plural and interpolation assertions in i18n.test.ts onto keys the app actually uses, since the previous fixtures were among the dead keys.
Both server remove confirmations built their message from a hardcoded English template literal, so the dialog body stayed in English for he, ru, and ar users while the surrounding title and buttons translated.
The servers:dialog.removeMessage key already existed but had gone unreferenced, which is how the hardcoding went unnoticed.

Points both call sites at the key and adds a {{server}} placeholder to it, since the previous wording was generic while the hardcoded string named the server.
The English output is unchanged; the other three locales are rephrased to match.
Mark in-flight and shipped items from the July launch batch, reconcile
stale Open rows, and set the next mobile actions (typecheck, Maestro,
onboarding).

Co-authored-by: Cursor <cursoragent@cursor.com>

[skip-ci]
Unblocks `voice_dictation` by granting speech-recognition and microphone
via simctl privacy in ensure-release-build.js.

Co-authored-by: Cursor <cursoragent@cursor.com>
Guard Skip/swipe so Welcome cannot dump into an empty Hub, advance
immediately after QR exchange, add back-to-choose, and fix Mac-centric
copy / default port 8766.

Co-authored-by: Cursor <cursoragent@cursor.com>
Attribute browse failures to setup/XCUITest flake rather than FilterSortSheet,
add layout/close testIDs, and make feat1 switch Classic→Tree before drilling.

Co-authored-by: Cursor <cursoragent@cursor.com>
RonenMars and others added 25 commits July 22, 2026 23:08
…to worktree-merge-open-prs

# Conflicts:
#	jest.setup.js
The API returns a JSONL-derived conversation name, but mobile discarded it: the list and detail adapters hardcoded title from project_name, and the live-session header never read the session's name field.
Wire session_name through the Raw/Session types and both adapters, and add it to the live-view and session-row fallback chains (user rename -> session name -> project name).
…live pause toggle

Keep the transcript on screen during a live reload instead of blanking.
Every delta drain rebuilt ConversationDetail from raw pages, giving each Message a fresh object identity even when byte-identical, so FlashList (startRenderingFromBottom) dropped and re-measured every cell — a blank "blink".
reuseMessageIdentities reuses the prior object for any unchanged id, so existing rows keep stable references and only genuine appends read as new.

Fade newly-arrived tail messages in from the bottom with Reanimated FadeInDown, gated so only live appends animate — cold-open history and older-page backfill do not.

Add a live indicator and pause/resume toggle to the read-only conversation view.
Pausing stops the freshness poll and conversation_updated drains and freezes the rendered snapshot; resuming runs one catch-up drain and re-enables live updates.
The control appears only while the session is actually live.
Reuse search-target + match navigation so users can search the open
conversation without going through hub-wide list search.

Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
Live session screen messages could appear missing on arrival until a
tiny (5-15px) manual scroll revealed them.
Root cause: `LiveConversationView` drove scroll with JS `scrollToEnd()`
effects that raced FlashList's cell-layout pass for the newly appended
row, landing short.
Fix: adopt FlashList's native `maintainVisibleContentPosition`
bottom-anchoring (same config already used by `ConversationHistoryList`)
and drop the racing scroll effects.

## Test plan
- [x] `npx tsc --noEmit` — no new errors
- [x] `npx eslint components/conversation/LiveConversationView.tsx` —
clean
- [ ] Manual verification on device/simulator: send messages in a live
session, confirm no scroll-to-reveal needed
## Summary
Messages with a tool/diff/thinking block plus a text block showed a
visibly larger gap to adjacent rows than plain text-to-text messages.
Root cause: `MessageBubble`'s own `marginVertical` stacked with
`MessageItem`'s `toolContainer` margin when the bubble was nested inside
it (the tool/diff + text branch), doubling the effective spacing there.
Fix: added a `noOuterMargin` prop to `MessageBubble`, passed from the
nested usage in `MessageItem`, so spacing in that branch comes only from
`toolContainer`.

## Test plan
- [x] `npx tsc --noEmit` — no new errors
- [x] `npx eslint components/conversation/MessageBubble.tsx
components/conversation/MessageItem.tsx` — clean
- [ ] Manual verification: view a conversation with a tool-call + prose
message adjacent to plain text messages, confirm consistent spacing
A session parked on a question/permission card replays only blank ring-buffer rows over the WS terminal_replay.
The handler treated any replay as a successful load, latching replayReceivedRef and clearing the 2s HTTP fallback timer, so the terminal stayed blank forever even though GET /api/sessions/:id/output still held the full transcript.
Only accept a replay that carries at least one non-blank line; otherwise leave the fallback timer armed so /output fills the screen.
Sessions open via router.push and the native stack keeps pushed-under screens mounted, so each opened session left its WS handlers firing on every frame and its VirtualTerminal buffer growing.
The app slowed down linearly with the number of sessions opened in one run (noticeable after 5-6).
freezeOnBlur suspends a hidden screen's render and effects until it's focused again.
RonenMars added a commit that referenced this pull request Jul 23, 2026
…kip-ci]

Record PRs #385, #386 and #387 as a separate follow-up chain in the land-open-prs runbook.
#385 and #386 target main independently; #387 is stacked on #386 and must be rebased onto main before merging.

[skip-ci]
RonenMars added a commit that referenced this pull request Jul 24, 2026
…kip-ci]

Record PRs #385, #386 and #387 as a separate follow-up chain in the land-open-prs runbook.
#385 and #386 target main independently; #387 is stacked on #386 and must be rebased onto main before merging.

[skip-ci]
RonenMars added a commit that referenced this pull request Jul 26, 2026
…kip-ci]

Record PRs #385, #386 and #387 as a separate follow-up chain in the land-open-prs runbook.
#385 and #386 target main independently; #387 is stacked on #386 and must be rebased onto main before merging.

[skip-ci]
RonenMars added a commit that referenced this pull request Jul 28, 2026
…kip-ci]

Record PRs #385, #386 and #387 as a separate follow-up chain in the land-open-prs runbook.
#385 and #386 target main independently; #387 is stacked on #386 and must be rebased onto main before merging.

[skip-ci]
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