Skip to content

API Client: offline Monaco, data-loss and hang fixes, render perf, folder collections, Insomnia/Bruno import - #310

Merged
itsmeakhil merged 15 commits into
release-v0.1.16from
feat/api-client-phase0
Aug 24, 2026
Merged

API Client: offline Monaco, data-loss and hang fixes, render perf, folder collections, Insomnia/Bruno import#310
itsmeakhil merged 15 commits into
release-v0.1.16from
feat/api-client-phase0

Conversation

@itsmeakhil

@itsmeakhilitsmeakhil commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

What does this change?

API Client work in four parts: fix the things that lose data or hang the tab, stop the per-keystroke re-render of the whole editor, land the git-friendly folder collections that were stranded on an unmerged branch, and add Insomnia + Bruno + Postman-environment import.

It started as a gap analysis against Bruno v4.1 and turned into the first two phases of the plan that came out of it.

Closes #

Why?

Two of these were quietly serious:

  1. One saved example could wipe every open tab. Saved-example bodies were persisted into the api-client-tabs localStorage key; a 2 MB response blew the 5 MB quota and the catch deleted the whole key.
  2. A top-level while (true) in a pre-request script hung the tab permanently. The 3s script budget was only checked inside pm.test(), and Cancel aborted the fetch, not the worker.

Plus: git-friendly folder-backed collections (de5aa0a3) had been sitting on api-tester-improvements, unmerged, since July — that feature is our answer to Bruno's core pitch.

Type of change

  • Bug fix
  • New tool
  • Improvement to an existing tool
  • Documentation
  • Translation
  • Chore (deps, config, tooling)

What's in it, commit by commit

c50bd6e7 — trust blockers

  • Script runner: 5s hard timeout, worker terminated and respawned so the next send works.
  • Tabs: graphqlSchema (refetchable) always stripped; on quota overflow, retry once with example bodies and uploaded file bytes stripped instead of dropping every tab.
  • History: file bytes stripped before the POST too (not just localStorage); ORDER BY … LIMIT in SQL instead of loading every row and sorting in Rust; clear/delete now hard-delete (sync is gone, tombstones only accumulated).
  • Rust proxy: pooled reqwest clients (was one per request — a TLS handshake every send), 50 MB response cap with an error envelope instead of unbounded buffering.
  • Dropped the NEXT_PUBLIC_FIREBASE_API_KEY hard-fail from build-tauri.mjs — leftover from the removed auth, it blocked builds for a feature that no longer exists.

26282f74 — render performance

  • RequestTabs and ResponsePanel memoized; the nine inline (v) => updateActiveTab({...}) props replaced with one memoized setter map; stable onMount/onChange/onSaveExample.
  • handleSend and handleCopyCurl go through latest-value refs, so RequestPanel's memo holds and the window keydown listener stops being removed/re-added on every keystroke.
  • Response body parsed once per body instead of three times per render (tryParseJson was called inline in three eagerly-evaluated JSX props).
  • Monaco no longer remounts on Send: the request panel stays in one ResizablePanelGroup and the response panel joins it conditionally; body/pre-request/tests tabs are forceMount + hidden.
  • History rows and TabBar's measure effect no longer invalidate on every tab-array identity change.
  • sendProxyRequest() returns the Rust envelope directly instead of stringify → fake Response.json() (two extra full passes over the body on the UI thread).
  • Secret scan covers {params, headers, auth, body} instead of JSON.stringify(activeTab) — it was dragging the previous response and every saved example through a regex on each send.
  • next/dynamic for GrpcPanel (protobufjs), WebSocketPanel, ImportDialog (js-yaml), runner, metrics, cookie jar; exporters imported on click.
  • JSON tree/table views cap at 200 children / 500 rows (unvirtualised — a 10k-element array mounted ~100k DOM nodes on tab open).

711f3bd4 — merge api-tester-improvements
Folder-of-YAML collections (file-store.ts, collection-files.ts, move-to-vault.ts, Rust fs/dialog/persisted-scope plugins). Conflict resolution keeps main's auth removal — the branch's deep-link plugin, await_browser_auth command and cookie_store deps stay deleted — and main's reqwest 0.13 / base64 0.23. The "Open folder as collection" button moved into ToolSidebarActions, where main had relocated the sidebar header controls.

58aaf5bc — marketing copy
The Postman and Insomnia comparison pages were still conceding mock servers, collections and test automation to Postman — all of which ship. Rewritten to what's real, conceding only team/cloud workspaces, a CLI runner and Windows/Linux builds. Added /compare/mydevtools-vs-bruno and /compare/bruno-alternative.

