@testsmith/api-spector 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -905,6 +905,7 @@ function checkDCE$1() {
905
905
  reactDom.exports = reactDom_production;
906
906
  }
907
907
  var reactDomExports = reactDom.exports;
908
+ const ReactDOM$2 = /* @__PURE__ */ getDefaultExportFromCjs(reactDomExports);
908
909
  /**
909
910
  * @license React
910
911
  * react-dom-client.production.js
@@ -13413,7 +13414,7 @@ const useStore = create()(
13413
13414
  sessionVars: {},
13414
13415
  showGeneratorPanel: false,
13415
13416
  theme: localStorage.getItem("theme") ?? "dark",
13416
- zoom: Number(localStorage.getItem("zoom") ?? "1"),
13417
+ zoom: Number(localStorage.getItem("zoom") ?? "1.1"),
13417
13418
  history: [],
13418
13419
  sidebarTab: "collections",
13419
13420
  workspaceSettingsOpen: false,
@@ -13641,6 +13642,12 @@ const useStore = create()(
13641
13642
  s.collections[id2].data.tls = tls;
13642
13643
  s.collections[id2].dirty = true;
13643
13644
  }),
13645
+ updateCollectionAuthAndHeaders: (id2, auth, headers) => set2((s) => {
13646
+ if (!s.collections[id2]) return;
13647
+ s.collections[id2].data.auth = auth;
13648
+ s.collections[id2].data.headers = headers;
13649
+ s.collections[id2].dirty = true;
13650
+ }),
13644
13651
  // ── Folder CRUD ───────────────────────────────────────────────────────────
13645
13652
  addFolder: (collectionId, parentFolderId, name2) => set2((s) => {
13646
13653
  const col = s.collections[collectionId]?.data;
@@ -13760,6 +13767,37 @@ const useStore = create()(
13760
13767
  s.tabs.push(tab);
13761
13768
  s.activeTabId = tab.id;
13762
13769
  }),
13770
+ moveRequest: (srcCollectionId, requestId, destCollectionId, destFolderId, destIndex) => set2((s) => {
13771
+ const srcCol = s.collections[srcCollectionId]?.data;
13772
+ const destCol = s.collections[destCollectionId]?.data;
13773
+ if (!srcCol || !destCol) return;
13774
+ const req = srcCol.requests[requestId];
13775
+ if (!req) return;
13776
+ const srcFolder = findFolderContaining(srcCol.rootFolder, requestId);
13777
+ const srcIdx = srcFolder ? srcFolder.requestIds.indexOf(requestId) : -1;
13778
+ removeFromFolder(srcCol.rootFolder, requestId);
13779
+ s.collections[srcCollectionId].dirty = true;
13780
+ if (srcCollectionId !== destCollectionId) {
13781
+ delete srcCol.requests[requestId];
13782
+ destCol.requests[requestId] = req;
13783
+ const tab = s.tabs.find((t2) => t2.requestId === requestId);
13784
+ if (tab) tab.collectionId = destCollectionId;
13785
+ }
13786
+ const destFolder = findFolder(destCol.rootFolder, destFolderId);
13787
+ if (destFolder) {
13788
+ if (destIndex !== void 0) {
13789
+ let adjusted = destIndex;
13790
+ if (srcCollectionId === destCollectionId && srcFolder?.id === destFolder.id && srcIdx < destIndex) {
13791
+ adjusted--;
13792
+ }
13793
+ adjusted = Math.max(0, Math.min(adjusted, destFolder.requestIds.length));
13794
+ destFolder.requestIds.splice(adjusted, 0, requestId);
13795
+ } else {
13796
+ destFolder.requestIds.push(requestId);
13797
+ }
13798
+ }
13799
+ s.collections[destCollectionId].dirty = true;
13800
+ }),
13763
13801
  // ── Tags ──────────────────────────────────────────────────────────────────
13764
13802
  updateFolderTags: (collectionId, folderId, tags2) => set2((s) => {
13765
13803
  const col = s.collections[collectionId]?.data;
@@ -13804,16 +13842,12 @@ const useStore = create()(
13804
13842
  const state = useStore.getState();
13805
13843
  const colEntry = Object.values(state.collections).find((c) => c.data.requests[requestId]);
13806
13844
  if (!colEntry) return { auth: null, headers: [] };
13845
+ let inheritedAuth = colEntry.data.auth && colEntry.data.auth.type !== "none" ? colEntry.data.auth : null;
13846
+ let inheritedHeaders = colEntry.data.headers?.filter((h) => h.enabled && h.key) ?? [];
13807
13847
  const path = findFolderPath(colEntry.data.rootFolder, requestId);
13808
- let inheritedAuth = null;
13809
- let inheritedHeaders = [];
13810
13848
  for (const folder of path) {
13811
- if (folder.auth && folder.auth.type !== "none") {
13812
- inheritedAuth = folder.auth;
13813
- }
13814
- if (folder.headers && folder.headers.length > 0) {
13815
- inheritedHeaders = [...inheritedHeaders, ...folder.headers];
13816
- }
13849
+ if (folder.auth && folder.auth.type !== "none") inheritedAuth = folder.auth;
13850
+ if (folder.headers?.length) inheritedHeaders = [...inheritedHeaders, ...folder.headers];
13817
13851
  }
13818
13852
  return { auth: inheritedAuth, headers: inheritedHeaders };
13819
13853
  },
@@ -34587,24 +34621,80 @@ function parseVarTokens(str) {
34587
34621
  }
34588
34622
  return found;
34589
34623
  }
34590
- function VarInput({ value, onChange, className, wrapperClassName, ...rest }) {
34624
+ function PortalDropdown({
34625
+ pos,
34626
+ items: items2,
34627
+ activeIndex,
34628
+ mode,
34629
+ onPick
34630
+ }) {
34631
+ return ReactDOM$2.createPortal(
34632
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
34633
+ "ul",
34634
+ {
34635
+ style: { position: "fixed", top: pos.top, left: pos.left, minWidth: pos.minWidth, zIndex: 9999 },
34636
+ className: "max-h-52 overflow-y-auto bg-surface-800 border border-surface-600 rounded shadow-xl text-xs",
34637
+ children: items2.map((s, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34638
+ "button",
34639
+ {
34640
+ onMouseDown: (e) => {
34641
+ e.preventDefault();
34642
+ onPick(s);
34643
+ },
34644
+ className: `w-full text-left px-3 py-1.5 font-mono transition-colors ${i === activeIndex ? "bg-blue-600 text-white" : "hover:bg-surface-700"}`,
34645
+ children: mode === "var" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
34646
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500", children: "{{" }),
34647
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: s }),
34648
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500", children: "}}" })
34649
+ ] }) : s
34650
+ }
34651
+ ) }, s))
34652
+ }
34653
+ ),
34654
+ document.body
34655
+ );
34656
+ }
34657
+ function VarInput({ value, onChange, className, wrapperClassName, staticSuggestions, ...rest }) {
34591
34658
  const varNames = useVarNames();
34592
34659
  const varValues = useVarValues();
34593
34660
  const inputRef = reactExports.useRef(null);
34594
34661
  const [suggestions, setSuggestions] = reactExports.useState([]);
34662
+ const [suggestionMode, setSuggestionMode] = reactExports.useState("var");
34595
34663
  const [activeIndex, setActiveIndex] = reactExports.useState(-1);
34664
+ const [dropPos, setDropPos] = reactExports.useState(null);
34596
34665
  const [showPreview, setShowPreview] = reactExports.useState(false);
34666
+ reactExports.useEffect(() => {
34667
+ if (suggestions.length === 0) {
34668
+ setDropPos(null);
34669
+ return;
34670
+ }
34671
+ const el = inputRef.current;
34672
+ if (!el) return;
34673
+ const r = el.getBoundingClientRect();
34674
+ setDropPos({ top: r.bottom + 2, left: r.left, minWidth: r.width });
34675
+ }, [suggestions.length]);
34597
34676
  function detectQuery(val, cursor2) {
34598
34677
  const before = val.slice(0, cursor2);
34599
- const match = /\{\{(\w*)$/.exec(before);
34600
- if (!match) {
34678
+ const varMatch = /\{\{(\w*)$/.exec(before);
34679
+ if (varMatch) {
34680
+ const q = varMatch[1].toLowerCase();
34681
+ const filtered = varNames.filter((n) => n.toLowerCase().includes(q));
34682
+ setSuggestions(filtered);
34683
+ setSuggestionMode("var");
34684
+ setActiveIndex(-1);
34685
+ } else if (staticSuggestions) {
34686
+ const q = val.toLowerCase();
34687
+ const filtered = q ? staticSuggestions.filter((s) => s.toLowerCase().includes(q)).sort((a, b) => {
34688
+ const aP = a.toLowerCase().startsWith(q) ? 0 : 1;
34689
+ const bP = b.toLowerCase().startsWith(q) ? 0 : 1;
34690
+ return aP - bP;
34691
+ }).slice(0, 20) : staticSuggestions.slice(0, 20);
34692
+ setSuggestions(filtered);
34693
+ setSuggestionMode("static");
34694
+ setActiveIndex(-1);
34695
+ } else {
34601
34696
  setSuggestions([]);
34602
- return;
34603
34697
  }
34604
- const q = match[1].toLowerCase();
34605
- const filtered = varNames.filter((n) => n.toLowerCase().includes(q));
34606
- setSuggestions(filtered);
34607
- setActiveIndex(-1);
34608
34698
  }
34609
34699
  function handleChange(e) {
34610
34700
  const newVal = e.target.value;
@@ -34612,6 +34702,10 @@ function VarInput({ value, onChange, className, wrapperClassName, ...rest }) {
34612
34702
  onChange(newVal);
34613
34703
  detectQuery(newVal, cursor2);
34614
34704
  }
34705
+ function handleFocus(e) {
34706
+ detectQuery(value, e.target.selectionStart ?? value.length);
34707
+ rest.onFocus?.(e);
34708
+ }
34615
34709
  function handleKeyDown(e) {
34616
34710
  if (suggestions.length === 0) return;
34617
34711
  if (e.key === "ArrowDown") {
@@ -34627,21 +34721,28 @@ function VarInput({ value, onChange, className, wrapperClassName, ...rest }) {
34627
34721
  setSuggestions([]);
34628
34722
  }
34629
34723
  }
34630
- const apply = reactExports.useCallback((name2) => {
34724
+ const apply = reactExports.useCallback((suggestion) => {
34725
+ if (suggestionMode === "static") {
34726
+ onChange(suggestion);
34727
+ setSuggestions([]);
34728
+ return;
34729
+ }
34631
34730
  const el = inputRef.current;
34632
34731
  const cursor2 = el?.selectionStart ?? value.length;
34633
34732
  const before = value.slice(0, cursor2);
34634
34733
  const after = value.slice(cursor2);
34635
34734
  const match = /\{\{(\w*)$/.exec(before);
34636
34735
  if (!match) return;
34637
- const newVal = before.slice(0, match.index) + `{{${name2}}}` + after;
34638
- const newCursor = match.index + name2.length + 4;
34736
+ const newVal = before.slice(0, match.index) + `{{${suggestion}}}` + after;
34737
+ const newCursor = match.index + suggestion.length + 4;
34639
34738
  onChange(newVal);
34640
34739
  setSuggestions([]);
34641
34740
  requestAnimationFrame(() => el?.setSelectionRange(newCursor, newCursor));
34642
- }, [value, onChange]);
34643
- function handleBlur() {
34741
+ }, [suggestionMode, value, onChange]);
34742
+ function handleBlur(e) {
34644
34743
  setTimeout(() => setSuggestions([]), 150);
34744
+ setShowPreview(false);
34745
+ rest.onBlur?.(e);
34645
34746
  }
34646
34747
  const tokens = parseVarTokens(value);
34647
34748
  const hasVars = tokens.length > 0;
@@ -34649,6 +34750,7 @@ function VarInput({ value, onChange, className, wrapperClassName, ...rest }) {
34649
34750
  name: name2,
34650
34751
  resolved: varValues[name2] ?? null
34651
34752
  }));
34753
+ const { onFocus: _f, onBlur: _b, ...inputRest } = rest;
34652
34754
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
34653
34755
  "div",
34654
34756
  {
@@ -34663,26 +34765,22 @@ function VarInput({ value, onChange, className, wrapperClassName, ...rest }) {
34663
34765
  value,
34664
34766
  onChange: handleChange,
34665
34767
  onKeyDown: handleKeyDown,
34768
+ onFocus: handleFocus,
34666
34769
  onBlur: handleBlur,
34667
34770
  className: `w-full ${className ?? ""}`,
34668
- ...rest
34771
+ ...inputRest
34669
34772
  }
34670
34773
  ),
34671
- suggestions.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "absolute left-0 top-full mt-0.5 z-50 min-w-[160px] max-h-44 overflow-y-auto bg-surface-800 border border-surface-600 rounded shadow-xl text-xs", children: suggestions.map((name2, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
34672
- "button",
34774
+ suggestions.length > 0 && dropPos && /* @__PURE__ */ jsxRuntimeExports.jsx(
34775
+ PortalDropdown,
34673
34776
  {
34674
- onMouseDown: (e) => {
34675
- e.preventDefault();
34676
- apply(name2);
34677
- },
34678
- className: `w-full text-left px-3 py-1.5 font-mono transition-colors ${i === activeIndex ? "bg-blue-600 text-white" : "hover:bg-surface-700"}`,
34679
- children: [
34680
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500", children: "{{" }),
34681
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: name2 }),
34682
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500", children: "}}" })
34683
- ]
34777
+ pos: dropPos,
34778
+ items: suggestions,
34779
+ activeIndex,
34780
+ mode: suggestionMode,
34781
+ onPick: apply
34684
34782
  }
