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 |
| Add a keybinding, pane, overlay, auth type, body type, hook, importer, CLI flag | recipes.md |
| Write tests for new feature | testing.md |
| Fix a bug or investigate a regression | testing.md → Bug-fix workflow |
| Add/modify persistent state (new files, config, timeline) | 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.
- Reproduce the reported behavior before changing production code.
- When practical, add the smallest focused failing regression test that proves the defect.
- 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.
- Make the smallest localized change that passes the regression test. Do not refactor unrelated code or change behavior outside the reported bug.
- Never delete, skip, weaken, or broadly rewrite tests merely to make them pass.
- Run the focused test first, then the full test suite after the patch.
- 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.tsdeliberately emits safe reasons without raw filesystem causes. Use the surrounding module's error-message style; amodule.function:prefix is not universal. -
Variable syntax:
$VARNAME(no braces), with names matching^\w+$.src/variableReference.tsis 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.tsowns reusable auth defaults,src/lang/auth.tsowns strict shared request and folder parsing/serialization, andsrc/ui/authRows.tsowns field metadata and mutation for both editors. OAuth 1.0a signing lives insrc/requests/oauth1.ts; OAuth 2.0 token acquisition, refresh, vault storage, and loopback browser authorization live inoauth2.tsandoauth2Browser.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()andvalidateFilenameSegment()fromuserPath.ts. Windows release assets use.exe; the PowerShell installer verifies checksums and updates user PATH. The updater returnsrestart_requiredandlog_path, with replacement, version verification, rollback, and skill refresh owned bycomplete-windows-update.ps1after 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/--cookiesformats the existing redacted response result inhumanOutput.ts; escape terminal controls and keep binary bodies metadata-only.services.tsincludes received-cookie metadata with every cookie value masked in JSON and human output. Keep original-byte downloads in the existing--outputflow. -
YAML files:
.ymlextension (not.yaml). Requests stored one-per-file in collection dir. -
Environments: Dotenv-style
.envfiles in<collection>/.environments/. Public, disabled, and secret keys use^\w+$;_coloris reserved metadata. Preserve all value content after the first=, including trailing spaces.# @secret NAMEplus a blank placeholder declares an OS-vault secret;process.env.NAMEtakes precedence over the stored value. -
Draft pattern:
useRequestDraftholdsMap<id, Request>of dirty edits.DraftOpand mutation logic live inrequestDraftReducer.ts. Compare withisDirtyvia 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.tsxowns the sidebar width and separate stacked and side-by-side split ratios.MainView.tsxhandles mouse drag state, responsive minimums, and double-click reset;RequestResponseView.tsxrenders 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.tsdefines bindings gated onfocus,mode,overlay, andview;layers.tsassembles them. UseuseBindings()from@opentui/keymap/react. -
Optional tabs:
useEditBrowseanduseFolderEditBrowseown 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 requestv/ctargets, usesd/f/jonly for visible script tabs, andofor the menu. Keep visibility, focus, and buffered keyboard navigation consistent. -
Edit/Browse FSM: Three modes —
inactive → browsing → editing.useEditBrowsehook manages cursor, commit, cancel. -
JSON and XML request bodies:
RequestBodyTabrenders both through the inlineCodeEditorRenderable; JSON validates substituted content, while XML uses Tree-sitter highlighting and is sent unchanged after substitution with anapplication/xmldefault when no enabled Content-Type exists.editingBodycontrols focus and the editor updates the request draft throughonBodyChange. Escape and Shift+Tab return to the body-type selector; Ctrl+Z undoes and Ctrl+Shift+Z redoes body edits. -
JSON validation:
jsonValidation.tsvalidates 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:
ResponseVisualBodyrenders the JSON/XML tree fromresponseVisual.tswith expandable previews and compact tables. The footer andresponse.body-viewcommand (min 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.tsxowns session-only visibility;commandActions.tshandlesCtrl+Band 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 toCtrl+Alt+B. -
Binary responses and downloads:
responseBody.tsowns MIME/UTF-8 classification, byte sizes, image signatures, and safe filenames. Preserve non-enumerablebodyByteswhen copying a live response; never reconstruct downloads from text. Binary JSON, history, and Runner details contain metadata only.responseFile.tsand 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 incommandActions.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:
ResponsePaneuses a read-onlyCodeEditorRenderablewith source-numbered folds and a themed scrollbar. Folded selections and body copying must return the original source, not the collapsed display text. -
Environment UI:
eopensEnvironmentPickerOverlay;F3opens the full editor.Ctrl+Nin the editor opensNewEnvironmentOverlay, anduseEnvironmentEditor.createEnv()persists the validated name and optional color. -
Collection formatting:
collection format <path>loads and rewrites every request with canonical YAML, pretty-printing valid JSON bodies throughformatJson. 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.tsparses and resolvesstatus,response.time, case-insensitive header, and JSON body expressions.src/assertions.tsevaluates typed operators. Asyncsrc/executionResults.tsorchestrates 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
captureentry is an object with requiredvalue, optionalenabled, and optionalpersist; scalar shorthand is invalid.src/runScope.tsstores typed successful values for one top-level call and exposes an environment overlay tosubstitute(). 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 andrequest runmay persist plaintext or secret values throughpersistResponseCaptures(), 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
scriptsgroup, including child-call summaries and inherited origins. The optionaltestsgroup 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.tslimits 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.tsowns the explicitnoodle.randomcatalog. Each invocation has independent English Faker 10.6.0 state; seeds reset only that sequence, relative dates need an explicit timezone-bearingrefDate, 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.tsownsTIME_METHODSand validated handlers for frozennoodle.timein both phases;preRequestScript.tsincludes 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 JavaScriptDateand random timestamp methods. Cover both phases, invalid-input rollback, timezone offsets, and Date-range boundaries intests/unit/scriptTime.test.ts, with timezone support in the compiled smoke check. See the public time API. -
Body templates:
bodyTemplate.tsreusesscriptRandom.ts,scriptTime.ts, andscriptJson.tsfor$random/$timebody 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. -
External sources:
scriptSourceResolver.tsresolves collection-relative./file.jsdeclarations 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.tsowns one phase-aware QuickJS runner with controlled async jobs insrc/scriptAsync.tsand the thin pre entry point. Noodle APIs live under one frozen, null-prototypenoodleglobal;consoleand JavaScript built-ins remain global, with no bare API aliases.SCRIPT_API_CONTRACTdeclares 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 insrc/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;ScriptConsolerenders 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 and scripting regression guidance. -
Script authoring:
ScriptEditorwraps the sharedCodeEditorRenderable;RequestScriptTab, folder tabs, andCollectionScriptsuse existing draft/save paths. ReuseSCRIPT_API_CONTRACTandscriptApiTypes.tsfor phase-specific declarations; regenerate.agents/skills/noodle-use/noodle-script.d.tswithbun scripts/generate-script-api.tsand verify with--check.scriptDiagnostics.worker.tsowns advisory semantic analysis, completion, and parameter help with bundled QuickJS-compatible libraries. Generate/check those libraries withbun 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.tsformats JSON with source-preserving edits and JavaScript through the diagnostics worker.Ctrl+Alt+Fand globalformat_on_savereuse 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.tsowns 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.tsxrenders the two-pane workspace and shared expandableResponseResults;runnerLayers.tsowns navigation. F5 opens it by default. It composesselectCollectionRunRequests()andcollectionRun()rather than owning alternate request declaration editors or execution semantics. -
Selective collection runs:
collection run <path> [<target>...]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
tagsform dynamic suites. Effective tags are the union across the ancestor chain. Collection targets resolve first, then repeated--tagvalues use AND matching and repeated--exclude-tagvalues use OR matching before environment, proxy, TLS, cookies, or execution. Preserve collection order, RunScope isolation, fixed failure-category ordering, fail-fast skips, and exit codes0success,1completed failure,2configuration failure,130collection cancellation.collectionRun()accepts trailing{ signal, onEvent }options. Top-level selected executions emitrequest-startthenrequest-complete, orrequest-skip, with request ID and optional iteration. Child calls stay in script diagnostics. Interrupted results carrycancelled: true; unstarted skips usecancelled. Summary completed/cancelled/unstarted counts exclude cancellation from ordinary request failures. Preserve partial response phases, per-invocation rollback, first-stop precedence, and cookie/signal cleanup. The lifecycle returnsstatus: "cancelled"; manual Send retains any completed response, otherwise returns to idle, and reports cancellation to history. Timeline entries carrycancelled: true; cancelled attempts do not affect folder success rates or timing averages. Keep overlapping sends owned by their original request and signal, and merge pending history loads with saves without stopping the queue after a failed write. Keep caller cancellation signals distinct from child/script deadlines. Render editable Settings tags, Runner filters, and Runner request-row tags with the sharedBadgetreatment:#prefix,theme.accentontheme.backgroundElementwhen inactive,theme.primarywiththeme.backgroundPaneltext 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 reusesYamlFileEditorfor per-file repair drafts, validation, save, and delete actions. -
Updates:
src/app/commands/update.tsreads the versionedhttps://noodlerest.dev/update.jsonmanifest, caches validated checksums, and verifies the matching binary before replacement.useUpdateFlowchecks updates when the TUI starts, automatically installs standalone updates, and only notifies Homebrew users to runbrew upgrade noodle, whileHeaderandAboutOverlayrender progress. After a successful standalone update,updateInstall.tsrefreshes an existing managednoodle-useskill with the new executable; refresh failure is non-fatal and must surfacenoodle agent installas the retry. Homebrew CLI updates only print the command and returnhomebrew_managed; refresh skills explicitly withnoodle agent installafter a brew upgrade. Keep manifest schema, release workflow publishing, installation docs, and update tests synchronized when changing this flow. -
Agent skill installation:
src/agentSkill.tsembeds the repository'snoodle-usefiles, installs the managed copy under~/.agents/skills/noodle-use, and links detected Claude, Cursor, Codex, and OpenCode skill directories.src/app/commands/agent.tsexposesnoodle agent install [--json] [--force]; the command palette calls the same installer throughcommandActions.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
Cookieheaders; known captured secrets; and assertion metadata before persistence. Response headers and bodies recursively redact the same known values, and sensitive response headers such asSet-Cookieare field-masked. Bodies larger than 10 KB move to gzip sidecars under.timeline/<request-id>.yml.bodies/; YAML entries retain abodyRef. 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.tswrapsBun.secretsfor environment secrets, proxy credentials, and encrypted mTLS key passphrases. Collection-scoped accounts use the generatedcollection_id; persist configuration and secret mutations transactionally so one cannot succeed without the other. -
Cookie jars:
src/cookies/index.tswrapstough-cookiewith one concurrency-safe jar percollection_idunder~/.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-onlyget/set/deletetransaction: 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: falsesuppresses 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: trueand credentials come from the OS vault.src/tls.tsvalidates 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.tsnormalizes issuer URLs;src/requests/oauth2.tsfills only missing endpoints usingdiscovery_url. Absentdiscovery_url_kindmeansissuer;documentrequests the exact URL. Discovery and token subrequests useresolveVariables: falsebecause auth values were already resolved.authRows.tsexposes both discovery fields in request and folder editors. OpenAPI supportsopenIdConnect; Postman requires explicit endpoints for the selected grant. -
System theme:
generateSystemTheme()derives colors from the terminal palette.ThemeProviderrefreshes on palette/theme notifications, cancels stale updates when leaving System, and keeps the savedsystempreference 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 fromschema/index.ts. -
Imported scripts:
src/converters/scriptCompatibility.tsresolves lexical identifiers and checks direct members againstSCRIPT_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 intests/unit/scriptCompatibility.test.tsand destination/placement behavior intests/integration/importScriptWarnings.test.ts. -
Generated script reference:
bun run script:generatederivesnoodle-script.d.tsandreference/script-api.mdfromSCRIPT_API_CONTRACTandSCRIPT_LIMITS;bun run script:checkrejects drift. Keep both embedded throughsrc/agentSkill.tsand checked in binary, CI, and release builds. -
TUI collection transfer: The command palette owns Import Collection and Export Collection.
ImportCollectionOverlaycan create a new collection or write into the current one, which must have no unsaved changes;ExportCollectionOverlaypreviews an OpenAPI or Postman target and picks the next available Postman directory. Both use@/path completion throughuserPath.ts. -
Command actions: Shared command logic lives in
commandActions.ts. Keymap layers andcommands.tsimport 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.tsdetects supported editor executables and macOS applications. Global Behavior stores the selectedexternal_editor; command actions open the collection or application settings directories and validated external JavaScript files. Script opening usescreateScriptSourceResolver(...).resolveFile(...), preserves collection confinement, and never creates or rewrites files. -
CommandItem.run returns boolean: Palette commands return
true(close palette) orfalse(stay open). Unavailable commands (save when not dirty, copy body when no response) returnfalse. -
Commands are contextual by view: Build them in view-specific arrays (
requestCommands,mainEnvCommands,editorEnvCommands,workspaceCommands,systemCommands, etc.) inbuildCommandPaletteCommands. Use arrays for view-level availability; state-dependentrun()guards returnfalsewhen unavailable. -
PickerOverlay isNavigable: Command palette sections are generated by
CommandPaletteOverlay. For other picker items with non-selectable rows, passisNavigableso navigation skips them. -
Completion and notifications:
Autocomplete.tsxowns bounded menus, cursor anchoring, keyboard/mouse selection, and match highlighting. Reuse it fromVarInputandCodeEditorCompletion; the variable hook supplies tokens and acceptance, not menu navigation. Keep transient Toast non-focusable;NotificationOverlayhandles persistent keyboard scrolling through Show Last Notification. -
Modal keyboard isolation:
useModalKeyboardShieldinstalls 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 openSelectmenu) use a priority above the shield. -
getView reads React state, not keymap: In
AppInner.tsx,getView: () => keymap.getData("app.view")is stale during render. UsegetView: () => viewwhereviewis the React state variable. -
Script persistence:
noodle.run.set/unsetaccept 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 andrequest runflush 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.getremains 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: truefor 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.tscycleFocus()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: () => voidinstead ofrun: () => boolean— palette won't close correctly - Adding vanilla
run()with earlyreturninstead ofreturn 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: falseas 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.
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.


