Uh oh!
There was an error while loading. Please reload this page.
API Client: offline Monaco, data-loss and hang fixes, render perf, folder collections, Insomnia/Bruno import - #310
Merged
Conversation
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_01H3GuKxASDXLwFCLA7o3GGHContributor
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| 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 |
ThreatCrush Security Scan1 finding(s) MEDIUM: 1
Snippets are redacted; ThreatCrush never prints matched credential material. |
- 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
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
api-client-tabslocalStorage key; a 2 MB response blew the 5 MB quota and thecatchdeleted the whole key.while (true)in a pre-request script hung the tab permanently. The 3s script budget was only checked insidepm.test(), and Cancel aborted the fetch, not the worker.Plus:
git-friendly folder-backed collections(de5aa0a3) had been sitting onapi-tester-improvements, unmerged, since July — that feature is our answer to Bruno's core pitch.Type of change
What's in it, commit by commit
c50bd6e7— trust blockersgraphqlSchema(refetchable) always stripped; on quota overflow, retry once with example bodies and uploaded file bytes stripped instead of dropping every tab.ORDER BY … LIMITin SQL instead of loading every row and sorting in Rust; clear/delete now hard-delete (sync is gone, tombstones only accumulated).reqwestclients (was one per request — a TLS handshake every send), 50 MB response cap with an error envelope instead of unbounded buffering.NEXT_PUBLIC_FIREBASE_API_KEYhard-fail frombuild-tauri.mjs— leftover from the removed auth, it blocked builds for a feature that no longer exists.26282f74— render performanceRequestTabsandResponsePanelmemoized; the nine inline(v) => updateActiveTab({...})props replaced with one memoized setter map; stableonMount/onChange/onSaveExample.handleSendandhandleCopyCurlgo through latest-value refs, soRequestPanel's memo holds and the window keydown listener stops being removed/re-added on every keystroke.tryParseJsonwas called inline in three eagerly-evaluated JSX props).ResizablePanelGroupand the response panel joins it conditionally; body/pre-request/tests tabs areforceMount+ hidden.TabBar's measure effect no longer invalidate on every tab-array identity change.sendProxyRequest()returns the Rust envelope directly instead ofstringify→ fakeResponse→.json()(two extra full passes over the body on the UI thread).{params, headers, auth, body}instead ofJSON.stringify(activeTab)— it was dragging the previous response and every saved example through a regex on each send.next/dynamicfor GrpcPanel (protobufjs), WebSocketPanel, ImportDialog (js-yaml), runner, metrics, cookie jar; exporters imported on click.711f3bd4— mergeapi-tester-improvementsFolder-of-YAML collections (
file-store.ts,collection-files.ts,move-to-vault.ts, Rust fs/dialog/persisted-scope plugins). Conflict resolution keepsmain's auth removal — the branch'sdeep-linkplugin,await_browser_authcommand andcookie_storedeps stay deleted — andmain's reqwest 0.13 / base64 0.23. The "Open folder as collection" button moved intoToolSidebarActions, wheremainhad relocated the sidebar header controls.58aaf5bc— marketing copyThe 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-brunoand/compare/bruno-alternative.63d78ed9— spacingParams/Headers column headers never lined up with their inputs (a flex header with no
gapagainst rows that had one, plus aw-10spacer against a 36px button); both now share one grid template. The toolbar's trailing divider rendered even thoughOfflineIndicatorreturnsnullwhile online, leaving a hairline and two gaps hanging off the row — the indicator now brings its own separator. Group dividers went frombg-border/50+mx-1(invisible on dark, and off the 8px rhythm) tobg-border.abf5190e— Monaco stays on the CDNReverts the local Monaco bundling that
c50bd6e7had introduced, at the maintainer's request. See the reviewer note below.5ce3275a,ab1b1d6b— imports{{ _.VAR }}→{{VAR}}, scripts, environments. Round-trips our own Insomnia v4 exporter..brulanguage and v4 OpenCollection YAML — folder trees withfolder.brudefaults,seqordering,~keydisabled rows, json/text/xml/form/multipart (@file(...))/graphql bodies, basic/bearer/apikey/oauth2, scripts,environments/*.How did you verify it?
cd apps/desktop-ui && pnpm test→ 135 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 bothapps/desktop-uiandapps/web→ clean.cargo checkandcargo testinapps/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 arowid DESCtiebreak and the test now also asserts that clear actually empties./app/format-converterand read the page's ownperformance.getEntriesByType('resource')— the editor renders and pullscdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs/*, i.e. the CDN behaviour this repo already had.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 lintpasses — see note belowcd apps/desktop-ui && pnpm test), and logic changes have a testuseTranslations— the one new string (ApiClient.environmentManager.exportPostman) is in all 27 localesmessages/en.json(inserted into all 27 locales; JSON re-validated)try/catchwithtoast.error(message)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/purityonDate.now()in render, oneset-state-in-effectintabs-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?
ResizablePanelGroupchange inapi-client.tsx. Panels now get explicitid/orderbecause the group's membership is conditional; if the response panel's saved size behaves oddly on your machine, that's where it is.public/monaco/vs+loader.config) so editors worked with no network; the maintainer asked for the CDN back, soabf5190ereverts 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-editorstays at 0.55.1 as a types-only dep because that is the exact version the loader requests from the CDN.readCollectionFilesinlib/desktop/collection-files.tsto read.bru/.yml/.json— it is.yaml-only today.importBrunoFolderalready takes the flat{path, text}[]that pass would produce.git logis not linear —711f3bd4merges a five-week-old branch. Review withgit diff main...HEADrather than commit-by-commit if the merge noise gets in the way.proxy.rs, the GraphQL subscriptions client, the OpenAPI spec watcher, cookie-jar export, and the backend's drag-and-dropmoveop. Not in this PR — worth their own.🤖 Generated with Claude Code
https://claude.ai/code/session_01H3GuKxASDXLwFCLA7o3GGH