34685
- ) }, name2)) }),
34783
+ ),
34686
34784
  showPreview && suggestions.length === 0 && previewItems.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 top-full mt-0.5 z-50 bg-surface-800 border border-surface-600 rounded shadow-xl text-xs min-w-[220px] max-w-[420px]", children: previewItems.map(({ name: name2, resolved }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-baseline gap-2 px-3 py-1.5 border-b border-surface-700 last:border-0", children: [
34687
34785
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-blue-400 shrink-0", children: `{{${name2}}}` }),
34688
34786
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500 mx-0.5", children: "→" }),
@@ -34692,7 +34790,113 @@ function VarInput({ value, onChange, className, wrapperClassName, ...rest }) {
34692
34790
  }
34693
34791
  );
34694
34792
  }
34695
- function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "Value" }) {
34793
+ const HEADER_NAMES = [
34794
+ "Accept",
34795
+ "Authorization",
34796
+ "Content-Type",
34797
+ "Cache-Control",
34798
+ "Accept-Language",
34799
+ "Accept-Encoding",
34800
+ "Connection",
34801
+ "Content-Encoding",
34802
+ "Content-Length",
34803
+ "Cookie",
34804
+ "Host",
34805
+ "Origin",
34806
+ "Referer",
34807
+ "User-Agent",
34808
+ "X-Api-Key",
34809
+ "X-Auth-Token",
34810
+ "X-Correlation-ID",
34811
+ "X-Request-ID",
34812
+ "X-Forwarded-For",
34813
+ "X-Requested-With",
34814
+ "X-Tenant-ID",
34815
+ "If-Match",
34816
+ "If-None-Match",
34817
+ "If-Modified-Since",
34818
+ "If-Unmodified-Since",
34819
+ "If-Range",
34820
+ "Date",
34821
+ "Expect",
34822
+ "From",
34823
+ "Max-Forwards",
34824
+ "Pragma",
34825
+ "Proxy-Authorization",
34826
+ "Range",
34827
+ "TE",
34828
+ "Upgrade",
34829
+ "Via",
34830
+ "Warning"
34831
+ ];
34832
+ const HEADER_VALUE_SUGGESTIONS = {
34833
+ "accept": [
34834
+ "application/json",
34835
+ "application/xml",
34836
+ "text/html",
34837
+ "text/plain",
34838
+ "application/ld+json",
34839
+ "*/*"
34840
+ ],
34841
+ "content-type": [
34842
+ "application/json",
34843
+ "application/xml",
34844
+ "application/x-www-form-urlencoded",
34845
+ "multipart/form-data",
34846
+ "text/plain",
34847
+ "text/html",
34848
+ "text/xml",
34849
+ "application/octet-stream",
34850
+ "application/graphql",
34851
+ "application/ld+json"
34852
+ ],
34853
+ "cache-control": [
34854
+ "no-cache",
34855
+ "no-store",
34856
+ "max-age=0",
34857
+ "max-age=3600",
34858
+ "must-revalidate",
34859
+ "private",
34860
+ "public"
34861
+ ],
34862
+ "connection": [
34863
+ "keep-alive",
34864
+ "close",
34865
+ "upgrade"
34866
+ ],
34867
+ "accept-encoding": [
34868
+ "gzip",
34869
+ "deflate",
34870
+ "br",
34871
+ "gzip, deflate, br",
34872
+ "identity",
34873
+ "*"
34874
+ ],
34875
+ "accept-language": [
34876
+ "en-US",
34877
+ "en-US,en;q=0.9",
34878
+ "en",
34879
+ "*"
34880
+ ],
34881
+ "pragma": [
34882
+ "no-cache"
34883
+ ],
34884
+ "transfer-encoding": [
34885
+ "chunked",
34886
+ "gzip",
34887
+ "deflate",
34888
+ "compress",
34889
+ "identity"
34890
+ ],
34891
+ "upgrade": [
34892
+ "websocket",
34893
+ "HTTP/2.0"
34894
+ ]
34895
+ };
34896
+ function getValueSuggestions(headerName) {
34897
+ return HEADER_VALUE_SUGGESTIONS[headerName.toLowerCase()];
34898
+ }
34899
+ function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "Value", headerMode }) {
34696
34900
  const [descVisible, setDescVisible] = reactExports.useState(
34697
34901
  () => new Set(rows.map((r, i) => r.description ? i : -1).filter((i) => i >= 0))
34698
34902
  );
@@ -34746,7 +34950,8 @@ function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "V
34746
34950
  onChange: (v) => update(idx, { key: v }),
34747
34951
  placeholder: keyPlaceholder,
34748
34952
  wrapperClassName: "flex-1",
34749
- className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500 font-mono"
34953
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500 font-mono",
34954
+ staticSuggestions: headerMode ? HEADER_NAMES : void 0
34750
34955
  }
