Add client-only desktop backend mode - #4444

Closed
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery
Closed

Add client-only desktop backend mode#4444
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Consolidation update

This branch is rebased onto the latest main and now serves as the combined client/server-model stack:

What Changed

Let the desktop app run as a pure client for remote or independently managed backends, instead of always owning a local backend process.

  • Add a persisted desktop backend mode with managed and client-only options, plus a launch-only --backend-mode override, exposed over IPC.
  • In client-only, bootstrap skips the backend pool entirely and serves the renderer from packaged static assets (or the dev-server proxy), opens the window on renderer readiness rather than backend readiness, and skips backend shutdown on quit.
  • ElectronProtocol now takes an explicit source: "proxy" | "static" (dropping the separate backendOrigin). Static serving adds path-safe file resolution with SPA fallback, MIME types, CSP, and HEAD support.
  • Web routing treats client-only like hosted static, and local environment bootstraps are empty, so a client-only desktop is never stranded on a dead primary-backend state — it is routed to Connections instead.

Why

The desktop app should be usable as a pure client without stranding users who have no saved connections.

Scope note

This PR previously also contained Linux local t3 serve discovery. That half has been split into a separate PR so the two can be reviewed independently — the discovery work carries a distinct security surface (a live pairing credential on disk) and is Linux-only, whereas this change is cross-platform.

UI Changes

  • Connections gains a Desktop applicationBackend mode control.
  • Client-only users with no connections are routed to Connections rather than a dead primary-backend state.
  • Providers gains per-environment selection, scoped provider cards and controls, and explicit loading, offline, error, empty, editable, and read-only states from feat(web): per-device provider settings #4479.
  • General, Source Control, Keybindings, and Diagnostics use the same selected-environment model; Diagnostics includes connection-aware refresh and failure states.
  • Settings navigation distinguishes client-owned settings from environment-owned settings.

Packaged desktop evidence

Final privacy-reviewed evidence for this PR is pinned to immutable asset commit
d23321ea7d7200ef4d5e14f20dd2367997731706.

Capture context:

  • PR head: bb2c567e6879cf762910a02a8d20943edc2689ce
  • PR base: ece05087a70e94efcd57441337fa1249559362ba
  • Synthetic combined-stack integration: 5ba3b7bd0995f401993706689105f41f51c16b02
  • Exact GUI-tested package: /nix/store/86n9kxipsprgw6nbzyw7qhrw0wl4lfwx-t3code-0.0.29-patched-main-20260724

The packaged Electron app started with --backend-mode=client-only without
starting or owning a local backend, resolved its installed renderer assets,
and paired successfully to an independently started packaged t3 serve
backend. The saved environment then appeared online.

Client-only startup with no saved environment

Client-only startup with no saved environment

Connections settings and active CLI override

Connections settings showing client-only semantics and the active CLI override

Independent backend paired and online

Independently managed backend paired and online

Connected home state ·
capture manifest

After this GUI capture, the maintained stack advanced its upstream base to
41a430a88e8dde9c428f59d54dd328aa6a66a8fd. The intervening upstream delta
was isolated to #4472 model registration and did not overlap this desktop
change. The resulting final installed package
/nix/store/brp90sqhjxnnpczsnw9nmp1rlzi0qjgk-t3code-0.0.29-patched-main-20260724
built and activated successfully. The screenshots above remain specifically
attributable to the exact earlier package and synthetic integration listed
above; they are not represented as a GUI recapture of the later package.

Checklist

  • corepack pnpm exec vp check (0 errors; 11 pre-existing unrelated warnings)
  • corepack pnpm exec vp run --filter @t3tools/contracts --filter @t3tools/shared --filter t3 --filter @t3tools/desktop --filter @t3tools/web typecheck — clean
  • CI=true vitest run apps/desktop/src apps/web/src/components/settings apps/web/src/environments apps/server/src/startupAccess.test.ts — 63 files, 437 tests
  • Manually verified in an isolated Electron desktop: mode switch relaunches, client-only opens without a backend, packaged static renderer serves deep links, saved connections reconnect after restart

Note

Add client-only desktop backend mode that serves static assets without a local backend

  • Introduces a client-only backend mode for the desktop app, selectable via the Connections settings page or --backend-mode CLI flag; in this mode the desktop app skips local backend startup entirely.
  • Adds DesktopBackendMode service to parse, validate, and latch backend mode from settings and CLI; persists mode via DesktopAppSettings and triggers an app relaunch on change.
  • Extends ElectronProtocol to serve packaged static assets (with SPA fallback, CSP, path traversal protection) when in client-only mode instead of proxying to a local backend.
  • Exposes getBackendModeState (sync) and setBackendMode IPC methods via preload.ts so the renderer can read and change mode; the UI prevents switching to client-only without a saved remote environment.
  • Refactors settings panels (General, Diagnostics, Keybindings, Source Control, Providers) to be environment-scoped, disabling server-backed controls when the selected environment is disconnected.
  • Adds isDesktopClientOnlyMode detection in the web layer so routing, connection source selection, and sidebar/thread logic treat client-only the same as the hosted static app.
  • Risk: switching backend mode triggers an app relaunch; if relaunch fails the settings change is reverted, but the UI will surface the error.

Macroscope summarized acb11a6.


Note

High Risk
Touches Electron startup/shutdown, custom protocol static file serving (path traversal mitigations), and preload fail-closed behavior that can suppress local backend until IPC is ready; large cross-cutting desktop + web surface.

Overview
Introduces managed vs client-only desktop backend mode (persisted settings, --backend-mode CLI override, IPC get/set). In client-only, startup skips the backend pool, registers Electron protocol as static packaged assets or dev proxy, opens the main window on renderer readiness, and does not stop backends on quit.

ElectronProtocol now uses source: "proxy" | "static" (drops backendOrigin); static mode adds safe path resolution, SPA fallback, MIME/CSP, and GET/HEAD handling. Connections exposes backend mode switching (relaunch); preload fails closed to client-only if mode IPC is missing.

Web UI shifts from always using the primary server atom to environment-scoped config: Providers/Add Provider, Diagnostics (selector + connection-aware loading), keybindings editor keys, and sidebar/command palette grouping use useDefaultServerConfig, EnvironmentPresenceScope, and ownsLocalEnvironment so client-only desktops do not assume a local primary backend.

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

@coderabbitai

coderabbitaiBot commented Jul 24, 2026

Copy link
Copy Markdown

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 Plus

Run ID: 76f5a485-2760-41bb-b19f-f63af805e9fe

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/server/src/serverRuntimeStartup.ts
Comment threadapps/web/src/environments/primary/target.ts
Comment threadapps/desktop/src/app/DesktopLocalServerDiscovery.ts Outdated
Comment threadapps/desktop/src/electron/ElectronProtocol.ts
@macroscopeapp

macroscopeappBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

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

@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from a0f2a4f to 9d6b3f5CompareJuly 24, 2026 17:00
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from 32f076d to 048f271CompareJuly 24, 2026 19:48
@colonelpanic8colonelpanic8 changed the title Add client-only desktop mode with local server discoveryAdd client-only desktop backend modeJul 24, 2026
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/desktop/src/preload.ts
Comment threadapps/desktop/src/app/DesktopApp.ts
Comment threadapps/desktop/src/ipc/methods/backendMode.ts
Comment threadapps/desktop/src/app/DesktopLifecycle.ts
t3dotggand others added 3 commits July 24, 2026 13:56
Providers settings were hardwired to the primary backend, so remote boxes
reached through T3 Connect or app.t3.codes could not be configured.
- List every registered execution environment and let one be selected
- Scope provider reads, settings writes, refreshes, updates, and instance
creation to the selected environment
- Gate controls on raw server config, connection phase, and operate scope
- Keep shared model preferences intact when removing per-device config
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review findings on the per-device providers panel.
- Treat unresolved primary session scopes as loading instead of editable, so
controls are never offered before permissions are known
- Render provider status rows for read-only sessions instead of replacing the
whole panel with a blocking message
- Distinguish a hydrating environment catalog from having no devices
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Extract resolvePrimaryOperateAccess so SWR revalidation no longer reports
pending while cached session data is available, which was unmounting the
provider editor and discarding in-progress edits
- Carry a reason on the loading state so waiting on permissions is not
described as waiting on device configuration
- Name read-only provider rows with deriveProviderInstanceEntries so multiple
instances of one driver stay distinguishable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch 2 times, most recently from 704649e to 20ba333CompareJuly 24, 2026 23:18
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
colonelpanic8and others added 2 commits July 27, 2026 19:37
Let the desktop app run as a pure client for remote or independently
managed backends, instead of always owning a local backend process.
- Add a persisted `managed` / `client-only` backend mode with a
launch-only `--backend-mode` override, exposed over IPC.
- In client-only mode, bootstrap skips the backend pool entirely and
serves the renderer from packaged static assets (or the dev proxy),
opening the window on renderer readiness rather than backend
readiness, and skipping backend shutdown on quit.
- `ElectronProtocol` takes an explicit `source: "proxy" | "static"`.
Static serving adds path-safe file resolution, SPA fallback, MIME
types, CSP and HEAD support.
- Web routing treats client-only like hosted static, and local
environment bootstraps are empty, so a client-only desktop is never
stranded on a dead primary-backend state.
Co-Authored-By: Claude <noreply@anthropic.com>
colonelpanic8and others added 11 commits July 27, 2026 19:37
The sidebar decided a thread or project was remote by comparing its
environment against the primary environment id. A client-only desktop
(and the hosted static web app) never registers a primary, so that id is
always null and every row read as local: no cloud/server icon, and no
environment name on the project header.
Make the comparison explicit about the two different meanings of a null
primary id. `isRemoteEnvironmentId` now takes an `ownsLocalEnvironment`
flag: when the app can never serve an environment from its own backend,
every environment is remote; when it can, a null primary id still means
"the local backend has not registered yet" and nothing is flagged remote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The diagnostics environment picker made three pieces of panel-local state
lie about which machine they belong to:
- `openLogsDirectoryError` / `isOpeningLogsDirectory` kept showing an
environment A failure (or spinner) after switching to environment B.
They are now stored together with the environment they belong to, so
they only render for that environment and a late completion can only
update its own request.
- `signalingPid` was a single shared value, so completing a signal on
environment A re-enabled the signal controls for an in-flight signal on
environment B and allowed a duplicate SIGKILL. Pending signals are now
identified by environment id plus pid.
- Diagnostics queries only run while the selected environment's
supervisor is connected, so a disconnected machine showed permanent
"Loading..." placeholders with the refresh buttons disabled. The panel
now derives a connection notice and renders it instead of the loading
state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the review feedback on the previous commit:
- `DiagnosticsRefreshButton` used a single `isPending` prop for both the
spinner and `disabled`, so suppressing the spinner while the selected
environment is disconnected also made the button clickable even though
the query cannot run. It now takes an explicit `isDisabled` prop.
- The open-logs completion matched on environment id alone, so returning
to an environment and starting a second open while the first was still
in flight let the older completion clear the newer pending state. The
completion now matches the request by identity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from f2af1bf to cda2160CompareJuly 28, 2026 02:41
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Jul 28, 2026
Comment threadapps/web/src/hooks/useDefaultServerConfig.ts
Comment threadapps/web/src/components/settings/ProviderSettingsPanel.tsx

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort 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 2fbd1f5. Configure here.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

Closing this PR after an automated pass over open pull requests. Introduces an alternate client-only desktop architecture across 81 files.

@t3dotggt3dotgg closed this Aug 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add client-only desktop backend mode - #4444

Closed
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery
Closed

Add client-only desktop backend mode#4444
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Consolidation update

This branch is rebased onto the latest main and now serves as the combined client/server-model stack:

What Changed

Let the desktop app run as a pure client for remote or independently managed backends, instead of always owning a local backend process.

  • Add a persisted desktop backend mode with managed and client-only options, plus a launch-only --backend-mode override, exposed over IPC.
  • In client-only, bootstrap skips the backend pool entirely and serves the renderer from packaged static assets (or the dev-server proxy), opens the window on renderer readiness rather than backend readiness, and skips backend shutdown on quit.
  • ElectronProtocol now takes an explicit source: "proxy" | "static" (dropping the separate backendOrigin). Static serving adds path-safe file resolution with SPA fallback, MIME types, CSP, and HEAD support.
  • Web routing treats client-only like hosted static, and local environment bootstraps are empty, so a client-only desktop is never stranded on a dead primary-backend state — it is routed to Connections instead.

Why

The desktop app should be usable as a pure client without stranding users who have no saved connections.

Scope note

This PR previously also contained Linux local t3 serve discovery. That half has been split into a separate PR so the two can be reviewed independently — the discovery work carries a distinct security surface (a live pairing credential on disk) and is Linux-only, whereas this change is cross-platform.

UI Changes

  • Connections gains a Desktop applicationBackend mode control.
  • Client-only users with no connections are routed to Connections rather than a dead primary-backend state.
  • Providers gains per-environment selection, scoped provider cards and controls, and explicit loading, offline, error, empty, editable, and read-only states from feat(web): per-device provider settings #4479.
  • General, Source Control, Keybindings, and Diagnostics use the same selected-environment model; Diagnostics includes connection-aware refresh and failure states.
  • Settings navigation distinguishes client-owned settings from environment-owned settings.

Packaged desktop evidence

Final privacy-reviewed evidence for this PR is pinned to immutable asset commit
d23321ea7d7200ef4d5e14f20dd2367997731706.

Capture context:

  • PR head: bb2c567e6879cf762910a02a8d20943edc2689ce
  • PR base: ece05087a70e94efcd57441337fa1249559362ba
  • Synthetic combined-stack integration: 5ba3b7bd0995f401993706689105f41f51c16b02
  • Exact GUI-tested package: /nix/store/86n9kxipsprgw6nbzyw7qhrw0wl4lfwx-t3code-0.0.29-patched-main-20260724

The packaged Electron app started with --backend-mode=client-only without
starting or owning a local backend, resolved its installed renderer assets,
and paired successfully to an independently started packaged t3 serve
backend. The saved environment then appeared online.

Client-only startup with no saved environment

Client-only startup with no saved environment

Connections settings and active CLI override

Connections settings showing client-only semantics and the active CLI override

Independent backend paired and online

Independently managed backend paired and online

Connected home state ·
capture manifest

After this GUI capture, the maintained stack advanced its upstream base to
41a430a88e8dde9c428f59d54dd328aa6a66a8fd. The intervening upstream delta
was isolated to #4472 model registration and did not overlap this desktop
change. The resulting final installed package
/nix/store/brp90sqhjxnnpczsnw9nmp1rlzi0qjgk-t3code-0.0.29-patched-main-20260724
built and activated successfully. The screenshots above remain specifically
attributable to the exact earlier package and synthetic integration listed
above; they are not represented as a GUI recapture of the later package.

Checklist

  • corepack pnpm exec vp check (0 errors; 11 pre-existing unrelated warnings)
  • corepack pnpm exec vp run --filter @t3tools/contracts --filter @t3tools/shared --filter t3 --filter @t3tools/desktop --filter @t3tools/web typecheck — clean
  • CI=true vitest run apps/desktop/src apps/web/src/components/settings apps/web/src/environments apps/server/src/startupAccess.test.ts — 63 files, 437 tests
  • Manually verified in an isolated Electron desktop: mode switch relaunches, client-only opens without a backend, packaged static renderer serves deep links, saved connections reconnect after restart

Note

Add client-only desktop backend mode that serves static assets without a local backend

  • Introduces a client-only backend mode for the desktop app, selectable via the Connections settings page or --backend-mode CLI flag; in this mode the desktop app skips local backend startup entirely.
  • Adds DesktopBackendMode service to parse, validate, and latch backend mode from settings and CLI; persists mode via DesktopAppSettings and triggers an app relaunch on change.
  • Extends ElectronProtocol to serve packaged static assets (with SPA fallback, CSP, path traversal protection) when in client-only mode instead of proxying to a local backend.
  • Exposes getBackendModeState (sync) and setBackendMode IPC methods via preload.ts so the renderer can read and change mode; the UI prevents switching to client-only without a saved remote environment.
  • Refactors settings panels (General, Diagnostics, Keybindings, Source Control, Providers) to be environment-scoped, disabling server-backed controls when the selected environment is disconnected.
  • Adds isDesktopClientOnlyMode detection in the web layer so routing, connection source selection, and sidebar/thread logic treat client-only the same as the hosted static app.
  • Risk: switching backend mode triggers an app relaunch; if relaunch fails the settings change is reverted, but the UI will surface the error.

Macroscope summarized acb11a6.


Note

High Risk
Touches Electron startup/shutdown, custom protocol static file serving (path traversal mitigations), and preload fail-closed behavior that can suppress local backend until IPC is ready; large cross-cutting desktop + web surface.

Overview
Introduces managed vs client-only desktop backend mode (persisted settings, --backend-mode CLI override, IPC get/set). In client-only, startup skips the backend pool, registers Electron protocol as static packaged assets or dev proxy, opens the main window on renderer readiness, and does not stop backends on quit.

ElectronProtocol now uses source: "proxy" | "static" (drops backendOrigin); static mode adds safe path resolution, SPA fallback, MIME/CSP, and GET/HEAD handling. Connections exposes backend mode switching (relaunch); preload fails closed to client-only if mode IPC is missing.

Web UI shifts from always using the primary server atom to environment-scoped config: Providers/Add Provider, Diagnostics (selector + connection-aware loading), keybindings editor keys, and sidebar/command palette grouping use useDefaultServerConfig, EnvironmentPresenceScope, and ownsLocalEnvironment so client-only desktops do not assume a local primary backend.

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

@coderabbitai

coderabbitaiBot commented Jul 24, 2026

Copy link
Copy Markdown

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 Plus

Run ID: 76f5a485-2760-41bb-b19f-f63af805e9fe

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/server/src/serverRuntimeStartup.ts
Comment threadapps/web/src/environments/primary/target.ts
Comment threadapps/desktop/src/app/DesktopLocalServerDiscovery.ts Outdated
Comment threadapps/desktop/src/electron/ElectronProtocol.ts
@macroscopeapp

macroscopeappBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

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

@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from a0f2a4f to 9d6b3f5CompareJuly 24, 2026 17:00
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from 32f076d to 048f271CompareJuly 24, 2026 19:48
@colonelpanic8colonelpanic8 changed the title Add client-only desktop mode with local server discoveryAdd client-only desktop backend modeJul 24, 2026
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/desktop/src/preload.ts
Comment threadapps/desktop/src/app/DesktopApp.ts
Comment threadapps/desktop/src/ipc/methods/backendMode.ts
Comment threadapps/desktop/src/app/DesktopLifecycle.ts
t3dotggand others added 3 commits July 24, 2026 13:56
Providers settings were hardwired to the primary backend, so remote boxes
reached through T3 Connect or app.t3.codes could not be configured.
- List every registered execution environment and let one be selected
- Scope provider reads, settings writes, refreshes, updates, and instance
creation to the selected environment
- Gate controls on raw server config, connection phase, and operate scope
- Keep shared model preferences intact when removing per-device config
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review findings on the per-device providers panel.
- Treat unresolved primary session scopes as loading instead of editable, so
controls are never offered before permissions are known
- Render provider status rows for read-only sessions instead of replacing the
whole panel with a blocking message
- Distinguish a hydrating environment catalog from having no devices
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Extract resolvePrimaryOperateAccess so SWR revalidation no longer reports
pending while cached session data is available, which was unmounting the
provider editor and discarding in-progress edits
- Carry a reason on the loading state so waiting on permissions is not
described as waiting on device configuration
- Name read-only provider rows with deriveProviderInstanceEntries so multiple
instances of one driver stay distinguishable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch 2 times, most recently from 704649e to 20ba333CompareJuly 24, 2026 23:18
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
colonelpanic8and others added 2 commits July 27, 2026 19:37
Let the desktop app run as a pure client for remote or independently
managed backends, instead of always owning a local backend process.
- Add a persisted `managed` / `client-only` backend mode with a
launch-only `--backend-mode` override, exposed over IPC.
- In client-only mode, bootstrap skips the backend pool entirely and
serves the renderer from packaged static assets (or the dev proxy),
opening the window on renderer readiness rather than backend
readiness, and skipping backend shutdown on quit.
- `ElectronProtocol` takes an explicit `source: "proxy" | "static"`.
Static serving adds path-safe file resolution, SPA fallback, MIME
types, CSP and HEAD support.
- Web routing treats client-only like hosted static, and local
environment bootstraps are empty, so a client-only desktop is never
stranded on a dead primary-backend state.
Co-Authored-By: Claude <noreply@anthropic.com>
colonelpanic8and others added 11 commits July 27, 2026 19:37
The sidebar decided a thread or project was remote by comparing its
environment against the primary environment id. A client-only desktop
(and the hosted static web app) never registers a primary, so that id is
always null and every row read as local: no cloud/server icon, and no
environment name on the project header.
Make the comparison explicit about the two different meanings of a null
primary id. `isRemoteEnvironmentId` now takes an `ownsLocalEnvironment`
flag: when the app can never serve an environment from its own backend,
every environment is remote; when it can, a null primary id still means
"the local backend has not registered yet" and nothing is flagged remote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The diagnostics environment picker made three pieces of panel-local state
lie about which machine they belong to:
- `openLogsDirectoryError` / `isOpeningLogsDirectory` kept showing an
environment A failure (or spinner) after switching to environment B.
They are now stored together with the environment they belong to, so
they only render for that environment and a late completion can only
update its own request.
- `signalingPid` was a single shared value, so completing a signal on
environment A re-enabled the signal controls for an in-flight signal on
environment B and allowed a duplicate SIGKILL. Pending signals are now
identified by environment id plus pid.
- Diagnostics queries only run while the selected environment's
supervisor is connected, so a disconnected machine showed permanent
"Loading..." placeholders with the refresh buttons disabled. The panel
now derives a connection notice and renders it instead of the loading
state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the review feedback on the previous commit:
- `DiagnosticsRefreshButton` used a single `isPending` prop for both the
spinner and `disabled`, so suppressing the spinner while the selected
environment is disconnected also made the button clickable even though
the query cannot run. It now takes an explicit `isDisabled` prop.
- The open-logs completion matched on environment id alone, so returning
to an environment and starting a second open while the first was still
in flight let the older completion clear the newer pending state. The
completion now matches the request by identity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from f2af1bf to cda2160CompareJuly 28, 2026 02:41
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Jul 28, 2026
Comment threadapps/web/src/hooks/useDefaultServerConfig.ts
Comment threadapps/web/src/components/settings/ProviderSettingsPanel.tsx

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort 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 2fbd1f5. Configure here.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

Closing this PR after an automated pass over open pull requests. Introduces an alternate client-only desktop architecture across 81 files.

@t3dotggt3dotgg closed this Aug 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add client-only desktop backend mode - #4444

Closed
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery
Closed

Add client-only desktop backend mode#4444
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Consolidation update

This branch is rebased onto the latest main and now serves as the combined client/server-model stack:

What Changed

Let the desktop app run as a pure client for remote or independently managed backends, instead of always owning a local backend process.

  • Add a persisted desktop backend mode with managed and client-only options, plus a launch-only --backend-mode override, exposed over IPC.
  • In client-only, bootstrap skips the backend pool entirely and serves the renderer from packaged static assets (or the dev-server proxy), opens the window on renderer readiness rather than backend readiness, and skips backend shutdown on quit.
  • ElectronProtocol now takes an explicit source: "proxy" | "static" (dropping the separate backendOrigin). Static serving adds path-safe file resolution with SPA fallback, MIME types, CSP, and HEAD support.
  • Web routing treats client-only like hosted static, and local environment bootstraps are empty, so a client-only desktop is never stranded on a dead primary-backend state — it is routed to Connections instead.

Why

The desktop app should be usable as a pure client without stranding users who have no saved connections.

Scope note

This PR previously also contained Linux local t3 serve discovery. That half has been split into a separate PR so the two can be reviewed independently — the discovery work carries a distinct security surface (a live pairing credential on disk) and is Linux-only, whereas this change is cross-platform.

UI Changes

  • Connections gains a Desktop applicationBackend mode control.
  • Client-only users with no connections are routed to Connections rather than a dead primary-backend state.
  • Providers gains per-environment selection, scoped provider cards and controls, and explicit loading, offline, error, empty, editable, and read-only states from feat(web): per-device provider settings #4479.
  • General, Source Control, Keybindings, and Diagnostics use the same selected-environment model; Diagnostics includes connection-aware refresh and failure states.
  • Settings navigation distinguishes client-owned settings from environment-owned settings.

Packaged desktop evidence

Final privacy-reviewed evidence for this PR is pinned to immutable asset commit
d23321ea7d7200ef4d5e14f20dd2367997731706.

Capture context:

  • PR head: bb2c567e6879cf762910a02a8d20943edc2689ce
  • PR base: ece05087a70e94efcd57441337fa1249559362ba
  • Synthetic combined-stack integration: 5ba3b7bd0995f401993706689105f41f51c16b02
  • Exact GUI-tested package: /nix/store/86n9kxipsprgw6nbzyw7qhrw0wl4lfwx-t3code-0.0.29-patched-main-20260724

The packaged Electron app started with --backend-mode=client-only without
starting or owning a local backend, resolved its installed renderer assets,
and paired successfully to an independently started packaged t3 serve
backend. The saved environment then appeared online.

Client-only startup with no saved environment

Client-only startup with no saved environment

Connections settings and active CLI override

Connections settings showing client-only semantics and the active CLI override

Independent backend paired and online

Independently managed backend paired and online

Connected home state ·
capture manifest

After this GUI capture, the maintained stack advanced its upstream base to
41a430a88e8dde9c428f59d54dd328aa6a66a8fd. The intervening upstream delta
was isolated to #4472 model registration and did not overlap this desktop
change. The resulting final installed package
/nix/store/brp90sqhjxnnpczsnw9nmp1rlzi0qjgk-t3code-0.0.29-patched-main-20260724
built and activated successfully. The screenshots above remain specifically
attributable to the exact earlier package and synthetic integration listed
above; they are not represented as a GUI recapture of the later package.

Checklist

  • corepack pnpm exec vp check (0 errors; 11 pre-existing unrelated warnings)
  • corepack pnpm exec vp run --filter @t3tools/contracts --filter @t3tools/shared --filter t3 --filter @t3tools/desktop --filter @t3tools/web typecheck — clean
  • CI=true vitest run apps/desktop/src apps/web/src/components/settings apps/web/src/environments apps/server/src/startupAccess.test.ts — 63 files, 437 tests
  • Manually verified in an isolated Electron desktop: mode switch relaunches, client-only opens without a backend, packaged static renderer serves deep links, saved connections reconnect after restart

Note

Add client-only desktop backend mode that serves static assets without a local backend

  • Introduces a client-only backend mode for the desktop app, selectable via the Connections settings page or --backend-mode CLI flag; in this mode the desktop app skips local backend startup entirely.
  • Adds DesktopBackendMode service to parse, validate, and latch backend mode from settings and CLI; persists mode via DesktopAppSettings and triggers an app relaunch on change.
  • Extends ElectronProtocol to serve packaged static assets (with SPA fallback, CSP, path traversal protection) when in client-only mode instead of proxying to a local backend.
  • Exposes getBackendModeState (sync) and setBackendMode IPC methods via preload.ts so the renderer can read and change mode; the UI prevents switching to client-only without a saved remote environment.
  • Refactors settings panels (General, Diagnostics, Keybindings, Source Control, Providers) to be environment-scoped, disabling server-backed controls when the selected environment is disconnected.
  • Adds isDesktopClientOnlyMode detection in the web layer so routing, connection source selection, and sidebar/thread logic treat client-only the same as the hosted static app.
  • Risk: switching backend mode triggers an app relaunch; if relaunch fails the settings change is reverted, but the UI will surface the error.

Macroscope summarized acb11a6.


Note

High Risk
Touches Electron startup/shutdown, custom protocol static file serving (path traversal mitigations), and preload fail-closed behavior that can suppress local backend until IPC is ready; large cross-cutting desktop + web surface.

Overview
Introduces managed vs client-only desktop backend mode (persisted settings, --backend-mode CLI override, IPC get/set). In client-only, startup skips the backend pool, registers Electron protocol as static packaged assets or dev proxy, opens the main window on renderer readiness, and does not stop backends on quit.

ElectronProtocol now uses source: "proxy" | "static" (drops backendOrigin); static mode adds safe path resolution, SPA fallback, MIME/CSP, and GET/HEAD handling. Connections exposes backend mode switching (relaunch); preload fails closed to client-only if mode IPC is missing.

Web UI shifts from always using the primary server atom to environment-scoped config: Providers/Add Provider, Diagnostics (selector + connection-aware loading), keybindings editor keys, and sidebar/command palette grouping use useDefaultServerConfig, EnvironmentPresenceScope, and ownsLocalEnvironment so client-only desktops do not assume a local primary backend.

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

@coderabbitai

coderabbitaiBot commented Jul 24, 2026

Copy link
Copy Markdown

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 Plus

Run ID: 76f5a485-2760-41bb-b19f-f63af805e9fe

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/server/src/serverRuntimeStartup.ts
Comment threadapps/web/src/environments/primary/target.ts
Comment threadapps/desktop/src/app/DesktopLocalServerDiscovery.ts Outdated
Comment threadapps/desktop/src/electron/ElectronProtocol.ts
@macroscopeapp

macroscopeappBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

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

@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from a0f2a4f to 9d6b3f5CompareJuly 24, 2026 17:00
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from 32f076d to 048f271CompareJuly 24, 2026 19:48
@colonelpanic8colonelpanic8 changed the title Add client-only desktop mode with local server discoveryAdd client-only desktop backend modeJul 24, 2026
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/desktop/src/preload.ts
Comment threadapps/desktop/src/app/DesktopApp.ts
Comment threadapps/desktop/src/ipc/methods/backendMode.ts
Comment threadapps/desktop/src/app/DesktopLifecycle.ts
t3dotggand others added 3 commits July 24, 2026 13:56
Providers settings were hardwired to the primary backend, so remote boxes
reached through T3 Connect or app.t3.codes could not be configured.
- List every registered execution environment and let one be selected
- Scope provider reads, settings writes, refreshes, updates, and instance
creation to the selected environment
- Gate controls on raw server config, connection phase, and operate scope
- Keep shared model preferences intact when removing per-device config
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review findings on the per-device providers panel.
- Treat unresolved primary session scopes as loading instead of editable, so
controls are never offered before permissions are known
- Render provider status rows for read-only sessions instead of replacing the
whole panel with a blocking message
- Distinguish a hydrating environment catalog from having no devices
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Extract resolvePrimaryOperateAccess so SWR revalidation no longer reports
pending while cached session data is available, which was unmounting the
provider editor and discarding in-progress edits
- Carry a reason on the loading state so waiting on permissions is not
described as waiting on device configuration
- Name read-only provider rows with deriveProviderInstanceEntries so multiple
instances of one driver stay distinguishable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch 2 times, most recently from 704649e to 20ba333CompareJuly 24, 2026 23:18
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
colonelpanic8and others added 2 commits July 27, 2026 19:37
Let the desktop app run as a pure client for remote or independently
managed backends, instead of always owning a local backend process.
- Add a persisted `managed` / `client-only` backend mode with a
launch-only `--backend-mode` override, exposed over IPC.
- In client-only mode, bootstrap skips the backend pool entirely and
serves the renderer from packaged static assets (or the dev proxy),
opening the window on renderer readiness rather than backend
readiness, and skipping backend shutdown on quit.
- `ElectronProtocol` takes an explicit `source: "proxy" | "static"`.
Static serving adds path-safe file resolution, SPA fallback, MIME
types, CSP and HEAD support.
- Web routing treats client-only like hosted static, and local
environment bootstraps are empty, so a client-only desktop is never
stranded on a dead primary-backend state.
Co-Authored-By: Claude <noreply@anthropic.com>
colonelpanic8and others added 11 commits July 27, 2026 19:37
The sidebar decided a thread or project was remote by comparing its
environment against the primary environment id. A client-only desktop
(and the hosted static web app) never registers a primary, so that id is
always null and every row read as local: no cloud/server icon, and no
environment name on the project header.
Make the comparison explicit about the two different meanings of a null
primary id. `isRemoteEnvironmentId` now takes an `ownsLocalEnvironment`
flag: when the app can never serve an environment from its own backend,
every environment is remote; when it can, a null primary id still means
"the local backend has not registered yet" and nothing is flagged remote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The diagnostics environment picker made three pieces of panel-local state
lie about which machine they belong to:
- `openLogsDirectoryError` / `isOpeningLogsDirectory` kept showing an
environment A failure (or spinner) after switching to environment B.
They are now stored together with the environment they belong to, so
they only render for that environment and a late completion can only
update its own request.
- `signalingPid` was a single shared value, so completing a signal on
environment A re-enabled the signal controls for an in-flight signal on
environment B and allowed a duplicate SIGKILL. Pending signals are now
identified by environment id plus pid.
- Diagnostics queries only run while the selected environment's
supervisor is connected, so a disconnected machine showed permanent
"Loading..." placeholders with the refresh buttons disabled. The panel
now derives a connection notice and renders it instead of the loading
state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the review feedback on the previous commit:
- `DiagnosticsRefreshButton` used a single `isPending` prop for both the
spinner and `disabled`, so suppressing the spinner while the selected
environment is disconnected also made the button clickable even though
the query cannot run. It now takes an explicit `isDisabled` prop.
- The open-logs completion matched on environment id alone, so returning
to an environment and starting a second open while the first was still
in flight let the older completion clear the newer pending state. The
completion now matches the request by identity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from f2af1bf to cda2160CompareJuly 28, 2026 02:41
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Jul 28, 2026
Comment threadapps/web/src/hooks/useDefaultServerConfig.ts
Comment threadapps/web/src/components/settings/ProviderSettingsPanel.tsx

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort 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 2fbd1f5. Configure here.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

Closing this PR after an automated pass over open pull requests. Introduces an alternate client-only desktop architecture across 81 files.

@t3dotggt3dotgg closed this Aug 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add client-only desktop backend mode - #4444

Closed
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery
Closed

Add client-only desktop backend mode#4444
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Consolidation update

