Skip to content

feat(desktop): add global default editor setting#1649

Merged
Kitenite merged 5 commits into
superset-sh:mainfrom
de1mat:feat/global-default-editor
Feb 23, 2026
Merged

feat(desktop): add global default editor setting#1649
Kitenite merged 5 commits into
superset-sh:mainfrom
de1mat:feat/global-default-editor

Conversation

@de1mat
Copy link
Copy Markdown
Contributor

@de1mat de1mat commented Feb 21, 2026

Description

Adds a global default editor setting that acts as a fallback when no per-project default is configured. The first time a user opens any project in an editor, that editor is automatically saved as the global default. Non-editor apps (Finder, Terminal, iTerm, Warp, Ghostty) are filtered out.

Resolution order: per-project default -> global default -> error with clear toast message.

Related Issues

Closes #1647

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

  • Verified the app starts and renders correctly with the new migration
  • Open a project in VS Code -> global default is auto-set
  • Open a project in Finder -> global default is NOT set (non-editor filter)
  • Settings page ClickablePath shows "Default" badge next to the global default
  • Open-in-editor hotkey is a no-op when no default is configured
  • File context menu shows toast error when no editor configured

Additional Notes

Schema change: Adds default_editor text column to the settings table via migration 0031_add_default_editor_setting.sql.

Key files:

  • external/index.ts - resolveDefaultEditor() and ensureGlobalDefaultEditor() helpers, NON_EDITOR_APPS filter
  • settings/index.ts - New getDefaultEditor/setDefaultEditor tRPC endpoints
  • projects.ts - Replaced inline fallback logic with shared resolveDefaultEditor() import
  • UI components - Null-safe handling with fallback icons, disabled states, and toast errors

Summary by CodeRabbit

Release Notes

  • New Features

    • Added global default editor configuration in settings
    • Default editor automatically applied when opening files and paths
    • Per-project editor selection with global fallback support
  • Improvements

    • Error notifications now displayed when no default editor is configured
    • Validation prevents non-editor applications from being set as default

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

This PR implements a global default editor setting feature. It adds a database column for storing a global default editor, creates settings router procedures to manage it, introduces a resolver function that prioritizes project-level then global defaults, and updates UI components to use this resolver with proper fallback handling.

Changes

