Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@testsmith/api-spector-agent",
"version": "0.5.1",
"version": "0.5.2",
"description": "API Spector private runner: monitor internal/local APIs the cloud cannot reach, with no inbound connections.",
"license": "MIT",
"homepage": "https://api-spector.dev",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@testsmith/api-spector",
"productName": "API Spector",
"version": "0.5.1",
"version": "0.5.2",
"description": "Local-first API testing tool to inspect, test and mock APIs",
"repository": {
"type": "git",
Expand Down
12 changes: 10 additions & 2 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ function loadAppIcon(): Electron.NativeImage | undefined {
}


// Keep the splash on screen at least this long so it reads as intentional, but
// never add a fixed delay on top of load time (it used to always wait 1.2s
// AFTER the window finished loading).
const MIN_SPLASH_MS = 350;

function createWindow(): void {
const startedAt = Date.now();
const splash = createSplashWindow();
const appIcon = loadAppIcon();

Expand Down Expand Up @@ -119,11 +125,13 @@ function createWindow(): void {
}

win.webContents.once('did-finish-load', () => {
// Brief pause so the splash is visible even on fast machines
// Show as soon as the window is loaded; only hold back for whatever remains
// of the minimum splash time (0 once load already took that long).
const remaining = Math.max(0, MIN_SPLASH_MS - (Date.now() - startedAt));
setTimeout(() => {
splash.close();
win.show();
}, 1200);
}, remaining);
});

// On Windows the native title bar is shown — include the version in the title
Expand Down
9 changes: 8 additions & 1 deletion src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export default function App () {
const updateMock = useStore( s => s.updateMock );
const theme = useStore( s => s.theme );
const setCommandPaletteOpen = useStore( s => s.setCommandPaletteOpen );
const requestSend = useStore( s => s.requestSend );
const setWsStatus = useStore( s => s.setWsStatus );
const addWsMessage = useStore( s => s.addWsMessage );
const pushLiveStreamEvents = useStore( s => s.pushLiveStreamEvents );
Expand Down Expand Up @@ -283,10 +284,16 @@ export default function App () {
e.preventDefault();
setCommandPaletteOpen( true );
}
// Cmd/Ctrl+Enter (re)sends the active request. The active RequestBuilder
// handles the actual send and ignores it for WebSocket/gRPC tabs.
if ( e.key === 'Enter' && ( e.metaKey || e.ctrlKey ) ) {
e.preventDefault();
requestSend();
}
}
window.addEventListener( 'keydown', handleKeyDown );
return () => window.removeEventListener( 'keydown', handleKeyDown );
}, [setCommandPaletteOpen] );
}, [setCommandPaletteOpen, requestSend] );

// Keep light class in sync when OS preference changes (system theme)
useEffect( () => {
Expand Down
16 changes: 10 additions & 6 deletions src/renderer/src/components/RequestBuilder/BodyTab.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2024-2026 Testsmith.io
// SPDX-License-Identifier: MIT

import React, { useMemo } from 'react';
import React, { useMemo, lazy, Suspense } from 'react';
import CodeMirror from '@uiw/react-codemirror';
import { oneDark } from '@codemirror/theme-one-dark';
import type { ApiRequest, RequestBody } from '../../../../shared/types';
Expand All @@ -10,8 +10,12 @@ import { varCompletionExtension, varHoverTooltipExtension } from './atCompletion
import { jsonWithComments, xmlWithComments, commentKeymap } from './commentKeymap';
import { useVarNames } from '../../hooks/useVarNames';
import { useVarValues } from '../../hooks/useVarValues';
import { GraphQLEditor } from './GraphQLEditor';
import { SoapEditor } from './SoapEditor';

// GraphQL (graphql + cm6-graphql) and SOAP (WSDL/XML) editors are heavy and only
// used for those body modes, so they load on demand to keep startup lean.
const GraphQLEditor = lazy(() => import('./GraphQLEditor').then(m => ({ default: m.GraphQLEditor })));
const SoapEditor = lazy(() => import('./SoapEditor').then(m => ({ default: m.SoapEditor })));
const EditorFallback = <div className="p-4 text-xs text-surface-500">Loading editor…</div>;

type BodyMode = RequestBody['mode']

Expand All @@ -36,7 +40,7 @@ export function BodyTab({ request, onChange }: { request: ApiRequest; onChange:
if (isSoap) {
return (
<div className="flex flex-col h-full min-h-0">
<SoapEditor request={request} onChange={onChange} />
<Suspense fallback={EditorFallback}><SoapEditor request={request} onChange={onChange} /></Suspense>
</div>
);
}
Expand Down Expand Up @@ -126,13 +130,13 @@ export function BodyTab({ request, onChange }: { request: ApiRequest; onChange:

{mode === 'graphql' && (
<div className="flex-1 min-h-0">
<GraphQLEditor request={request} onChange={onChange} />
<Suspense fallback={EditorFallback}><GraphQLEditor request={request} onChange={onChange} /></Suspense>
</div>
)}

{mode === 'soap' && (
<div className="flex-1 min-h-0">
<SoapEditor request={request} onChange={onChange} />
<Suspense fallback={EditorFallback}><SoapEditor request={request} onChange={onChange} /></Suspense>
</div>
)}
</div>
Expand Down
Loading
Loading