This document outlines the rearchitected Electron application following modern best practices.
The application has been completely restructured to follow modern Electron development patterns:
src/
├── main/ # Main process (Node.js/Electron)
│ ├── index.ts # Main entry point
│ ├── bootstrap.ts # Service bootstrapping
│ ├── services/ # Business logic services
│ │ ├── interfaces.ts # Service interfaces
│ │ ├── GoogleCalendarService.ts
│ │ ├── timerService.ts
│ │ └── StorageService.ts
│ └── utils/
│ └── ServiceContainer.ts # Dependency injection
├── preload/ # Preload scripts
│ └── index.ts # Type-safe IPC bridge
├── renderer/ # Frontend (React)
│ ├── main/ # Main window
│ │ ├── App.tsx
│ │ ├── App.css
│ │ └── index.html
│ ├── settings/ # Settings window
│ │ ├── Settings.tsx
│ │ ├── Settings.css
│ │ └── index.html
│ └── shared/ # Shared renderer code
│ ├── stores/
│ │ └── useAppStore.ts # Zustand state management
│ └── mock-api.js
├── shared/ # Code shared between processes
│ └── types/
│ └── index.ts # Shared type definitions
└── test/ # Test infrastructure
└── setup.ts # Vitest setup
- Full TypeScript conversion for all components
- Type-safe IPC communications with schema validation
- Shared type definitions between main and renderer processes
- Proper generic types for storage and API calls
- Zustand store for centralized state management
- Automatic synchronization between windows
- Event-driven updates for real-time data sync
- Computed values for derived state
- Service container with type-safe token system
- Interface-based services for better testability
- Singleton and transient service lifecycles
- Centralized bootstrapping process
// Service interfaces for clean contracts
interface ITimerService {
getAllTimers(): Timer[];
startStopTimer(name: string): Promise<TimerResult>;
// ... other methods
}
// Dependency injection with type safety
serviceContainer.registerSingleton(
SERVICE_TOKENS.TimerService,
() => new TimerService()
);- Vitest + Testing Library for modern testing
- Component tests with proper mocking
- Service unit tests with isolated testing
- Type-safe mocks for window.api
- Test coverage reporting
- ✅ Type safety across the entire application
- ✅ Hot reload in development with proper source maps
- ✅ Testable architecture with dependency injection
- ✅ Modern tooling (Vite, TypeScript, Vitest)
- ✅ Separation of concerns with clear boundaries
- ✅ Interface-driven design for better contracts
- ✅ Centralized state management reducing complexity
- ✅ Proper error handling with type safety
- ✅ Clear file organization following conventions
- ✅ Modular services that can be easily replaced
- ✅ Comprehensive testing for regression prevention
- ✅ Documentation with TypeScript interfaces
npm run dev # Development with hot reload
npm run build # Production build
npm run test # Run test suite
npm run test:ui # Interactive test UI- Define interfaces in
src/main/services/interfaces.ts - Implement services with proper error handling
- Register services in
src/main/bootstrap.ts - Write unit tests for all services
- Use the Zustand store for state management
- Components should be pure and testable
- Type all props and state with shared types
- Write component tests for critical paths
- Service Container Pattern for dependency management
- Repository Pattern for data persistence
- Event-Driven Architecture for OAuth flow
- Factory Pattern for service creation
- Flux Pattern with Zustand for state management
- Component Pattern for reusable UI elements
- Hook Pattern for shared logic
- Observer Pattern for IPC event handling
- Type-safe channels with schema validation
- Promise-based async communication
- Event-driven real-time updates
- Error propagation with proper typing
- Context isolation enabled in all renderer processes
- Node integration disabled in renderer
- Content Security Policy through proper preload scripts
- Secure IPC with input validation
- Tree shaking with modern build tools
- Code splitting for renderer bundles
- Lazy loading for heavy components
- Efficient state updates with Zustand
The entire application is now fully typed:
// Shared types between processes
export interface Timer {
name: string;
calendarId: string;
}
// Type-safe IPC channels
export interface IPCChannels {
'add-timer': (name: string, calendarId: string) => Promise<Timer>;
'get-all-timers': () => Promise<Timer[]>;
}
// Type-safe state management
interface AppState {
timers: Timer[];
addTimer: (name: string, calendarId: string) => Promise<void>;
}The new architecture enables easy implementation of:
- Plugin system through dependency injection
- Multiple UI themes with centralized state
- Real-time collaboration with event-driven updates
- Offline support with service worker patterns
- A/B testing with feature flags in state
- Performance monitoring with service instrumentation
Critical: All styling and behavior has been preserved exactly as requested:
- ✅ Settings and App components look and function identically
- ✅ CSS files maintained without changes
- ✅ User interactions work exactly the same
- ✅ Window management preserved (tray, positioning, etc.)
- ✅ Google Calendar integration functionality intact
The rearchitecture is purely internal - users will see no difference in functionality or appearance.
Development → Build → Injection → Packaging → Signing → Notarization → Distribution
1. Development (pnpm dev)
- electron-vite with hot reload
- TypeScript compilation with watch
- React Fast Refresh
- Mock API for OAuth testing
2. Build (electron-vite build)
- Compile TypeScript main process →
out/main/index.js - Bundle React renderer →
out/renderer/ - Copy assets to
out/
3. Credential Injection (scripts/inject-credentials.js)
- Read
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETfrom environment - Replace env var lookups in compiled JavaScript with hardcoded values
- Ensures distributed app has working OAuth without .env file
4. Packaging (electron-builder)
- Bundle app into ASAR archive
- Create universal binary (x64 + arm64)
- Sign with Developer ID certificate
5. Notarization (scripts/notarize.js via afterSign hook)
- Upload to Apple's notarization service
- Wait for approval (2-10 minutes)
- Staple notarization ticket
6. Distribution
- Output:
dist-electron/Dingo Track-{version}-universal.dmg - Upload to GitHub Releases
- Copy to landing page for download
Problem: Environment variables don't exist in distributed apps.
Solution: Inject credentials at build time into compiled JavaScript.
// Before injection (compiled TypeScript):
this.clientId = process.env.GOOGLE_CLIENT_ID || process.env.DIST_GOOGLE_CLIENT_ID || "";
// After injection (post-processed):
this.clientId = "123456789-abc.apps.googleusercontent.com";Security model:
- Client ID is public (visible in OAuth flow anyway)
- Client Secret protected by:
- GitHub Secrets in CI/CD
.envfile (gitignored) locally- Only exists in compiled code, not source
Decision: Enable ASAR ("asar": true in package.json)
Benefits:
- 50% faster app loading (single file vs thousands)
- Reduced file I/O overhead
- Standard for production Electron apps
- Required for credential injection approach
Trade-off: Files inside ASAR cannot be modified after packaging (acceptable for production builds).
Decision: Build single DMG for both Intel and Apple Silicon
"mac": {
"target": [{ "target": "dmg", "arch": ["universal"] }]
}Process:
- electron-builder creates
mac-x64-temp/(Intel build) - electron-builder creates
mac-arm64-temp/(ARM build) - Binaries merged with
lipo→mac-universal/ - Custom notarization script skips
-tempdirectories - Only final merged app is notarized
Benefits:
- One download for all Mac users
- Native performance on both architectures
- Simpler version management
Trade-off: ~2x DMG size (acceptable for desktop app, ~100MB total).
Certificate: Developer ID Application (not Mac App Store)
- Allows direct download distribution
- No App Store review process
- Required for Gatekeeper approval
Custom notarization script rationale:
- electron-builder's built-in notarization had environment variable loading issues
- Custom script provides:
- Explicit credential loading from
.envor environment - Skip temporary build directories
- Better error messages
- Works reliably in both local and CI environments
- Explicit credential loading from
See PRODUCTION.md for detailed deployment architecture and CODE_SIGNING.md for setup instructions.