Cohort / File(s) Summary
Database Schema
packages/local-db/src/schema/schema.ts, packages/local-db/src/schema/zod.ts, packages/local-db/drizzle/0033_nosy_overlord.sql, packages/local-db/drizzle/meta/*
Adds defaultEditor column to settings table; introduces NON_EDITOR_APPS constant listing non-editor applications that cannot be set as global default.
External Router Core Logic
apps/desktop/src/lib/trpc/routers/external/index.ts
Introduces resolveDefaultEditor(projectId?) function implementing cascading resolution: project-level default → global default → null; adds per-project persistence after successful app launch; ensures non-editor apps cannot be saved as global default via ensureGlobalDefaultEditor() helper.
Settings and Projects Routers
apps/desktop/src/lib/trpc/routers/settings/index.ts, apps/desktop/src/lib/trpc/routers/projects/projects.ts
Adds getDefaultEditor() and setDefaultEditor() public procedures with validation preventing non-editor apps from being set globally; updates getDefaultApp in projects router to use resolveDefaultEditor() instead of direct DB lookup.
Dashboard Components
apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OpenInMenuButton/OpenInMenuButton.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
Removes hardcoded fallback defaults; updates conditional rendering to handle optional defaultApp; modifies getAppOption() to return undefined when app not found; gates UI elements and hotkey behavior on defaultApp presence; includes cache invalidation on successful app open when projectId is available.
Settings and Path Actions
apps/desktop/src/renderer/routes/_authenticated/settings/components/ClickablePath/ClickablePath.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/usePathActions.ts
Replaces static default editor with runtime query via getDefaultEditor(); adds onSuccess cache invalidation; introduces error toasts for missing default editor configuration; implements conditional logic to handle scenarios where no default is available.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as UI Component
    participant Resolver as resolveDefaultEditor()
    participant DB as Database
    participant App as External App
    
    User->>UI: Request to open in editor
    UI->>Resolver: resolveDefaultEditor(projectId)
    Resolver->>DB: Query project settings<br/>(if projectId provided)
    DB-->>Resolver: project defaultApp or null
    
    alt Project default found
        Resolver-->>UI: Return project default
    else No project default
        Resolver->>DB: Query global settings
        DB-->>Resolver: global defaultEditor or null
        Resolver-->>UI: Return global default or null
    end
    
    alt Default app exists
        UI->>App: Launch app with path
        App-->>UI: Success
        UI->>DB: Persist as project default<br/>(if projectId available)
        UI->>DB: Persist as global default<br/>(via ensureGlobalDefaultEditor)
    else No default configured
        UI-->>User: Error: Select editor<br/>from dropdown
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #1598: Modifies the same Open-in/IDE resolution code paths—implements per-project defaults while this PR adds global default layering with cascading resolution priority.

Poem

🐰 Hops with glee through editor defaults,
Per-project first, then global fallback—
No more hardcoded paths so cold,
Just ask the settings, watch the magic unfold!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(desktop): add global default editor setting' clearly and concisely summarizes the main change - adding a new global default editor setting feature for the desktop app.
Description check ✅ Passed The PR description is comprehensive and follows the template structure with all key sections: clear description of changes, linked issue reference, type of change identified, testing details, and additional notes about schema changes and key files.
Linked Issues check ✅ Passed The PR successfully implements all requirements from issue #1647: auto-saves first editor launch as global default [external/index.ts], filters non-editor apps [NON_EDITOR_APPS in zod.ts], resolves via per-project then global default [resolveDefaultEditor], and updates UI components with null-safe handling and error messages [multiple renderer components].
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the global default editor setting feature: database schema updates, new tRPC endpoints, helper functions for resolution, and UI component updates to handle the new setting. No unrelated code modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
apps/desktop/src/renderer/routes/_authenticated/settings/components/ClickablePath/ClickablePath.tsx (1)

32-38: Good use of getDefaultEditor for the settings page context.

The inline comments clearly explain why no projectId is used here.

Minor UX gap: the openInApp mutation has no onSuccess invalidation for settings.getDefaultEditor. If ensureGlobalDefaultEditor auto-sets the global default on the server (first-time use), the "Default" badge won't appear until react-query refetches. Consider adding invalidation:

Suggested improvement
+	const utils = electronTrpc.useUtils();
 	const openInApp = electronTrpc.external.openInApp.useMutation({
+		onSuccess: () => {
+			utils.settings.getDefaultEditor.invalidate();
+		},
 		onError: (error) => toast.error(`Failed to open: ${error.message}`),
 	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/components/ClickablePath/ClickablePath.tsx`
around lines 32 - 38, Add a success handler to the openInApp mutation so the
settings.getDefaultEditor cache is refreshed after a successful call: update the
openInApp = electronTrpc.external.openInApp.useMutation({ ... }) setup to
include an onSuccess that invalidates or refetches the
electronTrpc.settings.getDefaultEditor.useQuery cache (or calls its refetch) so
the "Default" badge appears immediately when ensureGlobalDefaultEditor sets the
global default on the server.
apps/desktop/src/lib/trpc/routers/external/index.ts (1)

151-163: Consider whether BAD_REQUEST is the best error code here.

The error is triggered when no editor is configured — this isn't really a malformed request from the client, but rather a "precondition not met" scenario. PRECONDITION_FAILED might be more semantically accurate, though BAD_REQUEST works in practice. This is a very minor nit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/external/index.ts` around lines 151 - 163,
The current tRPC mutation throws a TRPCError with code "BAD_REQUEST" when
resolveDefaultEditor(input.projectId) returns no editor; change the error code
to "PRECONDITION_FAILED" to better reflect a missing precondition rather than a
malformed request, keeping the same message; update the throw in the mutation
(the TRPCError instantiation that references resolveDefaultEditor) and run
type-checks to ensure "PRECONDITION_FAILED" is an accepted TRPCError code in
your `@trpc/server` version.
apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx (1)

102-103: getAppOption still silently falls back to APP_OPTIONS[1] (Cursor) for unknown IDs.

When defaultApp is defined but contains an unrecognized ExternalApp value (e.g., from a stale DB entry or future migration), getAppOption silently resolves to Cursor. Now that the PR explicitly removes hardcoded Cursor defaults elsewhere, this hidden fallback is inconsistent with the new design intent. Consider returning undefined instead and handling the miss at call sites, or at least logging a warning.

♻️ Suggested change
-export const getAppOption = (id: ExternalApp) =>
-	ALL_APP_OPTIONS.find((app) => app.id === id) ?? APP_OPTIONS[1];
+export const getAppOption = (id: ExternalApp) =>
+	ALL_APP_OPTIONS.find((app) => app.id === id);

Then update call sites (this file and OpenInMenuButton.tsx) to handle undefined:

const currentApp = defaultApp ? getAppOption(defaultApp) ?? null : null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx` around
lines 102 - 103, getAppOption currently falls back to APP_OPTIONS[1] (Cursor)
when an unknown ExternalApp id is passed; change getAppOption (and any helper
using ALL_APP_OPTIONS) to return undefined instead of defaulting to
APP_OPTIONS[1] so callers can handle unknown IDs explicitly. Update call sites
(e.g., OpenInButton/OpenInMenuButton where getAppOption is used) to
null-check/handle undefined (per suggestion: const currentApp = defaultApp ?
getAppOption(defaultApp) ?? null : null) and optionally log a warning when a
stored defaultApp is unrecognized so migrations/stale DB entries are visible.
Ensure the function signature and any downstream types reflect the possibility
of undefined.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OpenInMenuButton/OpenInMenuButton.tsx (1)

70-73: Guard on !defaultApp is purely defensive here — consider adding a toast for keyboard/assistive-tech users.

The button is visually disabled when !currentApp, so sighted mouse users won't trigger handleOpenInEditor. However, in some edge cases (e.g., the query resolves to undefined after the button was already focused and enabled, or assistive technology bypassing disabled), the silent return offers no feedback. The usePathActions hook shows a toast in this scenario. A toast here would provide a consistent experience, though the risk is low given the disabled guard.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OpenInMenuButton/OpenInMenuButton.tsx`
around lines 70 - 73, handleOpenInEditor silently returns when defaultApp is
falsy; instead, call the path-actions toast so keyboard/assistive-tech users get
feedback. Update the useCallback for handleOpenInEditor to check if
(!defaultApp) then invoke the toast from usePathActions (or the showToast
function exposed by usePathActions) with a short message and return, otherwise
proceed to call openInApp.mutate({ path: worktreePath, app: defaultApp,
projectId }). Keep existing guards for openInApp.isPending and
copyPath.isPending and ensure the toast call is included in the callback
dependencies.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 151-163: The current tRPC mutation throws a TRPCError with code
"BAD_REQUEST" when resolveDefaultEditor(input.projectId) returns no editor;
change the error code to "PRECONDITION_FAILED" to better reflect a missing
precondition rather than a malformed request, keeping the same message; update
the throw in the mutation (the TRPCError instantiation that references
resolveDefaultEditor) and run type-checks to ensure "PRECONDITION_FAILED" is an
accepted TRPCError code in your `@trpc/server` version.

In `@apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx`:
- Around line 102-103: getAppOption currently falls back to APP_OPTIONS[1]
(Cursor) when an unknown ExternalApp id is passed; change getAppOption (and any
helper using ALL_APP_OPTIONS) to return undefined instead of defaulting to
APP_OPTIONS[1] so callers can handle unknown IDs explicitly. Update call sites
(e.g., OpenInButton/OpenInMenuButton where getAppOption is used) to
null-check/handle undefined (per suggestion: const currentApp = defaultApp ?
getAppOption(defaultApp) ?? null : null) and optionally log a warning when a
stored defaultApp is unrecognized so migrations/stale DB entries are visible.
Ensure the function signature and any downstream types reflect the possibility
of undefined.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OpenInMenuButton/OpenInMenuButton.tsx`:
- Around line 70-73: handleOpenInEditor silently returns when defaultApp is
falsy; instead, call the path-actions toast so keyboard/assistive-tech users get
feedback. Update the useCallback for handleOpenInEditor to check if
(!defaultApp) then invoke the toast from usePathActions (or the showToast
function exposed by usePathActions) with a short message and return, otherwise
proceed to call openInApp.mutate({ path: worktreePath, app: defaultApp,
projectId }). Keep existing guards for openInApp.isPending and
copyPath.isPending and ensure the toast call is included in the callback
dependencies.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/components/ClickablePath/ClickablePath.tsx`:
- Around line 32-38: Add a success handler to the openInApp mutation so the
settings.getDefaultEditor cache is refreshed after a successful call: update the
openInApp = electronTrpc.external.openInApp.useMutation({ ... }) setup to
include an onSuccess that invalidates or refetches the
electronTrpc.settings.getDefaultEditor.useQuery cache (or calls its refetch) so
the "Default" badge appears immediately when ensureGlobalDefaultEditor sets the
global default on the server.

@de1mat de1mat force-pushed the feat/global-default-editor branch from 2e46357 to 624805a Compare February 22, 2026 06:46
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)

303-307: projectId as string type assertion suppresses type safety — prefer a safer narrowing.

The { enabled: !!projectId } guard makes this safe at runtime, but the as string cast hides the string | undefined type from the input object, defeating TypeScript's narrowing. A cleaner pattern is to pass the narrowed value explicitly:

♻️ Suggested refactor
-	const { data: defaultApp } =
-		electronTrpc.projects.getDefaultApp.useQuery(
-			{ projectId: projectId as string },
-			{ enabled: !!projectId },
-		);
+	const { data: defaultApp } =
+		electronTrpc.projects.getDefaultApp.useQuery(
+			{ projectId: projectId ?? "" },
+			{ enabled: !!projectId },
+		);

Alternatively, if tRPC supports skipToken (react-query v5+):

+import { skipToken } from "@tanstack/react-query";
...
-		electronTrpc.projects.getDefaultApp.useQuery(
-			{ projectId: projectId as string },
-			{ enabled: !!projectId },
-		);
+		electronTrpc.projects.getDefaultApp.useQuery(
+			projectId ? { projectId } : skipToken,
+		);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/`$workspaceId/page.tsx
around lines 303 - 307, The code casts projectId with "projectId as string"
which suppresses type safety; instead, pass a properly narrowed value to
electronTrpc.projects.getDefaultApp.useQuery by only supplying the params when
projectId is truthy (e.g., call useQuery with the object built from projectId or
use a skip/skipToken/undefined pattern supported by your TRPC/react-query
integration), or use the skipToken mechanism if available; update the call
referencing electronTrpc.projects.getDefaultApp.useQuery and the projectId
variable so the input type remains string | undefined-safe without using "as
string".
apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx (1)

129-133: projectId as string cast is a minor type smell.

enabled: !!projectId makes this safe at runtime, but projectId as string asserts away undefined. A non-casting alternative (e.g., projectId! or projectId ?? "") would be equivalent and more explicit about the intent.

🔧 Alternative without widening cast
-	const { data: defaultApp } =
-		electronTrpc.projects.getDefaultApp.useQuery(
-			{ projectId: projectId as string },
-			{ enabled: !!projectId },
-		);
+	const { data: defaultApp } =
+		electronTrpc.projects.getDefaultApp.useQuery(
+			// eslint-disable-next-line `@typescript-eslint/no-non-null-assertion`
+			{ projectId: projectId! },
+			{ enabled: !!projectId },
+		);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx` around
lines 129 - 133, The code casts away undefined with projectId as string when
calling electronTrpc.projects.getDefaultApp.useQuery; instead rely on the
enabled guard and use a non-widening expression (e.g., projectId! or projectId
?? "") to make the intent explicit. Update the call to
electronTrpc.projects.getDefaultApp.useQuery to pass projectId! (or a safe
default via ?? "") and keep enabled: !!projectId so types reflect that projectId
is guaranteed when the query runs.
apps/desktop/src/lib/trpc/routers/external/index.ts (1)

27-41: onConflictDoUpdate inside the null-guard doesn't give atomic "set if not set" semantics.

Two concurrent callers could both pass if (!row?.defaultEditor), then each unconditionally overwrite defaultEditor in the upsert — last writer wins, rather than "first use wins." This is very low risk in practice (Electron IPC is largely serialized), but the intent isn't fully expressed by the code. A purely advisory note: a single UPDATE settings SET default_editor = ? WHERE default_editor IS NULL (raw SQL) would be atomic without the two-step.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/external/index.ts` around lines 27 - 41,
ensureGlobalDefaultEditor currently reads row then upserts, which allows a race
where two callers both see null and both overwrite; replace that
read-then-upsert with an atomic operation: execute a single UPDATE on the
settings row (targeting id 1) that sets defaultEditor = app WHERE defaultEditor
IS NULL and check affected row count; if the UPDATE affected 0 rows then perform
an insert (or an upsert that does nothing on conflict) to handle the case the
settings row doesn't exist. Use the UPDATE-first approach in
ensureGlobalDefaultEditor to ensure "first use wins" semantics atomically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 131-134: The call to ensureGlobalDefaultEditor can throw and must
not cause the openInApp mutation to fail after openPathInApp already succeeded;
wrap the ensureGlobalDefaultEditor(input.app) invocation in a try/catch that
swallows any errors and logs them (e.g., console.error or the module's logger)
so the mutation remains successful and client onSuccess still runs; do not
change the openPathInApp call or its await, only protect the side-effect call
(ensureGlobalDefaultEditor) from propagating exceptions.

In `@apps/desktop/src/lib/trpc/routers/settings/index.ts`:
- Around line 667-680: The mutation setDefaultEditor currently accepts any
EXTERNAL_APPS value (including non-editors) — update the input validation to
reject NON_EDITOR_APPS so only real editors (or null) can be stored; import (and
if missing, export) NON_EDITOR_APPS from external/index.ts and change the
procedure input from z.enum(EXTERNAL_APPS).nullable() to a schema that refines
or validates values against NON_EDITOR_APPS (e.g., allow null or ensure the
value is not in NON_EDITOR_APPS) before writing to localDb, keeping references
to setDefaultEditor, EXTERNAL_APPS, NON_EDITOR_APPS, openFileInEditor,
openPathInApp, ensureGlobalDefaultEditor, localDb, and settings to locate the
related logic.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OpenInMenuButton/OpenInMenuButton.tsx`:
- Around line 103-126: In OpenInMenuButton adjust the className logic so
pointer-events-none is applied only when isLoading (not when !currentApp); keep
disabled={isLoading || !currentApp} as-is so clicks are blocked but remove
!currentApp from the pointer-events-none condition so the guidance tooltip
("Select an editor from the dropdown") can still appear when no editor is
configured; update the conditional expression that currently reads (isLoading ||
!currentApp) && "opacity-50 pointer-events-none" to only include isLoading for
pointer-events-none while preserving opacity-50 for the !currentApp state if
desired.

---

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 27-41: ensureGlobalDefaultEditor currently reads row then upserts,
which allows a race where two callers both see null and both overwrite; replace
that read-then-upsert with an atomic operation: execute a single UPDATE on the
settings row (targeting id 1) that sets defaultEditor = app WHERE defaultEditor
IS NULL and check affected row count; if the UPDATE affected 0 rows then perform
an insert (or an upsert that does nothing on conflict) to handle the case the
settings row doesn't exist. Use the UPDATE-first approach in
ensureGlobalDefaultEditor to ensure "first use wins" semantics atomically.

In `@apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx`:
- Around line 129-133: The code casts away undefined with projectId as string
when calling electronTrpc.projects.getDefaultApp.useQuery; instead rely on the
enabled guard and use a non-widening expression (e.g., projectId! or projectId
?? "") to make the intent explicit. Update the call to
electronTrpc.projects.getDefaultApp.useQuery to pass projectId! (or a safe
default via ?? "") and keep enabled: !!projectId so types reflect that projectId
is guaranteed when the query runs.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/`$workspaceId/page.tsx:
- Around line 303-307: The code casts projectId with "projectId as string" which
suppresses type safety; instead, pass a properly narrowed value to
electronTrpc.projects.getDefaultApp.useQuery by only supplying the params when
projectId is truthy (e.g., call useQuery with the object built from projectId or
use a skip/skipToken/undefined pattern supported by your TRPC/react-query
integration), or use the skipToken mechanism if available; update the call
referencing electronTrpc.projects.getDefaultApp.useQuery and the projectId
variable so the input type remains string | undefined-safe without using "as
string".

Comment thread apps/desktop/src/lib/trpc/routers/external/index.ts Outdated
Comment thread apps/desktop/src/lib/trpc/routers/settings/index.ts
@de1mat de1mat force-pushed the feat/global-default-editor branch 2 times, most recently from c1b9fde to 07e3c53 Compare February 22, 2026 07:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/usePathActions.ts`:
- Around line 23-28: The openInAppMutation call inside the openInEditor callback
lacks an onError handler so failures (e.g., missing app bundle) produce no user
feedback; add an onError to the electronTrpc.external.openInApp.useMutation
invocation (symmetric to electronTrpc.external.openFileInEditor.useMutation's
onError) that calls toast.error with a clear message and includes error.message
in the description so users see a failure notification when
openInAppMutation.mutate(...) fails.

---

Duplicate comments:
In `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 128-132: The call to ensureGlobalDefaultEditor should be treated
as best-effort and must not cause the mutation to fail after openPathInApp
succeeds; wrap ensureGlobalDefaultEditor(input.app) in a try/catch immediately
after the successful await of openPathInApp so any thrown errors are caught and
logged (e.g., processLogger.warn or console.error) and not rethrown, ensuring
the mutation returns success even if persisting the global default fails; keep
openPathInApp and ensureGlobalDefaultEditor calls in the same order and do not
change the primary operation.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OpenInMenuButton/OpenInMenuButton.tsx`:
- Around line 109-116: The button's class currently applies
"pointer-events-none" when !currentApp which blocks the tooltip; update the
conditional in OpenInMenuButton's className so pointer-events-none is only
applied for the loading state (isLoading) and not when no editor is
configured—e.g., replace (isLoading || !currentApp) && "opacity-50
pointer-events-none" with isLoading && "opacity-50 pointer-events-none" (keep
the disabled prop for the !currentApp case so clicks remain prevented) so the
guidance tooltip can still receive hover events when no editor is selected.

@de1mat de1mat force-pushed the feat/global-default-editor branch from 07e3c53 to 0b06abe Compare February 22, 2026 07:10
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/desktop/src/lib/trpc/routers/external/index.ts (1)

143-166: Accept line and column parameters in the input schema, but don't forward them to the editor.

The openFileInEditor mutation accepts line and column (lines 147–148) but openPathInApp(filePath, app) on line 165 doesn't receive them. Multiple callers already pass these values expecting editor navigation (e.g., Terminal/helpers.ts, useFileLinkClick.ts), but the implementation silently discards them.

Consider extending openPathInApp to accept and forward line and column to support editor jump-to-line functionality.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/external/index.ts` around lines 143 - 166,
openFileInEditor currently parses line and column from the input (z.object) but
discards them when calling openPathInApp; update the flow so these cursor
coordinates are forwarded: change openPathInApp signature to accept optional
line and column (or an options object) and update the mutation to call
openPathInApp(filePath, app, { line: input.line, column: input.column }) after
resolving filePath and app; adjust any affected callers and type definitions
(and resolvePath/resolveDefaultEditor usage if needed) so editors can receive
and act on jump-to-line coordinates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 143-166: openFileInEditor currently parses line and column from
the input (z.object) but discards them when calling openPathInApp; update the
flow so these cursor coordinates are forwarded: change openPathInApp signature
to accept optional line and column (or an options object) and update the
mutation to call openPathInApp(filePath, app, { line: input.line, column:
input.column }) after resolving filePath and app; adjust any affected callers
and type definitions (and resolvePath/resolveDefaultEditor usage if needed) so
editors can receive and act on jump-to-line coordinates.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/lib/trpc/routers/external/index.ts (1)

120-140: ⚠️ Potential issue | 🟡 Minor

Project defaultApp is updated before openPathInApp succeeds.

Line 122-127 persists input.app as the project's default before openPathInApp is awaited. If the app isn't installed and openPathInApp throws, the project's defaultApp is already set to an app that failed to launch. Consider moving the project update after the await openPathInApp(...) call, similar to how ensureGlobalDefaultEditor is placed.

Proposed fix — move project update after successful open
 		.mutation(async ({ input }) => {
-			if (input.projectId) {
-				localDb
-					.update(projects)
-					.set({ defaultApp: input.app })
-					.where(eq(projects.id, input.projectId))
-					.run();
-			}
-
 			await openPathInApp(input.path, input.app);
 
+			if (input.projectId) {
+				localDb
+					.update(projects)
+					.set({ defaultApp: input.app })
+					.where(eq(projects.id, input.projectId))
+					.run();
+			}
+
 			// Auto-set global default editor on first successful use (best-effort)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/external/index.ts` around lines 120 - 140,
The project defaultApp is being persisted before openPathInApp succeeds; move
the localDb.update(projects).set({ defaultApp: input.app
}).where(eq(projects.id, input.projectId)).run() call to after the awaited
openPathInApp(input.path, input.app) so the DB update only occurs on successful
launch; ensure you await the .run() promise and keep the existing projectId
guard and the try/catch around ensureGlobalDefaultEditor(input.app).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 120-140: The project defaultApp is being persisted before
openPathInApp succeeds; move the localDb.update(projects).set({ defaultApp:
input.app }).where(eq(projects.id, input.projectId)).run() call to after the
awaited openPathInApp(input.path, input.app) so the DB update only occurs on
successful launch; ensure you await the .run() promise and keep the existing
projectId guard and the try/catch around ensureGlobalDefaultEditor(input.app).

@de1mat de1mat closed this Feb 23, 2026
@de1mat de1mat force-pushed the feat/global-default-editor branch from 975cabb to 0479b6f Compare February 23, 2026 07:46
@de1mat
Copy link
Copy Markdown
Contributor Author

de1mat commented Feb 23, 2026

Re: project defaultApp persisted before openPathInApp succeeds

Good catch. Moved the project default update (and global default auto-set) to after the await openPathInApp(...) call, so defaults are only persisted on successful launch. Fixed in 0479b6f.

@de1mat de1mat reopened this Feb 23, 2026
@de1mat de1mat force-pushed the feat/global-default-editor branch 2 times, most recently from f001fc5 to 1f4af46 Compare February 23, 2026 17:30
@de1mat
Copy link
Copy Markdown
Contributor Author

de1mat commented Feb 23, 2026

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 23, 2026

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/desktop/src/lib/trpc/routers/settings/index.ts (1)

684-714: LGTM!

getDefaultEditor and setDefaultEditor follow the same upsert pattern established by every other settings accessor in this file. The .refine() validation against NON_EDITOR_APPS correctly prevents non-editor apps from being stored as the global default, as previously discussed and confirmed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/settings/index.ts` around lines 684 - 714,
The review approves the implementation: no code changes required — leave
getDefaultEditor and setDefaultEditor as-is (they correctly use EXTERNAL_APPS
with .refine against NON_EDITOR_APPS and perform the upsert via
localDb.insert(settings)...onConflictDoUpdate), so keep these functions and
their validation/upsert logic unchanged.
🧹 Nitpick comments (1)
apps/desktop/src/lib/trpc/routers/external/index.ts (1)

154-167: openFileInEditor now has a clear error when no editor is configured.

resolveDefaultEditor may return a non-editor app (e.g., "finder") if that's what the project's defaultApp is set to — which would cause openPathInApp to call shell.showItemInFolder instead of opening in an actual editor. This is pre-existing behavior from the per-project default, but now that openFileInEditor is explicitly named "editor", it might be worth filtering non-editor apps here too.

Consider whether resolveDefaultEditor should skip non-editor project defaults for this specific code path, or whether the function name is sufficient documentation of intent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/external/index.ts` around lines 154 - 167,
The code calls resolveDefaultEditor(...) and passes its result to
openPathInApp(...), but resolveDefaultEditor can return non-editor apps like
"finder" which causes shell.showItemInFolder to be used; update the mutation to
validate the returned app is an actual editor before calling openPathInApp:
after const app = resolveDefaultEditor(input.projectId) check for an
editor-specific property or type (e.g., app.kind === 'editor' or a helper
isEditorApp(app)), and if it isn’t an editor throw the same
TRPCError("PRECONDITION_FAILED", "No default editor configured...") or call
resolveDefaultEditor with a flag to skip non-editors; reference
resolveDefaultEditor and openPathInApp in your change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/desktop/src/lib/trpc/routers/settings/index.ts`:
- Around line 684-714: The review approves the implementation: no code changes
required — leave getDefaultEditor and setDefaultEditor as-is (they correctly use
EXTERNAL_APPS with .refine against NON_EDITOR_APPS and perform the upsert via
localDb.insert(settings)...onConflictDoUpdate), so keep these functions and
their validation/upsert logic unchanged.

---

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 154-167: The code calls resolveDefaultEditor(...) and passes its
result to openPathInApp(...), but resolveDefaultEditor can return non-editor
apps like "finder" which causes shell.showItemInFolder to be used; update the
mutation to validate the returned app is an actual editor before calling
openPathInApp: after const app = resolveDefaultEditor(input.projectId) check for
an editor-specific property or type (e.g., app.kind === 'editor' or a helper
isEditorApp(app)), and if it isn’t an editor throw the same
TRPCError("PRECONDITION_FAILED", "No default editor configured...") or call
resolveDefaultEditor with a flag to skip non-editors; reference
resolveDefaultEditor and openPathInApp in your change.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 975cabb and 1f4af46.

📒 Files selected for processing (13)
  • apps/desktop/src/lib/trpc/routers/external/index.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/lib/trpc/routers/settings/index.ts
  • apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OpenInMenuButton/OpenInMenuButton.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/ClickablePath/ClickablePath.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/usePathActions.ts
  • packages/local-db/drizzle/0032_add_default_editor_setting.sql
  • packages/local-db/drizzle/meta/0032_snapshot.json
  • packages/local-db/drizzle/meta/_journal.json
  • packages/local-db/src/schema/schema.ts
  • packages/local-db/src/schema/zod.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/local-db/drizzle/meta/_journal.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/ClickablePath/ClickablePath.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/usePathActions.ts
  • packages/local-db/src/schema/schema.ts
  • packages/local-db/src/schema/zod.ts

- Add `defaultEditor` column to settings table with migration
- Extract `resolveDefaultEditor()` and `ensureGlobalDefaultEditor()` helpers
- Auto-set global default on first successful editor launch
- Filter non-editor apps (Finder, terminals) from becoming default
- Add `getDefaultEditor`/`setDefaultEditor` tRPC endpoints
- Null-safe UI: fallback icon, disabled state, hotkey guard
- Show toast when no editor configured instead of silent no-op
@de1mat de1mat force-pushed the feat/global-default-editor branch from 1f4af46 to ca36c2a Compare February 23, 2026 17:43
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 41-53: resolveDefaultEditor currently returns project.defaultApp
without verifying it's an editor, allowing non-editor values (e.g., "finder",
"terminal") to be used; update resolveDefaultEditor to only return
project.defaultApp when it is a valid editor (e.g., via an existing helper like
isEditorApp or by checking against the canonical editor list) and otherwise fall
back to settings.defaultEditor; reference resolveDefaultEditor,
project.defaultApp, settings.defaultEditor, and the related helpers
ensureGlobalDefaultEditor/setDefaultEditor so the project-level lookup mirrors
the global guard.

In `@apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx`:
- Around line 129-132: The query call using
electronTrpc.projects.getDefaultApp.useQuery currently casts projectId with
"projectId as string" which weakens type safety; change the argument to use the
non-null assertion "projectId!" instead. Locate the use of
electronTrpc.projects.getDefaultApp.useQuery in OpenInButton and replace the
cast with projectId! so the enabled guard ({ enabled: !!projectId }) remains
correct and TypeScript keeps the tighter non-null typing for the query input.
Ensure no other occurrences in OpenInButton use the "as string" cast for
projectId.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f4af46 and ca36c2a.

📒 Files selected for processing (13)
  • apps/desktop/src/lib/trpc/routers/external/index.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/lib/trpc/routers/settings/index.ts
  • apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OpenInMenuButton/OpenInMenuButton.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/ClickablePath/ClickablePath.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/usePathActions.ts
  • packages/local-db/drizzle/0032_add_default_editor_setting.sql
  • packages/local-db/drizzle/meta/0032_snapshot.json
  • packages/local-db/drizzle/meta/_journal.json
  • packages/local-db/src/schema/schema.ts
  • packages/local-db/src/schema/zod.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/local-db/src/schema/schema.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/ClickablePath/ClickablePath.tsx
  • packages/local-db/drizzle/0032_add_default_editor_setting.sql
  • packages/local-db/src/schema/zod.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/usePathActions.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • packages/local-db/drizzle/meta/0032_snapshot.json
  • packages/local-db/drizzle/meta/_journal.json
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx

Comment on lines +41 to +53
/** Resolves the default editor from project setting, then global setting. */
export function resolveDefaultEditor(projectId?: string): ExternalApp | null {
if (projectId) {
const project = localDb
.select()
.from(projects)
.where(eq(projects.id, projectId))
.get();
if (project?.defaultApp) return project.defaultApp;
}
const row = localDb.select().from(settings).get();
return row?.defaultEditor ?? null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

resolveDefaultEditor can return a non-editor app from project.defaultApp.

The global default (settings.defaultEditor) is properly guarded against non-editor apps by both ensureGlobalDefaultEditor and setDefaultEditor. However, project.defaultApp stores whatever app was last used (including Finder, Terminal, etc.), and resolveDefaultEditor returns it without filtering.

If a user's last action on a project was "Open in Finder", then openFileInEditor would resolve to "finder" and call shell.showItemInFolder instead of launching an editor.

Consider filtering out non-editor apps from the project-level lookup:

Proposed fix
 export function resolveDefaultEditor(projectId?: string): ExternalApp | null {
 	if (projectId) {
 		const project = localDb
 			.select()
 			.from(projects)
 			.where(eq(projects.id, projectId))
 			.get();
-		if (project?.defaultApp) return project.defaultApp;
+		if (project?.defaultApp && !nonEditorSet.has(project.defaultApp))
+			return project.defaultApp;
 	}
 	const row = localDb.select().from(settings).get();
 	return row?.defaultEditor ?? null;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/external/index.ts` around lines 41 - 53,
resolveDefaultEditor currently returns project.defaultApp without verifying it's
an editor, allowing non-editor values (e.g., "finder", "terminal") to be used;
update resolveDefaultEditor to only return project.defaultApp when it is a valid
editor (e.g., via an existing helper like isEditorApp or by checking against the
canonical editor list) and otherwise fall back to settings.defaultEditor;
reference resolveDefaultEditor, project.defaultApp, settings.defaultEditor, and
the related helpers ensureGlobalDefaultEditor/setDefaultEditor so the
project-level lookup mirrors the global guard.

Comment on lines +129 to +132
const { data: defaultApp } = electronTrpc.projects.getDefaultApp.useQuery(
{ projectId: projectId as string },
{ enabled: !!projectId },
);
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx

Repository: superset-sh/superset

Length of output: 12118


🏁 Script executed:

rg -n "OpenInButton" --type=tsx --type=ts -B 2 -A 5

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

rg -n "OpenInButton" -B 2 -A 5

Repository: superset-sh/superset

Length of output: 6954


🏁 Script executed:

cat -n apps/desktop/src/renderer/components/ConfigFilePreview/ConfigFilePreview.tsx | grep -A 10 -B 5 "OpenInButton"

Repository: superset-sh/superset

Length of output: 1277


🏁 Script executed:

cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OpenInMenuButton/OpenInMenuButton.tsx | head -80

Repository: superset-sh/superset

Length of output: 3371


🏁 Script executed:

cat -n apps/desktop/src/renderer/routes/_authenticated/settings/components/ClickablePath/ClickablePath.tsx | head -80

Repository: superset-sh/superset

Length of output: 3199


🏁 Script executed:

rg "<OpenInButton" -B 3 -A 5

Repository: superset-sh/superset

Length of output: 1034


🏁 Script executed:

rg "OpenInButton" --type ts --type tsx | grep -v "import\|export\|interface\|from\|props\|Props"

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

cat -n apps/desktop/src/renderer/components/ConfigFilePreview/ConfigFilePreview.tsx | head -20

Repository: superset-sh/superset

Length of output: 762


projectId as string breaks type safety; replace with non-null assertion projectId!.

The idiomatic pattern for enabled-guarded tRPC queries is the non-null assertion projectId! rather than projectId as string. It's equally concise but maintains type safety:

Proposed fix
-	const { data: defaultApp } = electronTrpc.projects.getDefaultApp.useQuery(
-		{ projectId: projectId as string },
+	const { data: defaultApp } = electronTrpc.projects.getDefaultApp.useQuery(
+		{ projectId: projectId! },

Note: While projectId is optional in the props interface, OpenInButton is currently only rendered in ConfigFilePreview, which always supplies a projectId. The global default handling is out of scope for this component (see ClickablePath for the pattern used when handling settings without project context).

📝 Committable suggestion

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

Suggested change
const { data: defaultApp } = electronTrpc.projects.getDefaultApp.useQuery(
{ projectId: projectId as string },
{ enabled: !!projectId },
);
const { data: defaultApp } = electronTrpc.projects.getDefaultApp.useQuery(
{ projectId: projectId! },
{ enabled: !!projectId },
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx` around
lines 129 - 132, The query call using
electronTrpc.projects.getDefaultApp.useQuery currently casts projectId with
"projectId as string" which weakens type safety; change the argument to use the
non-null assertion "projectId!" instead. Locate the use of
electronTrpc.projects.getDefaultApp.useQuery in OpenInButton and replace the
cast with projectId! so the enabled guard ({ enabled: !!projectId }) remains
correct and TypeScript keeps the tighter non-null typing for the query input.
Ensure no other occurrences in OpenInButton use the "as string" cast for
projectId.

@de1mat
Copy link
Copy Markdown
Contributor Author

de1mat commented Feb 23, 2026

Hey @Kitenite - heads up, I rebased onto main and force-pushed which overwrote your 3 cleanup commits (9408227, 5ab01e5, 975cabb). Apologies for the overlap.

The current state incorporates all the same changes - conflict resolution, migration renumbering (now 0032), and the CodeRabbit feedback. I also moved openPathInApp() before the default persistence so we only save after a successful launch.

Let me know if anything looks off.

@Kitenite
Copy link
Copy Markdown
Collaborator

No worries @de1mat its my fault for not just merging. Apologies and will pull again

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/desktop/src/lib/trpc/routers/external/index.ts (1)

41-53: ⚠️ Potential issue | 🟡 Minor

Filter project defaults to editors before returning.
project.defaultApp can be a non-editor (Finder/Terminal), which would leak into editor-only flows.

🛠️ Suggested fix
-		if (project?.defaultApp) return project.defaultApp;
+		if (project?.defaultApp && !nonEditorSet.has(project.defaultApp)) {
+			return project.defaultApp;
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/external/index.ts` around lines 41 - 53,
resolveDefaultEditor currently returns project.defaultApp even if it's not an
editor; update resolveDefaultEditor to validate that the project default is an
editor before returning it (e.g., add an isEditor check or whitelist allowed
ExternalApp values) and only return project.defaultApp when it passes that
editor check; otherwise fall through to read settings.defaultEditor as before.
Target the resolveDefaultEditor function and the
project.defaultApp/row?.defaultEditor values in your change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 41-53: resolveDefaultEditor currently returns project.defaultApp
even if it's not an editor; update resolveDefaultEditor to validate that the
project default is an editor before returning it (e.g., add an isEditor check or
whitelist allowed ExternalApp values) and only return project.defaultApp when it
passes that editor check; otherwise fall through to read settings.defaultEditor
as before. Target the resolveDefaultEditor function and the
project.defaultApp/row?.defaultEditor values in your change.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca36c2a and 2de8344.

📒 Files selected for processing (7)
  • apps/desktop/src/lib/trpc/routers/external/index.ts
  • apps/desktop/src/lib/trpc/routers/settings/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • packages/local-db/drizzle/0033_nosy_overlord.sql
  • packages/local-db/drizzle/meta/0033_snapshot.json
  • packages/local-db/drizzle/meta/_journal.json
  • packages/local-db/src/schema/zod.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/local-db/drizzle/meta/0033_snapshot.json
  • packages/local-db/drizzle/0033_nosy_overlord.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/lib/trpc/routers/settings/index.ts

@Kitenite Kitenite merged commit fc84d5e into superset-sh:main Feb 23, 2026
5 of 7 checks passed
@Kitenite
Copy link
Copy Markdown
Collaborator

Merged, sorry for the delay @de1mat , looks like it's much cleaner too!

@de1mat
Copy link
Copy Markdown
Contributor Author

de1mat commented Feb 24, 2026

My pleasure @Kitenite.

@de1mat
Copy link
Copy Markdown
Contributor Author

de1mat commented Feb 24, 2026

Hmm when I updated to 0.0.84 locally I am getting an error

no such column: "open_links_in_app" - should this be a string literal in single-quotes?

Is that just me? I wonder if something was wrong with the migration?

I am looking into a few things now to double-check

@Kitenite

@Kitenite
Copy link
Copy Markdown
Collaborator

Uh oh looks like your local db got messed up probably from migration order. Most likely the work you did in dev changed your prod db config locally and now new migrations are being skipped.

@de1mat
Copy link
Copy Markdown
Contributor Author

de1mat commented Feb 24, 2026

Hmm when I updated to 0.0.84 locally I am getting an error

no such column: "open_links_in_app" - should this be a string literal in single-quotes?

Is that just me? I wonder if something was wrong with the migration?

I am looking into a few things now to double-check

@Kitenite

Ok you can ignore me, it was because my local install was dirty because of the dev work I had been doing. Phew! I have cleaned that up and now working fine.

@Kitenite
Copy link
Copy Markdown
Collaborator

Manually running that migration or adding the column in your db should fix it. Database should be under ~/.superset/*.db and it's sql

@Kitenite
Copy link
Copy Markdown
Collaborator

Nice glad that's fixed haha

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Global default editor setting

2 participants