port desktop app to Effect - #2546

Merged
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3
May 8, 2026
Merged

port desktop app to Effect#2546
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Reworked desktop backend readiness probing around Effect-based services and effects, with temporary promise shims preserved for existing call sites.
  • Added Effect/Vitest coverage for readiness polling, request timeouts, abort handling, and backend startup race behavior.
  • Updated the desktop main process to use typed URL handling for backend endpoints and the new readiness error type.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test for apps/desktop/src/backendReadiness.test.ts and apps/desktop/src/backendStartupReadiness.test.ts

Note

High Risk
Large refactor of the Electron desktop main process, backend spawning/readiness, and lifecycle handling; mistakes could prevent the app from starting or shut down cleanly. Also changes runtime configuration/paths and protocol handling, which can impact packaging and local file access.

Overview
Ports the desktop Electron main process to an Effect-based architecture, introducing layered services for app identity/environment/config, lifecycle/shutdown coordination, observability/tracing, and Electron wrappers (app/dialog/menu/protocol).

Reworks desktop backend startup into Effect-managed spawning with fd-based bootstrap payloads, HTTP readiness polling, structured child-process output logging, and automatic restart scheduling/cancellation logic.

Cleans up legacy promise/imperative utilities and tests (e.g., backendPort, readiness helpers, settings/persistence/dialog helpers) in favor of new Effect/Vitest test coverage, and updates build hygiene (.gitignore consolidation) plus bumps electron to 41.5.0.

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

Note

Port desktop app to Effect by replacing imperative Electron code with layered Effect-TS services

  • Rewrites main.ts from imperative Electron startup code to a declarative Effect-TS layer composition, using NodeRuntime.runMain to execute a DesktopApp.program with all services provided via layers.
  • Introduces a large set of new Effect-based Electron service wrappers: ElectronApp, ElectronDialog, ElectronMenu, ElectronProtocol, ElectronSafeStorage, ElectronShell, ElectronTheme, ElectronUpdater, and ElectronWindow, each with typed errors and scoped resource management.
  • Adds desktop domain services (DesktopEnvironment, DesktopAppSettings, DesktopClientSettings, DesktopSavedEnvironments, DesktopBackendManager, DesktopBackendConfiguration, DesktopServerExposure, DesktopSshEnvironment, DesktopUpdates, DesktopWindow, etc.) as injectable Effect layers.
  • Moves shared observability utilities (trace sink, NDJSON tracer, compactTraceAttributes) from the server app to @t3tools/shared/observability, and moves DesktopBackendBootstrap schema to @t3tools/contracts.
  • Adds a comprehensive set of typed IPC handlers via DesktopIpc and installDesktopIpcHandlers, covering settings, SSH, server exposure, updates, and window operations; the preload bridge now sends structured object payloads instead of positional arguments.
  • Converts all Effect imports across packages/contracts, packages/ssh, packages/shared, packages/tailscale, and related scripts to namespaced form, enforced by @effect/language-service plugin added to multiple tsconfig.json files.
  • Risk: main.ts is a wholesale rewrite; any existing behavior not covered by the new layers (imperative window creation, old IPC handler signatures, title bar overlay config) is removed or changed.

Macroscope summarized 35835a7.

@coderabbitai

coderabbitaiBot commented May 6, 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

Run ID: 43ea1a9d-4a21-4540-b157-0dd7ba97ae0b

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
  • Commit unit tests in branch t3code/a1238be3

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

@github-actionsgithub-actionsBot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 6, 2026
Comment threadapps/desktop/src/backendReadiness.ts Outdated

@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 and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Timeout silently succeeds instead of failing with error
    • Added Option.match after Effect.timeoutOption to convert Option.None (timeout) into a BackendTimeoutError failure instead of silently succeeding, matching the established pattern used in all other timeoutOption call sites in the codebase.

Create PR

Or push these changes by commenting:

@cursor push 27f2b65c9b
Preview (27f2b65c9b)
diff --git a/apps/desktop/src/backendReadiness.ts b/apps/desktop/src/backendReadiness.ts--- a/apps/desktop/src/backendReadiness.ts+++ b/apps/desktop/src/backendReadiness.ts@@ -3,6 +3,7 @@
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Schedule from "effect/Schedule";
import { HttpClient } from "effect/unstable/http";
@@ -60,9 +61,13 @@
yield* client.get(requestUrl).pipe(
Effect.asVoid,
Effect.timeoutOption(timeout),
+ Effect.flatMap(+ Option.match({+ onNone: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),+ onSome: () => Effect.void,+ }),+ ),
Effect.catchTags({
- TimeoutError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),- // Maybe map this to different error kind?
HttpClientError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),
}),
);

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/backendReadiness.ts Outdated
@macroscopeapp

macroscopeappBot commented May 6, 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.

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

@cursor push 27f2b65

macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
@macroscopeapp
macroscopeappBot dismissed their stale reviewMay 6, 2026 07:59

Dismissing prior approval to re-evaluate b5c8ea4

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated

@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 and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Port fields lose range validation in bootstrap schema
    • Replaced Schema.Number with a shared PortSchema (Schema.Int with isBetween 1-65535) for both port and tailscaleServePort fields in DesktopBackendBootstrap, restoring the validation that was present in the old BootstrapEnvelopeSchema.
  • ✅ Fixed: Readiness timeout fires but no window ever created
    • Added fallback window creation in the onReadinessFailure handler so users get a window (showing reconnection state) instead of being stuck with no UI when the readiness probe times out.

Create PR

Or push these changes by commenting:

@cursor push 1291f41e07
Preview (1291f41e07)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts--- a/apps/desktop/src/main.ts+++ b/apps/desktop/src/main.ts@@ -1496,6 +1496,12 @@
`bootstrap backend readiness warning message=${formatErrorMessage(error)}`,
);
console.warn("[desktop] backend readiness check failed during bootstrap", error);
++ if (isDevelopment) return;+ const existingWindow = mainWindow ?? BrowserWindow.getAllWindows()[0] ?? null;+ if (existingWindow !== null) return;+ mainWindow = createWindow();+ writeDesktopLogHeader("bootstrap main window created (readiness timeout fallback)");
}),
onOutput: (streamName, chunk) =>
Effect.sync(() => {
diff --git a/apps/server/src/cli.ts b/apps/server/src/cli.ts--- a/apps/server/src/cli.ts+++ b/apps/server/src/cli.ts@@ -5,6 +5,7 @@
CommandId,
DesktopBackendBootstrap,
OrchestrationReadModel,
+ PortSchema,
ProjectId,
type ClientOrchestrationCommand,
} from "@t3tools/contracts";
@@ -67,8 +68,6 @@
import { WorkspacePaths } from "./workspace/Services/WorkspacePaths.ts";
import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";
-const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));-
const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(
Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
Flag.optional,
diff --git a/packages/contracts/src/baseSchemas.ts b/packages/contracts/src/baseSchemas.ts--- a/packages/contracts/src/baseSchemas.ts+++ b/packages/contracts/src/baseSchemas.ts@@ -5,6 +5,7 @@
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
export const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
+export const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));
export const IsoDateTime = Schema.String;
export type IsoDateTime = typeof IsoDateTime.Type;
diff --git a/packages/contracts/src/desktopBootstrap.ts b/packages/contracts/src/desktopBootstrap.ts--- a/packages/contracts/src/desktopBootstrap.ts+++ b/packages/contracts/src/desktopBootstrap.ts@@ -1,14 +1,16 @@
import { Schema } from "effect";
+import { PortSchema } from "./baseSchemas.ts";+
export const DesktopBackendBootstrap = Schema.Struct({
mode: Schema.Literal("desktop"),
noBrowser: Schema.Boolean,
- port: Schema.Number,+ port: PortSchema,
t3Home: Schema.String,
host: Schema.String,
desktopBootstrapToken: Schema.String,
tailscaleServeEnabled: Schema.Boolean,
- tailscaleServePort: Schema.Number,+ tailscaleServePort: PortSchema,
otlpTracesUrl: Schema.optional(Schema.String),
otlpMetricsUrl: Schema.optional(Schema.String),
});

You can send follow-ups to the cloud agent here.

Comment threadpackages/contracts/src/desktopBootstrap.ts Outdated
Comment threadapps/desktop/src/backendProcess.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backendPort.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
@cursor

cursorBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Start leaks restart fiber without interrupting it
    • Added a call to the existing cancelRestart helper before the state update in start(), which properly interrupts the restart fiber before clearing its reference, preventing the orphaned fiber leak.

Create PR

Or push these changes by commenting:

@cursor push fbac652b11
Preview (fbac652b11)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -232,11 +232,11 @@
.exists(config.entryPath)
.pipe(Effect.orElseSucceed(() => false));
+ yield* cancelRestart;
yield* Ref.update(state, (latest) => ({
...latest,
desiredRunning: true,
ready: false,
- restartFiber: Option.none(),
}));
if (!entryExists) {

You can send follow-ups to the cloud agent here.

@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 and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Restart backoff resets on spawn, not readiness
    • Moved the restartAttempt: 0 reset from the onStarted callback to the onReady callback so exponential backoff is maintained for backends that crash during initialization.

Create PR

Or push these changes by commenting:

@cursor push 269ea9ef0e
Preview (269ea9ef0e)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -324,16 +324,13 @@
...run,
pid: Option.some(pid),
}));
- yield* Ref.update(state, (latest) => ({- ...latest,- restartAttempt: 0,- }));
yield* events.onStarted({ pid, config });
}),
onReady: () =>
Effect.gen(function* () {
yield* Ref.update(state, (latest) => ({
...latest,
+ restartAttempt: 0,
ready: Option.match(latest.active, {
onNone: () => latest.ready,
onSome: (run) => (run.id === runId ? true : latest.ready),

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/desktopBackendManager.ts Outdated
- Remove the DesktopRun service and derive run IDs from scoped log annotations
- Update backend lifecycle and menu logging to use Effect logging helpers
- Adjust tests for backend restarts and menu behavior

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Misleading log emitted when backend start is skipped
    • Moved the log statement inside the conditional block and added a distinct "skipped" log for the quitting case, so logs now accurately reflect whether the backend was started or skipped.

Create PR

Or push these changes by commenting:

@cursor push 5f643f6923
Preview (5f643f6923)
diff --git a/apps/desktop/src/app/DesktopApp.ts b/apps/desktop/src/app/DesktopApp.ts--- a/apps/desktop/src/app/DesktopApp.ts+++ b/apps/desktop/src/app/DesktopApp.ts@@ -178,8 +178,10 @@
if (!(yield* Ref.get(state.quitting))) {
yield* backendManager.start;
+ yield* Effect.logInfo("bootstrap backend start requested");+ } else {+ yield* Effect.logInfo("bootstrap backend start skipped (quitting)");
}
- yield* Effect.logInfo("bootstrap backend start requested");
if (environment.isDevelopment) {
yield* desktopWindow.ensureMain;

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
- Switch desktop backend readiness checks to `/.well-known/t3/environment`
- Update tests to expect the new probe URL
- Persist backend output and desktop logs in development
- Delay dev window creation until backend is ready
- Move Electron scheme privilege registration into startup layer

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: desktopState.backendReady is never set to true
    • Added Ref.set(desktopState.backendReady, true) in the onReady callback and removed the manual workaround in the test that externally set it to true.

Create PR

Or push these changes by commenting:

@cursor push e5394e09b4
Preview (e5394e09b4)
diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts--- a/apps/desktop/src/backend/DesktopBackendManager.test.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts@@ -328,7 +328,6 @@
yield* manager.start;
assert.equal(yield* Queue.take(startedPids), 123);
yield* Deferred.await(ready);
- yield* Ref.set(backendReady, true);
assert.isTrue(yield* Ref.get(backendReady));
assert.deepEqual(yield* manager.currentConfig, Option.some(baseConfig));
diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts--- a/apps/desktop/src/backend/DesktopBackendManager.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.ts@@ -443,6 +443,7 @@
onSome: (run) => (run.id === runId ? true : latest.ready),
}),
}));
+ yield* Ref.set(desktopState.backendReady, true);
yield* desktopWindow.handleBackendReady.pipe(
Effect.catch((error) =>
Effect.logError("failed to open main window after backend readiness").pipe(

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 413d2f9. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Move trace sink and OTLP helpers into shared observability code
- Add desktop trace export and structured backend child logging
- Update server observability wiring and tests for new trace records
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Ignore stale ready signals from previous runs
- Skip scheduled restarts once desiredRunning is false
- Add regression coverage for stop cancelling a pending restart
- wrap startup, lifecycle, backend, IPC, and settings flows with spans
- remove the separate desktop-main.log file logger in favor of trace events
- keep observability tests aligned with span-based logging
Comment threadapps/desktop/src/electron/ElectronWindow.ts
Co-authored-by: codex <codex@users.noreply.github.com>
- Ignore windows that are destroyed before sync runs
- Add coverage for appearance sync filtering
- Introduce a shared component logger helper for desktop services
- Replace ad hoc log annotations across startup, backend, updates, menu, and window code
@juliusmarminge
juliusmarminge merged commit aa219be into mainMay 8, 2026
12 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/a1238be3 branch May 8, 2026 04:17
harshk242 added a commit to harshk242/t3code that referenced this pull request May 8, 2026
Major upstream change: desktop app ported to Effect (pingdotgg#2546). The old
monolithic apps/desktop/src/main.ts is replaced by Effect layers under
apps/desktop/src/app/, backend/, electron/, etc.
Re-applied fork's Linux desktop integration in the new structure:
- apps/desktop/src/app/DesktopApp.ts: set process.env.CHROME_DESKTOP =
linuxDesktopEntryName before appendCommandLineSwitch("class",
linuxWmClass) so GNOME launcher matching works.
- apps/desktop/src/app/DesktopAppIdentity.ts: setName(linuxWmClass) on
Linux instead of displayName so .deb installs pin to the dock under
the WM class advertised by the .desktop entry.
Upstream's appendCommandLineSwitch("class", linuxWmClass) was already
present, so that part of the fork's old block in main.ts is redundant.
The deb-specific build pieces (stageLinuxIcons multi-size hicolor PNGs,
homepage/author/maintainer fields, dist:desktop:deb script) merged
cleanly with no conflicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
darjss pushed a commit to darjss/t3code that referenced this pull request Aug 26, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
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:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@cursoragent
, '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

port desktop app to Effect - #2546

Merged
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3
May 8, 2026
Merged

port desktop app to Effect#2546
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Reworked desktop backend readiness probing around Effect-based services and effects, with temporary promise shims preserved for existing call sites.
  • Added Effect/Vitest coverage for readiness polling, request timeouts, abort handling, and backend startup race behavior.
  • Updated the desktop main process to use typed URL handling for backend endpoints and the new readiness error type.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test for apps/desktop/src/backendReadiness.test.ts and apps/desktop/src/backendStartupReadiness.test.ts

Note

High Risk
Large refactor of the Electron desktop main process, backend spawning/readiness, and lifecycle handling; mistakes could prevent the app from starting or shut down cleanly. Also changes runtime configuration/paths and protocol handling, which can impact packaging and local file access.

Overview
Ports the desktop Electron main process to an Effect-based architecture, introducing layered services for app identity/environment/config, lifecycle/shutdown coordination, observability/tracing, and Electron wrappers (app/dialog/menu/protocol).

Reworks desktop backend startup into Effect-managed spawning with fd-based bootstrap payloads, HTTP readiness polling, structured child-process output logging, and automatic restart scheduling/cancellation logic.

Cleans up legacy promise/imperative utilities and tests (e.g., backendPort, readiness helpers, settings/persistence/dialog helpers) in favor of new Effect/Vitest test coverage, and updates build hygiene (.gitignore consolidation) plus bumps electron to 41.5.0.

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

Note

Port desktop app to Effect by replacing imperative Electron code with layered Effect-TS services

  • Rewrites main.ts from imperative Electron startup code to a declarative Effect-TS layer composition, using NodeRuntime.runMain to execute a DesktopApp.program with all services provided via layers.
  • Introduces a large set of new Effect-based Electron service wrappers: ElectronApp, ElectronDialog, ElectronMenu, ElectronProtocol, ElectronSafeStorage, ElectronShell, ElectronTheme, ElectronUpdater, and ElectronWindow, each with typed errors and scoped resource management.
  • Adds desktop domain services (DesktopEnvironment, DesktopAppSettings, DesktopClientSettings, DesktopSavedEnvironments, DesktopBackendManager, DesktopBackendConfiguration, DesktopServerExposure, DesktopSshEnvironment, DesktopUpdates, DesktopWindow, etc.) as injectable Effect layers.
  • Moves shared observability utilities (trace sink, NDJSON tracer, compactTraceAttributes) from the server app to @t3tools/shared/observability, and moves DesktopBackendBootstrap schema to @t3tools/contracts.
  • Adds a comprehensive set of typed IPC handlers via DesktopIpc and installDesktopIpcHandlers, covering settings, SSH, server exposure, updates, and window operations; the preload bridge now sends structured object payloads instead of positional arguments.
  • Converts all Effect imports across packages/contracts, packages/ssh, packages/shared, packages/tailscale, and related scripts to namespaced form, enforced by @effect/language-service plugin added to multiple tsconfig.json files.
  • Risk: main.ts is a wholesale rewrite; any existing behavior not covered by the new layers (imperative window creation, old IPC handler signatures, title bar overlay config) is removed or changed.

Macroscope summarized 35835a7.

@coderabbitai

coderabbitaiBot commented May 6, 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

Run ID: 43ea1a9d-4a21-4540-b157-0dd7ba97ae0b

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
  • Commit unit tests in branch t3code/a1238be3

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

@github-actionsgithub-actionsBot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 6, 2026
Comment threadapps/desktop/src/backendReadiness.ts Outdated

@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 and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Timeout silently succeeds instead of failing with error
    • Added Option.match after Effect.timeoutOption to convert Option.None (timeout) into a BackendTimeoutError failure instead of silently succeeding, matching the established pattern used in all other timeoutOption call sites in the codebase.

Create PR

Or push these changes by commenting:

@cursor push 27f2b65c9b
Preview (27f2b65c9b)
diff --git a/apps/desktop/src/backendReadiness.ts b/apps/desktop/src/backendReadiness.ts--- a/apps/desktop/src/backendReadiness.ts+++ b/apps/desktop/src/backendReadiness.ts@@ -3,6 +3,7 @@
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Schedule from "effect/Schedule";
import { HttpClient } from "effect/unstable/http";
@@ -60,9 +61,13 @@
yield* client.get(requestUrl).pipe(
Effect.asVoid,
Effect.timeoutOption(timeout),
+ Effect.flatMap(+ Option.match({+ onNone: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),+ onSome: () => Effect.void,+ }),+ ),
Effect.catchTags({
- TimeoutError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),- // Maybe map this to different error kind?
HttpClientError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),
}),
);

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/backendReadiness.ts Outdated
@macroscopeapp

macroscopeappBot commented May 6, 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.

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

@cursor push 27f2b65

macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
@macroscopeapp
macroscopeappBot dismissed their stale reviewMay 6, 2026 07:59

Dismissing prior approval to re-evaluate b5c8ea4

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated

@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 and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Port fields lose range validation in bootstrap schema
    • Replaced Schema.Number with a shared PortSchema (Schema.Int with isBetween 1-65535) for both port and tailscaleServePort fields in DesktopBackendBootstrap, restoring the validation that was present in the old BootstrapEnvelopeSchema.
  • ✅ Fixed: Readiness timeout fires but no window ever created
    • Added fallback window creation in the onReadinessFailure handler so users get a window (showing reconnection state) instead of being stuck with no UI when the readiness probe times out.

Create PR

Or push these changes by commenting:

@cursor push 1291f41e07
Preview (1291f41e07)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts--- a/apps/desktop/src/main.ts+++ b/apps/desktop/src/main.ts@@ -1496,6 +1496,12 @@
`bootstrap backend readiness warning message=${formatErrorMessage(error)}`,
);
console.warn("[desktop] backend readiness check failed during bootstrap", error);
++ if (isDevelopment) return;+ const existingWindow = mainWindow ?? BrowserWindow.getAllWindows()[0] ?? null;+ if (existingWindow !== null) return;+ mainWindow = createWindow();+ writeDesktopLogHeader("bootstrap main window created (readiness timeout fallback)");
}),
onOutput: (streamName, chunk) =>
Effect.sync(() => {
diff --git a/apps/server/src/cli.ts b/apps/server/src/cli.ts--- a/apps/server/src/cli.ts+++ b/apps/server/src/cli.ts@@ -5,6 +5,7 @@
CommandId,
DesktopBackendBootstrap,
OrchestrationReadModel,
+ PortSchema,
ProjectId,
type ClientOrchestrationCommand,
} from "@t3tools/contracts";
@@ -67,8 +68,6 @@
import { WorkspacePaths } from "./workspace/Services/WorkspacePaths.ts";
import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";
-const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));-
const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(
Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
Flag.optional,
diff --git a/packages/contracts/src/baseSchemas.ts b/packages/contracts/src/baseSchemas.ts--- a/packages/contracts/src/baseSchemas.ts+++ b/packages/contracts/src/baseSchemas.ts@@ -5,6 +5,7 @@
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
export const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
+export const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));
export const IsoDateTime = Schema.String;
export type IsoDateTime = typeof IsoDateTime.Type;
diff --git a/packages/contracts/src/desktopBootstrap.ts b/packages/contracts/src/desktopBootstrap.ts--- a/packages/contracts/src/desktopBootstrap.ts+++ b/packages/contracts/src/desktopBootstrap.ts@@ -1,14 +1,16 @@
import { Schema } from "effect";
+import { PortSchema } from "./baseSchemas.ts";+
export const DesktopBackendBootstrap = Schema.Struct({
mode: Schema.Literal("desktop"),
noBrowser: Schema.Boolean,
- port: Schema.Number,+ port: PortSchema,
t3Home: Schema.String,
host: Schema.String,
desktopBootstrapToken: Schema.String,
tailscaleServeEnabled: Schema.Boolean,
- tailscaleServePort: Schema.Number,+ tailscaleServePort: PortSchema,
otlpTracesUrl: Schema.optional(Schema.String),
otlpMetricsUrl: Schema.optional(Schema.String),
});

You can send follow-ups to the cloud agent here.

Comment threadpackages/contracts/src/desktopBootstrap.ts Outdated
Comment threadapps/desktop/src/backendProcess.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backendPort.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
@cursor

cursorBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Start leaks restart fiber without interrupting it
    • Added a call to the existing cancelRestart helper before the state update in start(), which properly interrupts the restart fiber before clearing its reference, preventing the orphaned fiber leak.

Create PR

Or push these changes by commenting:

@cursor push fbac652b11
Preview (fbac652b11)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -232,11 +232,11 @@
.exists(config.entryPath)
.pipe(Effect.orElseSucceed(() => false));
+ yield* cancelRestart;
yield* Ref.update(state, (latest) => ({
...latest,
desiredRunning: true,
ready: false,
- restartFiber: Option.none(),
}));
if (!entryExists) {

You can send follow-ups to the cloud agent here.

@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 and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Restart backoff resets on spawn, not readiness
    • Moved the restartAttempt: 0 reset from the onStarted callback to the onReady callback so exponential backoff is maintained for backends that crash during initialization.

Create PR

Or push these changes by commenting:

@cursor push 269ea9ef0e
Preview (269ea9ef0e)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -324,16 +324,13 @@
...run,
pid: Option.some(pid),
}));
- yield* Ref.update(state, (latest) => ({- ...latest,- restartAttempt: 0,- }));
yield* events.onStarted({ pid, config });
}),
onReady: () =>
Effect.gen(function* () {
yield* Ref.update(state, (latest) => ({
...latest,
+ restartAttempt: 0,
ready: Option.match(latest.active, {
onNone: () => latest.ready,
onSome: (run) => (run.id === runId ? true : latest.ready),

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/desktopBackendManager.ts Outdated
- Remove the DesktopRun service and derive run IDs from scoped log annotations
- Update backend lifecycle and menu logging to use Effect logging helpers
- Adjust tests for backend restarts and menu behavior

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Misleading log emitted when backend start is skipped
    • Moved the log statement inside the conditional block and added a distinct "skipped" log for the quitting case, so logs now accurately reflect whether the backend was started or skipped.

Create PR

Or push these changes by commenting:

@cursor push 5f643f6923
Preview (5f643f6923)
diff --git a/apps/desktop/src/app/DesktopApp.ts b/apps/desktop/src/app/DesktopApp.ts--- a/apps/desktop/src/app/DesktopApp.ts+++ b/apps/desktop/src/app/DesktopApp.ts@@ -178,8 +178,10 @@
if (!(yield* Ref.get(state.quitting))) {
yield* backendManager.start;
+ yield* Effect.logInfo("bootstrap backend start requested");+ } else {+ yield* Effect.logInfo("bootstrap backend start skipped (quitting)");
}
- yield* Effect.logInfo("bootstrap backend start requested");
if (environment.isDevelopment) {
yield* desktopWindow.ensureMain;

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
- Switch desktop backend readiness checks to `/.well-known/t3/environment`
- Update tests to expect the new probe URL
- Persist backend output and desktop logs in development
- Delay dev window creation until backend is ready
- Move Electron scheme privilege registration into startup layer

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: desktopState.backendReady is never set to true
    • Added Ref.set(desktopState.backendReady, true) in the onReady callback and removed the manual workaround in the test that externally set it to true.

Create PR

Or push these changes by commenting:

@cursor push e5394e09b4
Preview (e5394e09b4)
diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts--- a/apps/desktop/src/backend/DesktopBackendManager.test.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts@@ -328,7 +328,6 @@
yield* manager.start;
assert.equal(yield* Queue.take(startedPids), 123);
yield* Deferred.await(ready);
- yield* Ref.set(backendReady, true);
assert.isTrue(yield* Ref.get(backendReady));
assert.deepEqual(yield* manager.currentConfig, Option.some(baseConfig));
diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts--- a/apps/desktop/src/backend/DesktopBackendManager.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.ts@@ -443,6 +443,7 @@
onSome: (run) => (run.id === runId ? true : latest.ready),
}),
}));
+ yield* Ref.set(desktopState.backendReady, true);
yield* desktopWindow.handleBackendReady.pipe(
Effect.catch((error) =>
Effect.logError("failed to open main window after backend readiness").pipe(

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 413d2f9. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Move trace sink and OTLP helpers into shared observability code
- Add desktop trace export and structured backend child logging
- Update server observability wiring and tests for new trace records
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Ignore stale ready signals from previous runs
- Skip scheduled restarts once desiredRunning is false
- Add regression coverage for stop cancelling a pending restart
- wrap startup, lifecycle, backend, IPC, and settings flows with spans
- remove the separate desktop-main.log file logger in favor of trace events
- keep observability tests aligned with span-based logging
Comment threadapps/desktop/src/electron/ElectronWindow.ts
Co-authored-by: codex <codex@users.noreply.github.com>
- Ignore windows that are destroyed before sync runs
- Add coverage for appearance sync filtering
- Introduce a shared component logger helper for desktop services
- Replace ad hoc log annotations across startup, backend, updates, menu, and window code
@juliusmarminge
juliusmarminge merged commit aa219be into mainMay 8, 2026
12 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/a1238be3 branch May 8, 2026 04:17
harshk242 added a commit to harshk242/t3code that referenced this pull request May 8, 2026
Major upstream change: desktop app ported to Effect (pingdotgg#2546). The old
monolithic apps/desktop/src/main.ts is replaced by Effect layers under
apps/desktop/src/app/, backend/, electron/, etc.
Re-applied fork's Linux desktop integration in the new structure:
- apps/desktop/src/app/DesktopApp.ts: set process.env.CHROME_DESKTOP =
linuxDesktopEntryName before appendCommandLineSwitch("class",
linuxWmClass) so GNOME launcher matching works.
- apps/desktop/src/app/DesktopAppIdentity.ts: setName(linuxWmClass) on
Linux instead of displayName so .deb installs pin to the dock under
the WM class advertised by the .desktop entry.
Upstream's appendCommandLineSwitch("class", linuxWmClass) was already
present, so that part of the fork's old block in main.ts is redundant.
The deb-specific build pieces (stageLinuxIcons multi-size hicolor PNGs,
homepage/author/maintainer fields, dist:desktop:deb script) merged
cleanly with no conflicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
darjss pushed a commit to darjss/t3code that referenced this pull request Aug 26, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
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:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@cursoragent
, '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

port desktop app to Effect - #2546

Merged
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3
May 8, 2026
Merged

port desktop app to Effect#2546
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Reworked desktop backend readiness probing around Effect-based services and effects, with temporary promise shims preserved for existing call sites.
  • Added Effect/Vitest coverage for readiness polling, request timeouts, abort handling, and backend startup race behavior.
  • Updated the desktop main process to use typed URL handling for backend endpoints and the new readiness error type.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test for apps/desktop/src/backendReadiness.test.ts and apps/desktop/src/backendStartupReadiness.test.ts

Note

High Risk
Large refactor of the Electron desktop main process, backend spawning/readiness, and lifecycle handling; mistakes could prevent the app from starting or shut down cleanly. Also changes runtime configuration/paths and protocol handling, which can impact packaging and local file access.

Overview
Ports the desktop Electron main process to an Effect-based architecture, introducing layered services for app identity/environment/config, lifecycle/shutdown coordination, observability/tracing, and Electron wrappers (app/dialog/menu/protocol).

Reworks desktop backend startup into Effect-managed spawning with fd-based bootstrap payloads, HTTP readiness polling, structured child-process output logging, and automatic restart scheduling/cancellation logic.

Cleans up legacy promise/imperative utilities and tests (e.g., backendPort, readiness helpers, settings/persistence/dialog helpers) in favor of new Effect/Vitest test coverage, and updates build hygiene (.gitignore consolidation) plus bumps electron to 41.5.0.

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

Note

Port desktop app to Effect by replacing imperative Electron code with layered Effect-TS services

  • Rewrites main.ts from imperative Electron startup code to a declarative Effect-TS layer composition, using NodeRuntime.runMain to execute a DesktopApp.program with all services provided via layers.
  • Introduces a large set of new Effect-based Electron service wrappers: ElectronApp, ElectronDialog, ElectronMenu, ElectronProtocol, ElectronSafeStorage, ElectronShell, ElectronTheme, ElectronUpdater, and ElectronWindow, each with typed errors and scoped resource management.
  • Adds desktop domain services (DesktopEnvironment, DesktopAppSettings, DesktopClientSettings, DesktopSavedEnvironments, DesktopBackendManager, DesktopBackendConfiguration, DesktopServerExposure, DesktopSshEnvironment, DesktopUpdates, DesktopWindow, etc.) as injectable Effect layers.
  • Moves shared observability utilities (trace sink, NDJSON tracer, compactTraceAttributes) from the server app to @t3tools/shared/observability, and moves DesktopBackendBootstrap schema to @t3tools/contracts.
  • Adds a comprehensive set of typed IPC handlers via DesktopIpc and installDesktopIpcHandlers, covering settings, SSH, server exposure, updates, and window operations; the preload bridge now sends structured object payloads instead of positional arguments.
  • Converts all Effect imports across packages/contracts, packages/ssh, packages/shared, packages/tailscale, and related scripts to namespaced form, enforced by @effect/language-service plugin added to multiple tsconfig.json files.
  • Risk: main.ts is a wholesale rewrite; any existing behavior not covered by the new layers (imperative window creation, old IPC handler signatures, title bar overlay config) is removed or changed.

Macroscope summarized 35835a7.

@coderabbitai

coderabbitaiBot commented May 6, 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

Run ID: 43ea1a9d-4a21-4540-b157-0dd7ba97ae0b

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
  • Commit unit tests in branch t3code/a1238be3

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

@github-actionsgithub-actionsBot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 6, 2026
Comment threadapps/desktop/src/backendReadiness.ts Outdated

@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 and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Timeout silently succeeds instead of failing with error
    • Added Option.match after Effect.timeoutOption to convert Option.None (timeout) into a BackendTimeoutError failure instead of silently succeeding, matching the established pattern used in all other timeoutOption call sites in the codebase.

Create PR

Or push these changes by commenting:

@cursor push 27f2b65c9b
Preview (27f2b65c9b)
diff --git a/apps/desktop/src/backendReadiness.ts b/apps/desktop/src/backendReadiness.ts--- a/apps/desktop/src/backendReadiness.ts+++ b/apps/desktop/src/backendReadiness.ts@@ -3,6 +3,7 @@
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Schedule from "effect/Schedule";
import { HttpClient } from "effect/unstable/http";
@@ -60,9 +61,13 @@
yield* client.get(requestUrl).pipe(
Effect.asVoid,
Effect.timeoutOption(timeout),
+ Effect.flatMap(+ Option.match({+ onNone: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),+ onSome: () => Effect.void,+ }),+ ),
Effect.catchTags({
- TimeoutError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),- // Maybe map this to different error kind?
HttpClientError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),
}),
);

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/backendReadiness.ts Outdated
@macroscopeapp

macroscopeappBot commented May 6, 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.

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

@cursor push 27f2b65

macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
@macroscopeapp
macroscopeappBot dismissed their stale reviewMay 6, 2026 07:59

Dismissing prior approval to re-evaluate b5c8ea4

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated

@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 and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Port fields lose range validation in bootstrap schema
    • Replaced Schema.Number with a shared PortSchema (Schema.Int with isBetween 1-65535) for both port and tailscaleServePort fields in DesktopBackendBootstrap, restoring the validation that was present in the old BootstrapEnvelopeSchema.
  • ✅ Fixed: Readiness timeout fires but no window ever created
    • Added fallback window creation in the onReadinessFailure handler so users get a window (showing reconnection state) instead of being stuck with no UI when the readiness probe times out.

Create PR

Or push these changes by commenting:

@cursor push 1291f41e07
Preview (1291f41e07)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts--- a/apps/desktop/src/main.ts+++ b/apps/desktop/src/main.ts@@ -1496,6 +1496,12 @@
`bootstrap backend readiness warning message=${formatErrorMessage(error)}`,
);
console.warn("[desktop] backend readiness check failed during bootstrap", error);
++ if (isDevelopment) return;+ const existingWindow = mainWindow ?? BrowserWindow.getAllWindows()[0] ?? null;+ if (existingWindow !== null) return;+ mainWindow = createWindow();+ writeDesktopLogHeader("bootstrap main window created (readiness timeout fallback)");
}),
onOutput: (streamName, chunk) =>
Effect.sync(() => {
diff --git a/apps/server/src/cli.ts b/apps/server/src/cli.ts--- a/apps/server/src/cli.ts+++ b/apps/server/src/cli.ts@@ -5,6 +5,7 @@
CommandId,
DesktopBackendBootstrap,
OrchestrationReadModel,
+ PortSchema,
ProjectId,
type ClientOrchestrationCommand,
} from "@t3tools/contracts";
@@ -67,8 +68,6 @@
import { WorkspacePaths } from "./workspace/Services/WorkspacePaths.ts";
import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";
-const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));-
const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(
Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
Flag.optional,
diff --git a/packages/contracts/src/baseSchemas.ts b/packages/contracts/src/baseSchemas.ts--- a/packages/contracts/src/baseSchemas.ts+++ b/packages/contracts/src/baseSchemas.ts@@ -5,6 +5,7 @@
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
export const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
+export const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));
export const IsoDateTime = Schema.String;
export type IsoDateTime = typeof IsoDateTime.Type;
diff --git a/packages/contracts/src/desktopBootstrap.ts b/packages/contracts/src/desktopBootstrap.ts--- a/packages/contracts/src/desktopBootstrap.ts+++ b/packages/contracts/src/desktopBootstrap.ts@@ -1,14 +1,16 @@
import { Schema } from "effect";
+import { PortSchema } from "./baseSchemas.ts";+
export const DesktopBackendBootstrap = Schema.Struct({
mode: Schema.Literal("desktop"),
noBrowser: Schema.Boolean,
- port: Schema.Number,+ port: PortSchema,
t3Home: Schema.String,
host: Schema.String,
desktopBootstrapToken: Schema.String,
tailscaleServeEnabled: Schema.Boolean,
- tailscaleServePort: Schema.Number,+ tailscaleServePort: PortSchema,
otlpTracesUrl: Schema.optional(Schema.String),
otlpMetricsUrl: Schema.optional(Schema.String),
});

You can send follow-ups to the cloud agent here.

Comment threadpackages/contracts/src/desktopBootstrap.ts Outdated
Comment threadapps/desktop/src/backendProcess.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backendPort.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
@cursor

cursorBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Start leaks restart fiber without interrupting it
    • Added a call to the existing cancelRestart helper before the state update in start(), which properly interrupts the restart fiber before clearing its reference, preventing the orphaned fiber leak.

Create PR

Or push these changes by commenting:

