* refactor(structure): dissolve account/ into common/ and ui/ session.ts is split by import group so each file has one runtime context: - common/account/session.ts: the isomorphic store accessors + getPrivateKey (imports only insomnia-data + ~/runtimes), used by both main (sentry, cloud-sync) and renderer. - ui/account/session.ts: the window/insomnia-api auth flow (absorbKey, logout, credential cleanup, migrateFromLocalStorage); re-exports the common core so renderer callers keep one import surface. crypt.ts -> common/account/ (used by ipc + both crypto adapters); generateAES256Key now uses globalThis.crypto instead of window.crypto so the module satisfies the common/ no-DOM-globals rule. * refactor(structure): dissolve utils/ into ui/, common/, main/ Placed each former utils/ module by its actual importer context: - ui/utils/: router, try-interpolate, grpc, string-check, prettify/, xpath/ (renderer-only). The index.ts barrel merged into the existing ui/utils.ts. - common/utils/: environment-utils, graph-ql, plugin-name, invariant, utf8-bytes, vault, url/ (imported by both renderer and main/node side). - main/utils/: sealedbox (main-only). prettify tests now load fixtures via import.meta.glob instead of node:fs so they are legal in the renderer execution context. * refactor(structure): split plugins/ into ui/, common/, and plugin host - ui/plugins/: renderer-bridge, create, misc (renderer-only; window, no node) - common/plugins/: types, bridge-types (pure shared types, used by main + renderer + the plugin host) - plugins/ retained as the plugin-host residual: index, invoke-method, context/, themes. These run in the node-enabled plugin window and are intentionally dual-context (index.ts forks on __IS_RENDERER__ between window.main and electron.shell), so they belong to neither ui/ nor main/ nor common/. * refactor(structure): split templating/ into ui/, common/, and host residual - common/templating/: constants, types, render-error, render-context-serialization, tokenize-args, faker-functions, local-template-tags, liquid-engine, liquid-extension-worker, utils, mask-or-decrypt-vault-data, third_party (pure/isomorphic, imported by both sides). types.ts now declares a local BinaryToTextEncoding alias instead of importing node:crypto, so it is legal in common/. - ui/templating/: renderer-safe, worker (renderer/web-worker only). - templating/ retained as host residual: index + liquid-extension, which use node:crypto/os AND window.main (dual-context, like the plugin host). Updated the one cross-package importer (insomnia-scripting-environment) to the new common/ path. * style: re-sort imports after folder reorg (eslint --fix) * docs: runtime-context folder reorganization rationale * chore: treat vendored yarn-standalone bundle as a generated artifact The webpack-bundled bin/yarn-standalone.js is a vendored build artifact, not hand-edited source. An accidental reformat produced a 260k-line diff. Mark it generated/no-diff in .gitattributes (keeps git diffs fast, collapses on GitHub), make the prettier ignore explicit, and deny Claude read/edit access. ESLint already ignores it via the existing **/bin/* rule. * fix: drop unused try-interpolate import after rebase onto develop The rebase merge kept a direct tryToInterpolateRequestOrShowRenderErrorModal import in request-url-bar and websocket action-bar, but develop refactored both to call renderRealtimeConnectPayload instead, leaving the import unused (TS6133).
12 KiB
Runtime-Context Folder Reorganization (usage-driven)
Single source of truth for dissolving the ambiguous top-level feature folders under
packages/insomnia/src(account/,templating/,plugins/,utils/,scripting/) into the runtime-context folders the ESLint config already enforces. Placement is decided by who actually imports each file, not by what the file is named or "feels" like.
Placement rule
For each file, look at the execution context of its importers (transitively, via
the build entry points: entry.main = main; entry.client/root/*.worker/
entry.hidden-window/entry.preload/entry.plugin-window = renderer):
- Uses
getRuntime()→ placed by the same usage rule as everything else. getRuntime is isomorphic (no Node built-in, no DOM global), so such files are eligible forcommon/when used by both sides,ui//main/when used by one. (They are never forced to stay put; the earlier "leave in place" idea is superseded — placing by usage leaves no residual feature folders.) - Imported only by renderer-side code →
ui/. - Imported only by main-side code →
main/. - Imported by both →
common/.common/is the existing isomorphic bucket — the ESLint config already forbids DOM globals and Node built-ins there, so only genuinely shared, platform-neutral code qualifies. - Imported only within its own dissolving folder / via a barrel → follows its consumer. Resolve once the consumer's destination is fixed.
common/ must therefore stay small: it is only for code proven to be imported
from both sides. A file used by just one side never goes to common/, even if
it looks isomorphic.
Why this works with zero new ESLint config: the flat config in
eslint.config.mjs already pins context by folder —
ui/,routes/,basic-components/ and *.renderer.* forbid Node built-ins;
common/ forbids Node built-ins and window/document; main/ and
*.node.ts forbid DOM globals; *.worker.ts forbids both. The top-level feature
folders are simply uncovered today. Moving each file into the right folder is
what makes the existing rules apply — lint becomes the correctness oracle.
Evidence
Importer contexts were collected with a folder-qualified token search
(<folder>/<name> matches every alias and relative import form, avoiding
basename collisions). Buckets: RNDR (renderer), MAIN, NET (network/),
RT (runtimes/), COMMON, SYNC, SELF (same dissolving folder).
getRuntime files are marked ☆ and stay put.
Placement by folder
account/ → dissolves entirely (session split by import group)
session.ts is split by its three distinct import signatures, so each output
file has exactly one runtime context:
| Import group | Functions | → File | Context |
|---|---|---|---|
insomnia-data only |
SessionData, getUserSession, getCurrentSessionId, getAccountId, isLoggedIn, setSessionData, setVaultSessionData, unsetSessionData |
common/account/session.ts |
isomorphic (used by both: sentry/vcs main + routes/ui) |
insomnia-data + ~/runtimes |
getPrivateKey |
common/account/session.ts (same file — getRuntime is isomorphic) |
isomorphic (vcs main + invite-modal renderer) |
insomnia-api + window + ~/common/{constants,database} |
absorbKey, logout, _removeAllCredentials, _removeGitRepository, migrateFromLocalStorage |
ui/account/session.ts (re-exports the common core) |
renderer-only by usage |
Groups 1 and 2 share a file because
~/runtimesis isomorphic andcommon/permits it; if you'd rather isolate thegetRuntimecall, peelgetPrivateKeyintocommon/account/keys.ts. Group 3's functions all touchwindow/window.main(or are private helpers only those touch), and every caller is renderer-side, so no IPC plumbing is needed.
| File | Importers (evidence) | Destination | Confidence |
|---|---|---|---|
crypt.ts |
MAIN (ipc) + RT (node & renderer crypto adapters) | common/account/ |
high — used by both; window.crypto→globalThis.crypto to satisfy common/ |
Main callers (sentry → isLoggedIn; cloud-sync/vcs → getUserSession,
getPrivateKey) import the common file; renderer callers import the ui file
(which re-exports the common core, so they keep one import surface).
utils/ → residual keeps vault.ts
| File | Importers | Destination | Confidence |
|---|---|---|---|
☆ vault.ts |
RNDR (ui, routes) + MAIN-side (runtimes/crypto/crypto-adapter.node) + mask-or-decrypt |
common/utils/ |
high — used by both; getRuntime ok in common |
router.ts |
RNDR ×148 | ui/utils/ |
high |
try-interpolate.ts |
RNDR ×6 (imports ui/components/modals) |
ui/utils/ |
high |
index.ts (barrel) |
~/utils (DOM: HTMLElement, react-stately) |
ui/utils/ |
high |
grpc.ts |
RNDR | ui/utils/ |
high |
string-check.ts |
RNDR | ui/utils/ |
med (1 importer) |
sealedbox.ts |
MAIN | main/utils/ |
high |
environment-utils.ts |
RNDR + NET + COMMON + KON | common/utils/ |
high |
graph-ql.ts |
RNDR + MAIN + NET | common/utils/ |
high |
plugin-name.ts |
RNDR + MAIN | common/utils/ |
high |
invariant.ts |
RNDR + MAIN + COMMON + SYNC + SCRIPT (re-export of insomnia-data/common) |
common/utils/ |
high |
utf8-bytes.ts |
RNDR + NET | common/utils/ |
high |
plugins/ (no getRuntime files)
| File | Importers | Destination | Confidence |
|---|---|---|---|
index.ts (node: fs, path, require) |
MAIN (barrel) | main/plugins/ |
high |
invoke-method.ts |
RNDR (plugin-window, preload); pulls context/* |
main/plugins/ |
verify — runs in the node-enabled plugin host; confirm vs ui |
context/ (node:stream, fs) |
index, invoke-method | main/plugins/ |
high |
renderer-bridge.ts |
RNDR ×16 | ui/plugins/ |
high |
create.ts |
RNDR (window.main) |
ui/plugins/ |
high |
misc.ts |
RNDR (DOM <style>) |
ui/plugins/ |
high |
types.ts |
RNDR + MAIN | common/plugins/ |
high |
bridge-types.ts |
RNDR + MAIN | common/plugins/ |
high |
themes.ts |
index (main) + theme hooks (renderer) | common/plugins/ |
verify consumer split |
templating/ → residual keeps mask-or-decrypt-vault-data.ts
| File | Importers | Destination | Confidence |
|---|---|---|---|
☆ mask-or-decrypt-vault-data.ts |
COMMON (render) + NET (network) | common/templating/ |
high — used by both; getRuntime ok in common |
liquid-extension.ts (node:crypto, node:os) |
internal (engine) | main/templating/ |
high (node) |
worker.ts |
RNDR | ui/templating/ |
high |
renderer-safe.ts |
RNDR ×7 | ui/templating/ |
high |
constants.ts |
RNDR + COMMON | common/templating/ |
high |
types.ts |
RNDR + MAIN + NET + RT | common/templating/ |
high |
render-error.ts |
RNDR + MAIN + NET + COMMON | common/templating/ |
high |
render-context-serialization.ts |
RNDR + RT | common/templating/ |
high |
utils.ts |
RNDR + MAIN + COMMON (CodeMirror type imports only) | common/templating/ |
verify no DOM globals |
liquid-engine.ts |
worker (ui) + liquid-extension (main) | common/templating/ |
verify transitive |
liquid-extension-worker.ts |
worker (ui) + plugin host (main) | common/templating/ |
verify transitive |
tokenize-args.ts |
liquid-extension (main) + utils + worker | common/templating/ |
verify transitive |
faker-functions.ts |
postman importer (main) + local-template-tags | common/templating/ |
verify transitive |
local-template-tags.ts |
worker (ui) + main | common/templating/ |
verify transitive |
index.ts (barrel) |
RNDR + MAIN + RT | common/templating/ or ui/ |
verify what it re-exports |
scripting/ (no getRuntime files)
Reached from entry.hidden-window(-preload), script-executor.ts, and
ui/components/settings/scripting-settings.tsx — i.e. the hidden-window
(renderer) script-execution path, not main/ directly.
| File | Importers | Destination | Confidence |
|---|---|---|---|
run-script.ts |
RNDR (hidden-window) | ui/scripting/ |
verify — confirm hidden-window is renderer-context |
sandbox.ts |
hidden-window | ui/scripting/ |
verify |
require-interceptor.ts |
sandbox + script-executor | ui/scripting/ |
verify |
script-security-rules.ts |
RNDR | ui/scripting/ |
verify |
script-security-policy.ts |
sandbox (SELF) | follows sandbox | verify |
scripting/is the least certain: it's all renderer-side by current usage, but "sandbox/require-interceptor" read as node concepts. Confirm the hidden-window execution context before committing — it may warrantui/or its own context, notmain/.
Outcome (as executed)
account/ and utils/ dissolve entirely. plugins/ and templating/ are
mostly emptied but keep a small dual-context host residual, and scripting/
stays whole — see below. Every getRuntime file was placed by usage:
getPrivateKey, vault.ts, and mask-or-decrypt-vault-data.ts are each used by
both sides, so all three landed in common/.
The fourth context: node-enabled sandbox/host windows
Three areas turned out to be genuinely dual-context — they use Node builtins
and window/window.main in the same module because they execute in a
node-enabled BrowserWindow (the plugin window / hidden script-sandbox window),
not the plain renderer or main process. They fit none of ui//main//common/
and the existing ESLint config leaves them uncovered, so they remain as
purpose-named residual folders:
plugins/—index.ts(forks on__IS_RENDERER__betweenwindow.mainandelectron.shell),invoke-method.ts,context/,themes. The plugin host.templating/—index.ts+liquid-extension.ts(node:crypto/node:osandwindow.main.secureReadFile). The non-worker template renderer.scripting/—sandbox.ts,require-interceptor.ts,script-security-{policy,rules}.ts,run-script.ts. The script sandbox. (Left whole; it was not in the original split request.)
These residuals are the honest representation of a real execution context, not leftovers. A future pass could split each into a renderer client + a node host across an IPC boundary, but that is a larger change than a folder move.
Validation
npm run type-check (all workspaces) = 0 errors, npm run lint = clean,
vitest run (insomnia) = 1768 passed. Committed per folder.
Execution
Per folder, in order account → utils → plugins → templating → scripting,
committing after each so the PR has reviewable history:
- Move with
git mv(history follows the file). - Rewrite imports to the
~/alias for the new path. Use folder-qualified search/replace (account/crypt→common/account/crypt) so sibling files with the same basename are untouched. Drive correctness withnpx tsc --noEmit -p packages/insomnia/tsconfig.json(it names every stale specifier). - Resolve the
verifyrows by checking the file's real consumers/imports before fixing its folder; let lint confirm context (npx eslint <paths>— a Node import inui//common/, orwindowincommon//main/, fails). - Validate:
npm run type-check,npm run lint, thennpm test -w packages/insomnia(run vitest from insidepackages/insomniaso its~alias config applies). Rollback isgit reset --hard.
No new ESLint config is required — moving files into ui//main//common/
makes the existing context rules apply. The pre-push hook blocks on type errors
and context violations, so green type-check + lint is the bar.