# LynxHub Renderer (`src/renderer`)
This folder contains all renderer-side UI code (React + TypeScript) for:
- the main application window
- small auxiliary windows (context menu, loading, toast, share-screen picker, link preview)
- shared renderer services (IPC clients, Sentry boot, styles, shared providers/assets)
## Runtime map
| Area | Purpose |
| --------------- | ---------------------------------------------------------------------------------------- |
| `mainWindow/` | Primary app UI, state, layout, pages, plugin composition. |
| `childWindows/` | Independent lightweight renderer apps for secondary windows. |
| `shared/` | Cross-window renderer utilities: IPC wrappers, shared styles, assets, sentry, providers. |
| `*.html` | Multi-entry build inputs used by electron-vite for each renderer window. |
## Boot sequence (main window)
Main entry: `src/renderer/mainWindow/index.tsx`
Actual order:
1. initialize storage cache once via IPC (`initializeStorage()`)
2. load module renderer code (`loadModules()`)
3. load extension renderer code (`loadExtensions()`)
4. read settings from cached storage (`collectErrors`, `addBreadcrumbs`, etc.)
5. configure root error handling and Redux store
6. render `` inside providers and error boundaries
Root layout is in `src/renderer/mainWindow/App.tsx`:
- `UIProviders`
- `AppHooks`
- onboarding initializer
- extension-injected hooks
- background, title bar, main content, modals
## Main window architecture
### Feature layout
`mainWindow/` is split by responsibility:
- `components/`: reusable UI components and modal systems
- `features/`: heavy feature blocks (session browser/terminal/top bar flows)
- `layouts/`: shell structure (`titleBar`, `tabs`, `navBar`, `statusBar`)
- `pages/`: route-level pages (home, settings, plugins, dashboard, etc.)
- `hooks/`: app-level event wiring and side effects
- `redux/`: reducers, store setup, storage preloading
- `plugins/`: runtime module/extension composition
### State model
Main-window state uses Redux Toolkit plus plugin extension reducers:
- static reducers defined in `redux/reducers/*`
- preloaded state built from storage cache in `redux/store.ts`
- extension reducers injected from `plugins/extensions/loader.ts`
- Sentry Redux enhancer enabled when `collectErrors` is true
Module card data is managed via `useSyncExternalStore` in
`mainWindow/plugins/modules/index.ts`, enabling reactive shared module state
outside Redux for plugin-loaded cards.
## Plugin runtime in renderer
Renderer loads two plugin classes:
### Modules (cards)
- loader: `mainWindow/plugins/modules/index.ts`
- dev mode: tries local alias `@lynx_module/renderer`
- production: imports module entries from plugin addresses returned by IPC
- applies disabled-card filtering from storage and rebuilds card indexes/search data
### Extensions (UI injections)
- loader: `mainWindow/plugins/extensions/index.ts`
- API registry: `mainWindow/plugins/extensions/loader.ts`
- dev mode: tries `@lynx_extension/renderer/Extension`
- production: registers remotes via Module Federation and loads each extension entry
- one failing extension does not block others
Extensions can contribute:
- title/status/nav bar UI
- page customizations
- modal replacements/additions
- custom hooks
- additional reducers
- event listeners via emitter-backed extension events API
## Child windows
Each child window is its own renderer app with independent entry:
- `childWindows/contextMenu`
- `childWindows/loading`
- `childWindows/toast`
- `childWindows/shareScreen`
- `childWindows/linkPreview`
Each has a dedicated HTML file at `src/renderer/*.html`, registered in
`electron.vite.config.ts` under `renderer.build.rollupOptions.input`.
These windows should stay small, focused, and weakly coupled to main-window state.
## Shared renderer layer (`shared/`)
| Path | Role |
| --------------------------- | ----------------------------------------------------- |
| `shared/ipc/*` | Typed IPC wrappers used by renderer feature code. |
| `shared/ipc/lynxIpc.ts` | Low-level adapter over `window.electron.ipcRenderer`. |
| `shared/sentry/*` | Renderer Sentry init + breadcrumb controls. |
| `shared/HeroUIProvider.tsx` | Shared provider wrapper used by child windows. |
| `shared/styles.css` | Shared styles/theme for auxiliary windows. |
| `shared/assets/*` | Shared fonts/icons/static assets. |
## IPC rule in renderer
Do this:
- call domain wrappers in `shared/ipc/*`
- keep channel constants in `src/common/consts/ipcChannels/*`
- keep payload types in `src/common/types/*`
Avoid this:
- direct raw channel strings in feature components
- direct `window.electron.ipcRenderer` usage outside wrappers
## Imports and aliases
Common aliases used in renderer code:
- `@lynx/*` -> `src/renderer/mainWindow/*`
- `@lynx_shared/*` -> `src/renderer/shared/*`
- `@lynx_assets/*` -> `src/renderer/shared/assets/*`
- `@lynx_common/*` -> `src/common/*`
- `@lynx_module/*` and `@lynx_extension/*` for local dev plugin loading
## Contributor workflows
### Add a new main-window page
1. Add page under `mainWindow/pages//`.
2. Wire page into layout/router flow (`layouts` / page container usage).
3. Add/update reducer state if needed.
4. If native data is needed, add IPC wrapper in `shared/ipc`.
### Add a new child window
1. Create `src/renderer/childWindows//index.tsx`.
2. Add `src/renderer/.html` with `#root`.
3. Register HTML entry in `electron.vite.config.ts`.
4. Add main-process window management in `src/main/childWindows`.
### Add a new IPC method used by UI
1. Add channel in `src/common/consts/ipcChannels`.
2. Add/update shared type in `src/common/types`.
3. Implement main handler/listener in `src/main/ipc`.
4. Expose typed renderer function in `src/renderer/shared/ipc`.
5. Consume wrapper in UI code.
### Add extension injection points
1. Extend extension API types in `src/common/types/plugins/extensions`.
2. Implement mutation target in `mainWindow/plugins/extensions/loader.ts`.
3. Consume that injected data in target UI component/layout.
4. Verify no-extension and multi-extension behavior.
## Conventions and guardrails
- Keep business logic near its owning feature folder.
- Keep child windows isolated from main-window Redux when possible.
- Treat plugin-loaded code as untrusted: fail gracefully and isolate errors.
- Prefer shared helpers/contracts in `src/common` for cross-process logic.
- Keep startup paths deterministic; avoid async side effects in render-only components.
## Validate renderer changes
```bash
npm run typecheck:web
```
For cross-boundary changes:
```bash
npm run typecheck
```
Recommended manual checks:
- app launch and initial render
- module/extension loading behavior (dev and production assumptions)
- affected child windows
- IPC events for changed domains
## Related docs
- shared contracts: `src/common/README.md`
- main process architecture: `src/main/README.md`
- project overview: `README.md`