Repository navigation
Conversation
|
✅ KSAI Review: Finished
Run report (federated) · 1 paid run · $0.2130 total
Reviewing cost $0.2126; deciding how to run it cost $0.0004 |
There was a problem hiding this comment.
| Check | Result |
|---|---|
| Scope | Per-entity discard for git-sync's visual diff: IPC channel + route, main action with watcher re-import, VCS workdir write, canonicalize guard on stage/discard, folder-discard warning. |
| Mandate | typescript-code-reviewer |
| Findings | 2 Medium, 1 Low |
| Findings audit | kreview:findings-auditor red-teamed 2 findings: 1 upheld, 1 reworded (discard-path field loss happens in the flush regardless), 1 new Medium promoted for the stage path. |
The discard flow mirrors the stage flow and is wired end to end. The splice engine keeps siblings and shells missing parents, so per-entity discards converge with the index. The gap is canonicalization: the replay rewrites entities the action never touched.
Additional Risk pass: no secrets, no new attack surface beyond the existing stage-entity IPC pattern, no leaks or unbounded growth. One caveat: on the discard path the watcher flush strips unknown fields even when canonicalization is skipped, so the canonical-file.ts guard fixes the stage path only.
| const canonicalContent = await getCanonicalInsomniaFileContent(content); | ||
| // The import fills in what a file is missing, eg. a `__MOCK_SERVER_ID__` server for a mock file | ||
| // whose server was spliced out. Writing that would add an entity nobody added, so keep `content`. | ||
| if (canonicalContent === null || hasEntityIdsNotIn(canonicalContent, content)) { |
There was a problem hiding this comment.
🟠 Medium risk
Stage/discard canonicalize the spliced file through import→export. InsomniaFileSchema is a plain z.object, so the replay strips unknown fields. hasEntityIdsNotIn only catches entities the replay adds, never data it drops. Discarding req_1 deletes req_2's hand-added field with no warning; the banner covers committing only. On the discard path importAllFiles plus the DB→FS flush drops the field regardless, so this guard fixes the stage path; the discard path needs a UI warning. Import checkStrictSchemaMatch from ~/common/zod/check-schema-mismatch and InsomniaFileSchema from ~/common/import-v5-parser.
| const canonicalContent = await getCanonicalInsomniaFileContent(content); | |
| // The import fills in what a file is missing, eg. a `__MOCK_SERVER_ID__` server for a mock file | |
| // whose server was spliced out. Writing that would add an entity nobody added, so keep `content`. | |
| if (canonicalContent === null || hasEntityIdsNotIn(canonicalContent, content)) { | |
| // A file with fields this version does not know loses them in the replay. Keep `content`, | |
| // or staging one entity drops another entity's fields from the index. | |
| if (checkStrictSchemaMatch(InsomniaFileSchema, parse(content)).mismatch) { | |
| return content; | |
| } | |
| const canonicalContent = await getCanonicalInsomniaFileContent(content); | |
| // The import fills in what a file is missing, eg. a `__MOCK_SERVER_ID__` server for a mock file | |
| // whose server was spliced out. Writing that would add an entity nobody added, so keep `content`. | |
| if (canonicalContent === null || hasEntityIdsNotIn(canonicalContent, content)) { | |
| return content; | |
| } | |
| return canonicalContent; |
React 👍 if this finding helped, 👎 if it did not.
| // `content` is spliced together by the visual diff; write it the way Git Sync would export it. | ||
| await GitVCS.stagePartialContent( | ||
| filepath, | ||
| content === null ? null : await canonicalizeInsomniaFileContent(content), |
There was a problem hiding this comment.
🟠 Medium risk
Staging one entity canonicalizes the index copy and strips unknown fields that HEAD and the workdir still have. req_2 then shows as modified forever: staging it re-canonicalizes to the same index, so it never converges. The banner disappears too — unknownFieldRanges reads the index side. Discarding the phantom change deletes the committed field from disk. The strict-schema guard from the canonical-file finding fixes this path.
React 👍 if this finding helped, 👎 if it did not.
| // Unstaged view only: graft the entity's index/HEAD state (`before`) onto the | ||
| // workdir file (`after`), throwing away just this entity's unstaged edits. | ||
| const handleDiscard = useCallback( | ||
| (entityId: string) => | ||
| runAction(entityId, 'discard', () => | ||
| discardPartialContentFetcher.submit({ | ||
| filepath, | ||
| content: applyEntityChange(after, before, entityId), | ||
| projectId, | ||
| workspaceId, | ||
| }), | ||
| ), | ||
| [runAction, before, after, filepath, projectId, workspaceId, discardPartialContentFetcher], | ||
| ); |
There was a problem hiding this comment.
🔵 Low risk
A never-staged file has an empty before, so every entity is added and discardable. Discarding the last one writes identity-only YAML to the working tree. The file keeps showing as changed — one workspace card with no actions — until a second, file-level discard. Stage avoids its index-side version of this with content: null; discard has no counterpart. Mirror it, and let the route delete the file with removeLocalOnlyOrphans.
| // Unstaged view only: graft the entity's index/HEAD state (`before`) onto the | |
| // workdir file (`after`), throwing away just this entity's unstaged edits. | |
| const handleDiscard = useCallback( | |
| (entityId: string) => | |
| runAction(entityId, 'discard', () => | |
| discardPartialContentFetcher.submit({ | |
| filepath, | |
| content: applyEntityChange(after, before, entityId), | |
| projectId, | |
| workspaceId, | |
| }), | |
| ), | |
| [runAction, before, after, filepath, projectId, workspaceId, discardPartialContentFetcher], | |
| ); | |
| // Unstaged view only: graft the entity's index/HEAD state (`before`) onto the | |
| // workdir file (`after`), throwing away just this entity's unstaged edits. | |
| const handleDiscard = useCallback( | |
| (entityId: string) => | |
| runAction(entityId, 'discard', () => { | |
| const content = applyEntityChange(after, before, entityId); | |
| // A never-staged file loses its last entity: like stage's `null`, drop the | |
| // file instead of leaving identity-only YAML in the working tree. | |
| return discardPartialContentFetcher.submit({ | |
| filepath, | |
| content: !before && hasOnlyWorkspaceIdentity(content) ? null : content, | |
| projectId, | |
| workspaceId, | |
| }); | |
| }), | |
| [runAction, before, after, filepath, projectId, workspaceId, discardPartialContentFetcher], | |
| ); |
React 👍 if this finding helped, 👎 if it did not.
efca8be to
26091e0
Compare
669267c to
f80863e
Compare
c5efa09 to
e841209
Compare
0288066 to
7b97e24
Compare
…y Git Sync exports them Unstaged cards get a click-to-confirm Discard that rewrites only that entity in the working-tree file and syncs it back into the database; discarding a folder that only exists in the working tree warns that its contents go with it. Before a per-entity stage, unstage or discard is written, the grafted file is replayed through Git Sync's import -> export in a scratch in-memory database, so it comes out exactly as Insomnia would export it. Files that can't be replayed, or whose replay would add placeholder entities, are written as is. Co-authored-by: James Gatz <jamesgatzos@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
7b97e24 to
3294f08
Compare
Overview
Adds a Visual Diff view to the git staging/commit modal, as a "Visual (Beta)" toggle alongside the existing raw text diff (Text / Visual switch above the diff panel).
The problem: a git-tracked file in Insomnia isn't one entity — it's a whole workspace export bundled into a single YAML blob (a collection's requests and folders, an environment's base + sub-environments, etc.). The existing diff view is a raw side-by-side text diff of that entire blob, which makes it hard to tell what actually changed when many entities live in one file, and forces staging/committing at the granularity of "the whole file" even when the user only cares about one request.
How it works:
git add, using a low-level isomorphic-git primitive that lets the index diverge from the on-disk working file. Before it's written, the grafted file is replayed through Git Sync's own import → export in a scratch in-memory database (the helper from fix: Warn when a Git Sync change cannot be discarded [INS-3830] #10585), so what lands in the index or the working tree is exactly what Insomnia itself would export: no leftover empty arrays, no ordering-only differences, and a collection that gains unit tests is written as a design document. Unstaged cards also have a click-to-confirm Discard, which rewrites only that entity in the working-tree file and syncs it back into the database; discarding a folder that only exists in the working tree warns that its contents go with it. Failures of any of these show an error toast.What the per-entity actions guarantee:
Folder cards: what each action does, including to the folder's contents. A folder card covers only the folder's own fields (name, Auth, Headers, Scripts, Environment, Docs); the requests and subfolders inside it have their own cards.
¹ A request moved into a new folder from elsewhere goes with the folder. After unstaging the folder, Staged shows that request as Removed; unstaging it puts it back where HEAD has it. After discarding the folder, Unstaged shows it as Removed; discarding that brings it back to its original place. So it isn't lost, it just takes one more click. Only requests created inside the new folder are lost, which is what the confirmation warns about.
² Stage and Unstage/Discard aren't symmetric for a removed folder: staging the removal takes the contents with it, but unstaging or discarding it brings back only the folder. A folder carries only its own fields and keeps whatever contents the target side already has, which here is none. This lets each item be restored on its own; to restore everything at once, use the file-level Unstage or Discard.
The cards are readable in both dark and light themes: text on tinted backgrounds uses the theme's regular text color.
Text view: now also shows indentation-only changes, which Monaco hid by default — in YAML these are structural (e.g. a request moved out of a folder).
This is intentionally shipped as a "Visual (Beta)" toggle next to the existing Text view, not a replacement — the raw diff remains the source of truth for anyone who wants to see exactly what git will commit. When a file has changes but none of them can be shown as a card, the Visual view says so and links straight to the Text view.
Testing
Known limitations
certificates, so in practice git-synced files don't contain them.settings) as a diff of its JSON rather than per field.