Iris
A cross-platform Electron desktop app (macOS, Windows, Linux) for a print shop client. Three-layer architecture with typed IPC, role-based auth, rich CRUD, a five-chart Recharts dashboard, and a Go API path behind the local data layer.
A print-shop work order platform built as a cross-platform Electron app with a strict main/preload/renderer split and a typed `window.api` contract. Five-view analytics dashboard on top of the CRUD flow. What I took from it: typed IPC pays back every time the app gets a new feature.
Context
Iris was built for a print shop client that needed its work order flow in one place. The result is a cross-platform Electron application covering role-based authentication, full CRUD over work orders, and a data model that tracks job specs, billing, delivery, and operational logistics. Orders move through draft, active, completed, and cancelled states; only admins reach the reporting dashboard.
Architecture
Iris is split into three physical layers, each with its own tsconfig and typecheck script:
- Main process: owns the Electron window lifecycle, registers IPC handlers organized by domain (Login, WorkOrder), and controls all privileged Node.js access.
- Preload bridge: exposes a narrow, typed
window.apisurface to the renderer viacontextBridge. Every channel is a typed function, declared in ambient files. - Renderer: a React 19 SPA that talks to the main process only through
window.api. Pure aggregation utilities (filtering, chart data, summaries) live inside the renderer so they can be tested in isolation.
Shared domain models (WorkOrder, User) live in a model/ directory imported by both the main process and the renderer, so the types cannot drift across the process boundary. A companion Go HTTP API is designed to replace the local data layer over time.
Analytics
The dashboard uses Recharts for five views: monthly order volume, revenue by month, status distribution, delivery method breakdown, and top-client ranking. Operators filter by date range and by responsible staff member. Each chart answers a specific operational question and has its own empty, loading, and filtered-empty states.
Forms and testing
Forms are built with React Hook Form and Zod schemas, so validation, types, and UI live in one place. Vitest and React Testing Library cover the renderer independently of the Electron runtime, using vi.stubGlobal to isolate the IPC surface.
Delivery
Packaging uses electron-builder: DMG for macOS, NSIS for Windows, and AppImage, Snap, and Deb for Linux. Typecheck scripts for main, preload, and renderer run as a required preflight before any production build. Dev uses electron-vite for HMR. The UI targets Serbian-speaking operators (sr-Latn), registered at the Electron level via app.commandLine.appendSwitch('lang', 'sr-Latn'), with all date and currency formatting using sr-Latn-RS locale options.
What I took from it
Typed IPC boundaries keep an Electron app maintainable once it has real surface area. Investing in the preload bridge early costs little and pays back every time a new feature needs a new channel. Analytics UX and CRUD UX need different grammar in the same app: charts want sparse, direct answers; forms want rails and feedback.