# Development Short guide for anyone working on the PLWL codebase. ## Layout ``` src/ main process (Node/Electron backend) main.js entry point: tray, single-instance handoff, --launch / --open argv.js command-line parsing shared by boot and second-instance handoff window.js main window creation ipc/ all ipcMain handlers index.js handler registration, icon protocol, dialogs games.js games service: run/stop state, wine resolution, open-with handoff lansync.js LAN sync handlers (identity, devices, re-roll, sync + progress) runner.js spawns wine/proton (env, prefix, playtime, logs) library.js apps registry (apps.json) + settings + playtime files wines.js wine/proton build scan, registry, portable symlink healing prefixes.js prefix registry (files/wineprefixes) importer.js app import (copy/move/leave/prefix) openwith.js "Open with PLWL": ad-hoc runs, no library entry shortcuts.js .desktop shortcut generation paths.js portable layout, FILES-relative path helpers, translation sync i18n.js translation loading + {0} placeholder filling preload.js contextBridge API exposed to the renderer lansync.js LAN sync: identity + pairing, hashing, HTTP receiver, UDP discovery lansyncpush.js the pushing side (createPusher factory, keeps helper state out) appicon.js window/taskbar icon renderer/ UI (plain HTML/CSS/JS, no framework) app.js views, modals, custom-select, all renderer logic index.html static markup for every view style.css the whole look; CSS variables at the top files/ portable data the launcher ships/creates at runtime translations/ en-us.json, pl.json (copied to the data dir on start) test/ plain node scripts, run them with `node test/.test.js` tools/ install.sh / createshortcut.sh / build helpers ``` Main process vs renderer: the renderer never touches Node/Electron directly - everything goes through the `window.plwl` bridge (preload.js) and ipcMain handlers in src/ipc/. Keep new APIs in that pattern. ## Rules - Keep it simple. Plain functions over classes, no abstractions "for later". - Comments explain the WHY (quirks, workarounds, races), not the WHAT. If the code says it, don't repeat it in words. No comments at all when nothing needs explaining. - No duplicate logic: the second time you copy something, move it into a helper (a shared module in src/ or a function next to its users) and call it from both places. - Everything user-visible goes through the translation files (en-us.json, pl.json) with `{0}`, `{1}` placeholders. No hardcoded strings in app.js. - Paths stored on disk are FILES-relative (portable layout): write with toPortablePath(), read with resolvePortablePath() - never store absolute paths for anything inside files/. - Registries (apps.json, wines.json, prefixes.json) are rewritten whole via writeJson(); playtime lives in its own tiny file so the registry is not rewritten while a game runs. - Deleting user data needs a confirm modal; best-effort cleanup is try/catch-and-ignore with a comment saying why it is safe to ignore. - Windows apps run through runGame()/buildEnv() - never spawn wine directly somewhere else (prefix handling, Proton env and playtime live there). - CSS: reuse the existing variables/classes (.button, .field-label, .custom-select, form-card); spacing rhythm comes from --button-margin and the flex gap of the container, not per-element magic numbers. - LAN sync has two sides: the receiver lives in lansync.js, the sender in lansyncpush.js and is built by createPusher(helpers) - several launcher states share the module, so nothing may be kept in its module scope. - Tests are plain assert-by-print scripts (see test/): set PLWL_BUILD_DIR to a temp dir, check(), exit 1 on failure. Add one when fixing a bug. ## Verify before you commit ``` node --check # syntax node test/import.test.js # ...and the other test files node test/lansync.test.js # LAN sync contract (identity, pairing, mirror) npm start # click through the flow you touched ```