@testsmith/api-spector 0.1.9 → 0.2.0

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.
@@ -13340,10 +13340,10 @@ function makeCollection(name2) {
13340
13340
  requests: {}
13341
13341
  };
13342
13342
  }
13343
- function findFolder(root2, id2) {
13343
+ function findFolder$1(root2, id2) {
13344
13344
  if (root2.id === id2) return root2;
13345
13345
  for (const sub of root2.folders) {
13346
- const found = findFolder(sub, id2);
13346
+ const found = findFolder$1(sub, id2);
13347
13347
  if (found) return found;
13348
13348
  }
13349
13349
  return null;
@@ -13497,6 +13497,16 @@ const useStore = create()(
13497
13497
  s.activeCollectionId = next?.collectionId ?? null;
13498
13498
  }
13499
13499
  }),
13500
+ closeAllTabs: () => set2((s) => {
13501
+ s.tabs = [];
13502
+ s.activeTabId = null;
13503
+ }),
13504
+ closeOtherTabs: (keepTabId) => set2((s) => {
13505
+ const kept = s.tabs.find((t2) => t2.id === keepTabId);
13506
+ s.tabs = kept ? [kept] : [];
13507
+ s.activeTabId = kept?.id ?? null;
13508
+ if (kept) s.activeCollectionId = kept.collectionId;
13509
+ }),
13500
13510
  setActiveTabId: (id2) => set2((s) => {
13501
13511
  s.activeTabId = id2;
13502
13512
  const tab = s.tabs.find((t2) => t2.id === id2);
@@ -13624,6 +13634,42 @@ const useStore = create()(
13624
13634
  s.activeCollectionId = newId;
13625
13635
  if (s.workspace) s.workspace.collections.push(relPath);
13626
13636
  }),
13637
+ mergeIntoCollection: (collectionId, sourceFolder, sourceRequests) => set2((s) => {
13638
+ const entry = s.collections[collectionId];
13639
+ if (!entry) return;
13640
+ const col = entry.data;
13641
+ const cloned = JSON.parse(JSON.stringify(sourceFolder));
13642
+ function remapFolder(f) {
13643
+ f.id = v4();
13644
+ f.requestIds = f.requestIds.map((oldId) => {
13645
+ const newId = v4();
13646
+ const srcReq = sourceRequests[oldId];
13647
+ if (srcReq) col.requests[newId] = { ...srcReq, id: newId };
13648
+ return newId;
13649
+ });
13650
+ f.folders.forEach(remapFolder);
13651
+ }
13652
+ remapFolder(cloned);
13653
+ if (cloned.folders.length > 0) {
13654
+ for (const sub of cloned.folders) {
13655
+ col.rootFolder.folders.push(sub);
13656
+ }
13657
+ if (cloned.requestIds.length > 0) {
13658
+ const wrapper = {
13659
+ id: v4(),
13660
+ name: cloned.name || "Imported",
13661
+ description: "",
13662
+ folders: [],
13663
+ requestIds: cloned.requestIds
13664
+ };
13665
+ col.rootFolder.folders.push(wrapper);
13666
+ }
13667
+ } else {
13668
+ col.rootFolder.folders.push(cloned);
13669
+ }
13670
+ entry.dirty = true;
13671
+ s.activeCollectionId = collectionId;
13672
+ }),
13627
13673
  deleteCollection: (id2) => set2((s) => {
13628
13674
  const relPath = s.collections[id2]?.relPath;
13629
13675
  delete s.collections[id2];
@@ -13657,7 +13703,7 @@ const useStore = create()(
13657
13703
  addFolder: (collectionId, parentFolderId, name2) => set2((s) => {
13658
13704
  const col = s.collections[collectionId]?.data;
13659
13705
  if (!col) return;
13660
- const parent = findFolder(col.rootFolder, parentFolderId);
13706
+ const parent = findFolder$1(col.rootFolder, parentFolderId);
13661
13707
  if (!parent) return;
13662
13708
  parent.folders.push({ id: v4(), name: name2, description: "", folders: [], requestIds: [] });
13663
13709
  s.collections[collectionId].dirty = true;
@@ -13665,7 +13711,7 @@ const useStore = create()(
13665
13711
  renameFolder: (collectionId, folderId, name2) => set2((s) => {
13666
13712
  const col = s.collections[collectionId]?.data;
13667
13713
  if (!col) return;
13668
- const folder = findFolder(col.rootFolder, folderId);
13714
+ const folder = findFolder$1(col.rootFolder, folderId);
13669
13715
  if (folder) {
13670
13716
  folder.name = name2;
13671
13717
  s.collections[collectionId].dirty = true;
@@ -13674,7 +13720,7 @@ const useStore = create()(
13674
13720
  duplicateFolder: (collectionId, folderId) => set2((s) => {
13675
13721
  const col = s.collections[collectionId]?.data;
13676
13722
  if (!col) return;
13677
- const orig = findFolder(col.rootFolder, folderId);
13723
+ const orig = findFolder$1(col.rootFolder, folderId);
13678
13724
  if (!orig) return;
13679
13725
  const copy = JSON.parse(JSON.stringify(orig));
13680
13726
  copy.name = orig.name + " (copy)";
@@ -13703,7 +13749,7 @@ const useStore = create()(
13703
13749
  function collectIds(f) {
13704
13750
  return [...f.requestIds, ...f.folders.flatMap(collectIds)];
13705
13751
  }
13706
- const folder = findFolder(col.rootFolder, folderId);
13752
+ const folder = findFolder$1(col.rootFolder, folderId);
13707
13753
  if (folder) {
13708
13754
  collectIds(folder).forEach((rid) => delete col.requests[rid]);
13709
13755
  }
@@ -13717,7 +13763,7 @@ const useStore = create()(
13717
13763
  const col = s.collections[collectionId]?.data;
13718
13764
  if (!col) return;
13719
13765
  col.requests[req.id] = req;
13720
- const folder = findFolder(col.rootFolder, folderId) ?? col.rootFolder;
13766
+ const folder = findFolder$1(col.rootFolder, folderId) ?? col.rootFolder;
13721
13767
  folder.requestIds.push(req.id);
13722
13768
  s.activeCollectionId = collectionId;
13723
13769
  s.collections[collectionId].dirty = true;
@@ -13788,7 +13834,7 @@ const useStore = create()(
13788
13834
  const tab = s.tabs.find((t2) => t2.requestId === requestId);
13789
13835
  if (tab) tab.collectionId = destCollectionId;
13790
13836
  }
13791
- const destFolder = findFolder(destCol.rootFolder, destFolderId);
13837
+ const destFolder = findFolder$1(destCol.rootFolder, destFolderId);
13792
13838
  if (destFolder) {
13793
13839
  if (destIndex !== void 0) {
13794
13840
  let adjusted = destIndex;
@@ -13803,11 +13849,33 @@ const useStore = create()(
13803
13849
  }
13804
13850
  s.collections[destCollectionId].dirty = true;
13805
13851
  }),
13852
+ moveFolder: (collectionId, folderId, destParentFolderId, destIndex) => set2((s) => {
13853
+ const col = s.collections[collectionId]?.data;
13854
+ if (!col) return;
13855
+ const folder = findFolder$1(col.rootFolder, folderId);
13856
+ if (!folder) return;
13857
+ if (folderId === destParentFolderId) return;
13858
+ if (findFolder$1(folder, destParentFolderId)) return;
13859
+ const srcParent = findFolderParent(col.rootFolder, folderId) ?? col.rootFolder;
13860
+ const srcIdx = srcParent.folders.findIndex((f) => f.id === folderId);
13861
+ srcParent.folders.splice(srcIdx, 1);
13862
+ const destParent = findFolder$1(col.rootFolder, destParentFolderId);
13863
+ if (!destParent) return;
13864
+ if (destIndex !== void 0) {
13865
+ let adjusted = destIndex;
13866
+ if (srcParent.id === destParent.id && srcIdx < destIndex) adjusted--;
13867
+ adjusted = Math.max(0, Math.min(adjusted, destParent.folders.length));
13868
+ destParent.folders.splice(adjusted, 0, folder);
13869
+ } else {
13870
+ destParent.folders.push(folder);
13871
+ }
13872
+ s.collections[collectionId].dirty = true;
13873
+ }),
13806
13874
  // ── Tags ──────────────────────────────────────────────────────────────────
13807
13875
  updateFolderTags: (collectionId, folderId, tags2) => set2((s) => {
13808
13876
  const col = s.collections[collectionId]?.data;
13809
13877
  if (!col) return;
13810
- const folder = findFolder(col.rootFolder, folderId);
13878
+ const folder = findFolder$1(col.rootFolder, folderId);
13811
13879
  if (folder) {
13812
13880
  folder.tags = tags2;
13813
13881
  s.collections[collectionId].dirty = true;
@@ -13824,7 +13892,7 @@ const useStore = create()(
13824
13892
  updateFolder: (collectionId, folderId, patch) => set2((s) => {
13825
13893
  const col = s.collections[collectionId]?.data;
13826
13894
  if (!col) return;
13827
- const folder = findFolder(col.rootFolder, folderId);
13895
+ const folder = findFolder$1(col.rootFolder, folderId);
13828
13896
  if (folder) {
13829
13897
  Object.assign(folder, patch);
13830
13898
  s.collections[collectionId].dirty = true;
@@ -13904,8 +13972,12 @@ const useStore = create()(
13904
13972
  if (activeEnvId && s.environments[activeEnvId]) {
13905
13973
  const env = s.environments[activeEnvId].data;
13906
13974
  for (const [key, value] of Object.entries(result.updatedEnvVars)) {
13907
- const v = env.variables.find((v2) => v2.key === key);
13908
- if (v && !v.secret) v.value = value;
13975
+ const existing = env.variables.find((v) => v.key === key);
13976
+ if (existing && !existing.secret) {
13977
+ existing.value = value;
13978
+ } else if (!existing) {
13979
+ env.variables.push({ key, value, enabled: true });
13980
+ }
13909
13981
  }
13910
13982
  }
13911
13983
  s.globals = { ...s.globals, ...result.updatedGlobals };
@@ -14040,7 +14112,7 @@ const useStore = create()(
14040
14112
  })
14041
14113
  }))
14042
14114
  );
14043
- const { electron: electron$n } = window;
14115
+ const { electron: electron$p } = window;
14044
14116
  function useAutoSave() {
14045
14117
  const collections = useStore((s) => s.collections);
14046
14118
  useStore((s) => s.environments);
@@ -14056,7 +14128,7 @@ function useAutoSave() {
14056
14128
  for (const { relPath, data, dirty } of dirtyCollections) {
14057
14129
  if (!dirty) continue;
14058
14130
  try {
14059
- await electron$n.saveCollection(relPath, data);
14131
+ await electron$p.saveCollection(relPath, data);
14060
14132
  markCollectionClean(data.id);
14061
14133
  } catch (e) {
14062
14134
  console.error("Auto-save failed for", relPath, e);
@@ -14072,7 +14144,7 @@ function useAutoSave() {
14072
14144
  if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
14073
14145
  wsTimerRef.current = setTimeout(async () => {
14074
14146
  try {
14075
- await electron$n.saveWorkspace(workspace);
14147
+ await electron$p.saveWorkspace(workspace);
14076
14148
  } catch {
14077
14149
  }
14078
14150
  }, 300);
@@ -14081,7 +14153,7 @@ function useAutoSave() {
14081
14153
  };
14082
14154
  }, [workspace]);
14083
14155
  }
14084
- const { electron: electron$m } = window;
14156
+ const { electron: electron$o } = window;
14085
14157
  function useWorkspaceLoader() {
14086
14158
  const loadCollection = useStore((s) => s.loadCollection);
14087
14159
  const loadEnvironment = useStore((s) => s.loadEnvironment);
@@ -14100,28 +14172,28 @@ function useWorkspaceLoader() {
14100
14172
  });
14101
14173
  for (const colPath of ws2.collections) {
14102
14174
  try {
14103
- const col = await electron$m.loadCollection(colPath);
14175
+ const col = await electron$o.loadCollection(colPath);
14104
14176
  loadCollection(colPath, col);
14105
14177
  } catch {
14106
14178
  }
14107
14179
  }
14108
14180
  for (const envPath of ws2.environments) {
14109
14181
  try {
14110
- const env = await electron$m.loadEnvironment(envPath);
14182
+ const env = await electron$o.loadEnvironment(envPath);
14111
14183
  loadEnvironment(envPath, env);
14112
14184
  } catch {
14113
14185
  }
14114
14186
  }
14115
14187
  for (const relPath of ws2.mocks ?? []) {
14116
14188
  try {
14117
- const mockData = await electron$m.loadMock(relPath);
14189
+ const mockData = await electron$o.loadMock(relPath);
14118
14190
  loadMock(relPath, mockData);
14119
14191
  } catch {
14120
14192
  }
14121
14193
  }
14122
14194
  if (ws2.collections.length > 0) {
14123
14195
  try {
14124
- const firstCol = await electron$m.loadCollection(ws2.collections[0]);
14196
+ const firstCol = await electron$o.loadCollection(ws2.collections[0]);
14125
14197
  setActiveCollection(firstCol.id);
14126
14198
  } catch {
14127
14199
  }
@@ -24807,11 +24879,11 @@ class EditContextManager {
24807
24879
  this.handlers.textformatupdate = (e) => {
24808
24880
  let deco = [];
24809
24881
  for (let format2 of e.getTextFormats()) {
24810
- let lineStyle = format2.underlineStyle, thickness = format2.underlineThickness;
24811
- if (!/none/i.test(lineStyle) && !/none/i.test(thickness)) {
24882
+ let lineStyle2 = format2.underlineStyle, thickness = format2.underlineThickness;
24883
+ if (!/none/i.test(lineStyle2) && !/none/i.test(thickness)) {
24812
24884
  let from = this.toEditorPos(format2.rangeStart), to = this.toEditorPos(format2.rangeEnd);
24813
24885
  if (from < to) {
24814
- let style2 = `text-decoration: underline ${/^[a-z]/.test(lineStyle) ? lineStyle + " " : lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${/thin/i.test(thickness) ? 1 : 2}px`;
24886
+ let style2 = `text-decoration: underline ${/^[a-z]/.test(lineStyle2) ? lineStyle2 + " " : lineStyle2 == "Dashed" ? "dashed " : lineStyle2 == "Squiggle" ? "wavy " : ""}${/thin/i.test(thickness) ? 1 : 2}px`;
24815
24887
  deco.push(Decoration.mark({ attributes: { style: style2 } }).range(from, to));
24816
24888
  }
24817
24889
  }
@@ -26191,9 +26263,9 @@ function rectanglesForRange(view, className, range) {
26191
26263
  let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
26192
26264
  let ltr = view.textDirection == Direction.LTR;
26193
26265
  let content2 = view.contentDOM, contentRect = content2.getBoundingClientRect(), base2 = getBase(view);
26194
- let lineElt = content2.querySelector(".cm-line"), lineStyle = lineElt && window.getComputedStyle(lineElt);
26195
- let leftSide = contentRect.left + (lineStyle ? parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent)) : 0);
26196
- let rightSide = contentRect.right - (lineStyle ? parseInt(lineStyle.paddingRight) : 0);
26266
+ let lineElt = content2.querySelector(".cm-line"), lineStyle2 = lineElt && window.getComputedStyle(lineElt);
26267
+ let leftSide = contentRect.left + (lineStyle2 ? parseInt(lineStyle2.paddingLeft) + Math.min(0, parseInt(lineStyle2.textIndent)) : 0);
26268
+ let rightSide = contentRect.right - (lineStyle2 ? parseInt(lineStyle2.paddingRight) : 0);
26197
26269
  let startBlock = blockAt(view, from, 1), endBlock = blockAt(view, to, -1);
26198
26270
  let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
26199
26271
  let visualEnd = endBlock.type == BlockType.Text ? endBlock : null;
@@ -34901,7 +34973,7 @@ const HEADER_VALUE_SUGGESTIONS = {
34901
34973
  function getValueSuggestions(headerName) {
34902
34974
  return HEADER_VALUE_SUGGESTIONS[headerName.toLowerCase()];
34903
34975
  }
34904
- function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "Value", headerMode }) {
34976
+ function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "Value", headerMode, paramMode }) {
34905
34977
  const [descVisible, setDescVisible] = reactExports.useState(
34906
34978
  () => new Set(rows.map((r, i) => r.description ? i : -1).filter((i) => i >= 0))
34907
34979
  );
@@ -34948,6 +35020,19 @@ function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "V
34948
35020
  className: "accent-blue-500 flex-shrink-0"
34949
35021
  }
34950
35022
  ),
35023
+ paramMode && /* @__PURE__ */ jsxRuntimeExports.jsxs(
35024
+ "select",
35025
+ {
35026
+ value: row.paramType ?? "query",
35027
+ onChange: (e) => update(idx, { paramType: e.target.value }),
35028
+ title: (row.paramType ?? "query") === "path" ? "Path variable — substituted into the URL via {{name}}" : "Query string parameter — appended as ?key=value",
35029
+ 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"}`,
35030
+ children: [
35031
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "query", children: "query" }),
35032
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "path", children: "path" })
35033
+ ]
35034
+ }
35035
+ ),
34951
35036
  /* @__PURE__ */ jsxRuntimeExports.jsx(
34952
35037
  VarInput,
34953
35038
  {
@@ -35527,8 +35612,352 @@ function CollectionAuthPanel({ auth, onChange }) {
35527
35612
  ] }) })
35528
35613
  ] });
35529
35614
  }
35615
+ const { electron: electron$n } = window;
35616
+ function normalisePath(url) {
35617
+ let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
35618
+ if (!path.startsWith("/")) path = "/" + path;
35619
+ path = path.split("?")[0];
35620
+ path = path.replace(/\{\{([^}]+)\}\}/g, "{$1}");
35621
+ return path;
35622
+ }
35623
+ function collectRequestIds(folder) {
35624
+ const ids = new Set(folder.requestIds);
35625
+ for (const sub of folder.folders) {
35626
+ for (const id2 of collectRequestIds(sub)) ids.add(id2);
35627
+ }
35628
+ return ids;
35629
+ }
35630
+ function findFolder(root2, id2) {
35631
+ if (root2.id === id2) return root2;
35632
+ for (const sub of root2.folders) {
35633
+ const found = findFolder(sub, id2);
35634
+ if (found) return found;
35635
+ }
35636
+ return null;
35637
+ }
35638
+ function SchemaSyncModal({
35639
+ collectionId,
35640
+ scope: scope2 = { type: "collection" },
35641
+ onClose
35642
+ }) {
35643
+ const collections = useStore((s) => s.collections);
35644
+ const updateRequest = useStore((s) => s.updateRequest);
35645
+ const markCollectionClean = useStore((s) => s.markCollectionClean);
35646
+ const col = collections[collectionId]?.data;
35647
+ const [url, setUrl] = reactExports.useState("");
35648
+ const [loading, setLoading] = reactExports.useState(false);
35649
+ const [error2, setError] = reactExports.useState(null);
35650
+ const [specEntries, setSpecEntries] = reactExports.useState(null);
35651
+ const [selected, setSelected] = reactExports.useState(/* @__PURE__ */ new Set());
35652
+ const scopeRequestIds = reactExports.useMemo(() => {
35653
+ if (!col) return null;
35654
+ if (scope2.type === "request") return /* @__PURE__ */ new Set([scope2.requestId]);
35655
+ if (scope2.type === "folder") {
35656
+ const folder = findFolder(col.rootFolder, scope2.folderId);
35657
+ return folder ? collectRequestIds(folder) : /* @__PURE__ */ new Set();
35658
+ }
35659
+ return null;
35660
+ }, [col, scope2]);
35661
+ const matches = reactExports.useMemo(() => {
35662
+ if (!specEntries || !col) return [];
35663
+ const allRequests = Object.values(col.requests);
35664
+ const requests = scopeRequestIds ? allRequests.filter((r) => scopeRequestIds.has(r.id)) : allRequests;
35665
+ const result = [];
35666
+ for (const spec of specEntries) {
35667
+ const specPath = spec.pathTemplate.toLowerCase();
35668
+ const match = requests.find((r) => {
35669
+ if (r.method !== spec.method) return false;
35670
+ return normalisePath(r.url).toLowerCase() === specPath;
35671
+ });
35672
+ if (match) {
35673
+ result.push({
35674
+ specEntry: spec,
35675
+ request: match,
35676
+ oldSchema: match.schema ?? "",
35677
+ newSchema: spec.schema,
35678
+ changed: (match.schema ?? "").trim() !== spec.schema.trim()
35679
+ });
35680
+ }
35681
+ }
35682
+ return result;
35683
+ }, [specEntries, col, scopeRequestIds]);
35684
+ const changedCount = matches.filter((m) => m.changed).length;
35685
+ const unchangedCount = matches.length - changedCount;
35686
+ function autoSelectChanged(entries) {
35687
+ const allRequests = Object.values(col.requests);
35688
+ const requests = scopeRequestIds ? allRequests.filter((r) => scopeRequestIds.has(r.id)) : allRequests;
35689
+ setSelected(new Set(
35690
+ entries.filter((e) => {
35691
+ const specPath = e.pathTemplate.toLowerCase();
35692
+ const match = requests.find((r) => r.method === e.method && normalisePath(r.url).toLowerCase() === specPath);
35693
+ return match && (match.schema ?? "").trim() !== e.schema.trim();
35694
+ }).map((e) => `${e.method}:${e.pathTemplate}`)
35695
+ ));
35696
+ }
35697
+ const scopeLabel = scope2.type === "request" ? (col ? Object.values(col.requests).find((r) => r.id === scope2.requestId)?.name : "") ?? "request" : scope2.type === "folder" ? findFolder(col?.rootFolder, scope2.folderId)?.name ?? "folder" : col?.name ?? "collection";
35698
+ async function loadFromFile() {
35699
+ setLoading(true);
35700
+ setError(null);
35701
+ try {
35702
+ const entries = await electron$n.extractOpenApiSchemas();
35703
+ if (!entries) {
35704
+ setLoading(false);
35705
+ return;
35706
+ }
35707
+ setSpecEntries(entries);
35708
+ autoSelectChanged(entries);
35709
+ } catch (err) {
35710
+ setError(err instanceof Error ? err.message : String(err));
35711
+ } finally {
35712
+ setLoading(false);
35713
+ }
35714
+ }
35715
+ async function loadFromUrl() {
35716
+ const trimmed = url.trim();
35717
+ if (!trimmed) return;
35718
+ setLoading(true);
35719
+ setError(null);
35720
+ try {
35721
+ const entries = await electron$n.extractOpenApiSchemasFromUrl(trimmed);
35722
+ setSpecEntries(entries);
35723
+ autoSelectChanged(entries);
35724
+ } catch (err) {
35725
+ setError(err instanceof Error ? err.message : String(err));
35726
+ } finally {
35727
+ setLoading(false);
35728
+ }
35729
+ }
35730
+ function toggleEntry(key) {
35731
+ setSelected((prev) => {
35732
+ const next = new Set(prev);
35733
+ if (next.has(key)) next.delete(key);
35734
+ else next.add(key);
35735
+ return next;
35736
+ });
35737
+ }
35738
+ function selectAllChanged() {
35739
+ setSelected(new Set(matches.filter((m) => m.changed).map((m) => `${m.specEntry.method}:${m.specEntry.pathTemplate}`)));
35740
+ }
35741
+ function selectNone() {
35742
+ setSelected(/* @__PURE__ */ new Set());
35743
+ }
35744
+ async function applySync() {
35745
+ setLoading(true);
35746
+ try {
35747
+ for (const m of matches) {
35748
+ const key = `${m.specEntry.method}:${m.specEntry.pathTemplate}`;
35749
+ if (!selected.has(key)) continue;
35750
+ updateRequest(m.request.id, { schema: m.newSchema });
35751
+ }
35752
+ const entry = useStore.getState().collections[collectionId];
35753
+ if (entry) {
35754
+ await electron$n.saveCollection(entry.relPath, entry.data);
35755
+ markCollectionClean(collectionId);
35756
+ }
35757
+ onClose();
35758
+ } finally {
35759
+ setLoading(false);
35760
+ }
35761
+ }
35762
+ if (!col) return null;
35763
+ if (specEntries) {
35764
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60", onClick: onClose, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
35765
+ "div",
35766
+ {
35767
+ className: "bg-surface-900 border border-surface-700 rounded-xl shadow-2xl w-[640px] max-h-[80vh] p-5 flex flex-col gap-4",
35768
+ onClick: (e) => e.stopPropagation(),
35769
+ children: [
35770
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
35771
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-sm font-semibold text-surface-100", children: [
35772
+ "Sync schemas — ",
35773
+ scopeLabel
35774
+ ] }),
35775
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-500 hover:text-surface-300 text-lg leading-none", children: "×" })
35776
+ ] }),
35777
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 text-xs", children: [
35778
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-400", children: [
35779
+ specEntries.length,
35780
+ " operations in spec"
35781
+ ] }),
35782
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-400", children: "·" }),
35783
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-400", children: [
35784
+ matches.length,
35785
+ " matched to existing requests"
35786
+ ] }),
35787
+ changedCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
35788
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-400", children: "·" }),
35789
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-amber-400 font-medium", children: [
35790
+ changedCount,
35791
+ " changed"
35792
+ ] })
35793
+ ] }),
35794
+ unchangedCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
35795
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-400", children: "·" }),
35796
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400", children: [
35797
+ unchangedCount,
35798
+ " unchanged"
35799
+ ] })
35800
+ ] })
35801
+ ] }),
35802
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
35803
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium", children: [
35804
+ "Select schemas to update (",
35805
+ selected.size,
35806
+ ")"
35807
+ ] }),
35808
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
35809
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: selectAllChanged, className: "text-[10px] text-blue-400 hover:text-blue-300", children: "Select changed" }),
35810
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: selectNone, className: "text-[10px] text-blue-400 hover:text-blue-300", children: "Select none" })
35811
+ ] })
35812
+ ] }),
35813
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto border border-surface-800 rounded-lg", children: [
35814
+ matches.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "p-4 text-xs text-surface-500", children: "No operations from the spec matched any request in this collection. Matching uses HTTP method + URL path." }) : matches.map((m) => {
35815
+ const key = `${m.specEntry.method}:${m.specEntry.pathTemplate}`;
35816
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
35817
+ "label",
35818
+ {
35819
+ className: `flex items-center gap-2 px-3 py-2 hover:bg-surface-850 cursor-pointer border-b border-surface-800 last:border-b-0 ${!m.changed ? "opacity-50" : ""}`,
35820
+ children: [
35821
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35822
+ "input",
35823
+ {
35824
+ type: "checkbox",
35825
+ checked: selected.has(key),
35826
+ onChange: () => toggleEntry(key),
35827
+ className: "accent-blue-500"
35828
+ }
35829
+ ),
35830
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-mono font-bold w-14 shrink-0 ${methodColor$2(m.specEntry.method)}`, children: m.specEntry.method }),
35831
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
35832
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-surface-200 truncate", children: m.request.name }),
35833
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500 font-mono truncate", children: m.specEntry.pathTemplate })
35834
+ ] }),
35835
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] shrink-0 ${m.changed ? "text-amber-400" : "text-emerald-400"}`, children: m.changed ? "changed" : "up to date" })
35836
+ ]
35837
+ },
35838
+ key
35839
+ );
35840
+ }),
35841
+ specEntries.length > matches.length && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 border-t border-surface-700", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-500", children: [
35842
+ specEntries.length - matches.length,
35843
+ " operations not matched (no request with matching method + path)"
35844
+ ] }) })
35845
+ ] }),
35846
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400", children: error2 }),
35847
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between pt-1", children: [
35848
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35849
+ "button",
35850
+ {
35851
+ onClick: () => setSpecEntries(null),
35852
+ className: "px-3 py-1.5 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
35853
+ children: "Back"
35854
+ }
35855
+ ),
35856
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
35857
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35858
+ "button",
35859
+ {
35860
+ onClick: onClose,
35861
+ className: "px-3 py-1.5 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
35862
+ children: "Cancel"
35863
+ }
35864
+ ),
35865
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35866
+ "button",
35867
+ {
35868
+ disabled: loading || selected.size === 0,
35869
+ onClick: applySync,
35870
+ className: "px-3 py-1.5 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors",
35871
+ children: loading ? "Updating…" : `Update ${selected.size} schema${selected.size !== 1 ? "s" : ""}`
35872
+ }
35873
+ )
35874
+ ] })
35875
+ ] })
35876
+ ]
35877
+ }
35878
+ ) });
35879
+ }
35880
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60", onClick: onClose, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
35881
+ "div",
35882
+ {
35883
+ className: "bg-surface-900 border border-surface-700 rounded-xl shadow-2xl w-[420px] p-5 flex flex-col gap-4",
35884
+ onClick: (e) => e.stopPropagation(),
35885
+ children: [
35886
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
35887
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold text-surface-100", children: "Sync schemas from OpenAPI" }),
35888
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-500 hover:text-surface-300 text-lg leading-none", children: "×" })
35889
+ ] }),
35890
+ /* @__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." }),
35891
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35892
+ "button",
35893
+ {
35894
+ onClick: loadFromFile,
35895
+ disabled: loading,
35896
+ className: "px-3 py-2 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors font-medium",
35897
+ children: loading ? "Loading…" : "Choose file…"
35898
+ }
35899
+ ),
35900
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
35901
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium", children: "Or load from URL" }),
35902
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
35903
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35904
+ "input",
35905
+ {
35906
+ value: url,
35907
+ onChange: (e) => {
35908
+ setUrl(e.target.value);
35909
+ setError(null);
35910
+ },
35911
+ onKeyDown: (e) => {
35912
+ if (e.key === "Enter") loadFromUrl();
35913
+ },
35914
+ placeholder: "https://api.example.com/openapi.json",
35915
+ className: "flex-1 text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 placeholder-surface-600 font-mono"
35916
+ }
35917
+ ),
35918
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35919
+ "button",
35920
+ {
35921
+ onClick: loadFromUrl,
35922
+ disabled: !url.trim() || loading,
35923
+ className: "px-3 py-1.5 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors whitespace-nowrap",
35924
+ children: loading ? "Loading…" : "From URL"
35925
+ }
35926
+ )
35927
+ ] })
35928
+ ] }),
35929
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400", children: error2 }),
35930
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end pt-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35931
+ "button",
35932
+ {
35933
+ onClick: onClose,
35934
+ className: "px-3 py-1.5 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
35935
+ children: "Cancel"
35936
+ }
35937
+ ) })
35938
+ ]
35939
+ }
35940
+ ) });
35941
+ }
35942
+ function methodColor$2(method) {
35943
+ switch (method) {
35944
+ case "GET":
35945
+ return "text-emerald-400";
35946
+ case "POST":
35947
+ return "text-amber-400";
35948
+ case "PUT":
35949
+ return "text-sky-400";
35950
+ case "PATCH":
35951
+ return "text-violet-400";
35952
+ case "DELETE":
35953
+ return "text-red-400";
35954
+ default:
35955
+ return "text-surface-300";
35956
+ }
35957
+ }
35530
35958
  const DragCtx = reactExports.createContext({ dragging: null, setDragging: () => {
35531
- }, onDrop: () => {
35959
+ }, onDropRequest: () => {
35960
+ }, onDropFolder: () => {
35532
35961
  } });
35533
35962
  function InlineEdit({
35534
35963
  value,
@@ -35596,7 +36025,7 @@ function TagChips({
35596
36025
  if (forceAdding) setAdding(true);
35597
36026
  }, [forceAdding]);
35598
36027
  function commit() {
35599
- const t2 = draft.trim().toLowerCase();
36028
+ const t2 = draft.trim();
35600
36029
  if (t2 && !tags2.includes(t2)) onAdd(t2);
35601
36030
  setDraft("");
35602
36031
  setAdding(false);
@@ -35778,6 +36207,7 @@ function CollectionTree() {
35778
36207
  const updateRequest = useStore((s) => s.updateRequest);
35779
36208
  const openRunner = useStore((s) => s.openRunner);
35780
36209
  const moveRequest = useStore((s) => s.moveRequest);
36210
+ const moveFolder = useStore((s) => s.moveFolder);
35781
36211
  const colList = Object.values(collections);
35782
36212
  const [pendingConfirm, setPendingConfirm] = reactExports.useState(null);
35783
36213
  const [newRequestId, setNewRequestId] = reactExports.useState(null);
@@ -35788,12 +36218,18 @@ function CollectionTree() {
35788
36218
  setPendingConfirm(null);
35789
36219
  } });
35790
36220
  }
35791
- function onDrop(destCollectionId, destFolderId, destIndex) {
35792
- if (!dragging) return;
36221
+ function onDropRequest(destCollectionId, destFolderId, destIndex) {
36222
+ if (!dragging || dragging.type !== "request") return;
35793
36223
  moveRequest(dragging.collectionId, dragging.requestId, destCollectionId, destFolderId, destIndex);
35794
36224
  setDragging(null);
35795
36225
  }
35796
- return /* @__PURE__ */ jsxRuntimeExports.jsx(DragCtx.Provider, { value: { dragging, setDragging, onDrop }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 select-none", children: [
36226
+ function onDropFolder(destCollectionId, destParentFolderId, destIndex) {
36227
+ if (!dragging || dragging.type !== "folder") return;
36228
+ if (dragging.collectionId !== destCollectionId) return;
36229
+ moveFolder(dragging.collectionId, dragging.folderId, destParentFolderId, destIndex);
36230
+ setDragging(null);
36231
+ }
36232
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(DragCtx.Provider, { value: { dragging, setDragging, onDropRequest, onDropFolder }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 select-none", children: [
35797
36233
  pendingConfirm && /* @__PURE__ */ jsxRuntimeExports.jsx(
35798
36234
  ConfirmDialog,
35799
36235
  {
@@ -35827,6 +36263,10 @@ function CollectionTree() {
35827
36263
  onUpdateFolderTags: (folderId, tags2) => updateFolderTags(col.id, folderId, tags2),
35828
36264
  onUpdateRequestTags: updateRequestTags,
35829
36265
  onSetRequestHookType: (reqId, hookType) => updateRequest(reqId, { hookType }),
36266
+ onToggleRequestDisabled: (reqId) => {
36267
+ const r = col.requests[reqId];
36268
+ if (r) updateRequest(reqId, { disabled: !r.disabled });
36269
+ },
35830
36270
  onRunCollection: () => openRunner(col.id),
35831
36271
  onRunFolder: (folderId) => openRunner(col.id, folderId)
35832
36272
  },
@@ -35862,12 +36302,14 @@ function CollectionNode({
35862
36302
  onUpdateFolderTags,
35863
36303
  onUpdateRequestTags,
35864
36304
  onSetRequestHookType,
36305
+ onToggleRequestDisabled,
35865
36306
  onRunCollection,
35866
36307
  onRunFolder
35867
36308
  }) {
35868
36309
  const [expanded, setExpanded] = reactExports.useState(true);
35869
36310
  const [renaming, setRenaming] = reactExports.useState(false);
35870
36311
  const [showSettings, setShowSettings] = reactExports.useState(false);
36312
+ const [showSchemaSync, setShowSchemaSync] = reactExports.useState(false);
35871
36313
  const [expandCtrl, setExpandCtrl] = reactExports.useState({ value: true, seq: 0 });
35872
36314
  const [dropOver, setDropOver] = reactExports.useState(false);
35873
36315
  const dragCtx = reactExports.useContext(DragCtx);
@@ -35894,7 +36336,8 @@ function CollectionNode({
35894
36336
  onDrop: (e) => {
35895
36337
  e.preventDefault();
35896
36338
  setDropOver(false);
35897
- dragCtx.onDrop(col.id, col.rootFolder.id);
36339
+ if (dragCtx.dragging?.type === "folder") dragCtx.onDropFolder(col.id, col.rootFolder.id);
36340
+ else dragCtx.onDropRequest(col.id, col.rootFolder.id);
35898
36341
  },
35899
36342
  children: [
35900
36343
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] w-3 text-center shrink-0 mt-0.5", children: expanded ? "▾" : "▸" }),
@@ -35922,6 +36365,7 @@ function CollectionNode({
35922
36365
  { type: "separator" },
35923
36366
  { type: "item", label: "Collection data", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TableIcon, {}), onClick: onSelectCollection },
35924
36367
  { type: "item", label: "Settings", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(GearIcon, {}), onClick: () => setShowSettings(true) },
36368
+ { type: "item", label: "Sync schemas", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
35925
36369
  { type: "item", label: "Rename", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}), onClick: () => setRenaming(true) },
35926
36370
  { type: "item", label: "Duplicate", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: onDuplicateCollection },
35927
36371
  { type: "separator" },
@@ -35952,15 +36396,19 @@ function CollectionNode({
35952
36396
  onUpdateFolderTags,
35953
36397
  onUpdateRequestTags,
35954
36398
  onSetRequestHookType,
36399
+ onToggleRequestDisabled,
35955
36400
  onRunFolder
35956
36401
  }
35957
36402
  ),
35958
- showSettings && /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionSettingsModal, { collection: col, onClose: () => setShowSettings(false) })
36403
+ showSettings && /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionSettingsModal, { collection: col, onClose: () => setShowSettings(false) }),
36404
+ showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(SchemaSyncModal, { collectionId: col.id, scope: { type: "collection" }, onClose: () => setShowSchemaSync(false) })
35959
36405
  ] });
35960
36406
  }
35961
36407
  function FolderRow({
35962
36408
  folder,
35963
36409
  collectionId,
36410
+ parentFolderId,
36411
+ folderIndex,
35964
36412
  depth,
35965
36413
  expandCtrl,
35966
36414
  onAddRequest,
@@ -35972,36 +36420,73 @@ function FolderRow({
35972
36420
  onRun,
35973
36421
  children
35974
36422
  }) {
35975
- const [expanded, setExpanded] = reactExports.useState(true);
36423
+ const [expanded, setExpanded] = reactExports.useState(false);
35976
36424
  reactExports.useEffect(() => {
35977
36425
  if (expandCtrl.seq > 0) setExpanded(expandCtrl.value);
35978
36426
  }, [expandCtrl.seq]);
35979
36427
  const [renaming, setRenaming] = reactExports.useState(false);
35980
36428
  const [showSettings, setShowSettings] = reactExports.useState(false);
36429
+ const [showSchemaSync, setShowSchemaSync] = reactExports.useState(false);
35981
36430
  const [addingTag, setAddingTag] = reactExports.useState(false);
35982
- const [dropOver, setDropOver] = reactExports.useState(false);
36431
+ const [dropPos, setDropPos] = reactExports.useState(null);
35983
36432
  const dragCtx = reactExports.useContext(DragCtx);
35984
36433
  const tags2 = folder.tags ?? [];
35985
36434
  const indent = depth * 12 + 8;
35986
36435
  const hasInheritedConfig = folder.auth && folder.auth.type !== "none" || folder.headers && folder.headers.length > 0;
35987
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
36436
+ function handleFolderDragOver(e) {
36437
+ if (!dragCtx.dragging) return;
36438
+ if (dragCtx.dragging.type === "folder" && dragCtx.dragging.folderId === folder.id) return;
36439
+ e.preventDefault();
36440
+ const rect = e.currentTarget.getBoundingClientRect();
36441
+ const y = e.clientY - rect.top;
36442
+ const zone = y / rect.height;
36443
+ if (dragCtx.dragging.type === "folder") {
36444
+ if (zone < 0.25) setDropPos("before");
36445
+ else if (zone > 0.75) setDropPos("after");
36446
+ else setDropPos("inside");
36447
+ } else {
36448
+ setDropPos("inside");
36449
+ }
36450
+ }
36451
+ function handleFolderDrop(e) {
36452
+ e.preventDefault();
36453
+ e.stopPropagation();
36454
+ if (!dragCtx.dragging) return;
36455
+ if (dragCtx.dragging.type === "folder") {
36456
+ if (dropPos === "inside") {
36457
+ dragCtx.onDropFolder(collectionId, folder.id);
36458
+ } else {
36459
+ const insertIndex = dropPos === "before" ? folderIndex : folderIndex + 1;
36460
+ dragCtx.onDropFolder(collectionId, parentFolderId, insertIndex);
36461
+ }
36462
+ } else {
36463
+ dragCtx.onDropRequest(collectionId, folder.id);
36464
+ }
36465
+ setDropPos(null);
36466
+ }
36467
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
36468
+ dropPos === "before" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 inset-x-0 h-0.5 bg-blue-500 z-10 pointer-events-none" }),
35988
36469
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
35989
36470
  "div",
35990
36471
  {
35991
- className: `group flex items-start gap-1 py-1 hover:bg-surface-800 transition-colors cursor-pointer text-surface-400 ${dropOver ? "outline outline-1 outline-blue-500 rounded" : ""}`,
36472
+ draggable: true,
36473
+ className: `group flex items-start gap-1 py-1 hover:bg-surface-800 transition-colors cursor-pointer text-surface-400 ${dropPos === "inside" ? "outline outline-1 outline-blue-500 rounded" : ""}`,
35992
36474
  style: { paddingLeft: indent },
35993
36475
  onClick: () => setExpanded((e) => !e),
35994
- onDragOver: dragCtx.dragging ? (e) => {
35995
- e.preventDefault();
35996
- setDropOver(true);
35997
- } : void 0,
35998
- onDragLeave: () => setDropOver(false),
35999
- onDrop: (e) => {
36000
- e.preventDefault();
36001
- setDropOver(false);
36002
- dragCtx.onDrop(collectionId, folder.id);
36476
+ onDragStart: (e) => {
36477
+ e.dataTransfer.effectAllowed = "move";
36478
+ e.stopPropagation();
36479
+ dragCtx.setDragging({ type: "folder", folderId: folder.id, collectionId });
36480
+ },
36481
+ onDragEnd: () => {
36482
+ dragCtx.setDragging(null);
36483
+ setDropPos(null);
36003
36484
  },
36485
+ onDragOver: handleFolderDragOver,
36486
+ onDragLeave: () => setDropPos(null),
36487
+ onDrop: handleFolderDrop,
36004
36488
  children: [
36489
+ dropPos === "after" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 inset-x-0 h-0.5 bg-blue-500 z-10 pointer-events-none" }),
36005
36490
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] w-3 text-center shrink-0 mt-0.5", children: expanded ? "▾" : "▸" }),
36006
36491
  /* @__PURE__ */ jsxRuntimeExports.jsx(FolderIcon, { className: `shrink-0 mt-0.5 ${hasInheritedConfig ? "text-blue-500" : "text-amber-600"}` }),
36007
36492
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
@@ -36035,6 +36520,7 @@ function FolderRow({
36035
36520
  { type: "item", label: "Add sub-folder", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(FolderIcon, {}), onClick: onAddFolder },
36036
36521
  { type: "separator" },
36037
36522
  { type: "item", label: "Settings", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(KeyIcon, {}), onClick: () => setShowSettings(true) },
36523
+ { type: "item", label: "Sync schemas", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
36038
36524
  { type: "item", label: "Add tag", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TagIcon, {}), onClick: () => setAddingTag(true) },
36039
36525
  { type: "item", label: "Rename", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}), onClick: () => setRenaming(true) },
36040
36526
  { type: "item", label: "Duplicate", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: onDuplicate },
@@ -36052,6 +36538,14 @@ function FolderRow({
36052
36538
  folder,
36053
36539
  onClose: () => setShowSettings(false)
36054
36540
  }
36541
+ ),
36542
+ showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(
36543
+ SchemaSyncModal,
36544
+ {
36545
+ collectionId,
36546
+ scope: { type: "folder", folderId: folder.id },
36547
+ onClose: () => setShowSchemaSync(false)
36548
+ }
36055
36549
  )
36056
36550
  ] });
36057
36551
  }
@@ -36075,14 +36569,17 @@ function FolderContents({
36075
36569
  onUpdateFolderTags,
36076
36570
  onUpdateRequestTags,
36077
36571
  onSetRequestHookType,
36572
+ onToggleRequestDisabled,
36078
36573
  onRunFolder
36079
36574
  }) {
36080
36575
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
36081
- folder.folders.map((sub) => /* @__PURE__ */ jsxRuntimeExports.jsx(
36576
+ folder.folders.map((sub, subIndex) => /* @__PURE__ */ jsxRuntimeExports.jsx(
36082
36577
  FolderRow,
36083
36578
  {
36084
36579
  folder: sub,
36085
36580
  collectionId,
36581
+ parentFolderId: folder.id,
36582
+ folderIndex: subIndex,
36086
36583
  depth: depth + 1,
36087
36584
  expandCtrl,
36088
36585
  onAddRequest: () => onAddRequest(sub.id),
@@ -36114,6 +36611,7 @@ function FolderContents({
36114
36611
  onUpdateFolderTags,
36115
36612
  onUpdateRequestTags,
36116
36613
  onSetRequestHookType,
36614
+ onToggleRequestDisabled,
36117
36615
  onRunFolder
36118
36616
  }
36119
36617
  )
@@ -36132,7 +36630,9 @@ function FolderContents({
36132
36630
  reqIndex,
36133
36631
  name: req.name,
36134
36632
  method: req.method,
36633
+ authType: req.auth.type,
36135
36634
  hookType: req.hookType,
36635
+ disabled: req.disabled,
36136
36636
  tags: req.meta?.tags ?? [],
36137
36637
  isActive: req.id === activeRequestId,
36138
36638
  autoRename: req.id === newRequestId,
@@ -36142,7 +36642,8 @@ function FolderContents({
36142
36642
  onDelete: () => onDeleteRequest(req.id),
36143
36643
  onDuplicate: () => onDuplicateRequest(req.id),
36144
36644
  onUpdateTags: (tags2) => onUpdateRequestTags(req.id, tags2),
36145
- onSetHookType: (ht) => onSetRequestHookType(req.id, ht)
36645
+ onSetHookType: (ht) => onSetRequestHookType(req.id, ht),
36646
+ onToggleDisabled: () => onToggleRequestDisabled(req.id)
36146
36647
  },
36147
36648
  req.id
36148
36649
  );
@@ -36161,6 +36662,14 @@ const HOOK_COLORS = {
36161
36662
  after: "bg-cyan-700 text-white",
36162
36663
  afterAll: "bg-cyan-800 text-white"
36163
36664
  };
36665
+ const AUTH_BADGE_LABELS = {
36666
+ basic: "Basic",
36667
+ bearer: "Bearer",
36668
+ apikey: "Key",
36669
+ digest: "Digest",
36670
+ ntlm: "NTLM",
36671
+ oauth2: "OAuth2"
36672
+ };
36164
36673
  function RequestRow({
36165
36674
  reqId,
36166
36675
  collectionId,
@@ -36168,7 +36677,9 @@ function RequestRow({
36168
36677
  reqIndex,
36169
36678
  name: name2,
36170
36679
  method,
36680
+ authType,
36171
36681
  hookType,
36682
+ disabled,
36172
36683
  tags: tags2,
36173
36684
  isActive,
36174
36685
  indent,
@@ -36178,10 +36689,12 @@ function RequestRow({
36178
36689
  onDelete,
36179
36690
  onDuplicate,
36180
36691
  onUpdateTags,
36181
- onSetHookType
36692
+ onSetHookType,
36693
+ onToggleDisabled
36182
36694
  }) {
36183
36695
  const [renaming, setRenaming] = reactExports.useState(autoRename);
36184
36696
  const [addingTag, setAddingTag] = reactExports.useState(false);
36697
+ const [showSchemaSync, setShowSchemaSync] = reactExports.useState(false);
36185
36698
  const [dropPos, setDropPos] = reactExports.useState(null);
36186
36699
  const dragCtx = reactExports.useContext(DragCtx);
36187
36700
  const hookMenuItems = [
@@ -36192,7 +36705,7 @@ function RequestRow({
36192
36705
  }))
36193
36706
  ];
36194
36707
  function handleDragOver(e) {
36195
- if (!dragCtx.dragging || dragCtx.dragging.requestId === reqId) return;
36708
+ if (!dragCtx.dragging || dragCtx.dragging.type !== "request" || dragCtx.dragging.requestId === reqId) return;
36196
36709
  e.preventDefault();
36197
36710
  const rect = e.currentTarget.getBoundingClientRect();
36198
36711
  setDropPos(e.clientY < rect.top + rect.height / 2 ? "before" : "after");
@@ -36201,7 +36714,7 @@ function RequestRow({
36201
36714
  e.preventDefault();
36202
36715
  e.stopPropagation();
36203
36716
  const insertIndex = dropPos === "before" ? reqIndex : reqIndex + 1;
36204
- dragCtx.onDrop(collectionId, folderId, insertIndex);
36717
+ dragCtx.onDropRequest(collectionId, folderId, insertIndex);
36205
36718
  setDropPos(null);
36206
36719
  }
36207
36720
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
@@ -36210,13 +36723,13 @@ function RequestRow({
36210
36723
  "div",
36211
36724
  {
36212
36725
  draggable: true,
36213
- className: `group flex items-start gap-1.5 py-1 pr-1 rounded-sm cursor-pointer transition-colors ${isActive ? "bg-surface-800 text-[var(--text-primary)]" : "text-surface-300 hover:bg-surface-800"}`,
36726
+ className: `group flex items-start gap-1.5 py-1 pr-1 rounded-sm cursor-pointer transition-colors ${disabled ? "opacity-40" : ""} ${isActive ? "bg-surface-800 text-[var(--text-primary)]" : "text-surface-300 hover:bg-surface-800"}`,
36214
36727
  style: { paddingLeft: indent },
36215
36728
  onClick: onSelect,
36216
36729
  onDoubleClick: () => setRenaming(true),
36217
36730
  onDragStart: (e) => {
36218
36731
  e.dataTransfer.effectAllowed = "move";
36219
- dragCtx.setDragging({ requestId: reqId, collectionId });
36732
+ dragCtx.setDragging({ type: "request", requestId: reqId, collectionId });
36220
36733
  },
36221
36734
  onDragEnd: () => {
36222
36735
  dragCtx.setDragging(null);
@@ -36241,7 +36754,15 @@ function RequestRow({
36241
36754
  }
36242
36755
  ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 min-w-0", children: [
36243
36756
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs truncate", children: name2 }),
36244
- hookType && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[9px] font-bold px-1 py-px rounded ${HOOK_COLORS[hookType]}`, children: HOOK_LABELS[hookType].toUpperCase() })
36757
+ hookType && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[9px] font-bold px-1 py-px rounded ${HOOK_COLORS[hookType]}`, children: HOOK_LABELS[hookType].toUpperCase() }),
36758
+ authType !== "none" && /* @__PURE__ */ jsxRuntimeExports.jsx(
36759
+ "span",
36760
+ {
36761
+ className: "shrink-0 text-[9px] px-1 py-px rounded bg-amber-800/40 text-amber-400",
36762
+ title: `Auth: ${AUTH_BADGE_LABELS[authType] ?? authType}`,
36763
+ children: AUTH_BADGE_LABELS[authType] ?? authType
36764
+ }
36765
+ )
36245
36766
  ] }),
36246
36767
  (tags2.length > 0 || addingTag) && /* @__PURE__ */ jsxRuntimeExports.jsx(
36247
36768
  TagChips,
@@ -36258,6 +36779,8 @@ function RequestRow({
36258
36779
  { type: "item", label: "Rename", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}), onClick: () => setRenaming(true) },
36259
36780
  { type: "item", label: "Duplicate", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: onDuplicate },
36260
36781
  { type: "item", label: "Add tag", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TagIcon, {}), onClick: () => setAddingTag(true) },
36782
+ { type: "item", label: "Sync schema", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
36783
+ { type: "item", label: disabled ? "Enable" : "Disable", onClick: onToggleDisabled },
36261
36784
  { type: "separator" },
36262
36785
  { type: "header", label: "Hook type" },
36263
36786
  ...hookMenuItems,
@@ -36267,6 +36790,14 @@ function RequestRow({
36267
36790
  dropPos === "after" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 inset-x-0 h-0.5 bg-blue-500 z-10 pointer-events-none" })
36268
36791
  ]
36269
36792
  }
36793
+ ),
36794
+ showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(
36795
+ SchemaSyncModal,
36796
+ {
36797
+ collectionId,
36798
+ scope: { type: "request", requestId: reqId },
36799
+ onClose: () => setShowSchemaSync(false)
36800
+ }
36270
36801
  )
36271
36802
  ] });
36272
36803
  }
@@ -36306,48 +36837,52 @@ function ExpandAllIcon() {
36306
36837
  function CollapseAllIcon() {
36307
36838
  return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 6h16M4 12h8M4 18h16M21 12l-3-3-3 3" }) });
36308
36839
  }
36840
+ function SyncIcon() {
36841
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182M20.984 4.356v4.993" }) });
36842
+ }
36309
36843
  function GearIcon() {
36310
36844
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "w-3 h-3", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", children: [
36311
36845
  /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" }),
36312
36846
  /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" })
36313
36847
  ] });
36314
36848
  }
36315
- function findFolderById(root2, id2) {
36316
- if (root2.id === id2) return root2;
36849
+ function folderChainTo(root2, targetId) {
36850
+ if (root2.id === targetId) return [root2];
36317
36851
  for (const sub of root2.folders) {
36318
- const found = findFolderById(sub, id2);
36319
- if (found) return found;
36852
+ const chain = folderChainTo(sub, targetId);
36853
+ if (chain.length > 0) return [root2, ...chain];
36320
36854
  }
36321
- return null;
36855
+ return [];
36322
36856
  }
36323
- function makeHook(req, collectionVars, hookType, scopeId, scopeAncestors, mainRequestId) {
36324
- return { request: req, collectionVars, isHook: true, hookType, scopeId, scopeAncestors, mainRequestId };
36857
+ function makeHook(req, collectionVars, hookType, scopeId, scopeAncestors, scopePath, mainRequestId) {
36858
+ return { request: req, collectionVars, isHook: true, hookType, scopeId, scopeAncestors, scopePath, mainRequestId };
36325
36859
  }
36326
- function buildFolderPlan(folder, requests, collectionVars, filterTags, scopeId, ancestorIds, wrappers) {
36860
+ function buildFolderPlan(folder, requests, collectionVars, filterTags, scopeId, ancestorIds, parentPath, wrappers, isRoot) {
36327
36861
  const result = [];
36328
- const folderReqs = folder.requestIds.map((id2) => requests[id2]).filter(Boolean);
36862
+ const scopePath = isRoot ? [] : [...parentPath, folder.name];
36863
+ const folderReqs = folder.requestIds.map((id2) => requests[id2]).filter((r) => r && !r.disabled);
36329
36864
  const beforeAllHooks = folderReqs.filter((r) => r.hookType === "beforeAll");
36330
36865
  const beforeHooks = folderReqs.filter((r) => r.hookType === "before");
36331
36866
  const afterHooks = folderReqs.filter((r) => r.hookType === "after");
36332
36867
  const afterAllHooks = folderReqs.filter((r) => r.hookType === "afterAll");
36333
36868
  const regularReqs = folderReqs.filter((r) => !r.hookType);
36334
- const myWrapper = { scopeId, ancestors: ancestorIds, before: beforeHooks, after: afterHooks };
36869
+ const myWrapper = { scopeId, ancestors: ancestorIds, scopePath, before: beforeHooks, after: afterHooks };
36335
36870
  const allWrappers = [...wrappers, myWrapper];
36336
36871
  for (const req of beforeAllHooks) {
36337
- result.push(makeHook(req, collectionVars, "beforeAll", scopeId, ancestorIds));
36872
+ result.push(makeHook(req, collectionVars, "beforeAll", scopeId, ancestorIds, scopePath));
36338
36873
  }
36339
36874
  for (const req of regularReqs) {
36340
36875
  const tags2 = req.meta?.tags ?? [];
36341
36876
  if (filterTags.length > 0 && !filterTags.some((t2) => tags2.includes(t2))) continue;
36342
36877
  for (const w of allWrappers) {
36343
36878
  for (const hookReq of w.before) {
36344
- result.push(makeHook(hookReq, collectionVars, "before", w.scopeId, w.ancestors, req.id));
36879
+ result.push(makeHook(hookReq, collectionVars, "before", w.scopeId, w.ancestors, w.scopePath, req.id));
36345
36880
  }
36346
36881
  }
36347
- result.push({ request: req, collectionVars, scopeId, scopeAncestors: ancestorIds });
36882
+ result.push({ request: req, collectionVars, scopeId, scopeAncestors: ancestorIds, scopePath });
36348
36883
  for (const w of [...allWrappers].reverse()) {
36349
36884
  for (const hookReq of w.after) {
36350
- result.push(makeHook(hookReq, collectionVars, "after", w.scopeId, w.ancestors, req.id));
36885
+ result.push(makeHook(hookReq, collectionVars, "after", w.scopeId, w.ancestors, w.scopePath, req.id));
36351
36886
  }
36352
36887
  }
36353
36888
  }
@@ -36361,11 +36896,13 @@ function buildFolderPlan(folder, requests, collectionVars, filterTags, scopeId,
36361
36896
  effectiveFilter,
36362
36897
  sub.id,
36363
36898
  [...ancestorIds, scopeId],
36364
- allWrappers
36899
+ scopePath,
36900
+ allWrappers,
36901
+ false
36365
36902
  ));
36366
36903
  }
36367
36904
  for (const req of afterAllHooks) {
36368
- result.push(makeHook(req, collectionVars, "afterAll", scopeId, ancestorIds));
36905
+ result.push(makeHook(req, collectionVars, "afterAll", scopeId, ancestorIds, scopePath));
36369
36906
  }
36370
36907
  return result;
36371
36908
  }
@@ -36394,7 +36931,7 @@ function getHooksForRequest(requestId, collection) {
36394
36931
  const before = [];
36395
36932
  const afterReversed = [];
36396
36933
  for (const folder of path) {
36397
- const reqs = folder.requestIds.map((id2) => collection.requests[id2]).filter(Boolean);
36934
+ const reqs = folder.requestIds.map((id2) => collection.requests[id2]).filter((r) => r && !r.disabled);
36398
36935
  const bAll = reqs.filter((r) => r.hookType === "beforeAll");
36399
36936
  const bEach = reqs.filter((r) => r.hookType === "before");
36400
36937
  const aEach = reqs.filter((r) => r.hookType === "after");
@@ -36406,20 +36943,65 @@ function getHooksForRequest(requestId, collection) {
36406
36943
  }
36407
36944
  function buildRunPlan(collection, folderId, filterTags) {
36408
36945
  const collectionVars = collection.collectionVariables ?? {};
36409
- if (folderId) {
36410
- const folder = findFolderById(collection.rootFolder, folderId);
36411
- if (!folder) return [];
36412
- return buildFolderPlan(folder, collection.requests, collectionVars, filterTags, folder.id, [], []);
36946
+ if (!folderId) {
36947
+ return buildFolderPlan(
36948
+ collection.rootFolder,
36949
+ collection.requests,
36950
+ collectionVars,
36951
+ filterTags,
36952
+ collection.rootFolder.id,
36953
+ [],
36954
+ [],
36955
+ [],
36956
+ true
36957
+ );
36958
+ }
36959
+ const chain = folderChainTo(collection.rootFolder, folderId);
36960
+ if (chain.length === 0) return [];
36961
+ const targetFolder = chain[chain.length - 1];
36962
+ const ancestors = chain.slice(0, -1);
36963
+ const result = [];
36964
+ const ancestorWrappers = [];
36965
+ const ancestorIds = [];
36966
+ for (const f of ancestors) {
36967
+ const reqs = f.requestIds.map((id2) => collection.requests[id2]).filter((r) => r && !r.disabled);
36968
+ const beforeAllH = reqs.filter((r) => r.hookType === "beforeAll");
36969
+ const beforeH = reqs.filter((r) => r.hookType === "before");
36970
+ const afterH = reqs.filter((r) => r.hookType === "after");
36971
+ for (const req of beforeAllH) {
36972
+ result.push(makeHook(req, collectionVars, "beforeAll", f.id, [...ancestorIds], []));
36973
+ }
36974
+ ancestorWrappers.push({
36975
+ scopeId: f.id,
36976
+ ancestors: [...ancestorIds],
36977
+ scopePath: [],
36978
+ // ancestor hooks render with no folder heading
36979
+ before: beforeH,
36980
+ after: afterH
36981
+ });
36982
+ ancestorIds.push(f.id);
36413
36983
  }
36414
- return buildFolderPlan(
36415
- collection.rootFolder,
36984
+ result.push(...buildFolderPlan(
36985
+ targetFolder,
36416
36986
  collection.requests,
36417
36987
  collectionVars,
36418
36988
  filterTags,
36419
- collection.rootFolder.id,
36989
+ targetFolder.id,
36990
+ ancestorIds,
36420
36991
  [],
36421
- []
36422
- );
36992
+ ancestorWrappers,
36993
+ true
36994
+ ));
36995
+ for (let i = ancestors.length - 1; i >= 0; i--) {
36996
+ const f = ancestors[i];
36997
+ const reqs = f.requestIds.map((id2) => collection.requests[id2]).filter((r) => r && !r.disabled);
36998
+ const afterAllH = reqs.filter((r) => r.hookType === "afterAll");
36999
+ const myAncestors = ancestorIds.slice(0, i);
37000
+ for (const req of afterAllH) {
37001
+ result.push(makeHook(req, collectionVars, "afterAll", f.id, myAncestors, []));
37002
+ }
37003
+ }
37004
+ return result;
36423
37005
  }
36424
37006
  function ParamsTab({ request, onChange }) {
36425
37007
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -36428,7 +37010,8 @@ function ParamsTab({ request, onChange }) {
36428
37010
  rows: request.params,
36429
37011
  onChange: (rows) => onChange({ params: rows }),
36430
37012
  keyPlaceholder: "param",
36431
- valuePlaceholder: "value"
37013
+ valuePlaceholder: "value",
37014
+ paramMode: true
36432
37015
  }
36433
37016
  );
36434
37017
  }
@@ -42332,7 +42915,7 @@ const autoCloseTags$1 = /* @__PURE__ */ EditorView.inputHandler.of((view, from,
42332
42915
  ]);
42333
42916
  return true;
42334
42917
  });
42335
- const { electron: electron$l } = window;
42918
+ const { electron: electron$m } = window;
42336
42919
  function SoapEditor({ request, onChange }) {
42337
42920
  const soap = request.body.soap ?? {
42338
42921
  wsdlUrl: "",
@@ -42349,7 +42932,7 @@ function SoapEditor({ request, onChange }) {
42349
42932
  setFetching(true);
42350
42933
  setFetchError(null);
42351
42934
  try {
42352
- const result = await electron$l.wsdlFetch(soap.wsdlUrl.trim());
42935
+ const result = await electron$m.wsdlFetch(soap.wsdlUrl.trim());
42353
42936
  setOperations(result.operations);
42354
42937
  if (result.operations.length > 0) {
42355
42938
  const first = result.operations[0];
@@ -42463,11 +43046,12 @@ function BodyTab({ request, onChange }) {
42463
43046
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: mode === m ? "text-white" : "text-surface-400", children: m })
42464
43047
  ] }, m)) }),
42465
43048
  mode === "none" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: "No request body." }),
42466
- mode === "json" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded overflow-hidden border border-surface-700", style: { height: 140 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
43049
+ mode === "json" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded overflow-hidden border border-surface-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
42467
43050
  ReactCodeMirror,
42468
43051
  {
42469
43052
  value: body.json ?? "",
42470
- height: "140px",
43053
+ height: "300px",
43054
+ maxHeight: "50vh",
42471
43055
  theme: oneDark,
42472
43056
  extensions: [json(), varExt],
42473
43057
  onChange: (val) => onChange({ body: { ...body, json: val } }),
@@ -42493,11 +43077,12 @@ function BodyTab({ request, onChange }) {
42493
43077
  className: "text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1 w-48 focus:outline-none focus:border-blue-500"
42494
43078
  }
42495
43079
  ),
42496
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded overflow-hidden border border-surface-700", style: { height: 120 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
43080
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded overflow-hidden border border-surface-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
42497
43081
  ReactCodeMirror,
42498
43082
  {
42499
43083
  value: body.raw ?? "",
42500
- height: "120px",
43084
+ height: "300px",
43085
+ maxHeight: "50vh",
42501
43086
  theme: oneDark,
42502
43087
  extensions: [varExt],
42503
43088
  onChange: (val) => onChange({ body: { ...body, raw: val } }),
@@ -42509,7 +43094,7 @@ function BodyTab({ request, onChange }) {
42509
43094
  mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request, onChange }) })
42510
43095
  ] });
42511
43096
  }
42512
- const { electron: electron$k } = window;
43097
+ const { electron: electron$l } = window;
42513
43098
  const AUTH_TYPES = ["none", "bearer", "basic", "digest", "ntlm", "apikey", "oauth2"];
42514
43099
  function AuthTab({ request, onChange }) {
42515
43100
  const auth = request.auth;
@@ -42523,7 +43108,7 @@ function AuthTab({ request, onChange }) {
42523
43108
  }
42524
43109
  async function saveSecret(ref2) {
42525
43110
  if (!secretValue || !ref2) return;
42526
- await electron$k.setSecret(ref2, secretValue);
43111
+ await electron$l.setSecret(ref2, secretValue);
42527
43112
  setSaved(true);
42528
43113
  setSecretValue("");
42529
43114
  setTimeout(() => setSaved(false), 2e3);
@@ -42534,14 +43119,14 @@ function AuthTab({ request, onChange }) {
42534
43119
  try {
42535
43120
  const vars = {};
42536
43121
  if (auth.oauth2Flow === "authorization_code") {
42537
- const result = await electron$k.oauth2StartFlow(auth, vars);
43122
+ const result = await electron$l.oauth2StartFlow(auth, vars);
42538
43123
  setAuth({
42539
43124
  oauth2CachedToken: result.accessToken,
42540
43125
  oauth2TokenExpiry: result.expiresAt
42541
43126
  });
42542
43127
  if (result.refreshToken) setOauth2RT(result.refreshToken);
42543
43128
  } else {
42544
- const result = await electron$k.oauth2StartFlow(auth, vars);
43129
+ const result = await electron$l.oauth2StartFlow(auth, vars);
42545
43130
  setAuth({
42546
43131
  oauth2CachedToken: result.accessToken,
42547
43132
  oauth2TokenExpiry: result.expiresAt
@@ -42559,7 +43144,7 @@ function AuthTab({ request, onChange }) {
42559
43144
  setOauth2Status("fetching");
42560
43145
  setOauth2Error("");
42561
43146
  try {
42562
- const result = await electron$k.oauth2RefreshToken(auth, {}, oauth2RefreshToken);
43147
+ const result = await electron$l.oauth2RefreshToken(auth, {}, oauth2RefreshToken);
42563
43148
  setAuth({
42564
43149
  oauth2CachedToken: result.accessToken,
42565
43150
  oauth2TokenExpiry: result.expiresAt
@@ -50029,13 +50614,22 @@ const require$$3 = {
50029
50614
  })(ajv$1, ajv$1.exports);
50030
50615
  var ajvExports = ajv$1.exports;
50031
50616
  const Ajv = /* @__PURE__ */ getDefaultExportFromCjs(ajvExports);
50032
- const ajv = new Ajv({ allErrors: true });
50617
+ const ajv = new Ajv({ allErrors: true, strict: false });
50033
50618
  function SchemaTab({ request, onChange }) {
50034
50619
  const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
50035
50620
  const lastResponse = activeTab?.lastResponse ?? null;
50036
50621
  const [result, setResult] = reactExports.useState(null);
50037
50622
  const [error2, setError] = reactExports.useState(null);
50038
50623
  const schemaValue = request.schema ?? "";
50624
+ const contractSchema = request.contract?.bodySchema ?? "";
50625
+ const canDerive = Boolean(contractSchema.trim());
50626
+ function setSchema(val) {
50627
+ onChange({ schema: val });
50628
+ }
50629
+ function deriveFromContract() {
50630
+ if (!canDerive) return;
50631
+ setSchema(contractSchema);
50632
+ }
50039
50633
  function validate2() {
50040
50634
  setError(null);
50041
50635
  setResult(null);
@@ -50075,25 +50669,40 @@ function SchemaTab({ request, onChange }) {
50075
50669
  setError(`Schema compile error: ${e instanceof Error ? e.message : String(e)}`);
50076
50670
  }
50077
50671
  }
50078
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 h-full", children: [
50672
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
50079
50673
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
50080
50674
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-600 uppercase tracking-wider font-medium", children: "JSON Schema (draft-07+)" }),
50081
- /* @__PURE__ */ jsxRuntimeExports.jsx(
50082
- "button",
50083
- {
50084
- onClick: validate2,
50085
- className: "px-3 py-1 text-xs bg-blue-700 hover:bg-blue-600 rounded transition-colors font-medium",
50086
- children: "Validate"
50087
- }
50088
- )
50675
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
50676
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
50677
+ "button",
50678
+ {
50679
+ onClick: deriveFromContract,
50680
+ disabled: !canDerive,
50681
+ title: canDerive ? "Copy the body schema from this request's contract" : "No contract body schema defined yet",
50682
+ className: "px-3 py-1 text-xs bg-surface-800 hover:bg-surface-700 disabled:bg-surface-900 disabled:text-surface-600 rounded transition-colors font-medium",
50683
+ children: "Derive from contract"
50684
+ }
50685
+ ),
50686
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
50687
+ "button",
50688
+ {
50689
+ onClick: validate2,
50690
+ className: "px-3 py-1 text-xs bg-blue-700 hover:bg-blue-600 rounded transition-colors font-medium",
50691
+ children: "Validate"
50692
+ }
50693
+ )
50694
+ ] })
50089
50695
  ] }),
50090
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-[160px] border border-surface-700 rounded overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
50696
+ /* @__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." }),
50697
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border border-surface-700 rounded overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
50091
50698
  ReactCodeMirror,
50092
50699
  {
50093
50700
  value: schemaValue,
50701
+ height: "300px",
50702
+ maxHeight: "50vh",
50094
50703
  theme: oneDark,
50095
50704
  extensions: [json()],
50096
- onChange: (val) => onChange({ schema: val }),
50705
+ onChange: (val) => setSchema(val),
50097
50706
  placeholder: '{\n "type": "object",\n "properties": {}\n}',
50098
50707
  basicSetup: { lineNumbers: true, foldGutter: true }
50099
50708
  }
@@ -50113,7 +50722,7 @@ function SchemaTab({ request, onChange }) {
50113
50722
  ] }) })
50114
50723
  ] });
50115
50724
  }
50116
- const { electron: electron$j } = window;
50725
+ const { electron: electron$k } = window;
50117
50726
  const EMPTY = { statusCode: 200, headers: [], bodySchema: "" };
50118
50727
  function ContractTab({ request, onChange }) {
50119
50728
  const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
@@ -50127,7 +50736,7 @@ function ContractTab({ request, onChange }) {
50127
50736
  if (!lastResponse?.body) return;
50128
50737
  setInferring(true);
50129
50738
  try {
50130
- const schema = await electron$j.inferContractSchema(lastResponse.body);
50739
+ const schema = await electron$k.inferContractSchema(lastResponse.body);
50131
50740
  if (schema) update({ bodySchema: schema });
50132
50741
  } finally {
50133
50742
  setInferring(false);
@@ -50145,7 +50754,7 @@ function ContractTab({ request, onChange }) {
50145
50754
  update({ headers: (contract.headers ?? []).filter((_, idx) => idx !== i) });
50146
50755
  }
50147
50756
  const hasContract = contract.statusCode !== void 0 || contract.bodySchema?.trim() || contract.headers?.some((h) => h.key);
50148
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4", children: [
50757
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4 h-full min-h-0", children: [
50149
50758
  /* @__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: [
50150
50759
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-2 h-2 rounded-full ${hasContract ? "bg-blue-400" : "bg-surface-600"}` }),
50151
50760
  hasContract ? "Contract defined — will be verified in Contract panel" : "No contract defined yet"
@@ -50221,10 +50830,12 @@ function ContractTab({ request, onChange }) {
50221
50830
  }
50222
50831
  )
50223
50832
  ] }),
50224
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border border-surface-700 rounded overflow-hidden", style: { minHeight: 160 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
50833
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border border-surface-700 rounded overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
50225
50834
  ReactCodeMirror,
50226
50835
  {
50227
50836
  value: contract.bodySchema ?? "",
50837
+ height: "300px",
50838
+ maxHeight: "50vh",
50228
50839
  theme: oneDark,
50229
50840
  extensions: [json()],
50230
50841
  onChange: (val) => update({ bodySchema: val }),
@@ -50235,7 +50846,7 @@ function ContractTab({ request, onChange }) {
50235
50846
  ] })
50236
50847
  ] });
50237
50848
  }
50238
- const { electron: electron$i } = window;
50849
+ const { electron: electron$j } = window;
50239
50850
  function formatTime$1(ts) {
50240
50851
  const d = new Date(ts);
50241
50852
  const hh = String(d.getHours()).padStart(2, "0");
@@ -50255,14 +50866,14 @@ function WebSocketPanel({ request }) {
50255
50866
  const [sendText, setSendText] = reactExports.useState("");
50256
50867
  const logEndRef = reactExports.useRef(null);
50257
50868
  reactExports.useEffect(() => {
50258
- electron$i.onWsMessage(({ requestId, message }) => {
50869
+ electron$j.onWsMessage(({ requestId, message }) => {
50259
50870
  addWsMessage(requestId, message);
50260
50871
  });
50261
- electron$i.onWsStatus(({ requestId, status, error: error2 }) => {
50872
+ electron$j.onWsStatus(({ requestId, status, error: error2 }) => {
50262
50873
  setWsStatus(requestId, status, error2);
50263
50874
  });
50264
50875
  return () => {
50265
- electron$i.offWsEvents();
50876
+ electron$j.offWsEvents();
50266
50877
  };
50267
50878
  }, [addWsMessage, setWsStatus]);
50268
50879
  reactExports.useEffect(() => {
@@ -50275,19 +50886,19 @@ function WebSocketPanel({ request }) {
50275
50886
  if (h.enabled && h.key) headers[h.key] = h.value;
50276
50887
  }
50277
50888
  try {
50278
- await electron$i.wsConnect(request.id, request.url, headers);
50889
+ await electron$j.wsConnect(request.id, request.url, headers);
50279
50890
  } catch (err) {
50280
50891
  setWsStatus(request.id, "error", err instanceof Error ? err.message : String(err));
50281
50892
  }
50282
50893
  }
50283
50894
  async function disconnect() {
50284
- await electron$i.wsDisconnect(request.id);
50895
+ await electron$j.wsDisconnect(request.id);
50285
50896
  }
50286
50897
  async function sendMessage() {
50287
50898
  const text = sendText.trim();
50288
50899
  if (!text || !isConnected) return;
50289
50900
  try {
50290
- await electron$i.wsSend(request.id, text);
50901
+ await electron$j.wsSend(request.id, text);
50291
50902
  const msg = {
50292
50903
  id: crypto.randomUUID(),
50293
50904
  direction: "sent",
@@ -50390,7 +51001,7 @@ function WebSocketPanel({ request }) {
50390
51001
  ] })
50391
51002
  ] });
50392
51003
  }
50393
- const { electron: electron$h } = window;
51004
+ const { electron: electron$i } = window;
50394
51005
  const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
50395
51006
  const METHOD_COLORS = {
50396
51007
  GET: "text-emerald-400",
@@ -50401,6 +51012,13 @@ const METHOD_COLORS = {
50401
51012
  HEAD: "text-purple-400",
50402
51013
  OPTIONS: "text-gray-400"
50403
51014
  };
51015
+ function deriveHookStatus(r) {
51016
+ if (r.scriptResult.postScriptError) return "error";
51017
+ if (r.response.status >= 400) return "failed";
51018
+ const tests = r.scriptResult.testResults;
51019
+ if (tests.length === 0) return "skipped";
51020
+ return tests.every((t2) => t2.passed) ? "passed" : "failed";
51021
+ }
50404
51022
  function RequestBuilder({ request }) {
50405
51023
  const updateRequest = useStore((s) => s.updateRequest);
50406
51024
  const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
@@ -50468,17 +51086,24 @@ function RequestBuilder({ request }) {
50468
51086
  for (const hook of hooks.before) {
50469
51087
  const start = Date.now();
50470
51088
  try {
50471
- const r = await electron$h.sendRequest({ ...basePayload, request: hook, collectionVars, globals: liveGlobals });
51089
+ const hookEnv = activeEnvironmentId ? useStore.getState().environments[activeEnvironmentId]?.data ?? null : null;
51090
+ const hookSessionVars = useStore.getState().sessionVars;
51091
+ const r = await electron$i.sendRequest({
51092
+ ...basePayload,
51093
+ environment: hookEnv,
51094
+ request: hook,
51095
+ collectionVars: { ...collectionVars, ...hookSessionVars },
51096
+ globals: liveGlobals
51097
+ });
50472
51098
  applyScriptUpdates(r.scriptResult);
50473
51099
  collectionVars = { ...collectionVars, ...r.scriptResult.updatedCollectionVars };
50474
51100
  liveGlobals = { ...liveGlobals, ...r.scriptResult.updatedGlobals };
50475
- const allPassed = r.scriptResult.testResults.every((t2) => t2.passed);
50476
51101
  collectedHookResults.push({
50477
51102
  requestId: hook.id,
50478
51103
  name: hook.name,
50479
51104
  method: hook.method,
50480
51105
  resolvedUrl: r.scriptResult.resolvedUrl,
50481
- status: r.scriptResult.postScriptError ? "error" : r.scriptResult.testResults.length > 0 ? allPassed ? "passed" : "failed" : "passed",
51106
+ status: deriveHookStatus(r),
50482
51107
  httpStatus: r.response.status,
50483
51108
  durationMs: Date.now() - start,
50484
51109
  isHook: true,
@@ -50502,10 +51127,13 @@ function RequestBuilder({ request }) {
50502
51127
  });
50503
51128
  }
50504
51129
  }
50505
- const result = await electron$h.sendRequest({
51130
+ const freshEnv = activeEnvironmentId ? useStore.getState().environments[activeEnvironmentId]?.data ?? null : null;
51131
+ const freshSessionVars = useStore.getState().sessionVars;
51132
+ const result = await electron$i.sendRequest({
50506
51133
  ...basePayload,
51134
+ environment: freshEnv,
50507
51135
  request: mergedRequest,
50508
- collectionVars,
51136
+ collectionVars: { ...collectionVars, ...freshSessionVars },
50509
51137
  globals: liveGlobals
50510
51138
  });
50511
51139
  setTabResponse(activeTabId, result.response, result.scriptResult, result.sentRequest);
@@ -50524,17 +51152,24 @@ function RequestBuilder({ request }) {
50524
51152
  for (const hook of hooks.after) {
50525
51153
  const start = Date.now();
50526
51154
  try {
50527
- const r = await electron$h.sendRequest({ ...basePayload, request: hook, collectionVars, globals: liveGlobals });
51155
+ const hookEnv = activeEnvironmentId ? useStore.getState().environments[activeEnvironmentId]?.data ?? null : null;
51156
+ const hookSessionVars = useStore.getState().sessionVars;
51157
+ const r = await electron$i.sendRequest({
51158
+ ...basePayload,
51159
+ environment: hookEnv,
51160
+ request: hook,
51161
+ collectionVars: { ...collectionVars, ...hookSessionVars },
51162
+ globals: liveGlobals
51163
+ });
50528
51164
  applyScriptUpdates(r.scriptResult);
50529
51165
  collectionVars = { ...collectionVars, ...r.scriptResult.updatedCollectionVars };
50530
51166
  liveGlobals = { ...liveGlobals, ...r.scriptResult.updatedGlobals };
50531
- const allPassed = r.scriptResult.testResults.every((t2) => t2.passed);
50532
51167
  collectedHookResults.push({
50533
51168
  requestId: hook.id,
50534
51169
  name: hook.name,
50535
51170
  method: hook.method,
50536
51171
  resolvedUrl: r.scriptResult.resolvedUrl,
50537
- status: r.scriptResult.postScriptError ? "error" : r.scriptResult.testResults.length > 0 ? allPassed ? "passed" : "failed" : "passed",
51172
+ status: deriveHookStatus(r),
50538
51173
  httpStatus: r.response.status,
50539
51174
  durationMs: Date.now() - start,
50540
51175
  isHook: true,
@@ -50672,28 +51307,136 @@ function RequestBuilder({ request }) {
50672
51307
  },
50673
51308
  tab.id
50674
51309
  )) }),
50675
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
50676
- "div",
51310
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 flex-1 overflow-y-auto min-h-0", children: [
51311
+ activeTab === "params" && /* @__PURE__ */ jsxRuntimeExports.jsx(ParamsTab, { request, onChange: update }),
51312
+ activeTab === "headers" && /* @__PURE__ */ jsxRuntimeExports.jsx(HeadersTab, { request, onChange: update }),
51313
+ activeTab === "body" && /* @__PURE__ */ jsxRuntimeExports.jsx(BodyTab, { request, onChange: update }),
51314
+ activeTab === "auth" && /* @__PURE__ */ jsxRuntimeExports.jsx(AuthTab, { request, onChange: update }),
51315
+ activeTab === "scripts" && /* @__PURE__ */ jsxRuntimeExports.jsx(ScriptsTab, { request, onChange: update }),
51316
+ activeTab === "schema" && /* @__PURE__ */ jsxRuntimeExports.jsx(SchemaTab, { request, onChange: update }),
51317
+ activeTab === "contract" && /* @__PURE__ */ jsxRuntimeExports.jsx(ContractTab, { request, onChange: update })
51318
+ ] })
51319
+ ] })
51320
+ ] });
51321
+ }
51322
+ function JsonNode({ nodeKey, value, path, depth, onLeaf }) {
51323
+ const [expanded, setExpanded] = reactExports.useState(depth < 2);
51324
+ const keySpan = nodeKey !== null ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 font-mono text-xs shrink-0 select-all", children: [
51325
+ typeof nodeKey === "number" ? `[${nodeKey}]` : nodeKey,
51326
+ value === null || typeof value !== "object" ? ":" : ""
51327
+ ] }) : null;
51328
+ if (value === null || typeof value !== "object") {
51329
+ const display = value === null ? "null" : typeof value === "string" ? `"${value.length > 100 ? value.slice(0, 100) + "…" : value}"` : String(value);
51330
+ const cls = value === null ? "text-surface-600 italic" : typeof value === "string" ? "text-emerald-400" : typeof value === "number" ? "text-blue-400" : "text-amber-400";
51331
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group flex items-center gap-1.5 py-0.5 pl-1 rounded hover:bg-surface-800/40 min-w-0", children: [
51332
+ keySpan,
51333
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono text-xs ${cls} select-all break-all min-w-0 truncate`, children: display }),
51334
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
51335
+ "button",
50677
51336
  {
50678
- className: "px-4 py-3 flex-1 overflow-y-auto min-h-0",
50679
- style: { minHeight: activeTab === "body" && (request.body.mode === "graphql" || request.body.mode === "soap") || activeTab === "schema" ? "400px" : "180px" },
50680
- children: [
50681
- activeTab === "params" && /* @__PURE__ */ jsxRuntimeExports.jsx(ParamsTab, { request, onChange: update }),
50682
- activeTab === "headers" && /* @__PURE__ */ jsxRuntimeExports.jsx(HeadersTab, { request, onChange: update }),
50683
- activeTab === "body" && /* @__PURE__ */ jsxRuntimeExports.jsx(BodyTab, { request, onChange: update }),
50684
- activeTab === "auth" && /* @__PURE__ */ jsxRuntimeExports.jsx(AuthTab, { request, onChange: update }),
50685
- activeTab === "scripts" && /* @__PURE__ */ jsxRuntimeExports.jsx(ScriptsTab, { request, onChange: update }),
50686
- activeTab === "schema" && /* @__PURE__ */ jsxRuntimeExports.jsx(SchemaTab, { request, onChange: update }),
50687
- activeTab === "contract" && /* @__PURE__ */ jsxRuntimeExports.jsx(ContractTab, { request, onChange: update })
50688
- ]
51337
+ onClick: (e) => onLeaf(e, path, value),
51338
+ className: "ml-auto opacity-0 group-hover:opacity-100 shrink-0 text-[10px] px-1.5 leading-4 py-0.5 text-blue-400 border border-blue-800 hover:border-blue-500 hover:text-blue-300 rounded transition-all",
51339
+ title: "Add assertion for this value",
51340
+ children: "+ insert"
50689
51341
  }
50690
51342
  )
50691
- ] })
51343
+ ] });
51344
+ }
51345
+ const isArr = Array.isArray(value);
51346
+ const entries = isArr ? value.map((v, i) => [i, v]) : Object.entries(value);
51347
+ const summary = isArr ? `[${value.length}]` : `{${entries.length}}`;
51348
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
51349
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
51350
+ "button",
51351
+ {
51352
+ onClick: () => setExpanded((v) => !v),
51353
+ className: "flex items-center gap-1.5 py-0.5 pl-1 rounded hover:bg-surface-800/40 w-full text-left",
51354
+ children: [
51355
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-[10px] w-3 shrink-0 text-center", children: expanded ? "▾" : "▸" }),
51356
+ keySpan,
51357
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-xs", children: summary })
51358
+ ]
51359
+ }
51360
+ ),
51361
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-4 border-l border-surface-800 pl-1", children: entries.map(([k, v]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
51362
+ JsonNode,
51363
+ {
51364
+ nodeKey: k,
51365
+ value: v,
51366
+ path: [...path, k],
51367
+ depth: depth + 1,
51368
+ onLeaf
51369
+ },
51370
+ String(k)
51371
+ )) })
51372
+ ] });
51373
+ }
51374
+ function buildSelector(el) {
51375
+ const parts = [];
51376
+ let cur2 = el;
51377
+ while (cur2) {
51378
+ const parent = cur2.parentElement;
51379
+ if (!parent) break;
51380
+ const tag = cur2.tagName;
51381
+ const siblings = Array.from(parent.children).filter((c) => c.tagName === tag);
51382
+ parts.unshift(siblings.length > 1 ? `${tag}:nth-of-type(${siblings.indexOf(cur2) + 1})` : tag);
51383
+ cur2 = parent;
51384
+ }
51385
+ return parts.join(" > ");
51386
+ }
51387
+ function XmlNode({ element, depth, onLeaf }) {
51388
+ const [expanded, setExpanded] = reactExports.useState(depth < 3);
51389
+ const childEls = Array.from(element.children);
51390
+ const tag = element.tagName;
51391
+ if (childEls.length === 0) {
51392
+ const text = element.textContent ?? "";
51393
+ const selector = buildSelector(element);
51394
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group flex items-center gap-1.5 py-0.5 pl-1 rounded hover:bg-surface-800/40 min-w-0", children: [
51395
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-300 font-mono text-xs shrink-0", children: [
51396
+ "<",
51397
+ tag,
51398
+ ">"
51399
+ ] }),
51400
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400 font-mono text-xs select-all break-all min-w-0 truncate", children: text.length > 100 ? text.slice(0, 100) + "…" : text }),
51401
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
51402
+ "button",
51403
+ {
51404
+ onClick: (e) => onLeaf(e, selector, text),
51405
+ className: "ml-auto opacity-0 group-hover:opacity-100 shrink-0 text-[10px] px-1.5 leading-4 py-0.5 text-blue-400 border border-blue-800 hover:border-blue-500 hover:text-blue-300 rounded transition-all",
51406
+ title: "Add assertion for this value",
51407
+ children: "+ insert"
51408
+ }
51409
+ )
51410
+ ] });
51411
+ }
51412
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
51413
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
51414
+ "button",
51415
+ {
51416
+ onClick: () => setExpanded((v) => !v),
51417
+ className: "flex items-center gap-1.5 py-0.5 pl-1 rounded hover:bg-surface-800/40 w-full text-left",
51418
+ children: [
51419
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-[10px] w-3 shrink-0 text-center", children: expanded ? "▾" : "▸" }),
51420
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-300 font-mono text-xs", children: [
51421
+ "<",
51422
+ tag,
51423
+ ">"
51424
+ ] }),
51425
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-xs ml-1", children: childEls.length })
51426
+ ]
51427
+ }
51428
+ ),
51429
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-4 border-l border-surface-800 pl-1", children: childEls.map((child, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(XmlNode, { element: child, depth: depth + 1, onLeaf }, i)) })
50692
51430
  ] });
50693
51431
  }
50694
51432
  function esc(s) {
50695
51433
  return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "");
50696
51434
  }
51435
+ function toLit(v) {
51436
+ if (v === null) return "null";
51437
+ if (typeof v === "string") return `"${esc(v)}"`;
51438
+ return String(v);
51439
+ }
50697
51440
  function jsonAccessor(path) {
50698
51441
  return path.reduce((acc, key) => {
50699
51442
  if (typeof key === "number") return `${acc}[${key}]`;
@@ -50704,10 +51447,32 @@ function jsonPathLabel(path) {
50704
51447
  if (path.length === 0) return "$";
50705
51448
  return path.map((k) => typeof k === "number" ? `[${k}]` : k).join(".");
50706
51449
  }
50707
- function toLit(v) {
50708
- if (v === null) return "null";
50709
- if (typeof v === "string") return `"${esc(v)}"`;
50710
- return String(v);
51450
+ function getAtPath(root2, path) {
51451
+ let cur2 = root2;
51452
+ for (const key of path) {
51453
+ if (cur2 == null || typeof cur2 !== "object") return void 0;
51454
+ cur2 = cur2[key];
51455
+ }
51456
+ return cur2;
51457
+ }
51458
+ function toJsonPathExpr(path, filterKey, filterValue) {
51459
+ let arrayIdx = -1;
51460
+ for (let i = path.length - 1; i >= 0; i--) {
51461
+ if (typeof path[i] === "number") {
51462
+ arrayIdx = i;
51463
+ break;
51464
+ }
51465
+ }
51466
+ if (arrayIdx < 0) return "";
51467
+ const prefix2 = path.slice(0, arrayIdx).join(".");
51468
+ const arrayPart = prefix2 ? "$." + prefix2 : "$";
51469
+ const leafPart = path.slice(arrayIdx + 1).join(".");
51470
+ const filterVal = isNaN(Number(filterValue)) ? `"${filterValue.replace(/"/g, '\\"')}"` : filterValue;
51471
+ return leafPart ? `${arrayPart}[?(@.${filterKey}==${filterVal})].${leafPart}` : `${arrayPart}[?(@.${filterKey}==${filterVal})]`;
51472
+ }
51473
+ function varNameFromPath(path) {
51474
+ const stringSegments = path.filter((k) => typeof k === "string");
51475
+ return stringSegments[stringSegments.length - 1] ?? "extracted_value";
50711
51476
  }
50712
51477
  function makeJsonSnippet(path, value, mode) {
50713
51478
  const acc = jsonAccessor(path);
@@ -50739,29 +51504,6 @@ function makeJsonSnippet(path, value, mode) {
50739
51504
  });`;
50740
51505
  }
50741
51506
  }
50742
- function getAtPath(root2, path) {
50743
- let cur2 = root2;
50744
- for (const key of path) {
50745
- if (cur2 == null || typeof cur2 !== "object") return void 0;
50746
- cur2 = cur2[key];
50747
- }
50748
- return cur2;
50749
- }
50750
- function toJsonPathExpr(path, filterKey, filterValue) {
50751
- let arrayIdx = -1;
50752
- for (let i = path.length - 1; i >= 0; i--) {
50753
- if (typeof path[i] === "number") {
50754
- arrayIdx = i;
50755
- break;
50756
- }
50757
- }
50758
- if (arrayIdx < 0) return "";
50759
- const arrayPart = "$." + path.slice(0, arrayIdx).join(".");
50760
- const leafPart = path.slice(arrayIdx + 1).join(".");
50761
- const filterVal = isNaN(Number(filterValue)) ? `"${filterValue.replace(/"/g, '\\"')}"` : filterValue;
50762
- const expr = leafPart ? `${arrayPart}[?(@.${filterKey}==${filterVal})].${leafPart}` : `${arrayPart}[?(@.${filterKey}==${filterVal})]`;
50763
- return expr;
50764
- }
50765
51507
  function makeJsonPathSnippet(path, value, filterKey, filterValue) {
50766
51508
  const expr = toJsonPathExpr(path, filterKey, filterValue);
50767
51509
  const lit = toLit(value);
@@ -50788,9 +51530,6 @@ function makeXmlSnippet(selector, value, mode) {
50788
51530
  });`;
50789
51531
  }
50790
51532
  }
50791
- function varNameFromPath(path) {
50792
- return path.filter((k) => typeof k === "string").at(-1) ?? "extracted_value";
50793
- }
50794
51533
  function makeJsonExtractSnippet(path, target) {
50795
51534
  const acc = jsonAccessor(path);
50796
51535
  const varName = varNameFromPath(path);
@@ -50806,11 +51545,7 @@ sp.${target}.set("${varName}", String(matches[0] ?? ''));`;
50806
51545
  function makeXmlExtractSnippet(selector, target) {
50807
51546
  return `sp.${target}.set("extracted_value", sp.response.xmlText("${selector.replace(/"/g, '\\"')}") ?? '');`;
50808
51547
  }
50809
- function AssertMenu({
50810
- state,
50811
- onClose,
50812
- onConfirm
50813
- }) {
51548
+ function AssertMenu({ state, onClose, onConfirm }) {
50814
51549
  const ref2 = reactExports.useRef(null);
50815
51550
  const [jpOpen, setJpOpen] = reactExports.useState(false);
50816
51551
  const [filterKey, setFilterKey] = reactExports.useState("");
@@ -51034,126 +51769,6 @@ function AssertMenu({
51034
51769
  }
51035
51770
  );
51036
51771
  }
51037
- function JsonNode({
51038
- nodeKey,
51039
- value,
51040
- path,
51041
- depth,
51042
- onLeaf
51043
- }) {
51044
- const [expanded, setExpanded] = reactExports.useState(depth < 2);
51045
- const keySpan = nodeKey !== null ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 font-mono text-xs shrink-0 select-all", children: [
51046
- typeof nodeKey === "number" ? `[${nodeKey}]` : nodeKey,
51047
- value === null || typeof value !== "object" ? ":" : ""
51048
- ] }) : null;
51049
- if (value === null || typeof value !== "object") {
51050
- const display = value === null ? "null" : typeof value === "string" ? `"${value.length > 100 ? value.slice(0, 100) + "…" : value}"` : String(value);
51051
- const cls = value === null ? "text-surface-600 italic" : typeof value === "string" ? "text-emerald-400" : typeof value === "number" ? "text-blue-400" : "text-amber-400";
51052
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group flex items-center gap-1.5 py-0.5 pl-1 rounded hover:bg-surface-800/40 min-w-0", children: [
51053
- keySpan,
51054
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono text-xs ${cls} select-all break-all min-w-0 truncate`, children: display }),
51055
- /* @__PURE__ */ jsxRuntimeExports.jsx(
51056
- "button",
51057
- {
51058
- onClick: (e) => onLeaf(e, path, value),
51059
- className: "ml-auto opacity-0 group-hover:opacity-100 shrink-0 text-[10px] px-1.5 leading-4 py-0.5 text-blue-400 border border-blue-800 hover:border-blue-500 hover:text-blue-300 rounded transition-all",
51060
- title: "Add assertion for this value",
51061
- children: "+ insert"
51062
- }
51063
- )
51064
- ] });
51065
- }
51066
- const isArr = Array.isArray(value);
51067
- const entries = isArr ? value.map((v, i) => [i, v]) : Object.entries(value);
51068
- const summary = isArr ? `[${value.length}]` : `{${entries.length}}`;
51069
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
51070
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
51071
- "button",
51072
- {
51073
- onClick: () => setExpanded((v) => !v),
51074
- className: "flex items-center gap-1.5 py-0.5 pl-1 rounded hover:bg-surface-800/40 w-full text-left",
51075
- children: [
51076
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-[10px] w-3 shrink-0 text-center", children: expanded ? "▾" : "▸" }),
51077
- keySpan,
51078
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-xs", children: summary })
51079
- ]
51080
- }
51081
- ),
51082
- expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-4 border-l border-surface-800 pl-1", children: entries.map(([k, v]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
51083
- JsonNode,
51084
- {
51085
- nodeKey: k,
51086
- value: v,
51087
- path: [...path, k],
51088
- depth: depth + 1,
51089
- onLeaf
51090
- },
51091
- String(k)
51092
- )) })
51093
- ] });
51094
- }
51095
- function buildSelector(el) {
51096
- const parts = [];
51097
- let cur2 = el;
51098
- while (cur2) {
51099
- const parent = cur2.parentElement;
51100
- if (!parent) break;
51101
- const tag = cur2.tagName;
51102
- const siblings = Array.from(parent.children).filter((c) => c.tagName === tag);
51103
- parts.unshift(siblings.length > 1 ? `${tag}:nth-of-type(${siblings.indexOf(cur2) + 1})` : tag);
51104
- cur2 = parent;
51105
- }
51106
- return parts.join(" > ");
51107
- }
51108
- function XmlNode({
51109
- element,
51110
- depth,
51111
- onLeaf
51112
- }) {
51113
- const [expanded, setExpanded] = reactExports.useState(depth < 3);
51114
- const childEls = Array.from(element.children);
51115
- const tag = element.tagName;
51116
- if (childEls.length === 0) {
51117
- const text = element.textContent ?? "";
51118
- const selector = buildSelector(element);
51119
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group flex items-center gap-1.5 py-0.5 pl-1 rounded hover:bg-surface-800/40 min-w-0", children: [
51120
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-300 font-mono text-xs shrink-0", children: [
51121
- "<",
51122
- tag,
51123
- ">"
51124
- ] }),
51125
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400 font-mono text-xs select-all break-all min-w-0 truncate", children: text.length > 100 ? text.slice(0, 100) + "…" : text }),
51126
- /* @__PURE__ */ jsxRuntimeExports.jsx(
51127
- "button",
51128
- {
51129
- onClick: (e) => onLeaf(e, selector, text),
51130
- className: "ml-auto opacity-0 group-hover:opacity-100 shrink-0 text-[10px] px-1.5 leading-4 py-0.5 text-blue-400 border border-blue-800 hover:border-blue-500 hover:text-blue-300 rounded transition-all",
51131
- title: "Add assertion for this value",
51132
- children: "+ insert"
51133
- }
51134
- )
51135
- ] });
51136
- }
51137
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
51138
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
51139
- "button",
51140
- {
51141
- onClick: () => setExpanded((v) => !v),
51142
- className: "flex items-center gap-1.5 py-0.5 pl-1 rounded hover:bg-surface-800/40 w-full text-left",
51143
- children: [
51144
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-[10px] w-3 shrink-0 text-center", children: expanded ? "▾" : "▸" }),
51145
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-300 font-mono text-xs", children: [
51146
- "<",
51147
- tag,
51148
- ">"
51149
- ] }),
51150
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-xs ml-1", children: childEls.length })
51151
- ]
51152
- }
51153
- ),
51154
- expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-4 border-l border-surface-800 pl-1", children: childEls.map((child, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(XmlNode, { element: child, depth: depth + 1, onLeaf }, i)) })
51155
- ] });
51156
- }
51157
51772
  function InteractiveBody({ body, contentType, onAssert }) {
51158
51773
  const [popover, setPopover] = reactExports.useState(null);
51159
51774
  const isJson = contentType.includes("json");
@@ -51201,40 +51816,6 @@ function InteractiveBody({ body, contentType, onAssert }) {
51201
51816
  treeContent
51202
51817
  ] });
51203
51818
  }
51204
- const { electron: electron$g } = window;
51205
- function prettyJson(raw) {
51206
- try {
51207
- return JSON.stringify(JSON.parse(raw), null, 2);
51208
- } catch {
51209
- return raw;
51210
- }
51211
- }
51212
- function prettyXml(raw) {
51213
- try {
51214
- const indent = " ";
51215
- let result = "";
51216
- let depth = 0;
51217
- const tokens = raw.match(/<[^>]+>|[^<]+/g) ?? [];
51218
- for (const token of tokens) {
51219
- const text = token.trim();
51220
- if (!text) continue;
51221
- if (text.startsWith("<?") || text.startsWith("<!")) {
51222
- result += indent.repeat(depth) + text + "\n";
51223
- } else if (token.startsWith("</")) {
51224
- depth = Math.max(0, depth - 1);
51225
- result += indent.repeat(depth) + text + "\n";
51226
- } else if (token.startsWith("<") && !token.endsWith("/>") && !token.includes("</")) {
51227
- result += indent.repeat(depth) + text + "\n";
51228
- depth++;
51229
- } else {
51230
- result += indent.repeat(depth) + text + "\n";
51231
- }
51232
- }
51233
- return result.trimEnd();
51234
- } catch {
51235
- return raw;
51236
- }
51237
- }
51238
51819
  const HOOK_BADGE$1 = {
51239
51820
  beforeAll: { label: "BEFORE ALL", cls: "bg-violet-700 text-white" },
51240
51821
  before: { label: "BEFORE", cls: "bg-violet-600 text-white" },
@@ -51316,6 +51897,15 @@ function HookResultsPanel({ results }) {
51316
51897
  }) })
51317
51898
  ] });
51318
51899
  }
51900
+ const { electron: electron$h } = window;
51901
+ function extractPath(url) {
51902
+ try {
51903
+ return new URL(url).pathname || "/";
51904
+ } catch {
51905
+ const match = url.match(/(?:https?:\/\/[^/]+)?(\/[^?]*)/);
51906
+ return match?.[1] ?? "/";
51907
+ }
51908
+ }
51319
51909
  function SaveAsMockModal({ onClose }) {
51320
51910
  const mocks = useStore((s) => s.mocks);
51321
51911
  const addMock = useStore((s) => s.addMock);
@@ -51325,21 +51915,14 @@ function SaveAsMockModal({ onClose }) {
51325
51915
  const response = activeTab?.lastResponse ?? null;
51326
51916
  const activeRequestId = activeTab?.requestId ?? null;
51327
51917
  const activeRequest = activeRequestId ? Object.values(collections).find((c) => c.data.requests[activeRequestId])?.data.requests[activeRequestId] : null;
51328
- function extractPath(url) {
51329
- try {
51330
- return new URL(url).pathname || "/";
51331
- } catch {
51332
- const match = url.match(/(?:https?:\/\/[^/]+)?(\/[^?]*)/);
51333
- return match?.[1] ?? "/";
51334
- }
51335
- }
51336
51918
  const [targetMockId, setTargetMockId] = reactExports.useState(Object.keys(mocks)[0] ?? "__new__");
51337
51919
  const [newServerName, setNewServerName] = reactExports.useState("Mock Server");
51338
51920
  const [newServerPort, setNewServerPort] = reactExports.useState("3900");
51339
51921
  const [method, setMethod] = reactExports.useState(activeRequest?.method ?? "GET");
51340
51922
  const [path, setPath] = reactExports.useState(extractPath(activeRequest?.url ?? "/"));
51341
- const [statusCode, setStatusCode] = reactExports.useState(response.status);
51923
+ const [statusCode, setStatusCode] = reactExports.useState(response?.status ?? 200);
51342
51924
  const [body, setBody] = reactExports.useState(() => {
51925
+ if (!response) return "";
51343
51926
  try {
51344
51927
  return JSON.stringify(JSON.parse(response.body), null, 2);
51345
51928
  } catch {
@@ -51368,14 +51951,14 @@ function SaveAsMockModal({ onClose }) {
51368
51951
  const entry = state.mocks[serverId];
51369
51952
  const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
51370
51953
  updateMock(serverId, updated);
51371
- await electron$g.saveMock(entry.relPath, updated);
51954
+ await electron$h.saveMock(entry.relPath, updated);
51372
51955
  const ws2 = useStore.getState().workspace;
51373
- if (ws2) await electron$g.saveWorkspace(ws2);
51956
+ if (ws2) await electron$h.saveWorkspace(ws2);
51374
51957
  } else {
51375
51958
  const entry = useStore.getState().mocks[serverId];
51376
51959
  const updated = { ...entry.data, routes: [...entry.data.routes, route] };
51377
51960
  updateMock(serverId, updated);
51378
- await electron$g.saveMock(entry.relPath, updated);
51961
+ await electron$h.saveMock(entry.relPath, updated);
51379
51962
  }
51380
51963
  onClose();
51381
51964
  } finally {
@@ -51499,6 +52082,39 @@ function SaveAsMockModal({ onClose }) {
51499
52082
  }
51500
52083
  ) });
51501
52084
  }
52085
+ function prettyJson(raw) {
52086
+ try {
52087
+ return JSON.stringify(JSON.parse(raw), null, 2);
52088
+ } catch {
52089
+ return raw;
52090
+ }
52091
+ }
52092
+ function prettyXml(raw) {
52093
+ try {
52094
+ const indent = " ";
52095
+ let result = "";
52096
+ let depth = 0;
52097
+ const tokens = raw.match(/<[^>]+>|[^<]+/g) ?? [];
52098
+ for (const token of tokens) {
52099
+ const text = token.trim();
52100
+ if (!text) continue;
52101
+ if (text.startsWith("<?") || text.startsWith("<!")) {
52102
+ result += indent.repeat(depth) + text + "\n";
52103
+ } else if (token.startsWith("</")) {
52104
+ depth = Math.max(0, depth - 1);
52105
+ result += indent.repeat(depth) + text + "\n";
52106
+ } else if (token.startsWith("<") && !token.endsWith("/>") && !token.includes("</")) {
52107
+ result += indent.repeat(depth) + text + "\n";
52108
+ depth++;
52109
+ } else {
52110
+ result += indent.repeat(depth) + text + "\n";
52111
+ }
52112
+ }
52113
+ return result.trimEnd();
52114
+ } catch {
52115
+ return raw;
52116
+ }
52117
+ }
51502
52118
  function computeLineDiff(a, b) {
51503
52119
  const aLines = a.split("\n");
51504
52120
  const bLines = b.split("\n");
@@ -51527,20 +52143,20 @@ function computeLineDiff(a, b) {
51527
52143
  }
51528
52144
  return result;
51529
52145
  }
52146
+ const lineStyle = {
52147
+ equal: "text-surface-400",
52148
+ removed: "bg-red-900/30 text-red-300",
52149
+ added: "bg-emerald-900/30 text-emerald-300"
52150
+ };
52151
+ const linePrefix = {
52152
+ equal: " ",
52153
+ removed: "-",
52154
+ added: "+"
52155
+ };
51530
52156
  function DiffView({ pinned, current: current2 }) {
51531
52157
  const pinnedBody = prettyJson(pinned.body);
51532
52158
  const currentBody = prettyJson(current2.body);
51533
52159
  const diffLines = computeLineDiff(pinnedBody, currentBody);
51534
- const lineStyle = {
51535
- equal: "text-surface-400",
51536
- removed: "bg-red-900/30 text-red-300",
51537
- added: "bg-emerald-900/30 text-emerald-300"
51538
- };
51539
- const linePrefix = {
51540
- equal: " ",
51541
- removed: "-",
51542
- added: "+"
51543
- };
51544
52160
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full overflow-auto", children: [
51545
52161
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-6 px-4 py-2 border-b border-surface-800 text-xs shrink-0", children: [
51546
52162
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
@@ -51585,6 +52201,94 @@ function DiffView({ pinned, current: current2 }) {
51585
52201
  ] })
51586
52202
  ] });
51587
52203
  }
52204
+ function TestsPanel({ scriptResult }) {
52205
+ const sr = scriptResult;
52206
+ if (!sr || sr.testResults.length === 0 && !sr.preScriptError && !sr.postScriptError) {
52207
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-center h-full text-surface-400 text-xs", children: [
52208
+ "No tests ran. Add ",
52209
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "mx-1 bg-surface-800 px-1 rounded", children: "pm.test()" }),
52210
+ " calls to your post-response script."
52211
+ ] });
52212
+ }
52213
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 flex flex-col gap-2", children: [
52214
+ sr.preScriptError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 p-2 rounded bg-red-900/30 border border-red-700", children: [
52215
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-xs font-bold shrink-0", children: "PRE-SCRIPT ERROR" }),
52216
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-300 text-xs font-mono", children: sr.preScriptError })
52217
+ ] }),
52218
+ sr.postScriptError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 p-2 rounded bg-red-900/30 border border-red-700", children: [
52219
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-xs font-bold shrink-0", children: "POST-SCRIPT ERROR" }),
52220
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-300 text-xs font-mono", children: sr.postScriptError })
52221
+ ] }),
52222
+ sr.testResults.map((result, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
52223
+ "div",
52224
+ {
52225
+ className: `flex items-start gap-2 p-2 rounded border ${result.passed ? "bg-emerald-900/20 border-emerald-800" : "bg-red-900/20 border-red-800"}`,
52226
+ children: [
52227
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-bold shrink-0 ${result.passed ? "text-emerald-400" : "text-red-400"}`, children: result.passed ? "✓" : "✗" }),
52228
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-0.5", children: [
52229
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-white", children: result.name }),
52230
+ result.error && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-red-300 font-mono", children: result.error })
52231
+ ] })
52232
+ ]
52233
+ },
52234
+ i
52235
+ ))
52236
+ ] });
52237
+ }
52238
+ function RequestPanel({ sentRequest }) {
52239
+ if (!sentRequest) {
52240
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full text-surface-400 text-xs", children: "Send a request to see what was transmitted." });
52241
+ }
52242
+ const hasBody = sentRequest.body !== void 0 && sentRequest.body !== "";
52243
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto text-xs font-mono", children: [
52244
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 border-b border-surface-800 flex items-center gap-3", children: [
52245
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-blue-400 shrink-0", children: sentRequest.method }),
52246
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-white break-all", children: sentRequest.url })
52247
+ ] }),
52248
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2 border-b border-surface-800", children: [
52249
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 uppercase tracking-wider font-medium mb-1.5", children: "Headers" }),
52250
+ Object.keys(sentRequest.headers).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600", children: "No headers sent" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "w-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: Object.entries(sentRequest.headers).map(([k, v]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-800/50 last:border-0", children: [
52251
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 pr-4 text-surface-400 w-56 align-top", children: k }),
52252
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 text-white break-all", children: v })
52253
+ ] }, k)) }) })
52254
+ ] }),
52255
+ hasBody && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2", children: [
52256
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 uppercase tracking-wider font-medium mb-1.5", children: "Body" }),
52257
+ /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-white whitespace-pre-wrap break-all text-[11px]", children: sentRequest.body })
52258
+ ] })
52259
+ ] });
52260
+ }
52261
+ function ConsolePanel({ scriptResult }) {
52262
+ const sr = scriptResult;
52263
+ const hasErrors = !!(sr?.preScriptError || sr?.postScriptError);
52264
+ const hasOutput = !!(sr && sr.consoleOutput.length > 0);
52265
+ if (!sr || !hasErrors && !hasOutput) {
52266
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-center h-full text-surface-400 text-xs", children: [
52267
+ "No console output. Use ",
52268
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "mx-1 bg-surface-800 px-1 rounded", children: "console.log()" }),
52269
+ " in your scripts."
52270
+ ] });
52271
+ }
52272
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 flex flex-col gap-2", children: [
52273
+ sr.preScriptError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 p-2 rounded bg-red-900/30 border border-red-700", children: [
52274
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-xs font-bold shrink-0", children: "PRE-SCRIPT ERROR" }),
52275
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-300 text-xs font-mono", children: sr.preScriptError })
52276
+ ] }),
52277
+ sr.postScriptError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 p-2 rounded bg-red-900/30 border border-red-700", children: [
52278
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-xs font-bold shrink-0", children: "POST-SCRIPT ERROR" }),
52279
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-300 text-xs font-mono", children: sr.postScriptError })
52280
+ ] }),
52281
+ hasOutput && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-0", children: sr.consoleOutput.map((line, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(
52282
+ "div",
52283
+ {
52284
+ className: `text-xs font-mono py-0.5 border-b border-surface-800/50 last:border-0 ${line.startsWith("[error]") ? "text-red-300" : line.startsWith("[warn]") ? "text-amber-300" : line.startsWith("[set]") ? "text-cyan-400" : "text-surface-400"}`,
52285
+ children: line
52286
+ },
52287
+ i
52288
+ )) })
52289
+ ] });
52290
+ }
52291
+ const { electron: electron$g } = window;
51588
52292
  function ResponseViewer() {
51589
52293
  const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
51590
52294
  const activeTabId = useStore((s) => s.activeTabId);
@@ -51632,8 +52336,12 @@ function ResponseViewer() {
51632
52336
  const req = Object.values(state.collections).find((c) => c.data.requests[requestId])?.data.requests[requestId];
51633
52337
  if (!req) return;
51634
52338
  const existing = req.postRequestScript ?? "";
52339
+ let cleaned = snippet2;
52340
+ if (existing.includes("const json = sp.response.json()")) {
52341
+ cleaned = cleaned.replace(/^\s*const json = sp\.response\.json\(\);?\s*\n?/m, "").replace(/\n\s*const json = sp\.response\.json\(\);?\s*\n/g, "\n");
52342
+ }
51635
52343
  const sep = existing.trim() ? "\n\n" : "";
51636
- updateRequest(requestId, { postRequestScript: existing + sep + snippet2 });
52344
+ updateRequest(requestId, { postRequestScript: existing + sep + cleaned });
51637
52345
  if (activeTabId) {
51638
52346
  setTabRequestTab(activeTabId, "scripts");
51639
52347
  setTabScriptTab(activeTabId, "post");
@@ -51781,93 +52489,6 @@ function ResponseViewer() {
51781
52489
  ] }, k)) }) }) }) : tab === "tests" ? /* @__PURE__ */ jsxRuntimeExports.jsx(TestsPanel, { scriptResult }) : tab === "console" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ConsolePanel, { scriptResult }) : tab === "request" ? /* @__PURE__ */ jsxRuntimeExports.jsx(RequestPanel, { sentRequest }) : null })
51782
52490
  ] });
51783
52491
  }
51784
- function TestsPanel({ scriptResult }) {
51785
- const sr = scriptResult;
51786
- if (!sr || sr.testResults.length === 0 && !sr.preScriptError && !sr.postScriptError) {
51787
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-center h-full text-surface-400 text-xs", children: [
51788
- "No tests ran. Add ",
51789
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "mx-1 bg-surface-800 px-1 rounded", children: "pm.test()" }),
51790
- " calls to your post-response script."
51791
- ] });
51792
- }
51793
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 flex flex-col gap-2", children: [
51794
- sr.preScriptError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 p-2 rounded bg-red-900/30 border border-red-700", children: [
51795
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-xs font-bold shrink-0", children: "PRE-SCRIPT ERROR" }),
51796
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-300 text-xs font-mono", children: sr.preScriptError })
51797
- ] }),
51798
- sr.postScriptError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 p-2 rounded bg-red-900/30 border border-red-700", children: [
51799
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-xs font-bold shrink-0", children: "POST-SCRIPT ERROR" }),
51800
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-300 text-xs font-mono", children: sr.postScriptError })
51801
- ] }),
51802
- sr.testResults.map((result, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
51803
- "div",
51804
- {
51805
- className: `flex items-start gap-2 p-2 rounded border ${result.passed ? "bg-emerald-900/20 border-emerald-800" : "bg-red-900/20 border-red-800"}`,
51806
- children: [
51807
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-bold shrink-0 ${result.passed ? "text-emerald-400" : "text-red-400"}`, children: result.passed ? "✓" : "✗" }),
51808
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-0.5", children: [
51809
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-white", children: result.name }),
51810
- result.error && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-red-300 font-mono", children: result.error })
51811
- ] })
51812
- ]
51813
- },
51814
- i
51815
- ))
51816
- ] });
51817
- }
51818
- function RequestPanel({ sentRequest }) {
51819
- if (!sentRequest) {
51820
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full text-surface-400 text-xs", children: "Send a request to see what was transmitted." });
51821
- }
51822
- const hasBody = sentRequest.body !== void 0 && sentRequest.body !== "";
51823
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto text-xs font-mono", children: [
51824
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 border-b border-surface-800 flex items-center gap-3", children: [
51825
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-blue-400 shrink-0", children: sentRequest.method }),
51826
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-white break-all", children: sentRequest.url })
51827
- ] }),
51828
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2 border-b border-surface-800", children: [
51829
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 uppercase tracking-wider font-medium mb-1.5", children: "Headers" }),
51830
- Object.keys(sentRequest.headers).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600", children: "No headers sent" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "w-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: Object.entries(sentRequest.headers).map(([k, v]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-800/50 last:border-0", children: [
51831
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 pr-4 text-surface-400 w-56 align-top", children: k }),
51832
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 text-white break-all", children: v })
51833
- ] }, k)) }) })
51834
- ] }),
51835
- hasBody && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2", children: [
51836
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 uppercase tracking-wider font-medium mb-1.5", children: "Body" }),
51837
- /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-white whitespace-pre-wrap break-all text-[11px]", children: sentRequest.body })
51838
- ] })
51839
- ] });
51840
- }
51841
- function ConsolePanel({ scriptResult }) {
51842
- const sr = scriptResult;
51843
- const hasErrors = !!(sr?.preScriptError || sr?.postScriptError);
51844
- const hasOutput = !!(sr && sr.consoleOutput.length > 0);
51845
- if (!sr || !hasErrors && !hasOutput) {
51846
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-center h-full text-surface-400 text-xs", children: [
51847
- "No console output. Use ",
51848
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "mx-1 bg-surface-800 px-1 rounded", children: "console.log()" }),
51849
- " in your scripts."
51850
- ] });
51851
- }
51852
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 flex flex-col gap-2", children: [
51853
- sr.preScriptError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 p-2 rounded bg-red-900/30 border border-red-700", children: [
51854
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-xs font-bold shrink-0", children: "PRE-SCRIPT ERROR" }),
51855
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-300 text-xs font-mono", children: sr.preScriptError })
51856
- ] }),
51857
- sr.postScriptError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 p-2 rounded bg-red-900/30 border border-red-700", children: [
51858
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-xs font-bold shrink-0", children: "POST-SCRIPT ERROR" }),
51859
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-300 text-xs font-mono", children: sr.postScriptError })
51860
- ] }),
51861
- hasOutput && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-0", children: sr.consoleOutput.map((line, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(
51862
- "div",
51863
- {
51864
- className: `text-xs font-mono py-0.5 border-b border-surface-800/50 last:border-0 ${line.startsWith("[error]") ? "text-red-300" : line.startsWith("[warn]") ? "text-amber-300" : line.startsWith("[set]") ? "text-cyan-400" : "text-surface-400"}`,
51865
- children: line
51866
- },
51867
- i
51868
- )) })
51869
- ] });
51870
- }
51871
52492
  const { electron: electron$f } = window;
51872
52493
  const TARGETS = [
51873
52494
  { id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
@@ -53206,13 +53827,66 @@ const OPTIONS = [
53206
53827
  { id: "insomnia", label: "Insomnia", description: "Export v4 JSON" },
53207
53828
  { id: "bruno", label: "Bruno", description: "bruno.json collection file" }
53208
53829
  ];
53830
+ function listEndpoints(col) {
53831
+ const result = [];
53832
+ function walk(folder, path) {
53833
+ for (const id2 of folder.requestIds) {
53834
+ const req = col.requests[id2];
53835
+ if (req) result.push({ request: req, folderPath: path });
53836
+ }
53837
+ for (const sub of folder.folders) {
53838
+ walk(sub, [...path, sub.name]);
53839
+ }
53840
+ }
53841
+ walk(col.rootFolder, []);
53842
+ return result;
53843
+ }
53209
53844
  function ImportModal({ onImport, onClose }) {
53845
+ const collections = useStore((s) => s.collections);
53846
+ const environments = useStore((s) => s.environments);
53847
+ const setActiveCollection = useStore((s) => s.setActiveCollection);
53848
+ const mergeIntoCollection = useStore((s) => s.mergeIntoCollection);
53849
+ const markCollectionClean = useStore((s) => s.markCollectionClean);
53210
53850
  const [selected, setSelected] = reactExports.useState(null);
53211
53851
  const [url, setUrl] = reactExports.useState("");
53212
53852
  const [loading, setLoading] = reactExports.useState(false);
53213
53853
  const [error2, setError] = reactExports.useState(null);
53214
53854
  const urlInputRef = reactExports.useRef(null);
53215
- async function runImport(opt) {
53855
+ const [previewCol, setPreviewCol] = reactExports.useState(null);
53856
+ const [chosenIds, setChosenIds] = reactExports.useState(/* @__PURE__ */ new Set());
53857
+ const [target, setTarget] = reactExports.useState("__new__");
53858
+ const [newColName, setNewColName] = reactExports.useState("");
53859
+ const [baseUrl, setBaseUrl] = reactExports.useState("");
53860
+ const [varName, setVarName] = reactExports.useState("baseUrl");
53861
+ const [envTarget, setEnvTarget] = reactExports.useState("__new__");
53862
+ const [newEnvName, setNewEnvName] = reactExports.useState("");
53863
+ const endpoints = reactExports.useMemo(
53864
+ () => previewCol ? listEndpoints(previewCol) : [],
53865
+ [previewCol]
53866
+ );
53867
+ const grouped = reactExports.useMemo(() => {
53868
+ const map = /* @__PURE__ */ new Map();
53869
+ for (const ep of endpoints) {
53870
+ const key = ep.folderPath.join(" / ") || "(root)";
53871
+ const arr = map.get(key) ?? [];
53872
+ arr.push(ep);
53873
+ map.set(key, arr);
53874
+ }
53875
+ return Array.from(map.entries());
53876
+ }, [endpoints]);
53877
+ function enterPreview(col) {
53878
+ setPreviewCol(col);
53879
+ setNewColName(col.name);
53880
+ setChosenIds(new Set(Object.keys(col.requests)));
53881
+ const urls = Object.values(col.requests).map((r) => r.url).filter(Boolean);
53882
+ setBaseUrl(detectBaseUrl(urls));
53883
+ setVarName("baseUrl");
53884
+ const state = useStore.getState();
53885
+ const activeEnvId = state.activeEnvironmentId;
53886
+ setEnvTarget(activeEnvId && state.environments[activeEnvId] ? activeEnvId : "__new__");
53887
+ setNewEnvName(`${col.name} env`);
53888
+ }
53889
+ async function runFileImport(opt) {
53216
53890
  setLoading(true);
53217
53891
  setError(null);
53218
53892
  try {
@@ -53221,8 +53895,16 @@ function ImportModal({ onImport, onClose }) {
53221
53895
  if (opt.id === "openapi") col = await electron$8.importOpenApi();
53222
53896
  if (opt.id === "insomnia") col = await electron$8.importInsomnia();
53223
53897
  if (opt.id === "bruno") col = await electron$8.importBruno();
53224
- onImport(col);
53225
- onClose();
53898
+ if (!col) {
53899
+ setLoading(false);
53900
+ return;
53901
+ }
53902
+ if (opt.id === "openapi") {
53903
+ enterPreview(col);
53904
+ } else {
53905
+ onImport(col);
53906
+ onClose();
53907
+ }
53226
53908
  } catch (err) {
53227
53909
  setError(err instanceof Error ? err.message : String(err));
53228
53910
  } finally {
@@ -53236,14 +53918,338 @@ function ImportModal({ onImport, onClose }) {
53236
53918
  setError(null);
53237
53919
  try {
53238
53920
  const col = await electron$8.importOpenApiFromUrl(trimmed);
53239
- onImport(col);
53240
- onClose();
53921
+ if (col) enterPreview(col);
53922
+ } catch (err) {
53923
+ setError(err instanceof Error ? err.message : String(err));
53924
+ } finally {
53925
+ setLoading(false);
53926
+ }
53927
+ }
53928
+ function toggleId(id2) {
53929
+ setChosenIds((prev) => {
53930
+ const next = new Set(prev);
53931
+ if (next.has(id2)) next.delete(id2);
53932
+ else next.add(id2);
53933
+ return next;
53934
+ });
53935
+ }
53936
+ function toggleGroup(ids) {
53937
+ setChosenIds((prev) => {
53938
+ const next = new Set(prev);
53939
+ const allSelected = ids.every((id2) => next.has(id2));
53940
+ if (allSelected) ids.forEach((id2) => next.delete(id2));
53941
+ else ids.forEach((id2) => next.add(id2));
53942
+ return next;
53943
+ });
53944
+ }
53945
+ function selectAll2() {
53946
+ setChosenIds(new Set(endpoints.map((e) => e.request.id)));
53947
+ }
53948
+ function selectNone() {
53949
+ setChosenIds(/* @__PURE__ */ new Set());
53950
+ }
53951
+ async function confirmImport() {
53952
+ if (!previewCol) return;
53953
+ const picked = endpoints.filter((e) => chosenIds.has(e.request.id));
53954
+ if (!picked.length) {
53955
+ setError("Pick at least one endpoint");
53956
+ return;
53957
+ }
53958
+ const trimmedVarName = varName.trim();
53959
+ const trimmedBaseUrl = baseUrl.trim();
53960
+ const useVariable = Boolean(trimmedVarName && trimmedBaseUrl);
53961
+ if (trimmedBaseUrl && !trimmedVarName) {
53962
+ setError("Variable name is required when extracting base URL");
53963
+ return;
53964
+ }
53965
+ setLoading(true);
53966
+ setError(null);
53967
+ try {
53968
+ const rewriteUrl = (u) => {
53969
+ if (!useVariable) return u;
53970
+ return u.startsWith(trimmedBaseUrl) ? `{{${trimmedVarName}}}${u.slice(trimmedBaseUrl.length)}` : u;
53971
+ };
53972
+ for (const ep of picked) {
53973
+ ep.request.url = rewriteUrl(ep.request.url);
53974
+ }
53975
+ if (useVariable) {
53976
+ await persistBaseUrlVariable(trimmedVarName, trimmedBaseUrl);
53977
+ }
53978
+ if (target === "__new__") {
53979
+ const filtered = {
53980
+ ...previewCol,
53981
+ name: newColName.trim() || previewCol.name,
53982
+ requests: {},
53983
+ rootFolder: pruneFolder(previewCol.rootFolder, previewCol, chosenIds, {})
53984
+ };
53985
+ filtered.requests = collectRequestsByFolder(filtered.rootFolder, previewCol);
53986
+ onImport(filtered);
53987
+ onClose();
53988
+ } else {
53989
+ const targetCol = collections[target]?.data;
53990
+ if (!targetCol) throw new Error("Target collection not found");
53991
+ const prunedRoot = pruneFolder(previewCol.rootFolder, previewCol, chosenIds, {});
53992
+ const prunedRequests = collectRequestsByFolder(prunedRoot, previewCol);
53993
+ mergeIntoCollection(target, prunedRoot, prunedRequests);
53994
+ const entry = useStore.getState().collections[target];
53995
+ if (entry) {
53996
+ await electron$8.saveCollection(entry.relPath, entry.data);
53997
+ markCollectionClean(target);
53998
+ }
53999
+ setActiveCollection(target);
54000
+ onImport(null);
54001
+ onClose();
54002
+ }
53241
54003
  } catch (err) {
53242
54004
  setError(err instanceof Error ? err.message : String(err));
53243
54005
  } finally {
53244
54006
  setLoading(false);
53245
54007
  }
53246
54008
  }
54009
+ async function persistBaseUrlVariable(name2, value) {
54010
+ const state = useStore.getState();
54011
+ if (envTarget === "__new__") {
54012
+ const desiredName = newEnvName.trim() || "New Environment";
54013
+ const existingNames = Object.values(state.environments).map((e) => e.data.name);
54014
+ const finalName = uniqueEnvName(desiredName, existingNames);
54015
+ const envId = v4();
54016
+ const env = {
54017
+ version: "1.0",
54018
+ id: envId,
54019
+ name: finalName,
54020
+ variables: [{ key: name2, value, enabled: true }]
54021
+ };
54022
+ const relPath = envRelPath(finalName, envId);
54023
+ await electron$8.saveEnvironment(relPath, env);
54024
+ useStore.setState((s) => {
54025
+ s.environments[envId] = { relPath, data: env };
54026
+ if (!s.activeEnvironmentId) s.activeEnvironmentId = envId;
54027
+ if (s.workspace && !s.workspace.environments.includes(relPath)) {
54028
+ s.workspace.environments = [...s.workspace.environments, relPath];
54029
+ }
54030
+ return s;
54031
+ });
54032
+ const ws2 = useStore.getState().workspace;
54033
+ if (ws2) await electron$8.saveWorkspace(ws2);
54034
+ } else {
54035
+ const entry = state.environments[envTarget];
54036
+ if (!entry) throw new Error("Target environment not found");
54037
+ const updated = { ...entry.data };
54038
+ const idx = updated.variables.findIndex((v) => v.key === name2);
54039
+ if (idx >= 0) {
54040
+ if (!updated.variables[idx].value) {
54041
+ updated.variables = updated.variables.map(
54042
+ (v, i) => i === idx ? { ...v, value, enabled: true } : v
54043
+ );
54044
+ }
54045
+ } else {
54046
+ updated.variables = [...updated.variables, { key: name2, value, enabled: true }];
54047
+ }
54048
+ await electron$8.saveEnvironment(entry.relPath, updated);
54049
+ useStore.getState().updateEnvironment(envTarget, updated);
54050
+ }
54051
+ }
54052
+ if (previewCol) {
54053
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60", onClick: onClose, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
54054
+ "div",
54055
+ {
54056
+ className: "bg-surface-900 border border-surface-700 rounded-xl shadow-2xl w-[640px] max-h-[80vh] p-5 flex flex-col gap-4",
54057
+ onClick: (e) => e.stopPropagation(),
54058
+ children: [
54059
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
54060
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-sm font-semibold text-surface-100", children: [
54061
+ "Import OpenAPI — ",
54062
+ previewCol.name
54063
+ ] }),
54064
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54065
+ "button",
54066
+ {
54067
+ onClick: onClose,
54068
+ className: "text-surface-500 hover:text-surface-300 text-lg leading-none",
54069
+ children: "×"
54070
+ }
54071
+ )
54072
+ ] }),
54073
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 border border-surface-800 rounded-lg p-3", children: [
54074
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium", children: "Destination" }),
54075
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54076
+ "select",
54077
+ {
54078
+ value: target,
54079
+ onChange: (e) => setTarget(e.target.value),
54080
+ className: "text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1.5 focus:outline-none focus:border-blue-500",
54081
+ children: [
54082
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "__new__", children: "Create new collection…" }),
54083
+ Object.values(collections).map((c) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: c.data.id, children: c.data.name }, c.data.id))
54084
+ ]
54085
+ }
54086
+ ),
54087
+ target === "__new__" && /* @__PURE__ */ jsxRuntimeExports.jsx(
54088
+ "input",
54089
+ {
54090
+ value: newColName,
54091
+ onChange: (e) => setNewColName(e.target.value),
54092
+ placeholder: "Collection name",
54093
+ className: "text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1.5 focus:outline-none focus:border-blue-500"
54094
+ }
54095
+ )
54096
+ ] }),
54097
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 border border-surface-800 rounded-lg p-3", children: [
54098
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium", children: "Base URL → variable" }),
54099
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
54100
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54101
+ "input",
54102
+ {
54103
+ value: baseUrl,
54104
+ onChange: (e) => setBaseUrl(e.target.value),
54105
+ placeholder: "https://api.example.com/v1",
54106
+ className: "flex-1 text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1.5 focus:outline-none focus:border-blue-500 placeholder-surface-600 font-mono"
54107
+ }
54108
+ ),
54109
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-surface-500 self-center", children: "→" }),
54110
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54111
+ "input",
54112
+ {
54113
+ value: varName,
54114
+ onChange: (e) => setVarName(e.target.value),
54115
+ placeholder: "baseUrl",
54116
+ className: "w-32 text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1.5 focus:outline-none focus:border-blue-500 placeholder-surface-600 font-mono"
54117
+ }
54118
+ )
54119
+ ] }),
54120
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500", children: "Leave the URL field empty to keep absolute URLs in each request." }),
54121
+ baseUrl.trim() && varName.trim() && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
54122
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54123
+ "select",
54124
+ {
54125
+ value: envTarget,
54126
+ onChange: (e) => setEnvTarget(e.target.value),
54127
+ className: "text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1.5 focus:outline-none focus:border-blue-500",
54128
+ children: [
54129
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "__new__", children: "Create new environment…" }),
54130
+ Object.values(environments).map((e) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: e.data.id, children: e.data.name }, e.data.id))
54131
+ ]
54132
+ }
54133
+ ),
54134
+ envTarget === "__new__" && /* @__PURE__ */ jsxRuntimeExports.jsx(
54135
+ "input",
54136
+ {
54137
+ value: newEnvName,
54138
+ onChange: (e) => setNewEnvName(e.target.value),
54139
+ placeholder: "Environment name",
54140
+ className: "text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1.5 focus:outline-none focus:border-blue-500"
54141
+ }
54142
+ )
54143
+ ] })
54144
+ ] }),
54145
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
54146
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium", children: [
54147
+ "Endpoints (",
54148
+ chosenIds.size,
54149
+ " / ",
54150
+ endpoints.length,
54151
+ ")"
54152
+ ] }),
54153
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
54154
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: selectAll2, className: "text-[10px] text-blue-400 hover:text-blue-300", children: "Select all" }),
54155
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: selectNone, className: "text-[10px] text-blue-400 hover:text-blue-300", children: "Select none" })
54156
+ ] })
54157
+ ] }),
54158
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto border border-surface-800 rounded-lg", children: [
54159
+ grouped.map(([groupName, items2]) => {
54160
+ const ids = items2.map((i) => i.request.id);
54161
+ const allOn = ids.every((id2) => chosenIds.has(id2));
54162
+ const someOn = !allOn && ids.some((id2) => chosenIds.has(id2));
54163
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-b border-surface-800 last:border-b-0", children: [
54164
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54165
+ "button",
54166
+ {
54167
+ onClick: () => toggleGroup(ids),
54168
+ className: "w-full flex items-center gap-2 px-3 py-2 bg-surface-850 hover:bg-surface-800 text-left",
54169
+ children: [
54170
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54171
+ "input",
54172
+ {
54173
+ type: "checkbox",
54174
+ checked: allOn,
54175
+ ref: (el) => {
54176
+ if (el) el.indeterminate = someOn;
54177
+ },
54178
+ onChange: () => toggleGroup(ids),
54179
+ onClick: (e) => e.stopPropagation(),
54180
+ className: "accent-blue-500"
54181
+ }
54182
+ ),
54183
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-semibold text-surface-200", children: groupName }),
54184
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-surface-500", children: [
54185
+ "(",
54186
+ items2.length,
54187
+ ")"
54188
+ ] })
54189
+ ]
54190
+ }
54191
+ ),
54192
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: items2.map((ep) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54193
+ "label",
54194
+ {
54195
+ className: "flex items-center gap-2 px-3 py-1.5 pl-8 hover:bg-surface-850 cursor-pointer",
54196
+ children: [
54197
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54198
+ "input",
54199
+ {
54200
+ type: "checkbox",
54201
+ checked: chosenIds.has(ep.request.id),
54202
+ onChange: () => toggleId(ep.request.id),
54203
+ className: "accent-blue-500"
54204
+ }
54205
+ ),
54206
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-mono font-bold ${methodColor$1(ep.request.method)}`, children: ep.request.method }),
54207
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-surface-200 truncate", children: ep.request.name })
54208
+ ]
54209
+ },
54210
+ ep.request.id
54211
+ )) })
54212
+ ] }, groupName);
54213
+ }),
54214
+ !grouped.length && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-surface-500 p-3", children: "No endpoints found in spec." })
54215
+ ] }),
54216
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400", children: error2 }),
54217
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between pt-1", children: [
54218
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54219
+ "button",
54220
+ {
54221
+ onClick: () => {
54222
+ setPreviewCol(null);
54223
+ setError(null);
54224
+ },
54225
+ className: "px-3 py-1.5 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
54226
+ children: "Back"
54227
+ }
54228
+ ),
54229
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
54230
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54231
+ "button",
54232
+ {
54233
+ onClick: onClose,
54234
+ className: "px-3 py-1.5 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
54235
+ children: "Cancel"
54236
+ }
54237
+ ),
54238
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54239
+ "button",
54240
+ {
54241
+ disabled: loading || chosenIds.size === 0 || target === "__new__" && !newColName.trim(),
54242
+ onClick: confirmImport,
54243
+ className: "px-3 py-1.5 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors",
54244
+ children: loading ? "Importing…" : "Import"
54245
+ }
54246
+ )
54247
+ ] })
54248
+ ] })
54249
+ ]
54250
+ }
54251
+ ) });
54252
+ }
53247
54253
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60", onClick: onClose, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
53248
54254
  "div",
53249
54255
  {
@@ -53319,7 +54325,7 @@ function ImportModal({ onImport, onClose }) {
53319
54325
  disabled: !selected || loading,
53320
54326
  onClick: () => {
53321
54327
  const opt = OPTIONS.find((o) => o.id === selected);
53322
- if (opt) runImport(opt);
54328
+ if (opt) runFileImport(opt);
53323
54329
  },
53324
54330
  className: "px-3 py-1.5 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors",
53325
54331
  children: loading ? "Importing…" : "Choose File"
@@ -53330,6 +54336,63 @@ function ImportModal({ onImport, onClose }) {
53330
54336
  }
53331
54337
  ) });
53332
54338
  }
54339
+ function detectBaseUrl(urls) {
54340
+ if (!urls.length) return "";
54341
+ let prefix2 = urls[0];
54342
+ for (const u of urls.slice(1)) {
54343
+ let i = 0;
54344
+ while (i < prefix2.length && i < u.length && prefix2[i] === u[i]) i++;
54345
+ prefix2 = prefix2.slice(0, i);
54346
+ if (!prefix2) break;
54347
+ }
54348
+ if (!prefix2.includes("://")) return "";
54349
+ const protoEnd = prefix2.indexOf("://") + 3;
54350
+ const lastSlash = prefix2.lastIndexOf("/");
54351
+ if (lastSlash > protoEnd) prefix2 = prefix2.slice(0, lastSlash);
54352
+ return prefix2.replace(/\/$/, "");
54353
+ }
54354
+ function uniqueEnvName(base2, existing) {
54355
+ if (!existing.includes(base2)) return base2;
54356
+ let i = 2;
54357
+ while (existing.includes(`${base2} (${i})`)) i++;
54358
+ return `${base2} (${i})`;
54359
+ }
54360
+ function methodColor$1(method) {
54361
+ switch (method) {
54362
+ case "GET":
54363
+ return "text-emerald-400";
54364
+ case "POST":
54365
+ return "text-amber-400";
54366
+ case "PUT":
54367
+ return "text-sky-400";
54368
+ case "PATCH":
54369
+ return "text-violet-400";
54370
+ case "DELETE":
54371
+ return "text-red-400";
54372
+ default:
54373
+ return "text-surface-300";
54374
+ }
54375
+ }
54376
+ function pruneFolder(src, _col, keep, _out) {
54377
+ const subFolders = src.folders.map((f) => pruneFolder(f, _col, keep)).filter((f) => f.requestIds.length > 0 || f.folders.length > 0);
54378
+ const requestIds = src.requestIds.filter((id2) => keep.has(id2));
54379
+ return {
54380
+ ...src,
54381
+ folders: subFolders,
54382
+ requestIds
54383
+ };
54384
+ }
54385
+ function collectRequestsByFolder(folder, src) {
54386
+ const out = {};
54387
+ function walk(f) {
54388
+ for (const id2 of f.requestIds) {
54389
+ if (src.requests[id2]) out[id2] = src.requests[id2];
54390
+ }
54391
+ for (const sub of f.folders) walk(sub);
54392
+ }
54393
+ walk(folder);
54394
+ return out;
54395
+ }
53333
54396
  const { electron: electron$7 } = window;
53334
54397
  const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
53335
54398
  function PreferencesPopover() {
@@ -53666,8 +54729,13 @@ function buildHtmlReport(results, summary, meta2 = {}) {
53666
54729
  after: "AFTER",
53667
54730
  afterAll: "AFTER ALL"
53668
54731
  };
54732
+ let lastScopeKey = null;
53669
54733
  const cards = results.map((r, idx) => {
53670
- const statusCls = r.status === "passed" ? "badge-pass" : r.status === "failed" ? "badge-fail" : "badge-err";
54734
+ const scopeKey = (r.scopePath ?? []).join(" / ");
54735
+ const groupHeading = scopeKey && scopeKey !== lastScopeKey ? ` <div class="scope-heading">${esc2(scopeKey)}</div>
54736
+ ` : "";
54737
+ lastScopeKey = scopeKey;
54738
+ const statusCls = r.status === "passed" ? "badge-pass" : r.status === "failed" ? "badge-fail" : r.status === "skipped" ? "badge-skip" : "badge-err";
53671
54739
  const httpCls = r.httpStatus && r.httpStatus < 300 ? "http-ok" : r.httpStatus && r.httpStatus < 400 ? "http-redir" : "http-err";
53672
54740
  const dur = r.durationMs != null ? `${r.durationMs} ms` : "—";
53673
54741
  const label = r.iterationLabel ? ` <span class="muted">#${esc2(r.iterationLabel)}</span>` : "";
@@ -53705,7 +54773,7 @@ function buildHtmlReport(results, summary, meta2 = {}) {
53705
54773
  ${resp.body ? `<div class="section-label">Body</div><pre class="code-block">${prettyJson2(resp.body)}</pre>` : ""}
53706
54774
  </div>` : "";
53707
54775
  const hookCls = hookLabel ? r.hookType?.startsWith("before") ? "card-hook-before" : "card-hook-after" : "";
53708
- return `
54776
+ return `${groupHeading}
53709
54777
  <div class="card ${hookCls}" id="r${idx}">
53710
54778
  <div class="card-header" onclick="toggle(${idx})">
53711
54779
  <span class="chevron" id="ch${idx}">▶</span>
@@ -53746,8 +54814,11 @@ function buildHtmlReport(results, summary, meta2 = {}) {
53746
54814
  .stat-pass .stat-val { color: #3fb950; }
53747
54815
  .stat-fail .stat-val { color: #f85149; }
53748
54816
  .stat-err .stat-val { color: #d29922; }
54817
+ .stat-skip .stat-val { color: #8b949e; }
53749
54818
  /* Cards */
53750
54819
  .card { border: 1px solid #21262d; border-radius: 8px; margin-bottom: 8px; overflow: hidden; }
54820
+ .scope-heading { margin: 18px 0 6px; padding: 4px 2px; font-size: 11px; font-weight: 600; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid #21262d; }
54821
+ .scope-heading::before { content: "▸ "; color: #484f58; }
53751
54822
  .card-header { display: flex; align-items: baseline; gap: 8px; padding: 10px 14px; cursor: pointer; user-select: none; }
53752
54823
  .card-header:hover { background: #161b22; }
53753
54824
  .chevron { font-size: 10px; color: #8b949e; min-width: 10px; transition: transform .15s; }
@@ -53759,6 +54830,7 @@ function buildHtmlReport(results, summary, meta2 = {}) {
53759
54830
  .badge-pass { background: #0d3a1e; color: #3fb950; }
53760
54831
  .badge-fail { background: #3d1014; color: #f85149; }
53761
54832
  .badge-err { background: #3d2a00; color: #d29922; }
54833
+ .badge-skip { background: #21262d; color: #8b949e; }
53762
54834
  .method { font-family: monospace; font-size: 11px; font-weight: 700; color: #79c0ff; white-space: nowrap; }
53763
54835
  .method-badge { display: inline-block; font-family: monospace; font-size: 11px; font-weight: 700; color: #79c0ff; min-width: 52px; }
53764
54836
  .http-badge { font-family: monospace; font-size: 11px; font-weight: 600; white-space: nowrap; }
@@ -53812,6 +54884,7 @@ function buildHtmlReport(results, summary, meta2 = {}) {
53812
54884
  <div class="stat stat-pass"><div class="stat-val">${summary.passed}</div><div class="stat-lbl">Passed</div></div>
53813
54885
  <div class="stat stat-fail"><div class="stat-val">${summary.failed}</div><div class="stat-lbl">Failed</div></div>
53814
54886
  <div class="stat stat-err"><div class="stat-val">${summary.errors}</div><div class="stat-lbl">Errors</div></div>
54887
+ <div class="stat stat-skip"><div class="stat-val">${summary.skipped ?? 0}</div><div class="stat-lbl">No tests</div></div>
53815
54888
  <div class="stat"><div class="stat-val">${passRate}%</div><div class="stat-lbl">Pass rate</div></div>
53816
54889
  <div class="stat"><div class="stat-val">${summary.durationMs} ms</div><div class="stat-lbl">Duration</div></div>
53817
54890
  </div>
@@ -53855,6 +54928,8 @@ function buildJUnitReport(results, summary, meta2 = {}) {
53855
54928
  failures.push(` <error message="${esc2(r.preScriptError)}" type="PreScriptError" />`);
53856
54929
  } else if (r.postScriptError) {
53857
54930
  failures.push(` <error message="${esc2(r.postScriptError)}" type="PostScriptError" />`);
54931
+ } else if (r.status === "skipped") {
54932
+ failures.push(' <skipped message="No assertions defined for this request" />');
53858
54933
  } else if (r.testResults?.length) {
53859
54934
  for (const t2 of r.testResults) {
53860
54935
  if (!t2.passed) {
@@ -53869,10 +54944,11 @@ ${failures.join("\n")}
53869
54944
  ` : "";
53870
54945
  return ` <testcase name="${name2}" classname="${classname}" time="${timeSec}">${inner}</testcase>`;
53871
54946
  });
54947
+ const skipped = summary.skipped ?? 0;
53872
54948
  const lines = [
53873
54949
  '<?xml version="1.0" encoding="UTF-8"?>',
53874
- `<testsuites name="${suiteName}" tests="${summary.total}" failures="${summary.failed}" errors="${summary.errors}" time="${totalSec}">`,
53875
- ` <testsuite name="${suiteName}" tests="${summary.total}" failures="${summary.failed}" errors="${summary.errors}" time="${totalSec}" timestamp="${ts}">`,
54950
+ `<testsuites name="${suiteName}" tests="${summary.total}" failures="${summary.failed}" errors="${summary.errors}" skipped="${skipped}" time="${totalSec}">`,
54951
+ ` <testsuite name="${suiteName}" tests="${summary.total}" failures="${summary.failed}" errors="${summary.errors}" skipped="${skipped}" time="${totalSec}" timestamp="${ts}">`,
53876
54952
  ...cases,
53877
54953
  " </testsuite>",
53878
54954
  "</testsuites>"
@@ -53972,7 +55048,7 @@ function allTagsIn(collectionId, folderId) {
53972
55048
  const state = useStore.getState();
53973
55049
  const col = state.collections[collectionId]?.data;
53974
55050
  if (!col) return [];
53975
- const rootFolder = folderId ? findFolder(col.rootFolder, folderId) ?? col.rootFolder : col.rootFolder;
55051
+ const rootFolder = folderId ? findFolder$1(col.rootFolder, folderId) ?? col.rootFolder : col.rootFolder;
53976
55052
  return collectAllTags(rootFolder, col.requests);
53977
55053
  }
53978
55054
  function StatusDot({ status }) {
@@ -53981,7 +55057,8 @@ function StatusDot({ status }) {
53981
55057
  running: "bg-blue-400 animate-pulse",
53982
55058
  passed: "bg-emerald-500",
53983
55059
  failed: "bg-red-500",
53984
- error: "bg-orange-500"
55060
+ error: "bg-orange-500",
55061
+ skipped: "bg-surface-500"
53985
55062
  };
53986
55063
  return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `inline-block w-2 h-2 rounded-full shrink-0 ${colors[status] ?? "bg-surface-700"}` });
53987
55064
  }
@@ -54007,7 +55084,7 @@ function RunnerModal() {
54007
55084
  const { collectionId, folderId } = runnerModal;
54008
55085
  const colEntry = collectionId ? collections[collectionId] : null;
54009
55086
  const colName = colEntry?.data.name ?? "Collection";
54010
- const folderName = folderId && colEntry ? findFolder(colEntry.data.rootFolder, folderId)?.name ?? "Folder" : null;
55087
+ const folderName = folderId && colEntry ? findFolder$1(colEntry.data.rootFolder, folderId)?.name ?? "Folder" : null;
54011
55088
  const dataSet = colEntry?.data.dataSet ?? { columns: [], rows: [] };
54012
55089
  const iterCount = dataSet.rows.length;
54013
55090
  const availableTags = collectionId ? allTagsIn(collectionId, folderId) : [];
@@ -54053,7 +55130,8 @@ function RunnerModal() {
54053
55130
  iterationLabel: item.iterationLabel,
54054
55131
  isHook: item.isHook,
54055
55132
  hookType: item.hookType,
54056
- scopeId: item.scopeId
55133
+ scopeId: item.scopeId,
55134
+ scopePath: item.scopePath
54057
55135
  })));
54058
55136
  setSummary(null);
54059
55137
  setRunnerRunning(true);
@@ -54212,45 +55290,55 @@ function RunnerModal() {
54212
55290
  ] })
54213
55291
  ] })
54214
55292
  ] }),
54215
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: runnerResults.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { message: "Configure the run above and press Run." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "w-full text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: runnerResults.map((r, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54216
- "tr",
54217
- {
54218
- className: `border-b border-surface-800/50 hover:bg-surface-800/30 ${r.isHook ? "opacity-80" : ""}`,
54219
- children: [
54220
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2 w-6", children: /* @__PURE__ */ jsxRuntimeExports.jsx(StatusDot, { status: r.status }) }),
54221
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 pr-2 w-20", children: r.isHook && r.hookType ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[9px] font-bold px-1.5 py-0.5 rounded uppercase tracking-wide whitespace-nowrap ${HOOK_BADGE[r.hookType]?.cls ?? ""}`, children: HOOK_BADGE[r.hookType]?.label }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-bold ${getMethodColor(r.method)}`, children: r.method }) }),
54222
- /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-2 pr-2", children: [
54223
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `truncate max-w-[260px] ${r.isHook ? "text-surface-400 italic" : "text-[var(--text-primary)]"}`, children: [
54224
- r.name,
54225
- r.iterationLabel && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-1.5 text-[10px] text-surface-500 font-mono", children: [
54226
- "#",
54227
- r.iterationLabel
55293
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: runnerResults.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { message: "Configure the run above and press Run." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "w-full text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: runnerResults.map((r, idx) => {
55294
+ const scopeKey = (r.scopePath ?? []).join(" / ");
55295
+ const prevScope = idx > 0 ? (runnerResults[idx - 1].scopePath ?? []).join(" / ") : null;
55296
+ const showHeading = scopeKey !== "" && scopeKey !== prevScope;
55297
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(reactExports.Fragment, { children: [
55298
+ showHeading && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { className: "bg-surface-800/40", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { colSpan: 6, className: "px-4 py-1.5 text-[10px] uppercase tracking-wider font-semibold text-surface-400", children: [
55299
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600", children: "▸ " }),
55300
+ scopeKey
55301
+ ] }) }),
55302
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
55303
+ "tr",
55304
+ {
55305
+ className: `border-b border-surface-800/50 hover:bg-surface-800/30 ${r.isHook ? "opacity-80" : ""}`,
55306
+ children: [
55307
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2 w-6", children: /* @__PURE__ */ jsxRuntimeExports.jsx(StatusDot, { status: r.status }) }),
55308
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 pr-2 w-20", children: r.isHook && r.hookType ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[9px] font-bold px-1.5 py-0.5 rounded uppercase tracking-wide whitespace-nowrap ${HOOK_BADGE[r.hookType]?.cls ?? ""}`, children: HOOK_BADGE[r.hookType]?.label }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-bold ${getMethodColor(r.method)}`, children: r.method }) }),
55309
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-2 pr-2", children: [
55310
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `truncate max-w-[260px] ${r.isHook ? "text-surface-400 italic" : "text-[var(--text-primary)]"}`, children: [
55311
+ r.name,
55312
+ r.iterationLabel && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-1.5 text-[10px] text-surface-500 font-mono", children: [
55313
+ "#",
55314
+ r.iterationLabel
55315
+ ] })
55316
+ ] }),
55317
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500 font-mono truncate max-w-[260px]", children: r.resolvedUrl })
55318
+ ] }),
55319
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 pr-2 text-right text-surface-400 w-20", children: r.httpStatus ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono ${r.httpStatus < 400 ? "text-emerald-400" : "text-red-400"}`, children: r.httpStatus }) : r.error ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-[10px]", children: "error" }) : null }),
55320
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 pr-4 text-right w-16 text-surface-400", children: r.durationMs !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
55321
+ r.durationMs,
55322
+ "ms"
55323
+ ] }) }),
55324
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-2 pr-4 w-24", children: [
55325
+ r.testResults && r.testResults.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: `text-[10px] ${r.testResults.every((t2) => t2.passed) ? "text-emerald-400" : "text-red-400"}`, children: [
55326
+ r.testResults.filter((t2) => t2.passed).length,
55327
+ "/",
55328
+ r.testResults.length,
55329
+ " tests"
55330
+ ] }),
55331
+ r.error && !r.error.startsWith("Skipped") && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-orange-400", title: r.error, children: [
55332
+ "⚠ ",
55333
+ r.error.slice(0, 30)
55334
+ ] }),
55335
+ r.error?.startsWith("Skipped") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-500 italic", children: "skipped" })
54228
55336
  ] })
54229
- ] }),
54230
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500 font-mono truncate max-w-[260px]", children: r.resolvedUrl })
54231
- ] }),
54232
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 pr-2 text-right text-surface-400 w-20", children: r.httpStatus ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono ${r.httpStatus < 400 ? "text-emerald-400" : "text-red-400"}`, children: r.httpStatus }) : r.error ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-[10px]", children: "error" }) : null }),
54233
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 pr-4 text-right w-16 text-surface-400", children: r.durationMs !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
54234
- r.durationMs,
54235
- "ms"
54236
- ] }) }),
54237
- /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-2 pr-4 w-24", children: [
54238
- r.testResults && r.testResults.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: `text-[10px] ${r.testResults.every((t2) => t2.passed) ? "text-emerald-400" : "text-red-400"}`, children: [
54239
- r.testResults.filter((t2) => t2.passed).length,
54240
- "/",
54241
- r.testResults.length,
54242
- " tests"
54243
- ] }),
54244
- r.error && !r.error.startsWith("Skipped") && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-orange-400", title: r.error, children: [
54245
- "⚠ ",
54246
- r.error.slice(0, 30)
54247
- ] }),
54248
- r.error?.startsWith("Skipped") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-500 italic", children: "skipped" })
54249
- ] })
54250
- ]
54251
- },
54252
- idx
54253
- )) }) }) }),
55337
+ ]
55338
+ }
55339
+ )
55340
+ ] }, idx);
55341
+ }) }) }) }),
54254
55342
  summary && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 px-4 py-2 border-t border-surface-800 bg-surface-800/30 flex-shrink-0 text-xs", children: [
54255
55343
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400 font-medium", children: [
54256
55344
  summary.passed,
@@ -54264,6 +55352,10 @@ function RunnerModal() {
54264
55352
  summary.errors,
54265
55353
  " errors"
54266
55354
  ] }),
55355
+ summary.skipped > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-400 font-medium", title: "Requests with no assertions to verify", children: [
55356
+ summary.skipped,
55357
+ " no tests"
55358
+ ] }),
54267
55359
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-400", children: [
54268
55360
  summary.total,
54269
55361
  " total · ",
@@ -56855,6 +57947,9 @@ function App() {
56855
57947
  const activeTabId = useStore((s) => s.activeTabId);
56856
57948
  const setActiveTabId = useStore((s) => s.setActiveTabId);
56857
57949
  const closeTab = useStore((s) => s.closeTab);
57950
+ const closeAllTabs = useStore((s) => s.closeAllTabs);
57951
+ const closeOtherTabs = useStore((s) => s.closeOtherTabs);
57952
+ const [tabContextMenu, setTabContextMenu] = reactExports.useState(null);
56858
57953
  const showGeneratorPanel = useStore((s) => s.showGeneratorPanel);
56859
57954
  const sidebarTab = useStore((s) => s.sidebarTab);
56860
57955
  const setSidebarTab = useStore((s) => s.setSidebarTab);
@@ -56873,7 +57968,7 @@ function App() {
56873
57968
  const setWsStatus = useStore((s) => s.setWsStatus);
56874
57969
  const addWsMessage = useStore((s) => s.addWsMessage);
56875
57970
  const [sidebarOpen, setSidebarOpen] = reactExports.useState(true);
56876
- const [responseOpen, setResponseOpen] = reactExports.useState(true);
57971
+ const [responseOpen, setResponseOpen] = reactExports.useState(false);
56877
57972
  const [docsModalOpen, setDocsModalOpen] = reactExports.useState(false);
56878
57973
  const [sidebarWidth, setSidebarWidth] = reactExports.useState(256);
56879
57974
  const [requestPaneWidth, setRequestPaneWidth] = reactExports.useState(null);
@@ -56940,6 +58035,9 @@ function App() {
56940
58035
  }, [theme2]);
56941
58036
  const activeTab = tabs.find((t2) => t2.id === activeTabId) ?? null;
56942
58037
  const activeRequest = activeTab?.requestId ? Object.values(collections).find((c) => c.data.requests[activeTab.requestId])?.data.requests[activeTab.requestId] : null;
58038
+ reactExports.useEffect(() => {
58039
+ setResponseOpen(Boolean(activeTab?.lastResponse));
58040
+ }, [activeTabId, activeTab?.lastResponse]);
56943
58041
  function selectPanel(tab) {
56944
58042
  if (sidebarTab === tab && sidebarOpen) {
56945
58043
  setSidebarOpen(false);
@@ -56957,7 +58055,7 @@ function App() {
56957
58055
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
56958
58056
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
56959
58057
  "v",
56960
- "0.1.9"
58058
+ "0.2.0"
56961
58059
  ] }),
56962
58060
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
56963
58061
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -57071,34 +58169,105 @@ function App() {
57071
58169
  }
57072
58170
  ),
57073
58171
  /* @__PURE__ */ jsxRuntimeExports.jsxs("main", { className: "flex-1 min-w-0 flex flex-col min-h-0", children: [
57074
- tabs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center border-b border-surface-800 bg-surface-950 overflow-x-auto flex-shrink-0", children: tabs.map((tab) => {
57075
- const req = tab.requestId ? Object.values(collections).find((c) => c.data.requests[tab.requestId])?.data.requests[tab.requestId] : null;
57076
- const isActive = tab.id === activeTabId;
57077
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
58172
+ tabs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border-b border-surface-800 bg-surface-950 flex-shrink-0", children: [
58173
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center overflow-x-auto flex-1 min-w-0", children: tabs.map((tab) => {
58174
+ const req = tab.requestId ? Object.values(collections).find((c) => c.data.requests[tab.requestId])?.data.requests[tab.requestId] : null;
58175
+ const isActive = tab.id === activeTabId;
58176
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
58177
+ "div",
58178
+ {
58179
+ onClick: () => setActiveTabId(tab.id),
58180
+ onContextMenu: (e) => {
58181
+ e.preventDefault();
58182
+ setTabContextMenu({ x: e.clientX, y: e.clientY, tabId: tab.id });
58183
+ },
58184
+ className: `group flex items-center gap-1.5 px-3 py-1.5 border-r border-surface-800 cursor-pointer min-w-0 max-w-[200px] flex-shrink-0 transition-colors ${isActive ? "bg-surface-900 border-b-2 border-b-blue-500 -mb-px" : "hover:bg-surface-900/50 text-surface-600"}`,
58185
+ children: [
58186
+ req && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-bold shrink-0 ${TAB_METHOD_COLORS[req.method] ?? "text-gray-400"}`, children: req.method }),
58187
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs truncate ${isActive ? "text-white" : ""}`, children: req?.name ?? "Untitled" }),
58188
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
58189
+ "button",
58190
+ {
58191
+ onClick: (e) => {
58192
+ e.stopPropagation();
58193
+ closeTab(tab.id);
58194
+ },
58195
+ className: "ml-auto opacity-0 group-hover:opacity-100 shrink-0 text-surface-600 hover:text-white transition-all leading-none",
58196
+ title: "Close tab",
58197
+ children: "×"
58198
+ }
58199
+ )
58200
+ ]
58201
+ },
58202
+ tab.id
58203
+ );
58204
+ }) }),
58205
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
58206
+ "button",
58207
+ {
58208
+ onClick: closeAllTabs,
58209
+ title: "Close all tabs",
58210
+ className: "flex-shrink-0 px-2 py-1.5 text-surface-500 hover:text-white hover:bg-surface-900/50 border-l border-surface-800 text-xs leading-none transition-colors",
58211
+ children: "⨯ all"
58212
+ }
58213
+ )
58214
+ ] }),
58215
+ tabContextMenu && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
58216
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
58217
+ "div",
58218
+ {
58219
+ className: "fixed inset-0 z-40",
58220
+ onClick: () => setTabContextMenu(null),
58221
+ onContextMenu: (e) => {
58222
+ e.preventDefault();
58223
+ setTabContextMenu(null);
58224
+ }
58225
+ }
58226
+ ),
58227
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
57078
58228
  "div",
57079
58229
  {
57080
- onClick: () => setActiveTabId(tab.id),
57081
- className: `group flex items-center gap-1.5 px-3 py-1.5 border-r border-surface-800 cursor-pointer min-w-0 max-w-[200px] flex-shrink-0 transition-colors ${isActive ? "bg-surface-900 border-b-2 border-b-blue-500 -mb-px" : "hover:bg-surface-900/50 text-surface-600"}`,
58230
+ className: "fixed z-50 bg-surface-900 border border-surface-700 rounded shadow-2xl py-1 text-xs min-w-[160px]",
58231
+ style: { left: tabContextMenu.x, top: tabContextMenu.y },
57082
58232
  children: [
57083
- req && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-bold shrink-0 ${TAB_METHOD_COLORS[req.method] ?? "text-gray-400"}`, children: req.method }),
57084
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs truncate ${isActive ? "text-white" : ""}`, children: req?.name ?? "Untitled" }),
57085
58233
  /* @__PURE__ */ jsxRuntimeExports.jsx(
57086
58234
  "button",
57087
58235
  {
57088
- onClick: (e) => {
57089
- e.stopPropagation();
57090
- closeTab(tab.id);
58236
+ onClick: () => {
58237
+ closeTab(tabContextMenu.tabId);
58238
+ setTabContextMenu(null);
57091
58239
  },
57092
- className: "ml-auto opacity-0 group-hover:opacity-100 shrink-0 text-surface-600 hover:text-white transition-all leading-none",
57093
- title: "Close tab",
57094
- children: "×"
58240
+ className: "w-full text-left px-3 py-1.5 text-surface-300 hover:bg-surface-800 hover:text-white transition-colors",
58241
+ children: "Close"
58242
+ }
58243
+ ),
58244
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
58245
+ "button",
58246
+ {
58247
+ onClick: () => {
58248
+ closeOtherTabs(tabContextMenu.tabId);
58249
+ setTabContextMenu(null);
58250
+ },
58251
+ disabled: tabs.length < 2,
58252
+ className: "w-full text-left px-3 py-1.5 text-surface-300 hover:bg-surface-800 hover:text-white disabled:text-surface-600 disabled:hover:bg-transparent transition-colors",
58253
+ children: "Close others"
58254
+ }
58255
+ ),
58256
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
58257
+ "button",
58258
+ {
58259
+ onClick: () => {
58260
+ closeAllTabs();
58261
+ setTabContextMenu(null);
58262
+ },
58263
+ className: "w-full text-left px-3 py-1.5 text-surface-300 hover:bg-surface-800 hover:text-white transition-colors",
58264
+ children: "Close all"
57095
58265
  }
57096
58266
  )
57097
58267
  ]
57098
- },
57099
- tab.id
57100
- );
57101
- }) }),
58268
+ }
58269
+ )
58270
+ ] }),
57102
58271
  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(
57103
58272
  RecorderPanel,
57104
58273
  {