@cursor push fbac652b11
Preview (fbac652b11)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -232,11 +232,11 @@
.exists(config.entryPath)
.pipe(Effect.orElseSucceed(() => false));
+ yield* cancelRestart;
yield* Ref.update(state, (latest) => ({
...latest,
desiredRunning: true,
ready: false,
- restartFiber: Option.none(),
}));
if (!entryExists) {

You can send follow-ups to the cloud agent here.

@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 and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Restart backoff resets on spawn, not readiness
    • Moved the restartAttempt: 0 reset from the onStarted callback to the onReady callback so exponential backoff is maintained for backends that crash during initialization.

Create PR

Or push these changes by commenting:

@cursor push 269ea9ef0e
Preview (269ea9ef0e)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -324,16 +324,13 @@
...run,
pid: Option.some(pid),
}));
- yield* Ref.update(state, (latest) => ({- ...latest,- restartAttempt: 0,- }));
yield* events.onStarted({ pid, config });
}),
onReady: () =>
Effect.gen(function* () {
yield* Ref.update(state, (latest) => ({
...latest,
+ restartAttempt: 0,
ready: Option.match(latest.active, {
onNone: () => latest.ready,
onSome: (run) => (run.id === runId ? true : latest.ready),

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/desktopBackendManager.ts Outdated
- Remove the DesktopRun service and derive run IDs from scoped log annotations
- Update backend lifecycle and menu logging to use Effect logging helpers
- Adjust tests for backend restarts and menu behavior

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Misleading log emitted when backend start is skipped
    • Moved the log statement inside the conditional block and added a distinct "skipped" log for the quitting case, so logs now accurately reflect whether the backend was started or skipped.

Create PR

Or push these changes by commenting:

@cursor push 5f643f6923
Preview (5f643f6923)
diff --git a/apps/desktop/src/app/DesktopApp.ts b/apps/desktop/src/app/DesktopApp.ts--- a/apps/desktop/src/app/DesktopApp.ts+++ b/apps/desktop/src/app/DesktopApp.ts@@ -178,8 +178,10 @@
if (!(yield* Ref.get(state.quitting))) {
yield* backendManager.start;
+ yield* Effect.logInfo("bootstrap backend start requested");+ } else {+ yield* Effect.logInfo("bootstrap backend start skipped (quitting)");
}
- yield* Effect.logInfo("bootstrap backend start requested");
if (environment.isDevelopment) {
yield* desktopWindow.ensureMain;

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
- Switch desktop backend readiness checks to `/.well-known/t3/environment`
- Update tests to expect the new probe URL
- Persist backend output and desktop logs in development
- Delay dev window creation until backend is ready
- Move Electron scheme privilege registration into startup layer

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: desktopState.backendReady is never set to true
    • Added Ref.set(desktopState.backendReady, true) in the onReady callback and removed the manual workaround in the test that externally set it to true.

Create PR

Or push these changes by commenting:

@cursor push e5394e09b4
Preview (e5394e09b4)
diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts--- a/apps/desktop/src/backend/DesktopBackendManager.test.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts@@ -328,7 +328,6 @@
yield* manager.start;
assert.equal(yield* Queue.take(startedPids), 123);
yield* Deferred.await(ready);
- yield* Ref.set(backendReady, true);
assert.isTrue(yield* Ref.get(backendReady));
assert.deepEqual(yield* manager.currentConfig, Option.some(baseConfig));
diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts--- a/apps/desktop/src/backend/DesktopBackendManager.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.ts@@ -443,6 +443,7 @@
onSome: (run) => (run.id === runId ? true : latest.ready),
}),
}));
+ yield* Ref.set(desktopState.backendReady, true);
yield* desktopWindow.handleBackendReady.pipe(
Effect.catch((error) =>
Effect.logError("failed to open main window after backend readiness").pipe(

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 413d2f9. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Move trace sink and OTLP helpers into shared observability code
- Add desktop trace export and structured backend child logging
- Update server observability wiring and tests for new trace records
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Ignore stale ready signals from previous runs
- Skip scheduled restarts once desiredRunning is false
- Add regression coverage for stop cancelling a pending restart
- wrap startup, lifecycle, backend, IPC, and settings flows with spans
- remove the separate desktop-main.log file logger in favor of trace events
- keep observability tests aligned with span-based logging
Comment threadapps/desktop/src/electron/ElectronWindow.ts
Co-authored-by: codex <codex@users.noreply.github.com>
- Ignore windows that are destroyed before sync runs
- Add coverage for appearance sync filtering
- Introduce a shared component logger helper for desktop services
- Replace ad hoc log annotations across startup, backend, updates, menu, and window code
@juliusmarminge
juliusmarminge merged commit aa219be into mainMay 8, 2026
12 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/a1238be3 branch May 8, 2026 04:17
harshk242 added a commit to harshk242/t3code that referenced this pull request May 8, 2026
Major upstream change: desktop app ported to Effect (pingdotgg#2546). The old
monolithic apps/desktop/src/main.ts is replaced by Effect layers under
apps/desktop/src/app/, backend/, electron/, etc.
Re-applied fork's Linux desktop integration in the new structure:
- apps/desktop/src/app/DesktopApp.ts: set process.env.CHROME_DESKTOP =
linuxDesktopEntryName before appendCommandLineSwitch("class",
linuxWmClass) so GNOME launcher matching works.
- apps/desktop/src/app/DesktopAppIdentity.ts: setName(linuxWmClass) on
Linux instead of displayName so .deb installs pin to the dock under
the WM class advertised by the .desktop entry.
Upstream's appendCommandLineSwitch("class", linuxWmClass) was already
present, so that part of the fork's old block in main.ts is redundant.
The deb-specific build pieces (stageLinuxIcons multi-size hicolor PNGs,
homepage/author/maintainer fields, dist:desktop:deb script) merged
cleanly with no conflicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
darjss pushed a commit to darjss/t3code that referenced this pull request Aug 26, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
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:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@cursoragent
, '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

port desktop app to Effect - #2546

Merged
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3
May 8, 2026
Merged

port desktop app to Effect#2546
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Reworked desktop backend readiness probing around Effect-based services and effects, with temporary promise shims preserved for existing call sites.
  • Added Effect/Vitest coverage for readiness polling, request timeouts, abort handling, and backend startup race behavior.
  • Updated the desktop main process to use typed URL handling for backend endpoints and the new readiness error type.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test for apps/desktop/src/backendReadiness.test.ts and apps/desktop/src/backendStartupReadiness.test.ts

Note

High Risk
Large refactor of the Electron desktop main process, backend spawning/readiness, and lifecycle handling; mistakes could prevent the app from starting or shut down cleanly. Also changes runtime configuration/paths and protocol handling, which can impact packaging and local file access.

Overview
Ports the desktop Electron main process to an Effect-based architecture, introducing layered services for app identity/environment/config, lifecycle/shutdown coordination, observability/tracing, and Electron wrappers (app/dialog/menu/protocol).

Reworks desktop backend startup into Effect-managed spawning with fd-based bootstrap payloads, HTTP readiness polling, structured child-process output logging, and automatic restart scheduling/cancellation logic.

Cleans up legacy promise/imperative utilities and tests (e.g., backendPort, readiness helpers, settings/persistence/dialog helpers) in favor of new Effect/Vitest test coverage, and updates build hygiene (.gitignore consolidation) plus bumps electron to 41.5.0.

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

Note

Port desktop app to Effect by replacing imperative Electron code with layered Effect-TS services

  • Rewrites main.ts from imperative Electron startup code to a declarative Effect-TS layer composition, using NodeRuntime.runMain to execute a DesktopApp.program with all services provided via layers.
  • Introduces a large set of new Effect-based Electron service wrappers: ElectronApp, ElectronDialog, ElectronMenu, ElectronProtocol, ElectronSafeStorage, ElectronShell, ElectronTheme, ElectronUpdater, and ElectronWindow, each with typed errors and scoped resource management.
  • Adds desktop domain services (DesktopEnvironment, DesktopAppSettings, DesktopClientSettings, DesktopSavedEnvironments, DesktopBackendManager, DesktopBackendConfiguration, DesktopServerExposure, DesktopSshEnvironment, DesktopUpdates, DesktopWindow, etc.) as injectable Effect layers.
  • Moves shared observability utilities (trace sink, NDJSON tracer, compactTraceAttributes) from the server app to @t3tools/shared/observability, and moves DesktopBackendBootstrap schema to @t3tools/contracts.
  • Adds a comprehensive set of typed IPC handlers via DesktopIpc and installDesktopIpcHandlers, covering settings, SSH, server exposure, updates, and window operations; the preload bridge now sends structured object payloads instead of positional arguments.
  • Converts all Effect imports across packages/contracts, packages/ssh, packages/shared, packages/tailscale, and related scripts to namespaced form, enforced by @effect/language-service plugin added to multiple tsconfig.json files.
  • Risk: main.ts is a wholesale rewrite; any existing behavior not covered by the new layers (imperative window creation, old IPC handler signatures, title bar overlay config) is removed or changed.

Macroscope summarized 35835a7.

@coderabbitai

coderabbitaiBot commented May 6, 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

Run ID: 43ea1a9d-4a21-4540-b157-0dd7ba97ae0b

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
  • Commit unit tests in branch t3code/a1238be3

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

@github-actionsgithub-actionsBot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 6, 2026
Comment threadapps/desktop/src/backendReadiness.ts Outdated

@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 and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Timeout silently succeeds instead of failing with error
    • Added Option.match after Effect.timeoutOption to convert Option.None (timeout) into a BackendTimeoutError failure instead of silently succeeding, matching the established pattern used in all other timeoutOption call sites in the codebase.

Create PR

Or push these changes by commenting:

@cursor push 27f2b65c9b
Preview (27f2b65c9b)
diff --git a/apps/desktop/src/backendReadiness.ts b/apps/desktop/src/backendReadiness.ts--- a/apps/desktop/src/backendReadiness.ts+++ b/apps/desktop/src/backendReadiness.ts@@ -3,6 +3,7 @@
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Schedule from "effect/Schedule";
import { HttpClient } from "effect/unstable/http";
@@ -60,9 +61,13 @@
yield* client.get(requestUrl).pipe(
Effect.asVoid,
Effect.timeoutOption(timeout),
+ Effect.flatMap(+ Option.match({+ onNone: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),+ onSome: () => Effect.void,+ }),+ ),
Effect.catchTags({
- TimeoutError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),- // Maybe map this to different error kind?
HttpClientError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),
}),
);

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/backendReadiness.ts Outdated
@macroscopeapp

macroscopeappBot commented May 6, 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.

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

@cursor push 27f2b65

macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
@macroscopeapp
macroscopeappBot dismissed their stale reviewMay 6, 2026 07:59

Dismissing prior approval to re-evaluate b5c8ea4

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated

@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 and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Port fields lose range validation in bootstrap schema
    • Replaced Schema.Number with a shared PortSchema (Schema.Int with isBetween 1-65535) for both port and tailscaleServePort fields in DesktopBackendBootstrap, restoring the validation that was present in the old BootstrapEnvelopeSchema.
  • ✅ Fixed: Readiness timeout fires but no window ever created
    • Added fallback window creation in the onReadinessFailure handler so users get a window (showing reconnection state) instead of being stuck with no UI when the readiness probe times out.

Create PR

Or push these changes by commenting:

@cursor push 1291f41e07
Preview (1291f41e07)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts--- a/apps/desktop/src/main.ts+++ b/apps/desktop/src/main.ts@@ -1496,6 +1496,12 @@
`bootstrap backend readiness warning message=${formatErrorMessage(error)}`,
);
console.warn("[desktop] backend readiness check failed during bootstrap", error);
++ if (isDevelopment) return;+ const existingWindow = mainWindow ?? BrowserWindow.getAllWindows()[0] ?? null;+ if (existingWindow !== null) return;+ mainWindow = createWindow();+ writeDesktopLogHeader("bootstrap main window created (readiness timeout fallback)");
}),
onOutput: (streamName, chunk) =>
Effect.sync(() => {
diff --git a/apps/server/src/cli.ts b/apps/server/src/cli.ts--- a/apps/server/src/cli.ts+++ b/apps/server/src/cli.ts@@ -5,6 +5,7 @@
CommandId,
DesktopBackendBootstrap,
OrchestrationReadModel,
+ PortSchema,
ProjectId,
type ClientOrchestrationCommand,
} from "@t3tools/contracts";
@@ -67,8 +68,6 @@
import { WorkspacePaths } from "./workspace/Services/WorkspacePaths.ts";
import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";
-const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));-
const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(
Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
Flag.optional,
diff --git a/packages/contracts/src/baseSchemas.ts b/packages/contracts/src/baseSchemas.ts--- a/packages/contracts/src/baseSchemas.ts+++ b/packages/contracts/src/baseSchemas.ts@@ -5,6 +5,7 @@
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
export const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
+export const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));
export const IsoDateTime = Schema.String;
export type IsoDateTime = typeof IsoDateTime.Type;
diff --git a/packages/contracts/src/desktopBootstrap.ts b/packages/contracts/src/desktopBootstrap.ts--- a/packages/contracts/src/desktopBootstrap.ts+++ b/packages/contracts/src/desktopBootstrap.ts@@ -1,14 +1,16 @@
import { Schema } from "effect";
+import { PortSchema } from "./baseSchemas.ts";+
export const DesktopBackendBootstrap = Schema.Struct({
mode: Schema.Literal("desktop"),
noBrowser: Schema.Boolean,
- port: Schema.Number,+ port: PortSchema,
t3Home: Schema.String,
host: Schema.String,
desktopBootstrapToken: Schema.String,
tailscaleServeEnabled: Schema.Boolean,
- tailscaleServePort: Schema.Number,+ tailscaleServePort: PortSchema,
otlpTracesUrl: Schema.optional(Schema.String),
otlpMetricsUrl: Schema.optional(Schema.String),
});

You can send follow-ups to the cloud agent here.

Comment threadpackages/contracts/src/desktopBootstrap.ts Outdated
Comment threadapps/desktop/src/backendProcess.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backendPort.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
@cursor

cursorBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Start leaks restart fiber without interrupting it
    • Added a call to the existing cancelRestart helper before the state update in start(), which properly interrupts the restart fiber before clearing its reference, preventing the orphaned fiber leak.

Create PR

Or push these changes by commenting:

