Skip to content

Mobile sync stability + webhook relay productization + edit-protection removal#683

Merged
arul28 merged 8 commits into
mainfrom
ade/start-skill-launch-installe-dsimaultor-0d00d08b
Jul 2, 2026
Merged

Mobile sync stability + webhook relay productization + edit-protection removal#683
arul28 merged 8 commits into
mainfrom
ade/start-skill-launch-installe-dsimaultor-0d00d08b

Conversation

@arul28

@arul28 arul28 commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Large lane consolidating mobile (iOS) sync stability work, the GitHub webhook relay productization, and removal of file edit-protection.

iOS sync/chat stability (main focus)

Audited end-to-end (transport, chat render pipeline, SQLite/CRR, host). Crash/perf hardening, all verified (iOS build green, desktop sync 24/24, ade-cli mpRpc 10/10):

  • Fix Int64(Double) bind trap (safeInt64Value) — a poison-batch launch crasher; regression-tested
  • Fix gunzip pointer-escape UB; enforce 32 MiB chunk-reassembly byte cap (tested)
  • Fix reconnect-wedge (generation-paired reconnectConnectInFlight)
  • Evict per-session chat-event history (64-session LRU, tested)
  • Host chat_subscribe snapshot clamped to client maxBytes + snapshot/pump dedupe (tested)
  • Handoff chat classification via isChatToolType (cursor/droid + orphan rows)
  • Files diff O(n·m) LCS → CollectionDifference, computed once into @State
  • Attachment thumbnails downsample via CGImageSourceCreateThumbnailAtIndex
  • Quick wins: memoized build hash, cached regex, in-place recordChatEventEnvelope, capped prDetailCache, CTO tab-visibility gate, LaneDetail defer-not-drop throttle

Webhook relay productization

Hosted Cloudflare Worker is the default; ADE polls repo-scoped routes (/github/repos/:owner/:repo/status + /events) authorized by the user's existing GitHub token — no relay token / local.secret.yaml needed. Legacy /projects/:projectId/... token routes preserved for self-hosted. HMAC-verified ingestion, table-wide retention pruning, negative-authz tested.

Edit-protection removed

Files are freely editable on desktop and mobile — no "Enable editing" step. The is_edit_protected column and its auto-rebase exclusion are untouched; only the file-edit gate was removed.

Tests / parity

Webhook 19/19, desktop sync/files/github green, TUI 744 green, ade-cli typecheck clean. Docs updated (files-and-editor, automations, sync-and-multi-device, onboarding). Mobile/CLI/TUI parity: no changes required.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced the all-projects hub for browsing projects, lanes, chats, and starting new chats.
    • Added mobile hub roster sync plus new chat history/subagent and lane-based PR actions.
    • Improved chat transcript controls and event history windowing.
    • Enhanced automation ingress and GitHub polling for hosted relay support.
  • Bug Fixes

    • Improved sync host handoffs, roster subscription/delta updates, and GitHub installation-status refresh/token handling.
    • Fixed chat transcript merging/trimming and tightened streamed envelope handling.
    • Made file/workspace editing more consistently available and improved mobile diff/preview behavior.

Greptile Summary

This PR consolidates three workstreams: iOS/mobile sync stability hardening, webhook relay productization (hosted Cloudflare Worker as default, GitHub-token auth), and removal of the file edit-protection gate.

  • iOS stability: Fixes a Int64(Double) bind trap in safeInt64Value, a gunzip pointer-escape UB, a reconnect-wedge via generation-paired reconnectConnectInFlight, and adds a 64-session LRU cap on the chat event history cache.
  • Webhook relay productization: The hosted Worker becomes the default relay (DEFAULT_GITHUB_RELAY_API_BASE_URL); desktop polls /github/repos/:owner/:repo/events using the user's existing GitHub token instead of a manually configured relay token. Legacy /projects/:projectId/... routes are preserved for self-hosted deployments with RELAY_ACCESS_TOKEN set.
  • Edit-protection removal: The "Enable editing" gate is dropped from the file UI on both desktop and mobile; the is_edit_protected DB column and auto-rebase exclusion remain untouched.

Confidence Score: 5/5

Safe to merge. All three workstreams are well-scoped, the iOS crash fixes are correct, and the relay productization is backed by 19 passing tests with explicit negative-authz coverage.

The iOS safeInt64Value bound uses strict < Double(Int64.max) (not <=), which correctly avoids the floating-point rounding trap. The SyncEnvelopeChunkAssembler byte cap tracks decoded bytes before assembly, closing the UB window. Generation pairing on reconnectConnectInFlight is properly threaded through all clear sites. The two observations flagged are design-level and do not affect correctness.

apps/webhook-relay/src/relay.ts (assertGitHubRepoAuthorized) and apps/desktop/src/main/services/github/githubRelayConfig.ts (configured field) are worth revisiting as usage scales up.

Important Files Changed

Filename Overview
apps/webhook-relay/src/relay.ts Adds repo-scoped events endpoint and updates repo-status to use GitHub-token auth; every poll makes a live uncached GitHub API call for authorization.
apps/desktop/src/main/services/github/githubRelayConfig.ts Introduces hosted default relay URL, changes configured to always true for the hosted default, and adds shouldUseLegacyGitHubRelayProjectRoute.
apps/desktop/src/main/services/automations/automationIngressService.ts Switches to repo-scoped relay polling using the user's GitHub token; keeps legacy project-token path for self-hosted.
apps/ios/ADE/Services/SyncService.swift Large stability sweep: safeInt64Value crash fix, 32 MiB chunk cap, generation-paired reconnectConnectInFlight, 64-session LRU chat history eviction, and all-projects roster subscription.
apps/ios/ADE/Services/Database.swift Adds safeInt64Value guard (isFinite + strict < Double(Int64.max)) to fix the Int64(Double) bind trap.
apps/ade-cli/src/services/sync/syncHostService.ts Adds all-projects roster provider with per-peer baseline diffing, debounced flush (250ms/1s cap), safety poll (15s), and correct backpressure rollback.
apps/desktop/src/main/services/chat/agentChatService.ts Adds transcript-based subagent snapshot hydration, mergeSubagentSnapshots, hard-cap option for mobile, and mergeAssistantTranscriptText for non-contiguous turn merging.
apps/webhook-relay/migrations/0003_github_events_repository.sql Adds index on (repository_full_name collate nocase, received_at desc) to support the new repo-scoped events queries.

Reviews (3): Last reviewed commit: "ship: iteration 2 — sweep AgentChatSessi..." | Re-trigger Greptile

arul28 and others added 6 commits July 1, 2026 03:47
iOS crash/perf hardening (verified: iOS build green, desktop sync 24/24,
ade-cli mpRpc 10/10):
- Fix Int64(Double) bind trap (safeInt64Value) — poison-batch launch crasher
- Fix gunzip pointer-escape UB; enforce 32MiB chunk-reassembly byte cap
- Fix reconnect-wedge (generation-paired reconnectConnectInFlight flag)
- Evict per-session chat-event history (64-session LRU)
- Clamp chat_subscribe snapshot to client maxBytes + dedupe snapshot/pump overlap
- Files diff: O(n·m) LCS -> CollectionDifference, computed once into @State
- Attachment thumbnails downsample via CGImageSourceCreateThumbnailAtIndex
- Handoff classification via isChatToolType (cursor/droid + orphan chat rows)
- fetchSessionLocked keeps lane-less sessions fetchable by id

Quick wins: memoize runtime build hash; cache compiled regex in
workRegexMatches; shared date parser in CTO workflows; raise highlight cache
to 160; LaneDetail defer-not-drop throttle; in-place recordChatEventEnvelope
(no per-event array copy); cap prDetailCache; CtoTeamScreen visibility gate.

Webhook relay productization: hosted Cloudflare Worker default, repo-scoped
routes authorized by the user's GitHub token (no relay token / local.secret
required); legacy project-token routes preserved for self-hosted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Files are freely editable with no "Enable editing" step. Removes the
FilesWorkbench read-only gate + editOverrides machinery; derives workspace
isReadOnlyByDefault as constant false (laneService, iOS Database, browserMock).
Keeps the is_edit_protected column and its auto-rebase exclusion filters
(`and is_edit_protected = 0`) untouched — this change is scoped to file
editing only. FilesWorkbench test flipped to assert edit-protected workspaces
are editable immediately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- relay.test.ts: cover token-present-but-denied on both repo-scoped routes
  (events + status) — asserts 403/404 and no webhook-event leakage to a
  denied caller. Closes the /quality-flagged coverage gap.
- docs: reflect edit-protection removal (files freely editable), webhook
  relay productization (hosted Worker default, repo-scoped token auth), the
  mobile chat_subscribe maxBytes clamp + 64-session history eviction, and
  the new all-projects Hub roster sub-protocol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview Jul 2, 2026 12:59am

@arul28 arul28 changed the title Context Iphone 17 Simulator Mobile sync stability + webhook relay productization + edit-protection removal Jul 1, 2026
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds mobile hub roster sync, new chat history/subagent command surfaces, hosted GitHub relay routing, files-workspace writable-by-default changes, and broad iOS Work chat, PR navigation, and core-service updates across desktop and iOS.

Changes

All-Projects Roster / Mobile Hub Sync Protocol

