Skip to content

electron app#106

Open
FranciscoMoretti wants to merge 21 commits intomainfrom
electron-app
Open

electron app#106
FranciscoMoretti wants to merge 21 commits intomainfrom
electron-app

Conversation

@FranciscoMoretti
Copy link
Owner

@FranciscoMoretti FranciscoMoretti commented Mar 12, 2026

  • electron app base
  • Refactor Electron app to improve titlebar height handling
  • Update layout and preload scripts for improved titlebar height handling
  • Add OAuth flow for Electron app with deep link handling
  • Enhance Electron app build configuration and icon generation
  • fixes
  • Refactor prompts.ts for improved code readability
  • Refactor API routes and enhance Electron documentation
  • Implement Electron authentication flow and enhance UI components
  • Add GitHub Actions workflow for Electron app release

Summary by Sourcery

Add an Electron desktop application wrapper for the ChatJS web app, including OAuth deep-link authentication, CLI scaffolding support, and documentation, while adjusting the web UI and templates to integrate cleanly with the desktop environment.

New Features:

  • Introduce an Electron app package that wraps the ChatJS web app with a native window, tray integration, auto-updates, and custom URL scheme handling for OAuth deep links.
  • Add an OAuth deep-link flow between the Electron app and the web app using /electron-auth pages and API routes for stateless session token exchange.
  • Extend the CLI create command to optionally scaffold an Electron sub-app with preconfigured build, icon, and protocol settings.

Bug Fixes:

  • Prevent OAuth state mismatch errors in the Electron environment by delegating social login to the system browser and handling callbacks via deep links.

Enhancements:

  • Improve layout and sidebar styling to respect a dynamic Electron titlebar height via CSS variables and a client-side offset component.
  • Refine login and signup copy to be less provider-specific and more generally applicable.
  • Generalize template sync tooling to support both the chat web app and the Electron template with drift checking and placeholder substitution.
  • Refactor CLI prompt helpers to add an Electron prompt and improve consistency of formatting and control flow.

Build:

  • Add Electron-specific build tooling, icon generation script, and electron-builder configuration for multi-platform packaging and publishing.
  • Add root-level npm scripts to run, build, and publish the Electron app through Turbo and Bun.

CI:

  • Introduce a GitHub Actions workflow to build and publish Electron releases for macOS, Windows, and Linux on version tags.

Deployment:

  • Configure Electron auto-updater to publish releases to GitHub using electron-builder and GitHub Releases.

Documentation:

  • Document the Electron desktop app integration, including scaffolding, development, authentication flow, auto-updates, and distribution, and update branding docs with Electron icon guidance.
  • Update CLI docs to describe the new Electron option in the project creation flow.

Chores:

  • Add an Electron app README describing local development, configuration, and distribution steps.

Summary by cubic

Adds a cross-platform Electron desktop app with deep-link OAuth, tray, auto-updates, and consistent titlebar spacing. Moves build config to electron-builder.config.js with a prebuild that writes branding.json from chat.config.ts, and updates CLI scaffolding and the release workflow.

  • New Features

    • New apps/electron wrapper: hidden title bar, tray menu, single-instance, and electron-updater auto-updates; OAuth uses the system browser + custom protocol with /api/auth/electron-callback and /api/auth/electron-exchange.
    • UI: ElectronTitlebarOffset and CSS --electron-titlebar-height for consistent spacing; SocialAuthProviders opens the browser in Electron.
    • CLI (@chat-js/cli): prompts to include Electron; scaffolds an electron/ folder, injects project/package placeholders and GitHub owner/repo, and adds turbo/root scripts (dev:electron, build:electron, dist:electron).
    • Build/Docs/CI: switched to electron-builder.config.js; prebuild writes branding.json from chat.config.ts; icon generation script; docs updated; .github/workflows/electron-release.yml builds/publishes installers on electron-v* tags and sets the version from the tag.
  • Migration

    • Set AUTH_SECRET in the web app.
    • Configure app name, URL scheme, and production URL in chat.config.ts; the prebuild writes apps/electron/branding.json, and electron-builder.config.js reads from it.
    • Update GitHub owner/repo in apps/electron/electron-builder.config.js for releases.
    • Replace apps/electron/icon.png (512×512) and run bun run generate-icons; dev: start the web app, then cd apps/electron && bun install && bun run dev; release: push an electron-vX.Y.Z tag.

Written for commit 6e1216e. Summary will update on new commits.