@cursor push fbac652b11
Preview (fbac652b11)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -232,11 +232,11 @@
.exists(config.entryPath)
.pipe(Effect.orElseSucceed(() => false));
+ yield* cancelRestart;
yield* Ref.update(state, (latest) => ({
...latest,
desiredRunning: true,
ready: false,
- restartFiber: Option.none(),
}));
if (!entryExists) {

You can send follow-ups to the cloud agent here.

@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 and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Restart backoff resets on spawn, not readiness
    • Moved the restartAttempt: 0 reset from the onStarted callback to the onReady callback so exponential backoff is maintained for backends that crash during initialization.

Create PR

Or push these changes by commenting:

@cursor push 269ea9ef0e
Preview (269ea9ef0e)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -324,16 +324,13 @@
...run,
pid: Option.some(pid),
}));
- yield* Ref.update(state, (latest) => ({- ...latest,- restartAttempt: 0,- }));
yield* events.onStarted({ pid, config });
}),
onReady: () =>
Effect.gen(function* () {
yield* Ref.update(state, (latest) => ({
...latest,
+ restartAttempt: 0,
ready: Option.match(latest.active, {
onNone: () => latest.ready,
onSome: (run) => (run.id === runId ? true : latest.ready),

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/desktopBackendManager.ts Outdated
- Remove the DesktopRun service and derive run IDs from scoped log annotations
- Update backend lifecycle and menu logging to use Effect logging helpers
- Adjust tests for backend restarts and menu behavior

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Misleading log emitted when backend start is skipped
    • Moved the log statement inside the conditional block and added a distinct "skipped" log for the quitting case, so logs now accurately reflect whether the backend was started or skipped.

Create PR

Or push these changes by commenting:

@cursor push 5f643f6923
Preview (5f643f6923)
diff --git a/apps/desktop/src/app/DesktopApp.ts b/apps/desktop/src/app/DesktopApp.ts--- a/apps/desktop/src/app/DesktopApp.ts+++ b/apps/desktop/src/app/DesktopApp.ts@@ -178,8 +178,10 @@
if (!(yield* Ref.get(state.quitting))) {
yield* backendManager.start;
+ yield* Effect.logInfo("bootstrap backend start requested");+ } else {+ yield* Effect.logInfo("bootstrap backend start skipped (quitting)");
}
- yield* Effect.logInfo("bootstrap backend start requested");
if (environment.isDevelopment) {
yield* desktopWindow.ensureMain;

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
- Switch desktop backend readiness checks to `/.well-known/t3/environment`
- Update tests to expect the new probe URL
- Persist backend output and desktop logs in development
- Delay dev window creation until backend is ready
- Move Electron scheme privilege registration into startup layer

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: desktopState.backendReady is never set to true
    • Added Ref.set(desktopState.backendReady, true) in the onReady callback and removed the manual workaround in the test that externally set it to true.

Create PR

Or push these changes by commenting:

@cursor push e5394e09b4
Preview (e5394e09b4)
diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts--- a/apps/desktop/src/backend/DesktopBackendManager.test.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts@@ -328,7 +328,6 @@
yield* manager.start;
assert.equal(yield* Queue.take(startedPids), 123);
yield* Deferred.await(ready);
- yield* Ref.set(backendReady, true);
assert.isTrue(yield* Ref.get(backendReady));
assert.deepEqual(yield* manager.currentConfig, Option.some(baseConfig));
diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts--- a/apps/desktop/src/backend/DesktopBackendManager.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.ts@@ -443,6 +443,7 @@
onSome: (run) => (run.id === runId ? true : latest.ready),
}),
}));
+ yield* Ref.set(desktopState.backendReady, true);
yield* desktopWindow.handleBackendReady.pipe(
Effect.catch((error) =>
Effect.logError("failed to open main window after backend readiness").pipe(

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 413d2f9. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Move trace sink and OTLP helpers into shared observability code
- Add desktop trace export and structured backend child logging
- Update server observability wiring and tests for new trace records
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Ignore stale ready signals from previous runs
- Skip scheduled restarts once desiredRunning is false
- Add regression coverage for stop cancelling a pending restart
- wrap startup, lifecycle, backend, IPC, and settings flows with spans
- remove the separate desktop-main.log file logger in favor of trace events
- keep observability tests aligned with span-based logging
Comment threadapps/desktop/src/electron/ElectronWindow.ts
Co-authored-by: codex <codex@users.noreply.github.com>
- Ignore windows that are destroyed before sync runs
- Add coverage for appearance sync filtering
- Introduce a shared component logger helper for desktop services
- Replace ad hoc log annotations across startup, backend, updates, menu, and window code
@juliusmarminge
juliusmarminge merged commit aa219be into mainMay 8, 2026
12 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/a1238be3 branch May 8, 2026 04:17
harshk242 added a commit to harshk242/t3code that referenced this pull request May 8, 2026
Major upstream change: desktop app ported to Effect (pingdotgg#2546). The old
monolithic apps/desktop/src/main.ts is replaced by Effect layers under
apps/desktop/src/app/, backend/, electron/, etc.
Re-applied fork's Linux desktop integration in the new structure:
- apps/desktop/src/app/DesktopApp.ts: set process.env.CHROME_DESKTOP =
linuxDesktopEntryName before appendCommandLineSwitch("class",
linuxWmClass) so GNOME launcher matching works.
- apps/desktop/src/app/DesktopAppIdentity.ts: setName(linuxWmClass) on
Linux instead of displayName so .deb installs pin to the dock under
the WM class advertised by the .desktop entry.
Upstream's appendCommandLineSwitch("class", linuxWmClass) was already
present, so that part of the fork's old block in main.ts is redundant.
The deb-specific build pieces (stageLinuxIcons multi-size hicolor PNGs,
homepage/author/maintainer fields, dist:desktop:deb script) merged
cleanly with no conflicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
darjss pushed a commit to darjss/t3code that referenced this pull request Aug 26, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
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:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@cursoragent
, '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

port desktop app to Effect - #2546

Merged
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3
May 8, 2026
Merged

port desktop app to Effect#2546
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Reworked desktop backend readiness probing around Effect-based services and effects, with temporary promise shims preserved for existing call sites.
  • Added Effect/Vitest coverage for readiness polling, request timeouts, abort handling, and backend startup race behavior.
  • Updated the desktop main process to use typed URL handling for backend endpoints and the new readiness error type.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test for apps/desktop/src/backendReadiness.test.ts and apps/desktop/src/backendStartupReadiness.test.ts

Note

High Risk
Large refactor of the Electron desktop main process, backend spawning/readiness, and lifecycle handling; mistakes could prevent the app from starting or shut down cleanly. Also changes runtime configuration/paths and protocol handling, which can impact packaging and local file access.

Overview
Ports the desktop Electron main process to an Effect-based architecture, introducing layered services for app identity/environment/config, lifecycle/shutdown coordination, observability/tracing, and Electron wrappers (app/dialog/menu/protocol).

Reworks desktop backend startup into Effect-managed spawning with fd-based bootstrap payloads, HTTP readiness polling, structured child-process output logging, and automatic restart scheduling/cancellation logic.

Cleans up legacy promise/imperative utilities and tests (e.g., backendPort, readiness helpers, settings/persistence/dialog helpers) in favor of new Effect/Vitest test coverage, and updates build hygiene (.gitignore consolidation) plus bumps electron to 41.5.0.

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

Note

Port desktop app to Effect by replacing imperative Electron code with layered Effect-TS services

  • Rewrites main.ts from imperative Electron startup code to a declarative Effect-TS layer composition, using NodeRuntime.runMain to execute a DesktopApp.program with all services provided via layers.
  • Introduces a large set of new Effect-based Electron service wrappers: ElectronApp, ElectronDialog, ElectronMenu, ElectronProtocol, ElectronSafeStorage, ElectronShell, ElectronTheme, ElectronUpdater, and ElectronWindow, each with typed errors and scoped resource management.
  • Adds desktop domain services (DesktopEnvironment, DesktopAppSettings, DesktopClientSettings, DesktopSavedEnvironments, DesktopBackendManager, DesktopBackendConfiguration, DesktopServerExposure, DesktopSshEnvironment, DesktopUpdates, DesktopWindow, etc.) as injectable Effect layers.
  • Moves shared observability utilities (trace sink, NDJSON tracer, compactTraceAttributes) from the server app to @t3tools/shared/observability, and moves DesktopBackendBootstrap schema to @t3tools/contracts.
  • Adds a comprehensive set of typed IPC handlers via DesktopIpc and installDesktopIpcHandlers, covering settings, SSH, server exposure, updates, and window operations; the preload bridge now sends structured object payloads instead of positional arguments.
  • Converts all Effect imports across packages/contracts, packages/ssh, packages/shared, packages/tailscale, and related scripts to namespaced form, enforced by @effect/language-service plugin added to multiple tsconfig.json files.
  • Risk: main.ts is a wholesale rewrite; any existing behavior not covered by the new layers (imperative window creation, old IPC handler signatures, title bar overlay config) is removed or changed.

Macroscope summarized 35835a7.

@coderabbitai

coderabbitaiBot commented May 6, 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

Run ID: 43ea1a9d-4a21-4540-b157-0dd7ba97ae0b

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
  • Commit unit tests in branch t3code/a1238be3

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

@github-actionsgithub-actionsBot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 6, 2026
Comment threadapps/desktop/src/backendReadiness.ts Outdated

@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 and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Timeout silently succeeds instead of failing with error
    • Added Option.match after Effect.timeoutOption to convert Option.None (timeout) into a BackendTimeoutError failure instead of silently succeeding, matching the established pattern used in all other timeoutOption call sites in the codebase.

Create PR

Or push these changes by commenting:

@cursor push 27f2b65c9b
Preview (27f2b65c9b)
diff --git a/apps/desktop/src/backendReadiness.ts b/apps/desktop/src/backendReadiness.ts--- a/apps/desktop/src/backendReadiness.ts+++ b/apps/desktop/src/backendReadiness.ts@@ -3,6 +3,7 @@
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Schedule from "effect/Schedule";
import { HttpClient } from "effect/unstable/http";
@@ -60,9 +61,13 @@
yield* client.get(requestUrl).pipe(
Effect.asVoid,
Effect.timeoutOption(timeout),
+ Effect.flatMap(+ Option.match({+ onNone: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),+ onSome: () => Effect.void,+ }),+ ),
Effect.catchTags({
- TimeoutError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),- // Maybe map this to different error kind?
HttpClientError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),
}),
);

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/backendReadiness.ts Outdated
@macroscopeapp

macroscopeappBot commented May 6, 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.

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

@cursor push 27f2b65

macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
@macroscopeapp
macroscopeappBot dismissed their stale reviewMay 6, 2026 07:59

Dismissing prior approval to re-evaluate b5c8ea4

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated

@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 and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Port fields lose range validation in bootstrap schema
    • Replaced Schema.Number with a shared PortSchema (Schema.Int with isBetween 1-65535) for both port and tailscaleServePort fields in DesktopBackendBootstrap, restoring the validation that was present in the old BootstrapEnvelopeSchema.
  • ✅ Fixed: Readiness timeout fires but no window ever created
    • Added fallback window creation in the onReadinessFailure handler so users get a window (showing reconnection state) instead of being stuck with no UI when the readiness probe times out.

Create PR

Or push these changes by commenting:

@cursor push 1291f41e07
Preview (1291f41e07)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts--- a/apps/desktop/src/main.ts+++ b/apps/desktop/src/main.ts@@ -1496,6 +1496,12 @@
`bootstrap backend readiness warning message=${formatErrorMessage(error)}`,
);
console.warn("[desktop] backend readiness check failed during bootstrap", error);
++ if (isDevelopment) return;+ const existingWindow = mainWindow ?? BrowserWindow.getAllWindows()[0] ?? null;+ if (existingWindow !== null) return;+ mainWindow = createWindow();+ writeDesktopLogHeader("bootstrap main window created (readiness timeout fallback)");
}),
onOutput: (streamName, chunk) =>
Effect.sync(() => {
diff --git a/apps/server/src/cli.ts b/apps/server/src/cli.ts--- a/apps/server/src/cli.ts+++ b/apps/server/src/cli.ts@@ -5,6 +5,7 @@
CommandId,
DesktopBackendBootstrap,
OrchestrationReadModel,
+ PortSchema,
ProjectId,
type ClientOrchestrationCommand,
} from "@t3tools/contracts";
@@ -67,8 +68,6 @@
import { WorkspacePaths } from "./workspace/Services/WorkspacePaths.ts";
import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";
-const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));-
const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(
Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
Flag.optional,
diff --git a/packages/contracts/src/baseSchemas.ts b/packages/contracts/src/baseSchemas.ts--- a/packages/contracts/src/baseSchemas.ts+++ b/packages/contracts/src/baseSchemas.ts@@ -5,6 +5,7 @@
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
export const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
+export const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));
export const IsoDateTime = Schema.String;
export type IsoDateTime = typeof IsoDateTime.Type;
diff --git a/packages/contracts/src/desktopBootstrap.ts b/packages/contracts/src/desktopBootstrap.ts--- a/packages/contracts/src/desktopBootstrap.ts+++ b/packages/contracts/src/desktopBootstrap.ts@@ -1,14 +1,16 @@
import { Schema } from "effect";
+import { PortSchema } from "./baseSchemas.ts";+
export const DesktopBackendBootstrap = Schema.Struct({
mode: Schema.Literal("desktop"),
noBrowser: Schema.Boolean,
- port: Schema.Number,+ port: PortSchema,
t3Home: Schema.String,
host: Schema.String,
desktopBootstrapToken: Schema.String,
tailscaleServeEnabled: Schema.Boolean,
- tailscaleServePort: Schema.Number,+ tailscaleServePort: PortSchema,
otlpTracesUrl: Schema.optional(Schema.String),
otlpMetricsUrl: Schema.optional(Schema.String),
});

You can send follow-ups to the cloud agent here.

Comment threadpackages/contracts/src/desktopBootstrap.ts Outdated
Comment threadapps/desktop/src/backendProcess.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backendPort.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
@cursor

cursorBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Start leaks restart fiber without interrupting it
    • Added a call to the existing cancelRestart helper before the state update in start(), which properly interrupts the restart fiber before clearing its reference, preventing the orphaned fiber leak.

Create PR

Or push these changes by commenting:

@cursor push fbac652b11
Preview (fbac652b11)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -232,11 +232,11 @@
.exists(config.entryPath)
.pipe(Effect.orElseSucceed(() => false));
+ yield* cancelRestart;
yield* Ref.update(state, (latest) => ({
...latest,
desiredRunning: true,
ready: false,
- restartFiber: Option.none(),
}));
if (!entryExists) {

You can send follow-ups to the cloud agent here.

@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 and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Restart backoff resets on spawn, not readiness
    • Moved the restartAttempt: 0 reset from the onStarted callback to the onReady callback so exponential backoff is maintained for backends that crash during initialization.

Create PR

Or push these changes by commenting:

@cursor push 269ea9ef0e
Preview (269ea9ef0e)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -324,16 +324,13 @@
...run,
pid: Option.some(pid),
}));
- yield* Ref.update(state, (latest) => ({- ...latest,- restartAttempt: 0,- }));
yield* events.onStarted({ pid, config });
}),
onReady: () =>
Effect.gen(function* () {
yield* Ref.update(state, (latest) => ({
...latest,
+ restartAttempt: 0,
ready: Option.match(latest.active, {
onNone: () => latest.ready,
onSome: (run) => (run.id === runId ? true : latest.ready),

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/desktopBackendManager.ts Outdated
- Remove the DesktopRun service and derive run IDs from scoped log annotations
- Update backend lifecycle and menu logging to use Effect logging helpers
- Adjust tests for backend restarts and menu behavior

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Misleading log emitted when backend start is skipped
    • Moved the log statement inside the conditional block and added a distinct "skipped" log for the quitting case, so logs now accurately reflect whether the backend was started or skipped.

Create PR

Or push these changes by commenting:

@cursor push 5f643f6923
Preview (5f643f6923)
diff --git a/apps/desktop/src/app/DesktopApp.ts b/apps/desktop/src/app/DesktopApp.ts--- a/apps/desktop/src/app/DesktopApp.ts+++ b/apps/desktop/src/app/DesktopApp.ts@@ -178,8 +178,10 @@
if (!(yield* Ref.get(state.quitting))) {
yield* backendManager.start;
+ yield* Effect.logInfo("bootstrap backend start requested");+ } else {+ yield* Effect.logInfo("bootstrap backend start skipped (quitting)");
}
- yield* Effect.logInfo("bootstrap backend start requested");
if (environment.isDevelopment) {
yield* desktopWindow.ensureMain;

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
- Switch desktop backend readiness checks to `/.well-known/t3/environment`
- Update tests to expect the new probe URL
- Persist backend output and desktop logs in development
- Delay dev window creation until backend is ready
- Move Electron scheme privilege registration into startup layer

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: desktopState.backendReady is never set to true
    • Added Ref.set(desktopState.backendReady, true) in the onReady callback and removed the manual workaround in the test that externally set it to true.

Create PR

Or push these changes by commenting:

@cursor push e5394e09b4
Preview (e5394e09b4)
diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts--- a/apps/desktop/src/backend/DesktopBackendManager.test.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts@@ -328,7 +328,6 @@
yield* manager.start;
assert.equal(yield* Queue.take(startedPids), 123);
yield* Deferred.await(ready);
- yield* Ref.set(backendReady, true);
assert.isTrue(yield* Ref.get(backendReady));
assert.deepEqual(yield* manager.currentConfig, Option.some(baseConfig));
diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts--- a/apps/desktop/src/backend/DesktopBackendManager.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.ts@@ -443,6 +443,7 @@
onSome: (run) => (run.id === runId ? true : latest.ready),
}),
}));
+ yield* Ref.set(desktopState.backendReady, true);
yield* desktopWindow.handleBackendReady.pipe(
Effect.catch((error) =>
Effect.logError("failed to open main window after backend readiness").pipe(

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 413d2f9. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Move trace sink and OTLP helpers into shared observability code
- Add desktop trace export and structured backend child logging
- Update server observability wiring and tests for new trace records
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Ignore stale ready signals from previous runs
- Skip scheduled restarts once desiredRunning is false
- Add regression coverage for stop cancelling a pending restart
- wrap startup, lifecycle, backend, IPC, and settings flows with spans
- remove the separate desktop-main.log file logger in favor of trace events
- keep observability tests aligned with span-based logging
Comment threadapps/desktop/src/electron/ElectronWindow.ts
Co-authored-by: codex <codex@users.noreply.github.com>
- Ignore windows that are destroyed before sync runs
- Add coverage for appearance sync filtering
- Introduce a shared component logger helper for desktop services
- Replace ad hoc log annotations across startup, backend, updates, menu, and window code
@juliusmarminge
juliusmarminge merged commit aa219be into mainMay 8, 2026
12 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/a1238be3 branch May 8, 2026 04:17
harshk242 added a commit to harshk242/t3code that referenced this pull request May 8, 2026
Major upstream change: desktop app ported to Effect (pingdotgg#2546). The old
monolithic apps/desktop/src/main.ts is replaced by Effect layers under
apps/desktop/src/app/, backend/, electron/, etc.
Re-applied fork's Linux desktop integration in the new structure:
- apps/desktop/src/app/DesktopApp.ts: set process.env.CHROME_DESKTOP =
linuxDesktopEntryName before appendCommandLineSwitch("class",
linuxWmClass) so GNOME launcher matching works.
- apps/desktop/src/app/DesktopAppIdentity.ts: setName(linuxWmClass) on
Linux instead of displayName so .deb installs pin to the dock under
the WM class advertised by the .desktop entry.
Upstream's appendCommandLineSwitch("class", linuxWmClass) was already
present, so that part of the fork's old block in main.ts is redundant.
The deb-specific build pieces (stageLinuxIcons multi-size hicolor PNGs,
homepage/author/maintainer fields, dist:desktop:deb script) merged
cleanly with no conflicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
darjss pushed a commit to darjss/t3code that referenced this pull request Aug 26, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
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:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@cursoragent
, '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

port desktop app to Effect - #2546

Merged
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3
May 8, 2026
Merged

port desktop app to Effect#2546
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Reworked desktop backend readiness probing around Effect-based services and effects, with temporary promise shims preserved for existing call sites.
  • Added Effect/Vitest coverage for readiness polling, request timeouts, abort handling, and backend startup race behavior.
  • Updated the desktop main process to use typed URL handling for backend endpoints and the new readiness error type.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test for apps/desktop/src/backendReadiness.test.ts and apps/desktop/src/backendStartupReadiness.test.ts

Note

High Risk
Large refactor of the Electron desktop main process, backend spawning/readiness, and lifecycle handling; mistakes could prevent the app from starting or shut down cleanly. Also changes runtime configuration/paths and protocol handling, which can impact packaging and local file access.

Overview
Ports the desktop Electron main process to an Effect-based architecture, introducing layered services for app identity/environment/config, lifecycle/shutdown coordination, observability/tracing, and Electron wrappers (app/dialog/menu/protocol).

Reworks desktop backend startup into Effect-managed spawning with fd-based bootstrap payloads, HTTP readiness polling, structured child-process output logging, and automatic restart scheduling/cancellation logic.

Cleans up legacy promise/imperative utilities and tests (e.g., backendPort, readiness helpers, settings/persistence/dialog helpers) in favor of new Effect/Vitest test coverage, and updates build hygiene (.gitignore consolidation) plus bumps electron to 41.5.0.

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

Note

Port desktop app to Effect by replacing imperative Electron code with layered Effect-TS services

  • Rewrites main.ts from imperative Electron startup code to a declarative Effect-TS layer composition, using NodeRuntime.runMain to execute a DesktopApp.program with all services provided via layers.
  • Introduces a large set of new Effect-based Electron service wrappers: ElectronApp, ElectronDialog, ElectronMenu, ElectronProtocol, ElectronSafeStorage, ElectronShell, ElectronTheme, ElectronUpdater, and ElectronWindow, each with typed errors and scoped resource management.
  • Adds desktop domain services (DesktopEnvironment, DesktopAppSettings, DesktopClientSettings, DesktopSavedEnvironments, DesktopBackendManager, DesktopBackendConfiguration, DesktopServerExposure, DesktopSshEnvironment, DesktopUpdates, DesktopWindow, etc.) as injectable Effect layers.
  • Moves shared observability utilities (trace sink, NDJSON tracer, compactTraceAttributes) from the server app to @t3tools/shared/observability, and moves DesktopBackendBootstrap schema to @t3tools/contracts.
  • Adds a comprehensive set of typed IPC handlers via DesktopIpc and installDesktopIpcHandlers, covering settings, SSH, server exposure, updates, and window operations; the preload bridge now sends structured object payloads instead of positional arguments.
  • Converts all Effect imports across packages/contracts, packages/ssh, packages/shared, packages/tailscale, and related scripts to namespaced form, enforced by @effect/language-service plugin added to multiple tsconfig.json files.
  • Risk: main.ts is a wholesale rewrite; any existing behavior not covered by the new layers (imperative window creation, old IPC handler signatures, title bar overlay config) is removed or changed.

Macroscope summarized 35835a7.

@coderabbitai

coderabbitaiBot commented May 6, 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

Run ID: 43ea1a9d-4a21-4540-b157-0dd7ba97ae0b

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
  • Commit unit tests in branch t3code/a1238be3

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

@github-actionsgithub-actionsBot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 6, 2026
Comment threadapps/desktop/src/backendReadiness.ts Outdated

@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 and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Timeout silently succeeds instead of failing with error
    • Added Option.match after Effect.timeoutOption to convert Option.None (timeout) into a BackendTimeoutError failure instead of silently succeeding, matching the established pattern used in all other timeoutOption call sites in the codebase.

Create PR

Or push these changes by commenting:

@cursor push 27f2b65c9b
Preview (27f2b65c9b)
diff --git a/apps/desktop/src/backendReadiness.ts b/apps/desktop/src/backendReadiness.ts--- a/apps/desktop/src/backendReadiness.ts+++ b/apps/desktop/src/backendReadiness.ts@@ -3,6 +3,7 @@
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Schedule from "effect/Schedule";
import { HttpClient } from "effect/unstable/http";
@@ -60,9 +61,13 @@
yield* client.get(requestUrl).pipe(
Effect.asVoid,
Effect.timeoutOption(timeout),
+ Effect.flatMap(+ Option.match({+ onNone: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),+ onSome: () => Effect.void,+ }),+ ),
Effect.catchTags({
- TimeoutError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),- // Maybe map this to different error kind?
HttpClientError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),
}),
);

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/backendReadiness.ts Outdated
@macroscopeapp

macroscopeappBot commented May 6, 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.

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

@cursor push 27f2b65

macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
@macroscopeapp
macroscopeappBot dismissed their stale reviewMay 6, 2026 07:59

Dismissing prior approval to re-evaluate b5c8ea4

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated

@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 and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Port fields lose range validation in bootstrap schema
    • Replaced Schema.Number with a shared PortSchema (Schema.Int with isBetween 1-65535) for both port and tailscaleServePort fields in DesktopBackendBootstrap, restoring the validation that was present in the old BootstrapEnvelopeSchema.
  • ✅ Fixed: Readiness timeout fires but no window ever created
    • Added fallback window creation in the onReadinessFailure handler so users get a window (showing reconnection state) instead of being stuck with no UI when the readiness probe times out.

Create PR

Or push these changes by commenting:

@cursor push 1291f41e07
Preview (1291f41e07)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts--- a/apps/desktop/src/main.ts+++ b/apps/desktop/src/main.ts@@ -1496,6 +1496,12 @@
`bootstrap backend readiness warning message=${formatErrorMessage(error)}`,
);
console.warn("[desktop] backend readiness check failed during bootstrap", error);
++ if (isDevelopment) return;+ const existingWindow = mainWindow ?? BrowserWindow.getAllWindows()[0] ?? null;+ if (existingWindow !== null) return;+ mainWindow = createWindow();+ writeDesktopLogHeader("bootstrap main window created (readiness timeout fallback)");
}),
onOutput: (streamName, chunk) =>
Effect.sync(() => {
diff --git a/apps/server/src/cli.ts b/apps/server/src/cli.ts--- a/apps/server/src/cli.ts+++ b/apps/server/src/cli.ts@@ -5,6 +5,7 @@
CommandId,
DesktopBackendBootstrap,
OrchestrationReadModel,
+ PortSchema,
ProjectId,
type ClientOrchestrationCommand,
} from "@t3tools/contracts";
@@ -67,8 +68,6 @@
import { WorkspacePaths } from "./workspace/Services/WorkspacePaths.ts";
import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";
-const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));-
const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(
Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
Flag.optional,
diff --git a/packages/contracts/src/baseSchemas.ts b/packages/contracts/src/baseSchemas.ts--- a/packages/contracts/src/baseSchemas.ts+++ b/packages/contracts/src/baseSchemas.ts@@ -5,6 +5,7 @@
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
export const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
+export const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));
export const IsoDateTime = Schema.String;
export type IsoDateTime = typeof IsoDateTime.Type;
diff --git a/packages/contracts/src/desktopBootstrap.ts b/packages/contracts/src/desktopBootstrap.ts--- a/packages/contracts/src/desktopBootstrap.ts+++ b/packages/contracts/src/desktopBootstrap.ts@@ -1,14 +1,16 @@
import { Schema } from "effect";
+import { PortSchema } from "./baseSchemas.ts";+
export const DesktopBackendBootstrap = Schema.Struct({
mode: Schema.Literal("desktop"),
noBrowser: Schema.Boolean,
- port: Schema.Number,+ port: PortSchema,
t3Home: Schema.String,
host: Schema.String,
desktopBootstrapToken: Schema.String,
tailscaleServeEnabled: Schema.Boolean,
- tailscaleServePort: Schema.Number,+ tailscaleServePort: PortSchema,
otlpTracesUrl: Schema.optional(Schema.String),
otlpMetricsUrl: Schema.optional(Schema.String),
});

You can send follow-ups to the cloud agent here.

Comment threadpackages/contracts/src/desktopBootstrap.ts Outdated
Comment threadapps/desktop/src/backendProcess.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backendPort.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
@cursor

cursorBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Start leaks restart fiber without interrupting it
    • Added a call to the existing cancelRestart helper before the state update in start(), which properly interrupts the restart fiber before clearing its reference, preventing the orphaned fiber leak.

Create PR

Or push these changes by commenting:

@cursor push fbac652b11
Preview (fbac652b11)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -232,11 +232,11 @@
.exists(config.entryPath)
.pipe(Effect.orElseSucceed(() => false));
+ yield* cancelRestart;
yield* Ref.update(state, (latest) => ({
...latest,
desiredRunning: true,
ready: false,
- restartFiber: Option.none(),
}));
if (!entryExists) {

You can send follow-ups to the cloud agent here.

@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 and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Restart backoff resets on spawn, not readiness
    • Moved the restartAttempt: 0 reset from the onStarted callback to the onReady callback so exponential backoff is maintained for backends that crash during initialization.

Create PR

Or push these changes by commenting:

@cursor push 269ea9ef0e
Preview (269ea9ef0e)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -324,16 +324,13 @@
...run,
pid: Option.some(pid),
}));
- yield* Ref.update(state, (latest) => ({- ...latest,- restartAttempt: 0,- }));
yield* events.onStarted({ pid, config });
}),
onReady: () =>
Effect.gen(function* () {
yield* Ref.update(state, (latest) => ({
...latest,
+ restartAttempt: 0,
ready: Option.match(latest.active, {
onNone: () => latest.ready,
onSome: (run) => (run.id === runId ? true : latest.ready),

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/desktopBackendManager.ts Outdated
- Remove the DesktopRun service and derive run IDs from scoped log annotations
- Update backend lifecycle and menu logging to use Effect logging helpers
- Adjust tests for backend restarts and menu behavior

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Misleading log emitted when backend start is skipped
    • Moved the log statement inside the conditional block and added a distinct "skipped" log for the quitting case, so logs now accurately reflect whether the backend was started or skipped.

Create PR

Or push these changes by commenting:

@cursor push 5f643f6923
Preview (5f643f6923)
diff --git a/apps/desktop/src/app/DesktopApp.ts b/apps/desktop/src/app/DesktopApp.ts--- a/apps/desktop/src/app/DesktopApp.ts+++ b/apps/desktop/src/app/DesktopApp.ts@@ -178,8 +178,10 @@
if (!(yield* Ref.get(state.quitting))) {
yield* backendManager.start;
+ yield* Effect.logInfo("bootstrap backend start requested");+ } else {+ yield* Effect.logInfo("bootstrap backend start skipped (quitting)");
}
- yield* Effect.logInfo("bootstrap backend start requested");
if (environment.isDevelopment) {
yield* desktopWindow.ensureMain;

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
- Switch desktop backend readiness checks to `/.well-known/t3/environment`
- Update tests to expect the new probe URL
- Persist backend output and desktop logs in development
- Delay dev window creation until backend is ready
- Move Electron scheme privilege registration into startup layer

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: desktopState.backendReady is never set to true
    • Added Ref.set(desktopState.backendReady, true) in the onReady callback and removed the manual workaround in the test that externally set it to true.

Create PR

Or push these changes by commenting:

@cursor push e5394e09b4
Preview (e5394e09b4)
diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts--- a/apps/desktop/src/backend/DesktopBackendManager.test.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts@@ -328,7 +328,6 @@
yield* manager.start;
assert.equal(yield* Queue.take(startedPids), 123);
yield* Deferred.await(ready);
- yield* Ref.set(backendReady, true);
assert.isTrue(yield* Ref.get(backendReady));
assert.deepEqual(yield* manager.currentConfig, Option.some(baseConfig));
diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts--- a/apps/desktop/src/backend/DesktopBackendManager.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.ts@@ -443,6 +443,7 @@
onSome: (run) => (run.id === runId ? true : latest.ready),
}),
}));
+ yield* Ref.set(desktopState.backendReady, true);
yield* desktopWindow.handleBackendReady.pipe(
Effect.catch((error) =>
Effect.logError("failed to open main window after backend readiness").pipe(

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 413d2f9. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Move trace sink and OTLP helpers into shared observability code
- Add desktop trace export and structured backend child logging
- Update server observability wiring and tests for new trace records
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Ignore stale ready signals from previous runs
- Skip scheduled restarts once desiredRunning is false
- Add regression coverage for stop cancelling a pending restart
- wrap startup, lifecycle, backend, IPC, and settings flows with spans
- remove the separate desktop-main.log file logger in favor of trace events
- keep observability tests aligned with span-based logging
Comment threadapps/desktop/src/electron/ElectronWindow.ts
Co-authored-by: codex <codex@users.noreply.github.com>
- Ignore windows that are destroyed before sync runs
- Add coverage for appearance sync filtering
- Introduce a shared component logger helper for desktop services
- Replace ad hoc log annotations across startup, backend, updates, menu, and window code
@juliusmarminge
juliusmarminge merged commit aa219be into mainMay 8, 2026
12 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/a1238be3 branch May 8, 2026 04:17
harshk242 added a commit to harshk242/t3code that referenced this pull request May 8, 2026
Major upstream change: desktop app ported to Effect (pingdotgg#2546). The old
monolithic apps/desktop/src/main.ts is replaced by Effect layers under
apps/desktop/src/app/, backend/, electron/, etc.
Re-applied fork's Linux desktop integration in the new structure:
- apps/desktop/src/app/DesktopApp.ts: set process.env.CHROME_DESKTOP =
linuxDesktopEntryName before appendCommandLineSwitch("class",
linuxWmClass) so GNOME launcher matching works.
- apps/desktop/src/app/DesktopAppIdentity.ts: setName(linuxWmClass) on
Linux instead of displayName so .deb installs pin to the dock under
the WM class advertised by the .desktop entry.
Upstream's appendCommandLineSwitch("class", linuxWmClass) was already
present, so that part of the fork's old block in main.ts is redundant.
The deb-specific build pieces (stageLinuxIcons multi-size hicolor PNGs,
homepage/author/maintainer fields, dist:desktop:deb script) merged
cleanly with no conflicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
darjss pushed a commit to darjss/t3code that referenced this pull request Aug 26, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
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:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@cursoragent
, '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

port desktop app to Effect - #2546

Merged
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3
May 8, 2026
Merged

port desktop app to Effect#2546
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Reworked desktop backend readiness probing around Effect-based services and effects, with temporary promise shims preserved for existing call sites.
  • Added Effect/Vitest coverage for readiness polling, request timeouts, abort handling, and backend startup race behavior.
  • Updated the desktop main process to use typed URL handling for backend endpoints and the new readiness error type.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test for apps/desktop/src/backendReadiness.test.ts and apps/desktop/src/backendStartupReadiness.test.ts

Note

High Risk
Large refactor of the Electron desktop main process, backend spawning/readiness, and lifecycle handling; mistakes could prevent the app from starting or shut down cleanly. Also changes runtime configuration/paths and protocol handling, which can impact packaging and local file access.

Overview
Ports the desktop Electron main process to an Effect-based architecture, introducing layered services for app identity/environment/config, lifecycle/shutdown coordination, observability/tracing, and Electron wrappers (app/dialog/menu/protocol).

Reworks desktop backend startup into Effect-managed spawning with fd-based bootstrap payloads, HTTP readiness polling, structured child-process output logging, and automatic restart scheduling/cancellation logic.

Cleans up legacy promise/imperative utilities and tests (e.g., backendPort, readiness helpers, settings/persistence/dialog helpers) in favor of new Effect/Vitest test coverage, and updates build hygiene (.gitignore consolidation) plus bumps electron to 41.5.0.

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

Note

Port desktop app to Effect by replacing imperative Electron code with layered Effect-TS services

  • Rewrites main.ts from imperative Electron startup code to a declarative Effect-TS layer composition, using NodeRuntime.runMain to execute a DesktopApp.program with all services provided via layers.
  • Introduces a large set of new Effect-based Electron service wrappers: ElectronApp, ElectronDialog, ElectronMenu, ElectronProtocol, ElectronSafeStorage, ElectronShell, ElectronTheme, ElectronUpdater, and ElectronWindow, each with typed errors and scoped resource management.
  • Adds desktop domain services (DesktopEnvironment, DesktopAppSettings, DesktopClientSettings, DesktopSavedEnvironments, DesktopBackendManager, DesktopBackendConfiguration, DesktopServerExposure, DesktopSshEnvironment, DesktopUpdates, DesktopWindow, etc.) as injectable Effect layers.
  • Moves shared observability utilities (trace sink, NDJSON tracer, compactTraceAttributes) from the server app to @t3tools/shared/observability, and moves DesktopBackendBootstrap schema to @t3tools/contracts.
  • Adds a comprehensive set of typed IPC handlers via DesktopIpc and installDesktopIpcHandlers, covering settings, SSH, server exposure, updates, and window operations; the preload bridge now sends structured object payloads instead of positional arguments.
  • Converts all Effect imports across packages/contracts, packages/ssh, packages/shared, packages/tailscale, and related scripts to namespaced form, enforced by @effect/language-service plugin added to multiple tsconfig.json files.
  • Risk: main.ts is a wholesale rewrite; any existing behavior not covered by the new layers (imperative window creation, old IPC handler signatures, title bar overlay config) is removed or changed.

Macroscope summarized 35835a7.

@coderabbitai

coderabbitaiBot commented May 6, 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

Run ID: 43ea1a9d-4a21-4540-b157-0dd7ba97ae0b

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
  • Commit unit tests in branch t3code/a1238be3

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

@github-actionsgithub-actionsBot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 6, 2026
Comment threadapps/desktop/src/backendReadiness.ts Outdated

@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 and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Timeout silently succeeds instead of failing with error
    • Added Option.match after Effect.timeoutOption to convert Option.None (timeout) into a BackendTimeoutError failure instead of silently succeeding, matching the established pattern used in all other timeoutOption call sites in the codebase.

Create PR

Or push these changes by commenting:

@cursor push 27f2b65c9b
Preview (27f2b65c9b)
diff --git a/apps/desktop/src/backendReadiness.ts b/apps/desktop/src/backendReadiness.ts--- a/apps/desktop/src/backendReadiness.ts+++ b/apps/desktop/src/backendReadiness.ts@@ -3,6 +3,7 @@
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Schedule from "effect/Schedule";
import { HttpClient } from "effect/unstable/http";
@@ -60,9 +61,13 @@
yield* client.get(requestUrl).pipe(
Effect.asVoid,
Effect.timeoutOption(timeout),
+ Effect.flatMap(+ Option.match({+ onNone: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),+ onSome: () => Effect.void,+ }),+ ),
Effect.catchTags({
- TimeoutError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),- // Maybe map this to different error kind?
HttpClientError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),
}),
);

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/backendReadiness.ts Outdated
@macroscopeapp

macroscopeappBot commented May 6, 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.

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

@cursor push 27f2b65

macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
@macroscopeapp
macroscopeappBot dismissed their stale reviewMay 6, 2026 07:59

Dismissing prior approval to re-evaluate b5c8ea4

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated

@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 and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Port fields lose range validation in bootstrap schema
    • Replaced Schema.Number with a shared PortSchema (Schema.Int with isBetween 1-65535) for both port and tailscaleServePort fields in DesktopBackendBootstrap, restoring the validation that was present in the old BootstrapEnvelopeSchema.
  • ✅ Fixed: Readiness timeout fires but no window ever created
    • Added fallback window creation in the onReadinessFailure handler so users get a window (showing reconnection state) instead of being stuck with no UI when the readiness probe times out.

Create PR

Or push these changes by commenting:

@cursor push 1291f41e07
Preview (1291f41e07)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts--- a/apps/desktop/src/main.ts+++ b/apps/desktop/src/main.ts@@ -1496,6 +1496,12 @@
`bootstrap backend readiness warning message=${formatErrorMessage(error)}`,
);
console.warn("[desktop] backend readiness check failed during bootstrap", error);
++ if (isDevelopment) return;+ const existingWindow = mainWindow ?? BrowserWindow.getAllWindows()[0] ?? null;+ if (existingWindow !== null) return;+ mainWindow = createWindow();+ writeDesktopLogHeader("bootstrap main window created (readiness timeout fallback)");
}),
onOutput: (streamName, chunk) =>
Effect.sync(() => {
diff --git a/apps/server/src/cli.ts b/apps/server/src/cli.ts--- a/apps/server/src/cli.ts+++ b/apps/server/src/cli.ts@@ -5,6 +5,7 @@
CommandId,
DesktopBackendBootstrap,
OrchestrationReadModel,
+ PortSchema,
ProjectId,
type ClientOrchestrationCommand,
} from "@t3tools/contracts";
@@ -67,8 +68,6 @@
import { WorkspacePaths } from "./workspace/Services/WorkspacePaths.ts";
import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";
-const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));-
const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(
Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
Flag.optional,
diff --git a/packages/contracts/src/baseSchemas.ts b/packages/contracts/src/baseSchemas.ts--- a/packages/contracts/src/baseSchemas.ts+++ b/packages/contracts/src/baseSchemas.ts@@ -5,6 +5,7 @@
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
export const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
+export const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));
export const IsoDateTime = Schema.String;
export type IsoDateTime = typeof IsoDateTime.Type;
diff --git a/packages/contracts/src/desktopBootstrap.ts b/packages/contracts/src/desktopBootstrap.ts--- a/packages/contracts/src/desktopBootstrap.ts+++ b/packages/contracts/src/desktopBootstrap.ts@@ -1,14 +1,16 @@
import { Schema } from "effect";
+import { PortSchema } from "./baseSchemas.ts";+
export const DesktopBackendBootstrap = Schema.Struct({
mode: Schema.Literal("desktop"),
noBrowser: Schema.Boolean,
- port: Schema.Number,+ port: PortSchema,
t3Home: Schema.String,
host: Schema.String,
desktopBootstrapToken: Schema.String,
tailscaleServeEnabled: Schema.Boolean,
- tailscaleServePort: Schema.Number,+ tailscaleServePort: PortSchema,
otlpTracesUrl: Schema.optional(Schema.String),
otlpMetricsUrl: Schema.optional(Schema.String),
});

You can send follow-ups to the cloud agent here.

Comment threadpackages/contracts/src/desktopBootstrap.ts Outdated
Comment threadapps/desktop/src/backendProcess.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backendPort.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
@cursor

cursorBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Start leaks restart fiber without interrupting it
    • Added a call to the existing cancelRestart helper before the state update in start(), which properly interrupts the restart fiber before clearing its reference, preventing the orphaned fiber leak.

Create PR

Or push these changes by commenting:

@cursor push fbac652b11
Preview (fbac652b11)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -232,11 +232,11 @@
.exists(config.entryPath)
.pipe(Effect.orElseSucceed(() => false));
+ yield* cancelRestart;
yield* Ref.update(state, (latest) => ({
...latest,
desiredRunning: true,
ready: false,
- restartFiber: Option.none(),
}));
if (!entryExists) {

You can send follow-ups to the cloud agent here.

@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 and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Restart backoff resets on spawn, not readiness
    • Moved the restartAttempt: 0 reset from the onStarted callback to the onReady callback so exponential backoff is maintained for backends that crash during initialization.

Create PR

Or push these changes by commenting:

@cursor push 269ea9ef0e
Preview (269ea9ef0e)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -324,16 +324,13 @@
...run,
pid: Option.some(pid),
}));
- yield* Ref.update(state, (latest) => ({- ...latest,- restartAttempt: 0,- }));
yield* events.onStarted({ pid, config });
}),
onReady: () =>
Effect.gen(function* () {
yield* Ref.update(state, (latest) => ({
...latest,
+ restartAttempt: 0,
ready: Option.match(latest.active, {
onNone: () => latest.ready,
onSome: (run) => (run.id === runId ? true : latest.ready),

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/desktopBackendManager.ts Outdated
- Remove the DesktopRun service and derive run IDs from scoped log annotations
- Update backend lifecycle and menu logging to use Effect logging helpers
- Adjust tests for backend restarts and menu behavior

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Misleading log emitted when backend start is skipped
    • Moved the log statement inside the conditional block and added a distinct "skipped" log for the quitting case, so logs now accurately reflect whether the backend was started or skipped.

Create PR

Or push these changes by commenting:

@cursor push 5f643f6923
Preview (5f643f6923)
diff --git a/apps/desktop/src/app/DesktopApp.ts b/apps/desktop/src/app/DesktopApp.ts--- a/apps/desktop/src/app/DesktopApp.ts+++ b/apps/desktop/src/app/DesktopApp.ts@@ -178,8 +178,10 @@
if (!(yield* Ref.get(state.quitting))) {
yield* backendManager.start;
+ yield* Effect.logInfo("bootstrap backend start requested");+ } else {+ yield* Effect.logInfo("bootstrap backend start skipped (quitting)");
}
- yield* Effect.logInfo("bootstrap backend start requested");
if (environment.isDevelopment) {
yield* desktopWindow.ensureMain;

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
- Switch desktop backend readiness checks to `/.well-known/t3/environment`
- Update tests to expect the new probe URL
- Persist backend output and desktop logs in development
- Delay dev window creation until backend is ready
- Move Electron scheme privilege registration into startup layer

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: desktopState.backendReady is never set to true
    • Added Ref.set(desktopState.backendReady, true) in the onReady callback and removed the manual workaround in the test that externally set it to true.

Create PR

Or push these changes by commenting:

@cursor push e5394e09b4
Preview (e5394e09b4)
diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts--- a/apps/desktop/src/backend/DesktopBackendManager.test.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts@@ -328,7 +328,6 @@
yield* manager.start;
assert.equal(yield* Queue.take(startedPids), 123);
yield* Deferred.await(ready);
- yield* Ref.set(backendReady, true);
assert.isTrue(yield* Ref.get(backendReady));
assert.deepEqual(yield* manager.currentConfig, Option.some(baseConfig));
diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts--- a/apps/desktop/src/backend/DesktopBackendManager.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.ts@@ -443,6 +443,7 @@
onSome: (run) => (run.id === runId ? true : latest.ready),
}),
}));
+ yield* Ref.set(desktopState.backendReady, true);
yield* desktopWindow.handleBackendReady.pipe(
Effect.catch((error) =>
Effect.logError("failed to open main window after backend readiness").pipe(

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 413d2f9. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Move trace sink and OTLP helpers into shared observability code
- Add desktop trace export and structured backend child logging
- Update server observability wiring and tests for new trace records
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Ignore stale ready signals from previous runs
- Skip scheduled restarts once desiredRunning is false
- Add regression coverage for stop cancelling a pending restart
- wrap startup, lifecycle, backend, IPC, and settings flows with spans
- remove the separate desktop-main.log file logger in favor of trace events
- keep observability tests aligned with span-based logging
Comment threadapps/desktop/src/electron/ElectronWindow.ts
Co-authored-by: codex <codex@users.noreply.github.com>
- Ignore windows that are destroyed before sync runs
- Add coverage for appearance sync filtering
- Introduce a shared component logger helper for desktop services
- Replace ad hoc log annotations across startup, backend, updates, menu, and window code
@juliusmarminge
juliusmarminge merged commit aa219be into mainMay 8, 2026
12 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/a1238be3 branch May 8, 2026 04:17
harshk242 added a commit to harshk242/t3code that referenced this pull request May 8, 2026
Major upstream change: desktop app ported to Effect (pingdotgg#2546). The old
monolithic apps/desktop/src/main.ts is replaced by Effect layers under
apps/desktop/src/app/, backend/, electron/, etc.
Re-applied fork's Linux desktop integration in the new structure:
- apps/desktop/src/app/DesktopApp.ts: set process.env.CHROME_DESKTOP =
linuxDesktopEntryName before appendCommandLineSwitch("class",
linuxWmClass) so GNOME launcher matching works.
- apps/desktop/src/app/DesktopAppIdentity.ts: setName(linuxWmClass) on
Linux instead of displayName so .deb installs pin to the dock under
the WM class advertised by the .desktop entry.
Upstream's appendCommandLineSwitch("class", linuxWmClass) was already
present, so that part of the fork's old block in main.ts is redundant.
The deb-specific build pieces (stageLinuxIcons multi-size hicolor PNGs,
homepage/author/maintainer fields, dist:desktop:deb script) merged
cleanly with no conflicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
darjss pushed a commit to darjss/t3code that referenced this pull request Aug 26, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
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:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@cursoragent
, '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

port desktop app to Effect - #2546

Merged
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3
May 8, 2026
Merged

port desktop app to Effect#2546
juliusmarminge merged 44 commits into
mainfrom
t3code/a1238be3

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Reworked desktop backend readiness probing around Effect-based services and effects, with temporary promise shims preserved for existing call sites.
  • Added Effect/Vitest coverage for readiness polling, request timeouts, abort handling, and backend startup race behavior.
  • Updated the desktop main process to use typed URL handling for backend endpoints and the new readiness error type.

Testing

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test for apps/desktop/src/backendReadiness.test.ts and apps/desktop/src/backendStartupReadiness.test.ts

Note

High Risk
Large refactor of the Electron desktop main process, backend spawning/readiness, and lifecycle handling; mistakes could prevent the app from starting or shut down cleanly. Also changes runtime configuration/paths and protocol handling, which can impact packaging and local file access.

Overview
Ports the desktop Electron main process to an Effect-based architecture, introducing layered services for app identity/environment/config, lifecycle/shutdown coordination, observability/tracing, and Electron wrappers (app/dialog/menu/protocol).

Reworks desktop backend startup into Effect-managed spawning with fd-based bootstrap payloads, HTTP readiness polling, structured child-process output logging, and automatic restart scheduling/cancellation logic.

Cleans up legacy promise/imperative utilities and tests (e.g., backendPort, readiness helpers, settings/persistence/dialog helpers) in favor of new Effect/Vitest test coverage, and updates build hygiene (.gitignore consolidation) plus bumps electron to 41.5.0.

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

Note

Port desktop app to Effect by replacing imperative Electron code with layered Effect-TS services

  • Rewrites main.ts from imperative Electron startup code to a declarative Effect-TS layer composition, using NodeRuntime.runMain to execute a DesktopApp.program with all services provided via layers.
  • Introduces a large set of new Effect-based Electron service wrappers: ElectronApp, ElectronDialog, ElectronMenu, ElectronProtocol, ElectronSafeStorage, ElectronShell, ElectronTheme, ElectronUpdater, and ElectronWindow, each with typed errors and scoped resource management.
  • Adds desktop domain services (DesktopEnvironment, DesktopAppSettings, DesktopClientSettings, DesktopSavedEnvironments, DesktopBackendManager, DesktopBackendConfiguration, DesktopServerExposure, DesktopSshEnvironment, DesktopUpdates, DesktopWindow, etc.) as injectable Effect layers.
  • Moves shared observability utilities (trace sink, NDJSON tracer, compactTraceAttributes) from the server app to @t3tools/shared/observability, and moves DesktopBackendBootstrap schema to @t3tools/contracts.
  • Adds a comprehensive set of typed IPC handlers via DesktopIpc and installDesktopIpcHandlers, covering settings, SSH, server exposure, updates, and window operations; the preload bridge now sends structured object payloads instead of positional arguments.
  • Converts all Effect imports across packages/contracts, packages/ssh, packages/shared, packages/tailscale, and related scripts to namespaced form, enforced by @effect/language-service plugin added to multiple tsconfig.json files.
  • Risk: main.ts is a wholesale rewrite; any existing behavior not covered by the new layers (imperative window creation, old IPC handler signatures, title bar overlay config) is removed or changed.

Macroscope summarized 35835a7.

@coderabbitai

coderabbitaiBot commented May 6, 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

Run ID: 43ea1a9d-4a21-4540-b157-0dd7ba97ae0b

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
  • Commit unit tests in branch t3code/a1238be3

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

@github-actionsgithub-actionsBot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 6, 2026
Comment threadapps/desktop/src/backendReadiness.ts Outdated

@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 and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Timeout silently succeeds instead of failing with error
    • Added Option.match after Effect.timeoutOption to convert Option.None (timeout) into a BackendTimeoutError failure instead of silently succeeding, matching the established pattern used in all other timeoutOption call sites in the codebase.

Create PR

Or push these changes by commenting:

@cursor push 27f2b65c9b
Preview (27f2b65c9b)
diff --git a/apps/desktop/src/backendReadiness.ts b/apps/desktop/src/backendReadiness.ts--- a/apps/desktop/src/backendReadiness.ts+++ b/apps/desktop/src/backendReadiness.ts@@ -3,6 +3,7 @@
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as Schedule from "effect/Schedule";
import { HttpClient } from "effect/unstable/http";
@@ -60,9 +61,13 @@
yield* client.get(requestUrl).pipe(
Effect.asVoid,
Effect.timeoutOption(timeout),
+ Effect.flatMap(+ Option.match({+ onNone: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),+ onSome: () => Effect.void,+ }),+ ),
Effect.catchTags({
- TimeoutError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),- // Maybe map this to different error kind?
HttpClientError: () => Effect.fail(new BackendTimeoutError({ url: baseUrl })),
}),
);

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/backendReadiness.ts Outdated
@macroscopeapp

macroscopeappBot commented May 6, 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.

@juliusmarminge

Copy link
Copy Markdown
MemberAuthor

@cursor push 27f2b65

macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
macroscopeapp[bot]
macroscopeappBot previously approved these changes May 6, 2026
@macroscopeapp
macroscopeappBot dismissed their stale reviewMay 6, 2026 07:59

Dismissing prior approval to re-evaluate b5c8ea4

@github-actionsgithub-actionsBot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated

@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 and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Port fields lose range validation in bootstrap schema
    • Replaced Schema.Number with a shared PortSchema (Schema.Int with isBetween 1-65535) for both port and tailscaleServePort fields in DesktopBackendBootstrap, restoring the validation that was present in the old BootstrapEnvelopeSchema.
  • ✅ Fixed: Readiness timeout fires but no window ever created
    • Added fallback window creation in the onReadinessFailure handler so users get a window (showing reconnection state) instead of being stuck with no UI when the readiness probe times out.

Create PR

Or push these changes by commenting:

@cursor push 1291f41e07
Preview (1291f41e07)
diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts--- a/apps/desktop/src/main.ts+++ b/apps/desktop/src/main.ts@@ -1496,6 +1496,12 @@
`bootstrap backend readiness warning message=${formatErrorMessage(error)}`,
);
console.warn("[desktop] backend readiness check failed during bootstrap", error);
++ if (isDevelopment) return;+ const existingWindow = mainWindow ?? BrowserWindow.getAllWindows()[0] ?? null;+ if (existingWindow !== null) return;+ mainWindow = createWindow();+ writeDesktopLogHeader("bootstrap main window created (readiness timeout fallback)");
}),
onOutput: (streamName, chunk) =>
Effect.sync(() => {
diff --git a/apps/server/src/cli.ts b/apps/server/src/cli.ts--- a/apps/server/src/cli.ts+++ b/apps/server/src/cli.ts@@ -5,6 +5,7 @@
CommandId,
DesktopBackendBootstrap,
OrchestrationReadModel,
+ PortSchema,
ProjectId,
type ClientOrchestrationCommand,
} from "@t3tools/contracts";
@@ -67,8 +68,6 @@
import { WorkspacePaths } from "./workspace/Services/WorkspacePaths.ts";
import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";
-const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));-
const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(
Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
Flag.optional,
diff --git a/packages/contracts/src/baseSchemas.ts b/packages/contracts/src/baseSchemas.ts--- a/packages/contracts/src/baseSchemas.ts+++ b/packages/contracts/src/baseSchemas.ts@@ -5,6 +5,7 @@
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
export const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
+export const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }));
export const IsoDateTime = Schema.String;
export type IsoDateTime = typeof IsoDateTime.Type;
diff --git a/packages/contracts/src/desktopBootstrap.ts b/packages/contracts/src/desktopBootstrap.ts--- a/packages/contracts/src/desktopBootstrap.ts+++ b/packages/contracts/src/desktopBootstrap.ts@@ -1,14 +1,16 @@
import { Schema } from "effect";
+import { PortSchema } from "./baseSchemas.ts";+
export const DesktopBackendBootstrap = Schema.Struct({
mode: Schema.Literal("desktop"),
noBrowser: Schema.Boolean,
- port: Schema.Number,+ port: PortSchema,
t3Home: Schema.String,
host: Schema.String,
desktopBootstrapToken: Schema.String,
tailscaleServeEnabled: Schema.Boolean,
- tailscaleServePort: Schema.Number,+ tailscaleServePort: PortSchema,
otlpTracesUrl: Schema.optional(Schema.String),
otlpMetricsUrl: Schema.optional(Schema.String),
});

You can send follow-ups to the cloud agent here.

Comment threadpackages/contracts/src/desktopBootstrap.ts Outdated
Comment threadapps/desktop/src/backendProcess.ts Outdated
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels May 6, 2026
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/main.ts Outdated
Comment threadapps/desktop/src/backendPort.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
@cursor

cursorBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Start leaks restart fiber without interrupting it
    • Added a call to the existing cancelRestart helper before the state update in start(), which properly interrupts the restart fiber before clearing its reference, preventing the orphaned fiber leak.

Create PR

Or push these changes by commenting:

@cursor push fbac652b11
Preview (fbac652b11)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -232,11 +232,11 @@
.exists(config.entryPath)
.pipe(Effect.orElseSucceed(() => false));
+ yield* cancelRestart;
yield* Ref.update(state, (latest) => ({
...latest,
desiredRunning: true,
ready: false,
- restartFiber: Option.none(),
}));
if (!entryExists) {

You can send follow-ups to the cloud agent here.

@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 and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Restart backoff resets on spawn, not readiness
    • Moved the restartAttempt: 0 reset from the onStarted callback to the onReady callback so exponential backoff is maintained for backends that crash during initialization.

Create PR

Or push these changes by commenting:

@cursor push 269ea9ef0e
Preview (269ea9ef0e)
diff --git a/apps/desktop/src/desktopBackendManager.ts b/apps/desktop/src/desktopBackendManager.ts--- a/apps/desktop/src/desktopBackendManager.ts+++ b/apps/desktop/src/desktopBackendManager.ts@@ -324,16 +324,13 @@
...run,
pid: Option.some(pid),
}));
- yield* Ref.update(state, (latest) => ({- ...latest,- restartAttempt: 0,- }));
yield* events.onStarted({ pid, config });
}),
onReady: () =>
Effect.gen(function* () {
yield* Ref.update(state, (latest) => ({
...latest,
+ restartAttempt: 0,
ready: Option.match(latest.active, {
onNone: () => latest.ready,
onSome: (run) => (run.id === runId ? true : latest.ready),

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/desktopBackendManager.ts Outdated
- Remove the DesktopRun service and derive run IDs from scoped log annotations
- Update backend lifecycle and menu logging to use Effect logging helpers
- Adjust tests for backend restarts and menu behavior

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Misleading log emitted when backend start is skipped
    • Moved the log statement inside the conditional block and added a distinct "skipped" log for the quitting case, so logs now accurately reflect whether the backend was started or skipped.

Create PR

Or push these changes by commenting:

@cursor push 5f643f6923
Preview (5f643f6923)
diff --git a/apps/desktop/src/app/DesktopApp.ts b/apps/desktop/src/app/DesktopApp.ts--- a/apps/desktop/src/app/DesktopApp.ts+++ b/apps/desktop/src/app/DesktopApp.ts@@ -178,8 +178,10 @@
if (!(yield* Ref.get(state.quitting))) {
yield* backendManager.start;
+ yield* Effect.logInfo("bootstrap backend start requested");+ } else {+ yield* Effect.logInfo("bootstrap backend start skipped (quitting)");
}
- yield* Effect.logInfo("bootstrap backend start requested");
if (environment.isDevelopment) {
yield* desktopWindow.ensureMain;

You can send follow-ups to the cloud agent here.

Comment threadapps/desktop/src/app/DesktopApp.ts Outdated
- Switch desktop backend readiness checks to `/.well-known/t3/environment`
- Update tests to expect the new probe URL
- Persist backend output and desktop logs in development
- Delay dev window creation until backend is ready
- Move Electron scheme privilege registration into startup layer

@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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: desktopState.backendReady is never set to true
    • Added Ref.set(desktopState.backendReady, true) in the onReady callback and removed the manual workaround in the test that externally set it to true.

Create PR

Or push these changes by commenting:

@cursor push e5394e09b4
Preview (e5394e09b4)
diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts--- a/apps/desktop/src/backend/DesktopBackendManager.test.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts@@ -328,7 +328,6 @@
yield* manager.start;
assert.equal(yield* Queue.take(startedPids), 123);
yield* Deferred.await(ready);
- yield* Ref.set(backendReady, true);
assert.isTrue(yield* Ref.get(backendReady));
assert.deepEqual(yield* manager.currentConfig, Option.some(baseConfig));
diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts--- a/apps/desktop/src/backend/DesktopBackendManager.ts+++ b/apps/desktop/src/backend/DesktopBackendManager.ts@@ -443,6 +443,7 @@
onSome: (run) => (run.id === runId ? true : latest.ready),
}),
}));
+ yield* Ref.set(desktopState.backendReady, true);
yield* desktopWindow.handleBackendReady.pipe(
Effect.catch((error) =>
Effect.logError("failed to open main window after backend readiness").pipe(

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 413d2f9. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Move trace sink and OTLP helpers into shared observability code
- Add desktop trace export and structured backend child logging
- Update server observability wiring and tests for new trace records
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
- Ignore stale ready signals from previous runs
- Skip scheduled restarts once desiredRunning is false
- Add regression coverage for stop cancelling a pending restart
- wrap startup, lifecycle, backend, IPC, and settings flows with spans
- remove the separate desktop-main.log file logger in favor of trace events
- keep observability tests aligned with span-based logging
Comment threadapps/desktop/src/electron/ElectronWindow.ts
Co-authored-by: codex <codex@users.noreply.github.com>
- Ignore windows that are destroyed before sync runs
- Add coverage for appearance sync filtering
- Introduce a shared component logger helper for desktop services
- Replace ad hoc log annotations across startup, backend, updates, menu, and window code
@juliusmarminge
juliusmarminge merged commit aa219be into mainMay 8, 2026
12 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/a1238be3 branch May 8, 2026 04:17
harshk242 added a commit to harshk242/t3code that referenced this pull request May 8, 2026
Major upstream change: desktop app ported to Effect (pingdotgg#2546). The old
monolithic apps/desktop/src/main.ts is replaced by Effect layers under
apps/desktop/src/app/, backend/, electron/, etc.
Re-applied fork's Linux desktop integration in the new structure:
- apps/desktop/src/app/DesktopApp.ts: set process.env.CHROME_DESKTOP =
linuxDesktopEntryName before appendCommandLineSwitch("class",
linuxWmClass) so GNOME launcher matching works.
- apps/desktop/src/app/DesktopAppIdentity.ts: setName(linuxWmClass) on
Linux instead of displayName so .deb installs pin to the dock under
the WM class advertised by the .desktop entry.
Upstream's appendCommandLineSwitch("class", linuxWmClass) was already
present, so that part of the fork's old block in main.ts is redundant.
The deb-specific build pieces (stageLinuxIcons multi-size hicolor PNGs,
homepage/author/maintainer fields, dist:desktop:deb script) merged
cleanly with no conflicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
darjss pushed a commit to darjss/t3code that referenced this pull request Aug 26, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
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:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@cursoragent