This branch is rebased onto the latest main and now serves as the combined client/server-model stack:

What Changed

Let the desktop app run as a pure client for remote or independently managed backends, instead of always owning a local backend process.

  • Add a persisted desktop backend mode with managed and client-only options, plus a launch-only --backend-mode override, exposed over IPC.
  • In client-only, bootstrap skips the backend pool entirely and serves the renderer from packaged static assets (or the dev-server proxy), opens the window on renderer readiness rather than backend readiness, and skips backend shutdown on quit.
  • ElectronProtocol now takes an explicit source: "proxy" | "static" (dropping the separate backendOrigin). Static serving adds path-safe file resolution with SPA fallback, MIME types, CSP, and HEAD support.
  • Web routing treats client-only like hosted static, and local environment bootstraps are empty, so a client-only desktop is never stranded on a dead primary-backend state — it is routed to Connections instead.

Why

The desktop app should be usable as a pure client without stranding users who have no saved connections.

Scope note

This PR previously also contained Linux local t3 serve discovery. That half has been split into a separate PR so the two can be reviewed independently — the discovery work carries a distinct security surface (a live pairing credential on disk) and is Linux-only, whereas this change is cross-platform.

UI Changes

  • Connections gains a Desktop applicationBackend mode control.
  • Client-only users with no connections are routed to Connections rather than a dead primary-backend state.
  • Providers gains per-environment selection, scoped provider cards and controls, and explicit loading, offline, error, empty, editable, and read-only states from feat(web): per-device provider settings #4479.
  • General, Source Control, Keybindings, and Diagnostics use the same selected-environment model; Diagnostics includes connection-aware refresh and failure states.
  • Settings navigation distinguishes client-owned settings from environment-owned settings.

Packaged desktop evidence

Final privacy-reviewed evidence for this PR is pinned to immutable asset commit
d23321ea7d7200ef4d5e14f20dd2367997731706.

Capture context:

  • PR head: bb2c567e6879cf762910a02a8d20943edc2689ce
  • PR base: ece05087a70e94efcd57441337fa1249559362ba
  • Synthetic combined-stack integration: 5ba3b7bd0995f401993706689105f41f51c16b02
  • Exact GUI-tested package: /nix/store/86n9kxipsprgw6nbzyw7qhrw0wl4lfwx-t3code-0.0.29-patched-main-20260724

The packaged Electron app started with --backend-mode=client-only without
starting or owning a local backend, resolved its installed renderer assets,
and paired successfully to an independently started packaged t3 serve
backend. The saved environment then appeared online.

Client-only startup with no saved environment

Client-only startup with no saved environment

Connections settings and active CLI override

Connections settings showing client-only semantics and the active CLI override

Independent backend paired and online

Independently managed backend paired and online

Connected home state ·
capture manifest

After this GUI capture, the maintained stack advanced its upstream base to
41a430a88e8dde9c428f59d54dd328aa6a66a8fd. The intervening upstream delta
was isolated to #4472 model registration and did not overlap this desktop
change. The resulting final installed package
/nix/store/brp90sqhjxnnpczsnw9nmp1rlzi0qjgk-t3code-0.0.29-patched-main-20260724
built and activated successfully. The screenshots above remain specifically
attributable to the exact earlier package and synthetic integration listed
above; they are not represented as a GUI recapture of the later package.

Checklist

  • corepack pnpm exec vp check (0 errors; 11 pre-existing unrelated warnings)
  • corepack pnpm exec vp run --filter @t3tools/contracts --filter @t3tools/shared --filter t3 --filter @t3tools/desktop --filter @t3tools/web typecheck — clean
  • CI=true vitest run apps/desktop/src apps/web/src/components/settings apps/web/src/environments apps/server/src/startupAccess.test.ts — 63 files, 437 tests
  • Manually verified in an isolated Electron desktop: mode switch relaunches, client-only opens without a backend, packaged static renderer serves deep links, saved connections reconnect after restart

Note

Add client-only desktop backend mode that serves static assets without a local backend

  • Introduces a client-only backend mode for the desktop app, selectable via the Connections settings page or --backend-mode CLI flag; in this mode the desktop app skips local backend startup entirely.
  • Adds DesktopBackendMode service to parse, validate, and latch backend mode from settings and CLI; persists mode via DesktopAppSettings and triggers an app relaunch on change.
  • Extends ElectronProtocol to serve packaged static assets (with SPA fallback, CSP, path traversal protection) when in client-only mode instead of proxying to a local backend.
  • Exposes getBackendModeState (sync) and setBackendMode IPC methods via preload.ts so the renderer can read and change mode; the UI prevents switching to client-only without a saved remote environment.
  • Refactors settings panels (General, Diagnostics, Keybindings, Source Control, Providers) to be environment-scoped, disabling server-backed controls when the selected environment is disconnected.
  • Adds isDesktopClientOnlyMode detection in the web layer so routing, connection source selection, and sidebar/thread logic treat client-only the same as the hosted static app.
  • Risk: switching backend mode triggers an app relaunch; if relaunch fails the settings change is reverted, but the UI will surface the error.

Macroscope summarized acb11a6.


Note

High Risk
Touches Electron startup/shutdown, custom protocol static file serving (path traversal mitigations), and preload fail-closed behavior that can suppress local backend until IPC is ready; large cross-cutting desktop + web surface.

Overview
Introduces managed vs client-only desktop backend mode (persisted settings, --backend-mode CLI override, IPC get/set). In client-only, startup skips the backend pool, registers Electron protocol as static packaged assets or dev proxy, opens the main window on renderer readiness, and does not stop backends on quit.

ElectronProtocol now uses source: "proxy" | "static" (drops backendOrigin); static mode adds safe path resolution, SPA fallback, MIME/CSP, and GET/HEAD handling. Connections exposes backend mode switching (relaunch); preload fails closed to client-only if mode IPC is missing.

Web UI shifts from always using the primary server atom to environment-scoped config: Providers/Add Provider, Diagnostics (selector + connection-aware loading), keybindings editor keys, and sidebar/command palette grouping use useDefaultServerConfig, EnvironmentPresenceScope, and ownsLocalEnvironment so client-only desktops do not assume a local primary backend.

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

@coderabbitai

coderabbitaiBot commented Jul 24, 2026

Copy link
Copy Markdown

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 Plus

Run ID: 76f5a485-2760-41bb-b19f-f63af805e9fe

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/server/src/serverRuntimeStartup.ts
Comment threadapps/web/src/environments/primary/target.ts
Comment threadapps/desktop/src/app/DesktopLocalServerDiscovery.ts Outdated
Comment threadapps/desktop/src/electron/ElectronProtocol.ts
@macroscopeapp

macroscopeappBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

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

@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from a0f2a4f to 9d6b3f5CompareJuly 24, 2026 17:00
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from 32f076d to 048f271CompareJuly 24, 2026 19:48
@colonelpanic8colonelpanic8 changed the title Add client-only desktop mode with local server discoveryAdd client-only desktop backend modeJul 24, 2026
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/desktop/src/preload.ts
Comment threadapps/desktop/src/app/DesktopApp.ts
Comment threadapps/desktop/src/ipc/methods/backendMode.ts
Comment threadapps/desktop/src/app/DesktopLifecycle.ts
t3dotggand others added 3 commits July 24, 2026 13:56
Providers settings were hardwired to the primary backend, so remote boxes
reached through T3 Connect or app.t3.codes could not be configured.
- List every registered execution environment and let one be selected
- Scope provider reads, settings writes, refreshes, updates, and instance
creation to the selected environment
- Gate controls on raw server config, connection phase, and operate scope
- Keep shared model preferences intact when removing per-device config
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review findings on the per-device providers panel.
- Treat unresolved primary session scopes as loading instead of editable, so
controls are never offered before permissions are known
- Render provider status rows for read-only sessions instead of replacing the
whole panel with a blocking message
- Distinguish a hydrating environment catalog from having no devices
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Extract resolvePrimaryOperateAccess so SWR revalidation no longer reports
pending while cached session data is available, which was unmounting the
provider editor and discarding in-progress edits
- Carry a reason on the loading state so waiting on permissions is not
described as waiting on device configuration
- Name read-only provider rows with deriveProviderInstanceEntries so multiple
instances of one driver stay distinguishable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch 2 times, most recently from 704649e to 20ba333CompareJuly 24, 2026 23:18
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
colonelpanic8and others added 2 commits July 27, 2026 19:37
Let the desktop app run as a pure client for remote or independently
managed backends, instead of always owning a local backend process.
- Add a persisted `managed` / `client-only` backend mode with a
launch-only `--backend-mode` override, exposed over IPC.
- In client-only mode, bootstrap skips the backend pool entirely and
serves the renderer from packaged static assets (or the dev proxy),
opening the window on renderer readiness rather than backend
readiness, and skipping backend shutdown on quit.
- `ElectronProtocol` takes an explicit `source: "proxy" | "static"`.
Static serving adds path-safe file resolution, SPA fallback, MIME
types, CSP and HEAD support.
- Web routing treats client-only like hosted static, and local
environment bootstraps are empty, so a client-only desktop is never
stranded on a dead primary-backend state.
Co-Authored-By: Claude <noreply@anthropic.com>
colonelpanic8and others added 11 commits July 27, 2026 19:37
The sidebar decided a thread or project was remote by comparing its
environment against the primary environment id. A client-only desktop
(and the hosted static web app) never registers a primary, so that id is
always null and every row read as local: no cloud/server icon, and no
environment name on the project header.
Make the comparison explicit about the two different meanings of a null
primary id. `isRemoteEnvironmentId` now takes an `ownsLocalEnvironment`
flag: when the app can never serve an environment from its own backend,
every environment is remote; when it can, a null primary id still means
"the local backend has not registered yet" and nothing is flagged remote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The diagnostics environment picker made three pieces of panel-local state
lie about which machine they belong to:
- `openLogsDirectoryError` / `isOpeningLogsDirectory` kept showing an
environment A failure (or spinner) after switching to environment B.
They are now stored together with the environment they belong to, so
they only render for that environment and a late completion can only
update its own request.
- `signalingPid` was a single shared value, so completing a signal on
environment A re-enabled the signal controls for an in-flight signal on
environment B and allowed a duplicate SIGKILL. Pending signals are now
identified by environment id plus pid.
- Diagnostics queries only run while the selected environment's
supervisor is connected, so a disconnected machine showed permanent
"Loading..." placeholders with the refresh buttons disabled. The panel
now derives a connection notice and renders it instead of the loading
state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the review feedback on the previous commit:
- `DiagnosticsRefreshButton` used a single `isPending` prop for both the
spinner and `disabled`, so suppressing the spinner while the selected
environment is disconnected also made the button clickable even though
the query cannot run. It now takes an explicit `isDisabled` prop.
- The open-logs completion matched on environment id alone, so returning
to an environment and starting a second open while the first was still
in flight let the older completion clear the newer pending state. The
completion now matches the request by identity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from f2af1bf to cda2160CompareJuly 28, 2026 02:41
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Jul 28, 2026
Comment threadapps/web/src/hooks/useDefaultServerConfig.ts
Comment threadapps/web/src/components/settings/ProviderSettingsPanel.tsx

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort 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 2fbd1f5. Configure here.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

Closing this PR after an automated pass over open pull requests. Introduces an alternate client-only desktop architecture across 81 files.

@t3dotggt3dotgg closed this Aug 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add client-only desktop backend mode - #4444

Closed
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery
Closed

Add client-only desktop backend mode#4444
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Consolidation update

This branch is rebased onto the latest main and now serves as the combined client/server-model stack:

What Changed

Let the desktop app run as a pure client for remote or independently managed backends, instead of always owning a local backend process.

  • Add a persisted desktop backend mode with managed and client-only options, plus a launch-only --backend-mode override, exposed over IPC.
  • In client-only, bootstrap skips the backend pool entirely and serves the renderer from packaged static assets (or the dev-server proxy), opens the window on renderer readiness rather than backend readiness, and skips backend shutdown on quit.
  • ElectronProtocol now takes an explicit source: "proxy" | "static" (dropping the separate backendOrigin). Static serving adds path-safe file resolution with SPA fallback, MIME types, CSP, and HEAD support.
  • Web routing treats client-only like hosted static, and local environment bootstraps are empty, so a client-only desktop is never stranded on a dead primary-backend state — it is routed to Connections instead.

Why

The desktop app should be usable as a pure client without stranding users who have no saved connections.

Scope note

This PR previously also contained Linux local t3 serve discovery. That half has been split into a separate PR so the two can be reviewed independently — the discovery work carries a distinct security surface (a live pairing credential on disk) and is Linux-only, whereas this change is cross-platform.

UI Changes

  • Connections gains a Desktop applicationBackend mode control.
  • Client-only users with no connections are routed to Connections rather than a dead primary-backend state.
  • Providers gains per-environment selection, scoped provider cards and controls, and explicit loading, offline, error, empty, editable, and read-only states from feat(web): per-device provider settings #4479.
  • General, Source Control, Keybindings, and Diagnostics use the same selected-environment model; Diagnostics includes connection-aware refresh and failure states.
  • Settings navigation distinguishes client-owned settings from environment-owned settings.

Packaged desktop evidence

Final privacy-reviewed evidence for this PR is pinned to immutable asset commit
d23321ea7d7200ef4d5e14f20dd2367997731706.

Capture context:

  • PR head: bb2c567e6879cf762910a02a8d20943edc2689ce
  • PR base: ece05087a70e94efcd57441337fa1249559362ba
  • Synthetic combined-stack integration: 5ba3b7bd0995f401993706689105f41f51c16b02
  • Exact GUI-tested package: /nix/store/86n9kxipsprgw6nbzyw7qhrw0wl4lfwx-t3code-0.0.29-patched-main-20260724

The packaged Electron app started with --backend-mode=client-only without
starting or owning a local backend, resolved its installed renderer assets,
and paired successfully to an independently started packaged t3 serve
backend. The saved environment then appeared online.

Client-only startup with no saved environment

Client-only startup with no saved environment

Connections settings and active CLI override

Connections settings showing client-only semantics and the active CLI override

Independent backend paired and online

Independently managed backend paired and online

Connected home state ·
capture manifest

After this GUI capture, the maintained stack advanced its upstream base to
41a430a88e8dde9c428f59d54dd328aa6a66a8fd. The intervening upstream delta
was isolated to #4472 model registration and did not overlap this desktop
change. The resulting final installed package
/nix/store/brp90sqhjxnnpczsnw9nmp1rlzi0qjgk-t3code-0.0.29-patched-main-20260724
built and activated successfully. The screenshots above remain specifically
attributable to the exact earlier package and synthetic integration listed
above; they are not represented as a GUI recapture of the later package.

Checklist

  • corepack pnpm exec vp check (0 errors; 11 pre-existing unrelated warnings)
  • corepack pnpm exec vp run --filter @t3tools/contracts --filter @t3tools/shared --filter t3 --filter @t3tools/desktop --filter @t3tools/web typecheck — clean
  • CI=true vitest run apps/desktop/src apps/web/src/components/settings apps/web/src/environments apps/server/src/startupAccess.test.ts — 63 files, 437 tests
  • Manually verified in an isolated Electron desktop: mode switch relaunches, client-only opens without a backend, packaged static renderer serves deep links, saved connections reconnect after restart

Note

Add client-only desktop backend mode that serves static assets without a local backend

  • Introduces a client-only backend mode for the desktop app, selectable via the Connections settings page or --backend-mode CLI flag; in this mode the desktop app skips local backend startup entirely.
  • Adds DesktopBackendMode service to parse, validate, and latch backend mode from settings and CLI; persists mode via DesktopAppSettings and triggers an app relaunch on change.
  • Extends ElectronProtocol to serve packaged static assets (with SPA fallback, CSP, path traversal protection) when in client-only mode instead of proxying to a local backend.
  • Exposes getBackendModeState (sync) and setBackendMode IPC methods via preload.ts so the renderer can read and change mode; the UI prevents switching to client-only without a saved remote environment.
  • Refactors settings panels (General, Diagnostics, Keybindings, Source Control, Providers) to be environment-scoped, disabling server-backed controls when the selected environment is disconnected.
  • Adds isDesktopClientOnlyMode detection in the web layer so routing, connection source selection, and sidebar/thread logic treat client-only the same as the hosted static app.
  • Risk: switching backend mode triggers an app relaunch; if relaunch fails the settings change is reverted, but the UI will surface the error.

Macroscope summarized acb11a6.


Note

High Risk
Touches Electron startup/shutdown, custom protocol static file serving (path traversal mitigations), and preload fail-closed behavior that can suppress local backend until IPC is ready; large cross-cutting desktop + web surface.

Overview
Introduces managed vs client-only desktop backend mode (persisted settings, --backend-mode CLI override, IPC get/set). In client-only, startup skips the backend pool, registers Electron protocol as static packaged assets or dev proxy, opens the main window on renderer readiness, and does not stop backends on quit.

ElectronProtocol now uses source: "proxy" | "static" (drops backendOrigin); static mode adds safe path resolution, SPA fallback, MIME/CSP, and GET/HEAD handling. Connections exposes backend mode switching (relaunch); preload fails closed to client-only if mode IPC is missing.

Web UI shifts from always using the primary server atom to environment-scoped config: Providers/Add Provider, Diagnostics (selector + connection-aware loading), keybindings editor keys, and sidebar/command palette grouping use useDefaultServerConfig, EnvironmentPresenceScope, and ownsLocalEnvironment so client-only desktops do not assume a local primary backend.

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

@coderabbitai

coderabbitaiBot commented Jul 24, 2026

Copy link
Copy Markdown

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 Plus

Run ID: 76f5a485-2760-41bb-b19f-f63af805e9fe

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/server/src/serverRuntimeStartup.ts
Comment threadapps/web/src/environments/primary/target.ts
Comment threadapps/desktop/src/app/DesktopLocalServerDiscovery.ts Outdated
Comment threadapps/desktop/src/electron/ElectronProtocol.ts
@macroscopeapp

macroscopeappBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

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

@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from a0f2a4f to 9d6b3f5CompareJuly 24, 2026 17:00
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from 32f076d to 048f271CompareJuly 24, 2026 19:48
@colonelpanic8colonelpanic8 changed the title Add client-only desktop mode with local server discoveryAdd client-only desktop backend modeJul 24, 2026
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/desktop/src/preload.ts
Comment threadapps/desktop/src/app/DesktopApp.ts
Comment threadapps/desktop/src/ipc/methods/backendMode.ts
Comment threadapps/desktop/src/app/DesktopLifecycle.ts
t3dotggand others added 3 commits July 24, 2026 13:56
Providers settings were hardwired to the primary backend, so remote boxes
reached through T3 Connect or app.t3.codes could not be configured.
- List every registered execution environment and let one be selected
- Scope provider reads, settings writes, refreshes, updates, and instance
creation to the selected environment
- Gate controls on raw server config, connection phase, and operate scope
- Keep shared model preferences intact when removing per-device config
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review findings on the per-device providers panel.
- Treat unresolved primary session scopes as loading instead of editable, so
controls are never offered before permissions are known
- Render provider status rows for read-only sessions instead of replacing the
whole panel with a blocking message
- Distinguish a hydrating environment catalog from having no devices
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Extract resolvePrimaryOperateAccess so SWR revalidation no longer reports
pending while cached session data is available, which was unmounting the
provider editor and discarding in-progress edits
- Carry a reason on the loading state so waiting on permissions is not
described as waiting on device configuration
- Name read-only provider rows with deriveProviderInstanceEntries so multiple
instances of one driver stay distinguishable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch 2 times, most recently from 704649e to 20ba333CompareJuly 24, 2026 23:18
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
colonelpanic8and others added 2 commits July 27, 2026 19:37
Let the desktop app run as a pure client for remote or independently
managed backends, instead of always owning a local backend process.
- Add a persisted `managed` / `client-only` backend mode with a
launch-only `--backend-mode` override, exposed over IPC.
- In client-only mode, bootstrap skips the backend pool entirely and
serves the renderer from packaged static assets (or the dev proxy),
opening the window on renderer readiness rather than backend
readiness, and skipping backend shutdown on quit.
- `ElectronProtocol` takes an explicit `source: "proxy" | "static"`.
Static serving adds path-safe file resolution, SPA fallback, MIME
types, CSP and HEAD support.
- Web routing treats client-only like hosted static, and local
environment bootstraps are empty, so a client-only desktop is never
stranded on a dead primary-backend state.
Co-Authored-By: Claude <noreply@anthropic.com>
colonelpanic8and others added 11 commits July 27, 2026 19:37
The sidebar decided a thread or project was remote by comparing its
environment against the primary environment id. A client-only desktop
(and the hosted static web app) never registers a primary, so that id is
always null and every row read as local: no cloud/server icon, and no
environment name on the project header.
Make the comparison explicit about the two different meanings of a null
primary id. `isRemoteEnvironmentId` now takes an `ownsLocalEnvironment`
flag: when the app can never serve an environment from its own backend,
every environment is remote; when it can, a null primary id still means
"the local backend has not registered yet" and nothing is flagged remote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The diagnostics environment picker made three pieces of panel-local state
lie about which machine they belong to:
- `openLogsDirectoryError` / `isOpeningLogsDirectory` kept showing an
environment A failure (or spinner) after switching to environment B.
They are now stored together with the environment they belong to, so
they only render for that environment and a late completion can only
update its own request.
- `signalingPid` was a single shared value, so completing a signal on
environment A re-enabled the signal controls for an in-flight signal on
environment B and allowed a duplicate SIGKILL. Pending signals are now
identified by environment id plus pid.
- Diagnostics queries only run while the selected environment's
supervisor is connected, so a disconnected machine showed permanent
"Loading..." placeholders with the refresh buttons disabled. The panel
now derives a connection notice and renders it instead of the loading
state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the review feedback on the previous commit:
- `DiagnosticsRefreshButton` used a single `isPending` prop for both the
spinner and `disabled`, so suppressing the spinner while the selected
environment is disconnected also made the button clickable even though
the query cannot run. It now takes an explicit `isDisabled` prop.
- The open-logs completion matched on environment id alone, so returning
to an environment and starting a second open while the first was still
in flight let the older completion clear the newer pending state. The
completion now matches the request by identity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from f2af1bf to cda2160CompareJuly 28, 2026 02:41
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Jul 28, 2026
Comment threadapps/web/src/hooks/useDefaultServerConfig.ts
Comment threadapps/web/src/components/settings/ProviderSettingsPanel.tsx

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort 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 2fbd1f5. Configure here.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

Closing this PR after an automated pass over open pull requests. Introduces an alternate client-only desktop architecture across 81 files.

@t3dotggt3dotgg closed this Aug 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add client-only desktop backend mode - #4444

Closed
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery
Closed

Add client-only desktop backend mode#4444
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Consolidation update

This branch is rebased onto the latest main and now serves as the combined client/server-model stack:

What Changed

