Project Structure

Tour the main, preload, renderer, shared, and Supabase layout.

The Electron template is split by process boundary first, then by feature. The renderer owns React UI, the main process owns privileged desktop APIs, preload exposes the narrow bridge, and shared/ holds constants/config that both sides can import.

Top-level layout

app.tsx
renderer.tsx
electron.vite.config.ts
electron-builder.yml
tsconfig.node.json
tsconfig.web.json

Renderer: app/

app/ is the sandboxed React SPA. It uses HashRouter, lazy routes, TanStack Query, zustand stores, i18next, and shadcn-style UI primitives.

PathRole
app/app.tsxRoute tree, providers, app-intent navigation, analytics route tracking, onboarding gate.
app/renderer.tsxBrowser entry point, error listeners, Supabase env validation, i18n bootstrap, React mount.
app/routes/Home, Settings, Account, Billing, About, Logs, Help, Update, auth pages, and Showcase routes.
app/components/Shell, sidebar, command palette, window/titlebar pieces, onboarding, billing cards, UI primitives.
app/services/Supabase and Edge Function service calls for todos, profile, avatar, subscription, billing, license, analytics.
app/stores/auth-store and subscription-store zustand state.
app/lib/Auth schemas, Supabase client/types, query client, i18n bootstrap, feature gate, support URLs.
app/locales/English and Simplified Chinese JSON catalogs.
app/showcase/ and app/routes/showcase/[template-demo] capability catalog, component gallery, and demos.

Main process: lib/main/

lib/main/ owns privileged desktop behavior and never runs in the renderer. Notable modules include:

ModuleResponsibility
main.tsApp bootstrap, single-instance lock, protocol registration, service wiring, handler registration.
app.tsBrowserWindow creation, sandbox/context isolation, navigation guard, external URL handling, window-state persistence hooks.
settings.tsVersioned JSON settings store with forward migrations.
updater.tselectron-updater service, background checks, manual checks, status events.
tray.ts and menu.tsNative tray and application menu, including flag-gated Showcase entries.
shortcuts.ts and notifications.tsGlobal shortcut registry and native notification service.
secure-storage.tsOS-keychain-backed storage through Electron safeStorage.
backup.ts, diagnostics.ts, logger.ts, log-redact.tsSettings export/import, redacted diagnostics, file logging, credential scrubbing.
protocols.ts, app-intents.ts, argv-intents.tsResource protocol, deep-link intent dispatch, Windows/Linux second-instance argv parsing.
navigation-guard.ts and external-url.tsOne audited path for rejecting unsafe navigation and opening external URLs in the system browser.
window-state.ts, zoom.ts, permissions.ts, i18n.tsDesktop window preferences, zoom, permission handlers, and main-process localization.
feature-flags.ts, error-reporting.tsMain-process showcase flag and optional Sentry error slot.

This layer is heavily covered by colocated *.test.ts files. Treat those tests as executable documentation when extending main-process behavior.

Preload and IPC

lib/preload/preload.ts exposes one global object:

window.conveyor

The object comes from lib/conveyor/api/index.ts and is published with Electron contextBridge. App code should call useConveyor('window'), useConveyor('app'), or another Conveyor domain instead of importing ipcRenderer.

lib/conveyor/ has four parts:

PathRole
api/Renderer-facing classes for app, file, logs, notification, secure, shortcut, update, and window.
handlers/Main-process IPC implementations registered during bootstrap.
schemas/Zod schemas for args, return values, and main-to-renderer events.
conveyor.d.tsGlobal window.conveyor typing for renderer code.

The full add-a-channel walkthrough lives on the IPC page.

Shared config

shared/constants.ts is the product identity source: app name, app id, product slug, deep-link scheme, support email, repository, homepage, license URL, and EULA settings.

shared/config.ts reads renderer-facing feature flags and opt-in analytics config once. shared/price-config.ts maps Creem product IDs to Free, Pro, and Lifetime tiers.

Supabase, resources, scripts, docs

PathRole
supabase/migrations/Committed SQL migrations. Today it includes the billing read model; Supabase setup docs add the missing profile/todos/avatar SQL.
supabase/functions/Edge Functions for checkout, customer portal, license activation, and Creem webhook handling.
resources/Build assets, icons, macOS entitlements, notarization hook.
scripts/dev.mjsDevelopment launcher that wraps electron-vite dev -w.
scripts/rebrand.mjsOne-pass replacement for product name, slug, scheme, app id, and static config.
scripts/deploy-edge-functions.shDeploys Supabase Edge Functions.
docs/CUSTOMIZE.mdAuthoritative Showcase removal checklist.
docs/APP_ICONS.mdApp icon replacement guide.

Build configuration

electron.vite.config.ts builds three bundles:

BundleEntryNotes
Mainlib/main/main.tsNode/Electron APIs, externalized dependencies, build metadata defines.
Preloadlib/preload/preload.tsBundled dependencies because sandboxed preload cannot require arbitrary packages.
Rendererapp/index.htmlReact/Vite app with Tailwind and React plugin.

Aliases are shared across bundles: @/app, @/lib, @/resources, and @/shared.

tsconfig.node.json covers main, preload, tests, config, resources, app, and shared code. tsconfig.web.json covers renderer-facing code and Conveyor types. Both expose the root alias as @/*.

Environment prefixes are process-specific:

  • VITE_* is bundled into renderer code.
  • MAIN_VITE_* is available to the main bundle through electron-vite.
  • Release/build variables such as UPDATE_SERVER_URL, signing credentials, and Supabase Edge secrets are not app renderer config.

Naming and tests

The template follows the conventions in its own AGENTS.md:

  • React components use PascalCase, such as TitlebarMenu.tsx.
  • Hooks use kebab-case with a use- prefix, such as use-conveyor.ts.
  • Utility and process modules use descriptive kebab-case filenames.
  • Tests are colocated as *.test.ts or *.test.tsx.

Showcase/demo code is marked with [template-demo]. Preview the app with VITE_SHOWCASE_ENABLED=false and MAIN_VITE_SHOWCASE_ENABLED=false before deleting those files, then follow Showcase.

On this page