Skip to content

feat(desktop): Electron shell, build pipeline, and renderer skeleton#1012

Open
emsanakhchivan wants to merge 65 commits intoGitlawb:mainfrom
emsanakhchivan:desktop/pr1-electron-shell
Open

feat(desktop): Electron shell, build pipeline, and renderer skeleton#1012
emsanakhchivan wants to merge 65 commits intoGitlawb:mainfrom
emsanakhchivan:desktop/pr1-electron-shell

Conversation

@emsanakhchivan
Copy link
Copy Markdown
Contributor

@emsanakhchivan emsanakhchivan commented May 4, 2026

PR 1: Electron Shell + Build System

Summary

Introduces the OpenClaude Desktop application as packages/desktop/ — a bootable Electron app with main process, preload, and renderer skeleton. Window opens with "OpenClaude Desktop" placeholder, dev server works with HMR, build produces runnable binary, and all 11 tests pass. This PR is the first in the 18-PR desktop stack, establishing the foundational Electron architecture, build pipeline, and test infrastructure that subsequent PRs build upon.


Design Doc

Committed at docs/desktop-app-design.md — covers architecture, permission system, SDK integration, database schema, 18-PR wave plan, and tech stack.

Electron vs Tauri: Leaning toward Electron for stability and Node.js native runtime (SDK runs directly in main process — no sidecar needed for MCP servers/tool execution). Tauri's smaller binary is attractive but requires sidecar for Node.js work + Rust for native code. Open to discussion.

Same repo vs separate: Direct SDK import without npm publish, shared CI infrastructure.

openclaude-nth-v.mp4
openclaude-d-o

Settings Menu Design V:

openclaude-settings

What changed

