@testsmith/api-spector 0.0.3 → 0.0.5

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.
@@ -8339,6 +8339,14 @@ const useStore = create()(
8339
8339
  s.activeEnvironmentId = env.id;
8340
8340
  if (s.workspace) s.workspace.environments.push(relPath);
8341
8341
  }),
8342
+ deleteEnvironment: (id2) => set2((s) => {
8343
+ const relPath = s.environments[id2]?.relPath;
8344
+ delete s.environments[id2];
8345
+ if (s.activeEnvironmentId === id2) s.activeEnvironmentId = null;
8346
+ if (relPath && s.workspace) {
8347
+ s.workspace.environments = s.workspace.environments.filter((p2) => p2 !== relPath);
8348
+ }
8349
+ }),
8342
8350
  // ── Globals ───────────────────────────────────────────────────────────────
8343
8351
  setGlobals: (globals) => set2((s) => {
8344
8352
  s.globals = globals;
@@ -9161,6 +9169,43 @@ function TagChips({
9161
9169
  )
9162
9170
  ] });
9163
9171
  }
9172
+ function ConfirmDialog({ message, onConfirm, onCancel }) {
9173
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
9174
+ "div",
9175
+ {
9176
+ className: "fixed inset-0 bg-black/50 z-[300] flex items-center justify-center",
9177
+ onClick: onCancel,
9178
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
9179
+ "div",
9180
+ {
9181
+ className: "bg-surface-900 border border-surface-700 rounded-lg shadow-2xl p-4 w-72 flex flex-col gap-4",
9182
+ onClick: (e) => e.stopPropagation(),
9183
+ children: [
9184
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-white", children: message }),
9185
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2", children: [
9186
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9187
+ "button",
9188
+ {
9189
+ onClick: onCancel,
9190
+ className: "px-3 py-1.5 text-xs text-surface-400 hover:text-white transition-colors",
9191
+ children: "Cancel"
9192
+ }
9193
+ ),
9194
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9195
+ "button",
9196
+ {
9197
+ onClick: onConfirm,
9198
+ className: "px-3 py-1.5 text-xs bg-red-700 hover:bg-red-600 rounded transition-colors",
9199
+ children: "Delete"
9200
+ }
9201
+ )
9202
+ ] })
9203
+ ]
9204
+ }
9205
+ )
9206
+ }
9207
+ );
9208
+ }
9164
9209
  function IconBtn({
9165
9210
  title: title2,
9166
9211
  onClick,
@@ -9168,18 +9213,20 @@ function IconBtn({
9168
9213
  danger = false,
9169
9214
  alwaysVisible = false
9170
9215
  }) {
9171
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
9172
- "button",
9173
- {
9174
- title: title2,
9175
- onClick: (e) => {
9176
- e.stopPropagation();
9177
- onClick(e);
9178
- },
9179
- className: `px-1 py-0.5 rounded transition-colors ${alwaysVisible ? "" : "opacity-0 group-hover:opacity-100"} ${danger ? "hover:text-red-400" : "hover:text-blue-400"} text-surface-400`,
9180
- children
9181
- }
9182
- );
9216
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative group/tip", children: [
9217
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
9218
+ "button",
9219
+ {
9220
+ onClick: (e) => {
9221
+ e.stopPropagation();
9222
+ onClick(e);
9223
+ },
9224
+ className: `px-1 py-0.5 rounded transition-all ${alwaysVisible ? "" : "opacity-0 group-hover:opacity-100"} ${danger ? "hover:text-red-400" : "hover:text-blue-400"} text-surface-400 hover:scale-150`,
9225
+ children
9226
+ }
9227
+ ),
9228
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pointer-events-none absolute top-full left-1/2 -translate-x-1/2 mt-1 z-50 hidden group-hover/tip:block", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap rounded px-1.5 py-0.5 text-[10px] bg-[#1e1b2e] text-gray-300 border border-white/10 shadow-lg", children: title2 }) })
9229
+ ] });
9183
9230
  }
9184
9231
  function RunBtn({ onClick }) {
9185
9232
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -9190,7 +9237,7 @@ function RunBtn({ onClick }) {
9190
9237
  e.stopPropagation();
9191
9238
  onClick(e);
9192
9239
  },
9193
- className: "opacity-0 group-hover:opacity-100 px-1 py-0.5 rounded text-emerald-500 hover:text-emerald-400 transition-all",
9240
+ className: "px-1 py-0.5 rounded text-emerald-500 hover:text-emerald-400 transition-all",
9194
9241
  children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-3 h-3", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z", clipRule: "evenodd" }) })
9195
9242
  }
9196
9243
  );
@@ -9217,19 +9264,22 @@ function CollectionTree() {
9217
9264
  const updateRequestTags = useStore((s) => s.updateRequestTags);
9218
9265
  const openRunner = useStore((s) => s.openRunner);
9219
9266
  const colList = Object.values(collections);
9267
+ const [pendingConfirm, setPendingConfirm] = reactExports.useState(null);
9268
+ function confirmThen(message, action) {
9269
+ setPendingConfirm({ message, onConfirm: () => {
9270
+ action();
9271
+ setPendingConfirm(null);
9272
+ } });
9273
+ }
9220
9274
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 select-none", children: [
9221
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 text-xs font-semibold text-surface-400 uppercase tracking-wider flex items-center justify-between flex-shrink-0", children: [
9222
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Collections" }),
9223
- /* @__PURE__ */ jsxRuntimeExports.jsx(
9224
- "button",
9225
- {
9226
- onClick: () => addCollection("New Collection"),
9227
- title: "New collection",
9228
- className: "text-surface-400 hover:text-blue-400 transition-colors px-1",
9229
- children: "+"
9230
- }
9231
- )
9232
- ] }),
9275
+ pendingConfirm && /* @__PURE__ */ jsxRuntimeExports.jsx(
9276
+ ConfirmDialog,
9277
+ {
9278
+ message: pendingConfirm.message,
9279
+ onConfirm: pendingConfirm.onConfirm,
9280
+ onCancel: () => setPendingConfirm(null)
9281
+ }
9282
+ ),
9233
9283
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
9234
9284
  colList.map(({ data: col }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
9235
9285
  CollectionNode,
@@ -9243,13 +9293,9 @@ function CollectionTree() {
9243
9293
  onAddRequest: (folderId) => addRequest(col.id, folderId),
9244
9294
  onAddFolder: (parentId, name2) => addFolder(col.id, parentId, name2),
9245
9295
  onRenameCollection: (name2) => renameCollection(col.id, name2),
9246
- onDeleteCollection: () => {
9247
- if (confirm(`Delete collection "${col.name}"?`)) deleteCollection(col.id);
9248
- },
9296
+ onDeleteCollection: () => confirmThen(`Delete collection "${col.name}"?`, () => deleteCollection(col.id)),
9249
9297
  onRenameFolder: (folderId, name2) => renameFolder(col.id, folderId, name2),
9250
- onDeleteFolder: (folderId) => {
9251
- if (confirm("Delete this folder and all its requests?")) deleteFolder(col.id, folderId);
9252
- },
9298
+ onDeleteFolder: (folderId) => confirmThen("Delete this folder and all its requests?", () => deleteFolder(col.id, folderId)),
9253
9299
  onRenameRequest: renameRequest,
9254
9300
  onDeleteRequest: (reqId) => deleteRequest(col.id, reqId),
9255
9301
  onDuplicateRequest: (reqId) => duplicateRequest(col.id, reqId),
@@ -9291,6 +9337,13 @@ function CollectionNode({
9291
9337
  }) {
9292
9338
  const [expanded, setExpanded] = reactExports.useState(true);
9293
9339
  const [renaming, setRenaming] = reactExports.useState(false);
9340
+ const [expandCtrl, setExpandCtrl] = reactExports.useState({ value: true, seq: 0 });
9341
+ function expandAll() {
9342
+ setExpandCtrl((c) => ({ value: true, seq: c.seq + 1 }));
9343
+ }
9344
+ function collapseAll() {
9345
+ setExpandCtrl((c) => ({ value: false, seq: c.seq + 1 }));
9346
+ }
9294
9347
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
9295
9348
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
9296
9349
  "div",
@@ -9315,13 +9368,15 @@ function CollectionNode({
9315
9368
  validate: (v2) => existingCollectionNames.filter((n2) => n2 !== col.name).includes(v2) ? `"${v2}" already exists` : null
9316
9369
  }
9317
9370
  ) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-semibold truncate block", children: col.name }) }),
9318
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center shrink-0 gap-0", children: [
9371
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "hidden group-hover:flex items-center shrink-0 gap-0", children: [
9319
9372
  /* @__PURE__ */ jsxRuntimeExports.jsx(RunBtn, { onClick: onRunCollection }),
9320
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Collection data (iterations)", onClick: onSelectCollection, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TableIcon, {}) }),
9321
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add request", onClick: () => onAddRequest(col.rootFolder.id), children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "+" }) }),
9322
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add folder", onClick: () => onAddFolder(col.rootFolder.id, "New Folder"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(FolderIcon, {}) }),
9323
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Rename", onClick: () => setRenaming(true), children: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}) }),
9324
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Delete collection", onClick: onDeleteCollection, danger: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}) })
9373
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Expand all folders", onClick: expandAll, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandAllIcon, {}) }),
9374
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Collapse all folders", onClick: collapseAll, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CollapseAllIcon, {}) }),
9375
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Collection data (iterations)", onClick: onSelectCollection, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TableIcon, {}) }),
9376
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add request", onClick: () => onAddRequest(col.rootFolder.id), alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "+" }) }),
9377
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add folder", onClick: () => onAddFolder(col.rootFolder.id, "New Folder"), alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(FolderIcon, {}) }),
9378
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Rename", onClick: () => setRenaming(true), alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}) }),
9379
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Delete collection", onClick: onDeleteCollection, danger: true, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}) })
9325
9380
  ] })
9326
9381
  ]
9327
9382
  }
@@ -9334,6 +9389,7 @@ function CollectionNode({
9334
9389
  requests: col.requests,
9335
9390
  activeRequestId,
9336
9391
  depth: 0,
9392
+ expandCtrl,
9337
9393
  onSelectRequest,
9338
9394
  onAddRequest,
9339
9395
  onAddFolder,
@@ -9353,6 +9409,7 @@ function FolderRow({
9353
9409
  folder,
9354
9410
  collectionId,
9355
9411
  depth,
9412
+ expandCtrl,
9356
9413
  onAddRequest,
9357
9414
  onAddFolder,
9358
9415
  onRename,
@@ -9362,6 +9419,9 @@ function FolderRow({
9362
9419
  children
9363
9420
  }) {
9364
9421
  const [expanded, setExpanded] = reactExports.useState(true);
9422
+ reactExports.useEffect(() => {
9423
+ if (expandCtrl.seq > 0) setExpanded(expandCtrl.value);
9424
+ }, [expandCtrl.seq]);
9365
9425
  const [renaming, setRenaming] = reactExports.useState(false);
9366
9426
  const [showSettings, setShowSettings] = reactExports.useState(false);
9367
9427
  const tags2 = folder.tags ?? [];
@@ -9399,16 +9459,16 @@ function FolderRow({
9399
9459
  }
9400
9460
  )
9401
9461
  ] }),
9402
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center shrink-0 gap-0", children: [
9462
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "hidden group-hover:flex items-center shrink-0 gap-0", children: [
9403
9463
  /* @__PURE__ */ jsxRuntimeExports.jsx(RunBtn, { onClick: onRun }),
9404
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add request", onClick: onAddRequest, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "+" }) }),
9405
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add sub-folder", onClick: onAddFolder, children: /* @__PURE__ */ jsxRuntimeExports.jsx(FolderIcon, {}) }),
9406
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Folder auth & headers", onClick: () => setShowSettings(true), children: /* @__PURE__ */ jsxRuntimeExports.jsx(KeyIcon, {}) }),
9464
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add request", onClick: onAddRequest, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "+" }) }),
9465
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add sub-folder", onClick: onAddFolder, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(FolderIcon, {}) }),
9466
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Folder auth & headers", onClick: () => setShowSettings(true), alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(KeyIcon, {}) }),
9407
9467
  /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add tag", onClick: () => {
9408
- }, alwaysVisible: false, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TagChips, { tags: [], onRemove: () => {
9468
+ }, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TagChips, { tags: [], onRemove: () => {
9409
9469
  }, onAdd: (tag) => onUpdateTags([...tags2, tag]) }) }),
9410
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Rename", onClick: () => setRenaming(true), children: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}) }),
9411
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Delete folder", onClick: onDelete, danger: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}) })
9470
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Rename", onClick: () => setRenaming(true), alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}) }),
9471
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Delete folder", onClick: onDelete, danger: true, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}) })
9412
9472
  ] })
9413
9473
  ]
9414
9474
  }
@@ -9430,6 +9490,7 @@ function FolderContents({
9430
9490
  requests,
9431
9491
  activeRequestId,
9432
9492
  depth,
9493
+ expandCtrl,
9433
9494
  onSelectRequest,
9434
9495
  onAddRequest,
9435
9496
  onAddFolder,
@@ -9449,6 +9510,7 @@ function FolderContents({
9449
9510
  folder: sub,
9450
9511
  collectionId,
9451
9512
  depth: depth + 1,
9513
+ expandCtrl,
9452
9514
  onAddRequest: () => onAddRequest(sub.id),
9453
9515
  onAddFolder: () => onAddFolder(sub.id, "New Folder"),
9454
9516
  onRename: (name2) => onRenameFolder(sub.id, name2),
@@ -9463,6 +9525,7 @@ function FolderContents({
9463
9525
  requests,
9464
9526
  activeRequestId,
9465
9527
  depth: depth + 1,
9528
+ expandCtrl,
9466
9529
  onSelectRequest,
9467
9530
  onAddRequest,
9468
9531
  onAddFolder,
@@ -9576,6 +9639,12 @@ function CopyIcon() {
9576
9639
  function KeyIcon() {
9577
9640
  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: "M15 7a4 4 0 110 8 4 4 0 010-8zm-7 8l-1 1m0 0l-1 1m1-1l1 1M3 20l5-5" }) });
9578
9641
  }
9642
+ function ExpandAllIcon() {
9643
+ 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 18h16M15 15l3 3 3-3" }) });
9644
+ }
9645
+ function CollapseAllIcon() {
9646
+ 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" }) });
9647
+ }
9579
9648
  function ParamsTab({ request, onChange }) {
9580
9649
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
9581
9650
  KVTable,
@@ -36952,6 +37021,24 @@ sp.variables.set("field_value", json.field);`
36952
37021
  label: "Save JSON field to environment",
36953
37022
  code: `const json = sp.response.json();
36954
37023
  sp.environment.set("token", json.token);`
37024
+ },
37025
+ {
37026
+ label: "Extract via JSONPath to variable",
37027
+ code: `const matches = sp.jsonPath(sp.response.json(), '$.data[0].id');
37028
+ sp.variables.set("extracted_value", String(matches[0] ?? ''));`
37029
+ },
37030
+ {
37031
+ label: "Extract via JSONPath to environment",
37032
+ code: `const matches = sp.jsonPath(sp.response.json(), '$.data[0].id');
37033
+ sp.environment.set("extracted_value", String(matches[0] ?? ''));`
37034
+ },
37035
+ {
37036
+ label: "Extract from XML to variable",
37037
+ code: `sp.variables.set("extracted_value", sp.response.xmlText("ElementName") ?? '');`
37038
+ },
37039
+ {
37040
+ label: "Extract from XML to environment",
37041
+ code: `sp.environment.set("extracted_value", sp.response.xmlText("ElementName") ?? '');`
36955
37042
  }
36956
37043
  ]
36957
37044
  }
@@ -36965,6 +37052,7 @@ function ScriptsTab({ request, onChange }) {
36965
37052
  if (activeTabId) setTabScriptTab(activeTabId, t2);
36966
37053
  };
36967
37054
  const [expandedGroup, setExpandedGroup] = reactExports.useState(SNIPPET_GROUPS[0].group);
37055
+ const [snippetsOpen, setSnippetsOpen] = reactExports.useState(true);
36968
37056
  const varNames = useVarNames();
36969
37057
  const varValues = useVarValues();
36970
37058
  const extensions = reactExports.useMemo(
@@ -37005,9 +37093,27 @@ function ScriptsTab({ request, onChange }) {
37005
37093
  }
37006
37094
  ) })
37007
37095
  ] }),
37008
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-52 flex-shrink-0 flex flex-col overflow-y-auto border-l border-surface-700 pl-2", children: [
37009
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-semibold text-surface-400 uppercase tracking-wider mb-2", children: "Snippets" }),
37010
- SNIPPET_GROUPS.map((group) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-1", children: [
37096
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex-shrink-0 flex flex-col border-l border-surface-700 transition-all ${snippetsOpen ? "w-52 pl-2 overflow-y-auto" : "w-7"}`, children: [
37097
+ snippetsOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
37098
+ "button",
37099
+ {
37100
+ onClick: () => setSnippetsOpen(false),
37101
+ className: "flex items-center gap-1 text-[10px] font-semibold text-surface-400 uppercase tracking-wider mb-2 hover:text-white transition-colors w-full",
37102
+ children: [
37103
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "▾" }),
37104
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Quick inserts" })
37105
+ ]
37106
+ }
37107
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
37108
+ "button",
37109
+ {
37110
+ onClick: () => setSnippetsOpen(true),
37111
+ className: "flex-1 flex items-center justify-center hover:bg-surface-800 transition-colors rounded-sm",
37112
+ title: "Expand quick inserts",
37113
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-semibold text-surface-400 uppercase tracking-wider [writing-mode:vertical-rl] rotate-180", children: "Quick inserts" })
37114
+ }
37115
+ ),
37116
+ snippetsOpen && SNIPPET_GROUPS.map((group) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-1", children: [
37011
37117
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
37012
37118
  "button",
37013
37119
  {
@@ -43782,36 +43888,82 @@ function makeJsonSnippet(path, value, mode) {
43782
43888
  });`;
43783
43889
  }
43784
43890
  }
43891
+ function getAtPath(root, path) {
43892
+ let cur2 = root;
43893
+ for (const key of path) {
43894
+ if (cur2 == null || typeof cur2 !== "object") return void 0;
43895
+ cur2 = cur2[key];
43896
+ }
43897
+ return cur2;
43898
+ }
43899
+ function toJsonPathExpr(path, filterKey, filterValue) {
43900
+ let arrayIdx = -1;
43901
+ for (let i = path.length - 1; i >= 0; i--) {
43902
+ if (typeof path[i] === "number") {
43903
+ arrayIdx = i;
43904
+ break;
43905
+ }
43906
+ }
43907
+ if (arrayIdx < 0) return "";
43908
+ const arrayPart = "$." + path.slice(0, arrayIdx).join(".");
43909
+ const leafPart = path.slice(arrayIdx + 1).join(".");
43910
+ const filterVal = isNaN(Number(filterValue)) ? `"${filterValue.replace(/"/g, '\\"')}"` : filterValue;
43911
+ const expr = leafPart ? `${arrayPart}[?(@.${filterKey}==${filterVal})].${leafPart}` : `${arrayPart}[?(@.${filterKey}==${filterVal})]`;
43912
+ return expr;
43913
+ }
43914
+ function makeJsonPathSnippet(path, value, filterKey, filterValue) {
43915
+ const expr = toJsonPathExpr(path, filterKey, filterValue);
43916
+ const lit = toLit(value);
43917
+ return `sp.test('${expr} equals ${lit}', function() {
43918
+ const matches = sp.jsonPath(sp.response.json(), '${expr}');
43919
+ sp.expect(matches.length).to.be.above(0);
43920
+ sp.expect(matches[0]).to.equal(${lit});
43921
+ });`;
43922
+ }
43785
43923
  function makeXmlSnippet(selector, value, mode) {
43786
- const parse2 = `const doc = new DOMParser().parseFromString(sp.response.text(), "text/xml");`;
43787
- const query = `const el = doc.querySelector("${selector.replace(/"/g, '\\"')}");`;
43924
+ const sel = selector.replace(/"/g, '\\"');
43788
43925
  switch (mode) {
43789
43926
  case "equals":
43790
43927
  return `sp.test('${selector} equals "${esc(value)}"', function() {
43791
- ${parse2}
43792
- ${query}
43793
- sp.expect(el?.textContent?.trim()).to.equal("${esc(value)}");
43928
+ sp.expect(sp.response.xmlText("${sel}")).to.equal("${esc(value)}");
43794
43929
  });`;
43795
43930
  case "exists":
43796
43931
  return `sp.test('${selector} exists', function() {
43797
- ${parse2}
43798
- ${query}
43799
- sp.expect(el).to.not.equal(null);
43932
+ sp.expect(sp.response.xmlText("${sel}")).to.not.equal(null);
43800
43933
  });`;
43801
43934
  case "contains":
43802
43935
  return `sp.test('${selector} contains "${esc(value)}"', function() {
43803
- ${parse2}
43804
- ${query}
43805
- sp.expect(el?.textContent).to.include("${esc(value)}");
43936
+ sp.expect(sp.response.xmlText("${sel}")).to.include("${esc(value)}");
43806
43937
  });`;
43807
43938
  }
43808
43939
  }
43940
+ function varNameFromPath(path) {
43941
+ return path.filter((k2) => typeof k2 === "string").at(-1) ?? "extracted_value";
43942
+ }
43943
+ function makeJsonExtractSnippet(path, target) {
43944
+ const acc = jsonAccessor(path);
43945
+ const varName = varNameFromPath(path);
43946
+ return `const json = sp.response.json();
43947
+ sp.${target}.set("${varName}", String(${acc}));`;
43948
+ }
43949
+ function makeJsonPathExtractSnippet(path, filterKey, filterValue, target) {
43950
+ const expr = toJsonPathExpr(path, filterKey, filterValue);
43951
+ const varName = varNameFromPath(path);
43952
+ return `const matches = sp.jsonPath(sp.response.json(), '${expr}');
43953
+ sp.${target}.set("${varName}", String(matches[0] ?? ''));`;
43954
+ }
43955
+ function makeXmlExtractSnippet(selector, target) {
43956
+ return `sp.${target}.set("extracted_value", sp.response.xmlText("${selector.replace(/"/g, '\\"')}") ?? '');`;
43957
+ }
43809
43958
  function AssertMenu({
43810
43959
  state,
43811
43960
  onClose,
43812
43961
  onConfirm
43813
43962
  }) {
43814
43963
  const ref2 = reactExports.useRef(null);
43964
+ const [jpOpen, setJpOpen] = reactExports.useState(false);
43965
+ const [filterKey, setFilterKey] = reactExports.useState("");
43966
+ const [filterVal, setFilterVal] = reactExports.useState("");
43815
43967
  reactExports.useEffect(() => {
43816
43968
  function onMouse(e) {
43817
43969
  if (ref2.current && !ref2.current.contains(e.target)) onClose();
@@ -43828,8 +43980,10 @@ function AssertMenu({
43828
43980
  }, [onClose]);
43829
43981
  let title2 = "";
43830
43982
  let options = [];
43983
+ let jpSiblingKeys = [];
43984
+ let jpAvailable = false;
43831
43985
  if (state.type === "json") {
43832
- const { path, value } = state;
43986
+ const { path, value, root } = state;
43833
43987
  const isStr = typeof value === "string";
43834
43988
  const preview = isStr ? `"${value.length > 22 ? value.slice(0, 22) + "…" : value}"` : String(value);
43835
43989
  title2 = jsonPathLabel(path);
@@ -43839,6 +43993,26 @@ function AssertMenu({
43839
43993
  { label: `is ${value === null ? "null" : typeof value}`, snippet: makeJsonSnippet(path, value, "type") },
43840
43994
  ...isStr ? [{ label: `contains ${preview}`, snippet: makeJsonSnippet(path, value, "contains") }] : []
43841
43995
  ];
43996
+ const arrayIdx = [...path].reverse().findIndex((k2) => typeof k2 === "number");
43997
+ if (arrayIdx >= 0) {
43998
+ jpAvailable = true;
43999
+ const realIdx = path.length - 1 - arrayIdx;
44000
+ const itemObj = getAtPath(root, path.slice(0, realIdx + 1));
44001
+ if (itemObj != null && typeof itemObj === "object" && !Array.isArray(itemObj)) {
44002
+ jpSiblingKeys = Object.keys(itemObj).filter((k2) => {
44003
+ const v2 = itemObj[k2];
44004
+ return typeof v2 !== "object" || v2 === null;
44005
+ });
44006
+ }
44007
+ if (!filterKey && jpSiblingKeys.length > 0) {
44008
+ const defaultKey = jpSiblingKeys.find((k2) => k2 === "name" || k2 === "id") ?? jpSiblingKeys[0];
44009
+ setTimeout(() => {
44010
+ setFilterKey(defaultKey);
44011
+ const seed = getAtPath(root, [...path.slice(0, realIdx + 1), defaultKey]);
44012
+ setFilterVal(seed != null ? String(seed) : "");
44013
+ }, 0);
44014
+ }
44015
+ }
43842
44016
  } else {
43843
44017
  const { selector, value } = state;
43844
44018
  const preview = `"${value.length > 22 ? value.slice(0, 22) + "…" : value}"`;
@@ -43849,14 +44023,14 @@ function AssertMenu({
43849
44023
  { label: `contains ${preview}`, snippet: makeXmlSnippet(selector, value, "contains") }
43850
44024
  ];
43851
44025
  }
43852
- const x2 = Math.min(state.x, window.innerWidth - 260);
43853
- const y2 = Math.min(state.y, window.innerHeight - 220);
44026
+ const x2 = Math.min(state.x, window.innerWidth - 280);
44027
+ const y2 = Math.min(state.y, window.innerHeight - 280);
43854
44028
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
43855
44029
  "div",
43856
44030
  {
43857
44031
  ref: ref2,
43858
44032
  style: { top: y2, left: x2, position: "fixed" },
43859
- className: "z-[200] bg-surface-900 border border-surface-700 rounded-lg shadow-2xl p-2 min-w-[240px]",
44033
+ className: "z-[200] bg-surface-900 border border-surface-700 rounded-lg shadow-2xl p-2 min-w-[260px]",
43860
44034
  children: [
43861
44035
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500 font-mono px-1.5 pb-1.5 mb-1.5 border-b border-surface-800 truncate", children: title2 }),
43862
44036
  options.map((opt) => /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -43870,7 +44044,141 @@ function AssertMenu({
43870
44044
  children: opt.label
43871
44045
  },
43872
44046
  opt.label
43873
- ))
44047
+ )),
44048
+ jpAvailable && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 border-t border-surface-800 pt-1", children: [
44049
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
44050
+ "button",
44051
+ {
44052
+ onClick: () => setJpOpen((o) => !o),
44053
+ className: "w-full text-left text-xs text-blue-400 hover:text-blue-300 hover:bg-surface-800 rounded px-2 py-1.5 transition-colors flex items-center gap-1",
44054
+ children: [
44055
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: jpOpen ? "▾" : "▸" }),
44056
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "JSONPath assert (with filter)" })
44057
+ ]
44058
+ }
44059
+ ),
44060
+ jpOpen && state.type === "json" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 px-2 flex flex-col gap-1.5", children: [
44061
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
44062
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-400 w-16 shrink-0", children: "filter by" }),
44063
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
44064
+ "select",
44065
+ {
44066
+ value: filterKey,
44067
+ onChange: (e) => {
44068
+ const k2 = e.target.value;
44069
+ setFilterKey(k2);
44070
+ const arrayIdx2 = [...state.path].reverse().findIndex((seg) => typeof seg === "number");
44071
+ const realIdx2 = state.path.length - 1 - arrayIdx2;
44072
+ const itemObj2 = getAtPath(state.root, state.path.slice(0, realIdx2 + 1));
44073
+ const seed = itemObj2 != null ? itemObj2[k2] : void 0;
44074
+ setFilterVal(seed != null ? String(seed) : "");
44075
+ },
44076
+ className: "flex-1 bg-surface-800 border border-surface-700 rounded px-1 py-0.5 text-xs focus:outline-none",
44077
+ children: jpSiblingKeys.map((k2) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: k2, children: k2 }, k2))
44078
+ }
44079
+ )
44080
+ ] }),
44081
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
44082
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-400 w-16 shrink-0", children: "equals" }),
44083
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
44084
+ "input",
44085
+ {
44086
+ value: filterVal,
44087
+ onChange: (e) => setFilterVal(e.target.value),
44088
+ className: "flex-1 bg-surface-800 border border-surface-700 rounded px-1 py-0.5 text-xs font-mono focus:outline-none focus:border-blue-500"
44089
+ }
44090
+ )
44091
+ ] }),
44092
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 self-end", children: [
44093
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
44094
+ "button",
44095
+ {
44096
+ disabled: !filterKey || !filterVal,
44097
+ onClick: () => {
44098
+ onConfirm(makeJsonPathSnippet(state.path, state.value, filterKey, filterVal));
44099
+ onClose();
44100
+ },
44101
+ className: "text-xs px-2 py-1 bg-blue-700 hover:bg-blue-600 disabled:opacity-40 rounded transition-colors",
44102
+ children: "Assert"
44103
+ }
44104
+ ),
44105
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
44106
+ "button",
44107
+ {
44108
+ disabled: !filterKey || !filterVal,
44109
+ onClick: () => {
44110
+ onConfirm(makeJsonPathExtractSnippet(state.path, filterKey, filterVal, "variables"));
44111
+ onClose();
44112
+ },
44113
+ className: "text-xs px-2 py-1 bg-surface-700 hover:bg-surface-600 disabled:opacity-40 rounded transition-colors",
44114
+ children: "→ variable"
44115
+ }
44116
+ ),
44117
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
44118
+ "button",
44119
+ {
44120
+ disabled: !filterKey || !filterVal,
44121
+ onClick: () => {
44122
+ onConfirm(makeJsonPathExtractSnippet(state.path, filterKey, filterVal, "environment"));
44123
+ onClose();
44124
+ },
44125
+ className: "text-xs px-2 py-1 bg-surface-700 hover:bg-surface-600 disabled:opacity-40 rounded transition-colors",
44126
+ children: "→ env"
44127
+ }
44128
+ )
44129
+ ] })
44130
+ ] })
44131
+ ] }),
44132
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 border-t border-surface-800 pt-1", children: [
44133
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500 uppercase tracking-wider px-2 py-1", children: "Extract" }),
44134
+ state.type === "json" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
44135
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
44136
+ "button",
44137
+ {
44138
+ onClick: () => {
44139
+ onConfirm(makeJsonExtractSnippet(state.path, "variables"));
44140
+ onClose();
44141
+ },
44142
+ className: "w-full text-left text-xs text-surface-300 hover:text-white hover:bg-surface-800 rounded px-2 py-1.5 transition-colors",
44143
+ children: "Save to variable"
44144
+ }
44145
+ ),
44146
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
44147
+ "button",
44148
+ {
44149
+ onClick: () => {
44150
+ onConfirm(makeJsonExtractSnippet(state.path, "environment"));
44151
+ onClose();
44152
+ },
44153
+ className: "w-full text-left text-xs text-surface-300 hover:text-white hover:bg-surface-800 rounded px-2 py-1.5 transition-colors",
44154
+ children: "Save to environment"
44155
+ }
44156
+ )
44157
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
44158
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
44159
+ "button",
44160
+ {
44161
+ onClick: () => {
44162
+ onConfirm(makeXmlExtractSnippet(state.selector, "variables"));
44163
+ onClose();
44164
+ },
44165
+ className: "w-full text-left text-xs text-surface-300 hover:text-white hover:bg-surface-800 rounded px-2 py-1.5 transition-colors",
44166
+ children: "Save to variable"
44167
+ }
44168
+ ),
44169
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
44170
+ "button",
44171
+ {
44172
+ onClick: () => {
44173
+ onConfirm(makeXmlExtractSnippet(state.selector, "environment"));
44174
+ onClose();
44175
+ },
44176
+ className: "w-full text-left text-xs text-surface-300 hover:text-white hover:bg-surface-800 rounded px-2 py-1.5 transition-colors",
44177
+ children: "Save to environment"
44178
+ }
44179
+ )
44180
+ ] })
44181
+ ] })
43874
44182
  ]
43875
44183
  }
43876
44184
  );
@@ -43899,7 +44207,7 @@ function JsonNode({
43899
44207
  onClick: (e) => onLeaf(e, path, value),
43900
44208
  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",
43901
44209
  title: "Add assertion for this value",
43902
- children: "+ assert"
44210
+ children: "+ insert"
43903
44211
  }
43904
44212
  )
43905
44213
  ] });
@@ -43970,7 +44278,7 @@ function XmlNode({
43970
44278
  onClick: (e) => onLeaf(e, selector, text),
43971
44279
  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",
43972
44280
  title: "Add assertion for this value",
43973
- children: "+ assert"
44281
+ children: "+ insert"
43974
44282
  }
43975
44283
  )
43976
44284
  ] });
@@ -43999,22 +44307,26 @@ function InteractiveBody({ body, contentType, onAssert }) {
43999
44307
  const [popover, setPopover] = reactExports.useState(null);
44000
44308
  const isJson = contentType.includes("json");
44001
44309
  const isXml = !isJson && (contentType.includes("xml") || contentType.includes("html"));
44310
+ let parsedJson = null;
44311
+ if (isJson) {
44312
+ try {
44313
+ parsedJson = JSON.parse(body);
44314
+ } catch {
44315
+ }
44316
+ }
44002
44317
  function handleJsonLeaf(e, path, value) {
44003
44318
  e.stopPropagation();
44004
- setPopover({ type: "json", path, value, x: e.clientX + 10, y: e.clientY + 10 });
44319
+ setPopover({ type: "json", path, value, root: parsedJson, x: e.clientX + 10, y: e.clientY + 10 });
44005
44320
  }
44006
44321
  function handleXmlLeaf(e, selector, value) {
44007
44322
  e.stopPropagation();
44008
44323
  setPopover({ type: "xml", selector, value, x: e.clientX + 10, y: e.clientY + 10 });
44009
44324
  }
44010
44325
  const treeContent = isJson ? (() => {
44011
- let parsed;
44012
- try {
44013
- parsed = JSON.parse(body);
44014
- } catch {
44326
+ if (parsedJson === null) {
44015
44327
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-xs text-surface-600", children: "Unable to parse JSON response body" });
44016
44328
  }
44017
- return /* @__PURE__ */ jsxRuntimeExports.jsx(JsonNode, { nodeKey: null, value: parsed, path: [], depth: 0, onLeaf: handleJsonLeaf });
44329
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(JsonNode, { nodeKey: null, value: parsedJson, path: [], depth: 0, onLeaf: handleJsonLeaf });
44018
44330
  })() : isXml ? (() => {
44019
44331
  const doc2 = new DOMParser().parseFromString(body, "text/xml");
44020
44332
  const root = doc2.documentElement;
@@ -44046,6 +44358,32 @@ function prettyJson(raw) {
44046
44358
  return raw;
44047
44359
  }
44048
44360
  }
44361
+ function prettyXml(raw) {
44362
+ try {
44363
+ const indent = " ";
44364
+ let result = "";
44365
+ let depth = 0;
44366
+ const tokens = raw.match(/<[^>]+>|[^<]+/g) ?? [];
44367
+ for (const token of tokens) {
44368
+ const text = token.trim();
44369
+ if (!text) continue;
44370
+ if (text.startsWith("<?") || text.startsWith("<!")) {
44371
+ result += indent.repeat(depth) + text + "\n";
44372
+ } else if (token.startsWith("</")) {
44373
+ depth = Math.max(0, depth - 1);
44374
+ result += indent.repeat(depth) + text + "\n";
44375
+ } else if (token.startsWith("<") && !token.endsWith("/>") && !token.includes("</")) {
44376
+ result += indent.repeat(depth) + text + "\n";
44377
+ depth++;
44378
+ } else {
44379
+ result += indent.repeat(depth) + text + "\n";
44380
+ }
44381
+ }
44382
+ return result.trimEnd();
44383
+ } catch {
44384
+ return raw;
44385
+ }
44386
+ }
44049
44387
  function SaveAsMockModal({ onClose }) {
44050
44388
  const mocks = useStore((s) => s.mocks);
44051
44389
  const addMock = useStore((s) => s.addMock);
@@ -44381,7 +44719,7 @@ function ResponseViewer() {
44381
44719
  const isJson = contentType.includes("json");
44382
44720
  const isXml = !isJson && (contentType.includes("xml") || contentType.includes("html"));
44383
44721
  const supportsTree = isJson || isXml;
44384
- const displayBody = isJson ? prettyJson(response.body) : response.body;
44722
+ const displayBody = isJson ? prettyJson(response.body) : isXml ? prettyXml(response.body) : response.body;
44385
44723
  const passedCount = scriptResult?.testResults.filter((t2) => t2.passed).length ?? 0;
44386
44724
  const totalCount = scriptResult?.testResults.length ?? 0;
44387
44725
  const consoleCount = scriptResult?.consoleOutput.length ?? 0;
@@ -44489,17 +44827,16 @@ function ResponseViewer() {
44489
44827
  contentType,
44490
44828
  onAssert: handleAssert
44491
44829
  }
44492
- ) : tab === "body" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
44830
+ ) : tab === "body" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
44493
44831
  ReactCodeMirror,
44494
44832
  {
44495
44833
  value: displayBody,
44496
- height: "100%",
44497
44834
  theme: oneDark,
44498
- extensions: isJson ? [json()] : [],
44835
+ extensions: isJson ? [json()] : isXml ? [xml()] : [],
44499
44836
  readOnly: true,
44500
44837
  basicSetup: { lineNumbers: true, foldGutter: true }
44501
44838
  }
44502
- ) }) : tab === "headers" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "w-full text-xs px-4 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: Object.entries(response.headers).map(([k2, v2]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-800", children: [
44839
+ ) : tab === "headers" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "w-full text-xs px-4 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: Object.entries(response.headers).map(([k2, v2]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-800", children: [
44503
44840
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 px-4 text-surface-400 font-mono w-56 align-top", children: k2 }),
44504
44841
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 px-4 text-white font-mono break-all", children: v2 })
44505
44842
  ] }, k2)) }) }) }) : tab === "tests" ? /* @__PURE__ */ jsxRuntimeExports.jsx(TestsPanel, { scriptResult }) : tab === "console" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ConsolePanel, { scriptResult }) : tab === "request" ? /* @__PURE__ */ jsxRuntimeExports.jsx(RequestPanel, { sentRequest }) : null })
@@ -44909,7 +45246,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
44909
45246
  const [password, setPassword] = reactExports.useState("");
44910
45247
  const [error2, setError] = reactExports.useState("");
44911
45248
  const [copied, setCopied] = reactExports.useState(null);
44912
- async function confirm2() {
45249
+ async function confirm() {
44913
45250
  if (!password.trim()) {
44914
45251
  setError("Password cannot be empty.");
44915
45252
  return;
@@ -44955,7 +45292,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
44955
45292
  setPassword(e.target.value);
44956
45293
  setError("");
44957
45294
  },
44958
- onKeyDown: (e) => e.key === "Enter" && confirm2(),
45295
+ onKeyDown: (e) => e.key === "Enter" && confirm(),
44959
45296
  placeholder: "Enter master password…",
44960
45297
  className: "bg-surface-800 border border-surface-700 rounded px-3 py-1.5 text-sm font-mono focus:outline-none focus:border-blue-500"
44961
45298
  }
@@ -44991,7 +45328,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
44991
45328
  /* @__PURE__ */ jsxRuntimeExports.jsx(
44992
45329
  "button",
44993
45330
  {
44994
- onClick: confirm2,
45331
+ onClick: confirm,
44995
45332
  disabled: !password.trim(),
44996
45333
  className: "px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-500 disabled:opacity-40 rounded transition-colors",
44997
45334
  children: "Set Password & Continue"
@@ -45049,8 +45386,14 @@ function EnvironmentEditor({ onClose }) {
45049
45386
  const [savedIdx, setSavedIdx] = reactExports.useState(null);
45050
45387
  const [pendingEncryptIdx, setPendingEncryptIdx] = reactExports.useState(null);
45051
45388
  const [nameError, setNameError] = reactExports.useState(null);
45389
+ const deleteEnvironment = useStore((s) => s.deleteEnvironment);
45052
45390
  const envList = Object.values(environments);
45053
45391
  const env = selectedId ? environments[selectedId]?.data ?? null : null;
45392
+ function handleDelete(id2) {
45393
+ deleteEnvironment(id2);
45394
+ const remaining = Object.keys(environments).filter((k2) => k2 !== id2);
45395
+ setSelectedId(remaining[0] ?? "");
45396
+ }
45054
45397
  function updateVar(idx, patch) {
45055
45398
  if (!env) return;
45056
45399
  const vars = env.variables.map((v2, i) => i === idx ? { ...v2, ...patch } : v2);
@@ -45164,12 +45507,29 @@ function EnvironmentEditor({ onClose }) {
45164
45507
  children: [
45165
45508
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-44 border-r border-surface-800 flex flex-col flex-shrink-0", children: [
45166
45509
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 text-xs font-semibold text-surface-400 uppercase tracking-wider border-b border-surface-800", children: "Environments" }),
45167
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto py-1", children: envList.map(({ data: e }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
45168
- "button",
45510
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto py-1", children: envList.map(({ data: e }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
45511
+ "div",
45169
45512
  {
45170
- onClick: () => selectEnv(e.id),
45171
- className: `w-full text-left px-3 py-1.5 text-xs truncate transition-colors ${selectedId === e.id ? "bg-surface-800 text-white" : "text-surface-200 hover:bg-surface-800"}`,
45172
- children: e.name
45513
+ className: `group flex items-center pr-1 transition-colors ${selectedId === e.id ? "bg-surface-800" : "hover:bg-surface-800"}`,
45514
+ children: [
45515
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
45516
+ "button",
45517
+ {
45518
+ onClick: () => selectEnv(e.id),
45519
+ className: `flex-1 text-left px-3 py-1.5 text-xs truncate ${selectedId === e.id ? "text-white" : "text-surface-200"}`,
45520
+ children: e.name
45521
+ }
45522
+ ),
45523
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
45524
+ "button",
45525
+ {
45526
+ onClick: () => handleDelete(e.id),
45527
+ className: "opacity-0 group-hover:opacity-100 text-surface-400 hover:text-red-400 transition-all px-1 text-sm leading-none shrink-0",
45528
+ title: "Delete environment",
45529
+ children: "×"
45530
+ }
45531
+ )
45532
+ ]
45173
45533
  },
45174
45534
  e.id
45175
45535
  )) }),
@@ -45400,16 +45760,11 @@ function EnvironmentBar({ inline = false }) {
45400
45760
  const environments = useStore((s) => s.environments);
45401
45761
  const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
45402
45762
  const setActiveEnvironment = useStore((s) => s.setActiveEnvironment);
45403
- const addEnvironment = useStore((s) => s.addEnvironment);
45404
45763
  const [showEditor, setShowEditor] = reactExports.useState(false);
45405
45764
  const [pendingEnvId, setPendingEnvId] = reactExports.useState(null);
45406
45765
  const envList = Object.values(environments);
45407
45766
  const activeEnv = activeEnvironmentId ? environments[activeEnvironmentId]?.data : null;
45408
45767
  const varCount = activeEnv?.variables.filter((v2) => v2.enabled).length ?? 0;
45409
- function handleNew() {
45410
- addEnvironment();
45411
- setShowEditor(true);
45412
- }
45413
45768
  async function handleEnvChange(id2) {
45414
45769
  if (id2) {
45415
45770
  const hasSecrets = environments[id2]?.data.variables.some((v2) => v2.enabled && v2.secret);
@@ -45460,14 +45815,6 @@ function EnvironmentBar({ inline = false }) {
45460
45815
  children: activeEnv ? "Edit" : "Manage"
45461
45816
  }
45462
45817
  ),
45463
- /* @__PURE__ */ jsxRuntimeExports.jsx(
45464
- "button",
45465
- {
45466
- onClick: handleNew,
45467
- className: "text-surface-400 hover:text-white transition-colors text-xs",
45468
- children: "+ New"
45469
- }
45470
- ),
45471
45818
  showEditor && /* @__PURE__ */ jsxRuntimeExports.jsx(EnvironmentEditor, { onClose: () => setShowEditor(false) })
45472
45819
  ] });
45473
45820
  if (inline) return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: controls });
@@ -48129,6 +48476,7 @@ function App() {
48129
48476
  const sidebarTab = useStore((s) => s.sidebarTab);
48130
48477
  const setSidebarTab = useStore((s) => s.setSidebarTab);
48131
48478
  const historyCount = useStore((s) => s.history.length);
48479
+ const addCollection = useStore((s) => s.addCollection);
48132
48480
  const addMockHit = useStore((s) => s.addMockHit);
48133
48481
  const activeMockId = useStore((s) => s.activeMockId);
48134
48482
  const theme2 = useStore((s) => s.theme);
@@ -48187,9 +48535,13 @@ function App() {
48187
48535
  /* @__PURE__ */ jsxRuntimeExports.jsx(RunnerModal, {}),
48188
48536
  /* @__PURE__ */ jsxRuntimeExports.jsx(CommandPalette, {}),
48189
48537
  docsModalOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(DocsGeneratorModal, { onClose: () => setDocsModalOpen(false) }),
48190
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "drag-region flex-shrink-0 bg-surface-950 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "no-drag text-[11px] font-medium tracking-widest select-none", style: { color: "var(--text-muted)" }, children: [
48538
+ window.electron.platform !== "win32" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "drag-region flex-shrink-0 bg-surface-950 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "no-drag text-[11px] font-medium tracking-widest select-none", style: { color: "var(--text-muted)" }, children: [
48191
48539
  "api ",
48192
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" })
48540
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
48541
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
48542
+ "v",
48543
+ "0.0.5"
48544
+ ] })
48193
48545
  ] }) }),
48194
48546
  /* @__PURE__ */ jsxRuntimeExports.jsx(Toolbar, { onOpenDocs: () => setDocsModalOpen(true) }),
48195
48547
  workspace ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 min-h-0", children: [
@@ -48237,6 +48589,15 @@ function App() {
48237
48589
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-surface-600", children: sidebarTab === "collections" ? "Collections" : sidebarTab === "history" ? "History" : sidebarTab === "mocks" ? "Mocks" : "Contracts" }),
48238
48590
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
48239
48591
  sidebarTab === "history" && historyCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] bg-surface-700 text-surface-400 rounded px-1.5 py-0.5", children: historyCount }),
48592
+ sidebarTab === "collections" && /* @__PURE__ */ jsxRuntimeExports.jsx(
48593
+ "button",
48594
+ {
48595
+ onClick: () => addCollection("New Collection"),
48596
+ title: "New collection",
48597
+ className: "text-surface-600 hover:text-surface-300 transition-colors text-sm leading-none px-0.5",
48598
+ children: "+"
48599
+ }
48600
+ ),
48240
48601
  /* @__PURE__ */ jsxRuntimeExports.jsx(
48241
48602
  "button",
48242
48603
  {