Let the desktop app run as a pure client for remote or independently managed backends, instead of always owning a local backend process.

  • Add a persisted desktop backend mode with managed and client-only options, plus a launch-only --backend-mode override, exposed over IPC.
  • In client-only, bootstrap skips the backend pool entirely and serves the renderer from packaged static assets (or the dev-server proxy), opens the window on renderer readiness rather than backend readiness, and skips backend shutdown on quit.
  • ElectronProtocol now takes an explicit source: "proxy" | "static" (dropping the separate backendOrigin). Static serving adds path-safe file resolution with SPA fallback, MIME types, CSP, and HEAD support.
  • Web routing treats client-only like hosted static, and local environment bootstraps are empty, so a client-only desktop is never stranded on a dead primary-backend state — it is routed to Connections instead.

Why

The desktop app should be usable as a pure client without stranding users who have no saved connections.

Scope note

This PR previously also contained Linux local t3 serve discovery. That half has been split into a separate PR so the two can be reviewed independently — the discovery work carries a distinct security surface (a live pairing credential on disk) and is Linux-only, whereas this change is cross-platform.

UI Changes

  • Connections gains a Desktop applicationBackend mode control.
  • Client-only users with no connections are routed to Connections rather than a dead primary-backend state.
  • Providers gains per-environment selection, scoped provider cards and controls, and explicit loading, offline, error, empty, editable, and read-only states from feat(web): per-device provider settings #4479.
  • General, Source Control, Keybindings, and Diagnostics use the same selected-environment model; Diagnostics includes connection-aware refresh and failure states.
  • Settings navigation distinguishes client-owned settings from environment-owned settings.

Packaged desktop evidence

Final privacy-reviewed evidence for this PR is pinned to immutable asset commit
d23321ea7d7200ef4d5e14f20dd2367997731706.

Capture context:

  • PR head: bb2c567e6879cf762910a02a8d20943edc2689ce
  • PR base: ece05087a70e94efcd57441337fa1249559362ba
  • Synthetic combined-stack integration: 5ba3b7bd0995f401993706689105f41f51c16b02
  • Exact GUI-tested package: /nix/store/86n9kxipsprgw6nbzyw7qhrw0wl4lfwx-t3code-0.0.29-patched-main-20260724

The packaged Electron app started with --backend-mode=client-only without
starting or owning a local backend, resolved its installed renderer assets,
and paired successfully to an independently started packaged t3 serve
backend. The saved environment then appeared online.

Client-only startup with no saved environment

Client-only startup with no saved environment

Connections settings and active CLI override

Connections settings showing client-only semantics and the active CLI override

Independent backend paired and online

Independently managed backend paired and online

Connected home state ·
capture manifest

After this GUI capture, the maintained stack advanced its upstream base to
41a430a88e8dde9c428f59d54dd328aa6a66a8fd. The intervening upstream delta
was isolated to #4472 model registration and did not overlap this desktop
change. The resulting final installed package
/nix/store/brp90sqhjxnnpczsnw9nmp1rlzi0qjgk-t3code-0.0.29-patched-main-20260724
built and activated successfully. The screenshots above remain specifically
attributable to the exact earlier package and synthetic integration listed
above; they are not represented as a GUI recapture of the later package.

Checklist

  • corepack pnpm exec vp check (0 errors; 11 pre-existing unrelated warnings)
  • corepack pnpm exec vp run --filter @t3tools/contracts --filter @t3tools/shared --filter t3 --filter @t3tools/desktop --filter @t3tools/web typecheck — clean
  • CI=true vitest run apps/desktop/src apps/web/src/components/settings apps/web/src/environments apps/server/src/startupAccess.test.ts — 63 files, 437 tests
  • Manually verified in an isolated Electron desktop: mode switch relaunches, client-only opens without a backend, packaged static renderer serves deep links, saved connections reconnect after restart

Note

Add client-only desktop backend mode that serves static assets without a local backend

  • Introduces a client-only backend mode for the desktop app, selectable via the Connections settings page or --backend-mode CLI flag; in this mode the desktop app skips local backend startup entirely.
  • Adds DesktopBackendMode service to parse, validate, and latch backend mode from settings and CLI; persists mode via DesktopAppSettings and triggers an app relaunch on change.
  • Extends ElectronProtocol to serve packaged static assets (with SPA fallback, CSP, path traversal protection) when in client-only mode instead of proxying to a local backend.
  • Exposes getBackendModeState (sync) and setBackendMode IPC methods via preload.ts so the renderer can read and change mode; the UI prevents switching to client-only without a saved remote environment.
  • Refactors settings panels (General, Diagnostics, Keybindings, Source Control, Providers) to be environment-scoped, disabling server-backed controls when the selected environment is disconnected.
  • Adds isDesktopClientOnlyMode detection in the web layer so routing, connection source selection, and sidebar/thread logic treat client-only the same as the hosted static app.
  • Risk: switching backend mode triggers an app relaunch; if relaunch fails the settings change is reverted, but the UI will surface the error.

Macroscope summarized acb11a6.


Note

High Risk
Touches Electron startup/shutdown, custom protocol static file serving (path traversal mitigations), and preload fail-closed behavior that can suppress local backend until IPC is ready; large cross-cutting desktop + web surface.

Overview
Introduces managed vs client-only desktop backend mode (persisted settings, --backend-mode CLI override, IPC get/set). In client-only, startup skips the backend pool, registers Electron protocol as static packaged assets or dev proxy, opens the main window on renderer readiness, and does not stop backends on quit.

ElectronProtocol now uses source: "proxy" | "static" (drops backendOrigin); static mode adds safe path resolution, SPA fallback, MIME/CSP, and GET/HEAD handling. Connections exposes backend mode switching (relaunch); preload fails closed to client-only if mode IPC is missing.

Web UI shifts from always using the primary server atom to environment-scoped config: Providers/Add Provider, Diagnostics (selector + connection-aware loading), keybindings editor keys, and sidebar/command palette grouping use useDefaultServerConfig, EnvironmentPresenceScope, and ownsLocalEnvironment so client-only desktops do not assume a local primary backend.

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

@coderabbitai

coderabbitaiBot commented Jul 24, 2026

Copy link
Copy Markdown

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 Plus

Run ID: 76f5a485-2760-41bb-b19f-f63af805e9fe

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/server/src/serverRuntimeStartup.ts
Comment threadapps/web/src/environments/primary/target.ts
Comment threadapps/desktop/src/app/DesktopLocalServerDiscovery.ts Outdated
Comment threadapps/desktop/src/electron/ElectronProtocol.ts
@macroscopeapp

macroscopeappBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

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

@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from a0f2a4f to 9d6b3f5CompareJuly 24, 2026 17:00
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from 32f076d to 048f271CompareJuly 24, 2026 19:48
@colonelpanic8colonelpanic8 changed the title Add client-only desktop mode with local server discoveryAdd client-only desktop backend modeJul 24, 2026
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/desktop/src/preload.ts
Comment threadapps/desktop/src/app/DesktopApp.ts
Comment threadapps/desktop/src/ipc/methods/backendMode.ts
Comment threadapps/desktop/src/app/DesktopLifecycle.ts
t3dotggand others added 3 commits July 24, 2026 13:56
Providers settings were hardwired to the primary backend, so remote boxes
reached through T3 Connect or app.t3.codes could not be configured.
- List every registered execution environment and let one be selected
- Scope provider reads, settings writes, refreshes, updates, and instance
creation to the selected environment
- Gate controls on raw server config, connection phase, and operate scope
- Keep shared model preferences intact when removing per-device config
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review findings on the per-device providers panel.
- Treat unresolved primary session scopes as loading instead of editable, so
controls are never offered before permissions are known
- Render provider status rows for read-only sessions instead of replacing the
whole panel with a blocking message
- Distinguish a hydrating environment catalog from having no devices
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Extract resolvePrimaryOperateAccess so SWR revalidation no longer reports
pending while cached session data is available, which was unmounting the
provider editor and discarding in-progress edits
- Carry a reason on the loading state so waiting on permissions is not
described as waiting on device configuration
- Name read-only provider rows with deriveProviderInstanceEntries so multiple
instances of one driver stay distinguishable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch 2 times, most recently from 704649e to 20ba333CompareJuly 24, 2026 23:18
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
colonelpanic8and others added 2 commits July 27, 2026 19:37
Let the desktop app run as a pure client for remote or independently
managed backends, instead of always owning a local backend process.
- Add a persisted `managed` / `client-only` backend mode with a
launch-only `--backend-mode` override, exposed over IPC.
- In client-only mode, bootstrap skips the backend pool entirely and
serves the renderer from packaged static assets (or the dev proxy),
opening the window on renderer readiness rather than backend
readiness, and skipping backend shutdown on quit.
- `ElectronProtocol` takes an explicit `source: "proxy" | "static"`.
Static serving adds path-safe file resolution, SPA fallback, MIME
types, CSP and HEAD support.
- Web routing treats client-only like hosted static, and local
environment bootstraps are empty, so a client-only desktop is never
stranded on a dead primary-backend state.
Co-Authored-By: Claude <noreply@anthropic.com>
colonelpanic8and others added 11 commits July 27, 2026 19:37
The sidebar decided a thread or project was remote by comparing its
environment against the primary environment id. A client-only desktop
(and the hosted static web app) never registers a primary, so that id is
always null and every row read as local: no cloud/server icon, and no
environment name on the project header.
Make the comparison explicit about the two different meanings of a null
primary id. `isRemoteEnvironmentId` now takes an `ownsLocalEnvironment`
flag: when the app can never serve an environment from its own backend,
every environment is remote; when it can, a null primary id still means
"the local backend has not registered yet" and nothing is flagged remote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The diagnostics environment picker made three pieces of panel-local state
lie about which machine they belong to:
- `openLogsDirectoryError` / `isOpeningLogsDirectory` kept showing an
environment A failure (or spinner) after switching to environment B.
They are now stored together with the environment they belong to, so
they only render for that environment and a late completion can only
update its own request.
- `signalingPid` was a single shared value, so completing a signal on
environment A re-enabled the signal controls for an in-flight signal on
environment B and allowed a duplicate SIGKILL. Pending signals are now
identified by environment id plus pid.
- Diagnostics queries only run while the selected environment's
supervisor is connected, so a disconnected machine showed permanent
"Loading..." placeholders with the refresh buttons disabled. The panel
now derives a connection notice and renders it instead of the loading
state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the review feedback on the previous commit:
- `DiagnosticsRefreshButton` used a single `isPending` prop for both the
spinner and `disabled`, so suppressing the spinner while the selected
environment is disconnected also made the button clickable even though
the query cannot run. It now takes an explicit `isDisabled` prop.
- The open-logs completion matched on environment id alone, so returning
to an environment and starting a second open while the first was still
in flight let the older completion clear the newer pending state. The
completion now matches the request by identity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from f2af1bf to cda2160CompareJuly 28, 2026 02:41
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Jul 28, 2026
Comment threadapps/web/src/hooks/useDefaultServerConfig.ts
Comment threadapps/web/src/components/settings/ProviderSettingsPanel.tsx

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort 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 2fbd1f5. Configure here.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

Closing this PR after an automated pass over open pull requests. Introduces an alternate client-only desktop architecture across 81 files.

@t3dotggt3dotgg closed this Aug 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add client-only desktop backend mode - #4444

Closed
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery
Closed

Add client-only desktop backend mode#4444
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Consolidation update

This branch is rebased onto the latest main and now serves as the combined client/server-model stack:

What Changed