34751
34956
  ),
34752
34957
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -34756,7 +34961,8 @@ function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "V
34756
34961
  onChange: (v) => update(idx, { value: v }),
34757
34962
  placeholder: valuePlaceholder,
34758
34963
  wrapperClassName: "flex-1",
34759
- className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500 font-mono"
34964
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500 font-mono",
34965
+ staticSuggestions: headerMode ? getValueSuggestions(row.key) ?? void 0 : void 0
34760
34966
  }
34761
34967
  ),
34762
34968
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -34790,7 +34996,7 @@ function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "V
34790
34996
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: add2, className: "mt-1 text-surface-400 hover:text-white transition-colors", children: "+ Add" })
34791
34997
  ] });
34792
34998
  }
34793
- const AUTH_TYPES$1 = ["none", "bearer", "basic", "digest", "ntlm", "apikey"];
34999
+ const AUTH_TYPES$2 = ["none", "bearer", "basic", "digest", "ntlm", "apikey"];
34794
35000
  function FolderSettingsModal({ collectionId, folder, onClose }) {
34795
35001
  const updateFolder = useStore((s) => s.updateFolder);
34796
35002
  const [activeTab, setActiveTab] = reactExports.useState("auth");
@@ -34841,7 +35047,8 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
34841
35047
  rows: headers,
34842
35048
  onChange: setHeaders,
34843
35049
  keyPlaceholder: "Header-Name",
34844
- valuePlaceholder: "value"
35050
+ valuePlaceholder: "value",
35051
+ headerMode: true
34845
35052
  }
34846
35053
  )
34847
35054
  ] }),
@@ -34874,7 +35081,7 @@ function FolderAuthPanel({ auth, onChange }) {
34874
35081
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600", children: "Auth configured here is inherited by all requests in this folder unless the request overrides it with its own non-none auth type." }),
34875
35082
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
34876
35083
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-400", children: "Type:" }),
34877
- AUTH_TYPES$1.map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1 cursor-pointer", children: [
35084
+ AUTH_TYPES$2.map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1 cursor-pointer", children: [
34878
35085
  /* @__PURE__ */ jsxRuntimeExports.jsx(
34879
35086
  "input",
34880
35087
  {
@@ -35020,14 +35227,23 @@ function FolderAuthPanel({ auth, onChange }) {
35020
35227
  ] }) })
35021
35228
  ] });
35022
35229
  }
35230
+ const AUTH_TYPES$1 = ["none", "bearer", "basic", "digest", "ntlm", "apikey"];
35023
35231
  function CollectionSettingsModal({ collection, onClose }) {
35024
35232
  const updateCollectionTls = useStore((s) => s.updateCollectionTls);
35233
+ const updateCollectionAuthAndHeaders = useStore((s) => s.updateCollectionAuthAndHeaders);
35234
+ const [activeTab, setActiveTab] = reactExports.useState("auth");
35235
+ const [auth, setAuth] = reactExports.useState(collection.auth ?? { type: "none" });
35236
+ const [headers, setHeaders] = reactExports.useState(collection.headers ?? []);
35025
35237
  const existing = collection.tls;
35026
35238
  const [caCertPath, setCaCertPath] = reactExports.useState(existing?.caCertPath ?? "");
35027
35239
  const [clientCertPath, setClientCertPath] = reactExports.useState(existing?.clientCertPath ?? "");
35028
35240
  const [clientKeyPath, setClientKeyPath] = reactExports.useState(existing?.clientKeyPath ?? "");
35029
35241
  const [rejectUnauthorized, setRejectUnauthorized] = reactExports.useState(existing?.rejectUnauthorized !== false);
35242
+ function patchAuth(patch) {
35243
+ setAuth((prev) => ({ ...prev, ...patch }));
35244
+ }
35030
35245
  function save() {
35246
+ updateCollectionAuthAndHeaders(collection.id, auth, headers);
35031
35247
  const hasAny = caCertPath.trim() || clientCertPath.trim() || clientKeyPath.trim();
35032
35248
  updateCollectionTls(collection.id, hasAny ? {
35033
35249
  caCertPath: caCertPath.trim() || void 0,
@@ -35045,65 +35261,90 @@ function CollectionSettingsModal({ collection, onClose }) {
35045
35261
  children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
35046
35262
  "div",
35047
35263
  {
35048
- className: "w-[560px] bg-surface-900 border border-surface-800 rounded-lg shadow-2xl flex flex-col max-h-[80vh]",
35264
+ className: "w-[600px] bg-surface-900 border border-surface-800 rounded-lg shadow-2xl flex flex-col max-h-[80vh]",
35049
35265
  onClick: (e) => e.stopPropagation(),
35050
35266
  children: [
35051
35267
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-4 py-2.5 border-b border-surface-800 shrink-0", children: [
35052
35268
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
35053
35269
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold", children: "Collection settings" }),
35054
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-0.5", children: collection.name })
35270
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-0.5", children: [
35271
+ collection.name,
35272
+ " — auth and headers inherited by all requests in this collection"
35273
+ ] })
35055
35274
  ] }),
35056
35275
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
35057
35276
  ] }),
35058
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 flex-1 overflow-y-auto text-xs flex flex-col gap-3", children: [
35059
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: "TLS settings override the workspace-level configuration for every request in this collection. Leave all paths empty to inherit from the workspace." }),
35060
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
35061
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "CA Certificate path" }),
35062
- /* @__PURE__ */ jsxRuntimeExports.jsx(
35063
- "input",
35064
- {
35065
- value: caCertPath,
35066
- onChange: (e) => setCaCertPath(e.target.value),
35067
- placeholder: "/path/to/ca.crt (leave empty to inherit)",
35068
- className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
35069
- }
35070
- )
35071
- ] }),
35072
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
35073
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Client certificate path" }),
35074
- /* @__PURE__ */ jsxRuntimeExports.jsx(
35075
- "input",
35076
- {
35077
- value: clientCertPath,
35078
- onChange: (e) => setClientCertPath(e.target.value),
35079
- placeholder: "/path/to/client.crt (leave empty to inherit)",
35080
- className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
35081
- }
35082
- )
35083
- ] }),
35084
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
35085
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Client key path" }),
35086
- /* @__PURE__ */ jsxRuntimeExports.jsx(
35087
- "input",
35088
- {
35089
- value: clientKeyPath,
35090
- onChange: (e) => setClientKeyPath(e.target.value),
35091
- placeholder: "/path/to/client.key (leave empty to inherit)",
35092
- className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
35093
- }
35094
- )
35095
- ] }),
35096
- /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
35097
- /* @__PURE__ */ jsxRuntimeExports.jsx(
35098
- "input",
35099
- {
35100
- type: "checkbox",
35101
- checked: rejectUnauthorized,
35102
- onChange: (e) => setRejectUnauthorized(e.target.checked),
35103
- className: "accent-blue-500"
35104
- }
35105
- ),
35106
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Reject unauthorized / self-signed certificates" })
35277
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-4 shrink-0", children: ["auth", "headers", "tls"].map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsx(
35278
+ "button",
35279
+ {
35280
+ onClick: () => setActiveTab(t2),
35281
+ className: `px-3 py-1.5 text-xs transition-colors border-b-2 -mb-px capitalize ${activeTab === t2 ? "border-blue-500 text-white" : "border-transparent text-surface-400 hover:text-white"}`,
35282
+ children: t2 === "tls" ? "TLS" : t2
35283
+ },
35284
+ t2
35285
+ )) }),
35286
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 flex-1 overflow-y-auto text-xs", children: [
35287
+ activeTab === "auth" && /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionAuthPanel, { auth, onChange: patchAuth }),
35288
+ activeTab === "headers" && /* @__PURE__ */ jsxRuntimeExports.jsx(
35289
+ KVTable,
35290
+ {
35291
+ rows: headers,
35292
+ onChange: setHeaders,
35293
+ keyPlaceholder: "Header-Name",
35294
+ valuePlaceholder: "value",
35295
+ headerMode: true
35296
+ }
35297
+ ),
35298
+ activeTab === "tls" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
35299
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: "TLS settings override the workspace-level configuration for every request in this collection. Leave all paths empty to inherit from the workspace." }),
35300
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
35301
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "CA Certificate path" }),
35302
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35303
+ "input",
35304
+ {
35305
+ value: caCertPath,
35306
+ onChange: (e) => setCaCertPath(e.target.value),
35307
+ placeholder: "/path/to/ca.crt (leave empty to inherit)",
35308
+ className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
35309
+ }
35310
+ )
35311
+ ] }),
35312
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
35313
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Client certificate path" }),
35314
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35315
+ "input",
35316
+ {
35317
+ value: clientCertPath,
35318
+ onChange: (e) => setClientCertPath(e.target.value),
35319
+ placeholder: "/path/to/client.crt (leave empty to inherit)",
35320
+ className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
35321
+ }
35322
+ )
35323
+ ] }),
35324
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
35325
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Client key path" }),
35326
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35327
+ "input",
35328
+ {
35329
+ value: clientKeyPath,
35330
+ onChange: (e) => setClientKeyPath(e.target.value),
35331
+ placeholder: "/path/to/client.key (leave empty to inherit)",
35332
+ className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
35333
+ }
35334
+ )
35335
+ ] }),
35336
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
35337
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35338
+ "input",
35339
+ {
35340
+ type: "checkbox",
35341
+ checked: rejectUnauthorized,
35342
+ onChange: (e) => setRejectUnauthorized(e.target.checked),
35343
+ className: "accent-blue-500"
35344
+ }
35345
+ ),
35346
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Reject unauthorized / self-signed certificates" })
35347
+ ] })
35107
35348
  ] })
35108
35349
  ] }),
35109
35350
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 px-4 py-3 border-t border-surface-800 shrink-0", children: [
@@ -35130,6 +35371,160 @@ function CollectionSettingsModal({ collection, onClose }) {
35130
35371
  }
35131
35372
  );
35132
35373
  }