- Updated the build script in package.json to target browser for preload script.
- Removed the getTitlebarStyles function and integrated its logic directly into the createWindow function for better readability.
- Enhanced the preload script to set the titlebar height CSS variable more reliably during document loading.
- Added suppressHydrationWarning to the body element in layout.tsx for better hydration management.
- Removed the titlebar height setting logic from preload.ts, streamlining the script and improving performance.
- Implemented OAuth callback handling in Electron with new routes for token creation and verification.
- Created `electron-callback` and `electron-exchange` API routes to manage session tokens.
- Developed `ElectronAuth` page to initiate OAuth flow in the user's browser.
- Enhanced `SocialAuthProviders` component to support deep linking for OAuth in Electron.
- Configured Electron to handle custom URL protocols for deep link authentication.
- Updated Electron main process to manage deep link callbacks and session token injection.
- Added entitlements.mac.plist for macOS app security settings.
- Updated electron-builder.yml to include entitlements file and adjusted paths.
- Introduced icon.png and a script to generate necessary icon files for macOS.
- Added new scripts in package.json for icon generation during the build process.
- Standardized formatting and indentation for better consistency.
- Ensured uniformity in the structure of feature and authentication prompts.
- Enhanced clarity in the prompt functions by maintaining consistent spacing and alignment.
- Changed GET function in `electron-callback` and `electron-exchange` routes to remove async keyword for improved performance.
- Improved code readability by adding braces for conditional statements in `verifySignedToken`.
- Updated `social-auth-providers.tsx` for better formatting and clarity.
- Added detailed instructions for customizing the Electron app icon in the documentation.
- Created a new README file for the Electron app with development and customization guidelines.
- Removed `icon.png` from the sync template exclusion list to ensure proper file handling.
- Refactored `ElectronAuth` to handle session checks and redirect users upon authentication.
- Introduced `ElectronAuthSuccessClient` for managing successful sign-in and app launching.
- Created `SocialAuthProviders` component for streamlined social login options.
- Updated `login-form` and `signup-form` to use the new `SocialAuthProviders` component.
- Added documentation for the Electron desktop app integration, including setup and authentication details.
- Created a new workflow file `electron-release.yml` to automate the build and release process for macOS, Windows, and Linux.
- Configured jobs for each platform to install dependencies using Bun, build the application, and publish it to GitHub.
- Set up caching for Bun and node_modules to optimize build times.
- Triggered the workflow on tag pushes matching the pattern "electron-v*".
@vercel
Copy link

vercel bot commented Mar 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sparka Ready Ready Preview, Comment Mar 14, 2026 2:53pm

Request Review

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 12, 2026

Reviewer's Guide

Adds a first-class Electron desktop app wrapper for the ChatJS Next.js app, including OAuth deep-link auth flow, titlebar-height-aware layout integration, CLI scaffolding support, docs, and CI release automation, plus minor UI copy and prompt helper refactors.

Sequence diagram for Electron OAuth deep-link authentication flow

sequenceDiagram
    actor User
    participant ElectronApp as Electron_app
    participant Browser as Default_browser
    participant Web as Nextjs_web_app
    participant OAuth as OAuth_provider
    participant ApiCb as api_auth_electron_callback
    participant ApiEx as api_auth_electron_exchange

    User->>ElectronApp: Click Sign in via browser
    ElectronApp->>Browser: open /electron-auth in default browser

    Browser->>Web: GET /electron-auth
    Web-->>Browser: Render ElectronAuth page with SocialAuthProviders

    User->>Browser: Choose provider and sign in
    Browser->>OAuth: OAuth login flow
    OAuth-->>Browser: Redirect back to web app
    Browser->>Web: OAuth callback endpoint
    Web-->>Browser: Set better-auth.session_token cookie

    Browser->>ApiCb: GET /api/auth/electron-callback
    ApiCb->>ApiCb: Read better-auth.session_token cookie
    ApiCb->>ApiCb: createSignedToken(sessionToken)
    ApiCb-->>Browser: 302 redirect to /electron-auth/success?token=...

    Browser->>Web: GET /electron-auth/success?token=...
    Web-->>Browser: Render ElectronAuthSuccessClient
    Browser->>Browser: window.location = appScheme:///auth/callback?token=...

    Browser->>ElectronApp: OS delivers deep link appScheme:///auth/callback?token=...
    ElectronApp->>ElectronApp: handleDeepLink(url)
    ElectronApp->>ApiEx: GET /api/auth/electron-exchange?token=...
    ApiEx->>ApiEx: verifySignedToken(token)
    ApiEx-->>ElectronApp: { sessionToken }

    ElectronApp->>ElectronApp: Set better-auth.session_token cookie in Electron session
    ElectronApp->>Web: Reload BrowserWindow at APP_URL
    Web-->>ElectronApp: Authenticated app UI
    ElectronApp-->>User: Signed-in desktop experience
Loading

Sequence diagram for Electron titlebar height integration

sequenceDiagram
    participant Main as Electron_main
    participant Preload as Preload_script
    participant Renderer as Nextjs_renderer

    Main->>Renderer: Create BrowserWindow with titleBarStyle hiddenInset
    Main->>Renderer: Inject CSS setting --electron-titlebar-height and body padding

    Preload->>Renderer: Expose electronAPI with titlebarHeight

    Renderer->>Renderer: ElectronTitlebarOffset useEffect runs
    Renderer->>Renderer: Read window.electronAPI.titlebarHeight
    Renderer->>Renderer: Set CSS variable --electron-titlebar-height on documentElement

    Renderer->>Renderer: RootLayout body style padding-top uses --electron-titlebar-height
    Renderer->>Renderer: ElectronTitlebarOffset renders draggable region
    Renderer->>Renderer: Sidebar component uses top and height based on --electron-titlebar-height
Loading

Flow diagram for CLI create command with optional Electron scaffolding

flowchart TD
  U[User runs
npx @chat-js/cli create] --> C[create command]

  C --> PName[promptProjectName]
  PName --> PGateway[promptGateway]
  PGateway --> PFeatures[promptFeatures]
  PFeatures --> PAuth[promptAuth]
  PAuth --> PElectron[promptElectron
Include Electron desktop app?]
  PElectron --> PInstall[promptInstall]

  PInstall -->|use template repo| SRepo[scaffoldFromGit]
  PInstall -->|use local template| SLocal[scaffoldFromTemplate]

  SRepo --> AfterScaffold[Project scaffolding complete]
  SLocal --> AfterScaffold

  AfterScaffold -->|if withElectron = true| SElec[scaffoldElectron
create electron/ subfolder]
  AfterScaffold -->|if withElectron = false| SkipElec[no electron/ folder]

  SElec --> Done[Show next steps
including Electron dev instructions]
  SkipElec --> Done
Loading

File-Level Changes

Change Details Files
Introduce Electron desktop app wrapper with OAuth deep-link flow and auto-updates.
  • Add new apps/electron package with main process, preload, config, build scripts, and TypeScript config for Electron.
  • Configure BrowserWindow to load APP_URL, inject CSS for titlebar height, intercept OAuth navigations to open in the default browser, and minimize to tray instead of quitting.
  • Implement deep-link handling using a custom APP_SCHEME for OAuth callback URLs across macOS, Windows, and Linux, including single-instance handling and session cookie injection.
  • Wire up electron-updater for GitHub-based auto-updates and tray menu for show/quit behavior.
apps/electron/src/main.ts
apps/electron/src/config.ts
apps/electron/src/preload.ts
apps/electron/package.json
apps/electron/tsconfig.json
apps/electron/electron-builder.yml
apps/electron/entitlements.mac.plist
Implement backend support for Electron OAuth browser flow via short-lived signed tokens and dedicated routes.
  • Add /api/auth/electron-callback route to read Better Auth session cookie after OAuth, sign it into a short-lived HMAC token, and redirect to a success page with the token.
  • Add /api/auth/electron-exchange route to verify the signed token using AUTH_SECRET, enforce expiry, and return the raw session token for Electron to set as a cookie.
  • Add /electron-auth entry page that either redirects authenticated users to the callback route or presents provider selection for sign-in in the browser.
  • Add /electron-auth/success client/server components to trigger the deep link back into the Electron app using the app scheme and show a simple success UI.
apps/chat/app/api/auth/electron-callback/route.ts
apps/chat/app/api/auth/electron-exchange/route.ts
apps/chat/app/electron-auth/page.tsx
apps/chat/app/electron-auth/success/page.tsx
apps/chat/app/electron-auth/success/client.tsx
apps/chat/proxy.ts
Expose Electron-aware authentication UI in the web app and integrate titlebar-height-aware layout behavior.
  • Refactor SocialAuthProviders into auth-providers component that detects Electron via window.electronAPI and, when running in Electron, shows a single 'Sign in via browser' button that opens /electron-auth; otherwise it calls authClient.signIn.social with an optional callbackURL.
  • Update login and signup forms copy to be more generic and not mention social providers explicitly.
  • Add ElectronTitlebarOffset client component that reads titlebarHeight from window.electronAPI, sets the --electron-titlebar-height CSS variable, and renders a draggable region for the native titlebar.
  • Adjust RootLayout body padding and inject ElectronTitlebarOffset so content is offset by the Electron title bar when present, and update sidebar layout CSS to respect the titlebar height.
apps/chat/components/social-auth-providers.tsx
apps/chat/components/auth-providers.tsx
apps/chat/components/login-form.tsx
apps/chat/components/signup-form.tsx
apps/chat/components/electron-titlebar-offset.tsx
apps/chat/app/layout.tsx
apps/chat/components/ui/sidebar.tsx
Extend CLI scaffolding and template sync to support generating Electron apps alongside the chat app template.
  • Add promptElectron helper to ask whether to include an Electron desktop app during project creation.
  • Add scaffoldElectron helper to copy the Electron template into an electron/ subfolder and replace placeholders (APP_URL, APP_SCHEME, APP_ID, productName, GitHub owner/repo) with project-specific values.
  • Update create command to call promptElectron, conditionally scaffold the Electron app, and print Electron-specific getting-started instructions.
  • Extend sync-template script to copy apps/electron into a new templates/electron directory, with filtering and template transforms to substitute placeholders in config.ts and electron-builder.yml, and add a generic assertSynced helper to check both chat-app and electron templates.
