Building and Running poi
Launching Electron — the one trap that always bites
ELECTRON_RUN_AS_NODE=1 is inherited from VSCode-spawned and Claude Code shells and silently
turns Electron into plain Node — require('electron') then fails and app.getVersion crashes.
Always clear it:
env -u ELECTRON_RUN_AS_NODE npx electron .
poi also holds a single-instance lock: if the user's own poi is running, a launched instance
exits with "Another instance is running" (their process may be named poi.exe, not
electron.exe). Kill test instances with taskkill //F //IM electron.exe — that never touches
the packaged poi.exe.
Babel 8 constraints — easy to regress, do not undo
poi migrated to Babel 8 on 2026-07-16 (docs/babel8-migration.md has the full log).
@babel/registercan never come back. register@8 compiles in anode:worker_threadsworker unconditionally, and Electron renderer processes cannot create Node workers (nodeIntegrationInWorkerdoes not help). poi registers the hook in renderers (views/env.ts,assets/js/plugin-preload.js), so it uses the local in-processbabel-hook.js(pirates +transformSync+@cspotcode/source-map-support), fed bybabel-register.config.js(configFile-based; options must stay simple / structured-cloneable-ish).babel-plugin-styled-componentsis shimmed inbabel.config.js(styledComponentsBabel8Shim): it no-opsassertVersion(7)and stripsinherits: syntax-jsx. npmoverridesin package.json align its@babel/corepeer and@babel/plugin-syntax-jsxdep. Remove all three only when upstream ships Babel 8 support.- preset-react must stay scoped away from plain
.ts(configoverrideswithexclude: /\.ts$/): Babel 8's preset-typescript no longer strips the JSX parser plugin for.ts, so JSX-everywhere breaks generic arrows like<T>(x) => x. onlyRemoveTypeImports: falsemust stay on preset-typescript: otherwise Babel 8 keeps a side-effectrequire()for type-only imports, crashing plugins that import types from tsc-only specifiers.modules: 'commonjs'must stay explicit in preset-env: Babel 8 keeps ESM when the caller declares nothing, andbuild/compile-to-js.esis such a caller. It must also keep threadingassumptionsandoverridesthrough — it destructures the config.declare global { var x }plus a module-levelconst xin the same file breaks Babel 8's parser (tsc accepts it). Such blocks live inlib/globals.d.tsandviews/env-parts/globals.d.ts— do not move them back.- The
assumptionsblock is loose-mode-equivalent only withenumerableModuleMeta: true, which the official Babel migration list omits. index.htmlinstalls the require hook in an inline<script>(beforerequire('./views/env')). When auditing hook call sites, grep*.htmltoo — a.js/.ts/.es-scoped grep misses it.
glob v13 on Windows
poi migrated glob ^7 -> ^13 on 2026-07-28. glob v9+ treats \ as an **escape character on
all platforms**, so patterns built from ROOT or path.join() (backslashes on Windows) match
nothing — silently, with no error.
Every glob call site must pass { windowsPathsNoEscape: true }. Returned paths stay
backslash-absolute, identical to v7 output, so downstream path handling is unaffected.
Other v13 API changes already applied: no default export (import { glob } / { globSync }),
no callback form (glob() returns a Promise, so promisify(glob) and
new Promise(res => glob(p, cb)) were removed). The old callback form swallowed errors and
yielded []; the promise form rejects, so .catch(() => []) preserves that in
views/services/plugin-manager/.
Failure mode to watch for: a missing flag shows up as "no plugins found" or "i18n didn't load", never as an exception.
gulp build
gulp buildarchives from git HEAD — commit before building, or it builds stale sources.- Stage-2
npm ci --only=productionmay fail locally if the machine lacks a VC++ toolset (electron-drag-clickneeds node-gyp). npm then rolls back and leavesapp_compiled/node_modulesempty. The gulp task still exits 0, becauserunScriptinbuild/utils.tsignores child exit codes — so a green run does not prove success. Release artifacts are built in GitHub CI where the toolchain exists, so this is environmental. - To verify artifacts locally anyway: (1) check the stage-1 compile output — no
.es/.ts/.tsxoutside__tests__/__mocks__,package.jsonrewritten withlatestCommit; and (2) smoke-boot it withenv -u ELECTRON_RUN_AS_NODE npx electron app_compiled. Node resolution walks up to the repo's ownnode_modules, so the empty artifactnode_modulesdoes not block the boot.