Skip to content

feat(desktop): register t3:// OS protocol handler for thread deep links#2424

Open
davidmashburn wants to merge 2 commits intopingdotgg:mainfrom
davidmashburn:feat/t3-deep-link-protocol
Open

feat(desktop): register t3:// OS protocol handler for thread deep links#2424
davidmashburn wants to merge 2 commits intopingdotgg:mainfrom
davidmashburn:feat/t3-deep-link-protocol

Conversation

@davidmashburn
Copy link
Copy Markdown

@davidmashburn davidmashburn commented Apr 30, 2026

Summary

  • Adds a t3://thread/<threadId> OS protocol handler to the Electron desktop app so that t3:// URLs can be opened from anywhere (browser, Slack, terminal) without hitting the pairing screen
  • Main process (apps/desktop/src/main.ts): registers open-url / second-instance listeners, calls setAsDefaultProtocolClient('t3') in whenReady, and queues deep links received before the window loads
  • Preload (apps/desktop/src/preload.ts): exposes onOpenThread over the context bridge
  • Renderer (apps/web/src/components/AppSidebarLayout.tsx): subscribes to onOpenThread and navigates to /$environmentId/$threadId using TanStack Router
  • Build config (scripts/build-desktop-artifact.ts): adds CFBundleURLTypes with scheme t3 to the macOS packaged build via electron-builder's protocols field, enabling OS-level registration even when the app is not running

Test plan

  • Build a packaged macOS DMG (bun dist:desktop:dmg)
  • Install the build and confirm t3:// appears in lsregister -dump
  • Run open "t3://thread/<valid-threadId>" — app should come to front and navigate to the thread
  • Repeat with the app closed — app should launch and open the thread (cold-start path via pendingDeepLinkThreadId)
  • Confirm bun test still passes (existing test mocks updated in localApi.test.ts and SettingsPanels.browser.tsx)

🤖 Generated with Claude Code


Open in Devin Review

Note

Medium Risk
Adds OS-level protocol handling and IPC-driven navigation, which touches desktop app startup/single-instance behavior and could affect window lifecycle and deep-link dispatch timing across platforms.

Overview
Adds support for t3://thread/<threadId> deep links in the Electron desktop app, including early-event handling and queuing deep links until the main window finishes loading.

Registers the app as the default t3 protocol client and enforces single-instance behavior to route Windows/Linux deep links via second-instance, while macOS uses open-url. Exposes a new desktopBridge.onOpenThread IPC subscription and updates the web renderer to navigate to the specified thread for the active environment; test stubs/mocks and the macOS electron-builder config are updated accordingly.

Reviewed by Cursor Bugbot for commit c1e8393. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Register t3:// OS protocol handler for thread deep links in the desktop app

  • Registers the t3 custom URL scheme via app.setAsDefaultProtocolClient and, for macOS bundles, declares it in the build config protocols entry.
  • Adds handleDeepLink in main.ts to parse t3://thread/<threadId> URLs and send a desktop:open-thread IPC message to the renderer, buffering the threadId if the window hasn't finished loading yet.
  • Enforces single-instance lock so deep links opened in a second instance are forwarded to the primary process via the second-instance event.
  • Exposes desktopBridge.onOpenThread in preload.ts so renderer code can subscribe to open-thread events.
  • AppSidebarLayout.tsx subscribes to onOpenThread and navigates to the thread route using the active environment ID.

Macroscope summarized c1e8393.

davidmashburn and others added 2 commits April 29, 2026 16:05
Clicking a t3://thread/<threadId> link from anywhere — browser, terminal,
another app — now focuses the running T3 window and navigates directly
to that thread without a pairing screen.

Changes:
- main.ts: setAsDefaultProtocolClient("t3"), open-url handler (macOS),
  requestSingleInstanceLock + second-instance handler (Windows/Linux),
  handleDeepLink() parses t3://thread/<threadId> and sends IPC to the
  renderer; queues in pendingDeepLinkThreadId if window not yet loaded
- preload.ts: expose onOpenThread via desktopBridge (desktop:open-thread)
- AppSidebarLayout: subscribe to onOpenThread, navigate to
  /$activeEnvironmentId/$threadId on receipt
- contracts/ipc.ts: add onOpenThread to DesktopBridge interface
- Update two test mocks for the new bridge method

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Include the `t3` protocol in the electron-builder mac config so the
packaged app registers the t3:// URL scheme at install time via
CFBundleURLTypes in Info.plist, rather than relying solely on the
runtime setAsDefaultProtocolClient call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 34994830-a516-4636-8a2a-888f8411af5a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 30, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c1e8393. Configure here.

Comment thread apps/desktop/src/main.ts
if (pendingDeepLinkThreadId !== null) {
window.webContents.send(OPEN_THREAD_CHANNEL, pendingDeepLinkThreadId);
pendingDeepLinkThreadId = null;
}
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.

Cold-start deep link permanently lost due to timing

High Severity

The cold-start deep link path is broken due to two compounding timing issues. First, a comment in the same file notes that did-finish-load "typically fires before the first paint," but React's useEffect (which registers the onOpenThread IPC listener) runs after paint — so the IPC message is sent before any listener exists. Second, even if that race were avoided, activeEnvironmentId is initialized to null and only set once serverConfig loads from the backend; during cold start the callback will hit the if (!activeEnvironmentId) return guard and silently discard the thread ID. Since pendingDeepLinkThreadId is cleared after sending and never retried, the deep link is permanently lost.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c1e8393. Configure here.

@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented Apr 30, 2026

Approvability

Verdict: Needs human review

This PR introduces a new OS protocol handler feature with deep link navigation capabilities, which constitutes new user-facing behavior requiring human review. Additionally, an unresolved review comment identifies a high-severity timing bug where cold-start deep links may be permanently lost.

You can customize Macroscope's approvability policy. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant