@testsmith/api-spector 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13460,10 +13460,12 @@ const createRecorderSlice = (set2) => ({
13460
13460
  });
13461
13461
  const createContractSlice = (set2) => ({
13462
13462
  lastContractReport: null,
13463
+ lastContractRunMeta: null,
13463
13464
  contractSnapshots: {},
13464
13465
  activeContractSnapshotRelPath: null,
13465
- setLastContractReport: (r) => set2((s) => {
13466
+ setLastContractReport: (r, meta2) => set2((s) => {
13466
13467
  s.lastContractReport = r;
13468
+ s.lastContractRunMeta = r ? meta2 ?? null : null;
13467
13469
  }),
13468
13470
  loadContractSnapshot: (relPath, snapshot) => set2((s) => {
13469
13471
  s.contractSnapshots[relPath] = snapshot;
@@ -14242,7 +14244,7 @@ const useStore = create()(
14242
14244
  })
14243
14245
  }))
14244
14246
  );
14245
- const { electron: electron$q } = window;
14247
+ const { electron: electron$r } = window;
14246
14248
  function useAutoSave() {
14247
14249
  const collections = useStore((s) => s.collections);
14248
14250
  useStore((s) => s.environments);
@@ -14258,7 +14260,7 @@ function useAutoSave() {
14258
14260
  for (const { relPath, data, dirty } of dirtyCollections) {
14259
14261
  if (!dirty) continue;
14260
14262
  try {
14261
- await electron$q.saveCollection(relPath, data);
14263
+ await electron$r.saveCollection(relPath, data);
14262
14264
  markCollectionClean(data.id);
14263
14265
  } catch (e) {
14264
14266
  console.error("Auto-save failed for", relPath, e);
@@ -14274,7 +14276,7 @@ function useAutoSave() {
14274
14276
  if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
14275
14277
  wsTimerRef.current = setTimeout(async () => {
14276
14278
  try {
14277
- await electron$q.saveWorkspace(workspace);
14279
+ await electron$r.saveWorkspace(workspace);
14278
14280
  } catch {
14279
14281
  }
14280
14282
  }, 300);
@@ -14283,7 +14285,7 @@ function useAutoSave() {
14283
14285
  };
14284
14286
  }, [workspace]);
14285
14287
  }
14286
- const { electron: electron$p } = window;
14288
+ const { electron: electron$q } = window;
14287
14289
  function useWorkspaceLoader() {
14288
14290
  const loadCollection = useStore((s) => s.loadCollection);
14289
14291
  const loadEnvironment = useStore((s) => s.loadEnvironment);
@@ -14307,33 +14309,45 @@ function useWorkspaceLoader() {
14307
14309
  if (typeof ws2.settings?.zoom === "number") setZoom(ws2.settings.zoom);
14308
14310
  for (const colPath of ws2.collections) {
14309
14311
  try {
14310
- const col = await electron$p.loadCollection(colPath);
14312
+ const col = await electron$q.loadCollection(colPath);
14311
14313
  loadCollection(colPath, col);
14312
14314
  } catch {
14313
14315
  }
14314
14316
  }
14315
14317
  for (const envPath of ws2.environments) {
14316
14318
  try {
14317
- const env = await electron$p.loadEnvironment(envPath);
14319
+ const env = await electron$q.loadEnvironment(envPath);
14318
14320
  loadEnvironment(envPath, env);
14319
14321
  } catch {
14320
14322
  }
14321
14323
  }
14324
+ {
14325
+ const state = useStore.getState();
14326
+ const activeId = state.activeEnvironmentId;
14327
+ const activeIsValid = activeId !== null && Boolean(state.environments[activeId]);
14328
+ const defaultName = ws2.settings?.defaultEnvironment;
14329
+ if (!activeIsValid && defaultName) {
14330
+ const match = Object.values(state.environments).find(
14331
+ (e) => e.data.name.toLowerCase() === defaultName.toLowerCase()
14332
+ );
14333
+ if (match) state.setActiveEnvironment(match.data.id);
14334
+ }
14335
+ }
14322
14336
  for (const relPath of ws2.mocks ?? []) {
14323
14337
  try {
14324
- const mockData = await electron$p.loadMock(relPath);
14338
+ const mockData = await electron$q.loadMock(relPath);
14325
14339
  loadMock(relPath, mockData);
14326
14340
  } catch {
14327
14341
  }
14328
14342
  }
14329
14343
  try {
14330
- const snapshots = await electron$p.listContractSnapshots(ws2.contracts ?? []);
14344
+ const snapshots = await electron$q.listContractSnapshots(ws2.contracts ?? []);
14331
14345
  for (const { relPath, snapshot } of snapshots) loadContractSnapshot(relPath, snapshot);
14332
14346
  } catch {
14333
14347
  }
14334
14348
  if (ws2.collections.length > 0) {
14335
14349
  try {
14336
- const firstCol = await electron$p.loadCollection(ws2.collections[0]);
14350
+ const firstCol = await electron$q.loadCollection(ws2.collections[0]);
14337
14351
  setActiveCollection(firstCol.id);
14338
14352
  } catch {
14339
14353
  }
@@ -14341,6 +14355,40 @@ function useWorkspaceLoader() {
14341
14355
  }, [loadCollection, loadEnvironment, loadMock, loadContractSnapshot, setActiveCollection, setTheme, setZoom]);
14342
14356
  return { applyWorkspace };
14343
14357
  }
14358
+ function resolveEnvironmentChain(env, all) {
14359
+ if (!env.extends) return env;
14360
+ const chain = [env];
14361
+ const seen = /* @__PURE__ */ new Set([env.name]);
14362
+ let parentName = env.extends;
14363
+ while (parentName && !seen.has(parentName)) {
14364
+ const parent = all.find((e) => e.name === parentName);
14365
+ if (!parent) break;
14366
+ chain.push(parent);
14367
+ seen.add(parent.name);
14368
+ parentName = parent.extends;
14369
+ }
14370
+ if (chain.length === 1) return env;
14371
+ const merged = /* @__PURE__ */ new Map();
14372
+ for (const link of [...chain].reverse()) {
14373
+ for (const v of link.variables) merged.set(v.key, v);
14374
+ }
14375
+ return { ...env, variables: [...merged.values()] };
14376
+ }
14377
+ function resolveEnvironmentById(environments, id2) {
14378
+ if (!id2) return null;
14379
+ const env = environments[id2]?.data;
14380
+ if (!env) return null;
14381
+ const all = Object.values(environments).map((e) => e.data);
14382
+ return resolveEnvironmentChain(env, all);
14383
+ }
14384
+ function useActiveEnvironment() {
14385
+ const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
14386
+ const environments = useStore((s) => s.environments);
14387
+ return reactExports.useMemo(
14388
+ () => resolveEnvironmentById(environments, activeEnvironmentId),
14389
+ [environments, activeEnvironmentId]
14390
+ );
14391
+ }
14344
14392
  let rangeFrom = [], rangeTo = [];
14345
14393
  (() => {
14346
14394
  let numbers = "lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map((s) => s ? parseInt(s, 36) : 1);
@@ -34301,7 +34349,7 @@ const DYNAMIC_VAR_NAMES = [
34301
34349
  "$randomHexColor"
34302
34350
  ];
34303
34351
  const DYNAMIC_VAR_INFO = {
34304
- $uuid: "Random UUID v4 generated fresh each send",
34352
+ $uuid: "Random UUID v4 - generated fresh each send",
34305
34353
  $timestamp: "Current Unix timestamp in milliseconds",
34306
34354
  $isoTimestamp: "Current date/time as ISO 8601 string",
34307
34355
  $randomInt: "Random integer between 0 and 1000",
@@ -34341,7 +34389,7 @@ const SP_RESPONSE_MEMBERS = [
34341
34389
  { label: "statusText", type: "property", detail: "string", info: "Status text only" },
34342
34390
  { label: "responseTime", type: "property", detail: "number", info: "Request duration in ms" },
34343
34391
  { label: "responseSize", type: "property", detail: "number", info: "Body size in bytes" },
34344
- { label: "headers", type: "property", info: "Response headers use .get(name) or .toObject()" },
34392
+ { label: "headers", type: "property", info: "Response headers - use .get(name) or .toObject()" },
34345
34393
  { label: "json", type: "function", detail: "()", info: "Parse body as JSON and return it" },
34346
34394
  { label: "text", type: "function", detail: "()", info: "Return body as a raw string" }
34347
34395
  ];
@@ -34727,7 +34775,7 @@ function mockBodyCompletionExtension(pathParamNames = [], varNames = []) {
34727
34775
  const RESPONSE_MEMBERS = [
34728
34776
  { label: "statusCode", type: "property", detail: "number", info: "HTTP status code to send" },
34729
34777
  { label: "body", type: "property", detail: "string", info: "Response body string (overrides template)" },
34730
- { label: "headers", type: "property", detail: "object", info: 'Response headers modify with response.headers["X-Foo"] = "bar"' }
34778
+ { label: "headers", type: "property", detail: "object", info: 'Response headers - modify with response.headers["X-Foo"] = "bar"' }
34731
34779
  ];
34732
34780
  const REQUEST_SCRIPT_MEMBERS = [
34733
34781
  { label: "params", type: "property", info: "URL path params { id, slug, … }" },
@@ -34785,12 +34833,11 @@ function extractScriptVarNames(script) {
34785
34833
  return names2;
34786
34834
  }
34787
34835
  function useVarNames() {
34788
- const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
34789
34836
  const activeCollectionId = useStore((s) => s.activeCollectionId);
34790
- const environments = useStore((s) => s.environments);
34791
34837
  const collections = useStore((s) => s.collections);
34792
34838
  const globals = useStore((s) => s.globals);
34793
34839
  const sessionVars = useStore((s) => s.sessionVars);
34840
+ const activeEnv = useActiveEnvironment();
34794
34841
  return reactExports.useMemo(() => {
34795
34842
  const names2 = /* @__PURE__ */ new Set();
34796
34843
  Object.keys(globals).forEach((k) => names2.add(k));
@@ -34805,41 +34852,35 @@ function useVarNames() {
34805
34852
  }
34806
34853
  }
34807
34854
  }
34808
- if (activeEnvironmentId) {
34809
- const envVars = environments[activeEnvironmentId]?.data.variables ?? [];
34810
- envVars.filter((v) => v.enabled && v.key).forEach((v) => names2.add(v.key));
34811
- }
34855
+ const envVars = activeEnv?.variables ?? [];
34856
+ envVars.filter((v) => v.enabled && v.key).forEach((v) => names2.add(v.key));
34812
34857
  return [...DYNAMIC_VAR_NAMES, ...Array.from(names2).sort()];
34813
34858
  }, [
34814
- activeEnvironmentId,
34859
+ activeEnv,
34815
34860
  activeCollectionId,
34816
- environments,
34817
34861
  collections,
34818
34862
  globals,
34819
34863
  sessionVars
34820
34864
  ]);
34821
34865
  }
34822
34866
  function useVarValues() {
34823
- const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
34824
34867
  const activeCollectionId = useStore((s) => s.activeCollectionId);
34825
- const environments = useStore((s) => s.environments);
34826
34868
  const collections = useStore((s) => s.collections);
34827
34869
  const globals = useStore((s) => s.globals);
34870
+ const activeEnv = useActiveEnvironment();
34828
34871
  const result = { ...globals };
34829
34872
  if (activeCollectionId) {
34830
34873
  const colVars = collections[activeCollectionId]?.data.collectionVariables ?? {};
34831
34874
  Object.assign(result, colVars);
34832
34875
  }
34833
- if (activeEnvironmentId) {
34834
- for (const v of environments[activeEnvironmentId]?.data.variables ?? []) {
34835
- if (!v.enabled || !v.key) continue;
34836
- if (v.secret && v.secretEncrypted) {
34837
- result[v.key] = "••••••••";
34838
- } else if (v.envRef) {
34839
- result[v.key] = `$${v.envRef}`;
34840
- } else {
34841
- result[v.key] = v.value;
34842
- }
34876
+ for (const v of activeEnv?.variables ?? []) {
34877
+ if (!v.enabled || !v.key) continue;
34878
+ if (v.secret && v.secretEncrypted) {
34879
+ result[v.key] = "••••••••";
34880
+ } else if (v.envRef) {
34881
+ result[v.key] = `$${v.envRef}`;
34882
+ } else {
34883
+ result[v.key] = v.value;
34843
34884
  }
34844
34885
  }
34845
34886
  return result;
@@ -35181,7 +35222,7 @@ function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "V
35181
35222
  {
35182
35223
  value: row.paramType ?? "query",
35183
35224
  onChange: (e) => update(idx, { paramType: e.target.value }),
35184
- title: (row.paramType ?? "query") === "path" ? "Path variable substituted into the URL via {{name}}" : "Query string parameter appended as ?key=value",
35225
+ title: (row.paramType ?? "query") === "path" ? "Path variable - substituted into the URL via {{name}}" : "Query string parameter - appended as ?key=value",
35185
35226
  className: `flex-shrink-0 text-[10px] bg-surface-800 border border-surface-700 rounded px-1.5 py-1 focus:outline-none focus:border-blue-500 font-mono ${(row.paramType ?? "query") === "path" ? "text-violet-400" : "text-surface-400"}`,
35186
35227
  children: [
35187
35228
  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "query", children: "query" }),
@@ -35624,7 +35665,7 @@ function BearerPanel({
35624
35665
  "Token",
35625
35666
  " ",
35626
35667
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 text-[10px]", children: [
35627
- " supports ",
35668
+ "- supports ",
35628
35669
  "{{variables}}"
35629
35670
  ] })
35630
35671
  ] }),
@@ -35707,7 +35748,7 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
35707
35748
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold", children: "Folder settings" }),
35708
35749
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-0.5", children: [
35709
35750
  folder.name,
35710
- " auth and headers inherited by all requests in this folder"
35751
+ " - auth and headers inherited by all requests in this folder"
35711
35752
  ] })
35712
35753
  ] }),
35713
35754
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
@@ -35800,7 +35841,7 @@ function CollectionSettingsModal({ collection, onClose }) {
35800
35841
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold", children: "Collection settings" }),
35801
35842
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-0.5", children: [
35802
35843
  collection.name,
35803
- " auth and headers inherited by all requests in this collection"
35844
+ " - auth and headers inherited by all requests in this collection"
35804
35845
  ] })
35805
35846
  ] }),
35806
35847
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
@@ -35907,7 +35948,7 @@ function CollectionSettingsModal({ collection, onClose }) {
35907
35948
  }
35908
35949
  );
35909
35950
  }
35910
- const { electron: electron$o } = window;
35951
+ const { electron: electron$p } = window;
35911
35952
  function normalisePath(url) {
35912
35953
  let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
35913
35954
  if (!path.startsWith("/")) path = "/" + path;
@@ -35994,7 +36035,7 @@ function SchemaSyncModal({
35994
36035
  setLoading(true);
35995
36036
  setError(null);
35996
36037
  try {
35997
- const entries = await electron$o.extractOpenApiSchemas();
36038
+ const entries = await electron$p.extractOpenApiSchemas();
35998
36039
  if (!entries) {
35999
36040
  setLoading(false);
36000
36041
  return;
@@ -36013,7 +36054,7 @@ function SchemaSyncModal({
36013
36054
  setLoading(true);
36014
36055
  setError(null);
36015
36056
  try {
36016
- const entries = await electron$o.extractOpenApiSchemasFromUrl(trimmed);
36057
+ const entries = await electron$p.extractOpenApiSchemasFromUrl(trimmed);
36017
36058
  setSpecEntries(entries);
36018
36059
  autoSelectChanged(entries);
36019
36060
  } catch (err) {
@@ -36046,7 +36087,7 @@ function SchemaSyncModal({
36046
36087
  }
36047
36088
  const entry = useStore.getState().collections[collectionId];
36048
36089
  if (entry) {
36049
- await electron$o.saveCollection(entry.relPath, entry.data);
36090
+ await electron$p.saveCollection(entry.relPath, entry.data);
36050
36091
  markCollectionClean(collectionId);
36051
36092
  }
36052
36093
  onClose();
@@ -36065,7 +36106,7 @@ function SchemaSyncModal({
36065
36106
  children: [
36066
36107
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
36067
36108
  /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-sm font-semibold text-surface-100", children: [
36068
- "Sync schemas ",
36109
+ "Sync schemas - ",
36069
36110
  scopeLabel
36070
36111
  ] }),
36071
36112
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-500 hover:text-surface-300 text-lg leading-none", children: "×" })
@@ -36184,7 +36225,7 @@ function SchemaSyncModal({
36184
36225
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold text-surface-100", children: "Sync schemas from OpenAPI" }),
36185
36226
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-500 hover:text-surface-300 text-lg leading-none", children: "×" })
36186
36227
  ] }),
36187
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: "Load an OpenAPI spec to update response schemas on existing requests. Matching uses HTTP method + URL path. Only schemas are touched URLs, params, headers, auth, and scripts are preserved." }),
36228
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: "Load an OpenAPI spec to update response schemas on existing requests. Matching uses HTTP method + URL path. Only schemas are touched - URLs, params, headers, auth, and scripts are preserved." }),
36188
36229
  /* @__PURE__ */ jsxRuntimeExports.jsx(
36189
36230
  "button",
36190
36231
  {
@@ -36260,7 +36301,7 @@ const METHOD_COLORS$1 = {
36260
36301
  DELETE: "text-red-400",
36261
36302
  HEAD: "text-purple-400",
36262
36303
  OPTIONS: "text-surface-400",
36263
- QUERY: "text-teal-400",
36304
+ QUERY: "text-fuchsia-400",
36264
36305
  ANY: "text-surface-400"
36265
36306
  };
36266
36307
  function getMethodColor(method) {
@@ -54698,9 +54739,8 @@ function GraphQLEditor({ request, onChange }) {
54698
54739
  () => request.body.graphql ?? EMPTY_GQL,
54699
54740
  [request.body.graphql]
54700
54741
  );
54701
- const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
54702
54742
  const activeCollectionId = useStore((s) => s.activeCollectionId);
54703
- const envData = useStore((s) => activeEnvironmentId ? s.environments[activeEnvironmentId]?.data : null);
54743
+ const envData = useActiveEnvironment();
54704
54744
  const colVarsData = useStore((s) => activeCollectionId ? s.collections[activeCollectionId]?.data.collectionVariables : null);
54705
54745
  const globals = useStore((s) => s.globals);
54706
54746
  const hookVars = reactExports.useMemo(() => {
@@ -55303,7 +55343,7 @@ function withContentType(headers, value) {
55303
55343
  if (idx === -1) return [...headers, next];
55304
55344
  return headers.map((h, i) => i === idx ? { ...h, value, enabled: true } : h);
55305
55345
  }
55306
- const { electron: electron$n } = window;
55346
+ const { electron: electron$o } = window;
55307
55347
  function ParamTree({ params, depth = 0 }) {
55308
55348
  if (params.length === 0) {
55309
55349
  return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 italic", children: "No parameters declared in WSDL." });
@@ -55329,7 +55369,7 @@ function SoapEditor({ request, onChange }) {
55329
55369
  let cancelled = false;
55330
55370
  (async () => {
55331
55371
  try {
55332
- const result = await electron$n.wsdlFetch(url);
55372
+ const result = await electron$o.wsdlFetch(url);
55333
55373
  if (cancelled) return;
55334
55374
  setOperations(result.operations);
55335
55375
  setEndpoints(result.endpoints);
@@ -55366,7 +55406,7 @@ function SoapEditor({ request, onChange }) {
55366
55406
  setFetching(true);
55367
55407
  setFetchError(null);
55368
55408
  try {
55369
- const result = await electron$n.wsdlFetch(soap.wsdlUrl.trim());
55409
+ const result = await electron$o.wsdlFetch(soap.wsdlUrl.trim());
55370
55410
  setOperations(result.operations);
55371
55411
  setEndpoints(result.endpoints);
55372
55412
  setTargetNs(result.targetNamespace);
@@ -55507,7 +55547,7 @@ function SoapEditor({ request, onChange }) {
55507
55547
  "SOAPAction: ",
55508
55548
  soap.soapAction
55509
55549
  ] }),
55510
- !soap.operationName && !soap.soapAction && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500", children: soap.wsdlUrl?.trim() ? "WSDL not loaded the saved envelope below is still sent on Send." : "No WSDL hand-crafted SOAP envelope." })
55550
+ !soap.operationName && !soap.soapAction && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500", children: soap.wsdlUrl?.trim() ? "WSDL not loaded - the saved envelope below is still sent on Send." : "No WSDL - hand-crafted SOAP envelope." })
55511
55551
  ] }),
55512
55552
  soap.wsdlUrl?.trim() && /* @__PURE__ */ jsxRuntimeExports.jsx(
55513
55553
  "button",
@@ -55537,7 +55577,7 @@ function SoapEditor({ request, onChange }) {
55537
55577
  /* @__PURE__ */ jsxRuntimeExports.jsx("em", { children: "Fetch WSDL" }),
55538
55578
  "."
55539
55579
  ] }),
55540
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-sm", children: "The endpoint, SOAP version, Content-Type header, and per-operation envelope are derived from the WSDL you only pick the operation and fill the parameters." })
55580
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-sm", children: "The endpoint, SOAP version, Content-Type header, and per-operation envelope are derived from the WSDL - you only pick the operation and fill the parameters." })
55541
55581
  ] })
55542
55582
  ] });
55543
55583
  }
@@ -55636,7 +55676,7 @@ function BodyTab({ request, onChange }) {
55636
55676
  mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request, onChange }) })
55637
55677
  ] });
55638
55678
  }
55639
- const { electron: electron$m } = window;
55679
+ const { electron: electron$n } = window;
55640
55680
  const AUTH_TYPES = ["none", "bearer", "basic", "digest", "ntlm", "apikey", "oauth2"];
55641
55681
  function AuthTab({ request, onChange }) {
55642
55682
  const auth = request.auth;
@@ -55650,7 +55690,7 @@ function AuthTab({ request, onChange }) {
55650
55690
  }
55651
55691
  async function saveSecret(ref2) {
55652
55692
  if (!secretValue || !ref2) return;
55653
- await electron$m.setSecret(ref2, secretValue);
55693
+ await electron$n.setSecret(ref2, secretValue);
55654
55694
  setSaved(true);
55655
55695
  setSecretValue("");
55656
55696
  setTimeout(() => setSaved(false), 2e3);
@@ -55662,7 +55702,7 @@ function AuthTab({ request, onChange }) {
55662
55702
  setOauth2Error("");
55663
55703
  try {
55664
55704
  const vars = {};
55665
- const result = await electron$m.oauth2StartFlow(oauth2Auth, vars);
55705
+ const result = await electron$n.oauth2StartFlow(oauth2Auth, vars);
55666
55706
  setAuth({
55667
55707
  oauth2CachedToken: result.accessToken,
55668
55708
  oauth2TokenExpiry: result.expiresAt
@@ -55680,7 +55720,7 @@ function AuthTab({ request, onChange }) {
55680
55720
  setOauth2Status("fetching");
55681
55721
  setOauth2Error("");
55682
55722
  try {
55683
- const result = await electron$m.oauth2RefreshToken(oauth2Auth, {}, oauth2RefreshToken);
55723
+ const result = await electron$n.oauth2RefreshToken(oauth2Auth, {}, oauth2RefreshToken);
55684
55724
  setAuth({
55685
55725
  oauth2CachedToken: result.accessToken,
55686
55726
  oauth2TokenExpiry: result.expiresAt
@@ -56453,7 +56493,7 @@ const SNIPPET_GROUPS = [
56453
56493
  ]
56454
56494
  },
56455
56495
  {
56456
- group: "Variables Get",
56496
+ group: "Variables - Get",
56457
56497
  items: [
56458
56498
  {
56459
56499
  label: "Get variable",
@@ -56474,7 +56514,7 @@ const SNIPPET_GROUPS = [
56474
56514
  ]
56475
56515
  },
56476
56516
  {
56477
- group: "Variables Set",
56517
+ group: "Variables - Set",
56478
56518
  items: [
56479
56519
  {
56480
56520
  label: "Save token from response (use in next requests)",
@@ -56500,7 +56540,7 @@ sp.collectionVariables.set("token", json.access_token);`
56500
56540
  ]
56501
56541
  },
56502
56542
  {
56503
- group: "Variables Clear",
56543
+ group: "Variables - Clear",
56504
56544
  items: [
56505
56545
  {
56506
56546
  label: "Clear variable",
@@ -62943,7 +62983,7 @@ function SchemaTab({ request, onChange }) {
62943
62983
  )
62944
62984
  ] })
62945
62985
  ] }),
62946
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600", children: "Standalone schema for ad-hoc validation. Independent of the contract edits here don't affect it." }),
62986
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600", children: "Standalone schema for ad-hoc validation. Independent of the contract - edits here don't affect it." }),
62947
62987
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-surface-700 rounded overflow-hidden", children: [
62948
62988
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end px-2 py-0.5 bg-surface-800/50 border-b border-surface-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
62949
62989
  "button",
@@ -62974,9 +63014,9 @@ function SchemaTab({ request, onChange }) {
62974
63014
  )
62975
63015
  ] }),
62976
63016
  error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-red-400 bg-red-950/50 border border-red-800 rounded px-3 py-2", children: error2 }),
62977
- result && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `rounded border px-3 py-2 text-xs ${result.valid ? "bg-emerald-900/20 border-emerald-700" : "bg-red-900/20 border-red-700"}`, children: result.valid ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400 font-semibold", children: "Valid response matches the schema." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1.5", children: [
63017
+ result && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `rounded border px-3 py-2 text-xs ${result.valid ? "bg-emerald-900/20 border-emerald-700" : "bg-red-900/20 border-red-700"}`, children: result.valid ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400 font-semibold", children: "Valid: response matches the schema." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1.5", children: [
62978
63018
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-red-400 font-semibold", children: [
62979
- "Invalid ",
63019
+ "Invalid: ",
62980
63020
  result.errors.length,
62981
63021
  " error",
62982
63022
  result.errors.length !== 1 ? "s" : ""
@@ -62988,7 +63028,7 @@ function SchemaTab({ request, onChange }) {
62988
63028
  ] }) })
62989
63029
  ] });
62990
63030
  }
62991
- const { electron: electron$l } = window;
63031
+ const { electron: electron$m } = window;
62992
63032
  const EMPTY = { statusCode: 200, headers: [], bodySchema: "" };
62993
63033
  function ContractTab({ request, onChange }) {
62994
63034
  const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
@@ -63002,7 +63042,7 @@ function ContractTab({ request, onChange }) {
63002
63042
  if (!lastResponse?.body) return;
63003
63043
  setInferring(true);
63004
63044
  try {
63005
- const schema = await electron$l.inferContractSchema(lastResponse.body);
63045
+ const schema = await electron$m.inferContractSchema(lastResponse.body);
63006
63046
  if (schema) update({ bodySchema: schema });
63007
63047
  } finally {
63008
63048
  setInferring(false);
@@ -63023,7 +63063,7 @@ function ContractTab({ request, onChange }) {
63023
63063
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4 h-full min-h-0", children: [
63024
63064
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-2 rounded-lg text-xs border ${hasContract ? "bg-blue-950/40 border-blue-700 text-blue-300" : "bg-surface-800 border-surface-700 text-surface-500"}`, children: [
63025
63065
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-2 h-2 rounded-full ${hasContract ? "bg-blue-400" : "bg-surface-600"}` }),
63026
- hasContract ? "Contract defined will be verified in Contract panel" : "No contract defined yet"
63066
+ hasContract ? "Contract defined - will be verified in Contract panel" : "No contract defined yet"
63027
63067
  ] }),
63028
63068
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
63029
63069
  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1.5", children: "Expected Status Code" }),
@@ -63128,7 +63168,7 @@ function ContractTab({ request, onChange }) {
63128
63168
  ] })
63129
63169
  ] });
63130
63170
  }
63131
- const { electron: electron$k } = window;
63171
+ const { electron: electron$l } = window;
63132
63172
  function formatTime$1(ts) {
63133
63173
  const d = new Date(ts);
63134
63174
  const hh = String(d.getHours()).padStart(2, "0");
@@ -63148,14 +63188,14 @@ function WebSocketPanel({ request }) {
63148
63188
  const [sendText, setSendText] = reactExports.useState("");
63149
63189
  const logEndRef = reactExports.useRef(null);
63150
63190
  reactExports.useEffect(() => {
63151
- electron$k.onWsMessage(({ requestId, message }) => {
63191
+ electron$l.onWsMessage(({ requestId, message }) => {
63152
63192
  addWsMessage(requestId, message);
63153
63193
  });
63154
- electron$k.onWsStatus(({ requestId, status, error: error2 }) => {
63194
+ electron$l.onWsStatus(({ requestId, status, error: error2 }) => {
63155
63195
  setWsStatus(requestId, status, error2);
63156
63196
  });
63157
63197
  return () => {
63158
- electron$k.offWsEvents();
63198
+ electron$l.offWsEvents();
63159
63199
  };
63160
63200
  }, [addWsMessage, setWsStatus]);
63161
63201
  reactExports.useEffect(() => {
@@ -63168,19 +63208,19 @@ function WebSocketPanel({ request }) {
63168
63208
  if (h.enabled && h.key) headers[h.key] = h.value;
63169
63209
  }
63170
63210
  try {
63171
- await electron$k.wsConnect(request.id, request.url, headers);
63211
+ await electron$l.wsConnect(request.id, request.url, headers);
63172
63212
  } catch (err) {
63173
63213
  setWsStatus(request.id, "error", err instanceof Error ? err.message : String(err));
63174
63214
  }
63175
63215
  }
63176
63216
  async function disconnect() {
63177
- await electron$k.wsDisconnect(request.id);
63217
+ await electron$l.wsDisconnect(request.id);
63178
63218
  }
63179
63219
  async function sendMessage() {
63180
63220
  const text = sendText.trim();
63181
63221
  if (!text || !isConnected) return;
63182
63222
  try {
63183
- await electron$k.wsSend(request.id, text);
63223
+ await electron$l.wsSend(request.id, text);
63184
63224
  const msg = {
63185
63225
  id: crypto.randomUUID(),
63186
63226
  direction: "sent",
@@ -63283,6 +63323,393 @@ function WebSocketPanel({ request }) {
63283
63323
  ] })
63284
63324
  ] });
63285
63325
  }
63326
+ function useToast(durationMs = 3e3) {
63327
+ const [toast, setToast] = reactExports.useState(null);
63328
+ const timer = reactExports.useRef(null);
63329
+ function show(msg, ok) {
63330
+ if (timer.current) clearTimeout(timer.current);
63331
+ setToast({ msg, ok });
63332
+ timer.current = setTimeout(() => setToast(null), durationMs);
63333
+ }
63334
+ return { toast, show };
63335
+ }
63336
+ function Toast({ toast }) {
63337
+ if (!toast) return null;
63338
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mx-3 mb-2 px-2 py-1.5 rounded text-[11px] flex-shrink-0 ${toast.ok ? "bg-emerald-900/50 text-emerald-300 border border-emerald-800/50" : "bg-red-900/50 text-red-300 border border-red-800/50"}`, children: toast.msg });
63339
+ }
63340
+ function statusColor$3(code2) {
63341
+ const d = String(code2)[0];
63342
+ return d === "2" ? "text-emerald-400" : d === "3" ? "text-amber-400" : "text-red-400";
63343
+ }
63344
+ const ORACLE_META = {
63345
+ "never-5xx": { label: "Server crash", badge: "bg-red-900/50 text-red-400", border: "border-red-600" },
63346
+ "accepted-invalid": { label: "Accepted invalid", badge: "bg-amber-900/50 text-amber-400", border: "border-amber-500" },
63347
+ "undocumented-status": { label: "Undocumented status", badge: "bg-blue-900/50 text-blue-400", border: "border-blue-500" },
63348
+ "response-schema": { label: "Response schema", badge: "bg-orange-900/50 text-orange-400", border: "border-orange-500" }
63349
+ };
63350
+ function FindingRow({ finding, onCopy }) {
63351
+ const [open, setOpen] = reactExports.useState(false);
63352
+ const meta2 = ORACLE_META[finding.oracle];
63353
+ const req = finding.request;
63354
+ const sentText = `${req.method} ${req.url}
63355
+ ` + Object.entries(req.headers).map(([k, v]) => `${k}: ${v}`).join("\n") + (req.body ? `
63356
+
63357
+ ${req.body}` : "");
63358
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex flex-col gap-1.5 px-4 py-2.5 border-l-2 ${meta2.border} bg-red-950/20 rounded-r`, children: [
63359
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
63360
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-bold px-1.5 py-0.5 rounded uppercase tracking-wide ${meta2.badge}`, children: meta2.label }),
63361
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-mono font-bold ${statusColor$3(finding.status)}`, children: finding.status === 0 ? "ERR" : finding.status }),
63362
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-500 bg-surface-800 px-1.5 py-0.5 rounded", children: finding.mutation.target }),
63363
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-400 bg-surface-800 px-1.5 py-0.5 rounded", children: finding.mutation.kind })
63364
+ ] }),
63365
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-200", children: finding.message }),
63366
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-surface-400", children: finding.mutation.description }),
63367
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
63368
+ "button",
63369
+ {
63370
+ onClick: () => setOpen((v) => !v),
63371
+ className: "text-[10px] text-surface-500 hover:text-surface-300 transition-colors self-start",
63372
+ children: open ? "▲ Hide sent request" : "▼ Show sent request"
63373
+ }
63374
+ ),
63375
+ open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 mt-0.5", children: [
63376
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-surface-800 bg-surface-900 overflow-hidden", children: [
63377
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-2.5 py-1.5 border-b border-surface-800", children: [
63378
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[11px] font-bold font-mono ${getMethodColor(req.method)}`, children: req.method }),
63379
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-mono text-surface-400 truncate flex-1", children: req.url }),
63380
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
63381
+ "button",
63382
+ {
63383
+ onClick: () => onCopy(sentText),
63384
+ className: "text-[10px] text-surface-500 hover:text-surface-200 transition-colors shrink-0",
63385
+ title: "Copy the sent request",
63386
+ children: "Copy"
63387
+ }
63388
+ )
63389
+ ] }),
63390
+ req.body && /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono text-surface-300 px-2.5 py-2 overflow-x-auto whitespace-pre-wrap break-words", children: req.body })
63391
+ ] }),
63392
+ finding.responseSample && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-surface-800 bg-surface-900 overflow-hidden", children: [
63393
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-2.5 py-1.5 border-b border-surface-800 text-[10px] uppercase tracking-wider text-surface-500 font-medium", children: "Response sample" }),
63394
+ /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono text-surface-300 px-2.5 py-2 overflow-x-auto whitespace-pre-wrap break-words", children: finding.responseSample })
63395
+ ] })
63396
+ ] })
63397
+ ] });
63398
+ }
63399
+ function OperationCard({ result, onCopy }) {
63400
+ const [open, setOpen] = reactExports.useState(true);
63401
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-red-800/60 overflow-hidden", children: [
63402
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
63403
+ "button",
63404
+ {
63405
+ onClick: () => setOpen((v) => !v),
63406
+ className: "w-full flex items-center gap-3 px-4 py-3 text-left bg-red-950/30 hover:bg-red-950/50 transition-colors",
63407
+ children: [
63408
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${getMethodColor(result.method)}`, children: result.method }),
63409
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-sm text-white truncate", children: result.requestName }),
63410
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden lg:block text-[11px] text-surface-500 font-mono truncate max-w-[260px]", children: result.url }),
63411
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 text-[10px] bg-red-900/50 text-red-300 rounded px-1.5 py-0.5 font-medium", children: [
63412
+ result.findings.length,
63413
+ " ",
63414
+ result.findings.length === 1 ? "finding" : "findings"
63415
+ ] }),
63416
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 text-[11px] text-surface-500", children: [
63417
+ result.cases,
63418
+ " cases"
63419
+ ] }),
63420
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-600 text-xs ml-1", children: open ? "▲" : "▼" })
63421
+ ]
63422
+ }
63423
+ ),
63424
+ open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3 bg-surface-900 border-t border-surface-800 flex flex-col gap-2", children: result.findings.map((f, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(FindingRow, { finding: f, onCopy }, i)) })
63425
+ ] });
63426
+ }
63427
+ function statusTone(status, finding) {
63428
+ if (finding) return "text-red-400";
63429
+ if (status === 0) return "text-surface-500";
63430
+ if (status >= 200 && status < 300) return "text-emerald-400";
63431
+ if (status >= 400) return "text-amber-400";
63432
+ return "text-surface-300";
63433
+ }
63434
+ function TraceRow({ trace, onCopy }) {
63435
+ const [open, setOpen] = reactExports.useState(false);
63436
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-surface-800 first:border-t-0", children: [
63437
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
63438
+ "button",
63439
+ {
63440
+ onClick: () => setOpen((v) => !v),
63441
+ className: "w-full flex items-center gap-3 px-3 py-1.5 text-left hover:bg-surface-800/60 transition-colors font-mono text-[11px]",
63442
+ children: [
63443
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-600 w-3", children: open ? "▾" : "▸" }),
63444
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 font-bold w-9 ${statusTone(trace.status, trace.finding)}`, children: trace.status || "ERR" }),
63445
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-300", children: trace.mutation.target }),
63446
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500 truncate", children: trace.mutation.kind }),
63447
+ trace.finding && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-auto shrink-0 text-[10px] bg-red-900/50 text-red-300 rounded px-1.5 py-0.5", children: "finding" })
63448
+ ]
63449
+ }
63450
+ ),
63451
+ open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 pb-3 pt-1 flex flex-col gap-2 bg-surface-950/40", children: [
63452
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-surface-400", children: trace.mutation.description }),
63453
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
63454
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
63455
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Request" }),
63456
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
63457
+ "button",
63458
+ {
63459
+ onClick: () => onCopy(`${trace.request.method} ${trace.request.url}
63460
+
63461
+ ${trace.request.body ?? ""}`),
63462
+ className: "text-[10px] text-surface-500 hover:text-surface-200 transition-colors",
63463
+ children: "copy"
63464
+ }
63465
+ )
63466
+ ] }),
63467
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] font-mono text-surface-500 break-all", children: [
63468
+ trace.request.method,
63469
+ " ",
63470
+ trace.request.url
63471
+ ] }),
63472
+ trace.request.body && /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono text-surface-300 bg-surface-900 border border-surface-800 rounded px-2.5 py-2 overflow-x-auto whitespace-pre-wrap break-words", children: trace.request.body })
63473
+ ] }),
63474
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
63475
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: [
63476
+ "Response (",
63477
+ trace.status || "no response",
63478
+ ")"
63479
+ ] }),
63480
+ /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono text-surface-300 bg-surface-900 border border-surface-800 rounded px-2.5 py-2 overflow-x-auto whitespace-pre-wrap break-words", children: trace.responseSample || "(empty)" })
63481
+ ] })
63482
+ ] })
63483
+ ] });
63484
+ }
63485
+ function TraceCard({ result, onCopy }) {
63486
+ const [open, setOpen] = reactExports.useState(false);
63487
+ if (!result.trace?.length) return null;
63488
+ const findingCount = result.trace.filter((t2) => t2.finding).length;
63489
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-surface-700 overflow-hidden", children: [
63490
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
63491
+ "button",
63492
+ {
63493
+ onClick: () => setOpen((v) => !v),
63494
+ className: "w-full flex items-center gap-3 px-4 py-2.5 text-left bg-surface-800 hover:bg-surface-700/60 transition-colors",
63495
+ children: [
63496
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${getMethodColor(result.method)}`, children: result.method }),
63497
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-xs text-surface-200 truncate", children: result.requestName }),
63498
+ findingCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[10px] bg-red-900/50 text-red-300 rounded px-1.5 py-0.5", children: findingCount }),
63499
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 text-[11px] text-surface-500", children: [
63500
+ result.trace.length,
63501
+ " cases sent"
63502
+ ] }),
63503
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-600 text-xs", children: open ? "▲" : "▼" })
63504
+ ]
63505
+ }
63506
+ ),
63507
+ open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-surface-900 border-t border-surface-800", children: result.trace.map((t2, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(TraceRow, { trace: t2, onCopy }, i)) })
63508
+ ] });
63509
+ }
63510
+ function FuzzResultsPanel({ report, onClear }) {
63511
+ const { toast, show: showToast } = useToast();
63512
+ async function copy(text) {
63513
+ try {
63514
+ await navigator.clipboard.writeText(text);
63515
+ showToast("Sent request copied to clipboard.", true);
63516
+ } catch (e) {
63517
+ showToast(e instanceof Error ? e.message : "Copy failed.", false);
63518
+ }
63519
+ }
63520
+ const withFindings = report.results.filter((r) => r.findings.length > 0);
63521
+ const clean = report.results.filter((r) => r.findings.length === 0);
63522
+ const clean5xx = report.totalFindings === 0;
63523
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full overflow-hidden", children: [
63524
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-4 px-6 py-3 border-b flex-shrink-0 ${clean5xx ? "bg-emerald-950/30 border-emerald-800/50" : "bg-red-950/30 border-red-800/50"}`, children: [
63525
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-base font-bold ${clean5xx ? "text-emerald-400" : "text-red-400"}`, children: clean5xx ? "✓ No findings" : `✗ ${report.totalFindings} finding${report.totalFindings !== 1 ? "s" : ""}` }),
63526
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm text-surface-400", children: [
63527
+ report.totalCases,
63528
+ " case",
63529
+ report.totalCases !== 1 ? "s" : "",
63530
+ " across ",
63531
+ report.results.length,
63532
+ " operation",
63533
+ report.results.length !== 1 ? "s" : ""
63534
+ ] }),
63535
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] bg-surface-800 text-surface-400 px-2 py-0.5 rounded font-mono", children: report.inputSource === "spec" ? "spec" : "request body" }),
63536
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] bg-surface-800 text-surface-400 px-2 py-0.5 rounded font-mono", children: [
63537
+ "seed ",
63538
+ report.seed
63539
+ ] }),
63540
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-surface-500 ml-auto", children: [
63541
+ report.durationMs,
63542
+ "ms"
63543
+ ] }),
63544
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
63545
+ "button",
63546
+ {
63547
+ onClick: onClear,
63548
+ className: "text-[11px] text-surface-600 hover:text-surface-300 transition-colors",
63549
+ title: "Clear results",
63550
+ children: "Clear"
63551
+ }
63552
+ )
63553
+ ] }),
63554
+ (report.skippedWrites > 0 || report.skippedNoBody > 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 px-6 py-2 border-b border-surface-800 bg-surface-900/60 flex-shrink-0", children: [
63555
+ report.skippedWrites > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400", children: [
63556
+ report.skippedWrites,
63557
+ " write-method request",
63558
+ report.skippedWrites !== 1 ? "s" : "",
63559
+ " skipped (enable Include write methods)."
63560
+ ] }),
63561
+ report.skippedNoBody > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-surface-500", children: [
63562
+ report.skippedNoBody,
63563
+ " request",
63564
+ report.skippedNoBody !== 1 ? "s" : "",
63565
+ " had no body to fuzz."
63566
+ ] })
63567
+ ] }),
63568
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
63569
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto min-h-0 p-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 max-w-4xl mx-auto", children: [
63570
+ withFindings.map((r) => /* @__PURE__ */ jsxRuntimeExports.jsx(OperationCard, { result: r, onCopy: copy }, r.requestId)),
63571
+ clean.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-surface-700 bg-surface-800 px-4 py-3", children: [
63572
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-emerald-400 font-medium mb-1.5", children: [
63573
+ clean.length,
63574
+ " operation",
63575
+ clean.length !== 1 ? "s" : "",
63576
+ " clean"
63577
+ ] }),
63578
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-0.5", children: clean.map((r) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-[11px]", children: [
63579
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-bold font-mono w-12 shrink-0 ${getMethodColor(r.method)}`, children: r.method }),
63580
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300 truncate", children: r.requestName }),
63581
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-600 ml-auto shrink-0", children: [
63582
+ r.cases,
63583
+ " cases"
63584
+ ] })
63585
+ ] }, r.requestId)) })
63586
+ ] }),
63587
+ report.results.some((r) => r.trace?.length) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
63588
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium mt-2", children: "All cases sent" }),
63589
+ report.results.filter((r) => r.trace?.length).map((r) => /* @__PURE__ */ jsxRuntimeExports.jsx(TraceCard, { result: r, onCopy: copy }, r.requestId))
63590
+ ] }),
63591
+ report.results.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 text-center mt-4", children: "No operations were fuzzed. Check that requests have a body or a matching spec operation." })
63592
+ ] }) })
63593
+ ] });
63594
+ }
63595
+ const { electron: electron$k } = window;
63596
+ const WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
63597
+ function FuzzModal({ request, onClose }) {
63598
+ const environments = useStore((s) => s.environments);
63599
+ const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
63600
+ const activeCollectionId = useStore((s) => s.activeCollectionId);
63601
+ const collections = useStore((s) => s.collections);
63602
+ const snapshots = useStore((s) => s.contractSnapshots);
63603
+ const activeSnapshotRelPath = useStore((s) => s.activeContractSnapshotRelPath);
63604
+ const [cases, setCases] = reactExports.useState(40);
63605
+ const [seed, setSeed] = reactExports.useState(1);
63606
+ const [trace, setTrace] = reactExports.useState(true);
63607
+ const [running, setRunning] = reactExports.useState(false);
63608
+ const [error2, setError] = reactExports.useState(null);
63609
+ const [report, setReport] = reactExports.useState(null);
63610
+ const isWrite = WRITE_METHODS.has(request.method);
63611
+ const snapshotList = Object.entries(snapshots).map(([relPath, s]) => ({ relPath, snapshot: s }));
63612
+ const [snapshotRelPath, setSnapshotRelPath] = reactExports.useState(activeSnapshotRelPath ?? "");
63613
+ async function run() {
63614
+ setRunning(true);
63615
+ setError(null);
63616
+ setReport(null);
63617
+ try {
63618
+ const env = resolveEnvironmentById(environments, activeEnvironmentId);
63619
+ const envVars = env ? Object.fromEntries(env.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])) : {};
63620
+ const collectionVars = activeCollectionId ? collections[activeCollectionId]?.data.collectionVariables ?? {} : {};
63621
+ const result = await electron$k.fuzzContracts({
63622
+ requests: [request],
63623
+ envVars,
63624
+ collectionVars,
63625
+ specSnapshotRelPath: snapshotRelPath || void 0,
63626
+ // No providerBaseUrl: the request is fuzzed against its own URL.
63627
+ casesPerOperation: cases,
63628
+ seed,
63629
+ trace,
63630
+ includeWrites: true
63631
+ // a per-request run is an explicit choice to fuzz this request
63632
+ });
63633
+ setReport(result);
63634
+ } catch (e) {
63635
+ setError(e instanceof Error ? e.message : String(e));
63636
+ } finally {
63637
+ setRunning(false);
63638
+ }
63639
+ }
63640
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
63641
+ Modal,
63642
+ {
63643
+ onClose,
63644
+ title: `Fuzz: ${request.name}`,
63645
+ subtitle: `${request.method} ${request.url}`,
63646
+ panelClassName: "bg-surface-900 border border-surface-800 rounded-lg shadow-2xl w-[720px] flex flex-col max-h-[85vh]",
63647
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col min-h-0 flex-1", children: [
63648
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end gap-3 px-4 py-3 border-b border-surface-800 flex-shrink-0 flex-wrap", children: [
63649
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
63650
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Cases" }),
63651
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
63652
+ "input",
63653
+ {
63654
+ type: "number",
63655
+ min: 1,
63656
+ value: cases,
63657
+ onChange: (e) => setCases(Math.max(1, Number(e.target.value))),
63658
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-20 focus:outline-none focus:border-blue-500"
63659
+ }
63660
+ )
63661
+ ] }),
63662
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
63663
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Seed" }),
63664
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
63665
+ "input",
63666
+ {
63667
+ type: "number",
63668
+ value: seed,
63669
+ onChange: (e) => setSeed(Number(e.target.value)),
63670
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-20 focus:outline-none focus:border-blue-500"
63671
+ }
63672
+ )
63673
+ ] }),
63674
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1 flex-1 min-w-[180px]", children: [
63675
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Spec (optional)" }),
63676
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
63677
+ "select",
63678
+ {
63679
+ value: snapshotRelPath,
63680
+ onChange: (e) => setSnapshotRelPath(e.target.value),
63681
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500",
63682
+ children: [
63683
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Request body (no spec)" }),
63684
+ snapshotList.map(({ relPath, snapshot }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: relPath, children: snapshot.name }, relPath))
63685
+ ]
63686
+ }
63687
+ )
63688
+ ] }),
63689
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1.5 text-[11px] text-surface-400 select-none", children: [
63690
+ /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: trace, onChange: (e) => setTrace(e.target.checked) }),
63691
+ "Record all cases"
63692
+ ] }),
63693
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
63694
+ "button",
63695
+ {
63696
+ onClick: run,
63697
+ disabled: running || !request.url,
63698
+ className: "px-4 py-1.5 bg-blue-600 hover:bg-blue-500 disabled:bg-surface-800 disabled:text-surface-400 rounded text-sm font-medium transition-colors",
63699
+ children: running ? "Fuzzing..." : "Run fuzz"
63700
+ }
63701
+ )
63702
+ ] }),
63703
+ isWrite && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400 px-4 py-2 border-b border-surface-800 flex-shrink-0", children: [
63704
+ request.method,
63705
+ " sends malformed writes. Point this request at a staging environment or a mock, not production."
63706
+ ] }),
63707
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-4 py-2 border-b border-surface-800 flex-shrink-0", children: error2 }),
63708
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: report ? /* @__PURE__ */ jsxRuntimeExports.jsx(FuzzResultsPanel, { report, onClear: () => setReport(null) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-center p-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: running ? "Sending malformed inputs..." : "Generates malformed variants of this request body and flags responses that crash (5xx) or accept invalid input. Pick a pinned spec for richer inputs, or fuzz the request body as-is." }) }) })
63709
+ ] })
63710
+ }
63711
+ );
63712
+ }
63286
63713
  const { electron: electron$j } = window;
63287
63714
  const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"];
63288
63715
  const METHOD_COLORS = {
@@ -63293,7 +63720,7 @@ const METHOD_COLORS = {
63293
63720
  DELETE: "text-red-400",
63294
63721
  HEAD: "text-purple-400",
63295
63722
  OPTIONS: "text-gray-400",
63296
- QUERY: "text-teal-400"
63723
+ QUERY: "text-fuchsia-400"
63297
63724
  };
63298
63725
  function deriveHookStatus(r) {
63299
63726
  if (r.scriptResult.postScriptError) return "error";
@@ -63325,6 +63752,7 @@ function RequestBuilder({ request }) {
63325
63752
  if (activeTabId) setTabRequestTab(activeTabId, t2);
63326
63753
  }
63327
63754
  const [editingName, setEditingName] = reactExports.useState(false);
63755
+ const [showFuzz, setShowFuzz] = reactExports.useState(false);
63328
63756
  const [runHooks, setRunHooks] = reactExports.useState(() => localStorage.getItem("runHooks") !== "false");
63329
63757
  function toggleRunHooks() {
63330
63758
  setRunHooks((prev) => {
@@ -63343,7 +63771,7 @@ function RequestBuilder({ request }) {
63343
63771
  setTabHookResults(activeTabId, null);
63344
63772
  const collectedHookResults = [];
63345
63773
  try {
63346
- const activeEnv = activeEnvironmentId ? environments[activeEnvironmentId]?.data ?? null : null;
63774
+ const activeEnv = resolveEnvironmentById(environments, activeEnvironmentId);
63347
63775
  const sessionVars = useStore.getState().sessionVars;
63348
63776
  const tls = collectionTls ? { ...workspaceSettings?.tls, ...collectionTls } : workspaceSettings?.tls;
63349
63777
  const basePayload = {
@@ -63369,7 +63797,10 @@ function RequestBuilder({ request }) {
63369
63797
  for (const hook of hooks.before) {
63370
63798
  const start = Date.now();
63371
63799
  try {
63372
- const hookEnv = activeEnvironmentId ? useStore.getState().environments[activeEnvironmentId]?.data ?? null : null;
63800
+ const hookEnv = resolveEnvironmentById(
63801
+ useStore.getState().environments,
63802
+ activeEnvironmentId
63803
+ );
63373
63804
  const hookSessionVars = useStore.getState().sessionVars;
63374
63805
  const r = await electron$j.sendRequest({
63375
63806
  ...basePayload,
@@ -63410,7 +63841,10 @@ function RequestBuilder({ request }) {
63410
63841
  });
63411
63842
  }
63412
63843
  }
63413
- const freshEnv = activeEnvironmentId ? useStore.getState().environments[activeEnvironmentId]?.data ?? null : null;
63844
+ const freshEnv = resolveEnvironmentById(
63845
+ useStore.getState().environments,
63846
+ activeEnvironmentId
63847
+ );
63414
63848
  const freshSessionVars = useStore.getState().sessionVars;
63415
63849
  const result = await electron$j.sendRequest({
63416
63850
  ...basePayload,
@@ -63435,7 +63869,10 @@ function RequestBuilder({ request }) {
63435
63869
  for (const hook of hooks.after) {
63436
63870
  const start = Date.now();
63437
63871
  try {
63438
- const hookEnv = activeEnvironmentId ? useStore.getState().environments[activeEnvironmentId]?.data ?? null : null;
63872
+ const hookEnv = resolveEnvironmentById(
63873
+ useStore.getState().environments,
63874
+ activeEnvironmentId
63875
+ );
63439
63876
  const hookSessionVars = useStore.getState().sessionVars;
63440
63877
  const r = await electron$j.sendRequest({
63441
63878
  ...basePayload,
@@ -63550,7 +63987,7 @@ function RequestBuilder({ request }) {
63550
63987
  if (activeTabId) setTabRequestTab(activeTabId, "body");
63551
63988
  },
63552
63989
  className: `px-2 py-1.5 transition-colors ${isSoap ? "bg-amber-700 text-amber-100" : "text-surface-500 hover:text-white"}`,
63553
- title: "SOAP endpoint and method are derived from the WSDL",
63990
+ title: "SOAP - endpoint and method are derived from the WSDL",
63554
63991
  children: "SOAP"
63555
63992
  }
63556
63993
  )
@@ -63587,7 +64024,7 @@ function RequestBuilder({ request }) {
63587
64024
  "button",
63588
64025
  {
63589
64026
  onClick: toggleRunHooks,
63590
- title: runHooks ? "Hooks enabled click to disable" : "Hooks disabled click to enable",
64027
+ title: runHooks ? "Hooks enabled - click to disable" : "Hooks disabled - click to enable",
63591
64028
  className: `px-2 py-1.5 rounded text-xs font-medium transition-colors border ${runHooks ? "border-violet-500 text-violet-400 hover:bg-violet-500/10" : "border-surface-700 text-surface-500 hover:text-surface-300"}`,
63592
64029
  children: "hooks"
63593
64030
  }
@@ -63603,19 +64040,32 @@ function RequestBuilder({ request }) {
63603
64040
  )
63604
64041
  ] })
63605
64042
  ] }),
64043
+ showFuzz && /* @__PURE__ */ jsxRuntimeExports.jsx(FuzzModal, { request, onClose: () => setShowFuzz(false) }),
63606
64044
  isWs ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(WebSocketPanel, { request }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
63607
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-4 gap-0 flex-shrink-0", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
63608
- "button",
63609
- {
63610
- onClick: () => setActiveTab(tab.id),
63611
- className: `px-3 py-1.5 text-xs transition-colors border-b-2 -mb-px ${activeTab === tab.id ? "border-blue-500 text-white" : "border-transparent text-surface-400 hover:text-white"}`,
63612
- children: [
63613
- tab.label,
63614
- tab.count > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1 text-[10px] bg-surface-600 text-white rounded px-1 font-medium", children: tab.count })
63615
- ]
63616
- },
63617
- tab.id
63618
- )) }),
64045
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-surface-800 px-4 gap-0 flex-shrink-0", children: [
64046
+ tabs.map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
64047
+ "button",
64048
+ {
64049
+ onClick: () => setActiveTab(tab.id),
64050
+ className: `px-3 py-1.5 text-xs transition-colors border-b-2 -mb-px ${activeTab === tab.id ? "border-blue-500 text-white" : "border-transparent text-surface-400 hover:text-white"}`,
64051
+ children: [
64052
+ tab.label,
64053
+ tab.count > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1 text-[10px] bg-surface-600 text-white rounded px-1 font-medium", children: tab.count })
64054
+ ]
64055
+ },
64056
+ tab.id
64057
+ )),
64058
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
64059
+ "button",
64060
+ {
64061
+ onClick: () => setShowFuzz(true),
64062
+ disabled: !request.url,
64063
+ title: "Fuzz this request with malformed inputs (opens a dialog; nothing is sent until you confirm)",
64064
+ className: "ml-auto my-1 self-center px-2 py-0.5 rounded text-[11px] border border-surface-700 text-surface-500 hover:text-fuchsia-400 hover:border-fuchsia-500 transition-colors disabled:opacity-40",
64065
+ children: "fuzz"
64066
+ }
64067
+ )
64068
+ ] }),
63619
64069
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 flex-1 overflow-y-auto min-h-0", children: [
63620
64070
  activeTab === "params" && /* @__PURE__ */ jsxRuntimeExports.jsx(ParamsTab, { request, onChange: update }),
63621
64071
  activeTab === "headers" && /* @__PURE__ */ jsxRuntimeExports.jsx(HeadersTab, { request, onChange: update }),
@@ -64190,7 +64640,7 @@ function HookResultsPanel({ results }) {
64190
64640
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2.passed ? "✓" : "✗" }),
64191
64641
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2.name }),
64192
64642
  t2.error && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 text-[10px]", children: [
64193
- " ",
64643
+ "- ",
64194
64644
  t2.error
64195
64645
  ] })
64196
64646
  ] }, ti)),
@@ -64593,20 +65043,6 @@ function ConsolePanel({ scriptResult }) {
64593
65043
  )) })
64594
65044
  ] });
64595
65045
  }
64596
- function useToast(durationMs = 3e3) {
64597
- const [toast, setToast] = reactExports.useState(null);
64598
- const timer = reactExports.useRef(null);
64599
- function show(msg, ok) {
64600
- if (timer.current) clearTimeout(timer.current);
64601
- setToast({ msg, ok });
64602
- timer.current = setTimeout(() => setToast(null), durationMs);
64603
- }
64604
- return { toast, show };
64605
- }
64606
- function Toast({ toast }) {
64607
- if (!toast) return null;
64608
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mx-3 mb-2 px-2 py-1.5 rounded text-[11px] flex-shrink-0 ${toast.ok ? "bg-emerald-900/50 text-emerald-300 border border-emerald-800/50" : "bg-red-900/50 text-red-300 border border-red-800/50"}`, children: toast.msg });
64609
- }
64610
65046
  const { electron: electron$h } = window;
64611
65047
  function ResponseViewer() {
64612
65048
  const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
@@ -64727,7 +65163,7 @@ function ResponseViewer() {
64727
65163
  {
64728
65164
  onClick: () => setBodyView("tree"),
64729
65165
  className: `px-2 py-0.5 text-[10px] transition-colors ${bodyView === "tree" ? "bg-surface-700 text-white" : "text-surface-600 hover:text-white"}`,
64730
- title: "Interactive tree view click values to add assertions",
65166
+ title: "Interactive tree view - click values to add assertions",
64731
65167
  children: "Tree"
64732
65168
  }
64733
65169
  ),
@@ -64841,7 +65277,7 @@ function GeneratorPanel() {
64841
65277
  setSelectedFile(null);
64842
65278
  try {
64843
65279
  const col = collections[selectedCollectionId]?.data;
64844
- const env = activeEnvironmentId ? environments[activeEnvironmentId]?.data ?? null : null;
65280
+ const env = resolveEnvironmentById(environments, activeEnvironmentId);
64845
65281
  const generated = await electron$g.generateCode({ collection: col, environment: env, target });
64846
65282
  setFiles(generated);
64847
65283
  setSelectedFile(generated[0]?.path ?? null);
@@ -65134,7 +65570,7 @@ function WelcomeScreen() {
65134
65570
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-400 text-xs max-w-xs", children: [
65135
65571
  "A workspace is a ",
65136
65572
  /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: ".spector" }),
65137
- " file. Commit it and your collections to Git secrets are stored in your OS keychain, never on disk."
65573
+ " file. Commit it and your collections to Git - secrets are stored in your OS keychain, never on disk."
65138
65574
  ] })
65139
65575
  ] });
65140
65576
  }
@@ -65145,11 +65581,11 @@ const EXAMPLES = [
65145
65581
  code: (pw) => `export API_SPECTOR_MASTER_KEY="${pw || "<your-password>"}"`
65146
65582
  },
65147
65583
  {
65148
- label: "Windows PowerShell profile",
65584
+ label: "Windows - PowerShell profile",
65149
65585
  code: (pw) => `$env:API_SPECTOR_MASTER_KEY = "${pw || "<your-password>"}"`
65150
65586
  },
65151
65587
  {
65152
- label: "Windows Command Prompt (permanent)",
65588
+ label: "Windows - Command Prompt (permanent)",
65153
65589
  code: (pw) => `setx API_SPECTOR_MASTER_KEY "${pw || "<your-password>"}"`
65154
65590
  }
65155
65591
  ];
@@ -65295,6 +65731,26 @@ function EnvironmentEditor({ onClose }) {
65295
65731
  const duplicateEnvironment = useStore((s) => s.duplicateEnvironment);
65296
65732
  const envList = Object.values(environments);
65297
65733
  const env = selectedId ? environments[selectedId]?.data ?? null : null;
65734
+ const allEnvDatas = envList.map((e) => e.data);
65735
+ let inheritSummary = null;
65736
+ if (env?.extends) {
65737
+ const parentNames = [];
65738
+ const seen = /* @__PURE__ */ new Set([env.name]);
65739
+ let parentName = env.extends;
65740
+ while (parentName && !seen.has(parentName)) {
65741
+ const parent = allEnvDatas.find((e) => e.name === parentName);
65742
+ if (!parent) break;
65743
+ parentNames.push(parent.name);
65744
+ seen.add(parent.name);
65745
+ parentName = parent.extends;
65746
+ }
65747
+ if (parentNames.length > 0) {
65748
+ const ownKeys = new Set(env.variables.map((v) => v.key));
65749
+ const resolved = resolveEnvironmentChain(env, allEnvDatas);
65750
+ const inheritedCount = resolved.variables.filter((v) => !ownKeys.has(v.key)).length;
65751
+ inheritSummary = `Inherits ${inheritedCount} variable${inheritedCount === 1 ? "" : "s"} from ${parentNames.join(", ")}`;
65752
+ }
65753
+ }
65298
65754
  function handleDelete(id2) {
65299
65755
  deleteEnvironment(id2);
65300
65756
  const remaining = Object.keys(environments).filter((k) => k !== id2);
@@ -65480,7 +65936,23 @@ function EnvironmentEditor({ onClose }) {
65480
65936
  ),
65481
65937
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
65482
65938
  ] }),
65483
- nameError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-400 mt-1", children: nameError })
65939
+ nameError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-400 mt-1", children: nameError }),
65940
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
65941
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-400 font-medium shrink-0", children: "Extends" }),
65942
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
65943
+ "select",
65944
+ {
65945
+ value: env.extends ?? "",
65946
+ onChange: (e) => updateEnvironment(env.id, { ...env, extends: e.target.value || void 0 }),
65947
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500",
65948
+ children: [
65949
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(none)" }),
65950
+ envList.filter(({ data: other }) => other.id !== env.id).map(({ data: other }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: other.name, children: other.name }, other.id))
65951
+ ]
65952
+ }
65953
+ )
65954
+ ] }),
65955
+ inheritSummary && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 mt-1", children: inheritSummary })
65484
65956
  ] }),
65485
65957
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
65486
65958
  /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-xs", children: [
@@ -65601,7 +66073,7 @@ function EnvironmentEditor({ onClose }) {
65601
66073
  "button",
65602
66074
  {
65603
66075
  onClick: () => cycleSource(idx),
65604
- title: mode === "plain" ? "Plain text click to switch to encrypted secret" : mode === "encrypted" ? "Encrypted secret click to switch to env var ref" : "OS env var reference click to switch to plain text",
66076
+ title: mode === "plain" ? "Plain text - click to switch to encrypted secret" : mode === "encrypted" ? "Encrypted secret - click to switch to env var ref" : "OS env var reference - click to switch to plain text",
65605
66077
  className: "flex items-center justify-center gap-1 px-1.5 py-0.5 rounded border transition-colors text-[10px] font-medium w-16 shrink-0 border-surface-700 hover:border-surface-500",
65606
66078
  children: [
65607
66079
  mode === "plain" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "abc" }),
@@ -65639,14 +66111,14 @@ function EnvironmentEditor({ onClose }) {
65639
66111
  ] }),
65640
66112
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-400", children: [
65641
66113
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: "🔒 Encrypted" }),
65642
- " AES-256-GCM, key from",
66114
+ ": AES-256-GCM, key from",
65643
66115
  " ",
65644
66116
  /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-200", children: "API_SPECTOR_MASTER_KEY" }),
65645
66117
  "."
65646
66118
  ] }),
65647
66119
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-400", children: [
65648
66120
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: "$ Env var" }),
65649
- " read from",
66121
+ ": read from",
65650
66122
  " ",
65651
66123
  /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-200", children: "process.env" }),
65652
66124
  " at send-time. Ideal for CI/CD."
@@ -65693,8 +66165,9 @@ function EnvironmentBar({ inline = false }) {
65693
66165
  const setActiveEnvironment = useStore((s) => s.setActiveEnvironment);
65694
66166
  const [showEditor, setShowEditor] = reactExports.useState(false);
65695
66167
  const [pendingEnvId, setPendingEnvId] = reactExports.useState(null);
66168
+ const defaultEnvName = useStore((s) => s.workspace?.settings?.defaultEnvironment);
65696
66169
  const envList = Object.values(environments);
65697
- const activeEnv = activeEnvironmentId ? environments[activeEnvironmentId]?.data : null;
66170
+ const activeEnv = useActiveEnvironment();
65698
66171
  const varCount = activeEnv?.variables.filter((v) => v.enabled).length ?? 0;
65699
66172
  async function handleEnvChange(id2) {
65700
66173
  if (id2) {
@@ -65729,7 +66202,7 @@ function EnvironmentBar({ inline = false }) {
65729
66202
  style: { color: "var(--text-primary)" },
65730
66203
  children: [
65731
66204
  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "No env" }),
65732
- envList.map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.id, children: env.name }, env.id))
66205
+ envList.map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.id, children: defaultEnvName && env.name.toLowerCase() === defaultEnvName.toLowerCase() ? `${env.name} (default)` : env.name }, env.id))
65733
66206
  ]
65734
66207
  }
65735
66208
  ),
@@ -65760,12 +66233,14 @@ const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
65760
66233
  function WorkspaceSettingsModal({ onClose }) {
65761
66234
  const workspace = useStore((s) => s.workspace);
65762
66235
  const updateWorkspaceSettings = useStore((s) => s.updateWorkspaceSettings);
66236
+ const environments = useStore((s) => s.environments);
65763
66237
  const theme2 = useStore((s) => s.theme);
65764
66238
  const zoom = useStore((s) => s.zoom);
65765
66239
  const setTheme = useStore((s) => s.setTheme);
65766
66240
  const setZoom = useStore((s) => s.setZoom);
65767
66241
  const existing = workspace?.settings ?? {};
65768
- const [activeTab, setActiveTab] = reactExports.useState("appearance");
66242
+ const [activeTab, setActiveTab] = reactExports.useState("general");
66243
+ const [defaultEnvironment, setDefaultEnvironment] = reactExports.useState(existing.defaultEnvironment ?? "");
65769
66244
  const [proxyUrl, setProxyUrl] = reactExports.useState(existing.proxy?.url ?? "");
65770
66245
  const [proxyUser, setProxyUser] = reactExports.useState(existing.proxy?.auth?.username ?? "");
65771
66246
  const [proxyPass, setProxyPass] = reactExports.useState(existing.proxy?.auth?.password ?? "");
@@ -65776,6 +66251,7 @@ function WorkspaceSettingsModal({ onClose }) {
65776
66251
  existing.tls?.rejectUnauthorized !== false
65777
66252
  // default true
65778
66253
  );
66254
+ const [dashboardUrl, setDashboardUrl] = reactExports.useState(existing.dashboardUrl ?? "");
65779
66255
  const [patterns, setPatterns] = reactExports.useState(
65780
66256
  existing.piiMaskPatterns ?? DEFAULT_PII_PATTERNS
65781
66257
  );
@@ -65807,6 +66283,10 @@ function WorkspaceSettingsModal({ onClose }) {
65807
66283
  rejectUnauthorized
65808
66284
  };
65809
66285
  settings.piiMaskPatterns = patterns;
66286
+ if (dashboardUrl.trim()) settings.dashboardUrl = dashboardUrl.trim();
66287
+ else delete settings.dashboardUrl;
66288
+ if (defaultEnvironment) settings.defaultEnvironment = defaultEnvironment;
66289
+ else delete settings.defaultEnvironment;
65810
66290
  updateWorkspaceSettings(settings);
65811
66291
  const updated = useStore.getState().workspace;
65812
66292
  if (updated) await electron$b.saveWorkspace(updated);
@@ -65818,10 +66298,12 @@ function WorkspaceSettingsModal({ onClose }) {
65818
66298
  setZoom(next);
65819
66299
  }
65820
66300
  const tabs = [
66301
+ { id: "general", label: "General" },
65821
66302
  { id: "appearance", label: "Appearance" },
65822
66303
  { id: "proxy", label: "Proxy" },
65823
66304
  { id: "tls", label: "TLS / Certificates" },
65824
- { id: "privacy", label: "Privacy" }
66305
+ { id: "privacy", label: "Privacy" },
66306
+ { id: "contracts", label: "Contracts" }
65825
66307
  ];
65826
66308
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
65827
66309
  Modal,
@@ -65841,6 +66323,24 @@ function WorkspaceSettingsModal({ onClose }) {
65841
66323
  t2.id
65842
66324
  )) }),
65843
66325
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 text-xs flex flex-col gap-4", children: [
66326
+ activeTab === "general" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
66327
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
66328
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Default environment" }),
66329
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
66330
+ "select",
66331
+ {
66332
+ value: defaultEnvironment,
66333
+ onChange: (e) => setDefaultEnvironment(e.target.value),
66334
+ className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500",
66335
+ children: [
66336
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(none)" }),
66337
+ Object.values(environments).map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.name, children: env.name }, env.id))
66338
+ ]
66339
+ }
66340
+ )
66341
+ ] }),
66342
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-600 text-[11px]", children: "CLI runs without --environment use this environment, and the app selects it when no environment is active." })
66343
+ ] }),
65844
66344
  activeTab === "appearance" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
65845
66345
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
65846
66346
  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Theme" }),
@@ -65930,6 +66430,21 @@ function WorkspaceSettingsModal({ onClose }) {
65930
66430
  ] })
65931
66431
  ] })
65932
66432
  ] }),
66433
+ activeTab === "contracts" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
66434
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
66435
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Dashboard URL (optional)" }),
66436
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
66437
+ "input",
66438
+ {
66439
+ value: dashboardUrl,
66440
+ onChange: (e) => setDashboardUrl(e.target.value),
66441
+ placeholder: "http://localhost:8080",
66442
+ className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
66443
+ }
66444
+ )
66445
+ ] }),
66446
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-600 text-[11px]", children: 'Where your contract dashboard is served (api-spector contract report --serve, locally or as a docker container). Adds an "Open dashboard" link to the contract results panel. The link is view-only: recorded results reach the dashboard through the workspace files, not through this URL.' })
66447
+ ] }),
65933
66448
  activeTab === "tls" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
65934
66449
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
65935
66450
  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "CA Certificate path" }),
@@ -66467,7 +66982,7 @@ function ImportModal({ onImport, onClose }) {
66467
66982
  children: [
66468
66983
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
66469
66984
  /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-sm font-semibold text-surface-100", children: [
66470
- "Import OpenAPI ",
66985
+ "Import OpenAPI - ",
66471
66986
  previewCol.name
66472
66987
  ] }),
66473
66988
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -67515,7 +68030,7 @@ function RunnerModal() {
67515
68030
  return { ...item, request: req };
67516
68031
  });
67517
68032
  }
67518
- const env = selectedEnvId ? environments[selectedEnvId]?.data ?? null : null;
68033
+ const env = resolveEnvironmentById(environments, selectedEnvId || null);
67519
68034
  setRunnerResults(items2.map((item) => ({
67520
68035
  requestId: item.request.id,
67521
68036
  name: item.request.name,
@@ -67582,7 +68097,7 @@ function RunnerModal() {
67582
68097
  onChange: (e) => setSelectedEnvId(e.target.value),
67583
68098
  className: "text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500",
67584
68099
  children: [
67585
- /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: " No environment" }),
68100
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(no environment)" }),
67586
68101
  Object.values(environments).map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.id, children: env.name }, env.id))
67587
68102
  ]
67588
68103
  }
@@ -67909,7 +68424,7 @@ function CollectionPanel() {
67909
68424
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-6 py-4", children: [
67910
68425
  activeTab === "data" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
67911
68426
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-600 text-[11px]", children: [
67912
- "Define variables here each row runs the entire collection once with those values injected. Columns become ",
68427
+ "Define variables here - each row runs the entire collection once with those values injected. Columns become ",
67913
68428
  /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: "{{variable}}" }),
67914
68429
  " placeholders."
67915
68430
  ] }),
@@ -67922,14 +68437,14 @@ function CollectionPanel() {
67922
68437
  {
67923
68438
  onClick: () => csvFileRef.current?.click(),
67924
68439
  className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors",
67925
- title: "Import CSV first row is column headers",
68440
+ title: "Import CSV - first row is column headers",
67926
68441
  children: "↑ Import CSV"
67927
68442
  }
67928
68443
  ),
67929
68444
  hasColumns && iterCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: exportCSV, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "↓ Export CSV" }),
67930
68445
  /* @__PURE__ */ jsxRuntimeExports.jsx("input", { ref: csvFileRef, type: "file", accept: ".csv,text/csv", className: "hidden", onChange: importCSV })
67931
68446
  ] }),
67932
- hasColumns && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: iterCount === 0 ? "No rows yet add rows or import a CSV." : `${iterCount} iteration${iterCount !== 1 ? "s" : ""} · columns: ${ds.columns.join(", ")}` }),
68447
+ hasColumns && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: iterCount === 0 ? "No rows yet - add rows or import a CSV." : `${iterCount} iteration${iterCount !== 1 ? "s" : ""} · columns: ${ds.columns.join(", ")}` }),
67933
68448
  hasColumns ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full border-collapse text-xs", children: [
67934
68449
  /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-700", children: [
67935
68450
  /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-8 px-2 py-1 text-surface-600 font-normal text-left", children: "#" }),
@@ -67960,7 +68475,7 @@ function CollectionPanel() {
67960
68475
  ) }, ci)),
67961
68476
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeRow(ri), className: "text-surface-400 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all", children: "×" }) })
67962
68477
  ] }, ri)),
67963
- iterCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: ds.columns.length + 2, className: "px-2 py-3 text-surface-600 text-center", children: 'No rows click "+ Row" or import a CSV' }) })
68478
+ iterCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: ds.columns.length + 2, className: "px-2 py-3 text-surface-600 text-center", children: 'No rows - click "+ Row" or import a CSV' }) })
67964
68479
  ] })
67965
68480
  ] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-8 text-surface-600", children: [
67966
68481
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "No columns defined." }),
@@ -68570,7 +69085,7 @@ function HitRow({ hit, matched }) {
68570
69085
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-[10px] w-3 shrink-0", children: open ? "▾" : "▸" }),
68571
69086
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-bold w-16 shrink-0 text-xs ${getMethodColor(hit.method)}`, children: hit.method }),
68572
69087
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", title: hit.path, children: hit.path }),
68573
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 shrink-0 truncate text-surface-600 text-xs font-sans", title: matched?.description ?? matched?.path ?? "", children: unmatched ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: "no match" }) : matched?.description || matched?.path || "" }),
69088
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 shrink-0 truncate text-surface-600 text-xs font-sans", title: matched?.description ?? matched?.path ?? "", children: unmatched ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: "no match" }) : matched?.description || matched?.path || "-" }),
68574
69089
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-12 text-right shrink-0 text-xs ${hit.status < 300 ? "text-emerald-400" : hit.status < 400 ? "text-amber-400" : "text-red-400"}`, children: hit.status }),
68575
69090
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "w-14 text-right shrink-0 text-surface-600 text-xs", children: [
68576
69091
  hit.durationMs,
@@ -68951,7 +69466,7 @@ function RecorderPanel({ onImportMock, onClose, defaultTargetMockId }) {
68951
69466
  onChange: (e) => setImportTarget(e.target.value),
68952
69467
  className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-[11px] text-surface-200 focus:outline-none focus:border-blue-500",
68953
69468
  children: [
68954
- /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "new", children: " New mock server" }),
69469
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "new", children: "(new mock server)" }),
68955
69470
  mockList.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: entry.data.id, children: entry.data.name }, entry.data.id))
68956
69471
  ]
68957
69472
  }
@@ -69040,7 +69555,7 @@ function EntryDetail({ entry }) {
69040
69555
  const sc = statusColor$1(entry.response.status);
69041
69556
  const prettyBody = (raw) => {
69042
69557
  if (!raw) return "";
69043
- if (raw.startsWith("base64:")) return "[binary content base64 encoded]";
69558
+ if (raw.startsWith("base64:")) return "[binary content - base64 encoded]";
69044
69559
  try {
69045
69560
  return JSON.stringify(JSON.parse(raw), null, 2);
69046
69561
  } catch {
@@ -69082,7 +69597,7 @@ function EntryDetail({ entry }) {
69082
69597
  tab === "response" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
69083
69598
  /* @__PURE__ */ jsxRuntimeExports.jsx(Section, { label: "Headers", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HeadersTable, { headers: entry.response.headers }) }),
69084
69599
  entry.response.body && /* @__PURE__ */ jsxRuntimeExports.jsx(Section, { label: "Body", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-[11px] whitespace-pre-wrap break-all text-surface-200 bg-surface-900 border border-surface-700 rounded p-3 max-h-72 overflow-y-auto", children: prettyBody(entry.response.body) }) }),
69085
- entry.response.binary && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-yellow-400", children: "Binary response body stored as base64" })
69600
+ entry.response.binary && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-yellow-400", children: "Binary response - body stored as base64" })
69086
69601
  ] })
69087
69602
  ] });
69088
69603
  }
@@ -69104,7 +69619,7 @@ function HeadersTable({ headers }) {
69104
69619
  ] }, k)) });
69105
69620
  }
69106
69621
  const { electron: electron$3 } = window;
69107
- function ContractPanel() {
69622
+ function ContractPanel({ fuzzReport, setFuzzReport }) {
69108
69623
  const collections = useStore((s) => s.collections);
69109
69624
  const environments = useStore((s) => s.environments);
69110
69625
  const activeEnvId = useStore((s) => s.activeEnvironmentId);
@@ -69125,18 +69640,28 @@ function ContractPanel() {
69125
69640
  const [running, setRunning] = reactExports.useState(false);
69126
69641
  const [capturing, setCapturing] = reactExports.useState(false);
69127
69642
  const [error2, setError] = reactExports.useState(null);
69643
+ const [casesPerOperation, setCasesPerOperation] = reactExports.useState(40);
69644
+ const [seed, setSeed] = reactExports.useState(1);
69645
+ const [includeWrites, setIncludeWrites] = reactExports.useState(false);
69646
+ const [strictStatus, setStrictStatus] = reactExports.useState(false);
69647
+ const [checkResponses, setCheckResponses] = reactExports.useState(false);
69648
+ const [trace, setTrace] = reactExports.useState(false);
69128
69649
  const snapshotList = Object.entries(snapshots).map(([relPath, snapshot]) => ({ relPath, snapshot })).sort((a, b) => b.snapshot.capturedAt.localeCompare(a.snapshot.capturedAt));
69129
69650
  const activeSnapshot = activeSnapshotRelPath ? snapshots[activeSnapshotRelPath] ?? null : null;
69130
69651
  const allRequests = Object.values(collections).flatMap((c) => Object.values(c.data.requests));
69131
69652
  const contractRequests = allRequests.filter(
69132
69653
  (r) => r.contract && (r.contract.statusCode !== void 0 || r.contract.bodySchema || r.contract.bodyMatcher || r.contract.headers?.length)
69133
69654
  );
69655
+ const isFuzz = mode === "fuzz";
69134
69656
  const needsSpec = mode === "provider" || mode === "bidirectional";
69657
+ const showSpec = needsSpec || isFuzz;
69135
69658
  const collectionVars = activeCollId ? collections[activeCollId]?.data.collectionVariables ?? {} : {};
69136
- const envVars = activeEnvId ? Object.fromEntries(
69137
- (environments[activeEnvId]?.data.variables ?? []).filter((v) => v.enabled).map((v) => [v.key, v.value])
69659
+ const resolvedEnv = resolveEnvironmentById(environments, activeEnvId);
69660
+ const envVars = resolvedEnv ? Object.fromEntries(
69661
+ resolvedEnv.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])
69138
69662
  ) : {};
69139
69663
  async function runContracts() {
69664
+ if (mode === "fuzz") return;
69140
69665
  if (needsSpec && !specUrl.trim() && !activeSnapshotRelPath) {
69141
69666
  setError("Provide an OpenAPI spec URL or pick a pinned snapshot for provider / bi-directional mode.");
69142
69667
  return;
@@ -69161,7 +69686,44 @@ function ContractPanel() {
69161
69686
  providerBaseUrl: providerBaseUrl.trim() || void 0,
69162
69687
  stateHandlerUrl: stateHandlerUrl.trim() || void 0
69163
69688
  });
69164
- setReport(result);
69689
+ const activeSnap = activeSnapshotRelPath ? snapshots[activeSnapshotRelPath] : void 0;
69690
+ const specLabel = activeSnap ? `${activeSnap.name}${activeSnap.specVersion ? ` v${activeSnap.specVersion}` : ""} (pinned)` : specUrl.trim() || void 0;
69691
+ setReport(result, {
69692
+ spec: needsSpec ? specLabel : void 0,
69693
+ provider: providerBaseUrl.trim() || void 0
69694
+ });
69695
+ } catch (e) {
69696
+ setError(e instanceof Error ? e.message : String(e));
69697
+ } finally {
69698
+ setRunning(false);
69699
+ }
69700
+ }
69701
+ async function runFuzz() {
69702
+ if (!providerBaseUrl.trim()) {
69703
+ setError("Provide a provider base URL (e.g. http://localhost:3000) to fuzz against.");
69704
+ return;
69705
+ }
69706
+ setRunning(true);
69707
+ setError(null);
69708
+ setFuzzReport(null);
69709
+ try {
69710
+ const result = await electron$3.fuzzContracts({
69711
+ requests: allRequests,
69712
+ // fuzz every request in the workspace, like provider mode
69713
+ envVars,
69714
+ collectionVars,
69715
+ specUrl: specUrl.trim() || void 0,
69716
+ specSnapshotRelPath: activeSnapshotRelPath ?? void 0,
69717
+ providerBaseUrl: providerBaseUrl.trim(),
69718
+ requestBaseUrl: requestBaseUrl.trim() || void 0,
69719
+ casesPerOperation,
69720
+ seed,
69721
+ includeWrites,
69722
+ strictStatus,
69723
+ checkResponses,
69724
+ trace
69725
+ });
69726
+ setFuzzReport(result);
69165
69727
  } catch (e) {
69166
69728
  setError(e instanceof Error ? e.message : String(e));
69167
69729
  } finally {
@@ -69205,21 +69767,23 @@ function ContractPanel() {
69205
69767
  ["consumer", "Consumer"],
69206
69768
  ["provider", "Provider"],
69207
69769
  ["provider-live", "Live"],
69208
- ["bidirectional", "Bi-dir"]
69770
+ ["bidirectional", "Bi-dir"],
69771
+ ["fuzz", "Fuzz"]
69209
69772
  ].map(([m, label]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
69210
69773
  "button",
69211
69774
  {
69212
69775
  onClick: () => {
69213
69776
  setMode(m);
69214
69777
  setReport(null);
69778
+ setFuzzReport(null);
69215
69779
  },
69216
69780
  className: `flex-1 py-1 text-[10px] font-semibold rounded transition-colors ${mode === m ? "bg-blue-600 text-white" : "text-surface-400 hover:text-surface-200"}`,
69217
69781
  children: label
69218
69782
  },
69219
69783
  m
69220
69784
  )) }),
69221
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 leading-relaxed", children: mode === "consumer" ? "Sends requests to the real provider and validates each response against the contract defined in the Contract tab." : mode === "provider" ? "Static analysis validates that your requests conform to the provider's published OpenAPI spec (no HTTP calls)." : mode === "provider-live" ? "Replays each contract against a running provider, seeding provider states first. The real provider verification." : "Checks static schema compatibility between consumer contracts and provider spec, then verifies live responses." }),
69222
- mode === "provider-live" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
69785
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 leading-relaxed", children: mode === "consumer" ? "Sends requests to the real provider and validates each response against the contract defined in the Contract tab." : mode === "provider" ? "Static analysis: validates that your requests conform to the provider's published OpenAPI spec (no HTTP calls)." : mode === "provider-live" ? "Replays each contract against a running provider, seeding provider states first. The real provider verification." : mode === "fuzz" ? "Sends malformed inputs generated from the spec (or, with no spec, the request body) and flags responses that crash (5xx) or accept invalid input." : "Checks static schema compatibility between consumer contracts and provider spec, then verifies live responses." }),
69786
+ (mode === "provider-live" || isFuzz) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
69223
69787
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
69224
69788
  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Provider base URL" }),
69225
69789
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -69231,9 +69795,9 @@ function ContractPanel() {
69231
69795
  className: "w-full text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
69232
69796
  }
69233
69797
  ),
69234
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-1 leading-relaxed", children: "Each request is rebased onto this origin before being replayed against the live provider." })
69798
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-1 leading-relaxed", children: isFuzz ? "Required. Each fuzzed request is rebased onto this origin before it is sent." : "Each request is rebased onto this origin before being replayed against the live provider." })
69235
69799
  ] }),
69236
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
69800
+ mode === "provider-live" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
69237
69801
  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: [
69238
69802
  "State handler URL ",
69239
69803
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "normal-case text-surface-600", children: "(optional)" })
@@ -69256,9 +69820,12 @@ function ContractPanel() {
69256
69820
  ] })
69257
69821
  ] })
69258
69822
  ] }),
69259
- needsSpec && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
69823
+ showSpec && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
69260
69824
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
69261
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Spec version" }),
69825
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: [
69826
+ "Spec version ",
69827
+ isFuzz && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "normal-case text-surface-600", children: "(optional)" })
69828
+ ] }),
69262
69829
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [
69263
69830
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
69264
69831
  "select",
@@ -69271,7 +69838,7 @@ function ContractPanel() {
69271
69838
  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Live URL (latest from provider)" }),
69272
69839
  snapshotList.map(({ relPath, snapshot }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: relPath, children: [
69273
69840
  snapshot.name,
69274
- snapshot.specVersion ? "" : ` ${snapshot.capturedAt.slice(0, 10)}`
69841
+ snapshot.specVersion ? "" : ` - ${snapshot.capturedAt.slice(0, 10)}`
69275
69842
  ] }, relPath))
69276
69843
  ]
69277
69844
  }
@@ -69289,7 +69856,7 @@ function ContractPanel() {
69289
69856
  activeSnapshot && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-1 font-mono truncate", children: [
69290
69857
  "Captured ",
69291
69858
  activeSnapshot.capturedAt.slice(0, 19).replace("T", " "),
69292
- " sha ",
69859
+ " - sha ",
69293
69860
  activeSnapshot.sha256.slice(0, 8)
69294
69861
  ] })
69295
69862
  ] }),
@@ -69335,15 +69902,93 @@ function ContractPanel() {
69335
69902
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-1 leading-relaxed", children: "If your requests point at a different host than the spec, enter that host here so paths match correctly." })
69336
69903
  ] })
69337
69904
  ] }),
69905
+ isFuzz && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
69906
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
69907
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
69908
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Cases per operation" }),
69909
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
69910
+ "input",
69911
+ {
69912
+ type: "number",
69913
+ min: 1,
69914
+ value: casesPerOperation,
69915
+ onChange: (e) => setCasesPerOperation(Math.max(1, Number(e.target.value) || 1)),
69916
+ className: "w-full text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono"
69917
+ }
69918
+ )
69919
+ ] }),
69920
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
69921
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Seed" }),
69922
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
69923
+ "input",
69924
+ {
69925
+ type: "number",
69926
+ value: seed,
69927
+ onChange: (e) => setSeed(Number(e.target.value) || 0),
69928
+ className: "w-full text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono"
69929
+ }
69930
+ )
69931
+ ] })
69932
+ ] }),
69933
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
69934
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
69935
+ "input",
69936
+ {
69937
+ type: "checkbox",
69938
+ checked: includeWrites,
69939
+ onChange: (e) => setIncludeWrites(e.target.checked),
69940
+ className: "accent-blue-600"
69941
+ }
69942
+ ),
69943
+ "Include write methods (POST/PUT/PATCH/DELETE)"
69944
+ ] }),
69945
+ includeWrites && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-amber-400 leading-relaxed -mt-1 ml-6", children: "This sends malformed writes to the provider. Target staging or a mock, not production." }),
69946
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
69947
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
69948
+ "input",
69949
+ {
69950
+ type: "checkbox",
69951
+ checked: strictStatus,
69952
+ onChange: (e) => setStrictStatus(e.target.checked),
69953
+ className: "accent-blue-600"
69954
+ }
69955
+ ),
69956
+ "Strict status"
69957
+ ] }),
69958
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
69959
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
69960
+ "input",
69961
+ {
69962
+ type: "checkbox",
69963
+ checked: checkResponses,
69964
+ onChange: (e) => setCheckResponses(e.target.checked),
69965
+ className: "accent-blue-600"
69966
+ }
69967
+ ),
69968
+ "Check response schemas"
69969
+ ] }),
69970
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
69971
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
69972
+ "input",
69973
+ {
69974
+ type: "checkbox",
69975
+ checked: trace,
69976
+ onChange: (e) => setTrace(e.target.checked),
69977
+ className: "accent-blue-600"
69978
+ }
69979
+ ),
69980
+ "Record all cases"
69981
+ ] })
69982
+ ] }),
69338
69983
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
69339
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-500", children: mode === "provider" ? `${allRequests.length} request${allRequests.length !== 1 ? "s" : ""}` : `${contractRequests.length} contract${contractRequests.length !== 1 ? "s" : ""} defined` }),
69984
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-500", children: mode === "provider" || isFuzz ? `${allRequests.length} request${allRequests.length !== 1 ? "s" : ""}` : `${contractRequests.length} contract${contractRequests.length !== 1 ? "s" : ""} defined` }),
69340
69985
  /* @__PURE__ */ jsxRuntimeExports.jsx(
69341
69986
  "button",
69342
69987
  {
69343
- onClick: runContracts,
69344
- disabled: running || needsSpec && !specUrl.trim() && !activeSnapshotRelPath || mode === "provider-live" && !providerBaseUrl.trim(),
69988
+ onClick: isFuzz ? runFuzz : runContracts,
69989
+ disabled: running || needsSpec && !specUrl.trim() && !activeSnapshotRelPath || (mode === "provider-live" || isFuzz) && !providerBaseUrl.trim(),
69345
69990
  className: "px-3 py-1 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors font-medium",
69346
- children: running ? "Running…" : "Run"
69991
+ children: running ? "Running…" : isFuzz ? "Run fuzz" : "Run"
69347
69992
  }
69348
69993
  )
69349
69994
  ] }),
@@ -69351,30 +69996,33 @@ function ContractPanel() {
69351
69996
  ] }),
69352
69997
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto min-h-0 p-3", children: [
69353
69998
  running && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 text-center mt-4", children: "Running…" }),
69354
- !report && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-2 text-center", children: [
69355
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure a mode above and click Run." }),
69356
- mode !== "provider" && contractRequests.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-[180px]", children: "Define a contract on a request via the Contract tab first." })
69999
+ isFuzz ? !fuzzReport && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-2 text-center", children: [
70000
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure fuzzing above and click Run fuzz." }),
70001
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-[180px]", children: "Fuzzing needs a provider base URL. A spec is optional: without one, request bodies are mutated." })
70002
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
70003
+ !report && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-2 text-center", children: [
70004
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure a mode above and click Run." }),
70005
+ mode !== "provider" && contractRequests.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-[180px]", children: "Define a contract on a request via the Contract tab first." })
70006
+ ] }),
70007
+ report && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-2 rounded-lg border text-xs ${report.failed === 0 ? "bg-emerald-800/30 border-emerald-400/50 text-emerald-400" : "bg-red-900/30 border-red-700 text-red-300"}`, children: [
70008
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: report.failed === 0 ? "✓ All passed" : `✗ ${report.failed} failed` }),
70009
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 ml-auto", children: [
70010
+ report.passed,
70011
+ "/",
70012
+ report.total
70013
+ ] })
70014
+ ] })
69357
70015
  ] }),
69358
- report && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-2 rounded-lg border text-xs ${report.failed === 0 ? "bg-emerald-800/30 border-emerald-400/50 text-emerald-400" : "bg-red-900/30 border-red-700 text-red-300"}`, children: [
69359
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: report.failed === 0 ? "✓ All passed" : `✗ ${report.failed} failed` }),
70016
+ isFuzz && fuzzReport && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-2 rounded-lg border text-xs ${fuzzReport.totalFindings === 0 ? "bg-emerald-800/30 border-emerald-400/50 text-emerald-400" : "bg-red-900/30 border-red-700 text-red-300"}`, children: [
70017
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: fuzzReport.totalFindings === 0 ? "✓ No findings" : `✗ ${fuzzReport.totalFindings} finding${fuzzReport.totalFindings !== 1 ? "s" : ""}` }),
69360
70018
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 ml-auto", children: [
69361
- report.passed,
69362
- "/",
69363
- report.total
70019
+ fuzzReport.totalCases,
70020
+ " cases"
69364
70021
  ] })
69365
70022
  ] })
69366
70023
  ] })
69367
70024
  ] });
69368
70025
  }
69369
- const METHOD_COLOR = {
69370
- GET: "text-emerald-400",
69371
- POST: "text-blue-400",
69372
- PUT: "text-amber-400",
69373
- PATCH: "text-orange-400",
69374
- DELETE: "text-red-400",
69375
- HEAD: "text-purple-400",
69376
- OPTIONS: "text-gray-400"
69377
- };
69378
70026
  function statusColor(code2) {
69379
70027
  const d = String(code2)[0];
69380
70028
  return d === "2" ? "text-emerald-400" : d === "3" ? "text-amber-400" : "text-red-400";
@@ -69408,7 +70056,7 @@ function ResultCard({ result }) {
69408
70056
  className: `w-full flex items-center gap-3 px-4 py-3 text-left transition-colors ${result.passed ? "bg-surface-800 hover:bg-surface-750" : "bg-red-950/30 hover:bg-red-950/50"}`,
69409
70057
  children: [
69410
70058
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[10px] font-bold px-2 py-0.5 rounded ${result.passed ? "bg-emerald-900/50 text-emerald-400" : "bg-red-900/50 text-red-400"}`, children: result.passed ? "PASS" : "FAIL" }),
69411
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${METHOD_COLOR[result.method] ?? "text-gray-400"}`, children: result.method }),
70059
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${getMethodColor(result.method)}`, children: result.method }),
69412
70060
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-sm text-white truncate", children: result.requestName }),
69413
70061
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden lg:block text-[11px] text-surface-500 font-mono truncate max-w-[260px]", children: result.url }),
69414
70062
  result.actualStatus !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-mono font-bold ${statusColor(result.actualStatus)}`, children: result.actualStatus }),
@@ -69430,7 +70078,37 @@ function ResultCard({ result }) {
69430
70078
  }
69431
70079
  function ContractResultsPanel() {
69432
70080
  const report = useStore((s) => s.lastContractReport);
70081
+ const runMeta = useStore((s) => s.lastContractRunMeta);
69433
70082
  const clearReport = useStore((s) => s.setLastContractReport);
70083
+ const activeCollId = useStore((s) => s.activeCollectionId);
70084
+ const collections = useStore((s) => s.collections);
70085
+ const dashboardUrl = useStore((s) => s.workspace?.settings?.dashboardUrl);
70086
+ const [recordOpen, setRecordOpen] = reactExports.useState(false);
70087
+ const [pacticipant, setPacticipant] = reactExports.useState("");
70088
+ const [version, setVersion] = reactExports.useState("");
70089
+ const [saving, setSaving] = reactExports.useState(false);
70090
+ const { toast, show: showToast } = useToast();
70091
+ async function recordRun() {
70092
+ if (!report) return;
70093
+ if (!pacticipant.trim() || !version.trim()) {
70094
+ showToast("Pacticipant and version are required to record.", false);
70095
+ return;
70096
+ }
70097
+ setSaving(true);
70098
+ try {
70099
+ await window.electron.recordContractResult({
70100
+ pacticipant: pacticipant.trim(),
70101
+ version: version.trim(),
70102
+ report
70103
+ });
70104
+ showToast(`Recorded ${pacticipant.trim()}@${version.trim()} for the dashboard and can-i-deploy.`, true);
70105
+ setRecordOpen(false);
70106
+ } catch (e) {
70107
+ showToast(e instanceof Error ? e.message : String(e), false);
70108
+ } finally {
70109
+ setSaving(false);
70110
+ }
70111
+ }
69434
70112
  if (!report) {
69435
70113
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-3 text-center", children: [
69436
70114
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-4xl opacity-30", children: "🔬" }),
@@ -69453,10 +70131,36 @@ function ContractResultsPanel() {
69453
70131
  report.durationMs,
69454
70132
  "ms"
69455
70133
  ] }),
70134
+ dashboardUrl && /* @__PURE__ */ jsxRuntimeExports.jsx(
70135
+ "button",
70136
+ {
70137
+ onClick: () => window.electron.openExternal(dashboardUrl),
70138
+ className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
70139
+ title: `Open the contract dashboard (${dashboardUrl})`,
70140
+ children: "Open dashboard"
70141
+ }
70142
+ ),
69456
70143
  /* @__PURE__ */ jsxRuntimeExports.jsx(
69457
70144
  "button",
69458
70145
  {
69459
- onClick: () => window.electron.exportContractReportHtml(report),
70146
+ onClick: () => {
70147
+ if (!recordOpen && !pacticipant) {
70148
+ setPacticipant(activeCollId ? collections[activeCollId]?.data.name ?? "" : "");
70149
+ }
70150
+ setRecordOpen((o) => !o);
70151
+ },
70152
+ className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
70153
+ title: "Record this run for the contract dashboard and can-i-deploy gate",
70154
+ children: "Record"
70155
+ }
70156
+ ),
70157
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
70158
+ "button",
70159
+ {
70160
+ onClick: () => window.electron.exportContractReportHtml(report, {
70161
+ spec: runMeta?.spec,
70162
+ provider: runMeta?.provider
70163
+ }),
69460
70164
  className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
69461
70165
  title: "Export a self-contained HTML report",
69462
70166
  children: "Export HTML"
@@ -69472,6 +70176,37 @@ function ContractResultsPanel() {
69472
70176
  }
69473
70177
  )
69474
70178
  ] }),
70179
+ recordOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-6 py-2 border-b border-surface-800 bg-surface-900/60 flex-shrink-0", children: [
70180
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
70181
+ "input",
70182
+ {
70183
+ value: pacticipant,
70184
+ onChange: (e) => setPacticipant(e.target.value),
70185
+ placeholder: "pacticipant (e.g. web-app)",
70186
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-48 focus:outline-none focus:border-blue-500"
70187
+ }
70188
+ ),
70189
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
70190
+ "input",
70191
+ {
70192
+ value: version,
70193
+ onChange: (e) => setVersion(e.target.value),
70194
+ placeholder: "version (e.g. 1.4.0)",
70195
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-32 focus:outline-none focus:border-blue-500"
70196
+ }
70197
+ ),
70198
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
70199
+ "button",
70200
+ {
70201
+ onClick: recordRun,
70202
+ disabled: saving,
70203
+ className: "text-[11px] px-3 py-1 rounded bg-blue-600 hover:bg-blue-500 text-white font-semibold disabled:opacity-50 transition-colors",
70204
+ children: saving ? "Recording..." : "Record result"
70205
+ }
70206
+ ),
70207
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-500", children: "Writes to contracts/results/ in the workspace; the dashboard picks it up on refresh." })
70208
+ ] }),
70209
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
69475
70210
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto min-h-0 p-6", children: (() => {
69476
70211
  const failed = report.results.filter((r) => !r.passed);
69477
70212
  const passed = report.results.filter((r) => r.passed);
@@ -69571,7 +70306,7 @@ ${secretHint} - script: ${runCmd}
69571
70306
  condition: always()
69572
70307
  `;
69573
70308
  }
69574
- return `# Unsupported platform adapt as needed
70309
+ return `# Unsupported platform - adapt as needed
69575
70310
  # ${runCmd}
69576
70311
  `;
69577
70312
  }
@@ -69749,7 +70484,7 @@ function ChangesTab({ status, onRefresh }) {
69749
70484
  status.conflicted.length,
69750
70485
  " merge conflict",
69751
70486
  status.conflicted.length !== 1 ? "s" : "",
69752
- " resolve below before committing"
70487
+ " - resolve below before committing"
69753
70488
  ] }),
69754
70489
  /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
69755
70490
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto", children: [
@@ -70144,7 +70879,7 @@ function BranchesTab({ onRefresh }) {
70144
70879
  {
70145
70880
  onClick: () => checkout(b.name, false),
70146
70881
  className: "w-full flex items-center gap-2 px-3 py-1.5 text-left text-xs text-surface-400 hover:bg-surface-800/50 hover:text-surface-200 transition-colors",
70147
- title: "Click to check out creates a local tracking branch if needed",
70882
+ title: "Click to check out - creates a local tracking branch if needed",
70148
70883
  children: [
70149
70884
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3" }),
70150
70885
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate", children: b.name })
@@ -70305,7 +71040,7 @@ function CiTab() {
70305
71040
  onChange: (e) => setEnvId(e.target.value),
70306
71041
  className: "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500",
70307
71042
  children: [
70308
- /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "none" }),
71043
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(none)" }),
70309
71044
  envList.map((e) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: e.data.id, children: e.data.name }, e.data.id))
70310
71045
  ]
70311
71046
  }
@@ -70662,7 +71397,8 @@ const TAB_METHOD_COLORS = {
70662
71397
  PATCH: "text-orange-400",
70663
71398
  DELETE: "text-red-400",
70664
71399
  HEAD: "text-purple-400",
70665
- OPTIONS: "text-gray-400"
71400
+ OPTIONS: "text-gray-400",
71401
+ QUERY: "text-fuchsia-400"
70666
71402
  };
70667
71403
  const TabRow = React$2.memo(function TabRow2({
70668
71404
  tabId,
@@ -70730,6 +71466,7 @@ function App() {
70730
71466
  const setCommandPaletteOpen = useStore((s) => s.setCommandPaletteOpen);
70731
71467
  const setWsStatus = useStore((s) => s.setWsStatus);
70732
71468
  const addWsMessage = useStore((s) => s.addWsMessage);
71469
+ const [fuzzReport, setFuzzReport] = reactExports.useState(null);
70733
71470
  const [sidebarOpen, setSidebarOpen] = reactExports.useState(true);
70734
71471
  const [responseOpen, setResponseOpen] = reactExports.useState(false);
70735
71472
  const [docsModalOpen, setDocsModalOpen] = reactExports.useState(false);
@@ -70831,7 +71568,7 @@ function App() {
70831
71568
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
70832
71569
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
70833
71570
  "v",
70834
- "0.3.5"
71571
+ "0.3.6"
70835
71572
  ] }),
70836
71573
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
70837
71574
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -70920,7 +71657,7 @@ function App() {
70920
71657
  )
70921
71658
  ] })
70922
71659
  ] }),
70923
- sidebarTab === "collections" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionTree, {}) : sidebarTab === "history" ? /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryPanel, {}) : sidebarTab === "mocks" ? /* @__PURE__ */ jsxRuntimeExports.jsx(MockPanel, {}) : sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx(GitPanel, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractPanel, {})
71660
+ sidebarTab === "collections" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionTree, {}) : sidebarTab === "history" ? /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryPanel, {}) : sidebarTab === "mocks" ? /* @__PURE__ */ jsxRuntimeExports.jsx(MockPanel, {}) : sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx(GitPanel, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractPanel, { fuzzReport, setFuzzReport })
70924
71661
  ] }),
70925
71662
  /* @__PURE__ */ jsxRuntimeExports.jsx(
70926
71663
  "div",
@@ -71028,13 +71765,13 @@ function App() {
71028
71765
  }
71029
71766
  )
71030
71767
  ] }),
71031
- sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitDiffPane, {}) }) : sidebarTab === "contracts" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ContractResultsPanel, {}) }) : sidebarTab === "mocks" && recorderRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
71768
+ sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitDiffPane, {}) }) : sidebarTab === "contracts" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: fuzzReport ? /* @__PURE__ */ jsxRuntimeExports.jsx(FuzzResultsPanel, { report: fuzzReport, onClear: () => setFuzzReport(null) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractResultsPanel, {}) }) : sidebarTab === "mocks" && recorderRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
71032
71769
  RecorderPanel,
71033
71770
  {
71034
71771
  defaultTargetMockId: recorderTargetMockId,
71035
71772
  onClose: () => setRecorderRunning(false),
71036
71773
  onImportMock: async (session, targetMockId) => {
71037
- const name2 = `Recorded ${new URL(session.upstream).hostname}`;
71774
+ const name2 = `Recorded - ${new URL(session.upstream).hostname}`;
71038
71775
  const newRoutes = await electron.recordToMock(session.entries, session.upstream, name2, session.port);
71039
71776
  if (targetMockId) {
71040
71777
  const existing = useStore.getState().mocks[targetMockId];