Let the desktop app run as a pure client for remote or independently managed backends, instead of always owning a local backend process.

  • Add a persisted desktop backend mode with managed and client-only options, plus a launch-only --backend-mode override, exposed over IPC.
  • In client-only, bootstrap skips the backend pool entirely and serves the renderer from packaged static assets (or the dev-server proxy), opens the window on renderer readiness rather than backend readiness, and skips backend shutdown on quit.
  • ElectronProtocol now takes an explicit source: "proxy" | "static" (dropping the separate backendOrigin). Static serving adds path-safe file resolution with SPA fallback, MIME types, CSP, and HEAD support.
  • Web routing treats client-only like hosted static, and local environment bootstraps are empty, so a client-only desktop is never stranded on a dead primary-backend state — it is routed to Connections instead.

Why

The desktop app should be usable as a pure client without stranding users who have no saved connections.

Scope note

This PR previously also contained Linux local t3 serve discovery. That half has been split into a separate PR so the two can be reviewed independently — the discovery work carries a distinct security surface (a live pairing credential on disk) and is Linux-only, whereas this change is cross-platform.

UI Changes

  • Connections gains a Desktop applicationBackend mode control.
  • Client-only users with no connections are routed to Connections rather than a dead primary-backend state.
  • Providers gains per-environment selection, scoped provider cards and controls, and explicit loading, offline, error, empty, editable, and read-only states from feat(web): per-device provider settings #4479.
  • General, Source Control, Keybindings, and Diagnostics use the same selected-environment model; Diagnostics includes connection-aware refresh and failure states.
  • Settings navigation distinguishes client-owned settings from environment-owned settings.

Packaged desktop evidence

Final privacy-reviewed evidence for this PR is pinned to immutable asset commit
d23321ea7d7200ef4d5e14f20dd2367997731706.

Capture context:

  • PR head: bb2c567e6879cf762910a02a8d20943edc2689ce
  • PR base: ece05087a70e94efcd57441337fa1249559362ba
  • Synthetic combined-stack integration: 5ba3b7bd0995f401993706689105f41f51c16b02
  • Exact GUI-tested package: /nix/store/86n9kxipsprgw6nbzyw7qhrw0wl4lfwx-t3code-0.0.29-patched-main-20260724

The packaged Electron app started with --backend-mode=client-only without
starting or owning a local backend, resolved its installed renderer assets,
and paired successfully to an independently started packaged t3 serve
backend. The saved environment then appeared online.

Client-only startup with no saved environment

Client-only startup with no saved environment

Connections settings and active CLI override

Connections settings showing client-only semantics and the active CLI override

Independent backend paired and online

Independently managed backend paired and online

Connected home state ·
capture manifest

After this GUI capture, the maintained stack advanced its upstream base to
41a430a88e8dde9c428f59d54dd328aa6a66a8fd. The intervening upstream delta
was isolated to #4472 model registration and did not overlap this desktop
change. The resulting final installed package
/nix/store/brp90sqhjxnnpczsnw9nmp1rlzi0qjgk-t3code-0.0.29-patched-main-20260724
built and activated successfully. The screenshots above remain specifically
attributable to the exact earlier package and synthetic integration listed
above; they are not represented as a GUI recapture of the later package.

Checklist

  • corepack pnpm exec vp check (0 errors; 11 pre-existing unrelated warnings)
  • corepack pnpm exec vp run --filter @t3tools/contracts --filter @t3tools/shared --filter t3 --filter @t3tools/desktop --filter @t3tools/web typecheck — clean
  • CI=true vitest run apps/desktop/src apps/web/src/components/settings apps/web/src/environments apps/server/src/startupAccess.test.ts — 63 files, 437 tests
  • Manually verified in an isolated Electron desktop: mode switch relaunches, client-only opens without a backend, packaged static renderer serves deep links, saved connections reconnect after restart

Note

Add client-only desktop backend mode that serves static assets without a local backend

  • Introduces a client-only backend mode for the desktop app, selectable via the Connections settings page or --backend-mode CLI flag; in this mode the desktop app skips local backend startup entirely.
  • Adds DesktopBackendMode service to parse, validate, and latch backend mode from settings and CLI; persists mode via DesktopAppSettings and triggers an app relaunch on change.
  • Extends ElectronProtocol to serve packaged static assets (with SPA fallback, CSP, path traversal protection) when in client-only mode instead of proxying to a local backend.
  • Exposes getBackendModeState (sync) and setBackendMode IPC methods via preload.ts so the renderer can read and change mode; the UI prevents switching to client-only without a saved remote environment.
  • Refactors settings panels (General, Diagnostics, Keybindings, Source Control, Providers) to be environment-scoped, disabling server-backed controls when the selected environment is disconnected.
  • Adds isDesktopClientOnlyMode detection in the web layer so routing, connection source selection, and sidebar/thread logic treat client-only the same as the hosted static app.
  • Risk: switching backend mode triggers an app relaunch; if relaunch fails the settings change is reverted, but the UI will surface the error.

Macroscope summarized acb11a6.


Note

High Risk
Touches Electron startup/shutdown, custom protocol static file serving (path traversal mitigations), and preload fail-closed behavior that can suppress local backend until IPC is ready; large cross-cutting desktop + web surface.

Overview
Introduces managed vs client-only desktop backend mode (persisted settings, --backend-mode CLI override, IPC get/set). In client-only, startup skips the backend pool, registers Electron protocol as static packaged assets or dev proxy, opens the main window on renderer readiness, and does not stop backends on quit.

ElectronProtocol now uses source: "proxy" | "static" (drops backendOrigin); static mode adds safe path resolution, SPA fallback, MIME/CSP, and GET/HEAD handling. Connections exposes backend mode switching (relaunch); preload fails closed to client-only if mode IPC is missing.

Web UI shifts from always using the primary server atom to environment-scoped config: Providers/Add Provider, Diagnostics (selector + connection-aware loading), keybindings editor keys, and sidebar/command palette grouping use useDefaultServerConfig, EnvironmentPresenceScope, and ownsLocalEnvironment so client-only desktops do not assume a local primary backend.

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

@coderabbitai

coderabbitaiBot commented Jul 24, 2026

Copy link
Copy Markdown

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 Plus

Run ID: 76f5a485-2760-41bb-b19f-f63af805e9fe

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/server/src/serverRuntimeStartup.ts
Comment threadapps/web/src/environments/primary/target.ts
Comment threadapps/desktop/src/app/DesktopLocalServerDiscovery.ts Outdated
Comment threadapps/desktop/src/electron/ElectronProtocol.ts
@macroscopeapp

macroscopeappBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

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

@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from a0f2a4f to 9d6b3f5CompareJuly 24, 2026 17:00
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from 32f076d to 048f271CompareJuly 24, 2026 19:48
@colonelpanic8colonelpanic8 changed the title Add client-only desktop mode with local server discoveryAdd client-only desktop backend modeJul 24, 2026
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/desktop/src/preload.ts
Comment threadapps/desktop/src/app/DesktopApp.ts
Comment threadapps/desktop/src/ipc/methods/backendMode.ts
Comment threadapps/desktop/src/app/DesktopLifecycle.ts
t3dotggand others added 3 commits July 24, 2026 13:56
Providers settings were hardwired to the primary backend, so remote boxes
reached through T3 Connect or app.t3.codes could not be configured.
- List every registered execution environment and let one be selected
- Scope provider reads, settings writes, refreshes, updates, and instance
creation to the selected environment
- Gate controls on raw server config, connection phase, and operate scope
- Keep shared model preferences intact when removing per-device config
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review findings on the per-device providers panel.
- Treat unresolved primary session scopes as loading instead of editable, so
controls are never offered before permissions are known
- Render provider status rows for read-only sessions instead of replacing the
whole panel with a blocking message
- Distinguish a hydrating environment catalog from having no devices
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Extract resolvePrimaryOperateAccess so SWR revalidation no longer reports
pending while cached session data is available, which was unmounting the
provider editor and discarding in-progress edits
- Carry a reason on the loading state so waiting on permissions is not
described as waiting on device configuration
- Name read-only provider rows with deriveProviderInstanceEntries so multiple
instances of one driver stay distinguishable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch 2 times, most recently from 704649e to 20ba333CompareJuly 24, 2026 23:18
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
colonelpanic8and others added 2 commits July 27, 2026 19:37
Let the desktop app run as a pure client for remote or independently
managed backends, instead of always owning a local backend process.
- Add a persisted `managed` / `client-only` backend mode with a
launch-only `--backend-mode` override, exposed over IPC.
- In client-only mode, bootstrap skips the backend pool entirely and
serves the renderer from packaged static assets (or the dev proxy),
opening the window on renderer readiness rather than backend
readiness, and skipping backend shutdown on quit.
- `ElectronProtocol` takes an explicit `source: "proxy" | "static"`.
Static serving adds path-safe file resolution, SPA fallback, MIME
types, CSP and HEAD support.
- Web routing treats client-only like hosted static, and local
environment bootstraps are empty, so a client-only desktop is never
stranded on a dead primary-backend state.
Co-Authored-By: Claude <noreply@anthropic.com>
colonelpanic8and others added 11 commits July 27, 2026 19:37
The sidebar decided a thread or project was remote by comparing its
environment against the primary environment id. A client-only desktop
(and the hosted static web app) never registers a primary, so that id is
always null and every row read as local: no cloud/server icon, and no
environment name on the project header.
Make the comparison explicit about the two different meanings of a null
primary id. `isRemoteEnvironmentId` now takes an `ownsLocalEnvironment`
flag: when the app can never serve an environment from its own backend,
every environment is remote; when it can, a null primary id still means
"the local backend has not registered yet" and nothing is flagged remote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The diagnostics environment picker made three pieces of panel-local state
lie about which machine they belong to:
- `openLogsDirectoryError` / `isOpeningLogsDirectory` kept showing an
environment A failure (or spinner) after switching to environment B.
They are now stored together with the environment they belong to, so
they only render for that environment and a late completion can only
update its own request.
- `signalingPid` was a single shared value, so completing a signal on
environment A re-enabled the signal controls for an in-flight signal on
environment B and allowed a duplicate SIGKILL. Pending signals are now
identified by environment id plus pid.
- Diagnostics queries only run while the selected environment's
supervisor is connected, so a disconnected machine showed permanent
"Loading..." placeholders with the refresh buttons disabled. The panel
now derives a connection notice and renders it instead of the loading
state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the review feedback on the previous commit:
- `DiagnosticsRefreshButton` used a single `isPending` prop for both the
spinner and `disabled`, so suppressing the spinner while the selected
environment is disconnected also made the button clickable even though
the query cannot run. It now takes an explicit `isDisabled` prop.
- The open-logs completion matched on environment id alone, so returning
to an environment and starting a second open while the first was still
in flight let the older completion clear the newer pending state. The
completion now matches the request by identity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from f2af1bf to cda2160CompareJuly 28, 2026 02:41
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Jul 28, 2026
Comment threadapps/web/src/hooks/useDefaultServerConfig.ts
Comment threadapps/web/src/components/settings/ProviderSettingsPanel.tsx

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort 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 2fbd1f5. Configure here.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

Closing this PR after an automated pass over open pull requests. Introduces an alternate client-only desktop architecture across 81 files.

@t3dotggt3dotgg closed this Aug 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add client-only desktop backend mode - #4444

Closed
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery
Closed

Add client-only desktop backend mode#4444
colonelpanic8 wants to merge 18 commits into
pingdotgg:mainfrom
colonelpanic8:desktop-client-only-discovery

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Consolidation update

This branch is rebased onto the latest main and now serves as the combined client/server-model stack:

What Changed

Let the desktop app run as a pure client for remote or independently managed backends, instead of always owning a local backend process.

  • Add a persisted desktop backend mode with managed and client-only options, plus a launch-only --backend-mode override, exposed over IPC.
  • In client-only, bootstrap skips the backend pool entirely and serves the renderer from packaged static assets (or the dev-server proxy), opens the window on renderer readiness rather than backend readiness, and skips backend shutdown on quit.
  • ElectronProtocol now takes an explicit source: "proxy" | "static" (dropping the separate backendOrigin). Static serving adds path-safe file resolution with SPA fallback, MIME types, CSP, and HEAD support.
  • Web routing treats client-only like hosted static, and local environment bootstraps are empty, so a client-only desktop is never stranded on a dead primary-backend state — it is routed to Connections instead.

Why

The desktop app should be usable as a pure client without stranding users who have no saved connections.

Scope note

This PR previously also contained Linux local t3 serve discovery. That half has been split into a separate PR so the two can be reviewed independently — the discovery work carries a distinct security surface (a live pairing credential on disk) and is Linux-only, whereas this change is cross-platform.

UI Changes

  • Connections gains a Desktop applicationBackend mode control.
  • Client-only users with no connections are routed to Connections rather than a dead primary-backend state.
  • Providers gains per-environment selection, scoped provider cards and controls, and explicit loading, offline, error, empty, editable, and read-only states from feat(web): per-device provider settings #4479.
  • General, Source Control, Keybindings, and Diagnostics use the same selected-environment model; Diagnostics includes connection-aware refresh and failure states.
  • Settings navigation distinguishes client-owned settings from environment-owned settings.

Packaged desktop evidence

Final privacy-reviewed evidence for this PR is pinned to immutable asset commit
d23321ea7d7200ef4d5e14f20dd2367997731706.

Capture context:

  • PR head: bb2c567e6879cf762910a02a8d20943edc2689ce
  • PR base: ece05087a70e94efcd57441337fa1249559362ba
  • Synthetic combined-stack integration: 5ba3b7bd0995f401993706689105f41f51c16b02
  • Exact GUI-tested package: /nix/store/86n9kxipsprgw6nbzyw7qhrw0wl4lfwx-t3code-0.0.29-patched-main-20260724

The packaged Electron app started with --backend-mode=client-only without
starting or owning a local backend, resolved its installed renderer assets,
and paired successfully to an independently started packaged t3 serve
backend. The saved environment then appeared online.

Client-only startup with no saved environment

Client-only startup with no saved environment

Connections settings and active CLI override

Connections settings showing client-only semantics and the active CLI override

Independent backend paired and online

Independently managed backend paired and online

Connected home state ·
capture manifest

After this GUI capture, the maintained stack advanced its upstream base to
41a430a88e8dde9c428f59d54dd328aa6a66a8fd. The intervening upstream delta
was isolated to #4472 model registration and did not overlap this desktop
change. The resulting final installed package
/nix/store/brp90sqhjxnnpczsnw9nmp1rlzi0qjgk-t3code-0.0.29-patched-main-20260724
built and activated successfully. The screenshots above remain specifically
attributable to the exact earlier package and synthetic integration listed
above; they are not represented as a GUI recapture of the later package.

Checklist

  • corepack pnpm exec vp check (0 errors; 11 pre-existing unrelated warnings)
  • corepack pnpm exec vp run --filter @t3tools/contracts --filter @t3tools/shared --filter t3 --filter @t3tools/desktop --filter @t3tools/web typecheck — clean
  • CI=true vitest run apps/desktop/src apps/web/src/components/settings apps/web/src/environments apps/server/src/startupAccess.test.ts — 63 files, 437 tests
  • Manually verified in an isolated Electron desktop: mode switch relaunches, client-only opens without a backend, packaged static renderer serves deep links, saved connections reconnect after restart

Note

Add client-only desktop backend mode that serves static assets without a local backend

  • Introduces a client-only backend mode for the desktop app, selectable via the Connections settings page or --backend-mode CLI flag; in this mode the desktop app skips local backend startup entirely.
  • Adds DesktopBackendMode service to parse, validate, and latch backend mode from settings and CLI; persists mode via DesktopAppSettings and triggers an app relaunch on change.
  • Extends ElectronProtocol to serve packaged static assets (with SPA fallback, CSP, path traversal protection) when in client-only mode instead of proxying to a local backend.
  • Exposes getBackendModeState (sync) and setBackendMode IPC methods via preload.ts so the renderer can read and change mode; the UI prevents switching to client-only without a saved remote environment.
  • Refactors settings panels (General, Diagnostics, Keybindings, Source Control, Providers) to be environment-scoped, disabling server-backed controls when the selected environment is disconnected.
  • Adds isDesktopClientOnlyMode detection in the web layer so routing, connection source selection, and sidebar/thread logic treat client-only the same as the hosted static app.
  • Risk: switching backend mode triggers an app relaunch; if relaunch fails the settings change is reverted, but the UI will surface the error.

Macroscope summarized acb11a6.


Note

High Risk
Touches Electron startup/shutdown, custom protocol static file serving (path traversal mitigations), and preload fail-closed behavior that can suppress local backend until IPC is ready; large cross-cutting desktop + web surface.

Overview
Introduces managed vs client-only desktop backend mode (persisted settings, --backend-mode CLI override, IPC get/set). In client-only, startup skips the backend pool, registers Electron protocol as static packaged assets or dev proxy, opens the main window on renderer readiness, and does not stop backends on quit.

ElectronProtocol now uses source: "proxy" | "static" (drops backendOrigin); static mode adds safe path resolution, SPA fallback, MIME/CSP, and GET/HEAD handling. Connections exposes backend mode switching (relaunch); preload fails closed to client-only if mode IPC is missing.

Web UI shifts from always using the primary server atom to environment-scoped config: Providers/Add Provider, Diagnostics (selector + connection-aware loading), keybindings editor keys, and sidebar/command palette grouping use useDefaultServerConfig, EnvironmentPresenceScope, and ownsLocalEnvironment so client-only desktops do not assume a local primary backend.

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

@coderabbitai

coderabbitaiBot commented Jul 24, 2026

Copy link
Copy Markdown

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 Plus

Run ID: 76f5a485-2760-41bb-b19f-f63af805e9fe

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.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/server/src/serverRuntimeStartup.ts
Comment threadapps/web/src/environments/primary/target.ts
Comment threadapps/desktop/src/app/DesktopLocalServerDiscovery.ts Outdated
Comment threadapps/desktop/src/electron/ElectronProtocol.ts
@macroscopeapp

macroscopeappBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

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

@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from a0f2a4f to 9d6b3f5CompareJuly 24, 2026 17:00
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
Comment threadapps/server/src/localServerAdvertisement.ts Outdated
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from 32f076d to 048f271CompareJuly 24, 2026 19:48
@colonelpanic8colonelpanic8 changed the title Add client-only desktop mode with local server discoveryAdd client-only desktop backend modeJul 24, 2026
@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment threadapps/desktop/src/preload.ts
Comment threadapps/desktop/src/app/DesktopApp.ts
Comment threadapps/desktop/src/ipc/methods/backendMode.ts
Comment threadapps/desktop/src/app/DesktopLifecycle.ts
t3dotggand others added 3 commits July 24, 2026 13:56
Providers settings were hardwired to the primary backend, so remote boxes
reached through T3 Connect or app.t3.codes could not be configured.
- List every registered execution environment and let one be selected
- Scope provider reads, settings writes, refreshes, updates, and instance
creation to the selected environment
- Gate controls on raw server config, connection phase, and operate scope
- Keep shared model preferences intact when removing per-device config
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review findings on the per-device providers panel.
- Treat unresolved primary session scopes as loading instead of editable, so
controls are never offered before permissions are known
- Render provider status rows for read-only sessions instead of replacing the
whole panel with a blocking message
- Distinguish a hydrating environment catalog from having no devices
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Extract resolvePrimaryOperateAccess so SWR revalidation no longer reports
pending while cached session data is available, which was unmounting the
provider editor and discarding in-progress edits
- Carry a reason on the loading state so waiting on permissions is not
described as waiting on device configuration
- Name read-only provider rows with deriveProviderInstanceEntries so multiple
instances of one driver stay distinguishable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch 2 times, most recently from 704649e to 20ba333CompareJuly 24, 2026 23:18
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
colonelpanic8and others added 2 commits July 27, 2026 19:37
Let the desktop app run as a pure client for remote or independently
managed backends, instead of always owning a local backend process.
- Add a persisted `managed` / `client-only` backend mode with a
launch-only `--backend-mode` override, exposed over IPC.
- In client-only mode, bootstrap skips the backend pool entirely and
serves the renderer from packaged static assets (or the dev proxy),
opening the window on renderer readiness rather than backend
readiness, and skipping backend shutdown on quit.
- `ElectronProtocol` takes an explicit `source: "proxy" | "static"`.
Static serving adds path-safe file resolution, SPA fallback, MIME
types, CSP and HEAD support.
- Web routing treats client-only like hosted static, and local
environment bootstraps are empty, so a client-only desktop is never
stranded on a dead primary-backend state.
Co-Authored-By: Claude <noreply@anthropic.com>
colonelpanic8and others added 11 commits July 27, 2026 19:37
The sidebar decided a thread or project was remote by comparing its
environment against the primary environment id. A client-only desktop
(and the hosted static web app) never registers a primary, so that id is
always null and every row read as local: no cloud/server icon, and no
environment name on the project header.
Make the comparison explicit about the two different meanings of a null
primary id. `isRemoteEnvironmentId` now takes an `ownsLocalEnvironment`
flag: when the app can never serve an environment from its own backend,
every environment is remote; when it can, a null primary id still means
"the local backend has not registered yet" and nothing is flagged remote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The diagnostics environment picker made three pieces of panel-local state
lie about which machine they belong to:
- `openLogsDirectoryError` / `isOpeningLogsDirectory` kept showing an
environment A failure (or spinner) after switching to environment B.
They are now stored together with the environment they belong to, so
they only render for that environment and a late completion can only
update its own request.
- `signalingPid` was a single shared value, so completing a signal on
environment A re-enabled the signal controls for an in-flight signal on
environment B and allowed a duplicate SIGKILL. Pending signals are now
identified by environment id plus pid.
- Diagnostics queries only run while the selected environment's
supervisor is connected, so a disconnected machine showed permanent
"Loading..." placeholders with the refresh buttons disabled. The panel
now derives a connection notice and renders it instead of the loading
state.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the review feedback on the previous commit:
- `DiagnosticsRefreshButton` used a single `isPending` prop for both the
spinner and `disabled`, so suppressing the spinner while the selected
environment is disconnected also made the button clickable even though
the query cannot run. It now takes an explicit `isDisabled` prop.
- The open-logs completion matched on environment id alone, so returning
to an environment and starting a second open while the first was still
in flight let the older completion clear the newer pending state. The
completion now matches the request by identity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the desktop-client-only-discovery branch from f2af1bf to cda2160CompareJuly 28, 2026 02:41
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Jul 28, 2026
Comment threadapps/web/src/hooks/useDefaultServerConfig.ts
Comment threadapps/web/src/components/settings/ProviderSettingsPanel.tsx

@cursorcursorBot 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.

Cursor Bugbot has reviewed your changes using high effort 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 2fbd1f5. Configure here.

@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

Closing this PR after an automated pass over open pull requests. Introduces an alternate client-only desktop architecture across 81 files.

@t3dotggt3dotgg closed this Aug 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg