Tutorial: Fix Patterns — error → fix lookup

Fix Patterns — error → fix lookup

Quick table of errors hit and how they were resolved. Add a row whenever you fix something reusable.

Error / symptom Cause Fix
ERR_MODULE_NOT_FOUND for a local import ESM relative import without a file extension Add .js: import x from "./foo.js".
require is not defined in ES module scope Leftover CommonJS require in an ESM file Convert to import, or — only for synchronous config-driven plugin loading / legacy migration — const require = createRequire(import.meta.url). See esm-patterns.md.
__dirname is not defined __dirname/__filename don't exist in ESM Use import.meta.dirname / import.meta.url.
A block loaded but fn is not a function require(esm) returns a namespace, not the function Call .default(...); read .full / .suggestion_exclude as named exports.
Settings read from the wrong directory after ESM conversion Imports evaluate before top-level process.chdir Move the chdir into chdir.js and import it first in common.js.
Cannot find package 'node-fetch' Dependency removed in 2.0.0 Delete the import; use the global fetch.
ESLint: 'console' is not defined in a stray .mjs A scratch file got no Node globals from the flat config It's a temp file — delete it, or add Node globals / ignore it.
ESLint errors no-useless-escape / no-dupe-else-if in prompt files Pre-existing redundant escapes / duplicate else if branches Left as warnings on purpose (changing them can change generated prompts). Review deliberately; don't bulk-rewrite.
Tauri updater build: failed to decode base64 secret key: Invalid symbol 239, offset 0 The TAURI_SIGNING_PRIVATE_KEY CI secret has a UTF-8 BOM (0xEF) — from setting it via Get-Content -Raw key | gh secret set in PowerShell. Installers still build; only the final signing step fails. Re-set BOM-free: gh secret set TAURI_SIGNING_PRIVATE_KEY --body ([System.IO.File]::ReadAllText("$env:USERPROFILE\.tauri\rap-updater.key").Trim()), then gh run rerun <id> --failed. See desktop-updater.md.
stylelint fails in CI but npm run format/tests passed locally npm run lint = eslint . && stylelint …; running only format/tests skips stylelint (modern rgb() notation, rule-empty-line-before, etc.) Run the full npm run lint (or stylelint --fix) before pushing CSS.
Bash sandbox reports package.json truncated / "Unterminated string" The Cowork bash mount returned a false, truncated view of a file the host had written correctly (risk of acting on bad data / data loss) Don't use the bash sandbox for file work here. Use PowerShell + the Read/Edit/Write tools, which see the real host files.
npm run lint / format suddenly flooded with errors after adding files under assets/ assets/ is gitignored but the tools walk the filesystem — ESLint/Prettier indexed the local reference snapshot (old CommonJS/jQuery → 100s of no-undef). Gitignored ≠ tool-ignored. Exclude assets/ in eslint.config.js ignores and .prettierignore (JSDoc only reads its configured source.include, so it's unaffected). Restore a tool-modified snapshot from its own .git (git checkout -- . inside it).
Doc generator: a doc comment never closes (e.g. Reached end of file while still inside a (nested) comment) A /** … */ JSDoc block whose text contains /* (e.g. a glob like presets/*.json or output/*.json) — the /* opens a nested comment that breaks the block Reword to avoid /* and */ sequences (drop the *: write data/presets or the output/ sidecars).
Source files show mojibake (em-dash → ‗,  , é) after a PowerShell edit script Windows PowerShell Get-Content -Raw reads as ANSI (Windows-1252), not UTF-8; writing back as UTF-8 double-encodes any non-ASCII char Read/write UTF-8 explicitly: [IO.File]::ReadAllText(p,[Text.Encoding]::UTF8) / WriteAllText(p,s,(New-Object Text.UTF8Encoding $false)) — or just use the Read/Edit tools. Repair a single round-trip: [Text.Encoding]::UTF8.GetString([Text.Encoding]::GetEncoding(1252).GetBytes($s)).
gh api --method PUT … --input - fails with Problems parsing JSON (HTTP 400) PowerShell pipes a string to a native process's stdin as UTF-16LE; gh expects UTF-8 and chokes Write the JSON body to a UTF-8 (no BOM) temp file and pass --input <file> (not -): [IO.File]::WriteAllText($f,$json,(New-Object Text.UTF8Encoding $false)); gh api --method PUT … --input $f.
A third-party widget (CodeMirror tooltip / autocomplete) ignores our theme colours — renders its own default (light in dark mode, unreadable) The library injects its base theme as an UNLAYERED <style>; our CSS is in @layer components. Unlayered normal rules beat layered ones regardless of specificity, so the library defaults win. Force our surface colours with !important (background/border/text) — important declarations win over the library's unlayered normal ones. (Or move the overrides to an unlayered sheet.) See generic-code-highlighting.css (.cm-tooltip*).
A Vitest suite intermittently FAILs with all tests skipped and a duration pinned at ~10000ms (e.g. promptEngine.integration — 18 skipped, 10020ms) — but passes in isolation Its beforeAll does real async work (ensureCatalog() glob-imports the whole data corpus, ~9s) and races the default 10s hook timeout; a heavier parallel suite tips it over, so Vitest fails the suite and skips its tests. Not a code bug. Give the slow warmup hook explicit headroom: beforeAll(() => ensureCatalog(), 30000). Confirm it's load-driven by running the file alone (passes) vs. the full suite (fails).
The CodeMirror line-number gutter is a bright-white slab and the active line is a milky blue wash in dark mode (computed .cm-gutters bg = rgb(245,245,245), .cm-activeLine = rgba(204,238,255,.267)) CM 6's gutter baseTheme ships BOTH &light .cm-gutters {#f5f5f5} and a &dark variant, chosen by whether the view's theme sets { dark: true }. Our editors never did → CM stayed light and injected the light chrome at the SAME specificity as .dpl-editor .cm-gutters, beating it on source order (CM injects late). Plain CSS can't win. Theme the chrome via an EditorView.theme({...}) extension (StyleModule priority > baseTheme) — targets/web/frontend/lib/editorChrome.js, shared by both editors. Use CSS vars for colours so it stays light/dark adaptive (no hard dark flag). NOT plain CSS.
The CodeMirror editor (DPL box) freezes the whole renderer when it mounts (Chrome tab unresponsive, CDP screenshots time out; navigating away recovers it) scrollbar-gutter: stable (or similar layout property) on the .cm-scroller fights CM's own scrollbar/size measure loop — CM re-measures, the gutter changes the metrics, CM re-measures again, forever. Don't put scrollbar-gutter on the CM scroller. To keep an overlay control off the scrollbar, inset the control instead (right: 1.25rem); CM already wraps long lines inside the scrollbar, so the text stays clear.
A prop is passed by the parent but the child silently ignores it (e.g. App.js passed onOpenImage to GenerateScreen, which never destructured it) → the UI renders fine but the control is DEAD (generated thumbs weren't tappable) JS destructuring makes an unused prop a silent no-op — no error, no warning. Marker/regex "surface parity" checks and render-only tests can NEVER catch this: the element is present, it just does nothing. This is exactly how it shipped. Guard BOTH ends with interaction tests: (1) in the child's test, fireEvent.press the control and assert the callback fired with the right payload; (2) in the parent's test, capture the mocked child's props and assert the callback is a function (typeof props.onOpenImage === "function"). Prove the test by re-introducing the bug — it must FAIL. See targets/mobile/screens/__tests__/GenerateScreen.test.jsx + __tests__/App.test.jsx.
A generated/derived image can't be opened in the detail view even though its thumbnail renders The row stored the raw provider source (data: / https:) instead of the saved gallery item. SingleScreen resolves by saved gallery uri (items.find(it => it.uri === image.uri)), so a raw source can never match. Store what saveImageSrc() RETURNS ({ name, uri }) in the row, not the provider's source, and pass that object to onOpenImage.
An absolutely-positioned overlay pinned over a CodeMirror editor (e.g. Manage's Modify/Draft corner) overlaps the code on a narrow pane, even though a padding-top gutter was added to .cm-content to clear it CM's baseTheme sets .cm-content { padding: 4px 0 } unlayered, at a specificity our .dpl-editor-wrap .dpl-editor .cm-content rule can't beat on source order — so the intended gutter (2.5rem) computes as 4px and the overlay lands on the text. Wide panes hide it (short lines never reach the corner); it only bites on the phone width. Measure the computed padding-top to confirm (4px, not 40px). Don't rely on the CM gutter for overlay clearance. On the narrow breakpoint, un-pin the overlay: make the wrapper a flex column and set the overlay position: static; order: -1; align-self: flex-end so it sits above the editor. If its popover is clipped by an overflow: hidden ancestor, float the popover to a position: fixed bottom sheet (see mobile-sheets.css). Example: manage-responsive.css.
A shared module can't be imported by a target at all (Metro: import.meta undefined / a Vite import.meta.glob is meaningless; Node: same) -- so the target hand-ports it and the copies drift A plugin pool needs discovery, and every runtime discovers differently: Vite import.meta.glob, Node fs.readdirSync, Metro neither (static module graph, no fs). A shared module that reaches for one locks the other targets out. Discover through a generated static index -- plain import statements, the one construct all three understand (scripts/build-provider-registry.mjs -> targets/shared/registry.generated.js; a --check mode in npm test fails if stale). Keep shared modules free of import.meta / node:, and inject anything platform-specific (see _shared/transport/config.js).
A PowerShell-driven node -e "..." codemod reports success but silently corrupts the files (here: it ate the label: line in 15 provider configs and wrote a literal \n) PowerShell interpolates $ inside the double-quoted command string, so a regex backreference (\/``) never reaches Node -- the replacement drops the captured text. The script still prints its happy changed N message. Never inline node -e through PowerShell when the code contains $ -- write a real .mjs script file and run node script.mjs. And always read a file back after a scripted bulk edit instead of trusting the script's own success message (cf. the [IO.File]::ReadAllText relative-path landmine).
Jest (jest-expo / React Native) throws A dynamic import callback was invoked without --experimental-vm-modules as soon as a test imports a module that lazy-loads with import() Jest runs in Node's CommonJS VM, where a native import() can't work. babel-preset-expo deliberately LEAVES import() alone (Metro uses it for code-splitting), so it reaches the VM intact. The shared provider manifests lazy-load their code/ + settings.js exactly this way. Add babel-plugin-dynamic-import-node to babel.config.js under env.test only -- it compiles import() down to require() for tests while the real Metro bundle keeps its dynamic imports. Also give Jest a moduleNameMapper for any Metro extraNodeModules alias (^shared/(.*)$) AND for ^@babel/runtime/(.*)$ (helpers can't resolve from a file outside the package).
A whole RN screen renders BLANK (white); the minified bundle logs Cannot access 'Ge' before initialization. Every unit test still passes. A hook/const referenced a value declared LOWER in the component (here a useRef(settings) placed above const settings = useMemo(...)) -> temporal-dead-zone ReferenceError, which React turns into an empty tree. The component tests never caught it because they mock the engine and the theme, so the real module/declaration order is never exercised. Declare-before-use inside the component body (move the block below the value it reads). And treat this as the proof that render-only + mocked unit tests cannot replace LOOKING at the app -- the screenshot was the only thing that saw it. Wire a page.on('pageerror') listener into any screenshot harness so a crashed render is reported, not silently shot as a white PNG.
A "surface parity" marker check passes while the feature is entirely missing from the target The marker regex was satisfied by an UNRELATED identifier that happened to share a word -- /suggestion/ matched the caret-completion suggestions array, so the web's rotating random-suggestion + shuffle control was reported present on mobile for months while it did not exist. A later attempt to fix it with /ShuffleIcon/ was ALSO wrong: the import line satisfies it even with the button deleted. A marker must match the wiring, not a name or an import (/onPress=\{useSuggestion\}/). Prove it: delete the feature, watch the gate go RED, restore. A marker that can be satisfied by an unrelated identifier is worse than no marker -- it buys false confidence.
A "supported load" number (1000 prompts, 100k gallery) gets implemented as a hard CAP -- the web silently truncated every roll to 50, mobile clamped at 1000 -- so the app refuses work it advertises, with no message Someone read a performance promise ("this much with no degradation") as permission ("no more than this"). It then got locked in by tests that asserted the cap (expect(len(999)).toBe(50) // capped). A test that asserts a bug is the bug's best defender -- it converts a defect into a specification and makes the fix look like a regression. Documented capacities are a floor for what must stay FAST, never a ceiling on what the user may ask for. Keep validity constraints (>= 1, integer); delete limits. When a test says // capped, ask who decided that -- and check the product intent before believing the assertion.
CI (or a fresh clone) fails to build with [UNRESOLVED_IMPORT] Could not resolve '<old path>' after a file was MOVED -- while every gate passes locally The importer updates exist in the working tree but were never committed: a multi-path git add silently staged only some of the listed files (13 of 17). Tests/build/parity all read the working tree, so they went green over a commit that doesn't compile. A working tree is not evidence. Run npm run check:committed after committing (fails when tracked source differs from HEAD), and for a release build from a clean git worktree at HEAD. Always diff git diff --cached --numstat against the files you actually touched -- never assume a multi-path git add took.
A git commit -m "…" (or any command) run through PowerShell executes text that was only meant to be quoted — here, a commit message describing an injection payload spawned calc PowerShell parses the whole command line: &, ` , `` ``, $(…) inside a double-quoted argument are still operators. The message was about x" & calc & ".png, and PowerShell obligingly ran it.
A backend endpoint runs a shell string built from a request value (exec(\cmd /c start "" "${fp}"`)`) — and the path validator "already sanitizes" it The validator (resolveOutputFile) blocked path traversal and nothing else. A filename may legally contain " and &; in a shell string those end the argument and start a command (CodeQL js/command-line-injection, critical). "Local-only backend" is one --host flag from a LAN. Remove the shell, don't improve the escaping — escaping is a losing game played on the attacker's board. Build {cmd, args} and call execFile; argv goes straight to the OS, so a quote in a filename is a quote in a filename. Keep the command builders pure so the invariant is testable (backend/osCommands.js + tests/regression/commandInjection.test.js). Not cmd /c either — cmd re-parses its own arguments (CVE-2024-27980).
Every SonarCloud scan fails with ERROR Organization key '<new-name>' does not exist. after the GitHub account was renamed A repo-wide rename of the old GitHub login swept up sonar.organization / sonar.projectKey in sonar-project.properties. Those are SonarCloud's own keys, minted when the project was bound; they do not follow a GitHub login change. Restore the Sonar keys to what SonarCloud shows on the project's Information page (here: junebug12851…). A find-and-replace across "current-state files" does not get to redefine a third party's identifiers — exclude external service keys (Sonar, Codecov, Netlify, app IDs) from any rename sweep, and confirm the scan passes before believing it's fixed.
An e2e test "proves" a serious app defect (the app never renders 1000 prompts) — but the app's own instrumentation says it finished long ago The test waited for "1000 generated", and the app accumulates results across rolls, so the label actually read "1220 generated". The assertion could never come true; the 10-minute timeout then dressed a test bug up as an app failure — and I chased a render bug for hours while the device log said committed 1220 result rows, 34 ms after the roll. Reset the app's state at the start of each measured interaction (here: tap Clear all), and assert on something that can only mean what you think it means. And when a test's verdict contradicts the app's own telemetry, suspect the test first: it is the newer, less-exercised code. Read the instrument you built before you distrust the thing it measures.
An expensive CI job (native build + emulator, 30–45 min) runs on every push, so every one-line fix costs 40 minutes and the gate becomes something to route around Slow gates were wired to push like the fast ones. Cost is not the same as value: this one protects the release, not the commit. Two tiers: fast checks on every push; expensive ones if: gated to the release path (PR into main, pushes to main, workflow_dispatch). main is still protected — the release PR must be green — and dev iteration stays fast.
CI is RED on Format check while the local npm test is green (9 files, none of them yours) npm test ran lint but not format:check; CI runs both. So a session can end "green" on a commit CI rejects — which is exactly what happened: dev sat red for an hour on files the previous session wrote and never formatted. A gate you don't run is not a gate — and a local gate that is a SUBSET of the CI gate is a lie about what green means. format:check is now inside npm test. When you add a check to CI, add it to npm test in the same change (and vice-versa); the two must be the same gate.
Prettier says a generated file is unformatted, but formatting it makes its freshness check say STALE Two gates demanding opposite things: format:check wants Prettier's output; check:registry compares the file byte-for-byte against what the generator would emit — and the generator hand-assembled strings that weren't Prettier-clean. Fixing either one breaks the other. Format at the SOURCE: the generator runs its rendered text through Prettier (format(src, {...await resolveConfig(out), parser:"babel"})) before writing and before comparing. Now the file it writes is the file both gates expect. Never hand-format a generated file.
node_modules is suddenly EMPTY (Cannot find package 'vite') after cleaning up a temporary git worktree A junction/symlink was created from the throwaway worktree to the real node_modules to avoid a reinstall; git worktree remove --force then followed the link and deleted the TARGET's contents. Never link node_modules into a worktree you intend to force-remove. Recover with npm install (+ npm --prefix targets/mobile install) -- nothing is lost, it's derived.