63d78ed9 — spacing
Params/Headers column headers never lined up with their inputs (a flex header with no gap against rows that had one, plus a w-10 spacer against a 36px button); both now share one grid template. The toolbar's trailing divider rendered even though OfflineIndicator returns null while online, leaving a hairline and two gaps hanging off the row — the indicator now brings its own separator. Group dividers went from bg-border/50 + mx-1 (invisible on dark, and off the 8px rhythm) to bg-border.

abf5190e — Monaco stays on the CDN
Reverts the local Monaco bundling that c50bd6e7 had introduced, at the maintainer's request. See the reviewer note below.

5ce3275a, ab1b1d6b — imports

  • The Postman environment importer already existed and had no caller. Wired into the dialog; added a matching per-environment export button.
  • New Insomnia importer: v4 JSON resources and v5 YAML, nested groups, all body/auth types we support, {{ _.VAR }}{{VAR}}, scripts, environments. Round-trips our own Insomnia v4 exporter.
  • New Bruno importer: the legacy .bru language and v4 OpenCollection YAML — folder trees with folder.bru defaults, seq ordering, ~key disabled rows, json/text/xml/form/multipart (@file(...))/graphql bodies, basic/bearer/apikey/oauth2, scripts, environments/*.
  • Imports that carry environments now create them instead of dropping them.

How did you verify it?

  • cd apps/desktop-ui && pnpm test135 suites, 1267 passed, 2 skipped. 31 of those tests are new (tab slimming, Postman env round-trip, 13 Insomnia, 9 Bruno, 2 detection).
  • ./node_modules/.bin/tsc --noEmit -p . in both apps/desktop-ui and apps/web → clean.
  • cargo check and cargo test in apps/desktop/src-tauri → 54 passed. One existing test (history_limit_and_clear) started failing on the new SQL ordering because three rows land in the same millisecond; fixed with a rowid DESC tiebreak and the test now also asserts that clear actually empties.
  • Monaco checked in a real browser: loaded /app/format-converter and read the page's own performance.getEntriesByType('resource') — the editor renders and pulls cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs/*, i.e. the CDN behaviour this repo already had.
  • Drove the API Client in the running app: switched between Params/Body/Auth/Pre-request/Tests (editors keep state, inactive tabs hidden), typed a JSON body, sent with ⌘↵, confirmed the split layout appears and the body survives the send.

Screenshots

Not attached — the visible UI is unchanged apart from the sidebar's "Open folder as collection" button and an export icon in the environment manager. The perf work is deliberately invisible.

Checklist

  • pnpm lint passes — see note below
  • Tests pass (cd apps/desktop-ui && pnpm test), and logic changes have a test
  • User-visible strings go through useTranslations — the one new string (ApiClient.environmentManager.exportPostman) is in all 27 locales
  • New/changed keys added to messages/en.json (inserted into all 27 locales; JSON re-validated)
  • Async mutations that can fail are wrapped in try/catch with toast.error(message)
  • No new mandatory network call, sign-in, sync or backend. Note the app still fetches Monaco from jsdelivr on first editor render — pre-existing behaviour, deliberately left as is (see below)
  • No secrets, tokens or personal data in the diff

On lint: the repo has ~360 pre-existing ESLint errors and lint is deliberately out of CI. I compared the touched files before and after: same three pre-existing errors (two react-hooks/purity on Date.now() in render, one set-state-in-effect in tabs-context), no new ones. I did fix the two I introduced mid-work (ref writes during render → effects) rather than leave them.

Anything reviewers should know?

  • Worth a close look: the ResizablePanelGroup change in api-client.tsx. Panels now get explicit id/order because the group's membership is conditional; if the response panel's saved size behaves oddly on your machine, that's where it is.
  • Monaco still loads from jsdelivr, by decision. I had bundled it locally (public/monaco/vs + loader.config) so editors worked with no network; the maintainer asked for the CDN back, so abf5190e reverts that. Consequence to be aware of: offline, every Monaco-backed editor — API client body/scripts/response plus ~15 other tools — stays on "Loading editor...", which sits awkwardly next to the "fully offline" claim in the README and on the site. monaco-editor stays at 0.55.1 as a types-only dep because that is the exact version the loader requests from the CDN.
  • Bruno import is paste-a-file only for now. Picking a whole Bruno directory needs readCollectionFiles in lib/desktop/collection-files.ts to read .bru/.yml/.json — it is .yaml-only today. importBrunoFolder already takes the flat {path, text}[] that pass would produce.
  • git log is not linear711f3bd4 merges a five-week-old branch. Review with git diff main...HEAD rather than commit-by-commit if the merge noise gets in the way.
  • The gap analysis this came from also flagged features that are fully built and wired to nothing: the SSE relay in proxy.rs, the GraphQL subscriptions client, the OpenAPI spec watcher, cookie-jar export, and the backend's drag-and-drop move op. Not in this PR — worth their own.
  • I did not touch the deliberately-deferred items: NTLM/SPNEGO stay 501 on desktop, no CLI runner, no git UI.

🤖 Generated with Claude Code

https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH

itsmeakhiland others added 9 commits July 21, 2026 22:46
Open a folder as a collection: requests stored as YAML files
(NNN- prefix = order, dirs = folders, collection.yaml manifest),
so collections can be committed, diffed and PR-reviewed. Filesystem
is the store — mutations diff the serialized tree and write only
changed files; window focus re-reads external edits. Secrets stay
out of files ({{vault.*}} tokens pass through; environments never
serialized). Desktop only: tauri fs/dialog/persisted-scope plugins
plus a recursive fs-scope grant command for picked dirs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… loss
- Serve Monaco from the app bundle (scripts/copy-monaco.mjs → public/monaco/vs,
loader.config in lib/monaco-loader.ts) instead of jsdelivr; every editor was
stuck on "Loading editor..." offline. Bump monaco-editor to 0.55.1 — the
0.53 AMD build throws in language/css/monaco.contribution.js.
- Drop the stale Firebase-key gate from build-tauri.mjs (auth was removed).
- Script runner: 5 s hard timeout with worker terminate + respawn; a top-level
infinite loop no longer hangs the tab forever.
- Tabs persistence: strip refetchable graphqlSchema always; on quota overflow
retry with example bodies + file bytes stripped instead of wiping all tabs.
- History: strip uploaded file bytes before POST; SQL ORDER BY/LIMIT instead of
loading every row; clear/delete hard-delete rows (sync is gone, tombstones
only accumulated).
- Rust proxy: pooled reqwest clients (TLS/keep-alive reuse) and a 50 MB
response body cap with an error envelope instead of unbounded buffering.
- generate-code.ts: replace raw NUL byte in Go escaper regex with \0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
…ors mounted
- RequestTabs + ResponsePanel are React.memo; the nine inline
`(v) => updateActiveTab({...})` setters become one memoized setter map,
onSaveExample/onMount/onChange handlers are stable.
- handleSend/handleCopyCurl go through latest-value refs so RequestPanel's memo
holds and the keydown listener stops re-registering on every edit;
handleLoadRequest/handleCancel/handleCurlPaste/example handlers memoized.
- Response JSON parsed once (useMemo) instead of three times per render for
the tree/table/geo views; those views cap at 200 children / 500 rows.
- Desktop layout keeps the request panel in one ResizablePanelGroup and adds
the response panel conditionally (id/order) — Send no longer remounts Monaco.
Body / pre-request / tests editor tabs are forceMount + hidden when inactive.
- History list row renderer memoized so VirtualHistoryList's memo holds;
TabBar's layout effect keys on tab id/kind/method/name, not the tabs array.
- sendProxyRequest() returns the Rust envelope directly instead of
stringify → fake Response → json(); typed ProxyEnvelope.
- Secret-variable scan covers params/headers/auth/body only, not the whole tab
(previous response + example bodies).
- Scripts worker caches pm.response.json() per run.
- next/dynamic for GrpcPanel (protobufjs), WebSocketPanel, ImportDialog
(js-yaml), CookieJarDialog, MetricsDialog, CollectionRunnerDialog; exporters
are imported on click.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
…llections
Brings the folder-of-YAML collections (file-store.ts, collection-files.ts,
move-to-vault.ts, Rust fs/dialog/persisted-scope plugins, registry commands)
onto the current main. Conflict resolution keeps main's auth removal — the
branch's deep-link plugin, auth_server command and cookie_store deps stay
deleted — and main's reqwest 0.13 / base64 0.23. The "Open folder as
collection" button moves into ToolSidebarActions, where main relocated the
sidebar header controls.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
Postman and Insomnia comparison pages stopped conceding mock servers,
collections, scripting and the runner — all shipped. They now list REST,
GraphQL, WebSocket, native gRPC, the local mock server, runner, vault-backed
secrets and import/export formats, and concede only team/cloud workspaces,
a CLI runner and Windows/Linux builds. New /compare/mydevtools-vs-bruno and
/compare/bruno-alternative pages; api-client metadata description/keywords
and the landing capability blurb describe the real scope.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
The Postman environment importer existed but nothing called it. The import
dialog now detects `{ name, values[] }` payloads and creates the environment;
the environment manager gets a per-environment Postman export button.
i18n key ApiClient.environmentManager.exportPostman in all 27 locales.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
…in palette entry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
Two importers for the switcher path, both pure libs with tests:
- Insomnia (`lib/import/insomnia.ts`): v4 JSON resources and v5 YAML, nested
groups, every body/auth type we support, `{{ _.VAR }}` → `{{VAR}}`,
pre-request/after-response scripts, environments. Round-trips our own
Insomnia v4 exporter.
- Bruno (`lib/import/bruno.ts`): the legacy `.bru` language and v4
OpenCollection YAML. Folder trees with `folder.bru` defaults, `seq`
ordering, `~key` disabled rows, json/text/xml/form/multipart (`@file(...)`)
/graphql bodies, basic/bearer/apikey/oauth2 auth, scripts, and
`environments/*` (secret var names come in with empty values).
Detection covers both, and the import dialog now creates the environments an
import brings with it instead of dropping them. 22 new tests.
Still folder-only from a paste of a single file: picking a whole Bruno
directory needs a desktop folder-read pass (readCollectionFiles is .yaml-only).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
@vercel

vercelBot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
mydevtools-techReadyReadyPreviewAug 24, 2026 9:34am

export function importInsomniaWithMeta(text: string): InsomniaImportResult {
const t = text.trim()
// yaml.load also parses JSON, so v4 YAML exports come for free.
const doc = (t.startsWith("{") ? JSON.parse(t) : yaml.load(t)) as
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

1 finding(s)

MEDIUM: 1

SeverityRuleLocation
MEDIUMjs-unsafe-yaml-loadapps/desktop-ui/src/lib/import/insomnia.ts:290

Snippets are redacted; ThreatCrush never prints matched credential material.

@itsmeakhil
itsmeakhil changed the base branch from main to release-v0.1.16August 23, 2026 18:44
- Params/Headers: the column header was a flex row with no `gap` while the
data rows had `gap-2`, and its trailing spacer was `w-10` against a 36px
delete button — so KEY/VALUE never sat over their inputs. Both now share one
grid template, and the labels use the Input's own `px-3`.
- Toolbar: `OfflineIndicator` renders null whenever you are online, but the
divider before it rendered unconditionally, leaving a hairline and two gaps
hanging off the end of the row. The indicator now brings its own separator.
- Group dividers were `bg-border/50` and `mx-1`: too faint to read as dividers
on a dark ground, so they looked like unexplained holes, and the extra margin
broke the row's 8px rhythm. Now `bg-border`, spaced by the row's own gap.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
Drops the local Monaco bundling from c50bd6e per maintainer decision:
removes lib/monaco-loader.ts, scripts/copy-monaco.mjs, the pre{dev,build}
hooks, the build-tauri copy step and the public/monaco gitignore entry.
@monaco-editor/loader falls back to its default
cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs.
Keeps monaco-editor at 0.55.1: it is a types-only dependency here, and 0.55.1
is the exact version the loader pulls from the CDN, so the local typings match
what runs.
Trade-off, recorded deliberately: with no network every Monaco-backed editor
(API client body/scripts/response plus ~15 other tools) stays on
"Loading editor..." — the app is not fully offline while this stands.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
Reported against the API client, but two of the three live in the shared
ToolSidebarLayout used by 11 tools.
- Collapsed, the 40px rail rendered a single toggle: no items, no identity,
just an empty strip. It now shows the tool's accent icon, which peeks the
full panel as a flyout (the existing Sheet, previously mobile-only) so the
list stays reachable without giving up the width; the pin-open toggle sits
below it.
- New `isOverlay` on the panel context — true for the mobile sheet and the new
desktop flyout — so picking a row dismisses a floating panel but never the
pinned one. All five call sites moved off `isMobile`, which would have left
the desktop flyout covering the thing you just opened.
- API client: the tool had no title of its own (the panel header says
"Collections"), leaving it unnamed once collapsed, and its toolbar sat flush
against the top edge. Adds an `h1` from the existing `Navigation.apiClient`
key — present in all 27 locales, so no new strings — and `px-4 pt-3`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
Two follow-ups to the collapsed-rail work, both about showing one control
where there should be one:
- The rail rendered the tool icon *and* a separate expand arrow — two buttons
for the same action stacked in 40px. Now a single button: the tool's accent
icon at rest (so the strip still names the tool), swapping to the toggle
glyph on hover/focus. The desktop flyout goes with it; clicking expands, as
the glyph promises. `isOverlay` narrows back to the mobile sheet.
- `PanelLeftClose` / `PanelLeftOpen` draw a panel outline *and* a chevron
inside 16px, which at this size reads as two icons overlapping rather than
one control — visible in the Notes header. Both replaced with plain
`PanelLeft`, the same glyph shadcn uses for its sidebar trigger.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH
@itsmeakhil
itsmeakhil merged commit bcbe669 into release-v0.1.16Aug 24, 2026
4 of 5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@itsmeakhil@github-advanced-security