--- name: noodle-dev description: Use when developing features for the noodle terminal REST client — adding panes, keybindings, hooks, overlays, auth types, body types, I/O operations, environment features, importers, CLI flags, or any feature work in this codebase --- # noodle-dev Terminal REST client. OpenTUI (React binding) on Bun. YAML files on disk. **REQUIRED BACKGROUND:** Read AGENTS.md for CLI commands, stack, and conventions. ## Quick routing | Task | Read | | ------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | Understand module boundaries, data flow, state, CLI, collection layout | [architecture.md](architecture.md) | | Add a keybinding, pane, overlay, auth type, body type, hook, importer, CLI flag | [recipes.md](recipes.md) | | Write tests for new feature | [testing.md](testing.md) | | Fix a bug or investigate a regression | [testing.md](testing.md) → [Bug-fix workflow](#bug-fix-workflow) | | Add/modify persistent state (new files, config, timeline) | [architecture.md](architecture.md) → "Collection directory layout" | | Build terminal UI components | **REQUIRED SUB-SKILL:** Use `opentui` skill | ## Bug-fix workflow For bug reports, keep investigation, regression-test creation, implementation, and review as separate stages. Never declare a bug fixed based only on code inspection. 1. Reproduce the reported behavior before changing production code. 2. When practical, add the smallest focused failing regression test that proves the defect. 3. If the user requests investigation or approval first, stop after reporting the reproduction, likely root cause, and proposed minimal fix; do not implement until approved. Otherwise, continue with the authorized fix. 4. Make the smallest localized change that passes the regression test. Do not refactor unrelated code or change behavior outside the reported bug. 5. Never delete, skip, weaken, or broadly rewrite tests merely to make them pass. 6. Run the focused test first, then the full test suite after the patch. 7. Review the final diff for regressions and unintended behavior changes. Report the root cause, changed files, tests changed or added, commands run, user-visible behavior changes, and remaining risks. If an automated regression test is not practical, explain why and provide a reproducible manual acceptance procedure. If the issue cannot be reproduced, do not make speculative production changes; report what was attempted instead. Ask for explicit approval before a compatibility-sensitive change that has not already been authorized: public CLI or API behavior, collection or YAML formats, configuration, keybindings, persistence, or backwards compatibility. ## Key conventions - **Error re-throws:** Preserve `{ cause: e }` except at redaction boundaries where the original error exposes secrets or local paths. `scriptSourceResolver.ts` deliberately emits safe reasons without raw filesystem causes. Use the surrounding module's error-message style; a `module.function:` prefix is not universal. - **Variable syntax:** `$VARNAME` (no braces), with names matching `^\w+$`. `src/variableReference.ts` is the shared scanner/replacer; `$$` emits a literal dollar and values resolve once. Applied in url/headers/params/body/formData/filePath/auth and assertion expected string values. Disabled fields remain source-identical. Automation overlays committed capture and script RunScope values through the same substitution path. - **Authentication:** `src/auth/defaults.ts` owns reusable auth defaults, `src/lang/auth.ts` owns strict shared request and folder parsing/serialization, and `src/ui/authRows.ts` owns field metadata and mutation for both editors. OAuth 1.0a signing lives in `src/requests/oauth1.ts`; OAuth 2.0 token acquisition, refresh, vault storage, and loopback browser authorization live in `oauth2.ts` and `oauth2Browser.ts`. Keep generated OAuth state out of YAML, re-sign or reapply auth per redirect leg, and strip credentials on origin changes. - **Windows x64 beta:** Keep logical IDs and diagnostic paths in forward-slash form, use native path operations at filesystem boundaries, and reuse `getNoodleConfigDir()` and `validateFilenameSegment()` from `userPath.ts`. Windows release assets use `.exe`; the PowerShell installer verifies checksums and updates user PATH. The updater returns `restart_required` and `log_path`, with replacement, version verification, rollback, and skill refresh owned by `complete-windows-update.ps1` after the parent exits. Windows skill links use absolute directory junctions; clipboard text uses UTF-16LE PowerShell input. Batch editor launches reject unsafe metacharacters before quoting. Windows ARM64 release binaries are unsupported. - **CLI response output:** `request run --body/--headers/--cookies` formats the existing redacted response result in `humanOutput.ts`; escape terminal controls and keep binary bodies metadata-only. `services.ts` includes received-cookie metadata with every cookie value masked in JSON and human output. Keep original-byte downloads in the existing `--output` flow. - **YAML files:** `.yml` extension (not `.yaml`). Requests stored one-per-file in collection dir. - **Environments:** Dotenv-style `.env` files in `/.environments/`. Public, disabled, and secret keys use `^\w+$`; `_color` is reserved metadata. Preserve all value content after the first `=`, including trailing spaces. `# @secret NAME` plus a blank placeholder declares an OS-vault secret; `process.env.NAME` takes precedence over the stored value. - **Draft pattern:** `useRequestDraft` holds `Map` of dirty edits. `DraftOp` and mutation logic live in `requestDraftReducer.ts`. Compare with `isDirty` via deep equality. - **Focus model:** `"sidebar" → "urlbar" → "request" → "response"` (main). URL bar has method and URL sub-focuses. `"env-sidebar" → "env-header" → "env-vars"` (env editor). `"cookie-sidebar" → "cookie-list"` (cookie jar). `"runner-options" → "runner-requests"` (collection Runner). Skips hidden panes. - **Resizable main layout:** `AppInner.tsx` owns the sidebar width and separate stacked and side-by-side split ratios. `MainView.tsx` handles mouse drag state, responsive minimums, and double-click reset; `RequestResponseView.tsx` renders the layout-specific handles. Keep sizing session-only unless persistence is explicitly requested, and stop resize events from changing pane focus. - **Keymap layers:** `src/ui/keymap/*Layers.ts` defines bindings gated on `focus`, `mode`, `overlay`, and `view`; `layers.ts` assembles them. Use `useBindings()` from `@opentui/keymap/react`. - **Optional tabs:** `useEditBrowse` and `useFolderEditBrowse` own session reveal state and menu focus. Empty request Assert/Capture/Pre Script/Post Script/Tests and folder script/test tabs stay behind the `+` Select; populated tabs remain visible. Navigation skips hidden tabs. Jump mode reveals request `v`/`c` targets, uses `d`/`f`/`j` only for visible script tabs, and `o` for the menu. Keep visibility, focus, and buffered keyboard navigation consistent. - **Edit/Browse FSM:** Three modes — `inactive → browsing → editing`. `useEditBrowse` hook manages cursor, commit, cancel. - **JSON and XML request bodies:** `RequestBodyTab` renders both through the inline `CodeEditorRenderable`; JSON validates substituted content, while XML uses Tree-sitter highlighting and is sent unchanged after substitution with an `application/xml` default when no enabled Content-Type exists. `editingBody` controls focus and the editor updates the request draft through `onBodyChange`. Escape and Shift+Tab return to the body-type selector; Ctrl+Z undoes and Ctrl+Shift+Z redoes body edits. - **JSON validation:** `jsonValidation.ts` validates the substituted payload but maps parse failures back to the request source, including the variable name and source line/column when a substituted value is invalid. - **Visual response bodies:** `ResponseVisualBody` renders the JSON/XML tree from `responseVisual.ts` with expandable previews and compact tables. The footer and `response.body-view` command (`m` in the focused Body tab) toggle Source/Visual; `/` performs literal, case-insensitive Visual search or Source JSONPath. Keep Visual copy bound to the original body, preserve numeric JSON tokens, reject XML DTDs, and retain the 5 MiB opt-in guard. Search and expansion state belong to the current response. Size scroll content to the viewport and use intrinsic row width only as its minimum so pane resizing does not introduce transient scrollbars. - **Sidebar visibility:** `AppInner.tsx` owns session-only visibility; `commandActions.ts` handles `Ctrl+B` and the palette action. Toggling preserves non-sidebar focus; hiding the focused sidebar falls back to the URL bar or folder pane. Focus cycling skips a hidden sidebar, and the sidebar jump reopens and focuses it. Response and cookie copying default to `Ctrl+Alt+B`. - **Binary responses and downloads:** `responseBody.ts` owns MIME/UTF-8 classification, byte sizes, image signatures, and safe filenames. Preserve non-enumerable `bodyBytes` when copying a live response; never reconstruct downloads from text. Binary JSON, history, and Runner details contain metadata only. `responseFile.ts` and the native addon own pinned-directory exclusive saves; maintain all eight prebuilds with their manifest. TUI Save As pins the response and destination, retries filename collisions without overwriting, and records saved paths by response identity. Keep Save/Open actions in `commandActions.ts`; Ctrl+Alt+S/O apply to the focused live binary Body tab. PNG/JPEG/static WebP/GIF first-frame previews retain the 5 MiB opt-in guard, decoder fallback, and unmount cleanup. - **Response bodies:** `ResponsePane` uses a read-only `CodeEditorRenderable` with source-numbered folds and a themed scrollbar. Folded selections and body copying must return the original source, not the collapsed display text. - **Environment UI:** `e` opens `EnvironmentPickerOverlay`; `F3` opens the full editor. `Ctrl+N` in the editor opens `NewEnvironmentOverlay`, and `useEnvironmentEditor.createEnv()` persists the validated name and optional color. - **Collection formatting:** `collection format ` loads and rewrites every request with canonical YAML, pretty-printing valid JSON bodies through `formatJson`. Invalid JSON remains unchanged, and valid numeric literals must retain their original precision. Imports run this formatter after writing the collection. - **Response assertions:** `src/response.ts` parses and resolves `status`, `response.time`, case-insensitive header, and JSON body expressions. `src/assertions.ts` evaluates typed operators. Async `src/executionResults.ts` orchestrates capture commits, one post callback, then assertions through one response resolver for manual TUI sends and automation. Assertions still run after post failure, with expectations from the original substitution pass. Outward result redaction waits for post-discovered secrets. Disabled assertions remain validated and editable but produce no results. Structured and persisted results recursively redact known secrets from expected and actual values; live response views and arbitrary server data remain visible. - **Response captures and RunScope:** Every request `capture` entry is an object with required `value`, optional `enabled`, and optional `persist`; scalar shorthand is invalid. `src/runScope.ts` stores typed successful values for one top-level call and exposes an environment overlay to `substitute()`. Captures commit before post and assertions and are visible to the same request's post script and later requests. Failed captures do not replace prior values, and disabled captures produce no mutation or result. Values captured from sensitive response headers become secret automatically. Manual sends and `request run` may persist plaintext or secret values through `persistResponseCaptures()`, using the captured value even after post failure/overwrites; collection runs and the TUI Runner ignore persistence. Capture declarations persist in request YAML but are excluded from timeline snapshots; capture results and RunScope values never enter timeline history. - **Timeline script diagnostics:** Manual history stores bounded, redacted pre/post results, logs, errors, and persistence outcomes in the entry-level `scripts` group, including child-call summaries and inherited origins. The optional `tests` group retains ordered test results, logs, and all block errors with legacy first-error compatibility. Script source, capture results, and RunScope values remain excluded. `buildTimelineEntry()` performs redaction; `filestore/timeline.ts` limits individual diagnostic text and each serialized log array to 10,000 bytes with `[TRUNCATED]` markers. Automation does not write timeline entries. - **Script test data:** `scriptRandom.ts` owns the explicit `noodle.random` catalog. Each invocation has independent English Faker 10.6.0 state; seeds reset only that sequence, relative dates need an explicit timezone-bearing `refDate`, and current timestamps remain clock-based. Passwords become known secrets immediately, including on failure; generated IDs/passwords provide no security or uniqueness guarantee. See the linked public schema for bounded options. - **Script time helpers:** `scriptTime.ts` owns `TIME_METHODS` and validated handlers for frozen `noodle.time` in both phases; `preRequestScript.ts` includes them in the shared API contract and bridge. Keep strict ISO inputs, UTC defaults, named-zone formatting, and elapsed-duration arithmetic (24-hour days). Preserve JavaScript `Date` and random timestamp methods. Cover both phases, invalid-input rollback, timezone offsets, and Date-range boundaries in `tests/unit/scriptTime.test.ts`, with timezone support in the compiled smoke check. See [the public time API](../noodle-use/schema.md#script-time-api). - **Body templates:** `bodyTemplate.ts` reuses `scriptRandom.ts`, `scriptTime.ts`, and `scriptJson.ts` for `$random`/`$time` body and text-form values. Preserve single-pass substitution before pre scripts, typed standalone JSON values, escaped string insertions, one clock instant per request, immediate password redaction, and literal direct script requests. Preview/validation must never generate data. See [body templates](../noodle-use/schema.md#random-body-templates). - **External sources:** `scriptSourceResolver.ts` resolves collection-relative `./file.js` declarations before execution and preflights selected collections before HTTP. Preserve confined realpaths, regular-file and UTF-8 checks, the 256 KiB cap, per-run read deduplication, fresh reads on later sends, safe errors without absolute paths, and inline/external diagnostic origins. The VM receives only source text. - **Scripting:** `src/preRequestScript.ts` owns one phase-aware QuickJS runner with controlled async jobs in `src/scriptAsync.ts` and the thin pre entry point. Noodle APIs live under one frozen, null-prototype `noodle` global; `console` and JavaScript built-ins remain global, with no bare API aliases. `SCRIPT_API_CONTRACT` declares the public namespace and nested paths while bridge operation names remain internal. Preserve folder merge → environment/RunScope → one substitution pass → pre → HTTP → captures → post → assertions → tests in `src/requestLifecycle.ts`, never a parallel execution path. Pre stages request/RunScope writes and skips HTTP on failure. Post reads the final transport-prepared request after signing and cookie/header preparation, centrally rejects every request mutation, and adds response metadata/headers plus lazy text and VM-cached JSON. Response text alone permits 5 MiB UTF-8; ordinary bridge/RunScope values retain 256 KiB/depth-32 limits. Post runs once per completed response, including HTTP/capture failures; failure rolls back only its staged RunScope/cookie writes, preserves earlier state/logs, and still reaches assertions. Successful post writes remain transient and reach later collection requests after assertion failure. Reuse existing phase-tagged result groups and redaction. Results owns outcomes and origins; `ScriptConsole` renders the same bounded logs with phase, level, and request-relative timing in live, Runner, and timeline views, without a separate log store. Read [the public API](../noodle-use/schema.md#inline-request-scripts) and [scripting regression guidance](testing.md#inline-scripting-regressions). - **Script authoring:** `ScriptEditor` wraps the shared `CodeEditorRenderable`; `RequestScriptTab`, folder tabs, and `CollectionScripts` use existing draft/save paths. Reuse `SCRIPT_API_CONTRACT` and `scriptApiTypes.ts` for phase-specific declarations; regenerate `.agents/skills/noodle-use/noodle-script.d.ts` with `bun scripts/generate-script-api.ts` and verify with `--check`. `scriptDiagnostics.worker.ts` owns advisory semantic analysis, completion, and parameter help with bundled QuickJS-compatible libraries. Generate/check those libraries with `bun scripts/build-script-type-libraries.ts [--check]` and include the worker entry in standalone builds. Syntax checks compile without execution; only TUI Send/Runner opt into source-resolver syntax preflight. Preserve shared lifecycle execution and CLI semantics. - **Code formatting:** `codeFormatting.ts` formats JSON with source-preserving edits and JavaScript through the diagnostics worker. `Ctrl+Alt+F` and global `format_on_save` reuse these paths; the setting defaults off. Skip external sources, XML, and invalid code, and never expand templates. Existing save owners persist prepared fields before applying editor edits, guard against newer drafts, and retain retryable changes on failure. A formatting-service failure warns and saves unformatted source. Collection drafts stay bound to their original collection across unmounts and delayed saves. - **Collection Runner:** `useCollectionRunner.ts` owns transient request/folder selection, environment, include/exclude tags, fail-fast, delay, and validated CSV/JSON data-file state, iteration progress, and in-memory results. `CollectionRunnerView.tsx` renders the two-pane workspace and shared expandable `ResponseResults`; `runnerLayers.ts` owns navigation. F5 opens it by default. It composes `selectCollectionRunRequests()` and `collectionRun()` rather than owning alternate request declaration editors or execution semantics. - **Selective collection runs:** `collection run [...]` accepts request IDs and folder paths ending in `/`. Resolve and validate all targets before sending, include nested folder requests, deduplicate overlaps, and retain collection order. - **Collection suites:** Optional request and non-root folder `tags` form dynamic suites. Effective tags are the union across the ancestor chain. Collection targets resolve first, then repeated `--tag` values use AND matching and repeated `--exclude-tag` values use OR matching before environment, proxy, TLS, cookies, or execution. Preserve collection order, RunScope isolation, fixed failure-category ordering, fail-fast skips, and exit codes `0` success, `1` completed failure, `2` configuration failure. Render editable Settings tags, Runner filters, and Runner request-row tags with the shared `Badge` treatment: `#` prefix, `theme.accent` on `theme.backgroundElement` when inactive, `theme.primary` with `theme.backgroundPanel` text when selected, and muted text for the add-tag control. Include badge padding and gaps in clipping and width calculations. - **File I/O:** Save/delete operations validate path IDs to prevent traversal. Environment replacement and settings saves are atomic via temporary files plus `rename()`; new, cloned, and renamed environments use atomic exclusive creation. Request and folder writes are direct writes. - **Collection modes:** TUI opens collection roots in editable collection mode, request-containing uninitialized directories in read-only browse mode, and empty directories in read-only empty mode. Initialize through the command palette before editing or sending. Invalid request or folder YAML opens `CollectionErrorView`, which reuses `YamlFileEditor` for per-file repair drafts, validation, save, and delete actions. - **Updates:** `src/app/commands/update.ts` reads the versioned `https://noodlerest.dev/update.json` manifest, caches validated checksums, and verifies the matching binary before replacement. `useUpdateFlow` checks updates when the TUI starts, automatically installs standalone updates, and only notifies Homebrew users to run `brew upgrade noodle`, while `Header` and `AboutOverlay` render progress. After a successful standalone update, `updateInstall.ts` refreshes an existing managed `noodle-use` skill with the new executable; refresh failure is non-fatal and must surface `noodle agent install` as the retry. Homebrew CLI updates only print the command and return `homebrew_managed`; refresh skills explicitly with `noodle agent install` after a brew upgrade. Keep manifest schema, release workflow publishing, installation docs, and update tests synchronized when changing this flow. - **Agent skill installation:** `src/agentSkill.ts` embeds the repository's `noodle-use` files, installs the managed copy under `~/.agents/skills/noodle-use`, and links detected Claude, Cursor, Codex, and OpenCode skill directories. `src/app/commands/agent.ts` exposes `noodle agent install [--json] [--force]`; the command palette calls the same installer through `commandActions.ts`. Preserve unmanaged targets by default and report every conflict before modifying anything. Force replacement must retain backups until all targets succeed and roll completed replacements back without overwriting a target that changed during the operation. - **Timeline storage and security:** Timeline request snapshots redact declared environment, proxy, and TLS secrets; substituted and literal credentials; jar-sent `Cookie` headers; known captured secrets; and assertion metadata before persistence. Response headers and bodies recursively redact the same known values, and sensitive response headers such as `Set-Cookie` are field-masked. Bodies larger than 10 KB move to gzip sidecars under `.timeline/.yml.bodies/`; YAML entries retain a `bodyRef`. Redact before compression; marking or updating a secret does not rewrite existing entries or sidecars. Preserve response structure while redacting values. Treat entries and sidecars as sensitive because public variables and unknown server data remain visible. - **Settings secrets:** `src/secrets/index.ts` wraps `Bun.secrets` for environment secrets, proxy credentials, and encrypted mTLS key passphrases. Collection-scoped accounts use the generated `collection_id`; persist configuration and secret mutations transactionally so one cannot succeed without the other. - **Cookie jars:** `src/cookies/index.ts` wraps `tough-cookie` with one concurrency-safe jar per `collection_id` under `~/.config/noodle/cookies/`. Use vault-backed encryption on Windows; plaintext fallback is macOS/Linux-only and must report its warning. Coordinate shared key initialization, preserve malformed stored keys, never replace unreadable state automatically, and back it up before an explicit reset. Post gets only a final-URL-scoped host-only `get/set/delete` transaction: revalidate its entire batch against the current jar before synchronous commit with RunScope writes and append successful operations to the existing journal. Keep tough-cookie validation/secure rules and deferred durability. Register applicable, received, read, staged, overwritten, and deleted values for redaction even on failure, including short values; do not change ordinary non-script cookie policy. `sendCookies: false` suppresses sending jar cookies and the post capability, not response Set-Cookie processing; received cookies survive post rollback. - **Network security:** Custom proxy URLs reject credentials and variables; authentication metadata is `auth: true` and credentials come from the OS vault. `src/tls.ts` validates collection TLS, resolves CA/client-certificate files, and matches profiles by exact host and effective port. Redirects reject HTTPS-to-HTTP downgrades. When the origin changes, disable request auth, strip sensitive headers plus headers containing known secrets, and refuse a redirect that would preserve a known secret in the request body; redirects that discard the body may continue. Normalize request-timeout aborts as transport failures while propagating caller-owned cancellation. - **OAuth discovery:** `src/auth/oauth2.ts` normalizes issuer URLs; `src/requests/oauth2.ts` fills only missing endpoints using `discovery_url`. Absent `discovery_url_kind` means `issuer`; `document` requests the exact URL. Discovery and token subrequests use `resolveVariables: false` because auth values were already resolved. `authRows.ts` exposes both discovery fields in request and folder editors. OpenAPI supports `openIdConnect`; Postman requires explicit endpoints for the selected grant. - **System theme:** `generateSystemTheme()` derives colors from the terminal palette. `ThemeProvider` refreshes on palette/theme notifications, cancels stale updates when leaving System, and keeps the saved `system` preference while falling back to Noodle colors if detection fails. - **OAuth security:** OAuth 1.0a PLAINTEXT is limited to HTTPS or loopback HTTP, body placement requires URL-encoded form data, and body hashes reject multipart. OAuth 2.0 endpoints require HTTPS except on loopback, browser grants use an HTTP loopback callback, tokens prefer OS-vault storage with session-only memory fallback, and non-interactive sends never open a browser. Generated code is unavailable for OAuth requests because signatures and tokens depend on request-specific secure state. - **Imports and exports:** Module singletons (`filestore`, `lang`, `env`, `executor`). `runImport()` lazily registers the OpenAPI 3.0, Swagger 2.0, Postman, and Insomnia importers and awaits their results; `runExport()` writes OpenAPI 3.0.3 or Postman Collection v2.1 output. XML bodies preserve literal examples and explicit MIME types across supported formats. Types come from `schema/index.ts`. - **Imported scripts:** `src/converters/scriptCompatibility.ts` resolves lexical identifiers and checks direct members against `SCRIPT_API_CONTRACT`, then uses compile-only QuickJS syntax validation. Never execute imported source or translate foreign APIs. Postman pre events map to collection/folder/request pre and only request test events map to tests; Insomnia preserves request pre/post hooks only. Keep conservative rejection of async source, API aliases, indirect access, unsupported placements, and duplicate/disabled/external Postman events. `runImport()` rejects every import retaining scripts or tests at current or existing targets before writes. Ordered warnings contain safe locations, unsupported global names, and reasons without source text or literal values; TUI notifications precede new-collection open confirmation. Cover compatibility in `tests/unit/scriptCompatibility.test.ts` and destination/placement behavior in `tests/integration/importScriptWarnings.test.ts`. - **Generated script reference:** `bun run script:generate` derives `noodle-script.d.ts` and `reference/script-api.md` from `SCRIPT_API_CONTRACT` and `SCRIPT_LIMITS`; `bun run script:check` rejects drift. Keep both embedded through `src/agentSkill.ts` and checked in binary, CI, and release builds. - **TUI collection transfer:** The command palette owns **Import Collection** and **Export Collection**. `ImportCollectionOverlay` can create a new collection or write into the current one, which must have no unsaved changes; `ExportCollectionOverlay` previews an OpenAPI or Postman target and picks the next available Postman directory. Both use `@/` path completion through `userPath.ts`. - **Command actions:** Shared command logic lives in `commandActions.ts`. Keymap layers and `commands.ts` import from it. If you add a new action, add it there and call from both paths. Do not duplicate logic. - **Collection identity:** `ensureCollectionId()` acquires the existing file lock against the canonical collection directory before reserving and saving a new ID, then re-reads settings under that lock. Preserve this coordination and the shared cookie-key lock across processes. - **External editors:** `externalEditor.ts` detects supported editor executables and macOS applications. Global Behavior stores the selected `external_editor`; command actions open the collection or application settings directories and validated external JavaScript files. Script opening uses `createScriptSourceResolver(...).resolveFile(...)`, preserves collection confinement, and never creates or rewrites files. - **CommandItem.run returns boolean:** Palette commands return `true` (close palette) or `false` (stay open). Unavailable commands (save when not dirty, copy body when no response) return `false`. - **Commands are contextual by view:** Build them in view-specific arrays (`requestCommands`, `mainEnvCommands`, `editorEnvCommands`, `workspaceCommands`, `systemCommands`, etc.) in `buildCommandPaletteCommands`. Use arrays for view-level availability; state-dependent `run()` guards return `false` when unavailable. - **PickerOverlay isNavigable:** Command palette sections are generated by `CommandPaletteOverlay`. For other picker items with non-selectable rows, pass `isNavigable` so navigation skips them. - **Completion and notifications:** `Autocomplete.tsx` owns bounded menus, cursor anchoring, keyboard/mouse selection, and match highlighting. Reuse it from `VarInput` and `CodeEditorCompletion`; the variable hook supplies tokens and acceptance, not menu navigation. Keep transient Toast non-focusable; `NotificationOverlay` handles persistent keyboard scrolling through **Show Last Notification**. - **Modal keyboard isolation:** `useModalKeyboardShield` installs a hard-blocking interceptor only for explicitly non-editable overlays. Editable and unknown overlays leave events available to the focused input; unknown names warn and remain input-safe. Modal-owned controls that must receive keys first (for example, an open `Select` menu) use a priority above the shield. - **getView reads React state, not keymap:** In `AppInner.tsx`, `getView: () => keymap.getData("app.view")` is stale during render. Use `getView: () => view` where `view` is the React state variable. - **Script persistence:** `noodle.run.set/unset` accept optional `{ persist: "environment" | "secret" }`. The VM stages bounded immutable intents, commits runtime state only on success, and delegates async storage to the shared lifecycle. Manual sends and `request run` flush pre before HTTP, then original capture snapshots before post intents; runners report transient suppression. Storage failures preserve runtime/request/cookie commits while failing overall script diagnostics. Persistent unset suppresses baseline substitution until a later set/capture; plain unset retains old behavior. `noodle.env.get` remains a snapshot. Reuse the environment batch/vault transaction helpers, retain historical secret redaction, and never expose raw intents or values in public results. ## Common pitfalls - Forgetting `{ cause: e }` on ordinary re-throws, or retaining unsafe causes at redaction boundaries - Using `{{var}}` instead of `$var` — noodle uses `$` prefix, not mustache - Skipping `validatePathId()` in new file operations — security risk - Adding keybindings without `fixed: true` for navigation keys (Tab, Enter, Escape, arrows) — users could break navigation - Not registering a new keymap layer in `layers.ts` — binding won't fire - Forgetting to update `focus.ts` `cycleFocus()` when adding new panes — tab cycling breaks - Adding command logic inline instead of in `commandActions.ts` — will drift from keymap layer and vice versa - Using `run: () => void` instead of `run: () => boolean` — palette won't close correctly - Adding vanilla `run()` with early `return` instead of `return false` — palette closes on unavailable commands - Handling modal keys only with `useKeyboard` — events can leak to obscured panes; consume them through a keymap interceptor instead - Treating `sendCookies: false` as a capture toggle; it suppresses outgoing jar cookies and post cookie access, not response Set-Cookie processing - Adding post handling in the TUI or CLI instead of the shared lifecycle, or running it for intermediate/failed transport legs ## Async scripting implementation Saved `noodle.runRequest` calls recurse through `src/requestLifecycle.ts`; direct `noodle.sendRequest` calls reuse the transport with literal inputs. Preserve RunScope fork/merge rollback, shared call/depth budgets, ancestor deadlines, cached-only nested OAuth, secret registration after failure, and parent staged cookie semantics. Tests await callbacks but reject network APIs at the host boundary. Child summaries use the shared Results/CLI/history paths and never retain child bodies. See [the public contract](../noodle-use/schema.md#async-scripts-and-request-chaining). ## Inheritance, schemas, and iteration data `scriptInheritance.ts` builds collection/folder/request blocks for the shared lifecycle. Pre, post, and tests run collection to outermost folder to nearest folder to request. The most specific successful post write wins, including persisted writes. Root `folder.yml` is ignored. Keep separate invocations, per-block rollback, ordered persistence, source origins, and legacy test error compatibility. `scriptSchemaValidator.ts` is bundled for QuickJS with `bun scripts/build-schema-validator.ts`; check the committed bundle with `--check`. Never compile or validate user schemas on the host. `iterationData.ts` validates whole files before execution; collectionRun owns row scopes and transient cookie snapshots. Runner uses iteration/request keys for rows and detail lookup. Maintain the existing no-data output and execution semantics. See ../noodle-use/schema.md for the public contract.