Frontend
Routing
All three page routes are lazy-loaded via React.lazy() in main.jsx, wrapped in <Suspense> with a .loader spinner fallback:
const Dashboard = React.lazy(() => import("./pages/Dashboard/Dashboard"));
const CreateProfile = React.lazy(() => import("./pages/CreateProfile/CreateProfile"));
const Settings = React.lazy(() => import("./pages/Settings/Settings"));
<BrowserRouter>
<Routes>
<Route path="/" element={<AppLayout />}>
<Route index element={<Suspense fallback={<div className="loader" />}><Dashboard /></Suspense>} />
<Route path="profile" element={<Suspense fallback={<div className="loader" />}><CreateProfile /></Suspense>} />
<Route path="setting" element={<Suspense fallback={<div className="loader" />}><Settings /></Suspense>} />
</Route>
</Routes>
</BrowserRouter>
AppLayout is the persistent shell. Child routes swap via <Outlet />.
Layout System
┌─────────────────────────────────────┐
│ Titlebar (40px, sticky, z-index 10)│
├────────┬────────────────────────────┤
│ Nav │ PageContainer (flex: 1) │
│ 200px │ ┌──────────────────────┐ │
│ (56px │ │ <AppPg> │ │
│ narrow)│ │ padding: 32px 40px │ │
│ │ │ 24px 20px @800px │ │
│ │ │ 16px 12px @500px │ │
│ │ └──────────────────────┘ │
├────────┴────────────────────────────┤
│ height: calc(100vh - 40px) │
└─────────────────────────────────────┘
- Nav collapses from
200pxto56pxat800pxwindow width — text labels hide viamax-width: 0; opacity: 0(no layout reflow) - Anti-jitter rule: No CSS transitions on layout properties (width, padding). Only visual-only properties (opacity, max-width) transition.
- Content padding steps down:
32px 40px→24px 20px@800px →16px 12px@500px
Vite Build Config
rollupOptions.output.manualChunks splits dependencies into separate chunks:
vendor—react+react-domrouter—react-router-dom
CSS Architecture
Shared form/page styles live in app/src/styles/forms.css (imported by CreateProfile and Settings). Dashboard.css only contains Dashboard-specific styles (header, buttons). Six duplicate @import "../../index.css" lines were removed from component CSS files — Vite handles CSS injection automatically.
Components
SelectMenu
Custom dropdown replacing native <select>, used for the Rainmeter layout picker on CreateProfile. Features animated open/close (120ms), click-outside dismiss, and full theme support via CSS variables.
ResizableTextarea
Custom resizable textarea that replaces the native Windows resize handle (an unstylable OS-drawn widget). The native handle is disabled with resize: none and a themed drag handle is rendered at the bottom-right corner. Height is set directly on the DOM ref during drag — zero React re-renders. Minimum height: 80px.
ImportExportModal
Full-screen modal overlay with backdrop blur for importing/exporting profiles. Supports radio toggle between single/all profiles for export. Auto-detects single vs wrapped JSON format on import.
UpdateBanner
Floating toast at bottom-right driven by useUpdateChecker hook. Three states: available (version + release notes), downloading (percentage + progress bar), downloaded (Restart & Install button). Dismiss animation slides right 30px + fades over 250ms using only GPU-composited properties.
ProfileCard
Displays profile name and active tool badges (emoji icons). Three action buttons: Run (calls sidecar), Edit (navigates to /profile), Remove.
Form Layout
CreateProfile and Settings forms use app/src/styles/forms.css:
.profileCont/.settingCont:grid-template-columns: 200px 1frwithalign-items: start.form-group:display: grid; grid-template-columns: subgrid— prevents textarea resize from affecting adjacent rows- At
800px: collapses to single-column flex layout
Tauri Window Config
| Property | Value |
|---|---|
| Default size | 1000x800 |
| Minimum size | 700x500 |
| Decorations | false (custom titlebar) |