Imported from LodyAI/Lody (
apps/electron/src/AGENTS.md). Install upstream withnpx skills add LodyAI/Lody --skill src. Copyright stays with the author.
apps/electron/src
CLAUDE.md is a symlink to this file. Edit AGENTS.md only.
Root AGENTS.md and apps/electron/AGENTS.md also apply. Build, packaging,
native-dependency, and OSS-composition rules stay in apps/electron/AGENTS.md.
Module boundaries
src/main/index.tsowns pre-application startup and the cloud desktop lease;application.tsowns lifecycle wiring, IPC registration and dependency injection. Keep credential stores and business imports behind the entry's dynamic import. Pre-ready APIs stay indesktop-bootstrap; buffer launch URLs before any await. Quit must retain ownership if the embedded CLI has not confirmed exit. Nightly reserves the CLI Host before application import and passes that identity toCliService; Worker stop/restart and control-only mode must not release it. Stable/local keep their existing external-runtime policy.- Put domain services in
src/main/services/*, IPC handlers and input validation insrc/main/ipc/*, and local-project worker/storage code insrc/main/local-project/*. - Main starts, awaits and signals processes only through
@lody/shared/node/process(neverchild_processorkill);check:cli-process-boundaryenforces it. Rules: process layer. - Main-process-only helpers belong in
src/main/utils.ts. Put cross-runtime types and pure logic in@lody/shared; shared Electron IPC contracts live in the narrow@lody/shared/electron-ipcexport. - Electron main and preload code must not import runtime values from the
@lody/sharedroot barrel. Use a narrow subpath so Node bundles do not pull in renderer modules orloro-crdtWASM. - Invoke signatures come from the
IpcServiceclasses and the one constructor list inregister-services.ts; every public instance method is renderer-facing and must have@IpcMethod(). Do not restore parallel handwritten invoke contracts or per-method preload lists.packages/componentsintentionally imports the inferred service type across the app/package boundary withimport type; the import is erased and must never become a runtime dependency. Shared push/send maps remain in@lody/shared/electron-ipc. Preload exposes only{ invoke, on, send }, permits invoke channels by the service groups inpreload/ipc-invoke-policy.ts, and keeps push/send allowlists. The IPC registration test keeps that policy aligned with the registered service constructors. There is nowindow.api. Validate foreign input at the IPC class boundary. - Preload runs under the renderer CSP. Zod schemas used there must pass
{ jitless: true }; do not addunsafe-evalto accommodate Zod's JIT path.
Renderer and window integration
-
Only factory-registered product windows may invoke product-window IPC. Dialogs, embedded browsers, navigation and close actions belong to their source window; auxiliary windows must not overwrite the primary window's persisted view state.
-
Devbar is off by default; hidden Developer Mode enables it, while
LODY_DEVBAR=trueis automation only. Keep data in memory; MCP/Terminals share that switch. GPU means process CPU/RSS, never hardware usage/VRAM. Runtime heap is approximate; only the startup override enables precise readings. -
Generic update metadata may carry localized Markdown under
vendor.lodyChangelog.locales.{en,zh_CN}in addition to the standard EnglishreleaseNotesfallback. Main validates and bounds those remote strings before exposing them throughElectronUpdaterState; renderer code must use the shared safe Markdown renderer rather than raw HTML. -
React root callbacks persist fatal IPC diagnostics; ErrorBoundary owns caught-error UI and PostHog. Deduplicate errors; report mounted only from a committed layout effect. Keep hang capture local and bounded, Wait tied to active stalls, and stack opt-in limited to trusted product main frames. Contract: renderer recovery.
-
A CLI-armed reset (
lody app reset-cache) is consumed once, before any window loads.hardis applied natively here because the renderer may not boot;cacheis handed to the renderer exactly once, because only it can spare the Shortcut outbox and individual localStorage keys. Spec:specs/desktop-local-reset.md. -
Theme changes must also update the native window color in
window-theme.ts. OS appearance changes whilethemeSourceissystemmust retint chrome and notify the renderer (app.nativeTheme). On macOS also subscribe toAppleInterfaceThemeChangedNotification; ChromiummatchMediaandnativeTheme.updatedoften miss Control Center switches. -
Frameless window drag is per-panel, not a root overlay: each column's top header (or a same-height
WindowDragStripwhen there is no header) is-webkit-app-region: drag. Interactive descendants useapp-region-no-drag. Dialog and alert-dialog overlays mount the strip themselves. Hide those regions in native fullscreen. Windows caption buttons stay an OS overlay (MAIN_WINDOW_TITLE_BAR_OVERLAY_HEIGHT); right-edge headers padpr-[144px]so toolbar controls do not sit under them. -
The onboarding window must be native Light before its first renderer paint; normal product windows start from the committed theme (main/AGENTS.md). An automatic login launch may suppress the initial product window, but onboarding and deep-link launches must remain visible during normal product use. Unpackaged E2E windows are the exception: they stay hidden unless
LODY_E2E_SHOW_WINDOW=1and always disable background throttling. -
sessionControl.sendstreams intermediate responses onsessionControl.responsekeyed by request id. The renderer subscribes beforeinvoke, removes the listener after settlement, and treats only the final response as completion. -
Before changing public-browser routing, isolation or its callers, read the service boundary.
-
Image preview export (
services/image-export-service.ts) keeps the native menu, clipboard, and save dialog here because the renderer holds the only copy of the image (ablob:URL main cannot download). Bytes cross once, after the menu selection. Naming/filter logic stays inimage-export-core.tsso it runs undernode --testwithout theelectronruntime.context-menu.tsdraws every other right-click and yields to it on images.
Local file resources
- Electron previews local files directly after matching the target to its on-disk
machine identity; never require daemon liveness or protocol metadata. Relative
paths need the original workspace root; never guess a substitute. Remote paths
stay remote. Never expose filesystem paths in resource URLs.
local-file-resource.tsissues opaque renderer-lifetime capabilities, bounded per renderer, revoked on navigation/destruction. - Each resource read opens a regular file without following a substituted symlink and checks device/inode/size/mtime/ctime before and during reads. Replacement or modification invalidates the preview; no mixing revisions or writes through resources.
- Text above the editor budget uses fixed bounded Range requests. Binary uses raw
streams with backpressure/cancellation; PDFs stay binary
application/pdf, and cross-origin readers can inspectAccept-RangesandContent-Range. Raster header dimensions bound decode cost. The scheme never bypasses CSP, executes file content, or authorizes a remote RPC.
