## Background
Workspace `hasUncommittedChanges` / `hasUnpushedChanges` flags are written only by cloud sync. When a project stopped being a cloud project, those flags were never cleared, so the project overview cards kept showing "Uncommitted changes".
Repro:
1. Open a cloud project whose workspaces have uncommitted changes.
2. Remove the project from the remote.
3. Open the project overview. The cards still show "Uncommitted changes" although the project is now local.
A project converted from cloud to git carries the same residual flags, because the values come from its former cloud state.
## Changes
- Clear both workspace sync flags when a project's `remoteId` becomes `null` in the project service. Remote deletion, conversion to local, cross-organization move, and conversion to git all update the project through this service.
- Reuse the cleanup in the startup database repair to fix existing stale records. Only dirty workspace metas are written, and `modified` is preserved.
- Add service and repair tests.
## Testing
- `npm test -w insomnia-data` (538 tests)
- `npm run type-check -w insomnia-data`
- `npm run lint -w insomnia-data`
## References
- INS-3899
## Background
In a Git-synced project, restarting Insomnia without any change marked every design document as modified in the Git staging list. The diff of each affected YAML contained exactly one line: the embedded ApiSpec `meta.modified` timestamp.
The v5 design-doc YAML does not serialize `ApiSpec.fileName` (the v5 schema has no such field), while v5 import parses a missing `fileName` as an empty string. Any git round-trip therefore reset `fileName` to empty in the database, and the startup repair (`_applyApiSpecName`) rewrote it via `docUpdate`, which unconditionally restamped `modified: Date.now()` for every design document in one burst. RepoFileWatcher then re-exported the database to disk and git status reported all files as modified, repeating on every startup until committed.
## Changes
- Import: fall back `ApiSpec.fileName` to the file top-level name when the spec uses the `{ contents }` shape, so a round-trip never stores an empty fileName.
- Repair: skip the fileName backfill when it would write the existing value; a no-op write only restamps `modified`.
- Regression tests for both behaviors.
Fixes INS-4058
* migrate request related p2
- grpc-request
- cookie-jar
- fix ts errors
* add unit test to check field mismatch
* migrate request group model
* migrate the socket io request and import parser
* migrate websocket schema and fix ts error
* fix order issue and add unit tests
* fix: Warn when a Git Sync change cannot be discarded [INS-3689]
When a committed YAML file is not in the exact format Insomnia generates
(hand-edited, or written by an older version), discarding it restores the
file, the repo file watcher imports it and re-exports it, and the change
reappears. Users get stuck with a change they can never discard.
Detect this up front by replaying the import -> export round-trip in memory
on the raw index/HEAD content. When the result equals the working copy, the
whole diff is Insomnia re-generating the file, so the Commit Changes modal
shows a warning above the diff and the discard confirmation explains that
the file needs to be committed once.
- Add sync/git/canonical-file.ts, which mirrors database.update (initModel)
and the getWithDescendants ordering (created, then _id)
- Split getInsomniaV5DataExport into a DB fetch and a pure
serializeInsomniaV5Export
- Return rawHead/rawStage from GitVCS.fileStatus, since head/stage are
migrated and reordered for display
- Export initModel from insomnia-data/node
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: Replay the Git Sync round-trip on a scratch database [INS-3689]
The undiscardable-change check reimplemented parts of the import/export
pipeline (initModel, getWithDescendants ordering) to avoid touching the app
database. That copy would silently drift whenever the real pipeline changes.
Run the real pipeline instead: write the imported docs to a scratch
in-memory NeDB instance and export them with getInsomniaV5DataExport.
- Keep NeDB collections, change buffer and listeners per instance in
createNedbDatabase. They were module-level, so a second instance's init()
would have replaced the app's collections. flushChangesImpl is now handed
to the wrapper instead of being exported.
- Add an optional db parameter to getInsomniaV5DataExport and drop the
serializeInsomniaV5Export split and the initModel export
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: Ignore import timestamps when detecting undiscardable changes [INS-3689]
Import fills a missing meta.created/modified with the current time, so a
hand-added entry without timestamps got a different value in the replay
than in the working copy the watcher wrote earlier. The exact comparison
then failed and the warning never showed, although the change still came
back after every discard.
Compare the replay with the working copy using hasSignificantChanges, the
same rules the Unstaged changes list uses, so differences the list already
ignores no longer hide the warning.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: Reword the undiscardable change warning [INS-3689]
Explain the cause in user terms, using the wording suggested in the ticket.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: Shorten the undiscardable change warning [INS-3689]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: Rename committedContent to baselineContent [INS-3689]
The content a discard restores is the staged content when there is one, so
it is not necessarily committed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Move Konnect projects into a global Control Planes organization
* fix: break new circular dependency in Konnect organization utils
useKonnectOrganization() imported useRootLoaderData from ~/root to read
accountId, but organization-utils.ts is itself reached from root.tsx via
the settings modal chain, closing a new import cycle flagged by CI's
dependency-cruiser check. Pass accountId in from the caller
(useOrganizations), which already has it, instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Konnect moved onboarding nudge.
* Can sync when the konnect org is empty.
* fix: guard two develop-introduced hooks against the Konnect organization
use-organization-storage-rule.ts and use-remote-files.ts's
useRemoteBackendProjects were added by develop's account-data refactor
after this plan's original API-guard audit, and both issued real
network/IPC calls for the local-only Konnect organization:
- useOrganizationStorageRule called the raw API directly and only
checked isScratchpadOrganizationId, missing Konnect. Fixed by
delegating to fetchAndCacheOrganizationStorageRule (which already
returns the correct local-only rules with no network call) instead
of gating with `enabled`, since a naive gate would fall back to the
permissive DEFAULT_STORAGE_RULES and surface Cloud Sync/Git Sync
inside Control Planes.
- useRemoteBackendProjects had no organization guard at all, firing
window.main.sync.remoteBackendProjectsOfTeam for an org whose
projects never have a remoteId. Fixed with an isLocalOrganizationId
check on its `enabled` gate.
Re-audited every other organizationId-scoped API guard in the plan;
no further gaps found. Plan doc updated with both findings.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: disable Connect & Sync in Konnect settings modal without entitlement
The modal let a user validate and store a PAT even when the account
lacks the Konnect control-planes entitlement, even though syncing
could never run in that state — the sidebar's Sync button was already
disabled here but the modal's own Connect & Sync button was not.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: re-fetch Konnect entitlements after logging back into the same account
refreshKonnectAccess deduped by accountId, but signing in never
reloads the renderer, so its module-level guard survives a logout —
and a fresh login into the same account keeps the same accountId,
silently skipping the re-fetch of /v1/user/entitlements.
Key the guard on sessionId instead: a new login always mints a new
session token even for the same account, so this still dedupes the
normal cold-start case (startup call and post-login loader share one
session) while correctly re-resolving after logout/login.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: navigate away when the Konnect organization becomes invisible
Deleting a user's last Konnect project (one at a time via the project
delete action, or in bulk via Disconnect) could leave them stranded on
a URL for an organization that no longer appears in the dropdown, with
no automatic navigation elsewhere.
Add getKonnectOrganizationEscapeRoute(organizationId), which re-checks
visibility with a fresh local project count (no network call — only
that half can change from a plain NeDB delete) combined with the
last-resolved entitlement, updates the shared access store so
useOrganizations() reflects it immediately, and returns the account's
first real organization to redirect to when the org just went dark.
Call it from both places that can remove the last Konnect project:
- organization.$organizationId._index.tsx's loader, which every
"no reachable project" fallback already redirects through
- organization.$organizationId.project.$projectId.delete.tsx's
action, which previously short-circuited straight back into the
same (now invisible) organization instead of going through that
loader
Extracted the shared "recompute + store update" logic into
reconcileKonnectAccess() to avoid duplicating it between
refreshKonnectAccess() and the new escape-route check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Migrate after login
* Force fetch storage rule
* Replace Konnect sync module-var trigger with event bus
Swaps the register/run module variable in konnect-sync-trigger.ts for
uiEventBus so the sync callback is subscribed/unsubscribed via useEffect
instead of being reassigned on every render with no cleanup.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Remove plan
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix: limit cookie template rendering to manually-set cookies and narrow template file access
* fix: only expand nested templates for variable substitution, not direct tag output
* test: cover OAuth2 access token exposure via the request tag for nested-template regression
* sec: exclude response-sourced cookies from WebSocket/Socket.IO connect rendering
* test: fix mTLS/cert smoke tests relying on secure-read-file path-prefix bug
* test: scope mtls smoke test locator to avoid strict-mode collision
* sec: close hard-link bypass of reserved NeDB database file check
* fix: avoid doubled path separator when allowlisted folder is a filesystem root
add initial settings for legacy test
add initial check for legacy unit test suites
show dropdown
remove spec route and merged with default debug page
showing document as collection
remove document term in UI
fix lint issue
fix ut failures
* refactor(templating): retire templateTagSandboxEnabled, unify on pluginSandboxEnabled
The experimental `templateTagSandboxEnabled` toggle only ever gated
template-tag execution and had been superseded by `pluginSandboxEnabled`,
which sandboxes every untrusted plugin surface. Retire the legacy flag:
- drop it from the Settings type + defaults
- simplify `isSandboxEnabled` to read only `pluginSandboxEnabled`
- bundle plugins are trusted, so their tags always run in-process (the
legacy all-modules/all-caps bundle sandbox branch is removed)
- remove the "Run template tags in sandbox" Preferences toggle
- carry a user's prior opt-in forward via a settings migration
(templateTagSandboxEnabled === true -> pluginSandboxEnabled = true),
then delete the stale field
Smoke helpers repointed to the surviving `toggle-plugin-sandbox`; the now
-duplicate `enablePluginSandbox` helper is folded into `enableSandbox`.
* refactor(plugins): rename 'trusted' execution mode to 'internal'
'trusted' is an internal-only PluginExecutionMode label for built-in
bundled plugins the user can't install or change; it's never surfaced in
the UI (the Plugins badge shows 'In-process'). Rename it to the more
neutral, descriptive 'internal' to match the docs. Distinct from the
plugin *registry* trust concept (resolveTrustedPlugin, the 'trusted-plugin'
guard), which is unchanged.
* sandbox: reject bundle-plugin name impersonation, remove stale doc (#10376)
* docs(settings): simplify 'Sandbox all plugin code' toggle description
Address review nit: drop implementation jargon (QuickJS-WASM, default-deny)
from the Preferences copy in favour of plain language, keeping the coverage
list and the full-host-access escape-hatch pointer.
---------
Co-authored-by: kwburns-kong <kyle.burns@konghq.com>
* add package
remove workspaceCount from rootLoaderData
move unrelevant loaders out
add query context
adapt tanstack query
fix som issues
add new ways to load data
initial changes
clean code
remove and fix code
add a method to get organization data in main
refine the way to get data from tanstack
fix issue
fix issues
fix duplicate calling issue
fix unsubscribe issue
fix merge issue
fix listen issue
fix find organization issue for workspace
support workspatemeta in tanstack query
update types
move logic to insomnia-data services
remove things
change to use service
fix the loader bug
remove a file
fix type issues
refine code for workspace-children
change style import
fix main type issue
fix type issue
fix data fetching
fix delete project issue
cache the sidebar data in main
add unit tests
update cache and cache service
fix issues from comment
revert organization change
fix issues from comment
fix test failures
fix test failures
add comments and refine way to get workspace children
fix dedup and import issue
remove useless code
add duplicate id check
fix type failures
fix mock server issue
resolve conflict
1.remove main process cache
add renderer level app data
fix issues
* fix issues from review
* feat(templating): (H1) in-sandbox response-hook API + marshaling
First increment of PR 10b (response hooks). Adds the sandbox-side machinery to
run a user plugin's response hook, without yet wiring the network adapters:
- __buildResponseApi(resp): faithful ES5 rebuild of plugins/context/response.ts
getters (status/headers/time, case-insensitive getHeader returning
string|string[]|null). getBody() bridges to the existing response.getBodyBuffer
path; getBodyStream() throws (a Node Readable can't cross the sandbox); setBody()
base64-encodes the bytes over a new response.setBody bridge path (no fs in the
sandbox) and updates bytesContent locally.
- __invokeHook: for response hooks, attaches context.response and a READ-ONLY
context.request (matches pluginRequest.init(..., true)), and marshals both the
request and response back out.
- host-bridge: response.setBody mapped at baseline (models.read, grouped with the
other response ops) — a bounded write to the host-set bodyPath, matching the
ungated in-process behavior; not a general fs write.
Unit test drives a response hook that reads status/headers, reads the body via
the bridge, and rewrites it via setBody (asserting the base64 round-trip and the
read-only request). Host runner + adapter integration + e2e are the next increment.
* feat(templating): (H1) route user-plugin response hooks through the sandbox
Wires the response-hook sandbox core into both hook paths so a user plugin's
response hook runs in QuickJS (bundle plugins and the flag-off path unchanged).
- templating-worker-database: runResponseHookInSandbox + the
plugin.runUserResponseHook handler; the response.setBody bridge handler
(base64-decode -> fs.writeFileSync) guarded to the responses directory as
defense in depth (bodyPath is host-set); pickHookResponseFields.
- network-adapter.node.ts (main/CLI): runs the response hook in the sandbox for
user plugins, merging the returned response fields onto newResponse.
- invoke-method.ts (plugin window): reaches the same runner over the protocol.
E2E: a user plugin response hook rewrites the body via setBody; the echo
response pane shows the rewrite. Flag off it reports ranin-mainprocess (control),
flag on ranin-sandboxed. Completes H1 (request hooks in PR 10a).
* fix(templating): (H1) revive bridged Buffer in response getBody + reject empty setBody
Addresses Copilot review on #10286:
- in-sandbox-bootstrap.ts: context.response.getBody() now revives the JSON-marshaled
Buffer shape ({ type: 'Buffer', data: [...] }) back into a (shimmed) Buffer, so hooks
consume it exactly like the in-process response API instead of a plain object.
- templating-worker-database.ts: response.setBody rejects a missing/non-string bodyBase64
(TypeError) instead of defaulting to '' — a write primitive must not silently truncate
the body to zero bytes. An empty string stays a valid empty body.
- sandbox-hooks.test.ts: the getBodyBuffer stub now returns the real marshaled Buffer
shape and the hook decodes with toString('utf8'), exercising the revive end-to-end.
* perf: rewrite command palette search to eliminate UI freezes and stale results
Problems fixed:
- clientLoader blocked navigation on every palette open, freezing the UI during
full sequential DB scan
- no AbortController: stale searches could overwrite newer results mid-type
- no debounce: every keystroke triggered a full DB traversal immediately
- React Aria's default contains filter re-filtered server-side fuzzy results,
silently dropping valid matches and making fuzzy work redundant
- no warm baseline: palette started blank until the entire load completed
Changes:
- move fuzzyMatch/fuzzyMatchAll → insomnia-data/common-src/search.ts (shared,
testable in node)
- move search logic → insomnia-data/node-src/services/helpers/command-search.ts
- per-request AbortController cancellation via abort(requestId)
- recursion depth guard (max 20) on request group traversal
- safeParent() replaces non-null assertions, safe against orphaned documents
- add CommandSearchResult type in insomnia-data/src/command-search-types.ts
- add useCommandSearch hook: 250ms debounce + abort on filter change + warm
baseline on mount
- add defaultFilter={() => true} to ComboBox, delegating all filtering to the
service
- delete routes/commands.tsx
* fix
* fix
* tech_design
* tech-doc
* add directory to git repo model
* tech doc
* tech doc
* use directory picker to select an existing repo to clone from/to
* tech doc
* Open git repo
* tech doc
* Implement Git project local storage features and add e2e tests
* tech doc
* Implement folder opening as Git projects with user trust confirmation
* Add Git credential selection to project creation form and enhance repo file watcher for directory availability
* tech doc
* refactor: clean up code and remove references to GIT_LOCAL_REPOS_DESIGN.md
* fix: handle optional author name in Git credential display
* feat: enhance Git credential handling for local repositories
* feat: enhance Git project folder handling and improve test descriptions
* fix: update Git project mode button copy
Rename 'Clone from URL' to 'Clone from Remote' and 'Open existing
folder' to 'Open local folder'. Update the smoke test selector and a
stale comment accordingly.
* fix: align control heights and styling in Git clone form
Standardize the credential select, author email select, and clone
location box to match the repository/branch comboboxes: shared
--line-height-xs height, consistent label spacing, and input-sized
value text and padding.
* feat: show clone target path and remember last clone folder
Default the clone parent directory to the folder the user last cloned
into, and render the resulting target path middle-truncated with a full
path tooltip via a new MiddleTruncate component.
* fix: reorder and align Open local folder input layout
Move the helper text directly below the Folder label, and align the
folder box and Choose folder button to the shared control height with
middle-truncated path display.
* feat: warn when opening a folder already used by a project
Add a git.checkGitRepoDirectory IPC that resolves the project adopting
a folder. The Open local folder flow checks at folder-pick time and
shows a red, no-background warning below the input offering to open the
existing project, and blocks continuing while the warning is present.
* fix: top-align empty organization view and scroll the full page
Replace the vertically centered grid with a top-aligned, page-scrolling
layout so the new project form no longer jumps when switching project
types and the scrollbar spans the whole pane.
* fix: align project modal to top and match folder description color
Top-align the project modal overlay so it no longer jumps as the form
height changes, and drop the dimmer color override on the Open local
folder helper text so it matches the repository URL field description.
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: always use getRepoBaseDir
* fix comment
* delete duplicate code
* fix: test
* test: add Git repository relocation tests
* fix: ensure selection change handler converts key to string
---------
Co-authored-by: Pavlos Koutoglou <pkoutoglou@gmail.com>
Co-authored-by: Curry Yang <163384738+CurryYangxx@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Curry Yang <1019yanglu@gmail.com>
Move insomnia-data models, services, database code, and common utilities into a dedicated workspace package. Update consumers to import from the new package entrypoints and declare workspace dependencies for the extracted package.