35374
+ function CollectionAuthPanel({ auth, onChange }) {
35375
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
35376
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600", children: "Auth configured here is inherited by all requests in this collection unless a folder or request overrides it with its own non-none auth type." }),
35377
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
35378
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-400", children: "Type:" }),
35379
+ AUTH_TYPES$1.map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1 cursor-pointer", children: [
35380
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35381
+ "input",
35382
+ {
35383
+ type: "radio",
35384
+ value: t2,
35385
+ checked: auth.type === t2,
35386
+ onChange: () => onChange({ type: t2 }),
35387
+ className: "accent-blue-500"
35388
+ }
35389
+ ),
35390
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: auth.type === t2 ? "text-white" : "text-surface-400", children: t2 })
35391
+ ] }, t2))
35392
+ ] }),
35393
+ auth.type === "bearer" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
35394
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Token" }),
35395
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35396
+ "input",
35397
+ {
35398
+ value: auth.token ?? "",
35399
+ onChange: (e) => onChange({ token: e.target.value }),
35400
+ placeholder: "Bearer token",
35401
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 font-mono focus:outline-none focus:border-blue-500"
35402
+ }
35403
+ )
35404
+ ] }),
35405
+ (auth.type === "basic" || auth.type === "digest") && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
35406
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
35407
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Username" }),
35408
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35409
+ "input",
35410
+ {
35411
+ value: auth.username ?? "",
35412
+ onChange: (e) => onChange({ username: e.target.value }),
35413
+ className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500"
35414
+ }
35415
+ )
35416
+ ] }),
35417
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
35418
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Password" }),
35419
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35420
+ "input",
35421
+ {
35422
+ type: "password",
35423
+ value: auth.password ?? "",
35424
+ onChange: (e) => onChange({ password: e.target.value }),
35425
+ className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 font-mono focus:outline-none focus:border-blue-500"
35426
+ }
35427
+ )
35428
+ ] })
35429
+ ] }),
35430
+ auth.type === "ntlm" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
35431
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
35432
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
35433
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Username" }),
35434
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35435
+ "input",
35436
+ {
35437
+ value: auth.username ?? "",
35438
+ onChange: (e) => onChange({ username: e.target.value }),
35439
+ className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500"
35440
+ }
35441
+ )
35442
+ ] }),
35443
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
35444
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Password" }),
35445
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35446
+ "input",
35447
+ {
35448
+ type: "password",
35449
+ value: auth.password ?? "",
35450
+ onChange: (e) => onChange({ password: e.target.value }),
35451
+ className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 font-mono focus:outline-none focus:border-blue-500"
35452
+ }
35453
+ )
35454
+ ] })
35455
+ ] }),
35456
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
35457
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
35458
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Domain" }),
35459
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35460
+ "input",
35461
+ {
35462
+ value: auth.ntlmDomain ?? "",
35463
+ onChange: (e) => onChange({ ntlmDomain: e.target.value }),
35464
+ placeholder: "WORKGROUP",
35465
+ className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500"
35466
+ }
35467
+ )
35468
+ ] }),
35469
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
35470
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Workstation" }),
35471
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35472
+ "input",
35473
+ {
35474
+ value: auth.ntlmWorkstation ?? "",
35475
+ onChange: (e) => onChange({ ntlmWorkstation: e.target.value }),
35476
+ placeholder: "MACHINE",
35477
+ className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500"
35478
+ }
35479
+ )
35480
+ ] })
35481
+ ] })
35482
+ ] }),
35483
+ auth.type === "apikey" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
35484
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
35485
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Key name" }),
35486
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35487
+ "input",
35488
+ {
35489
+ value: auth.apiKeyName ?? "X-API-Key",
35490
+ onChange: (e) => onChange({ apiKeyName: e.target.value }),
35491
+ className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500"
35492
+ }
35493
+ )
35494
+ ] }),
35495
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
35496
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "In" }),
35497
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
35498
+ "select",
35499
+ {
35500
+ value: auth.apiKeyIn ?? "header",
35501
+ onChange: (e) => onChange({ apiKeyIn: e.target.value }),
35502
+ className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500",
35503
+ children: [
35504
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "header", children: "Header" }),
35505
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "query", children: "Query" })
35506
+ ]
35507
+ }
35508
+ )
35509
+ ] }),
35510
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
35511
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Value" }),
35512
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35513
+ "input",
35514
+ {
35515
+ value: auth.apiKeyValue ?? "",
35516
+ onChange: (e) => onChange({ apiKeyValue: e.target.value }),
35517
+ placeholder: "API key value",
35518
+ className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 font-mono focus:outline-none focus:border-blue-500"
35519
+ }
35520
+ )
35521
+ ] })
35522
+ ] }) })
35523
+ ] });
35524
+ }
35525
+ const DragCtx = reactExports.createContext({ dragging: null, setDragging: () => {
35526
+ }, onDrop: () => {
35527
+ } });
35133
35528
  function InlineEdit({
35134
35529
  value,
35135
35530
  onCommit,
@@ -35377,16 +35772,23 @@ function CollectionTree() {
35377
35772
  const updateRequestTags = useStore((s) => s.updateRequestTags);
35378
35773
  const updateRequest = useStore((s) => s.updateRequest);
35379
35774
  const openRunner = useStore((s) => s.openRunner);
35775
+ const moveRequest = useStore((s) => s.moveRequest);
35380
35776
  const colList = Object.values(collections);
35381
35777
  const [pendingConfirm, setPendingConfirm] = reactExports.useState(null);
35382
35778
  const [newRequestId, setNewRequestId] = reactExports.useState(null);
35779
+ const [dragging, setDragging] = reactExports.useState(null);
35383
35780
  function confirmThen(message, action) {
35384
35781
  setPendingConfirm({ message, onConfirm: () => {
35385
35782
  action();
35386
35783
  setPendingConfirm(null);
35387
35784
  } });
35388
35785
  }
35389
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 select-none", children: [
35786
+ function onDrop(destCollectionId, destFolderId, destIndex) {
35787
+ if (!dragging) return;
35788
+ moveRequest(dragging.collectionId, dragging.requestId, destCollectionId, destFolderId, destIndex);
35789
+ setDragging(null);
35790
+ }
35791
+ 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: [
35390
35792
  pendingConfirm && /* @__PURE__ */ jsxRuntimeExports.jsx(
35391
35793
  ConfirmDialog,
35392
35794
  {
@@ -35431,7 +35833,7 @@ function CollectionTree() {
35431
35833
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "pt-1", children: "or import from Postman / OpenAPI above." })
35432
35834
  ] })
35433
35835
  ] })
35434
- ] });
35836
+ ] }) });
35435
35837
  }
35436
35838
  function CollectionNode({
35437
35839
  col,
@@ -35462,6 +35864,8 @@ function CollectionNode({
35462
35864
  const [renaming, setRenaming] = reactExports.useState(false);
35463
35865
  const [showSettings, setShowSettings] = reactExports.useState(false);
35464
35866
  const [expandCtrl, setExpandCtrl] = reactExports.useState({ value: true, seq: 0 });
35867
+ const [dropOver, setDropOver] = reactExports.useState(false);
35868
+ const dragCtx = reactExports.useContext(DragCtx);
35465
35869
  function expandAll() {
35466
35870
  setExpandCtrl((c) => ({ value: true, seq: c.seq + 1 }));
35467
35871
  }
@@ -35472,11 +35876,21 @@ function CollectionNode({
35472
35876
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
35473
35877
  "div",
35474
35878
  {
35475
- className: `group flex items-start gap-1 px-2 py-1.5 cursor-pointer hover:bg-surface-800 transition-colors ${isActive ? "text-[var(--text-primary)]" : "text-surface-400"}`,
35879
+ className: `group flex items-start gap-1 px-2 py-1.5 cursor-pointer hover:bg-surface-800 transition-colors ${isActive ? "text-[var(--text-primary)]" : "text-surface-400"} ${dropOver ? "outline outline-1 outline-blue-500 rounded" : ""}`,
35476
35880
  onClick: () => {
35477
35881
  onSelectCollection();
35478
35882
  setExpanded((e) => !e);
35479
35883
  },
35884
+ onDragOver: dragCtx.dragging ? (e) => {
35885
+ e.preventDefault();
35886
+ setDropOver(true);
35887
+ } : void 0,
35888
+ onDragLeave: () => setDropOver(false),
35889
+ onDrop: (e) => {
35890
+ e.preventDefault();
35891
+ setDropOver(false);
35892
+ dragCtx.onDrop(col.id, col.rootFolder.id);
35893
+ },
35480
35894
  children: [
35481
35895
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] w-3 text-center shrink-0 mt-0.5", children: expanded ? "▾" : "▸" }),
35482
35896
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0", children: renaming ? /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -35560,6 +35974,8 @@ function FolderRow({
35560
35974
  const [renaming, setRenaming] = reactExports.useState(false);
35561
35975
  const [showSettings, setShowSettings] = reactExports.useState(false);
35562
35976
  const [addingTag, setAddingTag] = reactExports.useState(false);
35977
+ const [dropOver, setDropOver] = reactExports.useState(false);
35978
+ const dragCtx = reactExports.useContext(DragCtx);
35563
35979
  const tags2 = folder.tags ?? [];
35564
35980
  const indent = depth * 12 + 8;
35565
35981
  const hasInheritedConfig = folder.auth && folder.auth.type !== "none" || folder.headers && folder.headers.length > 0;
@@ -35567,9 +35983,19 @@ function FolderRow({
35567
35983
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
35568
35984
  "div",
35569
35985
  {
35570
- className: "group flex items-start gap-1 py-1 hover:bg-surface-800 transition-colors cursor-pointer text-surface-400",
35986
+ 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" : ""}`,
35571
35987
  style: { paddingLeft: indent },
35572
35988
  onClick: () => setExpanded((e) => !e),
35989
+ onDragOver: dragCtx.dragging ? (e) => {
35990
+ e.preventDefault();
35991
+ setDropOver(true);
35992
+ } : void 0,
35993
+ onDragLeave: () => setDropOver(false),
35994
+ onDrop: (e) => {
35995
+ e.preventDefault();
35996
+ setDropOver(false);
35997
+ dragCtx.onDrop(collectionId, folder.id);
35998
+ },
35573
35999
  children: [
35574
36000
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] w-3 text-center shrink-0 mt-0.5", children: expanded ? "▾" : "▸" }),
35575
36001
  /* @__PURE__ */ jsxRuntimeExports.jsx(FolderIcon, { className: `shrink-0 mt-0.5 ${hasInheritedConfig ? "text-blue-500" : "text-amber-600"}` }),
@@ -35689,13 +36115,16 @@ function FolderContents({
35689
36115
  },
35690
36116
  sub.id
35691
36117
  )),
35692
- folder.requestIds.map((reqId) => {
36118
+ folder.requestIds.map((reqId, reqIndex) => {
35693
36119
  const req = requests[reqId];
35694
36120
  if (!req) return null;
35695
36121
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
35696
36122
  RequestRow,
35697
36123
  {
35698
36124
  reqId: req.id,
36125
+ collectionId,
36126
+ folderId: folder.id,
36127
+ reqIndex,
35699
36128
  name: req.name,
35700
36129
  method: req.method,
35701
36130
  hookType: req.hookType,
@@ -35728,7 +36157,10 @@ const HOOK_COLORS = {
35728
36157
  afterAll: "bg-cyan-800 text-white"
35729
36158
  };
35730
36159
  function RequestRow({
35731
- reqId: _reqId,
36160
+ reqId,
36161
+ collectionId,
36162
+ folderId,
36163
+ reqIndex,
35732
36164
  name: name2,
35733
36165
  method,
35734
36166
  hookType,
@@ -35745,6 +36177,8 @@ function RequestRow({
35745
36177
  }) {
35746
36178
  const [renaming, setRenaming] = reactExports.useState(autoRename);
35747
36179
  const [addingTag, setAddingTag] = reactExports.useState(false);
36180
+ const [dropPos, setDropPos] = reactExports.useState(null);
36181
+ const dragCtx = reactExports.useContext(DragCtx);
35748
36182
  const hookMenuItems = [
35749
36183
  ...["beforeAll", "before", "after", "afterAll"].map((ht) => ({
35750
36184
  type: "item",
@@ -35752,55 +36186,84 @@ function RequestRow({
35752
36186
  onClick: () => onSetHookType(hookType === ht ? void 0 : ht)
35753
36187
  }))
35754
36188
  ];
35755
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
35756
- "div",
35757
- {
35758
- 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"}`,
35759
- style: { paddingLeft: indent },
35760
- onClick: onSelect,
35761
- onDoubleClick: () => setRenaming(true),
35762
- children: [
35763
- /* @__PURE__ */ jsxRuntimeExports.jsx(MethodBadge, { method, size: "xs" }),
35764
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
35765
- renaming ? /* @__PURE__ */ jsxRuntimeExports.jsx(
35766
- InlineEdit,
35767
- {
35768
- value: name2,
35769
- onCommit: (v) => {
35770
- onRename(v);
35771
- setRenaming(false);
35772
- },
35773
- onCancel: () => setRenaming(false),
35774
- className: "w-full text-xs"
35775
- }
35776
- ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 min-w-0", children: [
35777
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs truncate", children: name2 }),
35778
- 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() })
36189
+ function handleDragOver(e) {
36190
+ if (!dragCtx.dragging || dragCtx.dragging.requestId === reqId) return;
36191
+ e.preventDefault();
36192
+ const rect = e.currentTarget.getBoundingClientRect();
36193
+ setDropPos(e.clientY < rect.top + rect.height / 2 ? "before" : "after");
36194
+ }
36195
+ function handleDrop(e) {
36196
+ e.preventDefault();
36197
+ e.stopPropagation();
36198
+ const insertIndex = dropPos === "before" ? reqIndex : reqIndex + 1;
36199
+ dragCtx.onDrop(collectionId, folderId, insertIndex);
36200
+ setDropPos(null);
36201
+ }
36202
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
36203
+ dropPos === "before" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 inset-x-0 h-0.5 bg-blue-500 z-10 pointer-events-none" }),
36204
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
36205
+ "div",
36206
+ {
36207
+ draggable: true,
36208
+ 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"}`,
36209
+ style: { paddingLeft: indent },
36210
+ onClick: onSelect,
36211
+ onDoubleClick: () => setRenaming(true),
36212
+ onDragStart: (e) => {
36213
+ e.dataTransfer.effectAllowed = "move";
36214
+ dragCtx.setDragging({ requestId: reqId, collectionId });
36215
+ },
36216
+ onDragEnd: () => {
36217
+ dragCtx.setDragging(null);
36218
+ setDropPos(null);
36219
+ },
36220
+ onDragOver: handleDragOver,
36221
+ onDragLeave: () => setDropPos(null),
36222
+ onDrop: handleDrop,
36223
+ children: [
36224
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MethodBadge, { method, size: "xs" }),
36225
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
36226
+ renaming ? /* @__PURE__ */ jsxRuntimeExports.jsx(
36227
+ InlineEdit,
36228
+ {
36229
+ value: name2,
36230
+ onCommit: (v) => {
36231
+ onRename(v);
36232
+ setRenaming(false);
36233
+ },
36234
+ onCancel: () => setRenaming(false),
36235
+ className: "w-full text-xs"
36236
+ }
36237
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 min-w-0", children: [
36238
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs truncate", children: name2 }),
36239
+ 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() })
36240
+ ] }),
36241
+ (tags2.length > 0 || addingTag) && /* @__PURE__ */ jsxRuntimeExports.jsx(
36242
+ TagChips,
36243
+ {
36244
+ tags: tags2,
36245
+ onRemove: (tag) => onUpdateTags(tags2.filter((t2) => t2 !== tag)),
36246
+ onAdd: (tag) => onUpdateTags([...tags2, tag]),
36247
+ forceAdding: addingTag,
36248
+ onDoneAdding: () => setAddingTag(false)
36249
+ }
36250
+ )
35779
36251
  ] }),
35780
- (tags2.length > 0 || addingTag) && /* @__PURE__ */ jsxRuntimeExports.jsx(
35781
- TagChips,
35782
- {
35783
- tags: tags2,
35784
- onRemove: (tag) => onUpdateTags(tags2.filter((t2) => t2 !== tag)),
35785
- onAdd: (tag) => onUpdateTags([...tags2, tag]),
35786
- forceAdding: addingTag,
35787
- onDoneAdding: () => setAddingTag(false)
35788
- }
35789
- )
35790
- ] }),
35791
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DotsBtn, { items: [
35792
- { type: "item", label: "Rename", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}), onClick: () => setRenaming(true) },
35793
- { type: "item", label: "Duplicate", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: onDuplicate },
35794
- { type: "item", label: "Add tag", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TagIcon, {}), onClick: () => setAddingTag(true) },
35795
- { type: "separator" },
35796
- { type: "header", label: "Hook type" },
35797
- ...hookMenuItems,
35798
- { type: "separator" },
35799
- { type: "item", label: "Delete", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDelete }
35800
- ] }) })
35801
- ]
35802
- }
35803
- );
36252
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DotsBtn, { items: [
36253
+ { type: "item", label: "Rename", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}), onClick: () => setRenaming(true) },
36254
+ { type: "item", label: "Duplicate", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: onDuplicate },
36255
+ { type: "item", label: "Add tag", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TagIcon, {}), onClick: () => setAddingTag(true) },
36256
+ { type: "separator" },
36257
+ { type: "header", label: "Hook type" },
36258
+ ...hookMenuItems,
36259
+ { type: "separator" },
36260
+ { type: "item", label: "Delete", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDelete }
36261
+ ] }) }),
36262
+ dropPos === "after" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 inset-x-0 h-0.5 bg-blue-500 z-10 pointer-events-none" })
36263
+ ]
36264
+ }
36265
+ )
36266
+ ] });
35804
36267
  }
