Skip to content

fix(mobile): refresh folder workspaces in Home catalog - #11767

Open
brennanb2025 wants to merge 7 commits into
mainfrom
brennanb2025/mobile-folder-workspace-catalog-refresh
Open

fix(mobile): refresh folder workspaces in Home catalog#11767
brennanb2025 wants to merge 7 commits into
mainfrom
brennanb2025/mobile-folder-workspace-catalog-refresh

Conversation

@brennanb2025

Copy link
Copy Markdown
Contributor

Summary

Fix Orca Mobile Home so a runtime-created folder workspace can enter the existing workspace catalog as soon as its host publishes reposChanged.

This is a separate catalog invalidation fix from #11751. It does not change native-chat routing or readability.

Before / after

Before: a real folder workspace could exist on a paired host and own a terminal, while Mobile Home kept showing the stale four-worktree list. Search used that same stale catalog, and the reposChanged path refreshed only decorative repo metadata.

After: reposChanged refreshes both the authoritative worktree.ps catalog snapshot and repo metadata. Folder workspaces and Git worktrees continue through the same host-scoped catalog and existing search behavior.

Screenshots

Exact-head iPad QA is authorized and pending after PR creation. Useful screenshots will be uploaded as PR attachments only; none are committed.

Native QA gap

Not yet run at PR opening. The authorized lane is UDID C589AF84-2F0C-4978-952B-748933DE6804 on existing port 3100 and helper PID 10152. This section will be updated with exact source, artifact, and installed executable hashes plus runtime results. No other simulator or helper will be touched.

Testing

  • Focused regression: 5/5 passed.
  • Full Mobile suite: 376 files passed; 2,794 tests passed and 3 skipped.
  • Mobile typecheck, oxlint, and format check passed.
  • Root typecheck and full lint passed.
  • Changed-code quality under Node 24.18.0: zero findings across both changed files.
  • React Doctor changed-files check: zero issues.
  • Reliability manifest: 56 gates passed.
  • Max-lines ratchet and git diff --check passed.
  • Reviewed and committed atop exact origin/main 651f707ce08bf0fb18961add08d835612098e762.

AI Review Report

Fresh same-model gpt-5.6-sol xhigh review on the exact current-main tree returned CLEAN. The review traced the runtime event source and Mobile catalog authority and checked multi-host isolation, SSH/relay behavior, folder-workspace and Git-worktree handling, search semantics, reconnect behavior, bounded refresh work, performance risk, UI scope, and basic security risk.

Security Audit

No protocol, authentication, authorization, credential, storage, dependency, or executable changes. The event triggers an existing read-only host-scoped RPC through existing connection and in-flight guards. Both pnpm-lock.yaml and mobile/pnpm-lock.yaml match current main byte-for-byte.

Notes

The effective diff is two files and 5 additions / 1 deletion. It adds no polling, timers, reconnect loops, caches, dependencies, UI changes, screenshots, or lockfile changes. The PR is intentionally scoped to the Mobile catalog refresh lifecycle and must not be merged as part of #11751.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Folder workspace create, update, and delete IPC handlers now use a dedicated catalog notification path when available. The path synchronizes the worktree watcher and retains the legacy repository notification as a fallback. Main-window service wiring passes runtime context to the handlers. Host worktree refreshes now queue overlapping requests, suppress superseded responses, and replay queued refreshes. New tests cover IPC mutations, request coordination, and refresh options.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Mobile folder workspace catalog refresh, which matches the primary change.
Description check ✅ Passed The description covers the change, testing, AI review, security audit, and notes, but native QA and screenshots remain pending.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes Mobile Home so that a folder workspace created at runtime on a paired desktop host immediately appears in the catalog when the host publishes reposChanged. Previously, reposChanged only triggered a repo-metadata refresh; the authoritative worktree.ps catalog snapshot was never re-fetched on that event, leaving Mobile showing a stale list.

  • Desktop side (src/main/ipc/repos.ts, attach-main-window-services.ts): Folder workspace create/update/delete operations now route through runtime.notifyFolderWorkspaceChanged() instead of calling mainWindow.webContents.send('repos:changed') directly. This causes the runtime to emit reposChanged into the client-events stream consumed by connected Mobile clients, and also invalidates the resolved worktree cache.
  • Mobile side (host-worktree-refresh.ts, index.tsx): reposChanged now calls fetchWorktrees (with allowDuringModal: true, queueIfInFlight: true) in addition to fetchRepoMetadata, so the catalog is re-fetched immediately. The simple boolean in-flight guard is replaced by HostWorktreeRequestGate, which supports multi-client isolation and coalesces trailing refreshes so mutation-triggered follow-ups always apply a newer generation than any in-flight response they supersede.

Confidence Score: 5/5

Safe to merge. The change is narrowly scoped to catalog refresh routing and does not touch authentication, storage, or protocol handling.

Both sides of the fix are logically correct. On the desktop, folder workspace mutations now route through the runtime's notifyFolderWorkspaceChanged(), which both invalidates the resolved worktree cache and emits reposChanged into the mobile client-events stream — neither of which happened before. On mobile, the new HostWorktreeRequestGate correctly handles multi-client isolation, coalesces trailing refreshes, and preserves the urgent/non-urgent modal priority distinction; all three gate scenarios are covered by unit tests. The clientRef cleanup return added to the useEffect is a correctness improvement that prevents stale follow-up calls after client change. The fallback path in notifyFolderWorkspaceCatalogChanged (no notifier → direct send) ensures existing callers are unaffected. No race conditions, duplicate notifications, or dropped notifications were identified.

Files Needing Attention: No files require special attention. All changed files are well-covered by the new and updated tests.

Important Files Changed

Filename Overview
mobile/src/worktree/host-worktree-refresh.ts Adds HostWorktreeRequestGate class and wires fetchWorktrees into the reposChanged handler; gate logic is correct and well-tested across all three priority/queuing scenarios.
mobile/app/h/[hostId]/index.tsx Replaces boolean in-flight guard with HostWorktreeRequestGate, adds queueIfInFlight option to fetchWorktrees, and adds cleanup return to the clientRef effect; all changes are logically correct.
src/main/ipc/repos.ts Adds FolderWorkspaceChangeNotifier type and notifyFolderWorkspaceCatalogChanged helper; folder workspace IPC handlers now route through the runtime notifier instead of sending repos:changed directly, with a correct fallback for callers that pass no notifier.
src/main/window/attach-main-window-services.ts One-line change passing runtime as the folderWorkspaceChangeNotifier; wiring is verified by the accompanying test.
mobile/src/worktree/host-worktree-refresh.test.ts Adds comprehensive HostWorktreeRequestGate unit tests covering coalescing, modal-priority preservation, and urgent-vs-ordinary gating; also updates startHostWorktreeRefresh test for the new reposChanged behavior.
src/main/ipc/repos-folder-workspace.test.ts New test file verifying that create/update/delete IPC handlers invoke the notifier and watcher sync without directly calling repos:changed; also covers the no-op path for missing targets.
src/main/window/attach-main-window-services.test.ts Adds notifyFolderWorkspaceChanged stub to the runtime stub and a test verifying the runtime is passed as the third argument to registerRepoHandlers.

Sequence Diagram

sequenceDiagram
    participant Desktop as Desktop IPC Handler<br/>(repos.ts)
    participant Runtime as OrcaRuntime
    participant DesktopRenderer as Desktop Renderer
    participant MobileClient as Mobile Client<br/>(clientEvents stream)
    participant MobileScreen as HostScreen<br/>(index.tsx)

    note over Desktop,MobileScreen: Before this PR — reposChanged not emitted for folder workspace mutations
    Desktop->>Desktop: folderWorkspaces:create / update / delete
    Desktop->>DesktopRenderer: mainWindow.webContents.send("repos:changed")
    note over MobileClient,MobileScreen: Mobile never received reposChanged → stale catalog

    note over Desktop,MobileScreen: After this PR
    Desktop->>Runtime: notifyFolderWorkspaceChanged()
    Runtime->>Runtime: invalidateResolvedWorktreeCache()
    Runtime->>Runtime: notifyReposChanged()
    Runtime->>DesktopRenderer: notifier.reposChanged() → "repos:changed"
    Runtime->>MobileClient: "emitClientEvent({ type: "reposChanged" })"
    MobileClient->>MobileScreen: event stream message
    MobileScreen->>MobileScreen: "fetchWorktrees({ allowDuringModal:true, queueIfInFlight:true })"
    note over MobileScreen: HostWorktreeRequestGate: begin() → true (or queue)
    MobileScreen->>Runtime: "worktree.ps { limit: 10000 }"
    Runtime-->>MobileScreen: "{ worktrees: [...] }"
    MobileScreen->>MobileScreen: isSuperseded? → apply or discard
    MobileScreen->>MobileScreen: setWorktrees / setCachedWorktrees
    note over MobileScreen: Folder workspace now visible in Home catalog
    MobileScreen->>MobileScreen: "fetchRepoMetadata({ force:true, queueIfInFlight:true })"
Loading

Reviews (5): Last reviewed commit: "fix(mobile): preserve urgent catalog ref..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 935c8076-0327-4aeb-abc6-fb09ecbb10bf

📥 Commits

Reviewing files that changed from the base of the PR and between afa1be5 and f4a89a3.

📒 Files selected for processing (2)
  • src/main/ipc/repos-folder-workspace.test.ts
  • src/main/window/attach-main-window-services.test.ts

Comment on lines +152 to +167
it('does not publish catalog notifications for missing update or delete targets', async () => {
mockStore.updateFolderWorkspace.mockReturnValue(null)
mockStore.removeFolderWorkspace.mockReturnValue(false)

await handlers.get('folderWorkspaces:update')?.(null, {
folderWorkspaceId: 'missing-folder',
updates: { name: 'Missing folder' }
})
await handlers.get('folderWorkspaces:delete')?.(null, {
folderWorkspaceId: 'missing-folder'
})

expect(folderWorkspaceChangeNotifier.notifyFolderWorkspaceChanged).not.toHaveBeenCalled()
expect(scheduleWatcherSyncMock).not.toHaveBeenCalled()
expect(mainWindow.webContents.send).not.toHaveBeenCalledWith('repos:changed')
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the missing-target test execute both handlers.

When either handler is missing, optional chaining skips the call and the negative assertions still pass. The test also does not prove that either store mutation was attempted.

Capture both handlers, assert that they are defined, call them directly, and assert that both store methods were called once.

Proposed test fix
+    const update = handlers.get('folderWorkspaces:update')
+    const remove = handlers.get('folderWorkspaces:delete')
+    expect(update).toBeDefined()
+    expect(remove).toBeDefined()
+
-    await handlers.get('folderWorkspaces:update')?.(null, {
+    await update!(null, {
       folderWorkspaceId: 'missing-folder',
       updates: { name: 'Missing folder' }
     })
-    await handlers.get('folderWorkspaces:delete')?.(null, {
+    await remove!(null, {
       folderWorkspaceId: 'missing-folder'
     })
 
+    expect(mockStore.updateFolderWorkspace).toHaveBeenCalledOnce()
+    expect(mockStore.removeFolderWorkspace).toHaveBeenCalledOnce()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('does not publish catalog notifications for missing update or delete targets', async () => {
mockStore.updateFolderWorkspace.mockReturnValue(null)
mockStore.removeFolderWorkspace.mockReturnValue(false)
await handlers.get('folderWorkspaces:update')?.(null, {
folderWorkspaceId: 'missing-folder',
updates: { name: 'Missing folder' }
})
await handlers.get('folderWorkspaces:delete')?.(null, {
folderWorkspaceId: 'missing-folder'
})
expect(folderWorkspaceChangeNotifier.notifyFolderWorkspaceChanged).not.toHaveBeenCalled()
expect(scheduleWatcherSyncMock).not.toHaveBeenCalled()
expect(mainWindow.webContents.send).not.toHaveBeenCalledWith('repos:changed')
})
it('does not publish catalog notifications for missing update or delete targets', async () => {
mockStore.updateFolderWorkspace.mockReturnValue(null)
mockStore.removeFolderWorkspace.mockReturnValue(false)
const update = handlers.get('folderWorkspaces:update')
const remove = handlers.get('folderWorkspaces:delete')
expect(update).toBeDefined()
expect(remove).toBeDefined()
await update!(null, {
folderWorkspaceId: 'missing-folder',
updates: { name: 'Missing folder' }
})
await remove!(null, {
folderWorkspaceId: 'missing-folder'
})
expect(mockStore.updateFolderWorkspace).toHaveBeenCalledOnce()
expect(mockStore.removeFolderWorkspace).toHaveBeenCalledOnce()
expect(folderWorkspaceChangeNotifier.notifyFolderWorkspaceChanged).not.toHaveBeenCalled()
expect(scheduleWatcherSyncMock).not.toHaveBeenCalled()
expect(mainWindow.webContents.send).not.toHaveBeenCalledWith('repos:changed')
})

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b002b283-38f8-44cb-adc8-a6277d5cc8e8

📥 Commits

Reviewing files that changed from the base of the PR and between f4a89a3 and 549e484.

📒 Files selected for processing (3)
  • mobile/app/h/[hostId]/index.tsx
  • mobile/src/worktree/host-worktree-refresh.test.ts
  • mobile/src/worktree/host-worktree-refresh.ts

Comment thread mobile/app/h/[hostId]/index.tsx Outdated
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