packages/cli/src/helpers/prompts.ts
packages/cli/src/helpers/scaffold.ts
packages/cli/src/commands/create.ts
scripts/sync-template.ts
packages/cli/templates/electron/**
Document Electron integration and add release automation and project scripts for building Electron distributions.
  • Add Electron Desktop App documentation explaining scaffolding, auth flow, system tray behavior, auto-updates, customization, and distribution steps.
  • Document Electron icon generation and icon.png placement in branding docs.
  • Add Electron-specific README in apps/electron with development, icon, scheme, URL, and build guidance.
  • Add root-level npm scripts for dev, build, and publish flows targeting the Electron app via turbo.
  • Add GitHub Actions workflow to build and publish Electron releases on tags matching electron-v* for macOS, Windows, and Linux using electron-builder and GitHub token.
apps/docs/desktop/electron.mdx
apps/docs/customization/branding.mdx
apps/electron/README.md
.github/workflows/electron-release.yml
package.json
Add Electron icon generation script and ensure Electron app assets are generated correctly.
  • Add generate-icons.sh to build/icon.png and build/icon.icns from icon.png, with macOS-specific icns generation using sips and iconutil.
  • Wire generate-icons into Electron build via prebuild script so icons are regenerated on build.
apps/electron/scripts/generate-icons.sh
apps/electron/package.json
Minor UX, routing, and configuration tweaks to support Electron and improve prompts helper consistency.
  • Update docs for CLI create flow to include the Electron desktop app prompt in the numbered steps.
  • Treat /electron-auth as a public route in the chat proxy so it is accessible without auth.
  • Normalize indentation and trailing commas in prompts.ts and add the new promptElectron function while preserving existing behaviors.
  • Add turbo tasks and lockfile updates required for the new app and workflows.
apps/docs/cli/create.mdx
apps/chat/proxy.ts
packages/cli/src/helpers/prompts.ts
turbo.json
apps/docs/docs.json
bun.lock
.gitignore

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

- Simplified conditional rendering in `ElectronAuthSuccessClient` for better clarity.
- Enhanced formatting in `SocialAuthProviders` to maintain consistent code style.
- Added braces in `PureUserMessage` to improve readability of conditional logic.
- Reformatted `TextMessagePart` for better alignment and consistency in JSX structure.
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • There are now multiple places controlling the Electron titlebar offset (inserted CSS in apps/electron/src/main.ts, ElectronTitlebarOffset, layout.tsx, and the sidebar styles); centralizing this in a single CSS variable/source of truth would reduce the risk of these getting out of sync over time.
  • In apps/electron/src/main.ts, app.setAsDefaultProtocolClient(APP_SCHEME) is called unconditionally; consider guarding this for development/portable runs (or checking the return value) to avoid unexpected behavior or errors on platforms where protocol handlers can’t be registered.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There are now multiple places controlling the Electron titlebar offset (inserted CSS in `apps/electron/src/main.ts`, `ElectronTitlebarOffset`, `layout.tsx`, and the sidebar styles); centralizing this in a single CSS variable/source of truth would reduce the risk of these getting out of sync over time.
- In `apps/electron/src/main.ts`, `app.setAsDefaultProtocolClient(APP_SCHEME)` is called unconditionally; consider guarding this for development/portable runs (or checking the return value) to avoid unexpected behavior or errors on platforms where protocol handlers can’t be registered.

## Individual Comments

### Comment 1
<location path="apps/chat/app/api/auth/electron-callback/route.ts" line_range="19-27" />
<code_context>
+}
+
+export function GET(request: NextRequest) {
+  const sessionToken = request.cookies.get("better-auth.session_token")?.value;
+
+  if (!sessionToken) {
+    return new NextResponse("No active session found after OAuth.", {
+      status: 400,
+    });
+  }
+
+  const token = createSignedToken(sessionToken);
+  const successUrl = new URL("/electron-auth/success", request.url);
+  successUrl.searchParams.set("token", token);
</code_context>
<issue_to_address>
**suggestion:** The cookie name and token-expiry logic are duplicated and could easily drift from the exchange route.

`electron-callback` and `electron-exchange` each hardcode `better-auth.session_token` and separately implement the `{ s, exp }` structure and 60s expiry. A change on one side could silently break Electron auth. Consider extracting the cookie name and signing/verification into a shared helper (e.g. `lib/electron-auth-token.ts`) so the logic stays in sync and the expiry window is easy to adjust.

Suggested implementation:

```typescript
  const sessionToken = request.cookies.get(ELECTRON_AUTH_COOKIE_NAME)?.value;

```

```typescript
  return signElectronAuthToken(sessionToken);
}

```

To fully implement the suggestion and remove duplication:

1. Create a shared helper at e.g. `apps/chat/lib/electron-auth-token.ts`:

```ts
import crypto from "node:crypto";
import { env } from "@/env.mjs";

export const ELECTRON_AUTH_COOKIE_NAME = "better-auth.session_token";
const ELECTRON_AUTH_TOKEN_TTL_MS = 60_000;

export type ElectronAuthTokenPayload = {
  s: string;      // session token
  exp: number;    // expiry timestamp (ms)
};

export function signElectronAuthToken(sessionToken: string): string {
  const payload = Buffer.from(
    JSON.stringify({ s: sessionToken, exp: Date.now() + ELECTRON_AUTH_TOKEN_TTL_MS } satisfies ElectronAuthTokenPayload)
  ).toString("base64url");

  const sig = crypto
    .createHmac("sha256", env.AUTH_SECRET)
    .update(payload)
    .digest("base64url");

  return `${payload}.${sig}`;
}

export function verifyElectronAuthToken(token: string): ElectronAuthTokenPayload | null {
  const [payload, sig] = token.split(".");
  if (!payload || !sig) return null;

  const expectedSig = crypto
    .createHmac("sha256", env.AUTH_SECRET)
    .update(payload)
    .digest("base64url");

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expectedSig))) {
    return null;
  }

  let decoded: ElectronAuthTokenPayload;
  try {
    decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
  } catch {
    return null;
  }

  if (typeof decoded.exp !== "number" || decoded.exp < Date.now()) {
    return null;
  }

  if (!decoded.s || typeof decoded.s !== "string") {
    return null;
  }

  return decoded;
}
```

2. In `apps/chat/app/api/auth/electron-callback/route.ts`, add an import near the other imports:

```ts
import { ELECTRON_AUTH_COOKIE_NAME, signElectronAuthToken } from "@/lib/electron-auth-token";
```

(Adjust the import path if your alias for `@` differs.)

3. In the corresponding `electron-exchange` route file, replace:
   - Mentions of `"better-auth.session_token"` with `ELECTRON_AUTH_COOKIE_NAME`.
   - Any inline `{ s, exp }` + HMAC signing logic with `signElectronAuthToken`.
   - Any inline verification/expiry logic with `verifyElectronAuthToken`.

4. Remove the now-unused local implementations of signing/verification (and any duplicated TTL constants) from both routes so that all Electron auth token behavior flows through `lib/electron-auth-token.ts`.
</issue_to_address>

### Comment 2
<location path="apps/chat/app/electron-auth/success/client.tsx" line_range="15-21" />
<code_context>
+  const searchParams = useSearchParams();
+  const [launched, setLaunched] = useState(false);
+
+  const openApp = useCallback(() => {
+    const token = searchParams.get("token");
+    if (!token) {
+      return;
+    }
+    window.location.href = `${appScheme}:///auth/callback?token=${encodeURIComponent(token)}`;
+    setLaunched(true);
+  }, [searchParams, appScheme]);
+
</code_context>
<issue_to_address>
**suggestion:** Short token lifetime combined with retry button can lead to opaque failures when the token expires.

With the current 60s token expiry, clicking “Open app again” after waiting on the success page will try to reuse an expired token, and the Electron app only logs an error. This looks like a silent failure to users. Consider either extending the expiry, showing a clear user-facing error when the exchange fails (e.g., an error page), or regenerating a fresh token in this flow (e.g., by re-hitting `electron-callback` instead of reusing the original token).

Suggested implementation:

```typescript
import { useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { Button } from "@/components/ui/button";

export function ElectronAuthSuccessClient({
  appScheme,
}: {
  appScheme: string;
}) {
  const searchParams = useSearchParams();
  const [launched, setLaunched] = useState(false);
  const [tokenExpired, setTokenExpired] = useState(false);

  const openApp = useCallback(() => {
    const token = searchParams.get("token");

    // If there is no token or it has expired, do not attempt to launch the app.
    if (!token || tokenExpired) {
      return;
    }

    window.location.href = `${appScheme}:///auth/callback?token=${encodeURIComponent(
      token,
    )}`;
    setLaunched(true);
  }, [searchParams, appScheme, tokenExpired]);

  useEffect(() => {
    // The Electron token currently expires after ~60 seconds.
    // To avoid opaque failures when retrying, mark it as expired slightly earlier
    // and rely on the UI to inform the user that they need to retry from the app.
    const timeoutId = window.setTimeout(() => {
      setTokenExpired(true);
    }, 55_000);

    return () => window.clearTimeout(timeoutId);
  }, []);

```

To fully implement the behavior described in your comment, you should also:

1. Ensure the “Open app again” (or equivalent) button uses the new callback:
   - Add `onClick={openApp}` to that button.
   - Disable it once the token is used or has expired, e.g. `disabled={launched || tokenExpired}`.

2. Provide a clear user-facing message when the token is expired, for example just below the button:
   ```tsx
   {tokenExpired && !launched && (
     <p className="mt-2 text-sm text-muted-foreground">
       This sign-in link has expired. Please restart sign-in from the Electron app.
     </p>
   )}
   ```

3. If you have tests or stories for this component, update them to cover:
   - Initial state (button enabled).
   - Post-expiry state (button disabled and message visible).
   - That `openApp` is a no-op when `tokenExpired` is `true`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- Introduced scaffolding for packaging ChatJS apps as native desktop applications for macOS, Windows, and Linux using Electron.
- Updated the CLI to prompt users during project creation to include an Electron app, copying a pre-configured `electron/` subfolder with essential files for main process, preload script, system tray, deep-link OAuth flow, and auto-updater.
- Enhanced documentation to reflect the new Electron integration and quickstart instructions.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

18 issues found across 36 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/electron/package.json">

<violation number="1" location="apps/electron/package.json:7">
P2: Use a cross-platform icon generation command here. Every build now shells out to Bash, so the documented Windows packaging flow depends on Git Bash before `electron-builder` even runs.</violation>
</file>

<file name="apps/electron/scripts/generate-icons.sh">

<violation number="1" location="apps/electron/scripts/generate-icons.sh:26">
P2: The generated `.icns` is missing the `icon_512x512@2x.png` Retina asset, so the macOS app icon will be lower quality on high-DPI displays.</violation>
</file>

<file name="apps/chat/app/api/auth/electron-exchange/route.ts">

<violation number="1" location="apps/chat/app/api/auth/electron-exchange/route.ts:34">
P1: Tokens with a missing `exp` field bypass the expiration check. `undefined < Date.now()` evaluates to `false`, so the guard never triggers and the token is treated as valid indefinitely. Add an explicit check that `exp` is a number.</violation>
</file>

<file name="apps/electron/src/main.ts">

<violation number="1" location="apps/electron/src/main.ts:64">
P2: Sensitive token is passed as a GET query parameter. Tokens in URLs are commonly captured in server access logs and intermediary proxy logs. Use a POST request with the token in the body to avoid this exposure.</violation>
</file>

<file name="apps/electron/electron-builder.yml">

<violation number="1" location="apps/electron/electron-builder.yml:17">
P1: The publish config points at `chat-js-1`, so release publishing and auto-update checks will hit the wrong GitHub repository.</violation>
</file>

<file name="apps/electron/entitlements.mac.plist">

<violation number="1" location="apps/electron/entitlements.mac.plist:7">
P1: `allow-unsigned-executable-memory` is an unnecessarily broad hardened-runtime exception here. Since `allow-jit` is already enabled above, this extra entitlement only weakens the app’s macOS memory protections.</violation>
</file>

<file name="apps/docs/desktop/electron.mdx">

<violation number="1" location="apps/docs/desktop/electron.mdx:26">
P3: This tree points readers at `build/icon.png`, but the editable source icon is `electron/icon.png`. Following the current layout will have people modifying a generated file that gets overwritten by `generate-icons`.</violation>

<violation number="2" location="apps/docs/desktop/electron.mdx:69">
P2: The auth flow diagram shows the callback route as POST, but the implementation only exposes GET. This will send readers to the wrong endpoint shape when they debug or reimplement the Electron auth flow.</violation>
</file>

<file name="turbo.json">

<violation number="1" location="turbo.json:71">
P2: Include the icon-generation files in this task's inputs so icon changes cannot be skipped by the Turbo cache.</violation>

<violation number="2" location="turbo.json:72">
P2: Add `build/**` to this task's outputs so Turbo restores the generated icon artifacts on cache hits.</violation>
</file>

<file name="apps/chat/components/auth-providers.tsx">

<violation number="1" location="apps/chat/components/auth-providers.tsx:45">
P1: Don't default to the web OAuth buttons before Electron detection completes. In Electron this briefly exposes the exact in-app sign-in flow the new code is trying to avoid, so users can still hit the `state_mismatch` path before the effect runs.</violation>
</file>

<file name=".github/workflows/electron-release.yml">

<violation number="1" location=".github/workflows/electron-release.yml:6">
P2: Validate or inject the Electron app version from the release tag before publishing. Right now a new `electron-v*` tag can still ship `0.1.0` metadata if `apps/electron/package.json` wasn't bumped.</violation>

<violation number="2" location=".github/workflows/electron-release.yml:34">
P1: Publish the macOS build with signing/notarization credentials. Unsigned macOS artifacts won't work reliably with `electron-updater` and are difficult for users to install.</violation>
</file>

<file name="apps/chat/app/api/auth/electron-callback/route.ts">

<violation number="1" location="apps/chat/app/api/auth/electron-callback/route.ts:9">
P1: This token exposes the real session cookie because it's only signed, then passed through query strings. Use an opaque one-time code or encrypted payload instead of embedding `sessionToken` here.</violation>
</file>

<file name="packages/cli/src/helpers/scaffold.ts">

<violation number="1" location="packages/cli/src/helpers/scaffold.ts:37">
P1: Validate or normalize `appScheme` before writing it into the Electron scaffold. Project names can start with digits, but URI schemes cannot, so some generated apps will get a broken deep-link protocol.</violation>
</file>

<file name="apps/chat/app/layout.tsx">

<violation number="1" location="apps/chat/app/layout.tsx:91">
P2: This duplicates the existing Electron titlebar offset logic and creates two sources of truth for the same body padding.</violation>
</file>

<file name="scripts/sync-template.ts">

<violation number="1" location="scripts/sync-template.ts:143">
P2: The Electron template transform is incomplete, so scaffolded apps still ship with hardcoded ChatJS branding.</violation>
</file>

<file name="packages/cli/src/commands/create.ts">

<violation number="1" location="packages/cli/src/commands/create.ts:218">
P2: The printed Electron command is wrong: after step 1 you're already in the project, and this sub-app expects Bun.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

<body className="antialiased">
<body
className="antialiased"
style={{ paddingTop: "var(--electron-titlebar-height, 0px)" }}
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 12, 2026

Choose a reason for hiding this comment

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

P2: This duplicates the existing Electron titlebar offset logic and creates two sources of truth for the same body padding.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chat/app/layout.tsx, line 91:

<comment>This duplicates the existing Electron titlebar offset logic and creates two sources of truth for the same body padding.</comment>

<file context>
@@ -85,7 +86,12 @@ export default async function RootLayout({
-      <body className="antialiased">
+      <body
+        className="antialiased"
+        style={{ paddingTop: "var(--electron-titlebar-height, 0px)" }}
+        suppressHydrationWarning
+      >
</file context>
Fix with Cubic

- Corrected the URL for Zustand documentation to the new location.
- Updated the URL for AI Elements to reflect the new site structure.
- Updated `turbo.json` to include additional inputs and outputs for the Electron build process.
- Improved token verification logic in `electron-exchange` route to ensure proper type checking.
- Revised documentation to clarify the structure of the Electron app, including the handling of the app icon and build files.
- Adjusted the `electron-builder.yml` repository name for consistency.
- Enhanced icon generation script to support a larger icon size for macOS.
- Updated `SocialAuthProviders` to handle Electron detection more gracefully by rendering nothing until detection completes.
- Modified `entitlements.mac.plist` to remove unnecessary keys for improved security.
- Enhanced `scaffoldElectron` function to ensure app scheme adheres to URI standards by prefixing with "app-" if necessary.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/chat/components/auth-providers.tsx">

<violation number="1" location="apps/chat/components/auth-providers.tsx:61">
P2: Returning `null` here hides all social login buttons on the first render for web users, so the auth section stays blank until hydration/effect completion.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

- Added a step to set the version from the Git tag in the Electron release workflow for macOS, Windows, and Linux.
- Updated `scaffoldElectron` function to inject a placeholder in `package.json` for project name consistency.
- Improved template synchronization by replacing hardcoded package names in the Electron template files.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/electron-release.yml">

<violation number="1" location=".github/workflows/electron-release.yml:35">
P1: In `build-win`, this version extraction uses bash syntax even though `windows-latest` runs `run` steps in PowerShell by default, so the Windows release job will fail before publishing.</violation>
</file>

<file name="apps/chat/components/auth-providers.tsx">

<violation number="1" location="apps/chat/components/auth-providers.tsx:50">
P2: This synchronous Electron check still renders the web OAuth buttons during SSR, so Electron clients hydrate to different markup and can flash the wrong auth UI.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +35 to +36
VERSION="${GITHUB_REF_NAME#electron-v}"
npm version "$VERSION" --no-git-tag-version
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 14, 2026

Choose a reason for hiding this comment

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

P1: In build-win, this version extraction uses bash syntax even though windows-latest runs run steps in PowerShell by default, so the Windows release job will fail before publishing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/electron-release.yml, line 35:

<comment>In `build-win`, this version extraction uses bash syntax even though `windows-latest` runs `run` steps in PowerShell by default, so the Windows release job will fail before publishing.</comment>

<file context>
@@ -29,6 +29,12 @@ jobs:
+      - name: Set version from tag
+        working-directory: apps/electron
+        run: |
+          VERSION="${GITHUB_REF_NAME#electron-v}"
+          npm version "$VERSION" --no-git-tag-version
+
</file context>
Suggested change
VERSION="${GITHUB_REF_NAME#electron-v}"
npm version "$VERSION" --no-git-tag-version
$VERSION = $env:GITHUB_REF_NAME -replace '^electron-v', ''
npm version $VERSION --no-git-tag-version
Fix with Cubic

- Introduced a new `electron-builder.config.js` file to replace the deprecated `electron-builder.yml`, streamlining the build configuration for the Electron app.
- Added a `branding.json` file to centralize branding information, generated by a new script `write-branding.ts`.
- Updated `package.json` to ensure branding script runs during the prebuild step.
- Modified configuration imports in the app to utilize the new branding structure for improved consistency.
- Updated the `scaffoldElectron` function to utilize `electron-builder.config.js` instead of `electron-builder.yml`, enhancing the build configuration process.
- Removed hardcoded values in template files, replacing them with placeholders for improved flexibility and maintainability.
- Added `branding.json` to the excluded files list to streamline template synchronization.
- Adjusted the `applyElectronTemplateTransforms` function to rewrite paths in `tsconfig.json` for better compatibility in monorepo setups.
- Revised the `electron.mdx` file to clarify the transition from `electron-builder.yml` to `electron-builder.config.js`, emphasizing the new structure for app name, URL scheme, and production URL management.
- Updated instructions for configuring the URL scheme and branding values, highlighting the role of `chat.config.ts` in the prebuild process.
- Enhanced clarity on the CLI's automatic pre-filling of GitHub repository details and the implications for app updates.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 11 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/electron/electron-builder.config.js">

<violation number="1" location="apps/electron/electron-builder.config.js:51">
P1: Add the custom protocol to the Linux build too; otherwise OAuth deep links won't return to the packaged Linux app.</violation>
</file>

<file name="apps/electron/scripts/write-branding.ts">

<violation number="1" location="apps/electron/scripts/write-branding.ts:9">
P1: The generated branding keys do not match what `electron-builder.config.js` reads, so packaging metadata resolves to `undefined`.</violation>
</file>

<file name="packages/cli/src/helpers/scaffold.ts">

<violation number="1" location="packages/cli/src/helpers/scaffold.ts:36">
P1: Removing URI-scheme normalization breaks Electron deep links for project names that start with digits.</violation>
</file>

<file name="apps/docs/desktop/electron.mdx">

<violation number="1" location="apps/docs/desktop/electron.mdx:119">
P2: This line overstates the scaffolded release config: the CLI only derives `repo`; `owner` is still a placeholder that must be edited manually.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

createStartMenuShortcut: true,
},

linux: {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 14, 2026

Choose a reason for hiding this comment

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

P1: Add the custom protocol to the Linux build too; otherwise OAuth deep links won't return to the packaged Linux app.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/electron/electron-builder.config.js, line 51:

<comment>Add the custom protocol to the Linux build too; otherwise OAuth deep links won't return to the packaged Linux app.</comment>

<file context>
@@ -0,0 +1,55 @@
+    createStartMenuShortcut: true,
+  },
+
+  linux: {
+    target: [{ target: "AppImage", arch: ["x64"] }],
+    category: "Network",
</file context>
Fix with Cubic


writeFileSync(
resolve(__dirname, "..", "branding.json"),
JSON.stringify({ appName, appPrefix, appUrl, orgName: organization.name }, null, 2)
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 14, 2026

Choose a reason for hiding this comment

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

P1: The generated branding keys do not match what electron-builder.config.js reads, so packaging metadata resolves to undefined.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/electron/scripts/write-branding.ts, line 9:

<comment>The generated branding keys do not match what `electron-builder.config.js` reads, so packaging metadata resolves to `undefined`.</comment>

<file context>
@@ -0,0 +1,12 @@
+
+writeFileSync(
+  resolve(__dirname, "..", "branding.json"),
+  JSON.stringify({ appName, appPrefix, appUrl, orgName: organization.name }, null, 2)
+);
+
</file context>
Fix with Cubic

await cp(templateDir, destination, { recursive: true });

// electron-builder.config.js: inject GitHub publish config
const builderPath = join(destination, "electron-builder.config.js");
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 14, 2026

Choose a reason for hiding this comment

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

P1: Removing URI-scheme normalization breaks Electron deep links for project names that start with digits.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/helpers/scaffold.ts, line 36:

<comment>Removing URI-scheme normalization breaks Electron deep links for project names that start with digits.</comment>

<file context>
@@ -28,32 +28,18 @@ export async function scaffoldElectron(
-  const builderPath = join(destination, "electron-builder.yml");
-  const appId = `com.example.${opts.projectName}`;
+  // electron-builder.config.js: inject GitHub publish config
+  const builderPath = join(destination, "electron-builder.config.js");
   const builder = (await readFile(builderPath, "utf8"))
-    .replace("__APP_ID__", appId)
</file context>
Fix with Cubic

},
```

The CLI pre-fills `owner` and `repo` from your project. Updates only run when the app is packaged (`app.isPackaged`). They are skipped in development.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 14, 2026

Choose a reason for hiding this comment

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

P2: This line overstates the scaffolded release config: the CLI only derives repo; owner is still a placeholder that must be edited manually.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/docs/desktop/electron.mdx, line 119:

<comment>This line overstates the scaffolded release config: the CLI only derives `repo`; `owner` is still a placeholder that must be edited manually.</comment>

<file context>
@@ -99,17 +105,18 @@ Closing the window hides it. Use **Quit** from the tray to fully exit.

-Updates only run when the app is packaged (app.isPackaged). They are skipped in development.
+The CLI pre-fills owner and repo from your project. Updates only run when the app is packaged (app.isPackaged). They are skipped in development.

Customization

</file context>


</details>

```suggestion
The CLI pre-fills `repo` from your project, but `owner` remains `your-github-username` and must be updated before releasing. Updates only run when the app is packaged (`app.isPackaged`). They are skipped in development.
Fix with Cubic

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant