Skip to content

chore(integration): combine browse work with iOS crash and CI fixes - #427

Open
RonenMars wants to merge 88 commits into
mainfrom
integration-merge-354-355-376-v2
Open

chore(integration): combine browse work with iOS crash and CI fixes#427
RonenMars wants to merge 88 commits into
mainfrom
integration-merge-354-355-376-v2

Conversation

@RonenMars

Copy link
Copy Markdown
Owner

Summary

Combined integration branch: the existing integration work plus both open fixes, so there is one ref that is simultaneously launch-safe and CI-green.

Stacked on 597f0293 (the browse-recents work already on integration-merge-354-355-376), then both fix commits cherry-picked on top.
Both applied without conflict — the three change sets touch disjoint file sets.

Commit What
597f0293 feat(browse): recent-dirs preview + modal (already on the integration branch)
b45cf7de fix(conversation): restore in-chat search button, repair locale drift (= #426)
d10889bc fix(ios): align expo module set to SDK 57.0.8, gate dyld symbols (= #425)

Why

integration-merge-354-355-376 currently ships the build-173 launch crash: expo-camera 57.0.1 against expo-modules-core 57.0.7, which aborts in dyld before any app code runs.
It also has three CI failures that predate both PRs and were never caught because test.yml does not run on that branch.

#425 and #426 are independent siblings off the same base, so neither alone gives a ref that is both crash-free and green.
This branch is that ref.

Verification

Run against the combined tree, not inherited from the source PRs.

  • npm ci resolves expo 57.0.8 / expo-modules-core 57.0.7 / expo-camera 57.0.3 / react-native-screens 4.26.2
  • pod install regenerates ios/Podfile.lock with zero drift from the committed lockfile
  • Release build for generic/platform=iOS succeeds
  • verify-dyld-symbols.sh: 637 in-bundle symbol references, all resolve — the crash is fixed in this combination
  • check:native-deps: no duplicate native modules
  • Type check: clean
  • Unit: 95/95 suites, 897 tests
  • i18n: 3/3 suites, 55 passed
  • Integration: 39/39 suites, 242/242 tests
  • TestFlight install from this ref confirming the app launches

Note on the integration suite: one run reported 1 failure that did not reproduce on re-run or in isolation — a parallel-worker load artifact of the kind CLAUDE.md documents, not a defect.
A serial --runInBand pass is the authoritative check.

Merge path

This branch supersedes integration-merge-354-355-376.
Once it is validated, #425 and #426 can be closed as included here rather than merged separately — their commits are already in this history.

To validate a build before switching over: run Deploy with deploy_ref: integration-merge-354-355-376-v2.
Build 173 should be pulled from TestFlight once a replacement is up.

RonenMars and others added 30 commits July 24, 2026 10:30
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>
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.
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>
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>
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]
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.
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.
Automated version bump after successful TestFlight upload (build 165).

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.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.
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>
Classify threadbase:// URIs and pt_* tokens, run exchangeToken for those,
and keep Bearer /api/profiles for long-lived API keys.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add server_drag_reorder.yaml to test:e2e:mock and remove the known-crashing
swipe on drag-handles; default path asserts the lock-toggle gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
RonenMars added 30 commits July 24, 2026 10:30
…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.
Claude Code repaints its spinner line only when it has other output to draw, so the elapsed counter in "✽ Swirling… (56s · ↑ 3.4k tokens)" sticks at whatever it was on the last paint.
On mobile a number that stops moving reads as a hung session, which is what users reported.
Verified against a raw node-pty probe: during ~50s of silent thinking the counter repainted once, and even while streaming it jumped 3s → 25s.

The existing chrome filters already dropped the bare "✽ Swirling…" spinner but not the variant carrying the counter, so that one line survived to the render path.
Filter it, along with the completed-turn timers ("✻ Crunched for 3m 17s"), and require the parenthetical to start with an elapsed-seconds value so ordinary output like "Testing… (this is real output)" is preserved.

Also widen the model status-line filter: it required a minor version and an ASCII pipe, so "Sonnet 5 │ ~/path" leaked through.

Surface model, effort and permission mode in the Session Info sheet from the new GET /api/sessions/:id fields.
InfoModal skips undefined values, so these rows simply do not render against a streamer that does not send them.
…ilently

The thinking bubble animated its dots only while `isStreaming` was true, but that flag goes false after 1500ms of PTY silence and Claude routinely thinks for 30s or more without emitting anything.
In that window the bubble showed a frozen block of stale terminal text with nothing moving, which reads as a dead session — the same root cause as the frozen status-line counter: Claude repaints only when it has something to draw.

Render a skeleton in that gap so an in-progress turn stays legible.
Reuses the existing SkeletonBox rather than adding another animation, and the other three bubble states (streaming, no output yet, question card) are unchanged.
Adds a Claude CLI flags section to the server edit modal, backed by the streamer's new GET/PUT /api/config/claude-flags.

The flag registry is supplied by the server rather than hardcoded here, so a streamer that knows about a newer CLI flag needs no app update; the form renders generically from each entry's valueType.
A server that predates the endpoint 404s, which getClaudeFlags maps to null so the section stays hidden instead of erroring.

Enabling a mode that removes Claude's confirmations (bypassPermissions, dontAsk) requires an explicit confirmation: biometric authentication when the device has it enrolled, otherwise an Approve/Cancel dialog. Declining leaves the value unstaged.
The values are never cached to disk — they live on the server and can change there, so a persisted copy would show a stale security setting.
…behavior

The comments said hold_session makes the server hold the PTY immediately instead of waiting out its ~4.5-min grace timer.
That stopped being true when the handler changed to call startGraceTimer — the same path a WS disconnect takes — so the session now keeps running until the timer elapses.

Comment-only; no behavior change.
Authored during the 3aaf4b13 session, which was wedged by the OSC-777 phantom-gate bug before it could commit them.
Evict stale session/conversation favorites and eager cache rows on 404,
rehydrate the bound conversation after foreground reconnect, and cover
the hub→browse handoff plus merge/dedupe helpers with tests.
Make Scan QR the recommended path, explain local vs remote reachability,
validate server URLs before dial, improve camera-denial and error support
paths, allow naming the paired server, and stop pre-filling localhost in
production Add Server.
Keep paginated conversation fetches cancellable, surface empty/offline
server sections, and force server chips when the same project path exists
on more than one host so partial failures never blank the hub.
Assert favorite ids instead of union-narrowed conversationId, and stub
getQueryData on the mocked query client used by reconnect rehydrate.
Treat any non-ok/non-error fetch status as warming, and align the
manual connect integration assertion with On your computer copy.
Derive managed/external/resumed/on-hold/historical/stale labels from
existing wire fields, drive hub rows and the session status bar from
one helper, and localize live pills and status chrome.
…mport

Inline ownership/liveness checks so the helper ports to main without
requiring integration-only session fields as type imports.
## Summary
- Prefer raw terminal when parse confidence is low or chat has no
messages while PTY output is active, so normalized chat is never treated
as authoritative when uncertain
- Add row-level `RenderErrorBoundary` containment for message and
terminal line renders
- Extract provider terminal-chrome adapters (Claude / Codex /
passthrough) and track unsupported/truncated escapes in
`VirtualTerminal`
- Virtualize the live PTY placeholder, add copy-all for terminal output,
and clarify terminal vs chat mode on the session status bar
- Add unit coverage for chrome adapters, confidence policy, long-output
caps, and render containment

## Test plan
- [x] Unit: `terminalChrome`, `renderConfidence`, `virtual-terminal` (+
long-output), `RenderErrorBoundary`
- [x] Integration: `TerminalOutput`, `TerminalView`,
`LiveConversationView`
- [ ] Manual: open a live session in chat mode with PTY-only output and
confirm raw-terminal fallback banner
- [ ] Manual: toggle Terminal/Chat chip on session status bar
- [ ] Manual: copy-all on terminal output
## Summary
- Document the repo’s dominant PR title format (`type(scope): summary`)
and matching branch names in `AGENTS.md` / `CLAUDE.md`
- Record that this integration wave bases PRs on
`integration-merge-354-355-376`
- Explicitly retire bracket-slug titles like `[feat][task-name]` for new
work

## Test plan
- [x] Diff is docs-only
Surface Edit/Write diffs in a scoped read-only review UI with filters,
large-diff safeguards, desktop handoff copy, and optional agent notes.
…405)

## Summary
- Gate session-card / message enter animations on OS Reduce Motion
- Enforce 44pt minimum touch targets on terminal jump/copy and diff copy
controls
- Cap Dynamic Type on monospace terminal/diff text; add VoiceOver labels
for terminal lines
- Document remaining U12 follow-ups in `docs/a11y-theme-perf.md`

## Test plan
- [x] Unit: a11y constants
- [x] Integration: TerminalOutput
- [ ] Manual: Reduce Motion on → no FadeInDown on new cards/messages
- [ ] Manual: Dynamic Type XL → terminal stays usable
…#407)

## Summary
- Consume streamer `GET /api/diagnostics` (contract v1) with per-check
status and stable remediation codes
- Add Settings → Server health screen: multi-server picker, retry,
localized remediation actions, copy/share sanitized report
- Keep existing client feedback diagnostics (`/diagnostics`) separate

## Test plan
- [x] Unit: parse/report helpers (`server-diagnostics.test.ts`)
- [ ] Manual: Settings → Server health against a paired streamer on
`integration/missing-prs-2026-07-23`
- [ ] Confirm failed provider check shows `PROVIDER_NOT_INSTALLED`
remediation card
- [ ] Confirm copy report contains no secrets/URLs beyond the server
label
)

## Summary
- Consume streamer `GET /api/providers` (capabilities + availability +
warnings)
- Browse provider picker disables unavailable CLIs and shows localized
capability/compatibility notes
- Typed parser + unit coverage for the providers envelope

## Test plan
- [x] Unit: `provider-health.test.ts`
- [ ] Manual: browse with one provider missing on host → start disabled
+ warning
- [ ] Manual: Codex selected → “no structured questions” note when
available
## Summary
- Consume streamer `GET /api/push/health` with delivery states
(never-delivered / healthy / failing / dead / revoked)
- Settings → Delivery health: quiet-hours preview, retry, re-register
(sends `deviceId`)
- Remove the notifications “Coming soon” overlay so prefs and local test
are usable

## Test plan
- [x] Unit: push-health parser
- [ ] Manual: open Delivery health against a paired streamer with push
store
- [ ] Manual: Re-register updates token list; tokens never shown in UI
## Summary
- Add Settings → Paired devices screen over `GET /api/devices` and `POST
/api/devices/:id/revoke` (capability chips, last-seen, idempotent
revoke).
- Pair exchange now sends `deviceName` and stores `deviceId` /
capabilities (device token in SecureStore only; never shown).
- Owner API key remains the auth credential so device admin and backup
stay available; U7 provider warning copy clarified as unverified, not
broken.

## Test plan
- [ ] Pair a server against streamer with device registry; open Settings
→ Paired devices and confirm this device appears with capabilities.
- [ ] Revoke another device; confirm list updates and a second revoke
reports already-revoked.
- [ ] Confirm credentials/tokens never appear in the UI.
- [ ] Unit: `devices`, `pair-exchange`, `useTBPair`, i18n
completeness/unused.
## Summary
- Add Settings → Backup & restore over `GET /api/backup/export` and
`POST /api/backup/restore`.
- Export is shareable/copyable JSON with an explicit metadata-only
warning (conversations are not in the archive).
- Restore is dry-run first, then confirm apply; `RESTORE_CONFLICT`
surfaces the plan and blocks apply.

## Test plan
- [ ] Export against a streamer with projects; copy/share JSON; confirm
counts and host/version.
- [ ] Paste the archive back, dry-run, confirm create/update summary,
then apply.
- [ ] Force a path conflict and confirm apply stays blocked with
conflict copy.
- [ ] Optional pathMap from→to rewrites paths in the dry-run plan.
- [ ] Unit: `backup` parsers + i18n completeness/unused.
expo-router pulls @expo/ui@57.0.6 which references ColorCompat, but the
lockfile had resolved expo-modules-core to 57.0.1 where that class does
not exist, so release minify failed in :app:minifyReleaseWithR8.
Show the newest three recent locations inline; when more exist, open a
modal that lists all of them and filters by path substring.
The integration branch has never been run through test.yml, so three failures accumulated unnoticed across ~82 commits.
PR #425 was the first thing to trigger CI on it, which surfaced them.

Type check and Integration tests both traced to app/conversation/[id].tsx.
a1e263c added LivePauseControl to the header actions and, in resolving that conflict, deleted the in-chat search Pressable that ff28c60 had introduced, while leaving the MagnifyingGlass import, the headerButton styles, the searchBarState hook, and the test coverage all in place.
The same commit also dropped the makeSearchStyles import while keeping its call site, which is the TS2552 error.
Restore both: the import and the search button, verbatim from a1e263c's parent.

i18n unused keys flagged three keys with no remaining code reference.
b75dfb8 pruned connect.step1 and connect.step2 as dead, but ConnectStep.tsx still renders all three steps as a numbered sequence, so step3 rendered alone after a gap.
Restore step1 and step2 in all four locales from their pre-prune text.
Drop onboarding.connect.footnote and servers.dialog.removeDismiss, which are genuinely unreferenced.

conversation.search.open is left as-is: it labels the restored search button, so it was never dead.
Build 173 crashed at launch on every device with EXC_CRASH (SIGABRT) / "DYLD 4 Symbol missing": ExpoCamera 57.0.1 referenced AnyModule._decorateExpoObject(object:in:appContext:), but the bundled ExpoModulesCore 57.0.7 renamed that protocol requirement to the two-argument _decorateModule(object:in:).

The skew came from #412, which pinned expo-modules-core forward to 57.0.7 via npm overrides to get ColorCompat for the Android R8 minify step, without moving the rest of the Expo module set with it.
ExpoModulesCore ships as a precompiled xcframework, so the mismatch was invisible at compile time and only surfaced in dyld.

Bump expo to 57.0.8 and align all 22 expo-*/@expo/* modules to that SDK's bundled native module set, which supplies expo-modules-core 57.0.7 legitimately and moves expo-camera to 57.0.3.
The override is now redundant and is removed; the Android ColorCompat fix is preserved because core stays at 57.0.7.
Also bump react-native-screens to 4.26.2 to match what expo-router 57.0.8 requires, removing a duplicate nested copy.

expo-image-manipulator was still on 57.0.1 and referenced the same missing symbol, so the original fix was incomplete.

Add two guardrails, both verified against a real Release build:
- scripts/verify-dyld-symbols.sh resolves every cross-framework symbol in the archive before altool upload, so a launch-crashing build cannot reach TestFlight.
- npm run check:native-deps fails CI on duplicate native modules, which is the npm-level shape of this bug.
Drop accidentally tracked crash-log.txt and log-mobile.txt from the tree.
…-355-376-v2

# Conflicts:
#	app.json
#	ios/Podfile.lock
Xcode 26.3's Swift 6 compiler fails on abs(milliseconds) in
expo-modules-jsi's Date decoding with "type of expression is
ambiguous without a type annotation", breaking the Ship iOS job.
Expo's real fix (Xcode 26.4) isn't on GitHub's macos-15 runner yet,
so patch-package disambiguates the call as a stopgap.
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