Layer / File(s) Summary
Roster types and provider contract
apps/desktop/src/shared/types/sync.ts, apps/ade-cli/src/services/sync/sharedSyncListener.ts, apps/ade-cli/src/services/sync/syncHostService.ts
Adds roster chat/lane/project payload types, envelope variants, and the SyncRosterProvider contract.
Roster snapshot builder and tests
apps/ade-cli/src/services/projects/projectScope.ts, apps/ade-cli/src/services/sync/rosterBuilder.ts, apps/ade-cli/src/services/sync/rosterBuilder.test.ts
Implements buildRosterSnapshot from disk/live data with coverage for unbooted, booted, and missing-DB cases.
Sync host roster lifecycle
apps/ade-cli/src/services/sync/syncHostService.ts, apps/ade-cli/src/services/sync/syncHostService.test.ts
Adds per-peer roster subscription state, coalesced flush/delta computation, and handoff re-priming.
CLI/bootstrap wiring
apps/ade-cli/src/bootstrap.ts, apps/ade-cli/src/cli.ts, apps/ade-cli/src/services/sync/syncService.ts
Wires the roster provider through bootstrap and sync-service initialization.
iOS roster models and sync extension
apps/ios/ADE/Models/RemoteRosterModels.swift, apps/ios/ADE/Services/SyncService.swift, apps/ios/ADE/Views/Hub/*, apps/ios/ADE/App/ContentView.swift, apps/ios/ADE/Views/Components/ADEDesignSystem.swift
Adds iOS roster models, roster sync persistence/application, and hub screen/composer/navigation UI.

Estimated code review effort: 5 (Critical) | ~180 minutes

Chat History, Remote Commands, and GitHub Relay Auth

Layer / File(s) Summary
Chat history backend
apps/desktop/src/main/services/chat/agentChatService.ts, apps/desktop/src/main/services/chat/agentChatService.test.ts, apps/desktop/src/shared/types/chat.ts, apps/ios/ADE/Models/RemoteModels.swift
Adds byte-budget trimming, subagent snapshot handling, and transcript correlation fields.
Remote command routing
apps/ade-cli/src/services/sync/syncRemoteCommandService.ts, apps/ade-cli/src/services/sync/syncRemoteCommandService.test.ts, apps/desktop/src/main/services/sync/syncRemoteCommandService.test.ts, apps/desktop/src/main/services/sync/syncService.ts
Registers chat event history, subagent transcript/list, and PR-by-lane remote commands.
GitHub relay auth and installation status
apps/desktop/src/main/services/github/*, apps/ade-cli/src/headlessLinearServices.ts, apps/desktop/src/main/main.ts, apps/desktop/src/main/services/automations/automationIngressService.ts, .test.ts
Reworks hosted vs legacy relay routing and token sourcing for installation checks and relay polling.
Build-hash diagnostics
apps/ade-cli/src/multiProjectRpcServer.ts, .test.ts, apps/ade-cli/src/stdioRpcDaemon.test.ts
Computes and caches a fallback SHA-256 runtime build hash from the entrypoint when none is advertised.

Estimated code review effort: 4 (Complex) | ~90 minutes

Files Workspace Read-Only Removal

Layer / File(s) Summary
Backend default
apps/ios/ADE/Resources/DatabaseBootstrap.sql, apps/desktop/src/main/services/lanes/laneService.ts, apps/ios/ADE/Services/Database.swift, apps/ade-cli/src/types/node-sqlite.d.ts
Replaces mobile_read_only with is_read_only_by_default and forces workspace editability to false by default.
Sync host write permissions
apps/desktop/src/main/services/sync/syncHostService.ts, apps/desktop/src/main/services/sync/syncHostService.test.ts
Removes mobile file-mutation gating and updates tests to expect successful writes.
Renderer edit-override removal
apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx, apps/desktop/src/renderer/components/files/v2/FilesWorkbench.test.tsx, apps/desktop/src/renderer/browserMock.ts
Removes the "Enable editing" override UI and makes browser mock workspaces writable by default.

Estimated code review effort: 3 (Moderate) | ~30 minutes

iOS Work Chat Timeline and Composer Overhaul

Layer / File(s) Summary
Timeline render models
apps/ios/ADE/Views/Work/WorkModels.swift, apps/ios/ADE/Models/RemoteModels.swift
Expands snapshot, marker, subagent, and event metadata used by the timeline.
Incremental rebuild pipeline
apps/ios/ADE/Views/Work/WorkChatSessionView+Actions.swift
Adds incremental snapshot caching and coalesced rebuild scheduling.
Session view body/state
apps/ios/ADE/Views/Work/WorkChatSessionView.swift, apps/ios/ADE/Views/Work/WorkChatSessionView+Timeline.swift
Rewires timeline/composer rendering around the new render context and render entries.
Composer/attachments/fast-mode
apps/ios/ADE/Views/Work/WorkChatComposerAndInputViews.swift, apps/ios/ADE/Views/Work/WorkChatAttachmentTray.swift, apps/ios/ADE/Views/Work/WorkContextCompactDivider.swift
Adds cached fast-mode checks and downsampled attachment previews.
Message rendering/preview budgeting
apps/ios/ADE/Views/Work/WorkChatHeaderAndMessageViews.swift
Adds head/tail anchored assistant previews and new message bubble styling.
Subagent rich cards
apps/ios/ADE/Views/Work/WorkChatRichCardViews.swift
Adds status ribbons and subagent popup/drawer UI.
Streaming merge/backfill
apps/ios/ADE/Views/Work/WorkErrorAndMessageHelpers.swift
Reworks streaming text merge and backfill replacement heuristics.
Markdown/model picker/activity indicator
apps/ios/ADE/Views/Work/WorkMarkdownParsing.swift, WorkMarkdownViews.swift, WorkModelPickerSheet.swift, WorkActivityIndicator.swift, WorkNavigationAndTranscriptHelpers.swift, WorkEventMapping.swift
Adds ordered-list numbering, cursor source selection, and updated activity / tool-card mappings.
Plan approval composer
apps/ios/ADE/Views/Work/WorkPlanComposerViews.swift, apps/ios/ADE/Views/Work/WorkPreviews.swift
Adds plan-approval strip/full-screen views and preview wiring.
Root sidebar/navigation
apps/ios/ADE/Views/Work/WorkRootComponents.swift, WorkRootScreen.swift, WorkRootScreen+Actions.swift
Adds compact/child rows and reworks navigation/presentation task wiring.
Artifact loading
apps/ios/ADE/Views/Work/WorkSessionDestinationView+Actions.swift
Rewrites artifact content loading paths.

Estimated code review effort: 5 (Critical) | ~240 minutes

iOS PR Navigation and Work Chat PR Integration

Layer / File(s) Summary
PR navigation model
apps/ios/ADE/Services/SyncService.swift, apps/ios/ADE/Views/PRs/PrHelpers.swift, apps/ios/ADE/Views/PRs/PrsRootScreen.swift
Introduces target-based PR navigation requests and single-lane create-PR flow.
Work chat PR views
apps/ios/ADE/Views/Work/WorkChatPrViews.swift
Adds PR badge and details sheet components.
Lane PR resolution
apps/ios/ADE/Views/Work/WorkSessionDestinationView+Actions.swift
Centralizes PR tag selection and mapping logic.

Estimated code review effort: 3 (Moderate) | ~40 minutes

iOS Core Services and Misc UI Polish

Layer / File(s) Summary
SyncService networking/reconnect and APIs
apps/ios/ADE/Services/SyncService.swift
Improves reconnect handling, chunking, and sync APIs.
Database hardening
apps/ios/ADE/Services/Database.swift
Adds safe int64 binding and refactored session fetch/cleanup.
Misc UI polish
apps/ios/ADE/Debug/ADEInspectorKit/ADEInspectable.swift, apps/ios/ADE/Views/Cto/*, apps/ios/ADE/Views/Settings/*, apps/ios/ADE/Views/Lanes/LaneDetailScreen.swift
Updates debug snapshot signatures, CTO polling, settings layout, and reload throttling.
Files browser transitions
apps/ios/ADE/Views/Files/*
Adds workspace-scoped transition IDs and diff rendering improvements.

Estimated code review effort: 3 (Moderate) | ~45 minutes

Possibly related PRs

  • arul28/ADE#525: Both modify agentChatService.getChatEventHistory/history truncation behavior and its returned metadata.
  • arul28/ADE#588: Both touch WorkChatComposerAndInputViews.swift fast-mode/provider-model wiring.
  • arul28/ADE#80: Both change assistant text aggregation behavior in agentChatService.

Suggested labels: desktop, ios

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the three main themes of the changeset: mobile sync stability, webhook relay updates, and edit-protection removal.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/start-skill-launch-installe-dsimaultor-0d00d08b

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mintlify

mintlify Bot commented Jul 1, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
ade-ac1c6011 🟢 Ready View Preview Jul 1, 2026, 11:46 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@arul28

arul28 commented Jul 1, 2026

Copy link
Copy Markdown
Owner Author

@copilot review but do not make fixes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/ade-cli/src/multiProjectRpcServer.ts (1)

460-493: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Align runtime build-hash fallback across RPC servers. apps/ade-cli/src/adeRpcServer.ts still returns buildHash: null when ADE_RUNTIME_BUILD_HASH is unset, while apps/ade-cli/src/multiProjectRpcServer.ts hashes process.argv[1] instead. That makes machineRuntimeMismatchReason depend on which server handles ade/initialize when the env is missing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/multiProjectRpcServer.ts` around lines 460 - 493, Align the
runtime build-hash fallback in the RPC server initialization path so both
servers behave the same when ADE_RUNTIME_BUILD_HASH is missing. Update
adeRpcServer’s runtime env resolution to match multiProjectRpcServer’s
computeRuntimeBuildHash logic, using process.argv[1] with file checks and
hashing as the fallback instead of returning null. Keep the behavior consistent
through the relevant resolveRuntimeEnvInfo/initialize flow so
machineRuntimeMismatchReason does not vary by server.
apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift (1)

47-62: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Provide a fallback when a saved host has no display name. stateDetailLine can return nil in the disconnected state when canReconnectToSavedHost is true but no host name is available, leaving the card blank; show a generic reconnect caption in that case. apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift:47-62, 136-152

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift` around lines 47 -
62, The disconnected-state copy in SettingsConnectionHeader is missing a
fallback when a saved host exists but has no display name, so the card can
render blank. Update stateDetailLine and/or the disconnected branch in
SettingsConnectionHeader to return a generic reconnect caption whenever
canReconnectToSavedHost is true but no host name is available, using the
existing pendingDescription/stateDetailLine flow as the place to preserve the
connected, pending, and onboarding cases.
apps/desktop/src/main/services/github/githubRelayConfig.ts (1)

169-175: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

"unconfigured" branch is now unreachable dead code.

Since apiBaseUrl is always populated via configuredApiBaseUrl || DEFAULT_GITHUB_RELAY_API_BASE_URL, config.apiBaseUrl can never be falsy, so if (!config.apiBaseUrl) here never triggers. This means fetchGitHubAppInstallationStatus can no longer report the "unconfigured" state even when a project genuinely has zero relay setup — it silently falls through to the hosted-default path instead. If that's intentional (per the hosted-default design), consider removing this dead branch or replacing the check with something that reflects the real "fully unconfigured" condition (e.g. !config.remoteProjectId && !config.accessToken && config.usesHostedDefault === undefined-style explicit flag), so the code doesn't imply a reachable state that no longer exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/github/githubRelayConfig.ts` around lines 169
- 175, The `fetchGitHubAppInstallationStatus` check for `!config.apiBaseUrl` is
dead code because `githubRelayConfig` always assigns a default API base URL, so
the `"unconfigured"` status can never be returned. Remove this unreachable
branch or replace it with an explicit fully-unconfigured condition based on the
real config flags used by `githubRelayConfig` and
`fetchGitHubAppInstallationStatus`, so the status logic matches the
hosted-default behavior and the code does not advertise an unreachable state.
🟡 Minor comments (12)
apps/ios/ADE/Views/PRs/PrsRootScreen.swift-1345-1357 (1)

1345-1357: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Programmatic create-PR navigation skips the canCreatePr capability gate.

The manual "+" button disables/greys out when !canCreatePr (Lines 664-665), but the request.createLaneId-driven path here presents createPrWizardSheet unconditionally, with no canCreatePr check. If the host is offline or lacks create capabilities, this could surface a create wizard that can't actually create anything.

🐛 Proposed fix to gate the programmatic create path
     if let createLaneId = request.createLaneId?.trimmingCharacters(in: .whitespacesAndNewlines),
        !createLaneId.isEmpty {
       await reload(refreshRemote: false)
+      guard canCreatePr else {
+        errorMessage = "Pull request creation isn't available on this phone right now."
+        syncService.requestedPrNavigation = nil
+        return
+      }
       rootSurfaceRawValue = PrRootSurface.github.rawValue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/PRs/PrsRootScreen.swift` around lines 1345 - 1357, The
programmatic create-PR navigation path in PrsRootScreen’s request.createLaneId
handling bypasses the same canCreatePr gate used by the manual “+” button.
Before setting createPresented and opening the createPrWizardSheet, check
canCreatePr in this block and only proceed when creation is allowed; otherwise
ignore the request or route back without presenting the wizard.
apps/ios/ADE/Views/Work/WorkSessionDestinationView+Actions.swift-697-701 (1)

697-701: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Keep PR details refresh single-flight even when forced.

Line 698 lets every forced refresh start while another refresh is in flight, so overlapping tasks can interleave and stale snapshot/error state can win after awaits. Prefer guarding all in-flight refreshes, and keep force only for the refresh behavior.

Proposed fix
-    guard force || !prDetailsRefreshing else { return }
+    guard !prDetailsRefreshing else { return }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Work/WorkSessionDestinationView`+Actions.swift around
lines 697 - 701, The refreshChatPrDetails flow currently allows forced calls to
bypass the in-flight guard, which can start overlapping refresh tasks and let
stale snapshot/error state overwrite newer results after awaits. Update
refreshChatPrDetails so the single-flight protection always blocks when
prDetailsRefreshing is already true, and use force only to control whether the
refresh should bypass cached data or trigger a reload. Keep the fix localized to
refreshChatPrDetails and the prDetailsRefreshing/prDetailsError state handling.
apps/ios/ADE/Views/Work/WorkChatRichCardViews.swift-1027-1060 (1)

1027-1060: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use status-specific icons and include metadata in the accessibility label.

Line 1034 shows a pause icon for every non-failed status, so completed/started statuses can look interrupted. Line 1060 also omits the metadata status when card.body is empty, even though that is the visible ribbon text.

Proposed fix
   private var statusRibbonBody: some View {
     let normalized = card.metadata.first?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() ?? ""
     let isFailure = normalized == "failed"
     let isInterrupted = normalized == "interrupted"
+    let isCompleted = ["completed", "complete", "succeeded", "success"].contains(normalized)
+    let icon = isFailure
+      ? "xmark.circle.fill"
+      : (isInterrupted ? "pause.circle.fill" : (isCompleted ? "checkmark.circle.fill" : "circle.fill"))
     let tint = isFailure ? ADEColor.danger : (isInterrupted ? ADEColor.warning : ADEColor.textMuted)
+    let statusText = normalized.isEmpty ? ribbonText.uppercased() : normalized.uppercased()
 
     return HStack(alignment: .center, spacing: 8) {
-      Image(systemName: isFailure ? "xmark.circle.fill" : "pause.circle.fill")
+      Image(systemName: icon)
         .font(.system(size: 11, weight: .bold))
-      Text(normalized.isEmpty ? ribbonText.uppercased() : normalized.uppercased())
+      Text(statusText)
         .font(.caption2.monospaced().weight(.semibold))
         .tracking(0.8)
@@
     .frame(maxWidth: .infinity, alignment: .leading)
     .accessibilityElement(children: .combine)
-    .accessibilityLabel([card.title, card.body].compactMap { $0 }.joined(separator: ". "))
+    .accessibilityLabel([card.title, statusText, card.body].compactMap { $0 }.joined(separator: ". "))
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Work/WorkChatRichCardViews.swift` around lines 1027 -
1060, Update statusRibbonBody in WorkChatRichCardViews so the leading SF Symbol
matches the normalized metadata status instead of defaulting every non-failed
case to pause.circle.fill; map completed/started/other statuses to appropriate
icons while keeping failed/interrupted distinct. Also fix the accessibilityLabel
on the ribbon to include the visible status text (normalized or ribbonText)
alongside card.title and card.body, so when card.body is empty the status is
still announced.
apps/ios/ADE/Views/Work/WorkChatSessionView.swift-139-160 (1)

139-160: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Resolve the @State visibility warnings.
These properties are used from WorkChatSessionView+Actions.swift and WorkChatSessionView+Timeline.swift, so they can’t be made private without moving that logic into the same file. Add a narrow swiftlint:disable private_swiftui_state with a brief rationale, or colocate the related code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Work/WorkChatSessionView.swift` around lines 139 - 160,
The `WorkChatSessionView` state properties are triggering visibility warnings
because they are shared with `WorkChatSessionView+Actions.swift` and
`WorkChatSessionView+Timeline.swift`. Keep the cross-file access intact and
either add a narrow `swiftlint:disable private_swiftui_state` with a brief
rationale near the affected `@State` declarations, or move the dependent logic
into the same `WorkChatSessionView` file. Focus on the state block containing
`composerLayoutHeight`, `timelineSnapshot`, `timelineRebuildTask`, and the other
shared `@State` members.

Sources: Path instructions, Linters/SAST tools

apps/ade-cli/src/services/sync/syncHostService.ts-2807-2810 (1)

2807-2810: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not drop roster dirties that arrive during an in-flight flush.

If a debounce/max-wait timer fires while rosterFlushInFlight is true, flushRoster() clears the timers and returns, so changes that happened after the snapshot build started can be lost until the safety poll. Track a pending flag and schedule another flush in finally.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/services/sync/syncHostService.ts` around lines 2807 - 2810,
The `flushRoster()` flow in `syncHostService.ts` is dropping roster dirties that
arrive while `rosterFlushInFlight` is true because it clears timers and exits
without retrying. Add a pending-flush flag in the roster flush state, have
`flushRoster()` set it when called during an in-flight flush, and in the
existing `finally` block schedule another flush if that flag is set. Use the
`flushRoster`, `clearRosterFlushTimers`, and `rosterFlushInFlight` logic to
locate the fix and ensure no dirty updates are lost between snapshot build and
completion.
apps/ade-cli/src/services/sync/syncHostService.ts-197-205 (1)

197-205: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include all roster-visible mutations in the dirtying set.

This set misses commands that remove or restore visible rows/metadata, e.g. chat.archive, chat.unarchive, chat.delete, lanes.unarchive, lanes.rename, and lanes.updateAppearance. Those hub actions can otherwise remain stale until the 15s safety poll.

Suggested patch
 const ROSTER_DIRTYING_COMMAND_ACTIONS = new Set<string>([
   "chat.create",
+  "chat.archive",
+  "chat.unarchive",
+  "chat.delete",
+  "chat.updateSession",
   "work.startCliSession",
   "work.resumeCliSession",
+  "work.updateSessionMeta",
   "lanes.create",
   "lanes.createChild",
+  "lanes.rename",
+  "lanes.updateAppearance",
   "lanes.archive",
+  "lanes.unarchive",
   "lanes.delete",
 ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/services/sync/syncHostService.ts` around lines 197 - 205,
The ROSTER_DIRTYING_COMMAND_ACTIONS set in syncHostService should include every
roster-visible mutation, not just creation/deletion actions. Update the set to
add the missing hub actions such as chat.archive, chat.unarchive, chat.delete,
lanes.unarchive, lanes.rename, and lanes.updateAppearance so syncHostService
marks the roster dirty immediately after those mutations.
apps/ade-cli/src/services/sync/rosterBuilder.ts-64-70 (1)

64-70: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

hostProjectId is documented but never used.

BuildRosterSnapshotArgs.hostProjectId claims the host project is "always booted — included with live fidelity," but this field is never read in buildRosterSnapshot/buildRosterProject. Both callers (bootstrap.ts, cli.ts) pass it expecting that guarantee. Today it happens to work out because switchSyncHost separately boots that scope, but the explicit "always" contract isn't actually enforced by this function — e.g. a roster build racing the host project's boot won't get live fidelity despite the promise.

Either wire it through (e.g. await/force-boot the host scope before falling back to disk-only) or drop the unused field and update the doc comment/callers accordingly.

Also applies to: 384-413

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/services/sync/rosterBuilder.ts` around lines 64 - 70,
`BuildRosterSnapshotArgs.hostProjectId` is documented as guaranteeing the host
project is always booted, but `buildRosterSnapshot`/`buildRosterProject` never
read it, so the contract is not enforced. Update the roster-building flow to
either use `hostProjectId` to ensure the host scope is booted and included with
live fidelity before snapshotting, or remove the unused field and adjust the doc
comment plus the `bootstrap.ts` and `cli.ts` callers to match the actual
behavior. Use the `buildRosterSnapshot` and `buildRosterProject` symbols to
locate the implementation.
apps/ios/ADE/Views/Hub/HubScreen.swift-316-323 (1)

316-323: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Rebuild presentations after restoring saved order.

projectOrder is restored here, but hubProjectPresentations may already have been built using the default alphabetical order. Rebuild immediately so the persisted layout is actually applied on reconnect.

Proposed fix
     collapsedLaneKeys = Set(saved.collapsedLanes)
     collapsedDefaultsSeededProjectIds = Set(saved.seededProjects)
     collapsedDefaultsSeededLaneKeys = Set(saved.seededLanes)
-    seedCollapsedHubDefaults(for: hubProjectPresentations)
+    rebuildHubProjectPresentations()
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Hub/HubScreen.swift` around lines 316 - 323, After
restoring layout in HubScreen.restoreSavedLayout, rebuild the presentation list
immediately because hubProjectPresentations may still reflect the default
alphabetical order. Update the flow so the restored projectOrder is applied
before any derived presentations are used, then regenerate or refresh the
presentations with the restored order. Use the existing restoreSavedLayout logic
and the hubProjectPresentations source so the persisted layout takes effect on
reconnect.
apps/ios/ADE/Views/Hub/HubScreen.swift-27-27 (1)

27-27: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep openChatTarget private.

@State is view-local mutable state; make openChatTarget private unless another type needs direct access.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Hub/HubScreen.swift` at line 27, Make the HubScreen view’s
openChatTarget state private, since it is only used as local mutable UI state.
Update the property declaration for openChatTarget in HubScreen.swift to use
private with `@State`, and keep any access through HubScreen’s own methods or
bindings unless another type truly needs it.

Source: Linters/SAST tools

apps/desktop/src/main/services/chat/agentChatService.ts-7242-7242 (1)

7242-7242: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Thread maxBytes through chat.getChatEventHistory
agentChatService.getChatEventHistory accepts maxBytes, but the renderer/IPC boundary still only forwards maxEvents, so the new limit can’t be set from apps/desktop/src/preload/preload.ts or apps/desktop/src/main/services/ipc/registerIpc.ts. Add maxBytes to the shared args or keep the option internal-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/chat/agentChatService.ts` at line 7242,
`agentChatService.getChatEventHistory` now accepts `maxBytes`, but the
renderer-to-main path still drops it, so the limit cannot be configured through
the IPC flow. Update the shared chat history args and the IPC wiring in
`preload` and `registerIpc` so `maxBytes` is forwarded alongside `maxEvents`
into `getChatEventHistory`, or remove it from the public signature if it should
remain internal-only. Make sure the change is applied consistently wherever the
chat history options are typed or destructured.

Source: Coding guidelines

apps/desktop/src/main/services/chat/agentChatService.ts-7269-7274 (1)

7269-7274: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

maxBytes should be measured in UTF-8 bytes, not JSON.stringify(...).length. The current mobile cap is well below the 16 MiB RPC ceiling, so this won’t usually trip transport limits, but it can still exceed the caller’s requested byte budget on multi-byte transcripts. Switching the envelope estimator to Buffer.byteLength(JSON.stringify(envelope), "utf8") would make the cap match the API contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/chat/agentChatService.ts` around lines 7269 -
7274, The maxBytes handling in agentChatService should use UTF-8 byte اندازه
rather than string length when estimating the response envelope. Update the
logic around the requestedMaxBytes/responseMaxChars calculation and the envelope
sizing path to measure JSON output with Buffer.byteLength(..., "utf8") instead
of JSON.stringify(...).length, so the cap in chat history responses matches the
API contract for multi-byte content.
apps/ios/ADE/Models/RemoteModels.swift-734-780 (1)

734-780: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the missing fields to AgentChatSessionSummary equality

cursorModeSnapshot, cursorConfigValues, computerUse, and completion are part of the model state but are ignored here, so summaries that differ only in those values still compare equal. That also flows into CtoRoster equality via cto ==. Add them to == unless they’re intentionally excluded.

🐛 Proposed fix
       && lhs.orchestrationStepId == rhs.orchestrationStepId
       && lhs.orchestrationBundlePath == rhs.orchestrationBundlePath
+      && lhs.cursorModeSnapshot == rhs.cursorModeSnapshot
+      && lhs.cursorConfigValues == rhs.cursorConfigValues
+      && lhs.computerUse == rhs.computerUse
+      && lhs.completion == rhs.completion
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Models/RemoteModels.swift` around lines 734 - 780,
`AgentChatSessionSummary` equality is missing several state fields, so objects
can compare equal even when they differ. Update the `==` implementation in
`AgentChatSessionSummary` to include `cursorModeSnapshot`, `cursorConfigValues`,
`computerUse`, and `completion` alongside the existing property checks, unless
they are intentionally meant to be excluded from identity. This will also keep
`CtoRoster` equality via `cto ==` consistent with the full model state.
🧹 Nitpick comments (6)
apps/ade-cli/src/multiProjectRpcServer.test.ts (1)

61-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move handler.dispose() into the finally block.

If the expect(init).toMatchObject(...) assertion fails, handler.dispose() (line 80) is skipped, leaking the handler/scope registry resources for the rest of the test run.

🧹 Proposed fix
       expect(init).toMatchObject({
         runtimeInfo: {
           buildHash: expectedHash,
           multiProject: true,
         },
       });
-      handler.dispose();
     } finally {
+      handler?.dispose();
       process.argv = originalArgv;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/multiProjectRpcServer.test.ts` around lines 61 - 89, Move
the cleanup for createMultiProjectRpcRequestHandler into the existing
try/finally flow so it always runs, even when the expect(init).toMatchObject
assertion fails. Keep the dispose call next to the handler variable in the
multiProjectRpcServer test, and ensure handler.dispose() is executed from the
finally block after the registry/argv/env restoration logic.
apps/ios/ADE/Views/Work/WorkChatPrViews.swift (1)

172-173: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the shared PR-state tint helper

workChatPrStateTint duplicates the same open/draft/closed/merged mapping already used by lanePullRequestTint, so this should call the shared helper to keep the badge and details sheet in sync if the palette changes.

♻️ Proposed consolidation
-    let stateTint = workChatPrStateTint(tag.state)
+    let stateTint = lanePullRequestTint(tag.state)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Work/WorkChatPrViews.swift` around lines 172 - 173, The
PR-state tint mapping in WorkChatPrViews is duplicated and can drift from the
shared badge/details palette. Update the state tint logic in the WorkChatPrViews
flow to use the shared lanePullRequestTint helper instead of the local
workChatPrStateTint mapping, keeping the branch/badge colors consistent wherever
tag.state is rendered.
apps/ios/ADE/Views/Components/ADECodeRenderingCache.swift (1)

14-14: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a size bound to the highlighted-string cache. countLimit = 160 only caps entry count, not the memory held by large AttributedString values. Since the cache stores highlighted output for the full input text, add totalCostLimit with per-entry cost accounting, or keep the lower limit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Components/ADECodeRenderingCache.swift` at line 14, The
highlighted-string cache in ADECodeRenderingCache only limits entry count via
attributedCache.countLimit, so large AttributedString values can still consume
too much memory. Update the cache setup to add a totalCostLimit and make the
caching path account for per-entry cost when storing highlighted output, or
reduce the existing limit if cost tracking is not feasible. Use the
ADECodeRenderingCache cache initialization and the highlighted-string storage
logic to apply the bound consistently.

Source: Path instructions

apps/ios/ADE/Views/Files/FilesModels.swift (1)

284-301: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid the full string concatenation just to compute a byte count.

combinedText = "\(diff.original.text)\n\(diff.modified.text)" (Line 293) copies both diff sides into a new string purely to compute combinedText.utf8.count. This is exactly the kind of large-diff cost the new filesLinePairCountExceedsLimit guard above is trying to avoid — for diffs that pass the line-pair check but are still large, this still forces a full duplicate allocation/copy. Summing the UTF-8 counts directly avoids the copy.

⚡ Proposed fix to avoid the concatenation
-  let combinedText = "\(diff.original.text)\n\(diff.modified.text)"
   return filesTextLimit(
-    byteCount: combinedText.utf8.count,
+    byteCount: diff.original.text.utf8.count + diff.modified.text.utf8.count + 1,
     lineCount: originalLineCount + modifiedLineCount,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Files/FilesModels.swift` around lines 284 - 301, The
byte-count path in FilesModels.swift still builds a full combined string in the
diff preview flow, which defeats the new large-diff guard. In the logic that
computes the limit check after filesLinePairCountExceedsLimit, replace the
combinedText concatenation with a direct calculation using the UTF-8 counts of
diff.original.text and diff.modified.text (including the separator if needed) so
FilesPreviewLimit and filesTextLimit can be evaluated without duplicating the
diff content.
apps/ios/ADE/Views/Components/FilesCodeSupport.swift (1)

348-437: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional cleanup: centralize the unchanged-line path and broaden diff tests.

  • Route the .unchanged case through appendLine too so the ID/line-number formatting stays in one place.
  • Add dedicated coverage for adjacent swaps, full rewrites, empty inputs, and trailing-only insert/remove cases; the current test only exercises a simple mixed diff.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Components/FilesCodeSupport.swift` around lines 348 - 437,
The diff-building logic in the inline diff routine should be cleaned up so the
`.unchanged` path uses the same `appendLine` helper as added/removed lines,
keeping ID and line-number formatting centralized in the same code path. Update
the line handling in the diff generation flow around `appendLine` and
`diffLines` so unchanged entries are appended consistently, then expand the
tests for this behavior to cover adjacent swaps, full rewrites, empty inputs,
and trailing-only insert/remove cases beyond the current mixed-diff scenario.
apps/desktop/src/shared/types/sync.ts (1)

374-376: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Align the sinceSeq contract with the host behavior.

Line 375 says the host can send a delta from sinceSeq, but handleRosterSubscribe ignores the payload and always sends a snapshot. Either remove this field/comment for now or implement resume-by-seq so clients do not rely on an unsupported protocol path. As per coding guidelines, “Keep IPC contracts, preload types, shared types, and renderer usage in sync whenever an interface changes”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/shared/types/sync.ts` around lines 374 - 376, The
SyncRosterSubscribePayload contract is ahead of the actual host behavior:
handleRosterSubscribe still ignores sinceSeq and always returns a snapshot, so
clients may rely on unsupported resume-by-seq behavior. Either remove sinceSeq
and its comment from SyncRosterSubscribePayload in sync.ts, or update the
host-side handleRosterSubscribe path to honor sinceSeq and send a delta when
possible, keeping the shared type and IPC behavior in sync.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/ade-cli/src/services/sync/rosterBuilder.ts`:
- Around line 303-331: The overlay lookup in buildRosterProject can block the
entire snapshot when scopeRegistry.getIfBooted(record.projectId) is still
pending for a booting scope. Update the getIfBooted await path in
buildRosterProject to use a short timeout or fallback so a slow/stuck boot
returns null and the function continues with disk-only fidelity. Keep the
best-effort behavior around the scopeRegistry, agentChatService, and
listSessions overlay so buildRosterSnapshot cannot be stalled by one project.
- Around line 14-20: The roster DB setup in rosterBuilder still opens
`node:sqlite` databases without read-only mode, which can take locks during the
all-projects scan. Update the `DatabaseSync` construction path in
`rosterBuilder` to pass a `readOnly: true` option, and extend the
`DatabaseSyncConstructor` typing plus `apps/ade-cli/src/types/node-sqlite.d.ts`
so `readOnly?: boolean` is accepted.

In `@apps/desktop/src/main/services/github/githubRelayConfig.ts`:
- Line 8: The default GitHub relay base URL is hardcoded to a personal hosted
relay, which would route tokens from unconfigured installs through an unintended
third-party endpoint. Update DEFAULT_GITHUB_RELAY_API_BASE_URL in
githubRelayConfig to point to vendor-owned infrastructure, and ensure the status
check and background poll paths only use a hosted relay after explicit
user-facing consent or configuration.

In `@apps/ios/ADE/Models/RemoteModels.swift`:
- Around line 2827-2862: TerminalSessionSummary equality is missing the
resumeMetadata.launch field, so launch-mode-only updates can be treated as
unchanged. Update the TerminalSessionSummary == implementation to compare
lhs.resumeMetadata?.launch with rhs.resumeMetadata?.launch alongside the other
resumeMetadata properties, so reload() detects these changes and refreshes
sessions correctly.

In `@apps/ios/ADE/Views/Hub/HubComponents.swift`:
- Around line 366-386: The hub row grouping logic is only treating isChatTool
chats as top-level, so standalone CLI rows are dropped instead of being shown in
the roster. Update the grouping in HubComponents.swift around topLevelChats,
childRowsByParentId, and the lane-building logic so child rows are identified
first from visibleChats, and any non-child rows (including CLI rows) are added
back as top-level entries. Keep the existing sorting/presentation flow in
HubChatRowPresentation.make and the lane grouping, but ensure rows without a
valid parent are not excluded.

In `@apps/ios/ADE/Views/Hub/HubComposerDrawer.swift`:
- Around line 727-782: The follow-up calls are still using the active project
context instead of the newly selected project, so a chat created elsewhere can
miss its opener and leave an orphaned lane. Update the HubComposerDrawer flow to
thread targetProjectId through the post-create calls in this block, especially
syncService.sendChatMessage and syncService.deleteLane, so both operate on the
same project envelope used by createChatSession.

In `@apps/ios/ADE/Views/Hub/HubScreen.swift`:
- Line 35: `activeRoster` is being overlaid without verifying it belongs to the
current project, which can merge stale chats after a project switch. Update
`HubScreen` so the roster is keyed by project id (for example, store the
roster’s project identifier alongside `activeRoster`) and only apply the overlay
when it matches the current project. Make the check in the
`hubPresentationKey`/`rebuildKey` flow and in the overlay logic that uses
`activeRoster`, so the previous project’s local chats are ignored once the
active project changes.

In `@apps/ios/ADE/Views/Hub/HubScreen`+ChatNavigation.swift:
- Around line 57-66: The chat navigation task in HubScreen+ChatNavigation.swift
always flips ready to true after calling syncService.openProjectForHubChat, even
though that method does not confirm success; gate rendering on a verified active
project instead. Update the .task logic to check the result by re-reading
syncService.isActiveProject(target.project) after the project switch attempt (or
otherwise confirming the activation completed) and only set ready = true when
the target project is actually active, keeping the existing early return path
for the already-active case.

In `@apps/ios/ADE/Views/Work/WorkChatRichCardViews.swift`:
- Around line 2072-2075: The color selection logic in color uses
abs(workStableSubagentHash(id)), which can trap on Int.min; update this indexing
to use UInt(bitPattern:) instead. Apply the same approach anywhere the
workStableSubagentHash(id) result is used for palette or glyph bit calculations
so the palette lookup stays safe and deterministic.

In `@apps/ios/ADE/Views/Work/WorkChatSessionView`+Actions.swift:
- Around line 391-400: The duplicate-echo cleanup in
workIncrementalRemoveDuplicateEchoes(matching:from:) is too broad because
removeAll removes every pending echo with the same normalized text. Update the
logic so it removes only the single matched optimistic echo entry from timeline,
keeping any other echo entries with the same text untouched; use the existing
entry.id prefix check and normalizedWorkLocalEchoText/message.markdown match to
identify the one echo to drop.
- Around line 120-129: The incremental tail update in
WorkChatSessionView+Actions is setting the turn active without updating
transcriptHasInterruptibleActivity, so the interrupt control state can become
stale. In the incremental snapshot update path, alongside
transcriptIndicatesActiveTurn and transcriptLatestTurnEnded, recompute or set
transcriptHasInterruptibleActivity to reflect the newly streaming assistant
activity. Make sure the logic stays consistent with shouldShowInterruptControl
and the same snapshot-building flow that uses workIncrementalLatestTimestamp and
workIncrementalLatestAssistantId.

In `@apps/ios/ADE/Views/Work/WorkErrorAndMessageHelpers.swift`:
- Around line 899-915: The text-fallback logic in WorkErrorAndMessageHelpers
should not resolve every queued steer that shares the same normalized text. In
the queued-steer scan and resolution path around
sortedWorkChatEnvelopes/transcript processing, prefer matching by steerId first,
and when falling back to text, consume only a single queued entry instead of
unioning the whole Set. If needed, update the fallback key to include turnId or
ordering so duplicate prompts do not clear multiple pending steers at once.
- Around line 1007-1022: In mergedWorkChatEnvelope, keep the original envelope
ordering stable when combining assistantText updates instead of always copying
incoming timestamp and sequence. Update the merge logic so the merged
WorkChatEnvelope preserves the existing envelope’s ordering fields (or only
advances them when explicitly required) while still merging text and ids,
preventing assistant fragments from being resorted ahead of surrounding
tool/status events. Use mergedWorkChatEnvelope and the assistantText case as the
main fix location.

In `@apps/ios/ADE/Views/Work/WorkRootScreen.swift`:
- Around line 221-228: The session presentation key is currently feeding pushed
screens with presentation-derived workOrderedLanes that can go stale when
sessionPresentationTaskKey becomes nil off the root. Update WorkRootScreen’s
sessionPresentationTaskKey and the consumers in WorkSessionDestinationView and
WorkNewChatScreen so they use live lanes from the root state, or derive a safe
fallback directly from lanes instead of relying on
sessionPresentation.workOrderedLanes.

In `@apps/ios/ADE/Views/Work/WorkRootScreen`+Actions.swift:
- Around line 383-391: The lane-navigation handler in WorkRootScreen+Actions
should not continue after cancellation, because try? await Task.sleep(for:) can
return early and still let the task scroll and clear
requestedWorkLaneNavigation. In the navigation flow around the request.id guard
and proxy.scrollTo inside the existing Task, explicitly stop when the task is
cancelled before consuming the request, then only perform the scroll and nil out
syncService.requestedWorkLaneNavigation if the task is still active and the
request still matches.

---

Outside diff comments:
In `@apps/ade-cli/src/multiProjectRpcServer.ts`:
- Around line 460-493: Align the runtime build-hash fallback in the RPC server
initialization path so both servers behave the same when ADE_RUNTIME_BUILD_HASH
is missing. Update adeRpcServer’s runtime env resolution to match
multiProjectRpcServer’s computeRuntimeBuildHash logic, using process.argv[1]
with file checks and hashing as the fallback instead of returning null. Keep the
behavior consistent through the relevant resolveRuntimeEnvInfo/initialize flow
so machineRuntimeMismatchReason does not vary by server.

In `@apps/desktop/src/main/services/github/githubRelayConfig.ts`:
- Around line 169-175: The `fetchGitHubAppInstallationStatus` check for
`!config.apiBaseUrl` is dead code because `githubRelayConfig` always assigns a
default API base URL, so the `"unconfigured"` status can never be returned.
Remove this unreachable branch or replace it with an explicit fully-unconfigured
condition based on the real config flags used by `githubRelayConfig` and
`fetchGitHubAppInstallationStatus`, so the status logic matches the
hosted-default behavior and the code does not advertise an unreachable state.

In `@apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift`:
- Around line 47-62: The disconnected-state copy in SettingsConnectionHeader is
missing a fallback when a saved host exists but has no display name, so the card
can render blank. Update stateDetailLine and/or the disconnected branch in
SettingsConnectionHeader to return a generic reconnect caption whenever
canReconnectToSavedHost is true but no host name is available, using the
existing pendingDescription/stateDetailLine flow as the place to preserve the
connected, pending, and onboarding cases.

---

Minor comments:
In `@apps/ade-cli/src/services/sync/rosterBuilder.ts`:
- Around line 64-70: `BuildRosterSnapshotArgs.hostProjectId` is documented as
guaranteeing the host project is always booted, but
`buildRosterSnapshot`/`buildRosterProject` never read it, so the contract is not
enforced. Update the roster-building flow to either use `hostProjectId` to
ensure the host scope is booted and included with live fidelity before
snapshotting, or remove the unused field and adjust the doc comment plus the
`bootstrap.ts` and `cli.ts` callers to match the actual behavior. Use the
`buildRosterSnapshot` and `buildRosterProject` symbols to locate the
implementation.

In `@apps/ade-cli/src/services/sync/syncHostService.ts`:
- Around line 2807-2810: The `flushRoster()` flow in `syncHostService.ts` is
dropping roster dirties that arrive while `rosterFlushInFlight` is true because
it clears timers and exits without retrying. Add a pending-flush flag in the
roster flush state, have `flushRoster()` set it when called during an in-flight
flush, and in the existing `finally` block schedule another flush if that flag
is set. Use the `flushRoster`, `clearRosterFlushTimers`, and
`rosterFlushInFlight` logic to locate the fix and ensure no dirty updates are
lost between snapshot build and completion.
- Around line 197-205: The ROSTER_DIRTYING_COMMAND_ACTIONS set in
syncHostService should include every roster-visible mutation, not just
creation/deletion actions. Update the set to add the missing hub actions such as
chat.archive, chat.unarchive, chat.delete, lanes.unarchive, lanes.rename, and
lanes.updateAppearance so syncHostService marks the roster dirty immediately
after those mutations.

In `@apps/desktop/src/main/services/chat/agentChatService.ts`:
- Line 7242: `agentChatService.getChatEventHistory` now accepts `maxBytes`, but
the renderer-to-main path still drops it, so the limit cannot be configured
through the IPC flow. Update the shared chat history args and the IPC wiring in
`preload` and `registerIpc` so `maxBytes` is forwarded alongside `maxEvents`
into `getChatEventHistory`, or remove it from the public signature if it should
remain internal-only. Make sure the change is applied consistently wherever the
chat history options are typed or destructured.
- Around line 7269-7274: The maxBytes handling in agentChatService should use
UTF-8 byte اندازه rather than string length when estimating the response
envelope. Update the logic around the requestedMaxBytes/responseMaxChars
calculation and the envelope sizing path to measure JSON output with
Buffer.byteLength(..., "utf8") instead of JSON.stringify(...).length, so the cap
in chat history responses matches the API contract for multi-byte content.

In `@apps/ios/ADE/Models/RemoteModels.swift`:
- Around line 734-780: `AgentChatSessionSummary` equality is missing several
state fields, so objects can compare equal even when they differ. Update the
`==` implementation in `AgentChatSessionSummary` to include
`cursorModeSnapshot`, `cursorConfigValues`, `computerUse`, and `completion`
alongside the existing property checks, unless they are intentionally meant to
be excluded from identity. This will also keep `CtoRoster` equality via `cto ==`
consistent with the full model state.

In `@apps/ios/ADE/Views/Hub/HubScreen.swift`:
- Around line 316-323: After restoring layout in HubScreen.restoreSavedLayout,
rebuild the presentation list immediately because hubProjectPresentations may
still reflect the default alphabetical order. Update the flow so the restored
projectOrder is applied before any derived presentations are used, then
regenerate or refresh the presentations with the restored order. Use the
existing restoreSavedLayout logic and the hubProjectPresentations source so the
persisted layout takes effect on reconnect.
- Line 27: Make the HubScreen view’s openChatTarget state private, since it is
only used as local mutable UI state. Update the property declaration for
openChatTarget in HubScreen.swift to use private with `@State`, and keep any
access through HubScreen’s own methods or bindings unless another type truly
needs it.

In `@apps/ios/ADE/Views/PRs/PrsRootScreen.swift`:
- Around line 1345-1357: The programmatic create-PR navigation path in
PrsRootScreen’s request.createLaneId handling bypasses the same canCreatePr gate
used by the manual “+” button. Before setting createPresented and opening the
createPrWizardSheet, check canCreatePr in this block and only proceed when
creation is allowed; otherwise ignore the request or route back without
presenting the wizard.

In `@apps/ios/ADE/Views/Work/WorkChatRichCardViews.swift`:
- Around line 1027-1060: Update statusRibbonBody in WorkChatRichCardViews so the
leading SF Symbol matches the normalized metadata status instead of defaulting
every non-failed case to pause.circle.fill; map completed/started/other statuses
to appropriate icons while keeping failed/interrupted distinct. Also fix the
accessibilityLabel on the ribbon to include the visible status text (normalized
or ribbonText) alongside card.title and card.body, so when card.body is empty
the status is still announced.

In `@apps/ios/ADE/Views/Work/WorkChatSessionView.swift`:
- Around line 139-160: The `WorkChatSessionView` state properties are triggering
visibility warnings because they are shared with
`WorkChatSessionView+Actions.swift` and `WorkChatSessionView+Timeline.swift`.
Keep the cross-file access intact and either add a narrow `swiftlint:disable
private_swiftui_state` with a brief rationale near the affected `@State`
declarations, or move the dependent logic into the same `WorkChatSessionView`
file. Focus on the state block containing `composerLayoutHeight`,
`timelineSnapshot`, `timelineRebuildTask`, and the other shared `@State`
members.

In `@apps/ios/ADE/Views/Work/WorkSessionDestinationView`+Actions.swift:
- Around line 697-701: The refreshChatPrDetails flow currently allows forced
calls to bypass the in-flight guard, which can start overlapping refresh tasks
and let stale snapshot/error state overwrite newer results after awaits. Update
refreshChatPrDetails so the single-flight protection always blocks when
prDetailsRefreshing is already true, and use force only to control whether the
refresh should bypass cached data or trigger a reload. Keep the fix localized to
refreshChatPrDetails and the prDetailsRefreshing/prDetailsError state handling.

---

Nitpick comments:
In `@apps/ade-cli/src/multiProjectRpcServer.test.ts`:
- Around line 61-89: Move the cleanup for createMultiProjectRpcRequestHandler
into the existing try/finally flow so it always runs, even when the
expect(init).toMatchObject assertion fails. Keep the dispose call next to the
handler variable in the multiProjectRpcServer test, and ensure handler.dispose()
is executed from the finally block after the registry/argv/env restoration
logic.

In `@apps/desktop/src/shared/types/sync.ts`:
- Around line 374-376: The SyncRosterSubscribePayload contract is ahead of the
actual host behavior: handleRosterSubscribe still ignores sinceSeq and always
returns a snapshot, so clients may rely on unsupported resume-by-seq behavior.
Either remove sinceSeq and its comment from SyncRosterSubscribePayload in
sync.ts, or update the host-side handleRosterSubscribe path to honor sinceSeq
and send a delta when possible, keeping the shared type and IPC behavior in
sync.

In `@apps/ios/ADE/Views/Components/ADECodeRenderingCache.swift`:
- Line 14: The highlighted-string cache in ADECodeRenderingCache only limits
entry count via attributedCache.countLimit, so large AttributedString values can
still consume too much memory. Update the cache setup to add a totalCostLimit
and make the caching path account for per-entry cost when storing highlighted
output, or reduce the existing limit if cost tracking is not feasible. Use the
ADECodeRenderingCache cache initialization and the highlighted-string storage
logic to apply the bound consistently.

In `@apps/ios/ADE/Views/Components/FilesCodeSupport.swift`:
- Around line 348-437: The diff-building logic in the inline diff routine should
be cleaned up so the `.unchanged` path uses the same `appendLine` helper as
added/removed lines, keeping ID and line-number formatting centralized in the
same code path. Update the line handling in the diff generation flow around
`appendLine` and `diffLines` so unchanged entries are appended consistently,
then expand the tests for this behavior to cover adjacent swaps, full rewrites,
empty inputs, and trailing-only insert/remove cases beyond the current
mixed-diff scenario.

In `@apps/ios/ADE/Views/Files/FilesModels.swift`:
- Around line 284-301: The byte-count path in FilesModels.swift still builds a
full combined string in the diff preview flow, which defeats the new large-diff
guard. In the logic that computes the limit check after
filesLinePairCountExceedsLimit, replace the combinedText concatenation with a
direct calculation using the UTF-8 counts of diff.original.text and
diff.modified.text (including the separator if needed) so FilesPreviewLimit and
filesTextLimit can be evaluated without duplicating the diff content.

In `@apps/ios/ADE/Views/Work/WorkChatPrViews.swift`:
- Around line 172-173: The PR-state tint mapping in WorkChatPrViews is
duplicated and can drift from the shared badge/details palette. Update the state
tint logic in the WorkChatPrViews flow to use the shared lanePullRequestTint
helper instead of the local workChatPrStateTint mapping, keeping the
branch/badge colors consistent wherever tag.state is rendered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5f5cbf68-4db2-4901-98b6-048f93eed3eb

📥 Commits

Reviewing files that changed from the base of the PR and between b407a9c and 258832a.

⛔ Files ignored due to path filters (10)
  • apps/ios/ADE.xcodeproj/project.pbxproj is excluded by !**/*.xcodeproj/project.pbxproj
  • docs/features/automations/README.md is excluded by !docs/**
  • docs/features/chat/composer-and-ui.md is excluded by !docs/**
  • docs/features/files-and-editor/README.md is excluded by !docs/**
  • docs/features/files-and-editor/editor-surfaces.md is excluded by !docs/**
  • docs/features/onboarding-and-settings/README.md is excluded by !docs/**
  • docs/features/project-home/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/ios-companion.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/remote-commands.md is excluded by !docs/**
📒 Files selected for processing (100)
  • apps/ade-cli/src/bootstrap.ts
  • apps/ade-cli/src/cli.ts
  • apps/ade-cli/src/headlessLinearServices.ts
  • apps/ade-cli/src/multiProjectRpcServer.test.ts
  • apps/ade-cli/src/multiProjectRpcServer.ts
  • apps/ade-cli/src/services/projects/projectScope.ts
  • apps/ade-cli/src/services/sync/rosterBuilder.test.ts
  • apps/ade-cli/src/services/sync/rosterBuilder.ts
  • apps/ade-cli/src/services/sync/sharedSyncListener.ts
  • apps/ade-cli/src/services/sync/syncHostService.test.ts
  • apps/ade-cli/src/services/sync/syncHostService.ts
  • apps/ade-cli/src/services/sync/syncRemoteCommandService.test.ts
  • apps/ade-cli/src/services/sync/syncRemoteCommandService.ts
  • apps/ade-cli/src/services/sync/syncService.ts
  • apps/desktop/src/main/main.ts
  • apps/desktop/src/main/services/automations/automationIngressService.test.ts
  • apps/desktop/src/main/services/automations/automationIngressService.ts
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/main/services/github/githubRelayConfig.ts
  • apps/desktop/src/main/services/github/githubService.test.ts
  • apps/desktop/src/main/services/github/githubService.ts
  • apps/desktop/src/main/services/lanes/laneService.ts
  • apps/desktop/src/main/services/sync/syncHostService.test.ts
  • apps/desktop/src/main/services/sync/syncRemoteCommandService.test.ts
  • apps/desktop/src/main/services/sync/syncService.test.ts
  • apps/desktop/src/renderer/browserMock.ts
  • apps/desktop/src/renderer/components/files/v2/FilesWorkbench.test.tsx
  • apps/desktop/src/renderer/components/files/v2/FilesWorkbench.tsx
  • apps/desktop/src/renderer/components/github/GitHubAppInstallPanel.tsx
  • apps/desktop/src/shared/types/chat.ts
  • apps/desktop/src/shared/types/sync.ts
  • apps/ios/ADE/App/ContentView.swift
  • apps/ios/ADE/Debug/ADEInspectorKit/ADEInspectable.swift
  • apps/ios/ADE/Models/RemoteModels.swift
  • apps/ios/ADE/Models/RemoteRosterModels.swift
  • apps/ios/ADE/Resources/DatabaseBootstrap.sql
  • apps/ios/ADE/Services/Database.swift
  • apps/ios/ADE/Services/SyncService.swift
  • apps/ios/ADE/Views/Components/ADECodeRenderingCache.swift
  • apps/ios/ADE/Views/Components/ADEDesignSystem.swift
  • apps/ios/ADE/Views/Components/FilesCodeSupport.swift
  • apps/ios/ADE/Views/Cto/CtoRootScreen.swift
  • apps/ios/ADE/Views/Cto/CtoTeamScreen.swift
  • apps/ios/ADE/Views/Cto/CtoWorkflowsScreen.swift
  • apps/ios/ADE/Views/Files/FilesDetailComponents.swift
  • apps/ios/ADE/Views/Files/FilesDetailScreen+Actions.swift
  • apps/ios/ADE/Views/Files/FilesDetailScreen.swift
  • apps/ios/ADE/Views/Files/FilesDirectoryContentsView.swift
  • apps/ios/ADE/Views/Files/FilesModels.swift
  • apps/ios/ADE/Views/Files/FilesRootComponents.swift
  • apps/ios/ADE/Views/Files/FilesSearchScreen.swift
  • apps/ios/ADE/Views/Hub/HubComponents.swift
  • apps/ios/ADE/Views/Hub/HubComposerDrawer.swift
  • apps/ios/ADE/Views/Hub/HubScreen+ChatNavigation.swift
  • apps/ios/ADE/Views/Hub/HubScreen.swift
  • apps/ios/ADE/Views/Lanes/LaneDetailScreen.swift
  • apps/ios/ADE/Views/PRs/PrHelpers.swift
  • apps/ios/ADE/Views/PRs/PrsRootScreen.swift
  • apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift
  • apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift
  • apps/ios/ADE/Views/Settings/SettingsPairingSection.swift
  • apps/ios/ADE/Views/Work/WorkActivityIndicator.swift
  • apps/ios/ADE/Views/Work/WorkBrowserHelpers.swift
  • apps/ios/ADE/Views/Work/WorkChatAttachmentTray.swift
  • apps/ios/ADE/Views/Work/WorkChatComposerAndInputViews.swift
  • apps/ios/ADE/Views/Work/WorkChatHeaderAndMessageViews.swift
  • apps/ios/ADE/Views/Work/WorkChatPrViews.swift
  • apps/ios/ADE/Views/Work/WorkChatRichCardViews.swift
  • apps/ios/ADE/Views/Work/WorkChatSessionView+Actions.swift
  • apps/ios/ADE/Views/Work/WorkChatSessionView+Timeline.swift
  • apps/ios/ADE/Views/Work/WorkChatSessionView.swift
  • apps/ios/ADE/Views/Work/WorkContextCompactDivider.swift
  • apps/ios/ADE/Views/Work/WorkErrorAndMessageHelpers.swift
  • apps/ios/ADE/Views/Work/WorkEventMapping.swift
  • apps/ios/ADE/Views/Work/WorkMarkdownParsing.swift
  • apps/ios/ADE/Views/Work/WorkMarkdownViews.swift
  • apps/ios/ADE/Views/Work/WorkModelPickerSheet.swift
  • apps/ios/ADE/Views/Work/WorkModels.swift
  • apps/ios/ADE/Views/Work/WorkNavigationAndTranscriptHelpers.swift
  • apps/ios/ADE/Views/Work/WorkNewChatScreen.swift
  • apps/ios/ADE/Views/Work/WorkPlanComposerViews.swift
  • apps/ios/ADE/Views/Work/WorkPreviews.swift
  • apps/ios/ADE/Views/Work/WorkRootComponents.swift
  • apps/ios/ADE/Views/Work/WorkRootScreen+Actions.swift
  • apps/ios/ADE/Views/Work/WorkRootScreen.swift
  • apps/ios/ADE/Views/Work/WorkSessionDestinationView+Actions.swift
  • apps/ios/ADE/Views/Work/WorkSessionDestinationView.swift
  • apps/ios/ADE/Views/Work/WorkSessionGrouping.swift
  • apps/ios/ADE/Views/Work/WorkSessionSettingsSheet.swift
  • apps/ios/ADE/Views/Work/WorkStatusAndFormattingHelpers.swift
  • apps/ios/ADE/Views/Work/WorkTimelineHelpers.swift
  • apps/ios/ADE/Views/Work/WorkTranscriptParser.swift
  • apps/ios/ADETests/ADETests.swift
  • apps/ios/ADETests/SyncEnvelopeChunkAssemblerTests.swift
  • apps/ios/ADETests/WorkMarkdownStreamingParsingTests.swift
  • apps/webhook-relay/README.md
  • apps/webhook-relay/migrations/0003_github_events_repository.sql
  • apps/webhook-relay/src/relay.ts
  • apps/webhook-relay/test/relay.test.ts
💤 Files with no reviewable changes (1)
  • apps/ios/ADE/Resources/DatabaseBootstrap.sql

Comment thread apps/ade-cli/src/services/sync/rosterBuilder.ts
Comment thread apps/ade-cli/src/services/sync/rosterBuilder.ts
export const ADE_GITHUB_APP_SLUG = "ade-for-github";
export const ADE_GITHUB_APP_INSTALL_URL = `https://github.com/apps/${ADE_GITHUB_APP_SLUG}/installations/new`;
export const GITHUB_APP_INSTALLATIONS_URL = "https://github.com/settings/installations";
export const DEFAULT_GITHUB_RELAY_API_BASE_URL = "https://ade-github-webhook-relay.arulsharma1028.workers.dev";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether this hosted relay domain is documented/explained anywhere in the repo.
rg -n "arulsharma1028|ade-github-webhook-relay" --type=md --type=ts

Repository: arul28/ADE

Length of output: 148


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== githubRelayConfig.ts ==\n'
sed -n '1,220p' apps/desktop/src/main/services/github/githubRelayConfig.ts

printf '\n== search relay usage ==\n'
rg -n "readGitHubRelayConfig|DEFAULT_GITHUB_RELAY_API_BASE_URL|github relay|relay config|apiBaseUrl" apps/desktop/src/main/services -g '*.ts'

printf '\n== consumers referenced in comment ==\n'
sed -n '1,260p' apps/desktop/src/main/services/github/githubService.ts
printf '\n----\n'
sed -n '1,260p' apps/desktop/src/main/services/headlessLinearServices.ts
printf '\n----\n'
sed -n '1,280p' apps/desktop/src/main/services/automationIngressService.ts

Repository: arul28/ADE

Length of output: 23055


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== automationIngressService.ts (relevant slice) ==\n'
sed -n '380,520p' apps/desktop/src/main/services/automations/automationIngressService.ts

printf '\n== automationIngressService.test.ts (relay-related slice) ==\n'
sed -n '150,380p' apps/desktop/src/main/services/automations/automationIngressService.test.ts

printf '\n== search for user-facing relay disclosure ==\n'
rg -n "GitHub relay|relay is not configured|ade-github-webhook-relay|GITHUB_RELAY|webhook relay|GitHub App relay" apps/desktop/src -g '*.ts' -g '*.tsx'

Repository: arul28/ADE

Length of output: 22607


Don't ship a hardcoded personal relay as the default — unconfigured installs will send the user's GitHub token to ade-github-webhook-relay.arulsharma1028.workers.dev through both the status check and background poll paths. Use vendor-owned infrastructure here and require explicit user-facing consent for the hosted relay.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/github/githubRelayConfig.ts` at line 8, The
default GitHub relay base URL is hardcoded to a personal hosted relay, which
would route tokens from unconfigured installs through an unintended third-party
endpoint. Update DEFAULT_GITHUB_RELAY_API_BASE_URL in githubRelayConfig to point
to vendor-owned infrastructure, and ensure the status check and background poll
paths only use a hosted relay after explicit user-facing consent or
configuration.

Comment thread apps/ios/ADE/Models/RemoteModels.swift
Comment thread apps/ios/ADE/Views/Hub/HubComponents.swift Outdated
Comment thread apps/ios/ADE/Views/Work/WorkChatSessionView+Actions.swift
Comment thread apps/ios/ADE/Views/Work/WorkErrorAndMessageHelpers.swift
Comment thread apps/ios/ADE/Views/Work/WorkErrorAndMessageHelpers.swift
Comment thread apps/ios/ADE/Views/Work/WorkRootScreen.swift
Comment thread apps/ios/ADE/Views/Work/WorkRootScreen+Actions.swift
CI:
- stdioRpcDaemon test: buildHash assertion was stale after the lane's
  `?? computeRuntimeBuildHash()` fallback; assert a computed hash instead of null.

Host (CodeRabbit):
- rosterBuilder: bound the getIfBooted overlay await (250ms) so a mid-boot
  scope can't stall the whole roster snapshot (Critical); open project DBs read-only.
- githubRelayConfig: document the hosted-relay default (pre-launch item, deferred).

iOS (CodeRabbit):
- Hub: keep standalone CLI rows visible; key activeRoster to its project so a
  stale roster isn't merged post-switch; gate chat render on successful activation;
  thread targetProjectId through composer send/cleanup.
- Work: recompute transcriptHasInterruptibleActivity on incremental tail; remove
  only the matched optimistic echo / resolve only the matched queued steer;
  preserve envelope ordering on assistant merge; UInt bucketing (no abs Int.min trap);
  guard cancelled lane-nav task; fall back to live lanes off-root.
- RemoteModels: compare resumeMetadata.launch in session Equatable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@arul28

arul28 commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

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

Copy link
Copy Markdown

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: 7942ff1789

ℹ️ 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".

&& lhs.codexFastMode == rhs.codexFastMode
&& lhs.fastMode == rhs.fastMode
&& lhs.executionMode == rhs.executionMode
&& lhs.permissionMode == rhs.permissionMode

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include cursor snapshots in session equality

When only the Cursor mode snapshot/config changes, this equality still reports the fetched AgentChatSessionSummary as unchanged because cursorModeSnapshot/cursorConfigValues are not compared here. refreshChatSummaryFromHost only replaces chatSummary when chatSummary != fetchedSummary, while the Work UI derives the displayed Cursor mode from summary.cursorModeId ?? workCursorCurrentModeId(summary.cursorModeSnapshot), so hosts that refresh modes without changing cursorModeId leave the iOS session header/settings stuck on the old mode until another compared field changes.

Useful? React with 👍 / 👎.

Compare the four remaining fields the manual == skipped —
cursorModeSnapshot, cursorConfigValues, computerUse, completion — so a
host-side change to any of them (e.g. Cursor mode-only refresh) triggers
the iOS session header/settings to update instead of latching stale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@arul28

arul28 commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/ade-cli/src/stdioRpcDaemon.test.ts`:
- Around line 454-458: The test in stdioRpcDaemon should assert the exact
computed SHA-256 value instead of only checking truthiness; update the
runtimeInfo.buildHash expectation in stdioRpcDaemon.test.ts to match the full
64-character hex digest produced by the daemon’s entrypoint hashing logic, using
the initialize/runtimeInfo path as the locator.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 57ceacca-e51f-418c-a9f1-0edb4aef0677

📥 Commits

Reviewing files that changed from the base of the PR and between 258832a and a9f1f80.

📒 Files selected for processing (15)
  • apps/ade-cli/src/services/sync/rosterBuilder.ts
  • apps/ade-cli/src/stdioRpcDaemon.test.ts
  • apps/ade-cli/src/types/node-sqlite.d.ts
  • apps/desktop/src/main/services/github/githubRelayConfig.ts
  • apps/ios/ADE/Models/RemoteModels.swift
  • apps/ios/ADE/Services/SyncService.swift
  • apps/ios/ADE/Views/Hub/HubComponents.swift
  • apps/ios/ADE/Views/Hub/HubComposerDrawer.swift
  • apps/ios/ADE/Views/Hub/HubScreen+ChatNavigation.swift
  • apps/ios/ADE/Views/Hub/HubScreen.swift
  • apps/ios/ADE/Views/Work/WorkChatRichCardViews.swift
  • apps/ios/ADE/Views/Work/WorkChatSessionView+Actions.swift
  • apps/ios/ADE/Views/Work/WorkErrorAndMessageHelpers.swift
  • apps/ios/ADE/Views/Work/WorkRootScreen+Actions.swift
  • apps/ios/ADE/Views/Work/WorkRootScreen.swift
🚧 Files skipped from review as they are similar to previous changes (12)
  • apps/ios/ADE/Views/Hub/HubScreen+ChatNavigation.swift
  • apps/desktop/src/main/services/github/githubRelayConfig.ts
  • apps/ios/ADE/Views/Hub/HubScreen.swift
  • apps/ios/ADE/Views/Work/WorkRootScreen+Actions.swift
  • apps/ios/ADE/Views/Hub/HubComponents.swift
  • apps/ios/ADE/Views/Work/WorkChatSessionView+Actions.swift
  • apps/ios/ADE/Views/Hub/HubComposerDrawer.swift
  • apps/ios/ADE/Models/RemoteModels.swift
  • apps/ade-cli/src/services/sync/rosterBuilder.ts
  • apps/ios/ADE/Views/Work/WorkErrorAndMessageHelpers.swift
  • apps/ios/ADE/Views/Work/WorkChatRichCardViews.swift
  • apps/ios/ADE/Views/Work/WorkRootScreen.swift

Comment on lines +454 to +458
// With no env-advertised build hash, the daemon computes a sha256 of its
// own entrypoint, so buildHash is a truthy string rather than null.
expect(
(initialize as { runtimeInfo?: { buildHash?: string | null } }).runtimeInfo?.buildHash,
).toBeTruthy();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== FILE LIST ==\n'
git ls-files apps/ade-cli/src/stdioRpcDaemon.test.ts apps/ade-cli/src | sed -n '1,120p'

printf '\n== OUTLINE TEST FILE ==\n'
ast-grep outline apps/ade-cli/src/stdioRpcDaemon.test.ts --view expanded || true

printf '\n== SEARCH buildHash REFERENCES ==\n'
rg -n "buildHash|runtimeInfo" apps/ade-cli/src apps/ade-cli/test apps/ade-cli -g '!**/dist/**' -g '!**/build/**' || true

printf '\n== CONTEXT AROUND CITED LINES ==\n'
sed -n '380,480p' apps/ade-cli/src/stdioRpcDaemon.test.ts

Repository: arul28/ADE

Length of output: 30108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '430,470p' apps/ade-cli/src/stdioRpcDaemon.test.ts
printf '\n----\n'
rg -n "sha256|buildHash" apps/ade-cli/src apps/desktop/src . -g '!**/dist/**' -g '!**/build/**'

Repository: arul28/ADE

Length of output: 50366


🌐 Web query:

TypeScript RegExp for SHA-256 64 hex build hash assertions best practice toBeTruthy non-empty string test weakness

💡 Result:

The recommended best practice for validating a SHA-256 hash in TypeScript is to use a strict, anchored regular expression, often combined with a type guard for runtime safety [1][2]. Recommended Regex Pattern To validate an exact SHA-256 hash (64 hexadecimal characters), use the following anchored regex [1]: const SHA256_REGEX = /^[a-f0-9]{64}$/i; - The ^ and $ anchors are critical to ensure the string contains nothing but the 64-character hash [1]. - The i flag makes it case-insensitive, accommodating both lowercase and uppercase hex representations [1][3]. Weakness of toBeTruthy and Non-empty String Tests Using generic assertions like toBeTruthy or checking that a string is simply non-empty is considered a significant testing weakness [4]. - toBeTruthy: This only checks if a value is "truthy." In JavaScript/TypeScript, this includes any non-empty string, non-zero number, or object. It provides zero validation that the string is a valid SHA-256 hash. - Non-empty check: This only confirms the string has length > 0. It would pass invalid values (e.g., "123", "abc", or 40-character SHA-1 hashes) [5]. Best Practices for Robust Validation 1. Use Type Guards: For production code, implement a type guard to ensure the string conforms to the expected hash format [2]. function isSha256(input: string): input is string { return /^[a-f0-9]{64}$/i.test(input); } 2. Prefer Dedicated Libraries: Instead of manually managing regex, use established libraries (e.g., Zod or dedicated hash validation utilities) that provide tested, reusable schemas and type safety [2][6]. 3. Positive Assertions in Tests: In your test suite, avoid "not equal" or "is truthy" assertions [4]. Instead, perform positive, explicit assertions that the generated hash matches the expected value or satisfies the format requirement (e.g., using toMatch or toHaveLength) [4][7]. 4. Security Warning: When dealing with crypto inputs, be aware that JavaScript strings can sometimes contain malformed UTF-16 sequences which may behave inconsistently across different hashing implementations [8]. Where possible, prefer working with Buffer or Uint8Array directly when using the Node.js crypto module or Web Crypto API [8][9].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== TEST BLOCKS AROUND 340-410 ==\n'
sed -n '340,410p' apps/ade-cli/src/stdioRpcDaemon.test.ts | cat -n

printf '\n== TEST NAMES CONTAINING build hash ==\n'
rg -n 'itUnix\(|it\(|build hash' apps/ade-cli/src/stdioRpcDaemon.test.ts

Repository: arul28/ADE

Length of output: 4882


Assert the full SHA-256 here. toBeTruthy() still accepts any non-empty string, so match the expected 64-character hex hash instead.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/stdioRpcDaemon.test.ts` around lines 454 - 458, The test in
stdioRpcDaemon should assert the exact computed SHA-256 value instead of only
checking truthiness; update the runtimeInfo.buildHash expectation in
stdioRpcDaemon.test.ts to match the full 64-character hex digest produced by the
daemon’s entrypoint hashing logic, using the initialize/runtimeInfo path as the
locator.

Source: Coding guidelines

@arul28 arul28 merged commit 323406c into main Jul 2, 2026
29 checks passed
@arul28 arul28 deleted the ade/start-skill-launch-installe-dsimaultor-0d00d08b branch July 2, 2026 01:26
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