@testsmith/api-spector 0.6.2 → 0.6.4

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.
Files changed (29) hide show
  1. package/out/main/agents.js +5 -5
  2. package/out/main/chunks/{broker-client-WEymdqAK.js → broker-client-CvRuiX3o.js} +26 -23
  3. package/out/main/chunks/{cli-common-vASoDfQH.js → cli-common-Cq_cphpZ.js} +4 -4
  4. package/out/main/chunks/{coverage-C54mZEh_.js → coverage-C0U4QM83.js} +8 -10
  5. package/out/main/chunks/{data-files-Br2h3Dt2.js → data-files-BYqhsoPe.js} +4 -4
  6. package/out/main/chunks/escape-DDVAlOvZ.js +9 -0
  7. package/out/main/chunks/{import-DdXhO56X.js → import-DYssQzex.js} +4 -4
  8. package/out/main/chunks/{mock-server-BxiXhP1m.js → mock-server-C4kTFkod.js} +9 -11
  9. package/out/main/chunks/{recorder-0Ij921El.js → recorder-BkO8G_mB.js} +2 -2
  10. package/out/main/chunks/{request-collection-Hqh3I3rs.js → request-collection-KGPuvN7o.js} +5 -4
  11. package/out/main/chunks/{request-exec-DAFA6Fap.js → request-exec-BRmVweue.js} +41 -41
  12. package/out/main/chunks/{soap-handler-BAPCALkN.js → soap-handler-BznuMFIP.js} +7 -9
  13. package/out/main/compare.js +10 -10
  14. package/out/main/contract.js +18 -20
  15. package/out/main/coverage.js +7 -7
  16. package/out/main/generate-tests.js +9 -9
  17. package/out/main/index.js +166 -263
  18. package/out/main/lib.js +6 -7
  19. package/out/main/mock.js +8 -8
  20. package/out/main/record.js +9 -9
  21. package/out/main/runner.js +50 -22
  22. package/out/main/wsdl.js +9 -9
  23. package/out/preload/index.js +1 -1
  24. package/out/renderer/assets/{GraphQLEditor-DtppmZJO.js → GraphQLEditor-Bih8eb_r.js} +6 -6
  25. package/out/renderer/assets/{SoapEditor-BwSm12wH.js → SoapEditor-D1sQKu7l.js} +3 -3
  26. package/out/renderer/assets/{index-D6-zmXen.js → index-vEDOfWlE.js} +418 -279
  27. package/out/renderer/index.html +1 -1
  28. package/package.json +1 -1
  29. package/out/main/chunks/escape-BwPM4Tf6.js +0 -9
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./GraphQLEditor-DtppmZJO.js","./codemirror-F6m3Hm1M.js","./ajv-2MAg6kGF.js","./SoapEditor-BwSm12wH.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./GraphQLEditor-Bih8eb_r.js","./codemirror-F6m3Hm1M.js","./ajv-2MAg6kGF.js","./SoapEditor-D1sQKu7l.js"])))=>i.map(i=>d[i]);
2
2
  import { r as reactExports, g as getDefaultExportFromCjs, R as React$2, a as autocompletion, h as hoverTooltip, j as jsxRuntimeExports, k as keymap, t as toggleComment, x as xml, b as json, c as ReactCodeMirror, o as oneDark, d as javascript } from "./codemirror-F6m3Hm1M.js";
3
3
  import { A as Ajv } from "./ajv-2MAg6kGF.js";
4
4
  var client = { exports: {} };
@@ -12797,132 +12797,6 @@ const immerImpl = (initializer) => (set2, get2, store) => {
12797
12797
  return initializer(store.setState, get2, store);
12798
12798
  };
12799
12799
  const immer = immerImpl;
12800
- function uniqueName(base, existing) {
12801
- if (!existing.includes(base)) return base;
12802
- let i = 2;
12803
- while (existing.includes(`${base} (${i})`)) i++;
12804
- return `${base} (${i})`;
12805
- }
12806
- function colRelPath(name, id) {
12807
- const safe = safeName(name) || id.slice(0, 8);
12808
- return `collections/${safe}.spector`;
12809
- }
12810
- function envRelPath(name, id) {
12811
- const safe = safeName(name) || id;
12812
- return `environments/${safe}.env.json`;
12813
- }
12814
- function safeName(name) {
12815
- return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
12816
- }
12817
- let getRandomValues;
12818
- const rnds8 = new Uint8Array(16);
12819
- function rng() {
12820
- if (!getRandomValues) {
12821
- getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
12822
- if (!getRandomValues) {
12823
- throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
12824
- }
12825
- }
12826
- return getRandomValues(rnds8);
12827
- }
12828
- const byteToHex = [];
12829
- for (let i = 0; i < 256; ++i) {
12830
- byteToHex.push((i + 256).toString(16).slice(1));
12831
- }
12832
- function unsafeStringify(arr, offset = 0) {
12833
- return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
12834
- }
12835
- const randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
12836
- const native = {
12837
- randomUUID
12838
- };
12839
- function v4(options, buf, offset) {
12840
- if (native.randomUUID && true && !options) {
12841
- return native.randomUUID();
12842
- }
12843
- options = options || {};
12844
- const rnds = options.random || (options.rng || rng)();
12845
- rnds[6] = rnds[6] & 15 | 64;
12846
- rnds[8] = rnds[8] & 63 | 128;
12847
- return unsafeStringify(rnds);
12848
- }
12849
- function orderedChildren(folder2) {
12850
- const reqSet = new Set(folder2.requestIds);
12851
- const folderSet = new Set(folder2.folders.map((f) => f.id));
12852
- const seen2 = /* @__PURE__ */ new Set();
12853
- const out = [];
12854
- for (const ref of folder2.childOrder ?? []) {
12855
- const key = `${ref.type}:${ref.id}`;
12856
- if (seen2.has(key)) continue;
12857
- if (ref.type === "request" && reqSet.has(ref.id) || ref.type === "folder" && folderSet.has(ref.id)) {
12858
- out.push({ type: ref.type, id: ref.id });
12859
- seen2.add(key);
12860
- }
12861
- }
12862
- for (const id of folder2.requestIds) if (!seen2.has(`request:${id}`)) out.push({ type: "request", id });
12863
- for (const f of folder2.folders) if (!seen2.has(`folder:${f.id}`)) out.push({ type: "folder", id: f.id });
12864
- return out;
12865
- }
12866
- function findFolder$1(root2, id) {
12867
- if (root2.id === id) return root2;
12868
- for (const sub of root2.folders) {
12869
- const found = findFolder$1(sub, id);
12870
- if (found) return found;
12871
- }
12872
- return null;
12873
- }
12874
- function findFolderContaining(root2, reqId) {
12875
- if (root2.requestIds.includes(reqId)) return root2;
12876
- for (const sub of root2.folders) {
12877
- const found = findFolderContaining(sub, reqId);
12878
- if (found) return found;
12879
- }
12880
- return null;
12881
- }
12882
- function removeFromFolder(root2, reqId) {
12883
- root2.requestIds = root2.requestIds.filter((id) => id !== reqId);
12884
- root2.folders.forEach((sub) => removeFromFolder(sub, reqId));
12885
- }
12886
- function removeFolderById(parent, folderId) {
12887
- const idx = parent.folders.findIndex((f) => f.id === folderId);
12888
- if (idx !== -1) {
12889
- parent.folders.splice(idx, 1);
12890
- return true;
12891
- }
12892
- for (const sub of parent.folders) {
12893
- if (removeFolderById(sub, folderId)) return true;
12894
- }
12895
- return false;
12896
- }
12897
- function findFolderParent(root2, folderId) {
12898
- for (const sub of root2.folders) {
12899
- if (sub.id === folderId) return root2;
12900
- const found = findFolderParent(sub, folderId);
12901
- if (found) return found;
12902
- }
12903
- return null;
12904
- }
12905
- function findFolderPath(root2, requestId) {
12906
- if (root2.requestIds.includes(requestId)) return [root2];
12907
- for (const sub of root2.folders) {
12908
- const path = findFolderPath(sub, requestId);
12909
- if (path.length > 0) return [root2, ...path];
12910
- }
12911
- return [];
12912
- }
12913
- function collectRequestIds$1(f) {
12914
- return [...f.requestIds, ...f.folders.flatMap(collectRequestIds$1)];
12915
- }
12916
- function reIdFolderTree(root2, mapRequestId) {
12917
- root2.id = v4();
12918
- root2.requestIds = root2.requestIds.map(mapRequestId);
12919
- root2.folders.forEach((sub) => reIdFolderTree(sub, mapRequestId));
12920
- }
12921
- function cloneAndReId(folder2, mapRequestId) {
12922
- const cloned = JSON.parse(JSON.stringify(folder2));
12923
- reIdFolderTree(cloned, mapRequestId);
12924
- return cloned;
12925
- }
12926
12800
  const WS_MESSAGE_CAP = 1e3;
12927
12801
  const createWsSlice = (set2) => ({
12928
12802
  wsConnections: {},
@@ -12988,7 +12862,7 @@ const createStreamSlice = (set2) => ({
12988
12862
  s.liveStream = { streamId, tabId, events: [], streaming: true };
12989
12863
  }),
12990
12864
  pushLiveStreamEvents: (streamId, events) => set2((s) => {
12991
- if (!s.liveStream || s.liveStream.streamId !== streamId) return;
12865
+ if (s.liveStream?.streamId !== streamId) return;
12992
12866
  s.liveStream.events.push(...events);
12993
12867
  const overflow = s.liveStream.events.length - LIVE_EVENT_CAP;
12994
12868
  if (overflow > 0) s.liveStream.events.splice(0, overflow);
@@ -13088,7 +12962,7 @@ const createContractSlice = (set2) => ({
13088
12962
  loadContractSnapshot: (relPath, snapshot) => set2((s) => {
13089
12963
  s.contractSnapshots[relPath] = snapshot;
13090
12964
  if (s.workspace) {
13091
- if (!s.workspace.contracts) s.workspace.contracts = [];
12965
+ s.workspace.contracts ??= [];
13092
12966
  if (!s.workspace.contracts.includes(relPath)) s.workspace.contracts.push(relPath);
13093
12967
  }
13094
12968
  }),
@@ -13111,6 +12985,38 @@ const createContractSlice = (set2) => ({
13111
12985
  s.contractDesignerSeed = null;
13112
12986
  })
13113
12987
  });
12988
+ let getRandomValues;
12989
+ const rnds8 = new Uint8Array(16);
12990
+ function rng() {
12991
+ if (!getRandomValues) {
12992
+ getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
12993
+ if (!getRandomValues) {
12994
+ throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
12995
+ }
12996
+ }
12997
+ return getRandomValues(rnds8);
12998
+ }
12999
+ const byteToHex = [];
13000
+ for (let i = 0; i < 256; ++i) {
13001
+ byteToHex.push((i + 256).toString(16).slice(1));
13002
+ }
13003
+ function unsafeStringify(arr, offset = 0) {
13004
+ return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
13005
+ }
13006
+ const randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
13007
+ const native = {
13008
+ randomUUID
13009
+ };
13010
+ function v4(options, buf, offset) {
13011
+ if (native.randomUUID && true && !options) {
13012
+ return native.randomUUID();
13013
+ }
13014
+ options = options || {};
13015
+ const rnds = options.random || (options.rng || rng)();
13016
+ rnds[6] = rnds[6] & 15 | 64;
13017
+ rnds[8] = rnds[8] & 63 | 128;
13018
+ return unsafeStringify(rnds);
13019
+ }
13114
13020
  function makeTab(requestId, collectionId, opts = {}) {
13115
13021
  return {
13116
13022
  id: v4(),
@@ -13241,6 +13147,100 @@ const createTabsSlice = (set2, get2) => ({
13241
13147
  if (tab) tab.requestTab = t;
13242
13148
  })
13243
13149
  });
13150
+ function uniqueName(base, existing) {
13151
+ if (!existing.includes(base)) return base;
13152
+ let i = 2;
13153
+ while (existing.includes(`${base} (${i})`)) i++;
13154
+ return `${base} (${i})`;
13155
+ }
13156
+ function colRelPath(name, id) {
13157
+ const safe = safeName(name) || id.slice(0, 8);
13158
+ return `collections/${safe}.spector`;
13159
+ }
13160
+ function envRelPath(name, id) {
13161
+ const safe = safeName(name) || id;
13162
+ return `environments/${safe}.env.json`;
13163
+ }
13164
+ function safeName(name) {
13165
+ return name.trim().toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-_]/g, "").replaceAll(/-+/g, "-").replaceAll(/^-|-$/g, "");
13166
+ }
13167
+ function orderedChildren(folder2) {
13168
+ const reqSet = new Set(folder2.requestIds);
13169
+ const folderSet = new Set(folder2.folders.map((f) => f.id));
13170
+ const seen2 = /* @__PURE__ */ new Set();
13171
+ const out = [];
13172
+ for (const ref of folder2.childOrder ?? []) {
13173
+ const key = `${ref.type}:${ref.id}`;
13174
+ if (seen2.has(key)) continue;
13175
+ if (ref.type === "request" && reqSet.has(ref.id) || ref.type === "folder" && folderSet.has(ref.id)) {
13176
+ out.push({ type: ref.type, id: ref.id });
13177
+ seen2.add(key);
13178
+ }
13179
+ }
13180
+ for (const id of folder2.requestIds) if (!seen2.has(`request:${id}`)) out.push({ type: "request", id });
13181
+ for (const f of folder2.folders) if (!seen2.has(`folder:${f.id}`)) out.push({ type: "folder", id: f.id });
13182
+ return out;
13183
+ }
13184
+ function findFolder$1(root2, id) {
13185
+ if (root2.id === id) return root2;
13186
+ for (const sub of root2.folders) {
13187
+ const found = findFolder$1(sub, id);
13188
+ if (found) return found;
13189
+ }
13190
+ return null;
13191
+ }
13192
+ function findFolderContaining(root2, reqId) {
13193
+ if (root2.requestIds.includes(reqId)) return root2;
13194
+ for (const sub of root2.folders) {
13195
+ const found = findFolderContaining(sub, reqId);
13196
+ if (found) return found;
13197
+ }
13198
+ return null;
13199
+ }
13200
+ function removeFromFolder(root2, reqId) {
13201
+ root2.requestIds = root2.requestIds.filter((id) => id !== reqId);
13202
+ root2.folders.forEach((sub) => removeFromFolder(sub, reqId));
13203
+ }
13204
+ function removeFolderById(parent, folderId) {
13205
+ const idx = parent.folders.findIndex((f) => f.id === folderId);
13206
+ if (idx !== -1) {
13207
+ parent.folders.splice(idx, 1);
13208
+ return true;
13209
+ }
13210
+ for (const sub of parent.folders) {
13211
+ if (removeFolderById(sub, folderId)) return true;
13212
+ }
13213
+ return false;
13214
+ }
13215
+ function findFolderParent(root2, folderId) {
13216
+ for (const sub of root2.folders) {
13217
+ if (sub.id === folderId) return root2;
13218
+ const found = findFolderParent(sub, folderId);
13219
+ if (found) return found;
13220
+ }
13221
+ return null;
13222
+ }
13223
+ function findFolderPath(root2, requestId) {
13224
+ if (root2.requestIds.includes(requestId)) return [root2];
13225
+ for (const sub of root2.folders) {
13226
+ const path = findFolderPath(sub, requestId);
13227
+ if (path.length > 0) return [root2, ...path];
13228
+ }
13229
+ return [];
13230
+ }
13231
+ function collectRequestIds$1(f) {
13232
+ return [...f.requestIds, ...f.folders.flatMap(collectRequestIds$1)];
13233
+ }
13234
+ function reIdFolderTree(root2, mapRequestId) {
13235
+ root2.id = v4();
13236
+ root2.requestIds = root2.requestIds.map(mapRequestId);
13237
+ root2.folders.forEach((sub) => reIdFolderTree(sub, mapRequestId));
13238
+ }
13239
+ function cloneAndReId(folder2, mapRequestId) {
13240
+ const cloned = structuredClone(folder2);
13241
+ reIdFolderTree(cloned, mapRequestId);
13242
+ return cloned;
13243
+ }
13244
13244
  const WRITE_METHODS$1 = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
13245
13245
  function newId() {
13246
13246
  try {
@@ -13250,7 +13250,7 @@ function newId() {
13250
13250
  }
13251
13251
  }
13252
13252
  function clone(base, patch) {
13253
- const copy2 = JSON.parse(JSON.stringify(base));
13253
+ const copy2 = structuredClone(base);
13254
13254
  copy2.id = newId();
13255
13255
  delete copy2.examples;
13256
13256
  delete copy2.contract;
@@ -13482,8 +13482,8 @@ const createCollectionsSlice = (set2, get2) => ({
13482
13482
  s.tabs = s.tabs.filter((t) => t.collectionId !== id);
13483
13483
  if (s.activeCollectionId === id) {
13484
13484
  s.activeCollectionId = Object.keys(s.collections)[0] ?? null;
13485
- const activeTab = s.tabs.find((t) => t.id === s.activeTabId);
13486
- if (!activeTab) s.activeTabId = s.tabs[0]?.id ?? null;
13485
+ const hasActiveTab = s.tabs.some((t) => t.id === s.activeTabId);
13486
+ if (!hasActiveTab) s.activeTabId = s.tabs[0]?.id ?? null;
13487
13487
  }
13488
13488
  });
13489
13489
  },
@@ -13629,7 +13629,7 @@ const createCollectionsSlice = (set2, get2) => ({
13629
13629
  const entry = Object.values(s.collections).find((c) => c.data.requests[requestId]);
13630
13630
  const ex = entry?.data.requests[requestId].examples?.find((e) => e.id === exampleId);
13631
13631
  if (!entry || !ex) return;
13632
- ex.request = { ...ex.request ?? {}, ...patch };
13632
+ ex.request = { ...ex.request, ...patch };
13633
13633
  entry.dirty = true;
13634
13634
  }),
13635
13635
  updateExampleResponse: (requestId, exampleId, response) => set2((s) => {
@@ -13711,7 +13711,7 @@ const createCollectionsSlice = (set2, get2) => ({
13711
13711
  }),
13712
13712
  duplicateRequest: (collectionId, id) => set2((s) => {
13713
13713
  const col = s.collections[collectionId]?.data;
13714
- if (!col || !col.requests[id]) return;
13714
+ if (!col?.requests[id]) return;
13715
13715
  const orig = col.requests[id];
13716
13716
  const copy2 = { ...JSON.parse(JSON.stringify(orig)), id: v4(), name: orig.name + " (copy)" };
13717
13717
  col.requests[copy2.id] = copy2;
@@ -13834,7 +13834,7 @@ const createCollectionsSlice = (set2, get2) => ({
13834
13834
  const entry = Object.values(s.collections).find((c) => c.data.requests[requestId]);
13835
13835
  if (!entry) return;
13836
13836
  const req = entry.data.requests[requestId];
13837
- req.meta = { ...req.meta ?? {}, tags };
13837
+ req.meta = { ...req.meta, tags };
13838
13838
  entry.dirty = true;
13839
13839
  }),
13840
13840
  // ── Folder settings ─────────────────────────────────────────────────────────
@@ -13882,7 +13882,7 @@ const STORAGE_KEYS = {
13882
13882
  updateSkipped: "apiSpectorUpdateSkipped"
13883
13883
  };
13884
13884
  function envSlugRelPath(name) {
13885
- return `environments/${name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}.env.json`;
13885
+ return `environments/${name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-")}.env.json`;
13886
13886
  }
13887
13887
  const createEnvironmentsSlice = (set2, get2) => ({
13888
13888
  environments: {},
@@ -13982,7 +13982,7 @@ const createMocksSlice = (set2, get2) => ({
13982
13982
  s.mocks[mock.id] = { relPath, data: mock, running: false };
13983
13983
  s.activeMockId = mock.id;
13984
13984
  if (s.workspace) {
13985
- if (!s.workspace.mocks) s.workspace.mocks = [];
13985
+ s.workspace.mocks ??= [];
13986
13986
  s.workspace.mocks.push(relPath);
13987
13987
  }
13988
13988
  }),
@@ -14045,7 +14045,7 @@ const createUiSlice = (set2) => ({
14045
14045
  s.theme = t;
14046
14046
  localStorage.setItem(STORAGE_KEYS.theme, t);
14047
14047
  if (s.workspace) {
14048
- if (!s.workspace.settings) s.workspace.settings = {};
14048
+ s.workspace.settings ??= {};
14049
14049
  s.workspace.settings.theme = t;
14050
14050
  }
14051
14051
  if (t === "system") {
@@ -14184,7 +14184,7 @@ const useStore = create()(
14184
14184
  if (activeColId && s.collections[activeColId]) {
14185
14185
  const col = s.collections[activeColId].data;
14186
14186
  col.collectionVariables = {
14187
- ...col.collectionVariables ?? {},
14187
+ ...col.collectionVariables,
14188
14188
  ...safeColVars
14189
14189
  };
14190
14190
  s.collections[activeColId].dirty = true;
@@ -14972,7 +14972,7 @@ function useVarNames() {
14972
14972
  }
14973
14973
  const envVars = activeEnv?.variables ?? [];
14974
14974
  envVars.filter((v) => v.enabled && v.key).forEach((v) => names.add(v.key));
14975
- return [...DYNAMIC_VAR_NAMES, ...Array.from(names).sort()];
14975
+ return [...DYNAMIC_VAR_NAMES, ...Array.from(names).sort((a, b) => a.localeCompare(b))];
14976
14976
  }, [
14977
14977
  activeEnv,
14978
14978
  activeCollectionId,
@@ -23837,7 +23837,7 @@ function detectLocale() {
23837
23837
  }
23838
23838
  function interpolate(str, vars) {
23839
23839
  if (!vars) return str;
23840
- return str.replace(/:([a-zA-Z][a-zA-Z0-9]*)/g, (m, k) => k in vars ? String(vars[k]) : m);
23840
+ return str.replaceAll(/:([a-zA-Z][a-zA-Z0-9]*)/g, (m, k) => k in vars ? String(vars[k]) : m);
23841
23841
  }
23842
23842
  function translate(locale, key, vars) {
23843
23843
  let msg = CATALOGS[locale]?.[key] ?? key;
@@ -23872,7 +23872,8 @@ function I18nProvider({ children }) {
23872
23872
  setLocaleState(l);
23873
23873
  }, []);
23874
23874
  const t = reactExports.useCallback((key, vars) => translate(locale, key, vars), [locale]);
23875
- return /* @__PURE__ */ jsxRuntimeExports.jsx(I18nContext.Provider, { value: { locale, setLocale, t }, children });
23875
+ const ctx = reactExports.useMemo(() => ({ locale, setLocale, t }), [locale, setLocale, t]);
23876
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(I18nContext.Provider, { value: ctx, children });
23876
23877
  }
23877
23878
  function useT() {
23878
23879
  return reactExports.useContext(I18nContext).t;
@@ -24359,18 +24360,34 @@ function Modal({
24359
24360
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
24360
24361
  "div",
24361
24362
  {
24363
+ role: closeOnBackdrop ? "button" : void 0,
24364
+ tabIndex: closeOnBackdrop ? -1 : void 0,
24365
+ "aria-label": closeOnBackdrop ? "Close dialog" : void 0,
24362
24366
  className: `fixed inset-0 ${overlayClassName}`,
24363
- onClick: closeOnBackdrop ? onClose : void 0,
24364
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: panelClassName, onClick: (e) => e.stopPropagation(), children: [
24365
- title !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-4 py-2.5 border-b border-surface-800 flex-shrink-0", children: [
24366
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
24367
- /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold", children: title }),
24368
- subtitle !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 mt-0.5", children: subtitle })
24369
- ] }),
24370
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-[var(--text-primary)] text-lg leading-none", children: "×" })
24371
- ] }),
24372
- children
24373
- ] })
24367
+ onClick: closeOnBackdrop ? (e) => {
24368
+ if (e.target === e.currentTarget) onClose();
24369
+ } : void 0,
24370
+ onKeyDown: closeOnBackdrop ? (e) => {
24371
+ if (e.key === "Escape") onClose();
24372
+ } : void 0,
24373
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
24374
+ "div",
24375
+ {
24376
+ role: "dialog",
24377
+ "aria-modal": "true",
24378
+ className: panelClassName,
24379
+ children: [
24380
+ title !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-4 py-2.5 border-b border-surface-800 flex-shrink-0", children: [
24381
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
24382
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold", children: title }),
24383
+ subtitle !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 mt-0.5", children: subtitle })
24384
+ ] }),
24385
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-[var(--text-primary)] text-lg leading-none", children: "×" })
24386
+ ] }),
24387
+ children
24388
+ ]
24389
+ }
24390
+ )
24374
24391
  }
24375
24392
  );
24376
24393
  }
@@ -25053,7 +25070,7 @@ function normalisePath(url) {
25053
25070
  let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
25054
25071
  if (!path.startsWith("/")) path = "/" + path;
25055
25072
  path = path.split("?")[0];
25056
- path = path.replace(/\{\{([^}]+)\}\}/g, "{$1}");
25073
+ path = path.replaceAll(/\{\{([^}]+)\}\}/g, "{$1}");
25057
25074
  return path;
25058
25075
  }
25059
25076
  function collectRequestIds(folder2) {
@@ -25499,7 +25516,7 @@ function collectAllTags(folder2, requests) {
25499
25516
  f.folders.forEach(walk2);
25500
25517
  }
25501
25518
  walk2(folder2);
25502
- return Array.from(tags).sort();
25519
+ return Array.from(tags).sort((a, b) => a.localeCompare(b));
25503
25520
  }
25504
25521
  function folderPathTo(root2, requestId) {
25505
25522
  if (root2.requestIds.includes(requestId)) return [root2];
@@ -25522,11 +25539,12 @@ function getHooksForRequest(requestId, collection2) {
25522
25539
  before.push(...bAll, ...bEach);
25523
25540
  afterReversed.push(...aEach, ...aAll);
25524
25541
  }
25525
- return { before, after: afterReversed.reverse() };
25542
+ afterReversed.reverse();
25543
+ return { before, after: afterReversed };
25526
25544
  }
25527
25545
  function authIsConfigured(auth) {
25528
25546
  if (!auth || auth.type === "none") return false;
25529
- const set2 = (s) => !!(s && s.trim());
25547
+ const set2 = (s) => !!s?.trim();
25530
25548
  switch (auth.type) {
25531
25549
  case "bearer":
25532
25550
  return set2(auth.token) || set2(auth.tokenSecretRef);
@@ -25557,8 +25575,9 @@ function resolveInheritedAuthAndHeaders(requestId, collection2) {
25557
25575
  }
25558
25576
  function buildRunPlan(collection2, folderId, filterTags) {
25559
25577
  const collectionVars = collection2.collectionVariables ?? {};
25578
+ const tag = (items) => items.map((i) => ({ ...i, collection: collection2.name }));
25560
25579
  if (!folderId) {
25561
- return buildFolderPlan(
25580
+ return tag(buildFolderPlan(
25562
25581
  collection2.rootFolder,
25563
25582
  collection2.requests,
25564
25583
  collectionVars,
@@ -25568,7 +25587,7 @@ function buildRunPlan(collection2, folderId, filterTags) {
25568
25587
  [],
25569
25588
  [],
25570
25589
  true
25571
- );
25590
+ ));
25572
25591
  }
25573
25592
  const chain = folderChainTo(collection2.rootFolder, folderId);
25574
25593
  if (chain.length === 0) return [];
@@ -25615,7 +25634,7 @@ function buildRunPlan(collection2, folderId, filterTags) {
25615
25634
  result.push(makeHook(req, collectionVars, "afterAll", f.id, myAncestors, []));
25616
25635
  }
25617
25636
  }
25618
- return result;
25637
+ return tag(result);
25619
25638
  }
25620
25639
  function expandRunPlanWithData(items, ds) {
25621
25640
  if (!ds || ds.rows.length === 0) return items;
@@ -25641,7 +25660,7 @@ function parseDataRow(columns, row) {
25641
25660
  const key = col.toLowerCase();
25642
25661
  const val = row[ci] ?? "";
25643
25662
  if (RESERVED_STATUS.has(key)) {
25644
- const nums = val.split(/[\s,|]+/).map((x) => parseInt(x, 10)).filter((n) => Number.isFinite(n));
25663
+ const nums = val.split(/[\s,|]+/).map((x) => Number.parseInt(x, 10)).filter((n) => Number.isFinite(n));
25645
25664
  if (nums.length) expectStatus = nums;
25646
25665
  } else if (RESERVED_OWASP.has(key)) {
25647
25666
  if (val.trim()) owaspTag = val.trim().toUpperCase();
@@ -25726,7 +25745,7 @@ async function pushRequestAsMonitor(request2, opts) {
25726
25745
  const s = useStore.getState();
25727
25746
  const colEntry = Object.values(s.collections).find((c) => c.data.requests[request2.id]);
25728
25747
  const collectionVars = {
25729
- ...colEntry?.data.collectionVariables ?? {},
25748
+ ...colEntry?.data.collectionVariables,
25730
25749
  ...s.getInheritedVariables(request2.id),
25731
25750
  ...s.sessionVars
25732
25751
  };
@@ -25948,12 +25967,13 @@ function InlineEdit({
25948
25967
  }
25949
25968
  onCommit(trimmed);
25950
25969
  }
25951
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { onClick: (e) => e.stopPropagation(), children: [
25970
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
25952
25971
  /* @__PURE__ */ jsxRuntimeExports.jsx(
25953
25972
  "input",
25954
25973
  {
25955
25974
  ref,
25956
25975
  value: draft,
25976
+ onClick: (e) => e.stopPropagation(),
25957
25977
  onChange: (e) => {
25958
25978
  setDraft(e.target.value);
25959
25979
  setError(null);
@@ -26045,6 +26065,8 @@ function ContextMenu({ items, x, y, onClose }) {
26045
26065
  "div",
26046
26066
  {
26047
26067
  ref,
26068
+ role: "menu",
26069
+ tabIndex: -1,
26048
26070
  style: { position: "fixed", top: pos.top, left: pos.left, zIndex: 9999 },
26049
26071
  className: "bg-surface-900 border border-surface-700 rounded-lg shadow-2xl py-1 min-w-[170px]",
26050
26072
  onMouseDown: (e) => e.stopPropagation(),
@@ -26095,6 +26117,14 @@ function DotsBtn({ items }) {
26095
26117
  menu && /* @__PURE__ */ jsxRuntimeExports.jsx(ContextMenu, { items, x: menu.x, y: menu.y, onClose: () => setMenu(null) })
26096
26118
  ] });
26097
26119
  }
26120
+ function onActivateKey(handler) {
26121
+ return (e) => {
26122
+ if (e.key === "Enter" || e.key === " ") {
26123
+ e.preventDefault();
26124
+ handler();
26125
+ }
26126
+ };
26127
+ }
26098
26128
  function useToast(durationMs = 3e3) {
26099
26129
  const [toast, setToast] = reactExports.useState(null);
26100
26130
  const timer = reactExports.useRef(null);
@@ -26201,6 +26231,14 @@ function RequestRow({
26201
26231
  "div",
26202
26232
  {
26203
26233
  draggable: true,
26234
+ role: "button",
26235
+ tabIndex: 0,
26236
+ onKeyDown: (e) => {
26237
+ if (e.key === "Enter" || e.key === " ") {
26238
+ e.preventDefault();
26239
+ handleRowClick(e);
26240
+ }
26241
+ },
26204
26242
  className: `group flex items-start gap-1.5 py-1 pr-1 rounded-sm cursor-pointer transition-colors ${disabled ? "opacity-40" : ""} ${selected ? "bg-blue-950/40 ring-1 ring-inset ring-blue-500" : isActive ? "bg-surface-800 text-[var(--text-primary)]" : "text-surface-300 hover:bg-surface-800"}`,
26205
26243
  style: { paddingLeft: indent },
26206
26244
  onClick: handleRowClick,
@@ -26350,7 +26388,10 @@ function ExampleRow({ name, indent, isActive, onOpen, onRename, onDelete, onDupl
26350
26388
  {
26351
26389
  className: `group flex items-center gap-1.5 py-1 pr-1 rounded-sm cursor-pointer transition-colors ${isActive ? "bg-surface-800 text-[var(--text-primary)]" : "text-surface-400 hover:bg-surface-800"}`,
26352
26390
  style: { paddingLeft: indent },
26391
+ role: "button",
26392
+ tabIndex: 0,
26353
26393
  onClick: onOpen,
26394
+ onKeyDown: onActivateKey(onOpen),
26354
26395
  onDoubleClick: () => setRenaming(true),
26355
26396
  children: [
26356
26397
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[8px] font-bold px-1 py-px rounded bg-surface-700 text-surface-300", title: t("Example"), children: "EX" }),
@@ -26466,46 +26507,56 @@ function TagChips({
26466
26507
  setAdding(false);
26467
26508
  onDoneAdding?.();
26468
26509
  }
26469
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-0.5 mt-0.5", onClick: (e) => e.stopPropagation(), children: [
26470
- tags.map((tag) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
26471
- "span",
26472
- {
26473
- className: "flex items-center gap-0.5 px-1 py-px rounded text-[9px] bg-blue-900/50 text-blue-300 border border-blue-800/50",
26474
- children: [
26475
- tag,
26476
- /* @__PURE__ */ jsxRuntimeExports.jsx(
26477
- "button",
26478
- {
26479
- onClick: () => onRemove(tag),
26480
- className: "hover:text-red-400 leading-none ml-0.5",
26481
- children: "×"
26482
- }
26483
- )
26484
- ]
26485
- },
26486
- tag
26487
- )),
26488
- adding ? /* @__PURE__ */ jsxRuntimeExports.jsx(
26489
- "input",
26490
- {
26491
- ref: inputRef,
26492
- value: draft,
26493
- onChange: (e) => setDraft(e.target.value),
26494
- onKeyDown: (e) => {
26495
- if (e.key === "Enter") commit();
26496
- if (e.key === "Escape") {
26497
- setAdding(false);
26498
- setDraft("");
26499
- onDoneAdding?.();
26510
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
26511
+ "div",
26512
+ {
26513
+ role: "group",
26514
+ "aria-label": "Tags",
26515
+ className: "flex flex-wrap items-center gap-0.5 mt-0.5",
26516
+ onClick: (e) => e.stopPropagation(),
26517
+ onKeyDown: (e) => e.stopPropagation(),
26518
+ children: [
26519
+ tags.map((tag) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
26520
+ "span",
26521
+ {
26522
+ className: "flex items-center gap-0.5 px-1 py-px rounded text-[9px] bg-blue-900/50 text-blue-300 border border-blue-800/50",
26523
+ children: [
26524
+ tag,
26525
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
26526
+ "button",
26527
+ {
26528
+ onClick: () => onRemove(tag),
26529
+ className: "hover:text-red-400 leading-none ml-0.5",
26530
+ children: "×"
26531
+ }
26532
+ )
26533
+ ]
26534
+ },
26535
+ tag
26536
+ )),
26537
+ adding ? /* @__PURE__ */ jsxRuntimeExports.jsx(
26538
+ "input",
26539
+ {
26540
+ ref: inputRef,
26541
+ value: draft,
26542
+ onChange: (e) => setDraft(e.target.value),
26543
+ onKeyDown: (e) => {
26544
+ if (e.key === "Enter") commit();
26545
+ if (e.key === "Escape") {
26546
+ setAdding(false);
26547
+ setDraft("");
26548
+ onDoneAdding?.();
26549
+ }
26550
+ e.stopPropagation();
26551
+ },
26552
+ onBlur: commit,
26553
+ className: "w-16 text-[9px] bg-surface-700 rounded px-1 py-px focus:outline-none focus:ring-1 focus:ring-blue-500",
26554
+ placeholder: t("tag…")
26500
26555
  }
26501
- e.stopPropagation();
26502
- },
26503
- onBlur: commit,
26504
- className: "w-16 text-[9px] bg-surface-700 rounded px-1 py-px focus:outline-none focus:ring-1 focus:ring-blue-500",
26505
- placeholder: t("tag…")
26506
- }
26507
- ) : null
26508
- ] });
26556
+ ) : null
26557
+ ]
26558
+ }
26559
+ );
26509
26560
  }
26510
26561
  function methodColor$2(method) {
26511
26562
  switch (method) {
@@ -26602,12 +26653,12 @@ function CollectionTree() {
26602
26653
  } });
26603
26654
  }
26604
26655
  function onDropRequest(destCollectionId, destFolderId, destIndex) {
26605
- if (!dragging || dragging.type !== "request") return;
26656
+ if (dragging?.type !== "request") return;
26606
26657
  moveRequest(dragging.collectionId, dragging.requestId, destCollectionId, destFolderId, destIndex);
26607
26658
  setDragging(null);
26608
26659
  }
26609
26660
  function onDropFolder(destCollectionId, destParentFolderId, destIndex) {
26610
- if (!dragging || dragging.type !== "folder") return;
26661
+ if (dragging?.type !== "folder") return;
26611
26662
  if (dragging.collectionId !== destCollectionId) return;
26612
26663
  moveFolder(dragging.collectionId, dragging.folderId, destParentFolderId, destIndex);
26613
26664
  setDragging(null);
@@ -26830,10 +26881,16 @@ function CollectionNode({
26830
26881
  "div",
26831
26882
  {
26832
26883
  className: `group flex items-center gap-1 px-2 py-1.5 cursor-pointer hover:bg-surface-800 transition-colors ${panelActive ? "bg-surface-800 text-[var(--text-primary)]" : isActive ? "text-[var(--text-primary)]" : "text-surface-400"} ${col.disabled ? "opacity-50" : ""} ${dropOver ? "outline outline-1 outline-blue-500 rounded" : ""}`,
26884
+ role: "button",
26885
+ tabIndex: 0,
26833
26886
  onClick: () => {
26834
26887
  onSelectCollection();
26835
26888
  setExpanded((e) => !e);
26836
26889
  },
26890
+ onKeyDown: onActivateKey(() => {
26891
+ onSelectCollection();
26892
+ setExpanded((e) => !e);
26893
+ }),
26837
26894
  onDragOver: dragCtx.dragging ? (e) => {
26838
26895
  e.preventDefault();
26839
26896
  setDropOver(true);
@@ -26989,10 +27046,16 @@ function FolderRow({
26989
27046
  draggable: true,
26990
27047
  className: `group flex items-center gap-1 py-1 transition-colors cursor-pointer ${isActive ? "bg-surface-800 text-[var(--text-primary)]" : "text-surface-400 hover:bg-surface-800"} ${folder2.disabled ? "opacity-40" : ""} ${dropInside ? "outline outline-1 outline-blue-500 rounded" : ""}`,
26991
27048
  style: { paddingLeft: indent },
27049
+ role: "button",
27050
+ tabIndex: 0,
26992
27051
  onClick: () => {
26993
27052
  openFolderPanel(collectionId, folder2.id);
26994
27053
  setExpanded((e) => !e);
26995
27054
  },
27055
+ onKeyDown: onActivateKey(() => {
27056
+ openFolderPanel(collectionId, folder2.id);
27057
+ setExpanded((e) => !e);
27058
+ }),
26996
27059
  onDragStart: (e) => {
26997
27060
  e.dataTransfer.effectAllowed = "move";
26998
27061
  e.stopPropagation();
@@ -27375,8 +27438,8 @@ function jsonWithComments() {
27375
27438
  function xmlWithComments() {
27376
27439
  return [xml(), commentKeymap];
27377
27440
  }
27378
- const GraphQLEditor = reactExports.lazy(() => __vitePreload(() => import("./GraphQLEditor-DtppmZJO.js"), true ? __vite__mapDeps([0,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.GraphQLEditor })));
27379
- const SoapEditor = reactExports.lazy(() => __vitePreload(() => import("./SoapEditor-BwSm12wH.js"), true ? __vite__mapDeps([3,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.SoapEditor })));
27441
+ const GraphQLEditor = reactExports.lazy(() => __vitePreload(() => import("./GraphQLEditor-Bih8eb_r.js"), true ? __vite__mapDeps([0,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.GraphQLEditor })));
27442
+ const SoapEditor = reactExports.lazy(() => __vitePreload(() => import("./SoapEditor-D1sQKu7l.js"), true ? __vite__mapDeps([3,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.SoapEditor })));
27380
27443
  function BodyTab({ request: request2, onChange }) {
27381
27444
  const t = useT();
27382
27445
  const editorFallback = /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-xs text-surface-500", children: t("Loading editor…") });
@@ -27608,7 +27671,7 @@ function AuthTab({ request: request2, onChange }) {
27608
27671
  {
27609
27672
  type: "number",
27610
27673
  value: auth.oauth2RedirectPort ?? 9876,
27611
- onChange: (e) => setAuth({ oauth2RedirectPort: parseInt(e.target.value, 10) || 9876 }),
27674
+ onChange: (e) => setAuth({ oauth2RedirectPort: Number.parseInt(e.target.value, 10) || 9876 }),
27612
27675
  className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500"
27613
27676
  }
27614
27677
  )
@@ -28231,17 +28294,17 @@ function renderInline(text, keyBase) {
28231
28294
  });
28232
28295
  }
28233
28296
  function renderMarkdown(md) {
28234
- const lines = md.replace(/\r\n/g, "\n").split("\n");
28297
+ const lines = md.replaceAll("\r\n", "\n").split("\n");
28235
28298
  const blocks = [];
28236
28299
  let i = 0;
28237
28300
  let key = 0;
28238
28301
  const k = () => `md-${key++}`;
28239
28302
  while (i < lines.length) {
28240
28303
  const line = lines[i];
28241
- if (/^```/.test(line.trim())) {
28304
+ if (line.trim().startsWith("```")) {
28242
28305
  const buf2 = [];
28243
28306
  i++;
28244
- while (i < lines.length && !/^```/.test(lines[i].trim())) {
28307
+ while (i < lines.length && !lines[i].trim().startsWith("```")) {
28245
28308
  buf2.push(lines[i]);
28246
28309
  i++;
28247
28310
  }
@@ -28300,7 +28363,7 @@ function renderMarkdown(md) {
28300
28363
  continue;
28301
28364
  }
28302
28365
  const buf = [];
28303
- while (i < lines.length && lines[i].trim() !== "" && !/^```/.test(lines[i].trim()) && !/^(#{1,6})\s+/.test(lines[i]) && !/^\s*>\s?/.test(lines[i]) && !/^\s*[-*+]\s+/.test(lines[i]) && !/^\s*\d+\.\s+/.test(lines[i]) && !/^\s*([-*_])\1{2,}\s*$/.test(lines[i])) {
28366
+ while (i < lines.length && lines[i].trim() !== "" && !lines[i].trim().startsWith("```") && !/^(#{1,6})\s+/.test(lines[i]) && !/^\s*>\s?/.test(lines[i]) && !/^\s*[-*+]\s+/.test(lines[i]) && !/^\s*\d+\.\s+/.test(lines[i]) && !/^\s*([-*_])\1{2,}\s*$/.test(lines[i])) {
28304
28367
  buf.push(lines[i]);
28305
28368
  i++;
28306
28369
  }
@@ -28452,7 +28515,7 @@ function useAiAvailable() {
28452
28515
  return available;
28453
28516
  }
28454
28517
  async function generateDocs(input) {
28455
- return electron$u.generateDocs({ ...input, model: getAiConfig().model });
28518
+ return electron$u.generateAiDocs({ ...input, model: getAiConfig().model });
28456
28519
  }
28457
28520
  function kvLines(label, rows) {
28458
28521
  const on = (rows ?? []).filter((r) => r.enabled !== false && r.key);
@@ -28465,8 +28528,7 @@ function requestDocsContext(req, example) {
28465
28528
  `Method: ${req.method}`,
28466
28529
  `URL: ${req.url}`
28467
28530
  ];
28468
- lines.push(...kvLines("Query params", req.params));
28469
- lines.push(...kvLines("Headers", req.headers));
28531
+ lines.push(...kvLines("Query params", req.params), ...kvLines("Headers", req.headers));
28470
28532
  if (req.auth && req.auth.type !== "none") lines.push(`Auth: ${req.auth.type}`);
28471
28533
  if (req.body && req.body.mode !== "none") {
28472
28534
  lines.push(`Request body (${req.body.mode}):`);
@@ -28474,8 +28536,7 @@ function requestDocsContext(req, example) {
28474
28536
  if (raw) lines.push(raw.slice(0, 2e3));
28475
28537
  }
28476
28538
  if (example?.response) {
28477
- lines.push(`Example response (status ${example.response.status}):`);
28478
- lines.push((example.response.body || "").slice(0, 2e3));
28539
+ lines.push(`Example response (status ${example.response.status}):`, (example.response.body || "").slice(0, 2e3));
28479
28540
  }
28480
28541
  return lines.join("\n");
28481
28542
  }
@@ -28496,8 +28557,7 @@ function collectionDocsContext(col) {
28496
28557
  const lines = [`API collection with ${reqs.length} request(s).`];
28497
28558
  const byMethod = {};
28498
28559
  for (const r of reqs) byMethod[r.method] = (byMethod[r.method] ?? 0) + 1;
28499
- lines.push("Methods: " + Object.entries(byMethod).map(([m, n]) => `${m}×${n}`).join(", "));
28500
- lines.push("Endpoints:");
28560
+ lines.push("Methods: " + Object.entries(byMethod).map(([m, n]) => `${m}×${n}`).join(", "), "Endpoints:");
28501
28561
  for (const r of reqs.slice(0, 60)) lines.push(` - ${r.method} ${r.url}`);
28502
28562
  if (reqs.length > 60) lines.push(` … and ${reqs.length - 60} more`);
28503
28563
  return lines.join("\n");
@@ -28764,7 +28824,7 @@ function textToMeta(text) {
28764
28824
  }
28765
28825
  function GrpcPanel({ request: request2, onChange }) {
28766
28826
  const t = useT();
28767
- const grpc = { ...DEFAULT_GRPC, ...request2.body.grpc ?? {} };
28827
+ const grpc = { ...DEFAULT_GRPC, ...request2.body.grpc };
28768
28828
  const grpcCalls = useStore((s) => s.grpcCalls);
28769
28829
  const setGrpcStatus = useStore((s) => s.setGrpcStatus);
28770
28830
  const addGrpcMessage = useStore((s) => s.addGrpcMessage);
@@ -29924,7 +29984,7 @@ function XmlNode({ element, depth, onLeaf }) {
29924
29984
  ] });
29925
29985
  }
29926
29986
  function esc(s) {
29927
- return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "");
29987
+ return s.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n").replaceAll(/\r/g, "");
29928
29988
  }
29929
29989
  function toLit(v) {
29930
29990
  if (v === null) return "null";
@@ -29961,7 +30021,7 @@ function toJsonPathExpr(path, filterKey, filterValue) {
29961
30021
  const prefix2 = path.slice(0, arrayIdx).join(".");
29962
30022
  const arrayPart = prefix2 ? "$." + prefix2 : "$";
29963
30023
  const leafPart = path.slice(arrayIdx + 1).join(".");
29964
- const filterVal = isNaN(Number(filterValue)) ? `"${filterValue.replace(/"/g, '\\"')}"` : filterValue;
30024
+ const filterVal = Number.isNaN(Number(filterValue)) ? `"${filterValue.replaceAll('"', '\\"')}"` : filterValue;
29965
30025
  return leafPart ? `${arrayPart}[?(@.${filterKey}==${filterVal})].${leafPart}` : `${arrayPart}[?(@.${filterKey}==${filterVal})]`;
29966
30026
  }
29967
30027
  function varNameFromPath(path) {
@@ -30003,7 +30063,7 @@ function makeJsonPathSnippet(path, value2, filterKey, filterValue) {
30003
30063
  });`;
30004
30064
  }
30005
30065
  function makeXmlSnippet(selector, value2, mode) {
30006
- const sel = selector.replace(/"/g, '\\"');
30066
+ const sel = selector.replaceAll('"', '\\"');
30007
30067
  switch (mode) {
30008
30068
  case "equals":
30009
30069
  return `sp.test('${selector} equals "${esc(value2)}"', function() {
@@ -30031,7 +30091,7 @@ function makeJsonPathExtractSnippet(path, filterKey, filterValue, target) {
30031
30091
  sp.${target}.set("${varName}", String(matches[0] ?? ''));`;
30032
30092
  }
30033
30093
  function makeXmlExtractSnippet(selector, target) {
30034
- return `sp.${target}.set("extracted_value", sp.response.xmlText("${selector.replace(/"/g, '\\"')}") ?? '');`;
30094
+ return `sp.${target}.set("extracted_value", sp.response.xmlText("${selector.replaceAll('"', '\\"')}") ?? '');`;
30035
30095
  }
30036
30096
  function AssertMenu({ state, onClose, onConfirm }) {
30037
30097
  const t = useT();
@@ -30330,7 +30390,7 @@ function findPrimaryArray(data) {
30330
30390
  const { node, path, depth } = queue.shift();
30331
30391
  if (depth > 6 || node === null || typeof node !== "object") continue;
30332
30392
  if (Array.isArray(node)) {
30333
- const key = (path.split(".").pop() ?? "").replace(/\[\d+\]/g, "");
30393
+ const key = (path.split(".").pop() ?? "").replaceAll(/\[\d+\]/g, "");
30334
30394
  const objs = node.filter((x) => x && typeof x === "object" && !Array.isArray(x)).length;
30335
30395
  found.push({ path, array: node, pref: PREFERRED.test(key) ? 1 : 0, objs, depth });
30336
30396
  } else {
@@ -30714,7 +30774,7 @@ function HookResultsPanel({ results }) {
30714
30774
  "div",
30715
30775
  {
30716
30776
  className: `flex items-center gap-2 py-0.5 ${hasDetail2 ? "cursor-pointer hover:bg-surface-800/20 rounded px-1 -mx-1" : ""}`,
30717
- onClick: () => hasDetail2 && toggle(r.requestId + i),
30777
+ ...hasDetail2 ? { role: "button", tabIndex: 0, onClick: () => toggle(r.requestId + i), onKeyDown: onActivateKey(() => toggle(r.requestId + i)) } : {},
30718
30778
  children: [
30719
30779
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-1.5 h-1.5 rounded-full shrink-0 ${r.status === "passed" ? "bg-emerald-500" : r.status === "failed" ? "bg-red-500" : "bg-orange-500"}` }),
30720
30780
  badge && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[9px] font-bold px-1 py-0.5 rounded uppercase tracking-wide shrink-0 ${badge.cls}`, children: t(badge.label) }),
@@ -30750,7 +30810,7 @@ function extractPath(url) {
30750
30810
  try {
30751
30811
  return new URL(url).pathname || "/";
30752
30812
  } catch {
30753
- const match = url.match(/(?:https?:\/\/[^/]+)?(\/[^?]*)/);
30813
+ const match = /(?:https?:\/\/[^/]+)?(\/[^?]*)/.exec(url);
30754
30814
  return match?.[1] ?? "/";
30755
30815
  }
30756
30816
  }
@@ -31220,6 +31280,7 @@ function OverflowMenu({ button, ariaLabel, title, wrapperClassName = "", buttonC
31220
31280
  ref: menuRef,
31221
31281
  id: menuId,
31222
31282
  role: "menu",
31283
+ tabIndex: -1,
31223
31284
  "aria-label": ariaLabel,
31224
31285
  onKeyDown: onMenuKeyDown,
31225
31286
  onClick: () => setOpen(false),
@@ -31253,7 +31314,7 @@ function validateHttpSemantics(res, opts = {}) {
31253
31314
  const body = hasBody(res);
31254
31315
  const contentType = header(res.headers, "content-type");
31255
31316
  const contentEncoding = header(res.headers, "content-encoding");
31256
- const clNum = Number(header(res.headers, "content-length") ?? NaN);
31317
+ const clNum = Number(header(res.headers, "content-length") ?? Number.NaN);
31257
31318
  const isBodiless = status in BODILESS || status >= 100 && status < 200;
31258
31319
  if (isBodiless) {
31259
31320
  const declaresBody = body || Number.isFinite(clNum) && clNum > 0;
@@ -31442,7 +31503,7 @@ function ResponseViewer() {
31442
31503
  const setTabScriptTab = useStore((s) => s.setTabScriptTab);
31443
31504
  const isSending = activeTab?.isSending ?? false;
31444
31505
  const liveStream = useStore((s) => s.liveStream);
31445
- const streamForTab = liveStream && liveStream.tabId === activeTabId ? liveStream : null;
31506
+ const streamForTab = liveStream?.tabId === activeTabId ? liveStream : null;
31446
31507
  const response = activeTab?.lastResponse ?? null;
31447
31508
  const scriptResult = activeTab?.lastScriptResult ?? null;
31448
31509
  const sentRequest = activeTab?.lastSentRequest ?? null;
@@ -31669,7 +31730,7 @@ function ResponseViewer() {
31669
31730
  " KB"
31670
31731
  ] })
31671
31732
  ] }),
31672
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { role: "tablist", "aria-label": t("Response sections"), onKeyDown: onTablistKey, className: "flex items-center gap-0 min-w-0 ml-1", children: [
31733
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { role: "tablist", tabIndex: -1, "aria-label": t("Response sections"), onKeyDown: onTablistKey, className: "flex items-center gap-0 min-w-0 ml-1", children: [
31673
31734
  tabList.map((def) => renderTabButton(def, "inline")),
31674
31735
  /* @__PURE__ */ jsxRuntimeExports.jsx(
31675
31736
  OverflowMenu,
@@ -31923,7 +31984,7 @@ function ResponseViewer() {
31923
31984
  type: "item",
31924
31985
  label: t('Create variable in ":env"', { env: activeEnvName ?? "" }),
31925
31986
  onClick: () => {
31926
- setVarDialog({ name: headerMenu.key.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, ""), value: headerMenu.value });
31987
+ setVarDialog({ name: headerMenu.key.replaceAll(/[^a-zA-Z0-9]+/g, "_").replaceAll(/^_+|_+$/g, ""), value: headerMenu.value });
31927
31988
  setHeaderMenu(null);
31928
31989
  }
31929
31990
  } : { type: "header", label: t("Select an environment first") }
@@ -32332,7 +32393,7 @@ function HistoryPanel() {
32332
32393
  return out;
32333
32394
  }, [history, search]);
32334
32395
  async function downloadHar() {
32335
- const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[:T]/g, "-");
32396
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replaceAll(/[:T]/g, "-");
32336
32397
  await electron$l.saveResults(historyToHar(history), `api-spector-history-${stamp}.har`);
32337
32398
  }
32338
32399
  function open2(entry) {
@@ -32536,7 +32597,7 @@ function WelcomeScreen() {
32536
32597
  )
32537
32598
  ] }),
32538
32599
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-400 text-sm max-w-sm", children: t("Local-first API testing with Robot Framework & Playwright code generation. Secrets stay on your machine.") }),
32539
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: t("version :version", { version: "0.6.2" }) })
32600
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: t("version :version", { version: "0.6.4" }) })
32540
32601
  ] }),
32541
32602
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 w-64", children: [
32542
32603
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -32806,8 +32867,7 @@ function EnvironmentEditor({ onClose }) {
32806
32867
  }
32807
32868
  function handleDelete(id) {
32808
32869
  deleteEnvironment(id);
32809
- const remaining = Object.keys(environments).filter((k) => k !== id);
32810
- setSelectedId(remaining[0] ?? "");
32870
+ setSelectedId(Object.keys(environments).find((k) => k !== id) ?? "");
32811
32871
  }
32812
32872
  function handleDuplicate(id) {
32813
32873
  duplicateEnvironment(id);
@@ -33259,7 +33319,7 @@ function EnvironmentBar({ inline = false }) {
33259
33319
  style: { color: "var(--text-primary)" },
33260
33320
  children: [
33261
33321
  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: t("No env") }),
33262
- envList.map(({ data: env2 }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env2.id, children: defaultEnvName && env2.name.toLowerCase() === defaultEnvName.toLowerCase() ? t(":name (default)", { name: env2.name }) : env2.name }, env2.id))
33322
+ envList.map(({ data: env2 }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env2.id, children: env2.name.toLowerCase() === defaultEnvName?.toLowerCase() ? t(":name (default)", { name: env2.name }) : env2.name }, env2.id))
33263
33323
  ]
33264
33324
  }
33265
33325
  ),
@@ -33541,7 +33601,7 @@ function WorkspaceSettingsModal({ onClose }) {
33541
33601
  }
33542
33602
  async function save() {
33543
33603
  const settings = {
33544
- ...workspace?.settings ?? {}
33604
+ ...workspace?.settings
33545
33605
  };
33546
33606
  if (proxyUrl.trim()) {
33547
33607
  settings.proxy = { url: proxyUrl.trim() };
@@ -33660,6 +33720,7 @@ function WorkspaceSettingsModal({ onClose }) {
33660
33720
  "input",
33661
33721
  {
33662
33722
  type: "checkbox",
33723
+ "aria-label": t("Persist request history"),
33663
33724
  checked: persistHistory,
33664
33725
  onChange: (e) => setPersistHistory(e.target.checked),
33665
33726
  className: "mt-0.5"
@@ -33795,6 +33856,7 @@ function WorkspaceSettingsModal({ onClose }) {
33795
33856
  "input",
33796
33857
  {
33797
33858
  type: "checkbox",
33859
+ "aria-label": t("Enable API Spector Cloud"),
33798
33860
  checked: cloudEnabled2,
33799
33861
  onChange: (e) => setCloudEnabled(e.target.checked),
33800
33862
  className: "mt-0.5 accent-blue-500"
@@ -34572,12 +34634,12 @@ function parseCurl(command) {
34572
34634
  } else if (hasData) {
34573
34635
  const data = dataParts.join("&");
34574
34636
  const ct = headers.find((h) => h.key.toLowerCase() === "content-type")?.value;
34575
- if (ct && ct.includes("json") || looksJson(data)) body = { mode: "json", json: data };
34637
+ if (ct?.includes("json") || looksJson(data)) body = { mode: "json", json: data };
34576
34638
  else body = { mode: "raw", raw: data, rawContentType: ct ?? "application/x-www-form-urlencoded" };
34577
34639
  }
34578
34640
  let name;
34579
34641
  try {
34580
- const u = new URL(url.replace(/\{\{[^}]+\}\}/g, "x"));
34642
+ const u = new URL(url.replaceAll(/\{\{[^}]+\}\}/g, "x"));
34581
34643
  name = `${resolvedMethod} ${u.pathname === "/" ? u.hostname : u.pathname}`;
34582
34644
  } catch {
34583
34645
  const cleaned = url.replace(/^[a-z]+:\/\//i, "").replace(/\?.*$/, "");
@@ -35426,7 +35488,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
35426
35488
  ] });
35427
35489
  }
35428
35490
  function escapeHtml(s) {
35429
- return String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
35491
+ return String(s ?? "").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
35430
35492
  }
35431
35493
  function buildJsonReport(results, summary, meta = {}) {
35432
35494
  return JSON.stringify({
@@ -35440,6 +35502,9 @@ function buildJsonReport(results, summary, meta = {}) {
35440
35502
  method: r.method,
35441
35503
  url: r.resolvedUrl,
35442
35504
  status: r.status,
35505
+ // Tree position: which collection and folder path this request sits under.
35506
+ collection: r.collection ?? meta.collection ?? null,
35507
+ folderPath: r.scopePath ?? [],
35443
35508
  isHook: r.isHook ?? false,
35444
35509
  hookType: r.hookType ?? null,
35445
35510
  httpStatus: r.httpStatus ?? null,
@@ -35480,12 +35545,26 @@ function buildHtmlReport(results, summary, meta = {}) {
35480
35545
  after: "AFTER",
35481
35546
  afterAll: "AFTER ALL"
35482
35547
  };
35483
- let lastScopeKey = null;
35548
+ let lastCollection = null;
35549
+ let lastPath = [];
35484
35550
  const cards = results.map((r, idx) => {
35485
- const scopeKey = (r.scopePath ?? []).join(" / ");
35486
- const groupHeading = scopeKey && scopeKey !== lastScopeKey ? ` <div class="scope-heading">${esc2(scopeKey)}</div>
35487
- ` : "";
35488
- lastScopeKey = scopeKey;
35551
+ let groupHeading = "";
35552
+ const coll = r.collection ?? meta.collection ?? "";
35553
+ if (coll && coll !== lastCollection) {
35554
+ groupHeading += ` <div class="scope-heading lvl-0">${esc2(coll)}</div>
35555
+ `;
35556
+ lastCollection = coll;
35557
+ lastPath = [];
35558
+ }
35559
+ const path = r.scopePath ?? [];
35560
+ let diverge = 0;
35561
+ while (diverge < path.length && diverge < lastPath.length && path[diverge] === lastPath[diverge]) diverge++;
35562
+ for (let d = diverge; d < path.length; d++) {
35563
+ groupHeading += ` <div class="scope-heading lvl-${Math.min(d + 1, 6)}">${esc2(path[d])}</div>
35564
+ `;
35565
+ }
35566
+ lastPath = path;
35567
+ const cardIndent = `lvl-${Math.min(path.length + 1, 6)}`;
35489
35568
  const statusCls = r.status === "passed" ? "badge-pass" : r.status === "failed" ? "badge-fail" : r.status === "skipped" ? "badge-skip" : "badge-err";
35490
35569
  const httpCls = r.httpStatus && r.httpStatus < 300 ? "http-ok" : r.httpStatus && r.httpStatus < 400 ? "http-redir" : "http-err";
35491
35570
  const dur = r.durationMs != null ? `${r.durationMs} ms` : "—";
@@ -35525,7 +35604,7 @@ function buildHtmlReport(results, summary, meta = {}) {
35525
35604
  </div>` : "";
35526
35605
  const hookCls = hookLabel ? r.hookType?.startsWith("before") ? "card-hook-before" : "card-hook-after" : "";
35527
35606
  return `${groupHeading}
35528
- <div class="card ${hookCls}" id="r${idx}">
35607
+ <div class="card ${hookCls} ${cardIndent}" id="r${idx}">
35529
35608
  <div class="card-header" onclick="toggle(${idx})">
35530
35609
  <span class="chevron" id="ch${idx}">▶</span>
35531
35610
  <span class="badge ${statusCls}">${r.status}</span>
@@ -35596,8 +35675,18 @@ function buildHtmlReport(results, summary, meta = {}) {
35596
35675
  .stat-skip .stat-val { color: var(--text-muted); }
35597
35676
  /* Cards */
35598
35677
  .card { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; overflow: hidden; }
35599
- .scope-heading { margin: 18px 0 6px; padding: 4px 2px; font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }
35678
+ .scope-heading { margin: 14px 0 6px; padding: 4px 2px; font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }
35600
35679
  .scope-heading::before { content: "\\25B8 "; color: var(--text-dim); }
35680
+ /* Nested tree indentation: collection (lvl-0) → folders (lvl-1..) → request card. */
35681
+ .lvl-0 { margin-left: 0; }
35682
+ .lvl-1 { margin-left: 16px; }
35683
+ .lvl-2 { margin-left: 32px; }
35684
+ .lvl-3 { margin-left: 48px; }
35685
+ .lvl-4 { margin-left: 64px; }
35686
+ .lvl-5 { margin-left: 80px; }
35687
+ .lvl-6 { margin-left: 96px; }
35688
+ .scope-heading.lvl-0 { font-size: 13px; color: var(--text); text-transform: none; letter-spacing: 0; margin-top: 22px; }
35689
+ .scope-heading.lvl-0::before { content: "\\1F5C0 "; }
35601
35690
  .card-header { display: flex; align-items: baseline; gap: 8px; padding: 10px 14px; cursor: pointer; user-select: none; }
35602
35691
  .card-header:hover { background: var(--bg-surface); }
35603
35692
  .chevron { font-size: 10px; color: var(--text-muted); min-width: 10px; transition: transform .15s; }
@@ -35715,7 +35804,7 @@ function buildJUnitReport(results, summary, meta = {}) {
35715
35804
  const label = r.iterationLabel ? ` #${r.iterationLabel}` : "";
35716
35805
  const hookPrefix = r.isHook && r.hookType ? `[${HOOK_JUNIT_LABELS[r.hookType] ?? "Hook"}] ` : "";
35717
35806
  const name = esc2(hookPrefix + r.name + label);
35718
- const classname = esc2(`${r.method} ${r.resolvedUrl}`);
35807
+ const classname = esc2([r.collection ?? meta.collection, ...r.scopePath ?? []].filter(Boolean).map((s) => String(s).replaceAll(".", "_")).join(".") || "API Tests");
35719
35808
  const timeSec = ((r.durationMs ?? 0) / 1e3).toFixed(3);
35720
35809
  const failures = [];
35721
35810
  if (r.status === "error") {
@@ -36300,13 +36389,13 @@ function RunnerModal() {
36300
36389
  }
36301
36390
  );
36302
36391
  }
36303
- const HTTP_METHODS$2 = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
36392
+ const HTTP_METHODS$2 = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
36304
36393
  function normalizePath(url) {
36305
36394
  let u = (url || "").trim();
36306
36395
  u = u.split("#")[0].split("?")[0];
36307
- u = u.replace(/\{\{[^}]*\}\}/g, "");
36396
+ u = u.replaceAll(/\{\{[^}]*\}\}/g, "");
36308
36397
  u = u.replace(/^[a-z0-9+.-]+:\/\/[^/]*/i, "");
36309
- u = u.replace(/\/{2,}/g, "/");
36398
+ u = u.replaceAll(/\/{2,}/g, "/");
36310
36399
  if (!u.startsWith("/")) u = "/" + u;
36311
36400
  u = u.replace(/\/+$/, "");
36312
36401
  return u === "" ? "/" : u;
@@ -36326,7 +36415,7 @@ function pathMatches(requestUrl, template) {
36326
36415
  }
36327
36416
  function resolveRef$2(spec2, ref) {
36328
36417
  const parts = ref.replace(/^#\//, "").split("/");
36329
- return parts.reduce((o, k) => o?.[decodeURIComponent(k.replace(/~1/g, "/").replace(/~0/g, "~"))], spec2);
36418
+ return parts.reduce((o, k) => o?.[decodeURIComponent(k.replaceAll("~1", "/").replaceAll("~0", "~"))], spec2);
36330
36419
  }
36331
36420
  function deref$2(spec2, node, depth = 0, seen2 = /* @__PURE__ */ new Set()) {
36332
36421
  if (!node || typeof node !== "object" || depth > 8) return node;
@@ -36347,8 +36436,7 @@ function flattenSchemaPaths(schema, prefix2 = "", depth = 0) {
36347
36436
  if (type === "object" || schema.properties) {
36348
36437
  for (const [name, sub] of Object.entries(schema.properties ?? {})) {
36349
36438
  const path = prefix2 ? `${prefix2}.${name}` : name;
36350
- out.push(path);
36351
- out.push(...flattenSchemaPaths(sub, path, depth + 1));
36439
+ out.push(path, ...flattenSchemaPaths(sub, path, depth + 1));
36352
36440
  }
36353
36441
  } else if (type === "array" && schema.items) {
36354
36442
  out.push(...flattenSchemaPaths(schema.items, `${prefix2}[]`, depth + 1));
@@ -36363,14 +36451,13 @@ function flattenValuePaths(value2, prefix2 = "", depth = 0) {
36363
36451
  } else {
36364
36452
  for (const [k, v] of Object.entries(value2)) {
36365
36453
  const path = prefix2 ? `${prefix2}.${k}` : k;
36366
- out.push(path);
36367
- out.push(...flattenValuePaths(v, path, depth + 1));
36454
+ out.push(path, ...flattenValuePaths(v, path, depth + 1));
36368
36455
  }
36369
36456
  }
36370
36457
  return [...new Set(out)];
36371
36458
  }
36372
36459
  function successResponseSchema$1(spec2, responses) {
36373
- const code = Object.keys(responses ?? {}).filter((c) => /^2\d\d$/.test(c)).sort()[0];
36460
+ const code = Object.keys(responses ?? {}).filter((c) => /^2\d\d$/.test(c)).sort((a, b) => a.localeCompare(b))[0];
36374
36461
  if (!code) return void 0;
36375
36462
  const schema = responses[code]?.content?.["application/json"]?.schema ?? responses[code]?.content?.["application/json;charset=utf-8"]?.schema;
36376
36463
  return schema ? deref$2(spec2, schema) : void 0;
@@ -36381,7 +36468,7 @@ function enumerateOperations(spec2) {
36381
36468
  for (const [path, item] of Object.entries(doc.paths ?? {})) {
36382
36469
  if (!item || typeof item !== "object") continue;
36383
36470
  for (const [method, op] of Object.entries(item)) {
36384
- if (!HTTP_METHODS$2.includes(method.toLowerCase())) continue;
36471
+ if (!HTTP_METHODS$2.has(method.toLowerCase())) continue;
36385
36472
  if (!op || typeof op !== "object") continue;
36386
36473
  const declaredStatuses = Object.keys(op.responses ?? {}).filter((c) => /^\d{3}$/.test(c));
36387
36474
  ops.push({ method: method.toUpperCase(), path, operationId: op.operationId, declaredStatuses, responses: op.responses });
@@ -36442,10 +36529,10 @@ function computeCoverage(spec2, requests, observations = []) {
36442
36529
  operations: operations2
36443
36530
  };
36444
36531
  }
36445
- const HTTP_METHODS$1 = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
36532
+ const HTTP_METHODS$1 = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
36446
36533
  function resolveRef$1(spec2, ref) {
36447
36534
  const parts = ref.replace(/^#\//, "").split("/");
36448
- return parts.reduce((o, k) => o?.[decodeURIComponent(k.replace(/~1/g, "/").replace(/~0/g, "~"))], spec2);
36535
+ return parts.reduce((o, k) => o?.[decodeURIComponent(k.replaceAll("~1", "/").replaceAll("~0", "~"))], spec2);
36449
36536
  }
36450
36537
  function deref$1(spec2, node, depth = 0, seen2 = /* @__PURE__ */ new Set()) {
36451
36538
  if (!node || typeof node !== "object" || depth > 8) return node;
@@ -36618,7 +36705,7 @@ function generateTests(spec2, options = {}) {
36618
36705
  for (const [path, item] of Object.entries(doc.paths ?? {})) {
36619
36706
  if (!item || typeof item !== "object") continue;
36620
36707
  for (const [method, op] of Object.entries(item)) {
36621
- if (!HTTP_METHODS$1.includes(method.toLowerCase())) continue;
36708
+ if (!HTTP_METHODS$1.has(method.toLowerCase())) continue;
36622
36709
  if (!op || typeof op !== "object") continue;
36623
36710
  if (opts.only.size && !opts.only.has(`${method.toUpperCase()} ${path}`)) continue;
36624
36711
  out.push(...generateForOperation(doc, method.toUpperCase(), path, item, op, opts));
@@ -36854,10 +36941,10 @@ function CoverageModal() {
36854
36941
  }
36855
36942
  );
36856
36943
  }
36857
- const HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
36944
+ const HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
36858
36945
  function resolveRef(spec2, ref) {
36859
36946
  const parts = ref.replace(/^#\//, "").split("/");
36860
- return parts.reduce((o, k) => o?.[decodeURIComponent(k.replace(/~1/g, "/").replace(/~0/g, "~"))], spec2);
36947
+ return parts.reduce((o, k) => o?.[decodeURIComponent(k.replaceAll("~1", "/").replaceAll("~0", "~"))], spec2);
36861
36948
  }
36862
36949
  function deref(spec2, node, depth = 0, seen2 = /* @__PURE__ */ new Set()) {
36863
36950
  if (!node || typeof node !== "object" || depth > 8) return node;
@@ -36898,7 +36985,7 @@ function requestSchema(spec2, op) {
36898
36985
  }
36899
36986
  function successResponseSchema(spec2, op) {
36900
36987
  const responses = op?.responses ?? {};
36901
- const code = Object.keys(responses).filter((c) => /^2\d\d$/.test(c)).sort()[0];
36988
+ const code = Object.keys(responses).filter((c) => /^2\d\d$/.test(c)).sort((a, b) => a.localeCompare(b))[0];
36902
36989
  return code ? deref(spec2, responses[code])?.content?.["application/json"]?.schema : void 0;
36903
36990
  }
36904
36991
  function paramRequired(spec2, pathItem, op) {
@@ -36912,7 +36999,7 @@ function operations(spec2) {
36912
36999
  for (const [path, item] of Object.entries(spec2?.paths ?? {})) {
36913
37000
  if (!item || typeof item !== "object") continue;
36914
37001
  for (const [method, op] of Object.entries(item)) {
36915
- if (!HTTP_METHODS.includes(method.toLowerCase())) continue;
37002
+ if (!HTTP_METHODS.has(method.toLowerCase())) continue;
36916
37003
  map.set(`${method.toUpperCase()} ${path}`, { method: method.toUpperCase(), path, pathItem: item, op });
36917
37004
  }
36918
37005
  }
@@ -37116,7 +37203,7 @@ function CompareModal() {
37116
37203
  );
37117
37204
  }
37118
37205
  function toCSV(ds) {
37119
- const escape = (s) => s.includes(",") || s.includes('"') || s.includes("\n") ? `"${s.replace(/"/g, '""')}"` : s;
37206
+ const escape = (s) => s.includes(",") || s.includes('"') || s.includes("\n") ? `"${s.replaceAll('"', '""')}"` : s;
37120
37207
  return [ds.columns, ...ds.rows].map((row) => row.map(escape).join(",")).join("\n");
37121
37208
  }
37122
37209
  function parseCSV(text) {
@@ -37182,7 +37269,7 @@ function DataSetEditor({ ds, onChange, exportName, scopeLabel = "collection" })
37182
37269
  const url = URL.createObjectURL(blob);
37183
37270
  const a = document.createElement("a");
37184
37271
  a.href = url;
37185
- a.download = `${exportName.replace(/\s+/g, "_")}_data.csv`;
37272
+ a.download = `${exportName.replaceAll(/\s+/g, "_")}_data.csv`;
37186
37273
  a.click();
37187
37274
  URL.revokeObjectURL(url);
37188
37275
  }
@@ -37340,7 +37427,7 @@ function CollectionPanel() {
37340
37427
  ] }),
37341
37428
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-6 flex-shrink-0", children: [
37342
37429
  { id: "documentation", label: "Documentation", badge: col.description?.trim() ? 1 : 0 },
37343
- { id: "data", label: "Data", badge: iterCount > 0 ? iterCount : 0 },
37430
+ { id: "data", label: "Data", badge: Math.max(iterCount, 0) },
37344
37431
  { id: "variables", label: "Variables", badge: Object.keys(col.collectionVariables ?? {}).length }
37345
37432
  ].map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
37346
37433
  "button",
@@ -37426,7 +37513,7 @@ function FolderPanel() {
37426
37513
  ] }),
37427
37514
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-6 flex-shrink-0", children: [
37428
37515
  { id: "documentation", label: "Documentation", badge: folder2.description?.trim() ? 1 : 0 },
37429
- { id: "data", label: "Data", badge: iterCount > 0 ? iterCount : 0 },
37516
+ { id: "data", label: "Data", badge: Math.max(iterCount, 0) },
37430
37517
  { id: "variables", label: "Variables", badge: varRows.length }
37431
37518
  ].map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
37432
37519
  "button",
@@ -37594,7 +37681,7 @@ function MockPanel() {
37594
37681
  await electron$9.saveMock(relPath, mock);
37595
37682
  const ws = useStore.getState().workspace;
37596
37683
  if (ws) {
37597
- if (!ws.mocks) ws.mocks = [];
37684
+ ws.mocks ??= [];
37598
37685
  ws.mocks.push(relPath);
37599
37686
  await electron$9.saveWorkspace(ws);
37600
37687
  }
@@ -39089,7 +39176,7 @@ function ViolationRow({ v }) {
39089
39176
  const t = useT();
39090
39177
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 px-4 py-2.5 border-l-2 border-red-600 bg-red-950/20 rounded-r", children: [
39091
39178
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
39092
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono font-bold text-red-400 uppercase tracking-wide", children: v.type.replace(/_/g, " ") }),
39179
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono font-bold text-red-400 uppercase tracking-wide", children: v.type.replaceAll("_", " ") }),
39093
39180
  v.path && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-500 bg-surface-800 px-1.5 py-0.5 rounded", children: v.path })
39094
39181
  ] }),
39095
39182
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-200", children: v.message }),
@@ -39278,7 +39365,7 @@ function designContractToMock(cc, port = 4100) {
39278
39365
  id: it2.id || `route-${i}`,
39279
39366
  // Pact path templates use {id}; the mock router uses :id.
39280
39367
  method: (it2.request.method || "GET").toUpperCase(),
39281
- path: (it2.request.path || "/").replace(/\{([^}]+)\}/g, ":$1"),
39368
+ path: (it2.request.path || "/").replaceAll(/\{([^}]+)\}/g, ":$1"),
39282
39369
  statusCode: it2.response.status,
39283
39370
  headers: { "Content-Type": "application/json", ...kvToHeaders(it2.response.headers) },
39284
39371
  body: it2.response.body?.trim() ? it2.response.body : "",
@@ -39366,8 +39453,8 @@ function ContractDesignerModal({ seed, onClose }) {
39366
39453
  ...base,
39367
39454
  ...seed,
39368
39455
  id: uid(),
39369
- request: { ...base.request, ...seed.request ?? {} },
39370
- response: { ...base.response, ...seed.response ?? {} }
39456
+ request: { ...base.request, ...seed.request },
39457
+ response: { ...base.response, ...seed.response }
39371
39458
  };
39372
39459
  const contract = { ...newContract(), interactions: [interaction] };
39373
39460
  setSelectedId(contract.id);
@@ -39967,7 +40054,7 @@ function ConflictEditor({ path, onClose, onResolved }) {
39967
40054
  }, [path]);
39968
40055
  const conflictCount = reactExports.useMemo(() => parts?.filter((p) => p.type === "conflict").length ?? 0, [parts]);
39969
40056
  const unresolved = reactExports.useMemo(() => parts?.filter((p) => p.type === "conflict" && !p.choice).length ?? 0, [parts]);
39970
- const content = manual != null ? manual : parts ? assemble(parts) : "";
40057
+ const content = manual ?? (parts ? assemble(parts) : "");
39971
40058
  const stillHasMarkers = /^(<{7}|={7}|>{7})/m.test(content);
39972
40059
  function choose(idx, choice) {
39973
40060
  setParts((prev) => prev?.map((p, i) => i === idx && p.type === "conflict" ? { ...p, choice } : p) ?? prev);
@@ -40325,7 +40412,10 @@ function ChangesTab({ status, onRefresh }) {
40325
40412
  "div",
40326
40413
  {
40327
40414
  className: `flex items-center gap-2 px-3 py-1 hover:bg-surface-800/50 cursor-pointer group text-xs ${diffFile === f.path && diffStaged ? "bg-surface-800" : ""}`,
40415
+ role: "button",
40416
+ tabIndex: 0,
40328
40417
  onClick: () => showDiff(f, true),
40418
+ onKeyDown: onActivateKey(() => showDiff(f, true)),
40329
40419
  children: [
40330
40420
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBadge, { status: f.status }),
40331
40421
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", children: f.path }),
@@ -40362,7 +40452,10 @@ function ChangesTab({ status, onRefresh }) {
40362
40452
  "div",
40363
40453
  {
40364
40454
  className: `flex items-center gap-2 px-3 py-1 hover:bg-surface-800/50 cursor-pointer group text-xs ${diffFile === f.path && !diffStaged ? "bg-surface-800" : ""}`,
40455
+ role: "button",
40456
+ tabIndex: 0,
40365
40457
  onClick: () => showDiff(f, false),
40458
+ onKeyDown: onActivateKey(() => showDiff(f, false)),
40366
40459
  children: [
40367
40460
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBadge, { status: f.status }),
40368
40461
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", children: f.path }),
@@ -40389,7 +40482,10 @@ function ChangesTab({ status, onRefresh }) {
40389
40482
  "div",
40390
40483
  {
40391
40484
  className: `flex items-center gap-2 px-3 py-1 hover:bg-surface-800/50 cursor-pointer group text-xs ${diffFile === path && !diffStaged ? "bg-surface-800" : ""}`,
40485
+ role: "button",
40486
+ tabIndex: 0,
40392
40487
  onClick: () => showDiff({ path, status: "modified" }, false),
40488
+ onKeyDown: onActivateKey(() => showDiff({ path, status: "modified" }, false)),
40393
40489
  children: [
40394
40490
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-[11px] font-bold w-4 shrink-0 text-red-400", children: "!" }),
40395
40491
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-red-300", children: path }),
@@ -41117,10 +41213,16 @@ function CommandPalette() {
41117
41213
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-h-80 overflow-y-auto", children: filtered2.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-6 text-center text-sm text-surface-600", children: t('No requests match ":query"', { query }) }) : filtered2.map((item, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
41118
41214
  "div",
41119
41215
  {
41216
+ role: "button",
41217
+ tabIndex: 0,
41120
41218
  onClick: () => {
41121
41219
  openInTab(item.requestId, item.collectionId);
41122
41220
  setOpen(false);
41123
41221
  },
41222
+ onKeyDown: onActivateKey(() => {
41223
+ openInTab(item.requestId, item.collectionId);
41224
+ setOpen(false);
41225
+ }),
41124
41226
  onMouseEnter: () => setActiveIdx(idx),
41125
41227
  className: `flex items-center gap-3 px-4 py-2.5 cursor-pointer transition-colors ${idx === activeIdx ? "bg-surface-800" : "hover:bg-surface-800/50"}`,
41126
41228
  children: [
@@ -41279,7 +41381,10 @@ const TabRow = React$2.memo(function TabRow2({
41279
41381
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
41280
41382
  "div",
41281
41383
  {
41384
+ role: "button",
41385
+ tabIndex: 0,
41282
41386
  onClick: () => onActivate(tabId),
41387
+ onKeyDown: onActivateKey(() => onActivate(tabId)),
41283
41388
  onContextMenu: (e) => {
41284
41389
  e.preventDefault();
41285
41390
  onContextMenu(tabId, e.clientX, e.clientY);
@@ -41485,7 +41590,7 @@ function App() {
41485
41590
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
41486
41591
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
41487
41592
  "v",
41488
- "0.6.2"
41593
+ "0.6.4"
41489
41594
  ] }),
41490
41595
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
41491
41596
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -41584,13 +41689,27 @@ function App() {
41584
41689
  /* @__PURE__ */ jsxRuntimeExports.jsx(
41585
41690
  "div",
41586
41691
  {
41587
- className: "flex-shrink-0 w-1 cursor-col-resize border-r border-surface-800 hover:border-blue-500 transition-colors",
41692
+ role: "separator",
41693
+ "aria-orientation": "vertical",
41694
+ "aria-label": "Resize sidebar",
41695
+ "aria-valuemin": 160,
41696
+ "aria-valuemax": 520,
41697
+ "aria-valuenow": sidebarWidth,
41698
+ tabIndex: 0,
41699
+ className: "flex-shrink-0 w-1 cursor-col-resize border-r border-surface-800 hover:border-blue-500 focus:border-blue-500 focus:outline-none transition-colors",
41588
41700
  onMouseDown: (e) => {
41589
41701
  dragging.current = "sidebar";
41590
41702
  dragStart.current = { x: e.clientX, w: sidebarWidth };
41591
41703
  document.body.style.cursor = "col-resize";
41592
41704
  document.body.style.userSelect = "none";
41593
41705
  e.preventDefault();
41706
+ },
41707
+ onKeyDown: (e) => {
41708
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
41709
+ e.preventDefault();
41710
+ const step = e.key === "ArrowLeft" ? -16 : 16;
41711
+ setSidebarWidth((w) => Math.max(160, Math.min(520, w + step)));
41712
+ }
41594
41713
  }
41595
41714
  }
41596
41715
  )
@@ -41636,8 +41755,14 @@ function App() {
41636
41755
  /* @__PURE__ */ jsxRuntimeExports.jsx(
41637
41756
  "div",
41638
41757
  {
41758
+ role: "button",
41759
+ tabIndex: -1,
41760
+ "aria-label": t("Close menu"),
41639
41761
  className: "fixed inset-0 z-40",
41640
41762
  onClick: () => setTabContextMenu(null),
41763
+ onKeyDown: (e) => {
41764
+ if (e.key === "Escape") setTabContextMenu(null);
41765
+ },
41641
41766
  onContextMenu: (e) => {
41642
41767
  e.preventDefault();
41643
41768
  setTabContextMenu(null);
@@ -41713,7 +41838,7 @@ function App() {
41713
41838
  await electron.saveMock(relPath, newRoutes);
41714
41839
  const ws = useStore.getState().workspace;
41715
41840
  if (ws) {
41716
- if (!ws.mocks) ws.mocks = [];
41841
+ ws.mocks ??= [];
41717
41842
  ws.mocks.push(relPath);
41718
41843
  await electron.saveWorkspace(ws);
41719
41844
  }
@@ -41735,7 +41860,13 @@ function App() {
41735
41860
  /* @__PURE__ */ jsxRuntimeExports.jsx(
41736
41861
  "div",
41737
41862
  {
41738
- className: "flex-shrink-0 w-1 relative cursor-col-resize border-x border-surface-800 hover:border-blue-500 transition-colors group",
41863
+ role: "separator",
41864
+ "aria-orientation": "vertical",
41865
+ "aria-label": "Resize request pane",
41866
+ "aria-valuemin": 200,
41867
+ "aria-valuenow": requestPaneWidth ?? leftPaneRef.current?.offsetWidth ?? 400,
41868
+ tabIndex: 0,
41869
+ className: "flex-shrink-0 w-1 relative cursor-col-resize border-x border-surface-800 hover:border-blue-500 focus:border-blue-500 focus:outline-none transition-colors group",
41739
41870
  onMouseDown: (e) => {
41740
41871
  dragging.current = "request";
41741
41872
  dragStart.current = { x: e.clientX, w: leftPaneRef.current?.offsetWidth ?? requestPaneWidth ?? 400 };
@@ -41743,6 +41874,14 @@ function App() {
41743
41874
  document.body.style.userSelect = "none";
41744
41875
  e.preventDefault();
41745
41876
  },
41877
+ onKeyDown: (e) => {
41878
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
41879
+ e.preventDefault();
41880
+ const step = e.key === "ArrowLeft" ? -16 : 16;
41881
+ const cur = leftPaneRef.current?.offsetWidth ?? requestPaneWidth ?? 400;
41882
+ setRequestPaneWidth(Math.max(200, Math.min(window.innerWidth - 300, cur + step)));
41883
+ }
41884
+ },
41746
41885
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(
41747
41886
  "button",
41748
41887
  {
@@ -41782,7 +41921,7 @@ if (savedTheme === "light") {
41782
41921
  }
41783
41922
  }
41784
41923
  const savedZoom = localStorage.getItem(STORAGE_KEYS.zoom);
41785
- if (savedZoom) window.electron.setZoomFactor(parseFloat(savedZoom));
41924
+ if (savedZoom) window.electron.setZoomFactor(Number.parseFloat(savedZoom));
41786
41925
  ReactDOM.createRoot(document.getElementById("root")).render(
41787
41926
  /* @__PURE__ */ jsxRuntimeExports.jsx(React$2.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(I18nProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(App, {}) }) }) })
41788
41927
  );