Status File Description
A docs/desktop-app-design.md Design doc: architecture, Electron vs Tauri rationale, permission system, SDK integration, database schema, 18-PR wave plan, tech stack
M .github/workflows/pr-checks.yml Adds desktop CI job — runs vitest on packages/desktop/tests/ with jsdom, parallel with existing web and smoke jobs
A packages/desktop/package.json Desktop package config: electron-vite scripts, electron-builder packaging (win/mac/linux), vitest, React 19, "private": true
A packages/desktop/electron.vite.config.ts Three-target Vite config — main CJS bundle, preload CJS bundle, renderer React bundle
A packages/desktop/tsconfig.json TypeScript config: ES2022 target, bundler moduleResolution, React JSX, @/* path alias
A packages/desktop/vitest.config.ts Vitest config with jsdom environment, React plugin, @ alias for renderer imports
A packages/desktop/.gitignore Ignores out/, release/, node_modules/, dist/
A packages/desktop/build/icon.{ico,icns,png} Placeholder icons for electron-builder (win/mac/linux)
A packages/desktop/src/main/index.ts Main process: BrowserWindow (1280×800, min 900×600), preload loading, app lifecycle, programmatic CSP injection with dev/prod branching
A packages/desktop/src/preload/index.ts Preload: exposes window.platform (os, arch) via contextBridge — no Node.js API leakage
A packages/desktop/src/renderer/index.html Renderer HTML entry (no inline CSP — injected from main process)
A packages/desktop/src/renderer/main.tsx React entry: ReactDOM.createRoot mount
A packages/desktop/src/renderer/App.tsx Placeholder App shell with dark theme
A packages/desktop/src/renderer/env.d.ts TypeScript declaration for window.platform
A packages/desktop/src/renderer/styles/globals.css Dark theme globals (#09090b background, flex layout)
A packages/desktop/tests/main/index.test.ts 6 tests: security settings, preload .cjs path, dimensions, watchWindowShortcuts, window-all-closed (macOS + non-macOS), vitest-only guards for bun compatibility
A packages/desktop/tests/preload/index.test.ts 2 tests: platform info exposed, no Node.js APIs leaked, vitest-only guards
A packages/desktop/tests/renderer/App.test.tsx 3 tests: renders header, loading message, app-shell structure, vitest-only guards

Notable: Root package.json unchanged — no workspaces added yet. Workspaces deferred to PR2 when SDK dependency is needed.


Why it changed

OpenClaude needs a desktop application to provide a native OS experience (system tray, file dialogs, local file access, dock/taskbar integration) that the CLI and web UI cannot offer. The design spec defines a full GUI desktop app with CLI parity, built as a feature-based monolith inside the monorepo. PR1 establishes the three-process Electron architecture (main / preload / renderer) with a working dev loop, production build pipeline, and comprehensive test coverage. This is the foundation upon which all 18 PRs depend.

Beyond CLI parity, the desktop surface unlocks UX patterns that a terminal or browser tab cannot: instant model switching from a persistent toolbar, native keyboard shortcuts, and a workspace that retains context across conversations without page reloads. These capabilities require a dedicated application layer, which this PR lays the groundwork for.


Impact

  • User-facing impact: None — desktop package is additive, no CLI behavior changes
  • Developer/maintainer impact:
    • New workspace package at packages/desktop/cd packages/desktop && bun install && bun run dev
    • Root bun install resolves workspaces including desktop
    • 11 tests in packages/desktop/tests/ — run with bun run test
    • Build produces out/main/index.cjs, out/preload/index.cjs, out/renderer/ assets
    • bun run package:win produces runnable binary in release/
  • Architecture decisions:
    • CJS output for main + preload (Electron requirement), React bundle for renderer
    • CSP injected from main process via session.defaultSession.webRequest.onHeadersReceived — dev mode allows ws://localhost:* for Vite HMR, production is strict
    • Dependencies stripped to minimum — better-sqlite3, electron-log, electron-updater, superjson deferred to later PRs
    • @electron-toolkit/utils for platform helpers, raw contextBridge (no @electron-toolkit/preload)

Testing

  • bun run test11 pass, 0 fail (6 main + 2 preload + 3 renderer)
  • bun run ts:check — 0 errors
  • bun run build — electron-vite produces all 3 bundles successfully
  • Main process tests use vi.doMock + vi.resetModules pattern to properly test promise-based whenReady()
  • Cross-platform preload path test uses regex /preload[/\\]index\.cjs/ for Windows/POSIX
  • window-all-closed handler tested for both macOS (no quit) and non-macOS (quit)
  • watchWindowShortcuts registration verified via browser-window-created event simulation

Notes

  • Provider/model path tested: N/A (Electron shell, no LLM interaction)
  • Screenshots attached: N/A
  • Follow-up work: This PR is PR1 of an 18-PR, 6-wave desktop stack:
    • Wave 1 (Foundation): PR1 ← you are here | PR2: tRPC Infrastructure | PR3: SQLite + Drizzle DB | PR4: React Skeleton + shadcn UI Kit
    • Wave 2 (Core): PR5: Chat UI + Streaming + Permissions | PR6: SDK Host Integration | PR7: Tool System UI | PR8: Settings UI
    • Wave 3 (Advanced): PR9: Monaco Editor | PR10: Diff Viewer | PR11: MCP Management | PR12: Project Management
    • Wave 4 (Skills + Stats): PR13: Plugin/Skill Management | PR14: Stats Dashboard + Graphs (Recharts)
    • Wave 5 (Polish): PR15: UX Polish + Animations | PR16: Auto-updater + Deep Linking | PR17: Comprehensive Test Suite
    • Wave 6 (Distribution): PR18: Build Pipeline + CI + Code Signing
    • Total: 18 PRs, ~33,500 lines across 6 waves
  • Tech stack: Electron 39, electron-vite 3, tRPC 11, trpc-electron, React 19, Tailwind 4 + shadcn, Jotai + Zustand, better-sqlite3 + Drizzle ORM, Monaco Editor, Recharts, Vitest
  • Key design decisions:
    • CSP via session headers (not HTML meta tag) to enable dev/prod branching without breaking Vite HMR
    • Preload path uses .cjs matching electron-vite CJS output format
    • private: true prevents accidental npm publish
    • window.platform typed via env.d.ts for renderer TypeScript support
  • Known limitations:
    • Placeholder icons only (no branding assets yet)
    • No auto-updater configuration (PR16)
    • No custom titlebar (deferred to UX PR)
    • Renderer is a static placeholder — tRPC connection and live UI come in PR2
  • Security considerations:
    • contextIsolation: true, nodeIntegration: false — renderer has no Node.js access
    • sandbox: false — required for process.platform/process.arch in preload
    • Preload exposes only window.platform (os, arch strings) — no filesystem, network, or IPC
    • Production CSP: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data: blob: https:
    • setWindowOpenHandler redirects all window.open to system browser

Ali Alakbarli added 30 commits April 23, 2026 15:16
Adds standalone SDK building blocks with no SDK source dependencies:
- sdk.d.ts: ambient type declarations for SDK bundle
- coreSchemas.ts + coreTypes.generated.ts: Zod schemas and generated types
- errors.ts: SDK-specific error classes
- validation.ts: input validation utilities
- messageFilters.ts: extracted message filter logic
- handlePromptSubmit.ts: imports from messageFilters
- 16 generated-types tests
…signature

Code review finding: assertFunction used `asserts value is Function` which
accepts any function-like value without narrowing. Changed to
`(...args: any[]) => any` for better type safety.
Reviewer noted the header said "Generated from index.ts" but no generator
produces this file. Updated to "Manually maintained — keep in sync with
index.ts". Drift detection added in validate-externals.ts (PR 3).
Tighten SDK public type contract to resolve reviewer blockers:

- PermissionResult: unknown[] → precise 6-shape discriminated union
  (addRules/replaceRules/removeRules/setMode/addDirectories/removeDirectories)
- SDKSessionInfo: snake_case → camelCase (sessionId, lastModified, etc.)
- ForkSessionResult: session_id → sessionId
- SDKPermissionRequestMessage: uuid + session_id now required
- SDKPermissionTimeoutMessage: added uuid + session_id
- SessionMessage: parent_uuid → parentUuid
- SDKMessage/SDKUserMessage/SDKResultMessage: replaced loose inline
  definitions with re-exports from coreTypes.generated.ts
Modifies core modules for SDK integration:
- QueryEngine, tools, state, commands: SDK type hooks
- SDK shared utilities (shared.ts, permissions.ts)
- 21 SDK tests (shared-utils, permissions)

Stack: main ← pr1-foundation ← pr2-sdk-core
casing.ts provides recursive key transformation for the SDK boundary
layer. Internal runtime uses snake_case; public API exposes camelCase.
Will be used by shared.ts, sessions.ts, query.ts at export boundaries.
Covers snakeToCamel, camelToSnake, mapKeysToCamel, mapKeysToSnake
including nested objects, arrays, null/undefined, and round-trips.
Completes the SDK implementation:
- SDK build target (dist/sdk.mjs) with TUI dependency stubbing
- External dependency lists (scripts/externals.ts)
- SDK type generation from Zod schemas (scripts/generate-sdk-types.ts)
- External validation (scripts/validate-externals.ts)
- SDK source: index, query, v2, sessions modules
- agentSdkTypes: re-exports SDK functions (query, createSession, etc.)
- 136 SDK tests + 7 build scanner tests

Stack: main ← pr1-foundation ← pr2-sdk-core ← pr3-sdk-runtime
shared.ts: SDKSessionInfo, ForkSessionResult, SessionMessage fields
now use camelCase matching sdk.d.ts. SDKPermissionRequestMessage and
SDKPermissionTimeoutMessage gain required uuid + session_id fields.

permissions.ts: onPermissionRequest/onTimeout callbacks now include
uuid and session_id in emitted messages.
sessions.ts: toSDKSessionInfo outputs camelCase keys, entryToSessionMessage
uses parentUuid, forkSession returns sessionId.

query.ts: reads sessionId from listSessions/forkSession results
instead of snake_case session_id.
session_id → sessionId in forkSession result assertions and
getSessionMessages calls.
…solve wrapper

Add createOnceOnlyResolve utility to prevent double-resolution of promises
when timeout and host response happen simultaneously. This ensures
deterministic behavior in the permission handling flow.
Changes:
- Use _+([a-z]) regex to match multiple consecutive underscores before letters
- Add lookahead (?=. ) to preserve underscore-letter pairs at string end
- Handle dunder names (__proto__, __typename) by stripping wrapper and capitalizing
- Add tests for consecutive underscores and trailing underscore preservation
When a canUseTool callback throws an error, the catch block now
includes the original error message in the denial message, making
debugging easier for SDK consumers.
Add timeout parameter to acquireEnvMutex() to prevent infinite waits
in deadlock scenarios. The timeout is optional and defaults to no timeout
(wait forever) for backward compatibility.

Returns a MutexAcquireResult object with acquired status and optional
timeout reason for failed acquisitions.
Add tests for timeout scenarios when host doesn't respond to permission
requests, fallback behavior when no onPermissionRequest callback, and
MCP connection edge cases for undefined/empty config.
…rror handling

- Add createPermissionTarget() factory that applies onceOnlyResolve at
  registration time, fixing race condition where timeout and host response
  could both try to resolve the same promise
- Add try-catch to releaseEnvMutex() to prevent permanent lock if callback throws
- Extract DEFAULT_PERMISSION_TIMEOUT_MS constant (30 seconds)
- Add MCP config validation rejecting null, non-objects, and arrays
- Preserve error stack traces in MCP connection failures
- Add runtime validation to mapMessageToSDK for null/non-object/invalid type
- Update tests to use createPermissionTarget and add validation tests
…nter, ripgrep)

Resolve conflicts:
- package.json: accept main's v0.7.0 + updated description
- build.ts: accept main's expanded feature flags; keep CLI_EXTERNALS import
- externals.ts: add @vscode/ripgrep from main to external list
- agentSdkTypes.ts: keep PR3's real type definitions over main's any stubs
Resolve 5 conflict files — all taking main's side:
- src/QueryEngine.ts: selective tool schema cache invalidation
- src/bootstrap/state.ts: parentSessionId field, AsyncLocalStorage JSDoc
- src/entrypoints/sdk/permissions.ts: SDKLogger injection, NO_SESSION_PLACEHOLDER,
  race condition fix (register before emit), try-catch host callback
- src/entrypoints/sdk/shared.ts: snake_case naming, enriched JSDoc, timeout cleanup
- tests/sdk/permissions.test.ts: regression tests for PR2 bug fixes
- Remove extra closing parenthesis in permissions.ts
- Remove extra closing braces in shared.ts type definitions
- Wrap MCP connection in try/catch to continue without MCP tools on failure
- Remove extra closing parenthesis in permissions.ts
- Remove extra closing braces in shared.ts type definitions
- Wrap MCP connection in try/catch to continue without MCP tools on failure
- Clarify thinkingConfig logic: use ?? true instead of !== false
- Add explanatory comment about thinkingEnabled default behavior
- Apply createOnceOnlyResolve wrapper in QueryImpl.registerPendingPermission
- Add try-catch around injectAgents() to gracefully handle plugin agent
  tool validation failures (prevents test crashes from unknown 'LS' tool)
- Add console.warn logging to agent loading/injection catch blocks for
  debugging visibility (matches v2.ts pattern)
- Add pendingPermissionPrompts.clear() to close() and interrupt() methods
  in both query.ts and v2.ts to prevent memory accumulation
- Add close() method to SDKSession interface and SDKSessionImpl
- Wrap MCP connection in query.ts with try-catch (matches v2.ts behavior)
- Add timeoutQueue cleanup in finally blocks (query.ts + v2.ts)
- Remove error.stack from MCP error messages to prevent internal path leak

All 208 SDK tests pass. TypeScript errors are pre-existing.
- Add SDKAgentLoadFailureMessage type for agent load failure events
- Emit agent definition/injection failures to SDK message stream
- Add tool name to permission timeout denial message
- Replace 'as any' casts with proper typed state access
- Fix supportedCommands to use correct mcp.commands/plugins.commands paths
- Update test for correct AppState structure
Blocking Issues Fixed:
- MCP cleanup missing on session/query close - now disconnects MCP clients
  to prevent resource leaks in long-running processes with multiple sessions
- Engine reference not cleared on close - now sets _engine = null to prevent
  memory leaks
- Added MCP cleanup tests (9 new tests covering cleanup scenarios)

Non-Blocking Issues Fixed:
- Removed redundant catch block that just rethrew errors (query.ts)
- Fixed inconsistent timeout denial message format (permissions.ts)
- Fixed hardcoded tool name 'Bash' in test (permissions.test.ts)
- Exported PermissionResolveDecision type for SDK consumers (index.ts)

All 217 SDK tests pass.
- Add close() method to SDKSession interface (documented but missing from type)
- Fix SDKSessionInfo, ForkSessionResult, SessionMessage field naming:
  snake_case → camelCase to match sdk.d.ts public contract and implementation
- Add uuid and session_id to SDKPermissionTimeoutMessage for correlation
- Fix JSDoc comment in forkSession to use sessionId (not session_id)

These changes align internal types (shared.ts) with the public SDK contract
(sdk.d.ts) and actual implementation output. The merge from origin/main
introduced snake_case types that mismatched camelCase implementation and tests.
Merge 0f3aa7a incorrectly took main's side for this comment, reverting
PR2 fix c725c48. Project has migrated to ~/.openclaude.json, not ~/.claude.json.

This is the only PR2 fix lost during merge - all other PR2 fixes
(permissions.ts race conditions, state.ts parentSessionId, etc.)
are preserved in PR3 via subsequent fix commits.
Ali Alakbarli added 14 commits May 3, 2026 05:47
package.json exports (Breaking Change Mitigation):
- Add "./package.json": "./package.json" for tool compatibility
- Add "./dist/cli.mjs": "./dist/cli.mjs" for CLI bundle access
- Keep ./sdk as sole library entrypoint
- Root import intentionally blocked (CLI-first package, no main field)

build.ts (Bug Fix):
- Add | undefined to result/sdkResult type declarations
- Add optional chaining: result?.success, sdkResult?.success
- Prevents TypeError masking actual build errors when Bun.build throws

tests/sdk/package-consumer-types.test.ts:
- Update simulated exports to match real package.json
- Add tests verifying exports map structure and file existence
- Add workspace support to root package.json
- Create packages/desktop with electron-vite build system
- Main process: window creation, app lifecycle, security settings
- Preload: contextBridge with platform info
- Renderer: React skeleton with dark theme
- Tests: 8 tests (main/preload/renderer)
- Build: electron-vite produces main/preload/renderer bundles

Part of 18-PR desktop app implementation plan.
Base branch: sdk/pr3-sdk-runtime
Production CSP no longer allows unsafe-eval or unsafe-inline scripts.
style-src retains unsafe-inline for Vite's injected styles.
electron-vite couldn't resolve trpc-electron/main from workspace root
node_modules. Externalize these deps instead of bundling. Also update
package.json main field to out/main/index.cjs (electron-vite output).
Merge origin/main into desktop/pr1-electron-shell to include:
- feat: SDK Runtime — Query Engine, Sessions, Build Pipeline (Gitlawb#984)
- fix(web-search): surface diagnostic for 0 hits (Gitlawb#1006)
- feat: Karpathy guidelines skill (Gitlawb#909)
- fix: commit attribution configuration (Gitlawb#920)
- feat: provider profiles in user config (Gitlawb#969)
- feat: self-hosted Firecrawl (Gitlawb#949)
- feat: context partitioning and relevance pruning (Gitlawb#849)
- fix: user agent loading (Gitlawb#972)
- fix(groq): strip unsupported store field (Gitlawb#983)
- feat: Hicap gateway provider (Gitlawb#979)
No conflicts — clean auto-merge.
- Fix critical preload path mismatch (.js → .cjs) that broke contextBridge at runtime
- Rewrite main-process tests to properly exercise whenReady promise chain
  (old tests used callback pattern, all assertions were silently skipped)
- Remove unused production deps (better-sqlite3, electron-log, electron-updater, superjson)
- Remove unused @electron-toolkit/preload devDep
- Clean external array in vite config
- Add .catch() error handler on app.whenReady()
- Tighten CSP by removing dev-only ws://localhost:* from connect-src
- Move CSP from HTML meta tag to session headers (dev/prod branching)
- Add Window.platform type declaration (env.d.ts)
- Add tests for optimizer, window-all-closed (win32 + macOS)
- Extract mockApp to test scope for handler inspection
- Remove @types/better-sqlite3, add private:true, clean vite config
…rkspace

Previous lockfile had drifted significantly, missing @smithy/* and
@aws-sdk/* transitive resolutions needed by CLI build/smoke.
Adding "workspaces": ["packages/*"] changes bun's module resolution
and stops hoisting transitive deps (@smithy/*, @aws-sdk/*, vscode-jsonrpc,
highlight.js) needed by CLI build. Desktop package is self-contained
in PR1 with no SDK dependency — workspaces deferred to PR2+ when SDK
integration is actually needed.
Root-level `bun test` discovers desktop test files but lacks
vitest-specific APIs (vi.resetModules, vi.doMock) and jsdom.
Main tests: skip when vi.resetModules unavailable (describe.skip guard)
Renderer tests: skip when DOM environment unavailable (document check)
Preload tests: pass normally (only uses basic vi.fn/vi.mock)
…covery

CI runs `bun test` from repo root which discovers *.test.* files.
Desktop tests use vitest-specific APIs (vi.resetModules, vi.doMock,
jsdom, @testing-library/react) incompatible with bun's built-in runner.

Rename *.test.ts -> *.vitest.ts so bun's test runner ignores them.
Vitest discovers them via the updated include pattern in vitest.config.ts.

Result:
- bun test (root CI): 0 desktop tests discovered — clean
- vitest (packages/desktop): 11/11 pass
@gnanam1990
Copy link
Copy Markdown
Collaborator

Thanks for this, @emsanakhchivan — and congrats on #984 landing earlier today. The PR1 code itself is well-scoped: additive, tests are thoughtful (the cross-platform path regex, the vi.doMock + resetModules pattern for whenReady(), the macOS vs non-macOS window-all-closed split), CSP injection from the main process is sensibly done, preload exposes only window.platform. On its own merits PR1 is landable.

The harder question is the 18-PR / ~33,500-line commitment outlined in the body. Merging PR1 implicitly endorses the full stack — once packages/desktop/ exists and the workspaces change is in package.json, walking it back is awkward. Before I review-approve, a few things I'd want answered:

  1. Is there an RFC / design doc for the desktop app outside this PR body? The body lists waves and a tech stack (Electron 39, electron-vite, tRPC 11, React 19, better-sqlite3 + Drizzle, Monaco, Recharts, Jotai + Zustand) but doesn't motivate why those vs. e.g. Tauri (smaller binary), or why this lives in the main repo vs a separate openclaude-desktop repo. A short doc would let the rest of us reason about the long-term maintenance shape.

  2. Has this been agreed with the other co-maintainers? cc @kevincodex1 @anandh8x — flagging this for visibility because adding "workspaces": ["packages/*"] to root package.json is the structural lock-in moment. Once that's in, every future change to the root package config has to consider both surfaces.

  3. Maintenance distribution. Are you committing to drive the full 18-PR stack to completion, or do other folks (paid? volunteer?) need to pick up some waves? An 18-PR feature stack from a single contributor is high-risk if it stalls at PR9 — we'd be left with a half-built desktop app in the repo.

If the answers to 1–3 are "yes there's a doc, yes it's agreed, yes I'll drive it" then PR1 is a clear approve from me. If not, I'd suggest pausing PR1 until alignment lands — not because the code is bad, but because of the path-dependency it creates.

One small nit on PR1 itself: the file table in the body lists tests/main/index.test.ts etc., but the actual files are index.vitest.ts. Worth syncing the body to match the diff so reviewers don't second-guess.

Ali Alakbarli added 2 commits May 4, 2026 20:08
Add comprehensive design doc for the desktop app initiative:
- 18-PR wave-based delivery plan
- Electron vs Tauri rationale (stability, Node.js native, sidecar trade-offs)
- Same-repo rationale (SDK access, shared CI, unified development)
- Permission system, SDK integration layer, database schema
- Technology stack and constraints

Addresses reviewer concern about missing RFC/design doc.
- Add `desktop` job to pr-checks.yml (parallel with `web` job)
- Tests run via vitest in packages/desktop/ with jsdom environment
- Rename *.vitest.* back to standard *.test.* pattern
- Add vitest-only guards to gracefully skip in bun:test runner
  - Main tests: skip when vi.resetModules unavailable
  - Renderer tests: skip when jsdom (document) unavailable
  - Preload tests: pass in both runners (basic vi.mock only)

Result:
- CI desktop job: 11/11 pass (vitest + jsdom)
- CI smoke-and-tests: 2 pass, 9 skip, 0 fail (bun:test)
@emsanakhchivan emsanakhchivan force-pushed the desktop/pr1-electron-shell branch from 4248c1e to 53375df Compare May 4, 2026 16:32
… error

describe.skip callback was being evaluated even when skipped,
causing 'Cannot find module @testing-library/react' error.
Moved imports into it() callbacks so they only execute when
tests actually run (vitest only, not bun:test).
@emsanakhchivan
Copy link
Copy Markdown
Contributor Author

emsanakhchivan commented May 4, 2026

Thanks for this, @emsanakhchivan — and congrats on #984 landing earlier today. The PR1 code itself is well-scoped: additive, tests are thoughtful (the cross-platform path regex, the vi.doMock + resetModules pattern for whenReady(), the macOS vs non-macOS window-all-closed split), CSP injection from the main process is sensibly done, preload exposes only window.platform. On its own merits PR1 is landable.

The harder question is the 18-PR / ~33,500-line commitment outlined in the body. Merging PR1 implicitly endorses the full stack — once packages/desktop/ exists and the workspaces change is in package.json, walking it back is awkward. Before I review-approve, a few things I'd want answered:

  1. Is there an RFC / design doc for the desktop app outside this PR body? The body lists waves and a tech stack (Electron 39, electron-vite, tRPC 11, React 19, better-sqlite3 + Drizzle, Monaco, Recharts, Jotai + Zustand) but doesn't motivate why those vs. e.g. Tauri (smaller binary), or why this lives in the main repo vs a separate openclaude-desktop repo. A short doc would let the rest of us reason about the long-term maintenance shape.
  2. Has this been agreed with the other co-maintainers? cc @kevincodex1 @anandh8x — flagging this for visibility because adding "workspaces": ["packages/*"] to root package.json is the structural lock-in moment. Once that's in, every future change to the root package config has to consider both surfaces.
  3. Maintenance distribution. Are you committing to drive the full 18-PR stack to completion, or do other folks (paid? volunteer?) need to pick up some waves? An 18-PR feature stack from a single contributor is high-risk if it stalls at PR9 — we'd be left with a half-built desktop app in the repo.

If the answers to 1–3 are "yes there's a doc, yes it's agreed, yes I'll drive it" then PR1 is a clear approve from me. If not, I'd suggest pausing PR1 until alignment lands — not because the code is bad, but because of the path-dependency it creates.

One small nit on PR1 itself: the file table in the body lists tests/main/index.test.ts etc., but the actual files are index.vitest.ts. Worth syncing the body to match the diff so reviewers don't second-guess.

Thanks for the thorough review, @gnanam1990 really appreciate the detailed feedback on the code quality and test patterns.

You're right that the 18-PR scope is the harder question here. Let me address each point:


Q1: RFC / Design Doc

Design doc committed: docs/desktop-app-design.md

Covers architecture, permission system, SDK integration, database schema, 18-PR wave plan, and tech stack.

Electron vs Tauri — leaning toward Electron for stability and Node.js native runtime (SDK runs directly in main process, no sidecar needed for MCP servers and tool execution). Tauri's smaller binary is attractive but introduces complexity: sidecar process for Node.js work + Rust for any native code. Open to discussion here if there's a preference.

Same repo vs separate — direct SDK import without publishing, shared CI.


Q2: Co-maintainer Approval

Waiting for @kevincodex1 and @anandh8x to review and comment in this thread.

Your workspaces concern was fair, it was in PR1 originally and did break the CLI build, so I've pulled it out. That said, the underlying question doesn't go away: desktop will need SDK access at some point, and we should agree on how before merging.

I see a few paths forward:

Approach Pros Cons
Workspaces (deferred) Clean imports, standard monorepo pattern Root package.json lock-in, bun hoisting issues
Local path import No workspaces, no lock-in Hacky (../../sdk/dist/index.mjs), fragile, breaks type resolution
Publish SDK to npm No workspaces needed, proper dependency Extra publish step, versioning overhead
Sidecar approach Desktop runs SDK in separate process, no workspace needed for bundling More complex IPC, but solves bun build issues

What's your instinct here? Happy to go with whatever the team prefers.


Q3: Maintenance Commitment

I'm committing to drive the 18-PR stack to completion. If the team needs to redistribute work across waves, each wave is scoped to be independently useful, so handoffs are straightforward. Will coordinate with project leads as needed.


Q4: File Table Nit

Resolved — files renamed back to standard *.test.* pattern and PR body updated to include the new desktop CI job. Test files now use vitest-only guards (describe.skip when vi.resetModules unavailable) to gracefully skip in bun:test runner while running the full suite in the dedicated vitest CI job.

@gnanam1990
Copy link
Copy Markdown
Collaborator

One more thing worth raising before this stack picks up momentum: we have no visual sense of what the desktop app actually looks like. No screenshots in PR1, no mockups for the planned waves, no walkthrough GIF, not even a Figma link. For an Electron application PR — where the entire point of the surface is visual — that's the single biggest piece of context missing.

Concretely, before merging PR1, would be very helpful to see:

  • A screenshot of the current PR1 placeholder window actually running (just to confirm it builds and renders end-to-end on at least one OS)
  • Mockups or sketches of the Wave 2 / Wave 3 surfaces (chat UI, tool system UI, settings, Monaco editor, MCP management, stats dashboard)
  • A short asciinema/GIF/video of the dev loop working — so reviewers know the foundation is real, not just a passing test suite

The PR body has 33,500 lines of described intent but zero pixels. Hard to give a confident review on that basis.

@emsanakhchivan
Copy link
Copy Markdown
Contributor Author

One more thing worth raising before this stack picks up momentum: we have no visual sense of what the desktop app actually looks like. No screenshots in PR1, no mockups for the planned waves, no walkthrough GIF, not even a Figma link. For an Electron application PR — where the entire point of the surface is visual — that's the single biggest piece of context missing.

Concretely, before merging PR1, would be very helpful to see:

  • A screenshot of the current PR1 placeholder window actually running (just to confirm it builds and renders end-to-end on at least one OS)
  • Mockups or sketches of the Wave 2 / Wave 3 surfaces (chat UI, tool system UI, settings, Monaco editor, MCP management, stats dashboard)
  • A short asciinema/GIF/video of the dev loop working — so reviewers know the foundation is real, not just a passing test suite

The PR body has 33,500 lines of described intent but zero pixels. Hard to give a confident review on that basis.

@gnanam1990

openclaude-d-o

@emsanakhchivan
Copy link
Copy Markdown
Contributor Author

One more thing worth raising before this stack picks up momentum: we have no visual sense of what the desktop app actually looks like. No screenshots in PR1, no mockups for the planned waves, no walkthrough GIF, not even a Figma link. For an Electron application PR — where the entire point of the surface is visual — that's the single biggest piece of context missing.

Concretely, before merging PR1, would be very helpful to see:

  • A screenshot of the current PR1 placeholder window actually running (just to confirm it builds and renders end-to-end on at least one OS)
  • Mockups or sketches of the Wave 2 / Wave 3 surfaces (chat UI, tool system UI, settings, Monaco editor, MCP management, stats dashboard)
  • A short asciinema/GIF/video of the dev loop working — so reviewers know the foundation is real, not just a passing test suite

The PR body has 33,500 lines of described intent but zero pixels. Hard to give a confident review on that basis.

@gnanam1990

Settings Menu Design V:

openclaude-settings

@kevincodex1
Copy link
Copy Markdown
Contributor

can we align the branding to https://openclaude.gitlawb.com/ ? so we will have a uniformed look and feel

@kevincodex1
Copy link
Copy Markdown
Contributor

this is great btw. thank you

Add Section 9 — Branding & Design System (Website Alignment) to the
desktop app design doc. Documents the complete OpenClaude website design
language that the desktop app must replicate for a unified visual identity:

- Typography: Fira Code monospace-first font stack with OpenType features
- Color system: Full light/dark theme tokens with warm orange (#ff7a1a)
  accent replacing the current blue (#0034FF)
- Theme toggle: Light default with localStorage persistence
- Borders: Sharp corners (no border-radius), dashed dividers
- Component specs: Nav, buttons, code blocks, scrollbars, splash screen
- PR4 rebranding checklist covering all UI surfaces
@emsanakhchivan
Copy link
Copy Markdown
Contributor Author

can we align the branding to https://openclaude.gitlawb.com/ ? so we will have a uniformed look and feel

@kevincodex1 Done. Here's what was done:

PR1 — Updated the design doc (docs/desktop-app-design.md) with a full Section 9 covering the complete
website design system: color tokens, typography, theme toggle, borders, component specs, and a
rebranding checklist for future PRs.

PR4 — Rebranded all 31 UI files to match the website:

  • Font: Fira Code monospace
  • Accent: #ff7a1a orange
  • Light/dark theme toggle (default light, localStorage persisted)
  • Sharp corners, dashed dividers, minimal 3px scrollbar
  • All surfaces — splash screen, sidebar, chat, settings, modals
openclaude-nth-v.mp4

@emsanakhchivan
Copy link
Copy Markdown
Contributor Author

Hi, @kevincodex1 - What are your thoughts on the topics mentioned above, specifically the Tauri vs. Electron debate and the architecture we should use to integrate the SDK, etc.?

@kevincodex1
Copy link
Copy Markdown
Contributor

Hi @emsanakhchivan thank you so much for working on this, please give me till tomorrow or Friday to have a deeper review on this

Bring sidebar architecture spec, branding & design system, hybrid JSONL
storage sections from PR3 to keep documentation consistent across branches.
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.

3 participants