35805
36268
  function DotsHorizontalIcon() {
35806
36269
  return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-3.5 h-3.5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 10a2 2 0 11-4 0 2 2 0 014 0zm6 0a2 2 0 11-4 0 2 2 0 014 0zm6 0a2 2 0 11-4 0 2 2 0 014 0z" }) });
@@ -35862,7 +36325,8 @@ function HeadersTab({ request, onChange }) {
35862
36325
  rows: request.headers,
35863
36326
  onChange: (rows) => onChange({ headers: rows }),
35864
36327
  keyPlaceholder: "Header-Name",
35865
- valuePlaceholder: "value"
36328
+ valuePlaceholder: "value",
36329
+ headerMode: true
35866
36330
  }
35867
36331
  );
35868
36332
  }
@@ -53464,7 +53928,7 @@ function RunnerModal() {
53464
53928
  {
53465
53929
  onClick: run,
53466
53930
  disabled: runnerRunning,
53467
- className: "px-4 py-1.5 text-xs bg-emerald-700 hover:bg-emerald-600 disabled:bg-surface-800 disabled:text-surface-400 rounded font-medium transition-colors flex items-center gap-1.5 whitespace-nowrap",
53931
+ className: "px-4 py-1.5 text-xs text-white bg-emerald-700 hover:bg-emerald-600 disabled:bg-surface-800 disabled:text-surface-400 rounded font-medium transition-colors flex items-center gap-1.5 whitespace-nowrap",
53468
53932
  children: [
53469
53933
  /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-3.5 h-3.5", 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" }) }),
53470
53934
  runnerRunning ? "Running…" : "Run"
@@ -53572,7 +54036,7 @@ function RunnerModal() {
53572
54036
  },
53573
54037
  idx
53574
54038
  )) }) }) }),
53575
- summary && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 px-4 py-2 border-t border-surface-800 bg-surface-950/50 flex-shrink-0 text-xs", children: [
54039
+ 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: [
53576
54040
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400 font-medium", children: [
53577
54041
  summary.passed,
53578
54042
  " passed"
@@ -56278,7 +56742,7 @@ function App() {
56278
56742
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
56279
56743
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
56280
56744
  "v",
56281
- "0.1.5"
56745
+ "0.1.6"
56282
56746
  ] }),
56283
56747
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
56284
56748
  /* @__PURE__ */ jsxRuntimeExports.jsx(