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
Renderer: app/
app/ is the sandboxed React SPA. It uses HashRouter, lazy routes, TanStack
Query, zustand stores, i18next, and shadcn-style UI primitives.
| Path | Role |
|---|---|
app/app.tsx | Route tree, providers, app-intent navigation, analytics route tracking, onboarding gate. |
app/renderer.tsx | Browser 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:
| Module | Responsibility |
|---|---|
main.ts | App bootstrap, single-instance lock, protocol registration, service wiring, handler registration. |
app.ts | BrowserWindow creation, sandbox/context isolation, navigation guard, external URL handling, window-state persistence hooks. |
settings.ts | Versioned JSON settings store with forward migrations. |
updater.ts | electron-updater service, background checks, manual checks, status events. |
tray.ts and menu.ts | Native tray and application menu, including flag-gated Showcase entries. |
shortcuts.ts and notifications.ts | Global shortcut registry and native notification service. |
secure-storage.ts | OS-keychain-backed storage through Electron safeStorage. |
backup.ts, diagnostics.ts, logger.ts, log-redact.ts | Settings export/import, redacted diagnostics, file logging, credential scrubbing. |
protocols.ts, app-intents.ts, argv-intents.ts | Resource protocol, deep-link intent dispatch, Windows/Linux second-instance argv parsing. |
navigation-guard.ts and external-url.ts | One audited path for rejecting unsafe navigation and opening external URLs in the system browser. |
window-state.ts, zoom.ts, permissions.ts, i18n.ts | Desktop window preferences, zoom, permission handlers, and main-process localization. |
feature-flags.ts, error-reporting.ts | Main-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.conveyorThe 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:
| Path | Role |
|---|---|
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.ts | Global 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
| Path | Role |
|---|---|
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.mjs | Development launcher that wraps electron-vite dev -w. |
scripts/rebrand.mjs | One-pass replacement for product name, slug, scheme, app id, and static config. |
scripts/deploy-edge-functions.sh | Deploys Supabase Edge Functions. |
docs/CUSTOMIZE.md | Authoritative Showcase removal checklist. |
docs/APP_ICONS.md | App icon replacement guide. |
Build configuration
electron.vite.config.ts builds three bundles:
| Bundle | Entry | Notes |
|---|---|---|
| Main | lib/main/main.ts | Node/Electron APIs, externalized dependencies, build metadata defines. |
| Preload | lib/preload/preload.ts | Bundled dependencies because sandboxed preload cannot require arbitrary packages. |
| Renderer | app/index.html | React/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 asTitlebarMenu.tsx. - Hooks use kebab-case with a
use-prefix, such asuse-conveyor.ts. - Utility and process modules use descriptive kebab-case filenames.
- Tests are colocated as
*.test.tsor*.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.
