@testsmith/api-spector 0.0.9 → 0.1.1

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.
@@ -13374,6 +13374,14 @@ function removeFolderById(parent, folderId) {
13374
13374
  }
13375
13375
  return false;
13376
13376
  }
13377
+ function findFolderParent(root2, folderId) {
13378
+ for (const sub of root2.folders) {
13379
+ if (sub.id === folderId) return root2;
13380
+ const found = findFolderParent(sub, folderId);
13381
+ if (found) return found;
13382
+ }
13383
+ return null;
13384
+ }
13377
13385
  function findFolderPath(root2, requestId) {
13378
13386
  if (root2.requestIds.includes(requestId)) return [root2];
13379
13387
  for (const sub of root2.folders) {
@@ -13579,6 +13587,34 @@ const useStore = create()(
13579
13587
  s.workspace.collections = s.workspace.collections.map((p) => p === oldRelPath ? newRelPath : p);
13580
13588
  }
13581
13589
  }),
13590
+ duplicateCollection: (id2) => set2((s) => {
13591
+ const entry = s.collections[id2];
13592
+ if (!entry) return;
13593
+ const orig = entry.data;
13594
+ const existingNames = Object.values(s.collections).map((c) => c.data.name);
13595
+ const newName = uniqueName(orig.name + " (copy)", existingNames);
13596
+ const newId = v4();
13597
+ const copy = JSON.parse(JSON.stringify(orig));
13598
+ copy.id = newId;
13599
+ copy.name = newName;
13600
+ const reqIdMap = {};
13601
+ Object.keys(copy.requests).forEach((oldReqId) => {
13602
+ const newReqId = v4();
13603
+ reqIdMap[oldReqId] = newReqId;
13604
+ copy.requests[newReqId] = { ...copy.requests[oldReqId], id: newReqId };
13605
+ delete copy.requests[oldReqId];
13606
+ });
13607
+ function remapFolder(f) {
13608
+ f.id = v4();
13609
+ f.requestIds = f.requestIds.map((rid) => reqIdMap[rid] ?? rid);
13610
+ f.folders.forEach(remapFolder);
13611
+ }
13612
+ remapFolder(copy.rootFolder);
13613
+ const relPath = colRelPath(newName, newId);
13614
+ s.collections[newId] = { relPath, data: copy, dirty: true };
13615
+ s.activeCollectionId = newId;
13616
+ if (s.workspace) s.workspace.collections.push(relPath);
13617
+ }),
13582
13618
  deleteCollection: (id2) => set2((s) => {
13583
13619
  const relPath = s.collections[id2]?.relPath;
13584
13620
  delete s.collections[id2];
@@ -13625,6 +13661,32 @@ const useStore = create()(
13625
13661
  s.collections[collectionId].dirty = true;
13626
13662
  }
13627
13663
  }),
13664
+ duplicateFolder: (collectionId, folderId) => set2((s) => {
13665
+ const col = s.collections[collectionId]?.data;
13666
+ if (!col) return;
13667
+ const orig = findFolder(col.rootFolder, folderId);
13668
+ if (!orig) return;
13669
+ const copy = JSON.parse(JSON.stringify(orig));
13670
+ copy.name = orig.name + " (copy)";
13671
+ function remapRequests(f) {
13672
+ f.requestIds = f.requestIds.map((oldId) => {
13673
+ const newId = v4();
13674
+ col.requests[newId] = { ...JSON.parse(JSON.stringify(col.requests[oldId])), id: newId };
13675
+ return newId;
13676
+ });
13677
+ f.folders.forEach(remapRequests);
13678
+ }
13679
+ remapRequests(copy);
13680
+ function reIdFolders(f) {
13681
+ f.id = v4();
13682
+ f.folders.forEach(reIdFolders);
13683
+ }
13684
+ reIdFolders(copy);
13685
+ const parent = findFolderParent(col.rootFolder, folderId) ?? col.rootFolder;
13686
+ const idx = parent.folders.findIndex((f) => f.id === folderId);
13687
+ parent.folders.splice(idx + 1, 0, copy);
13688
+ s.collections[collectionId].dirty = true;
13689
+ }),
13628
13690
  deleteFolder: (collectionId, folderId) => set2((s) => {
13629
13691
  const col = s.collections[collectionId]?.data;
13630
13692
  if (!col) return;
@@ -13921,7 +13983,7 @@ const useStore = create()(
13921
13983
  })
13922
13984
  }))
13923
13985
  );
13924
- const { electron: electron$l } = window;
13986
+ const { electron: electron$m } = window;
13925
13987
  function useAutoSave() {
13926
13988
  const collections = useStore((s) => s.collections);
13927
13989
  useStore((s) => s.environments);
@@ -13937,7 +13999,7 @@ function useAutoSave() {
13937
13999
  for (const { relPath, data, dirty } of dirtyCollections) {
13938
14000
  if (!dirty) continue;
13939
14001
  try {
13940
- await electron$l.saveCollection(relPath, data);
14002
+ await electron$m.saveCollection(relPath, data);
13941
14003
  markCollectionClean(data.id);
13942
14004
  } catch (e) {
13943
14005
  console.error("Auto-save failed for", relPath, e);
@@ -13953,7 +14015,7 @@ function useAutoSave() {
13953
14015
  if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
13954
14016
  wsTimerRef.current = setTimeout(async () => {
13955
14017
  try {
13956
- await electron$l.saveWorkspace(workspace);
14018
+ await electron$m.saveWorkspace(workspace);
13957
14019
  } catch {
13958
14020
  }
13959
14021
  }, 300);
@@ -13962,7 +14024,7 @@ function useAutoSave() {
13962
14024
  };
13963
14025
  }, [workspace]);
13964
14026
  }
13965
- const { electron: electron$k } = window;
14027
+ const { electron: electron$l } = window;
13966
14028
  function useWorkspaceLoader() {
13967
14029
  const loadCollection = useStore((s) => s.loadCollection);
13968
14030
  const loadEnvironment = useStore((s) => s.loadEnvironment);
@@ -13981,28 +14043,28 @@ function useWorkspaceLoader() {
13981
14043
  });
13982
14044
  for (const colPath of ws2.collections) {
13983
14045
  try {
13984
- const col = await electron$k.loadCollection(colPath);
14046
+ const col = await electron$l.loadCollection(colPath);
13985
14047
  loadCollection(colPath, col);
13986
14048
  } catch {
13987
14049
  }
13988
14050
  }
13989
14051
  for (const envPath of ws2.environments) {
13990
14052
  try {
13991
- const env = await electron$k.loadEnvironment(envPath);
14053
+ const env = await electron$l.loadEnvironment(envPath);
13992
14054
  loadEnvironment(envPath, env);
13993
14055
  } catch {
13994
14056
  }
13995
14057
  }
13996
14058
  for (const relPath of ws2.mocks ?? []) {
13997
14059
  try {
13998
- const mockData = await electron$k.loadMock(relPath);
14060
+ const mockData = await electron$l.loadMock(relPath);
13999
14061
  loadMock(relPath, mockData);
14000
14062
  } catch {
14001
14063
  }
14002
14064
  }
14003
14065
  if (ws2.collections.length > 0) {
14004
14066
  try {
14005
- const firstCol = await electron$k.loadCollection(ws2.collections[0]);
14067
+ const firstCol = await electron$l.loadCollection(ws2.collections[0]);
14006
14068
  setActiveCollection(firstCol.id);
14007
14069
  } catch {
14008
14070
  }
@@ -39703,6 +39765,33 @@ const FAKER_SUB = {
39703
39765
  { label: "words", type: "function", detail: "(count)", info: "Multiple lorem words" },
39704
39766
  { label: "sentence", type: "function", detail: "()", info: "Lorem sentence" },
39705
39767
  { label: "paragraph", type: "function", detail: "()", info: "Lorem paragraph" }
39768
+ ],
39769
+ location: [
39770
+ { label: "city", type: "function", detail: "()", info: "Random city name" },
39771
+ { label: "country", type: "function", detail: "()", info: "Random country name" },
39772
+ { label: "countryCode", type: "function", detail: "()", info: "Two-letter country code" },
39773
+ { label: "state", type: "function", detail: "()", info: "US state name" },
39774
+ { label: "streetAddress", type: "function", detail: "()", info: "Street address" },
39775
+ { label: "zipCode", type: "function", detail: "()", info: "ZIP / postal code" },
39776
+ { label: "latitude", type: "function", detail: "()", info: "Random latitude" },
39777
+ { label: "longitude", type: "function", detail: "()", info: "Random longitude" }
39778
+ ],
39779
+ finance: [
39780
+ { label: "iban", type: "function", detail: "()", info: "IBAN number" },
39781
+ { label: "bic", type: "function", detail: "()", info: "BIC / SWIFT code" },
39782
+ { label: "accountNumber", type: "function", detail: "()", info: "Bank account number" },
39783
+ { label: "amount", type: "function", detail: "({ min, max })", info: "Random monetary amount" },
39784
+ { label: "currencyCode", type: "function", detail: "()", info: "Currency code (USD, EUR, …)" },
39785
+ { label: "currencyName", type: "function", detail: "()", info: "Currency name" },
39786
+ { label: "creditCardNumber", type: "function", detail: "()", info: "Credit card number" },
39787
+ { label: "pin", type: "function", detail: "()", info: "Numeric PIN" },
39788
+ { label: "bitcoinAddress", type: "function", detail: "()", info: "Bitcoin address" }
39789
+ ],
39790
+ color: [
39791
+ { label: "human", type: "function", detail: "()", info: 'Human-readable color name (e.g. "red")' },
39792
+ { label: "hex", type: "function", detail: "()", info: "Hex color string (e.g. #a3f1c2)" },
39793
+ { label: "rgb", type: "function", detail: "()", info: "CSS rgb() string" },
39794
+ { label: "hsl", type: "function", detail: "()", info: "CSS hsl() string" }
39706
39795
  ]
39707
39796
  };
39708
39797
  function makeAtCompletionSource(varNames) {
@@ -40949,6 +41038,8 @@ function CollectionTree() {
40949
41038
  const renameRequest = useStore((s) => s.renameRequest);
40950
41039
  const deleteRequest = useStore((s) => s.deleteRequest);
40951
41040
  const duplicateRequest = useStore((s) => s.duplicateRequest);
41041
+ const duplicateCollection = useStore((s) => s.duplicateCollection);
41042
+ const duplicateFolder = useStore((s) => s.duplicateFolder);
40952
41043
  const updateFolderTags = useStore((s) => s.updateFolderTags);
40953
41044
  const updateRequestTags = useStore((s) => s.updateRequestTags);
40954
41045
  const openRunner = useStore((s) => s.openRunner);
@@ -40983,8 +41074,10 @@ function CollectionTree() {
40983
41074
  onAddFolder: (parentId, name2) => addFolder(col.id, parentId, name2),
40984
41075
  onRenameCollection: (name2) => renameCollection(col.id, name2),
40985
41076
  onDeleteCollection: () => confirmThen(`Delete collection "${col.name}"?`, () => deleteCollection(col.id)),
41077
+ onDuplicateCollection: () => duplicateCollection(col.id),
40986
41078
  onRenameFolder: (folderId, name2) => renameFolder(col.id, folderId, name2),
40987
41079
  onDeleteFolder: (folderId) => confirmThen("Delete this folder and all its requests?", () => deleteFolder(col.id, folderId)),
41080
+ onDuplicateFolder: (folderId) => duplicateFolder(col.id, folderId),
40988
41081
  onRenameRequest: renameRequest,
40989
41082
  onDeleteRequest: (reqId) => deleteRequest(col.id, reqId),
40990
41083
  onDuplicateRequest: (reqId) => duplicateRequest(col.id, reqId),
@@ -41014,8 +41107,10 @@ function CollectionNode({
41014
41107
  onAddFolder,
41015
41108
  onRenameCollection,
41016
41109
  onDeleteCollection,
41110
+ onDuplicateCollection,
41017
41111
  onRenameFolder,
41018
41112
  onDeleteFolder,
41113
+ onDuplicateFolder,
41019
41114
  onRenameRequest,
41020
41115
  onDeleteRequest,
41021
41116
  onDuplicateRequest,
@@ -41070,6 +41165,7 @@ function CollectionNode({
41070
41165
  /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add request", onClick: () => onAddRequest(col.rootFolder.id), alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "+" }) }),
41071
41166
  /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Add folder", onClick: () => onAddFolder(col.rootFolder.id, "New Folder"), alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(FolderIcon, {}) }),
41072
41167
  /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Rename", onClick: () => setRenaming(true), alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}) }),
41168
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Duplicate collection", onClick: onDuplicateCollection, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}) }),
41073
41169
  /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Delete collection", onClick: onDeleteCollection, danger: true, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}) })
41074
41170
  ] })
41075
41171
  ]
@@ -41089,6 +41185,7 @@ function CollectionNode({
41089
41185
  onAddFolder,
41090
41186
  onRenameFolder,
41091
41187
  onDeleteFolder,
41188
+ onDuplicateFolder,
41092
41189
  onRenameRequest,
41093
41190
  onDeleteRequest,
41094
41191
  onDuplicateRequest,
@@ -41109,6 +41206,7 @@ function FolderRow({
41109
41206
  onAddFolder,
41110
41207
  onRename,
41111
41208
  onDelete,
41209
+ onDuplicate,
41112
41210
  onUpdateTags,
41113
41211
  onRun,
41114
41212
  children
@@ -41163,6 +41261,7 @@ function FolderRow({
41163
41261
  }, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TagChips, { tags: [], onRemove: () => {
41164
41262
  }, onAdd: (tag) => onUpdateTags([...tags2, tag]) }) }),
41165
41263
  /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Rename", onClick: () => setRenaming(true), alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}) }),
41264
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Duplicate folder", onClick: onDuplicate, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}) }),
41166
41265
  /* @__PURE__ */ jsxRuntimeExports.jsx(IconBtn, { title: "Delete folder", onClick: onDelete, danger: true, alwaysVisible: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}) })
41167
41266
  ] })
41168
41267
  ]
@@ -41191,6 +41290,7 @@ function FolderContents({
41191
41290
  onAddFolder,
41192
41291
  onRenameFolder,
41193
41292
  onDeleteFolder,
41293
+ onDuplicateFolder,
41194
41294
  onRenameRequest,
41195
41295
  onDeleteRequest,
41196
41296
  onDuplicateRequest,
@@ -41210,6 +41310,7 @@ function FolderContents({
41210
41310
  onAddFolder: () => onAddFolder(sub.id, "New Folder"),
41211
41311
  onRename: (name2) => onRenameFolder(sub.id, name2),
41212
41312
  onDelete: () => onDeleteFolder(sub.id),
41313
+ onDuplicate: () => onDuplicateFolder(sub.id),
41213
41314
  onUpdateTags: (tags2) => onUpdateFolderTags(sub.id, tags2),
41214
41315
  onRun: () => onRunFolder(sub.id),
41215
41316
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -41226,6 +41327,7 @@ function FolderContents({
41226
41327
  onAddFolder,
41227
41328
  onRenameFolder,
41228
41329
  onDeleteFolder,
41330
+ onDuplicateFolder,
41229
41331
  onRenameRequest,
41230
41332
  onDeleteRequest,
41231
41333
  onDuplicateRequest,
@@ -42144,7 +42246,7 @@ const autoCloseTags = /* @__PURE__ */ EditorView.inputHandler.of((view, from, to
42144
42246
  ]);
42145
42247
  return true;
42146
42248
  });
42147
- const { electron: electron$j } = window;
42249
+ const { electron: electron$k } = window;
42148
42250
  function SoapEditor({ request, onChange }) {
42149
42251
  const soap = request.body.soap ?? {
42150
42252
  wsdlUrl: "",
@@ -42161,7 +42263,7 @@ function SoapEditor({ request, onChange }) {
42161
42263
  setFetching(true);
42162
42264
  setFetchError(null);
42163
42265
  try {
42164
- const result = await electron$j.wsdlFetch(soap.wsdlUrl.trim());
42266
+ const result = await electron$k.wsdlFetch(soap.wsdlUrl.trim());
42165
42267
  setOperations(result.operations);
42166
42268
  if (result.operations.length > 0) {
42167
42269
  const first = result.operations[0];
@@ -42321,7 +42423,7 @@ function BodyTab({ request, onChange }) {
42321
42423
  mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request, onChange }) })
42322
42424
  ] });
42323
42425
  }
42324
- const { electron: electron$i } = window;
42426
+ const { electron: electron$j } = window;
42325
42427
  const AUTH_TYPES = ["none", "bearer", "basic", "digest", "ntlm", "apikey", "oauth2"];
42326
42428
  function AuthTab({ request, onChange }) {
42327
42429
  const auth = request.auth;
@@ -42335,7 +42437,7 @@ function AuthTab({ request, onChange }) {
42335
42437
  }
42336
42438
  async function saveSecret(ref2) {
42337
42439
  if (!secretValue || !ref2) return;
42338
- await electron$i.setSecret(ref2, secretValue);
42440
+ await electron$j.setSecret(ref2, secretValue);
42339
42441
  setSaved(true);
42340
42442
  setSecretValue("");
42341
42443
  setTimeout(() => setSaved(false), 2e3);
@@ -42346,14 +42448,14 @@ function AuthTab({ request, onChange }) {
42346
42448
  try {
42347
42449
  const vars = {};
42348
42450
  if (auth.oauth2Flow === "authorization_code") {
42349
- const result = await electron$i.oauth2StartFlow(auth, vars);
42451
+ const result = await electron$j.oauth2StartFlow(auth, vars);
42350
42452
  setAuth({
42351
42453
  oauth2CachedToken: result.accessToken,
42352
42454
  oauth2TokenExpiry: result.expiresAt
42353
42455
  });
42354
42456
  if (result.refreshToken) setOauth2RT(result.refreshToken);
42355
42457
  } else {
42356
- const result = await electron$i.oauth2StartFlow(auth, vars);
42458
+ const result = await electron$j.oauth2StartFlow(auth, vars);
42357
42459
  setAuth({
42358
42460
  oauth2CachedToken: result.accessToken,
42359
42461
  oauth2TokenExpiry: result.expiresAt
@@ -42371,7 +42473,7 @@ function AuthTab({ request, onChange }) {
42371
42473
  setOauth2Status("fetching");
42372
42474
  setOauth2Error("");
42373
42475
  try {
42374
- const result = await electron$i.oauth2RefreshToken(auth, {}, oauth2RefreshToken);
42476
+ const result = await electron$j.oauth2RefreshToken(auth, {}, oauth2RefreshToken);
42375
42477
  setAuth({
42376
42478
  oauth2CachedToken: result.accessToken,
42377
42479
  oauth2TokenExpiry: result.expiresAt
@@ -49368,7 +49470,7 @@ function SchemaTab({ request, onChange }) {
49368
49470
  ] }) })
49369
49471
  ] });
49370
49472
  }
49371
- const { electron: electron$h } = window;
49473
+ const { electron: electron$i } = window;
49372
49474
  const EMPTY = { statusCode: 200, headers: [], bodySchema: "" };
49373
49475
  function ContractTab({ request, onChange }) {
49374
49476
  const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
@@ -49382,7 +49484,7 @@ function ContractTab({ request, onChange }) {
49382
49484
  if (!lastResponse?.body) return;
49383
49485
  setInferring(true);
49384
49486
  try {
49385
- const schema = await electron$h.inferContractSchema(lastResponse.body);
49487
+ const schema = await electron$i.inferContractSchema(lastResponse.body);
49386
49488
  if (schema) update({ bodySchema: schema });
49387
49489
  } finally {
49388
49490
  setInferring(false);
@@ -49490,7 +49592,7 @@ function ContractTab({ request, onChange }) {
49490
49592
  ] })
49491
49593
  ] });
49492
49594
  }
49493
- const { electron: electron$g } = window;
49595
+ const { electron: electron$h } = window;
49494
49596
  function formatTime$1(ts) {
49495
49597
  const d = new Date(ts);
49496
49598
  const hh = String(d.getHours()).padStart(2, "0");
@@ -49510,14 +49612,14 @@ function WebSocketPanel({ request }) {
49510
49612
  const [sendText, setSendText] = reactExports.useState("");
49511
49613
  const logEndRef = reactExports.useRef(null);
49512
49614
  reactExports.useEffect(() => {
49513
- electron$g.onWsMessage(({ requestId, message }) => {
49615
+ electron$h.onWsMessage(({ requestId, message }) => {
49514
49616
  addWsMessage(requestId, message);
49515
49617
  });
49516
- electron$g.onWsStatus(({ requestId, status, error: error2 }) => {
49618
+ electron$h.onWsStatus(({ requestId, status, error: error2 }) => {
49517
49619
  setWsStatus(requestId, status, error2);
49518
49620
  });
49519
49621
  return () => {
49520
- electron$g.offWsEvents();
49622
+ electron$h.offWsEvents();
49521
49623
  };
49522
49624
  }, [addWsMessage, setWsStatus]);
49523
49625
  reactExports.useEffect(() => {
@@ -49530,19 +49632,19 @@ function WebSocketPanel({ request }) {
49530
49632
  if (h.enabled && h.key) headers[h.key] = h.value;
49531
49633
  }
49532
49634
  try {
49533
- await electron$g.wsConnect(request.id, request.url, headers);
49635
+ await electron$h.wsConnect(request.id, request.url, headers);
49534
49636
  } catch (err) {
49535
49637
  setWsStatus(request.id, "error", err instanceof Error ? err.message : String(err));
49536
49638
  }
49537
49639
  }
49538
49640
  async function disconnect() {
49539
- await electron$g.wsDisconnect(request.id);
49641
+ await electron$h.wsDisconnect(request.id);
49540
49642
  }
49541
49643
  async function sendMessage() {
49542
49644
  const text = sendText.trim();
49543
49645
  if (!text || !isConnected) return;
49544
49646
  try {
49545
- await electron$g.wsSend(request.id, text);
49647
+ await electron$h.wsSend(request.id, text);
49546
49648
  const msg = {
49547
49649
  id: crypto.randomUUID(),
49548
49650
  direction: "sent",
@@ -49645,7 +49747,7 @@ function WebSocketPanel({ request }) {
49645
49747
  ] })
49646
49748
  ] });
49647
49749
  }
49648
- const { electron: electron$f } = window;
49750
+ const { electron: electron$g } = window;
49649
49751
  const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
49650
49752
  const METHOD_COLORS = {
49651
49753
  GET: "text-emerald-400",
@@ -49699,7 +49801,7 @@ function RequestBuilder({ request }) {
49699
49801
  auth: mergedAuth,
49700
49802
  headers: mergedHeaders
49701
49803
  };
49702
- const result = await electron$f.sendRequest({
49804
+ const result = await electron$g.sendRequest({
49703
49805
  request: mergedRequest,
49704
49806
  environment: activeEnv,
49705
49807
  collectionVars,
@@ -50348,7 +50450,7 @@ function InteractiveBody({ body, contentType, onAssert }) {
50348
50450
  treeContent
50349
50451
  ] });
50350
50452
  }
50351
- const { electron: electron$e } = window;
50453
+ const { electron: electron$f } = window;
50352
50454
  function prettyJson(raw) {
50353
50455
  try {
50354
50456
  return JSON.stringify(JSON.parse(raw), null, 2);
@@ -50434,14 +50536,14 @@ function SaveAsMockModal({ onClose }) {
50434
50536
  const entry = state.mocks[serverId];
50435
50537
  const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
50436
50538
  updateMock(serverId, updated);
50437
- await electron$e.saveMock(entry.relPath, updated);
50539
+ await electron$f.saveMock(entry.relPath, updated);
50438
50540
  const ws2 = useStore.getState().workspace;
50439
- if (ws2) await electron$e.saveWorkspace(ws2);
50541
+ if (ws2) await electron$f.saveWorkspace(ws2);
50440
50542
  } else {
50441
50543
  const entry = useStore.getState().mocks[serverId];
50442
50544
  const updated = { ...entry.data, routes: [...entry.data.routes, route] };
50443
50545
  updateMock(serverId, updated);
50444
- await electron$e.saveMock(entry.relPath, updated);
50546
+ await electron$f.saveMock(entry.relPath, updated);
50445
50547
  }
50446
50548
  onClose();
50447
50549
  } finally {
@@ -50672,7 +50774,7 @@ function ResponseViewer() {
50672
50774
  const [contractToast, setContractToast] = reactExports.useState(false);
50673
50775
  async function saveAsContract() {
50674
50776
  if (!response || !requestId || !activeTabId) return;
50675
- const schema = response.body ? await electron$e.inferContractSchema(response.body) : null;
50777
+ const schema = response.body ? await electron$f.inferContractSchema(response.body) : null;
50676
50778
  const contentType2 = response.headers["content-type"];
50677
50779
  const headers = contentType2 ? [{ key: "content-type", value: contentType2, required: true }] : [];
50678
50780
  updateRequest(requestId, {
@@ -50915,7 +51017,7 @@ function ConsolePanel({ scriptResult }) {
50915
51017
  i
50916
51018
  )) });
50917
51019
  }
50918
- const { electron: electron$d } = window;
51020
+ const { electron: electron$e } = window;
50919
51021
  const TARGETS = [
50920
51022
  { id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
50921
51023
  { id: "playwright_ts", label: "Playwright TS", description: "TypeScript page-object API classes + spec files" },
@@ -50946,7 +51048,7 @@ function GeneratorPanel() {
50946
51048
  try {
50947
51049
  const col = collections[selectedCollectionId]?.data;
50948
51050
  const env = activeEnvironmentId ? environments[activeEnvironmentId]?.data ?? null : null;
50949
- const generated = await electron$d.generateCode({ collection: col, environment: env, target });
51051
+ const generated = await electron$e.generateCode({ collection: col, environment: env, target });
50950
51052
  setFiles(generated);
50951
51053
  setSelectedFile(generated[0]?.path ?? null);
50952
51054
  } catch (e) {
@@ -50958,7 +51060,7 @@ function GeneratorPanel() {
50958
51060
  async function saveZip() {
50959
51061
  if (files.length === 0) return;
50960
51062
  const col = collections[selectedCollectionId]?.data;
50961
- await electron$d.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
51063
+ await electron$e.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
50962
51064
  }
50963
51065
  const selectedContent = files.find((f) => f.path === selectedFile)?.content ?? "";
50964
51066
  const activeTarget = TARGETS.find((t2) => t2.id === target);
@@ -51182,16 +51284,16 @@ function HistoryRow({
51182
51284
  }
51183
51285
  );
51184
51286
  }
51185
- const { electron: electron$c } = window;
51287
+ const { electron: electron$d } = window;
51186
51288
  function WelcomeScreen() {
51187
51289
  const { applyWorkspace } = useWorkspaceLoader();
51188
51290
  async function openWorkspace() {
51189
- const result = await electron$c.openWorkspace();
51291
+ const result = await electron$d.openWorkspace();
51190
51292
  if (!result) return;
51191
51293
  await applyWorkspace(result.workspace, result.workspacePath);
51192
51294
  }
51193
51295
  async function newWorkspace() {
51194
- const result = await electron$c.newWorkspace();
51296
+ const result = await electron$d.newWorkspace();
51195
51297
  if (!result) return;
51196
51298
  await applyWorkspace(result.workspace, result.workspacePath);
51197
51299
  }
@@ -51225,7 +51327,7 @@ function WelcomeScreen() {
51225
51327
  ] })
51226
51328
  ] });
51227
51329
  }
51228
- const { electron: electron$b } = window;
51330
+ const { electron: electron$c } = window;
51229
51331
  const EXAMPLES = [
51230
51332
  {
51231
51333
  label: "macOS / Linux (~/.zshrc or ~/.bashrc)",
@@ -51249,7 +51351,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
51249
51351
  setError("Password cannot be empty.");
51250
51352
  return;
51251
51353
  }
51252
- await electron$b.setMasterKey(password);
51354
+ await electron$c.setMasterKey(password);
51253
51355
  onSuccess(password);
51254
51356
  }
51255
51357
  function copy(idx, text) {
@@ -51339,7 +51441,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
51339
51441
  }
51340
51442
  );
51341
51443
  }
51342
- const { electron: electron$a } = window;
51444
+ const { electron: electron$b } = window;
51343
51445
  async function shortHash(value) {
51344
51446
  const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
51345
51447
  return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
@@ -51440,7 +51542,7 @@ function EnvironmentEditor({ onClose }) {
51440
51542
  async function saveEncrypted(idx) {
51441
51543
  const plaintext = secretInputs[idx] ?? "";
51442
51544
  if (!plaintext) return;
51443
- const { set: set2 } = await electron$a.checkMasterKey();
51545
+ const { set: set2 } = await electron$b.checkMasterKey();
51444
51546
  if (!set2) {
51445
51547
  setPendingEncryptIdx(idx);
51446
51548
  return;
@@ -51477,9 +51579,9 @@ function EnvironmentEditor({ onClose }) {
51477
51579
  } : state.workspace
51478
51580
  }));
51479
51581
  const ws2 = useStore.getState().workspace;
51480
- if (ws2) await electron$a.saveWorkspace(ws2);
51582
+ if (ws2) await electron$b.saveWorkspace(ws2);
51481
51583
  }
51482
- await electron$a.saveEnvironment(newRelPath, env);
51584
+ await electron$b.saveEnvironment(newRelPath, env);
51483
51585
  }
51484
51586
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
51485
51587
  pendingEncryptIdx !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -51763,7 +51865,7 @@ function EnvironmentEditor({ onClose }) {
51763
51865
  )
51764
51866
  ] });
51765
51867
  }
51766
- const { electron: electron$9 } = window;
51868
+ const { electron: electron$a } = window;
51767
51869
  function EnvironmentBar({ inline = false }) {
51768
51870
  const environments = useStore((s) => s.environments);
51769
51871
  const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
@@ -51777,7 +51879,7 @@ function EnvironmentBar({ inline = false }) {
51777
51879
  if (id2) {
51778
51880
  const hasSecrets = environments[id2]?.data.variables.some((v) => v.enabled && v.secret);
51779
51881
  if (hasSecrets) {
51780
- const { set: set2 } = await electron$9.checkMasterKey();
51882
+ const { set: set2 } = await electron$a.checkMasterKey();
51781
51883
  if (!set2) {
51782
51884
  setPendingEnvId(id2);
51783
51885
  return;
@@ -51831,7 +51933,7 @@ function EnvironmentBar({ inline = false }) {
51831
51933
  controls
51832
51934
  ] });
51833
51935
  }
51834
- const { electron: electron$8 } = window;
51936
+ const { electron: electron$9 } = window;
51835
51937
  const DEFAULT_PII_PATTERNS = ["authorization", "password", "token", "secret", "api-key", "x-api-key"];
51836
51938
  function WorkspaceSettingsModal({ onClose }) {
51837
51939
  const workspace = useStore((s) => s.workspace);
@@ -51877,7 +51979,7 @@ function WorkspaceSettingsModal({ onClose }) {
51877
51979
  settings.piiMaskPatterns = patterns;
51878
51980
  updateWorkspaceSettings(settings);
51879
51981
  const updated = useStore.getState().workspace;
51880
- if (updated) await electron$8.saveWorkspace(updated);
51982
+ if (updated) await electron$9.saveWorkspace(updated);
51881
51983
  onClose();
51882
51984
  }
51883
51985
  const tabs = [
@@ -52075,7 +52177,7 @@ function WorkspaceSettingsModal({ onClose }) {
52075
52177
  }
52076
52178
  );
52077
52179
  }
52078
- const { electron: electron$7 } = window;
52180
+ const { electron: electron$8 } = window;
52079
52181
  function DocsGeneratorModal({ onClose }) {
52080
52182
  const collections = useStore((s) => s.collections);
52081
52183
  const collectionList = Object.values(collections);
@@ -52104,9 +52206,9 @@ function DocsGeneratorModal({ onClose }) {
52104
52206
  setGenerating(true);
52105
52207
  setError(null);
52106
52208
  try {
52107
- const content2 = await electron$7.generateDocs(buildPayload());
52209
+ const content2 = await electron$8.generateDocs(buildPayload());
52108
52210
  const filename = format2 === "html" ? "api-docs.html" : "api-docs.md";
52109
- await electron$7.saveResults(content2, filename);
52211
+ await electron$8.saveResults(content2, filename);
52110
52212
  } catch (err) {
52111
52213
  setError(err instanceof Error ? err.message : String(err));
52112
52214
  } finally {
@@ -52117,7 +52219,7 @@ function DocsGeneratorModal({ onClose }) {
52117
52219
  setGenerating(true);
52118
52220
  setError(null);
52119
52221
  try {
52120
- const content2 = await electron$7.generateDocs(buildPayload());
52222
+ const content2 = await electron$8.generateDocs(buildPayload());
52121
52223
  setPreview(content2);
52122
52224
  } catch (err) {
52123
52225
  setError(err instanceof Error ? err.message : String(err));
@@ -52233,7 +52335,7 @@ function DocsGeneratorModal({ onClose }) {
52233
52335
  ] })
52234
52336
  ] }) });
52235
52337
  }
52236
- const { electron: electron$6 } = window;
52338
+ const { electron: electron$7 } = window;
52237
52339
  const OPTIONS = [
52238
52340
  { id: "postman", label: "Postman", description: "Collection v2.1 JSON" },
52239
52341
  { id: "openapi", label: "OpenAPI", description: "JSON or YAML (v3.x)", supportsUrl: true },
@@ -52251,10 +52353,10 @@ function ImportModal({ onImport, onClose }) {
52251
52353
  setError(null);
52252
52354
  try {
52253
52355
  let col = null;
52254
- if (opt.id === "postman") col = await electron$6.importPostman();
52255
- if (opt.id === "openapi") col = await electron$6.importOpenApi();
52256
- if (opt.id === "insomnia") col = await electron$6.importInsomnia();
52257
- if (opt.id === "bruno") col = await electron$6.importBruno();
52356
+ if (opt.id === "postman") col = await electron$7.importPostman();
52357
+ if (opt.id === "openapi") col = await electron$7.importOpenApi();
52358
+ if (opt.id === "insomnia") col = await electron$7.importInsomnia();
52359
+ if (opt.id === "bruno") col = await electron$7.importBruno();
52258
52360
  onImport(col);
52259
52361
  onClose();
52260
52362
  } catch (err) {
@@ -52269,7 +52371,7 @@ function ImportModal({ onImport, onClose }) {
52269
52371
  setLoading(true);
52270
52372
  setError(null);
52271
52373
  try {
52272
- const col = await electron$6.importOpenApiFromUrl(trimmed);
52374
+ const col = await electron$7.importOpenApiFromUrl(trimmed);
52273
52375
  onImport(col);
52274
52376
  onClose();
52275
52377
  } catch (err) {
@@ -52364,7 +52466,7 @@ function ImportModal({ onImport, onClose }) {
52364
52466
  }
52365
52467
  ) });
52366
52468
  }
52367
- const { electron: electron$5 } = window;
52469
+ const { electron: electron$6 } = window;
52368
52470
  const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
52369
52471
  function PreferencesPopover() {
52370
52472
  const theme2 = useStore((s) => s.theme);
@@ -52470,13 +52572,13 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52470
52572
  try {
52471
52573
  for (const { relPath, data, dirty } of Object.values(collections)) {
52472
52574
  if (!dirty) continue;
52473
- await electron$5.saveCollection(relPath, data);
52575
+ await electron$6.saveCollection(relPath, data);
52474
52576
  markCollectionClean(data.id);
52475
52577
  }
52476
52578
  for (const { relPath, data } of Object.values(environments)) {
52477
- await electron$5.saveEnvironment(relPath, data);
52579
+ await electron$6.saveEnvironment(relPath, data);
52478
52580
  }
52479
- if (workspace) await electron$5.saveWorkspace(workspace);
52581
+ if (workspace) await electron$6.saveWorkspace(workspace);
52480
52582
  } finally {
52481
52583
  setSaving(false);
52482
52584
  }
@@ -52484,14 +52586,14 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52484
52586
  async function afterImport(col) {
52485
52587
  if (!col) return;
52486
52588
  const relPath = colRelPath(col.name, col.id);
52487
- await electron$5.saveCollection(relPath, col);
52589
+ await electron$6.saveCollection(relPath, col);
52488
52590
  loadCollection(relPath, col);
52489
52591
  setActiveCollection(col.id);
52490
52592
  const ws2 = useStore.getState().workspace;
52491
52593
  if (ws2 && !ws2.collections.includes(relPath)) {
52492
52594
  const updated = { ...ws2, collections: [...ws2.collections, relPath] };
52493
52595
  useStore.setState({ workspace: updated });
52494
- await electron$5.saveWorkspace(updated);
52596
+ await electron$6.saveWorkspace(updated);
52495
52597
  }
52496
52598
  }
52497
52599
  if (!workspace) return null;
@@ -52557,7 +52659,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52557
52659
  "button",
52558
52660
  {
52559
52661
  onClick: async () => {
52560
- const result = await electron$5.openWorkspace();
52662
+ const result = await electron$6.openWorkspace();
52561
52663
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
52562
52664
  },
52563
52665
  className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
@@ -52569,7 +52671,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52569
52671
  "button",
52570
52672
  {
52571
52673
  onClick: async () => {
52572
- const result = await electron$5.newWorkspace();
52674
+ const result = await electron$6.newWorkspace();
52573
52675
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
52574
52676
  },
52575
52677
  className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
@@ -52581,7 +52683,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52581
52683
  "button",
52582
52684
  {
52583
52685
  onClick: async () => {
52584
- await electron$5.closeWorkspace();
52686
+ await electron$6.closeWorkspace();
52585
52687
  closeWorkspace();
52586
52688
  },
52587
52689
  className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
@@ -52978,7 +53080,7 @@ api-tests:
52978
53080
  function EmptyState({ message }) {
52979
53081
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-24 text-surface-400 text-xs", children: message });
52980
53082
  }
52981
- const { electron: electron$4 } = window;
53083
+ const { electron: electron$5 } = window;
52982
53084
  function collectRequests(collectionId, folderId, filterTags) {
52983
53085
  const state = useStore.getState();
52984
53086
  const col = state.collections[collectionId]?.data;
@@ -53074,13 +53176,13 @@ function RunnerModal() {
53074
53176
  setSummary(null);
53075
53177
  setRunnerRunning(true);
53076
53178
  progressIdxRef.current = 0;
53077
- electron$4.onRunProgress((result) => {
53179
+ electron$5.onRunProgress((result) => {
53078
53180
  const idx = progressIdxRef.current;
53079
53181
  patchRunnerResult(idx, result);
53080
53182
  if (result.status !== "running") progressIdxRef.current++;
53081
53183
  });
53082
53184
  try {
53083
- const s = await electron$4.runCollection({
53185
+ const s = await electron$5.runCollection({
53084
53186
  items: items2,
53085
53187
  environment: env,
53086
53188
  globals,
@@ -53091,7 +53193,7 @@ function RunnerModal() {
53091
53193
  });
53092
53194
  setSummary(s);
53093
53195
  } finally {
53094
- electron$4.offRunProgress();
53196
+ electron$5.offRunProgress();
53095
53197
  setRunnerRunning(false);
53096
53198
  }
53097
53199
  }, [collectionId, folderId, filterTags, selectedEnvId, environments, globals, colEntry, requestDelay, workspaceSettings, setRunnerResults, patchRunnerResult, setRunnerRunning]);
@@ -53304,7 +53406,7 @@ function RunnerModal() {
53304
53406
  };
53305
53407
  const content2 = exportFormat === "junit" ? buildJUnitReport(runnerResults, summary, meta2) : exportFormat === "html" ? buildHtmlReport(runnerResults, summary, meta2) : buildJsonReport(runnerResults, summary, meta2);
53306
53408
  const ext = exportFormat === "junit" ? "xml" : exportFormat === "html" ? "html" : "json";
53307
- electron$4.saveResults(content2, `spector-results.${ext}`);
53409
+ electron$5.saveResults(content2, `spector-results.${ext}`);
53308
53410
  },
53309
53411
  className: "px-2.5 py-0.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors text-[11px] whitespace-nowrap",
53310
53412
  children: "Export results"
@@ -53522,38 +53624,48 @@ function CollectionPanel() {
53522
53624
  ] })
53523
53625
  ] });
53524
53626
  }
53525
- const { electron: electron$3 } = window;
53627
+ const { electron: electron$4 } = window;
53526
53628
  function MockPanel() {
53527
53629
  const mocks = useStore((s) => s.mocks);
53528
53630
  const activeMockId = useStore((s) => s.activeMockId);
53529
53631
  const setActiveMock = useStore((s) => s.setActiveMockId);
53530
53632
  const addMock = useStore((s) => s.addMock);
53633
+ const deleteMock = useStore((s) => s.deleteMock);
53531
53634
  const setRunning = useStore((s) => s.setMockRunning);
53532
53635
  const mockList = Object.values(mocks);
53533
53636
  async function handleAddMock() {
53534
53637
  addMock();
53535
53638
  const ws2 = useStore.getState().workspace;
53536
- if (ws2) await electron$3.saveWorkspace(ws2);
53639
+ if (ws2) await electron$4.saveWorkspace(ws2);
53537
53640
  const state = useStore.getState();
53538
53641
  const newId = state.activeMockId;
53539
53642
  if (newId) {
53540
53643
  const entry = state.mocks[newId];
53541
- await electron$3.saveMock(entry.relPath, entry.data);
53644
+ await electron$4.saveMock(entry.relPath, entry.data);
53542
53645
  setActiveMock(newId);
53543
53646
  }
53544
53647
  }
53648
+ async function handleDelete(e, mockId) {
53649
+ e.stopPropagation();
53650
+ const entry = useStore.getState().mocks[mockId];
53651
+ if (!entry) return;
53652
+ if (entry.running) await electron$4.mockStop(mockId);
53653
+ deleteMock(mockId);
53654
+ const ws2 = useStore.getState().workspace;
53655
+ if (ws2) await electron$4.saveWorkspace(ws2);
53656
+ }
53545
53657
  async function toggleRunning(e, mockId) {
53546
53658
  e.stopPropagation();
53547
53659
  const entry = useStore.getState().mocks[mockId];
53548
53660
  if (!entry) return;
53549
53661
  try {
53550
53662
  if (entry.running) {
53551
- await electron$3.mockStop(mockId);
53663
+ await electron$4.mockStop(mockId);
53552
53664
  setRunning(mockId, false);
53553
53665
  } else {
53554
53666
  const latest2 = useStore.getState().mocks[mockId].data;
53555
- await electron$3.saveMock(entry.relPath, latest2);
53556
- await electron$3.mockStart(latest2);
53667
+ await electron$4.saveMock(entry.relPath, latest2);
53668
+ await electron$4.mockStart(latest2);
53557
53669
  setRunning(mockId, true);
53558
53670
  }
53559
53671
  } catch {
@@ -53602,15 +53714,26 @@ function MockPanel() {
53602
53714
  (mock.routes ?? []).length !== 1 ? "s" : ""
53603
53715
  ] })
53604
53716
  ] }),
53605
- /* @__PURE__ */ jsxRuntimeExports.jsx(
53606
- "button",
53607
- {
53608
- onClick: (e) => toggleRunning(e, mock.id),
53609
- className: `text-[10px] px-1.5 py-0.5 rounded shrink-0 transition-colors ${entry.running ? "text-emerald-400 hover:text-red-400" : "text-surface-400 hover:text-emerald-400 opacity-0 group-hover:opacity-100"}`,
53610
- title: entry.running ? "Stop" : "Start",
53611
- children: entry.running ? "" : ""
53612
- }
53613
- )
53717
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
53718
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
53719
+ "button",
53720
+ {
53721
+ onClick: (e) => toggleRunning(e, mock.id),
53722
+ className: `text-[10px] px-1.5 py-0.5 rounded transition-colors ${entry.running ? "text-emerald-400 hover:text-red-400" : "text-surface-400 hover:text-emerald-400 opacity-0 group-hover:opacity-100"}`,
53723
+ title: entry.running ? "Stop" : "Start",
53724
+ children: entry.running ? "■" : "▶"
53725
+ }
53726
+ ),
53727
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
53728
+ "button",
53729
+ {
53730
+ onClick: (e) => handleDelete(e, mock.id),
53731
+ className: "text-[10px] px-1 py-0.5 rounded text-surface-500 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-colors",
53732
+ title: "Delete",
53733
+ children: "✕"
53734
+ }
53735
+ )
53736
+ ] })
53614
53737
  ]
53615
53738
  },
53616
53739
  mock.id
@@ -53618,12 +53741,13 @@ function MockPanel() {
53618
53741
  }) })
53619
53742
  ] });
53620
53743
  }
53621
- const { electron: electron$2 } = window;
53744
+ const { electron: electron$3 } = window;
53622
53745
  const METHODS_PLUS_ANY = ["ANY", "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
53623
53746
  function RouteRow({
53624
53747
  route,
53625
53748
  onSave,
53626
53749
  onDelete,
53750
+ onDuplicate,
53627
53751
  initialEditing = false
53628
53752
  }) {
53629
53753
  const [editing, setEditing] = reactExports.useState(initialEditing);
@@ -53663,6 +53787,15 @@ function RouteRow({
53663
53787
  children: "Edit"
53664
53788
  }
53665
53789
  ),
53790
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
53791
+ "button",
53792
+ {
53793
+ onClick: onDuplicate,
53794
+ className: "opacity-0 group-hover:opacity-100 text-xs text-surface-600 hover:text-blue-400 transition-opacity",
53795
+ title: "Duplicate route",
53796
+ children: "⧉"
53797
+ }
53798
+ ),
53666
53799
  /* @__PURE__ */ jsxRuntimeExports.jsx(
53667
53800
  "button",
53668
53801
  {
@@ -53733,11 +53866,17 @@ function RouteRow({
53733
53866
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-surface-600", children: [
53734
53867
  "Use ",
53735
53868
  /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-400", children: "{{faker.person.firstName()}}" }),
53736
- ",",
53737
- " ",
53738
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-400", children: "{{request.params.id}}" }),
53739
- ",",
53740
- " ",
53869
+ pathParamNames.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
53870
+ ", ",
53871
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-blue-400/80", children: `{{request.params.${p}}}` })
53872
+ ] }, p)),
53873
+ pathParamNames.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
53874
+ ", ",
53875
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-400", children: "{{request.params.id}}" }),
53876
+ " ",
53877
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-700", children: "(add :id to path)" })
53878
+ ] }),
53879
+ ", ",
53741
53880
  /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-400", children: "{{request.query.search}}" })
53742
53881
  ] })
53743
53882
  ] }),
@@ -53887,6 +54026,55 @@ function timeAgo(ts) {
53887
54026
  if (s < 3600) return `${Math.floor(s / 60)}m ago`;
53888
54027
  return `${Math.floor(s / 3600)}h ago`;
53889
54028
  }
54029
+ function HitRow({ hit, matched }) {
54030
+ const [open, setOpen] = reactExports.useState(false);
54031
+ const unmatched = !hit.matchedRouteId;
54032
+ const prettyBody = reactExports.useMemo(() => {
54033
+ if (!hit.responseBody) return null;
54034
+ try {
54035
+ return JSON.stringify(JSON.parse(hit.responseBody), null, 2);
54036
+ } catch {
54037
+ return hit.responseBody;
54038
+ }
54039
+ }, [hit.responseBody]);
54040
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `border-b border-surface-800/40 ${unmatched ? "bg-red-950/20" : ""}`, children: [
54041
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54042
+ "button",
54043
+ {
54044
+ onClick: () => setOpen((o) => !o),
54045
+ className: `w-full flex items-center gap-3 px-4 py-2 text-sm font-mono text-left ${unmatched ? "" : "hover:bg-surface-800/20"} transition-colors`,
54046
+ children: [
54047
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-[10px] w-3 shrink-0", children: open ? "▾" : "▸" }),
54048
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-bold w-16 shrink-0 text-xs ${getMethodColor(hit.method)}`, children: hit.method }),
54049
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", title: hit.path, children: hit.path }),
54050
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 shrink-0 truncate text-surface-600 text-xs font-sans", title: matched?.description ?? matched?.path ?? "", children: unmatched ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: "no match" }) : matched?.description || matched?.path || "—" }),
54051
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-12 text-right shrink-0 text-xs ${hit.status < 300 ? "text-emerald-400" : hit.status < 400 ? "text-amber-400" : "text-red-400"}`, children: hit.status }),
54052
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "w-14 text-right shrink-0 text-surface-600 text-xs", children: [
54053
+ hit.durationMs,
54054
+ "ms"
54055
+ ] }),
54056
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-20 text-right shrink-0 text-surface-400 text-xs", children: timeAgo(hit.timestamp) })
54057
+ ]
54058
+ }
54059
+ ),
54060
+ open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 pb-3 bg-surface-900/60 border-t border-surface-800/40", children: [
54061
+ hit.responseHeaders && Object.keys(hit.responseHeaders).length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2", children: [
54062
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-wider text-surface-600 mb-1", children: "Response headers" }),
54063
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs space-y-0.5", children: Object.entries(hit.responseHeaders).map(([k, v]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
54064
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-400 shrink-0", children: [
54065
+ k,
54066
+ ":"
54067
+ ] }),
54068
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300 break-all", children: v })
54069
+ ] }, k)) })
54070
+ ] }),
54071
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2", children: [
54072
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-wider text-surface-600 mb-1", children: "Response body" }),
54073
+ prettyBody ? /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs text-surface-300 bg-surface-950 rounded p-2 overflow-x-auto max-h-60 overflow-y-auto whitespace-pre-wrap break-all", children: prettyBody }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-surface-600 italic", children: "empty" })
54074
+ ] })
54075
+ ] })
54076
+ ] });
54077
+ }
53890
54078
  function RequestLog({ serverId, routes, running }) {
53891
54079
  const hits = useStore((s) => s.mockLogs[serverId]) ?? [];
53892
54080
  const clearLogs = useStore((s) => s.clearMockLogs);
@@ -53918,6 +54106,7 @@ function RequestLog({ serverId, routes, running }) {
53918
54106
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-600 mt-1", children: "Waiting for incoming requests…" })
53919
54107
  ] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
53920
54108
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 px-4 py-1.5 border-b border-surface-800 text-[10px] uppercase tracking-wider text-surface-400 sticky top-0 bg-surface-950", children: [
54109
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3 shrink-0" }),
53921
54110
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-16 shrink-0", children: "Method" }),
53922
54111
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1", children: "Path" }),
53923
54112
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 shrink-0", children: "Matched route" }),
@@ -53925,28 +54114,14 @@ function RequestLog({ serverId, routes, running }) {
53925
54114
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-14 text-right shrink-0", children: "Duration" }),
53926
54115
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-20 text-right shrink-0", children: "Time" })
53927
54116
  ] }),
53928
- hits.map((hit) => {
53929
- const matched = hit.matchedRouteId ? routeMap.get(hit.matchedRouteId) : null;
53930
- const unmatched = !hit.matchedRouteId;
53931
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
53932
- "div",
53933
- {
53934
- className: `flex items-center gap-3 px-4 py-2 border-b border-surface-800/40 text-sm font-mono ${unmatched ? "bg-red-950/30" : "hover:bg-surface-800/20"}`,
53935
- children: [
53936
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-bold w-16 shrink-0 text-xs ${getMethodColor(hit.method)}`, children: hit.method }),
53937
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", title: hit.path, children: hit.path }),
53938
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 shrink-0 truncate text-surface-600 text-xs font-sans", title: matched?.description ?? matched?.path ?? "", children: unmatched ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: "no match" }) : matched?.description || matched?.path || "—" }),
53939
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-12 text-right shrink-0 text-xs ${hit.status < 300 ? "text-emerald-400" : hit.status < 400 ? "text-amber-400" : "text-red-400"}`, children: hit.status }),
53940
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "w-14 text-right shrink-0 text-surface-600 text-xs", children: [
53941
- hit.durationMs,
53942
- "ms"
53943
- ] }),
53944
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-20 text-right shrink-0 text-surface-400 text-xs", title: String(hit.timestamp), children: timeAgo(hit.timestamp) })
53945
- ]
53946
- },
53947
- hit.id
53948
- );
53949
- })
54117
+ hits.map((hit) => /* @__PURE__ */ jsxRuntimeExports.jsx(
54118
+ HitRow,
54119
+ {
54120
+ hit,
54121
+ matched: hit.matchedRouteId ? routeMap.get(hit.matchedRouteId) : null
54122
+ },
54123
+ hit.id
54124
+ ))
53950
54125
  ] })
53951
54126
  ] });
53952
54127
  }
@@ -53968,20 +54143,20 @@ function MockDetailPanel({ mockId }) {
53968
54143
  const routes = mock.routes ?? [];
53969
54144
  async function save(updated) {
53970
54145
  updateMock(mock.id, updated);
53971
- await electron$2.saveMock(entry.relPath, updated);
53972
- if (running) await electron$2.mockUpdateRoutes(mock.id, updated.routes ?? []);
53973
- if (workspace) await electron$2.saveWorkspace(workspace);
54146
+ await electron$3.saveMock(entry.relPath, updated);
54147
+ if (running) await electron$3.mockUpdateRoutes(mock.id, updated.routes ?? []);
54148
+ if (workspace) await electron$3.saveWorkspace(workspace);
53974
54149
  }
53975
54150
  async function toggleRunning() {
53976
54151
  setError(null);
53977
54152
  try {
53978
54153
  if (running) {
53979
- await electron$2.mockStop(mock.id);
54154
+ await electron$3.mockStop(mock.id);
53980
54155
  setRunning(mock.id, false);
53981
54156
  } else {
53982
54157
  const latest2 = useStore.getState().mocks[mock.id].data;
53983
- await electron$2.saveMock(entry.relPath, latest2);
53984
- await electron$2.mockStart(latest2);
54158
+ await electron$3.saveMock(entry.relPath, latest2);
54159
+ await electron$3.mockStart(latest2);
53985
54160
  setRunning(mock.id, true);
53986
54161
  }
53987
54162
  } catch (e) {
@@ -54076,19 +54251,25 @@ function MockDetailPanel({ mockId }) {
54076
54251
  "⚠ ",
54077
54252
  error2
54078
54253
  ] }),
54079
- /* @__PURE__ */ jsxRuntimeExports.jsx(
54080
- "button",
54081
- {
54082
- onClick: toggleRunning,
54083
- className: `px-3 py-1.5 rounded text-sm font-medium transition-colors ${running ? "bg-emerald-900/40 text-emerald-400 hover:bg-red-900/40 hover:text-red-400 border border-emerald-800/50" : "bg-surface-800 hover:bg-surface-700 text-surface-300 border border-surface-700"}`,
54084
- children: running ? " Running" : " Start"
54085
- }
54086
- ),
54254
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative group/cli flex items-center", children: [
54255
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54256
+ "button",
54257
+ {
54258
+ onClick: toggleRunning,
54259
+ className: `px-3 py-1.5 rounded text-sm font-medium transition-colors ${running ? "bg-emerald-900/40 text-emerald-400 hover:bg-red-900/40 hover:text-red-400 border border-emerald-800/50" : "bg-surface-800 hover:bg-surface-700 text-surface-300 border border-surface-700"}`,
54260
+ children: running ? "● Running" : "▶ Start"
54261
+ }
54262
+ ),
54263
+ !running && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pointer-events-none absolute bottom-full right-0 mb-2 hidden group-hover/cli:block z-50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#1e1b2e] border border-white/10 rounded px-2.5 py-1.5 shadow-xl text-[11px] text-surface-300 whitespace-nowrap", children: [
54264
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500 mr-1", children: "or run from CLI:" }),
54265
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-blue-300", children: "npx api-spector mock --workspace <path>" })
54266
+ ] }) })
54267
+ ] }),
54087
54268
  /* @__PURE__ */ jsxRuntimeExports.jsx(
54088
54269
  "button",
54089
54270
  {
54090
54271
  onClick: () => {
54091
- if (running) electron$2.mockStop(mock.id);
54272
+ if (running) electron$3.mockStop(mock.id);
54092
54273
  deleteMock(mock.id);
54093
54274
  setActive2(null);
54094
54275
  },
@@ -54132,6 +54313,12 @@ function MockDetailPanel({ mockId }) {
54132
54313
  setNewRouteId(null);
54133
54314
  },
54134
54315
  onDelete: () => save({ ...mock, routes: routes.filter((r) => r.id !== route.id) }),
54316
+ onDuplicate: () => {
54317
+ const copy = { ...route, id: v4() };
54318
+ const idx = routes.indexOf(route);
54319
+ const next = [...routes.slice(0, idx + 1), copy, ...routes.slice(idx + 1)];
54320
+ save({ ...mock, routes: next });
54321
+ },
54135
54322
  initialEditing: route.id === newRouteId
54136
54323
  },
54137
54324
  route.id
@@ -54149,7 +54336,7 @@ function MockDetailPanel({ mockId }) {
54149
54336
  ] })
54150
54337
  ] });
54151
54338
  }
54152
- const { electron: electron$1 } = window;
54339
+ const { electron: electron$2 } = window;
54153
54340
  function ContractPanel() {
54154
54341
  const collections = useStore((s) => s.collections);
54155
54342
  const environments = useStore((s) => s.environments);
@@ -54180,7 +54367,7 @@ function ContractPanel() {
54180
54367
  setReport(null);
54181
54368
  try {
54182
54369
  const requests = mode === "provider" ? allRequests : contractRequests;
54183
- const result = await electron$1.runContracts({
54370
+ const result = await electron$2.runContracts({
54184
54371
  mode,
54185
54372
  requests,
54186
54373
  envVars,
@@ -54378,6 +54565,812 @@ function ContractResultsPanel() {
54378
54565
  })() })
54379
54566
  ] });
54380
54567
  }
54568
+ const { electron: electron$1 } = window;
54569
+ function useToast() {
54570
+ const [toast, setToast] = reactExports.useState(null);
54571
+ const timer = reactExports.useRef(null);
54572
+ function show(msg, ok) {
54573
+ if (timer.current) clearTimeout(timer.current);
54574
+ setToast({ msg, ok });
54575
+ timer.current = setTimeout(() => setToast(null), 3e3);
54576
+ }
54577
+ return { toast, show };
54578
+ }
54579
+ function Toast({ toast }) {
54580
+ if (!toast) return null;
54581
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mx-3 mb-2 px-2 py-1.5 rounded text-[11px] flex-shrink-0 ${toast.ok ? "bg-emerald-900/50 text-emerald-300 border border-emerald-800/50" : "bg-red-900/50 text-red-300 border border-red-800/50"}`, children: toast.msg });
54582
+ }
54583
+ function StatusBadge({ status }) {
54584
+ const map = {
54585
+ modified: { label: "M", color: "text-amber-400" },
54586
+ added: { label: "A", color: "text-emerald-400" },
54587
+ deleted: { label: "D", color: "text-red-400" },
54588
+ renamed: { label: "R", color: "text-blue-400" },
54589
+ untracked: { label: "?", color: "text-surface-500" }
54590
+ };
54591
+ const { label, color } = map[status] ?? { label: "?", color: "text-surface-500" };
54592
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono text-[11px] font-bold w-4 shrink-0 ${color}`, children: label });
54593
+ }
54594
+ function DiffViewer({ diff }) {
54595
+ if (!diff) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 p-3", children: "No changes." });
54596
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono overflow-auto p-3 leading-relaxed", children: diff.split("\n").map((line, i) => {
54597
+ const cls = line.startsWith("+") && !line.startsWith("+++") ? "text-emerald-400" : line.startsWith("-") && !line.startsWith("---") ? "text-red-400" : line.startsWith("@@") ? "text-blue-400" : "text-surface-400";
54598
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cls, children: line || " " }, i);
54599
+ }) });
54600
+ }
54601
+ function ChangesTab({ status, onRefresh }) {
54602
+ const [message, setMessage] = reactExports.useState("");
54603
+ const [diffFile, setDiffFile] = reactExports.useState(null);
54604
+ const [diff, setDiff] = reactExports.useState("");
54605
+ const [diffStaged, setDiffStaged] = reactExports.useState(false);
54606
+ const [error2, setError] = reactExports.useState(null);
54607
+ const [pushing, setPushing] = reactExports.useState(false);
54608
+ const [pulling, setPulling] = reactExports.useState(false);
54609
+ const { toast, show: showToast } = useToast();
54610
+ async function showDiff(file, staged) {
54611
+ setDiffFile(file.path);
54612
+ setDiffStaged(staged);
54613
+ try {
54614
+ const d = staged ? await electron$1.gitDiffStaged(file.path) : await electron$1.gitDiff(file.path);
54615
+ setDiff(d);
54616
+ } catch {
54617
+ setDiff("");
54618
+ }
54619
+ }
54620
+ async function stage(paths) {
54621
+ try {
54622
+ await electron$1.gitStage(paths);
54623
+ onRefresh();
54624
+ } catch (e) {
54625
+ setError(String(e));
54626
+ }
54627
+ }
54628
+ async function unstage(paths) {
54629
+ try {
54630
+ await electron$1.gitUnstage(paths);
54631
+ onRefresh();
54632
+ } catch (e) {
54633
+ setError(String(e));
54634
+ }
54635
+ }
54636
+ async function stageAll() {
54637
+ try {
54638
+ await electron$1.gitStageAll();
54639
+ onRefresh();
54640
+ } catch (e) {
54641
+ setError(String(e));
54642
+ }
54643
+ }
54644
+ async function commit() {
54645
+ if (!message.trim()) return;
54646
+ try {
54647
+ setError(null);
54648
+ await electron$1.gitCommit(message.trim());
54649
+ setMessage("");
54650
+ onRefresh();
54651
+ } catch (e) {
54652
+ setError(String(e));
54653
+ }
54654
+ }
54655
+ async function pull() {
54656
+ setPulling(true);
54657
+ try {
54658
+ await electron$1.gitPull();
54659
+ showToast("Pull successful", true);
54660
+ onRefresh();
54661
+ } catch (e) {
54662
+ showToast(String(e), false);
54663
+ } finally {
54664
+ setPulling(false);
54665
+ }
54666
+ }
54667
+ async function push2() {
54668
+ setPushing(true);
54669
+ try {
54670
+ await electron$1.gitPush(!status.remote);
54671
+ showToast("Push successful", true);
54672
+ onRefresh();
54673
+ } catch (e) {
54674
+ showToast(String(e), false);
54675
+ } finally {
54676
+ setPushing(false);
54677
+ }
54678
+ }
54679
+ const allUnstaged = [...status.unstaged, ...status.untracked];
54680
+ const hasStaged = status.staged.length > 0;
54681
+ const needsPush = status.ahead > 0;
54682
+ const needsPull = status.behind > 0;
54683
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0", children: [
54684
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 border-b border-surface-800 flex items-center gap-2 flex-shrink-0", children: [
54685
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] font-mono text-blue-300 truncate flex-1", children: [
54686
+ "⎇ ",
54687
+ status.branch || "no branch"
54688
+ ] }),
54689
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54690
+ "button",
54691
+ {
54692
+ onClick: pull,
54693
+ disabled: pulling,
54694
+ title: needsPull ? `Pull (${status.behind} behind)` : "Pull",
54695
+ className: `flex items-center gap-0.5 text-xs px-1.5 py-0.5 rounded transition-colors disabled:opacity-40 ${needsPull ? "text-amber-300 bg-amber-900/30 hover:bg-amber-900/50" : "text-surface-400 hover:text-white"}`,
54696
+ children: [
54697
+ "↓",
54698
+ needsPull ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px]", children: status.behind }) : null
54699
+ ]
54700
+ }
54701
+ ),
54702
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54703
+ "button",
54704
+ {
54705
+ onClick: push2,
54706
+ disabled: pushing,
54707
+ title: needsPush ? `Push (${status.ahead} unpushed)` : status.remote ? "Push" : "Push & set upstream",
54708
+ className: `flex items-center gap-0.5 text-xs px-1.5 py-0.5 rounded transition-colors disabled:opacity-40 ${needsPush ? "text-blue-300 bg-blue-900/30 hover:bg-blue-900/50" : "text-surface-400 hover:text-white"}`,
54709
+ children: [
54710
+ "↑",
54711
+ needsPush ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px]", children: status.ahead }) : null
54712
+ ]
54713
+ }
54714
+ )
54715
+ ] }),
54716
+ status.remote && (needsPush ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mx-3 mt-2 px-2 py-1.5 rounded text-[11px] bg-blue-900/20 text-blue-300 border border-blue-800/40 flex items-center justify-between flex-shrink-0", children: [
54717
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
54718
+ status.ahead,
54719
+ " commit",
54720
+ status.ahead !== 1 ? "s" : "",
54721
+ " to push"
54722
+ ] }),
54723
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54724
+ "button",
54725
+ {
54726
+ onClick: push2,
54727
+ disabled: pushing,
54728
+ className: "text-blue-400 hover:text-blue-200 font-medium disabled:opacity-40",
54729
+ children: "Push ↑"
54730
+ }
54731
+ )
54732
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-3 mt-2 px-2 py-1 rounded text-[11px] text-surface-600 border border-surface-800 flex-shrink-0", children: "✓ Nothing to push" })),
54733
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
54734
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto", children: [
54735
+ hasStaged && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
54736
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
54737
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: [
54738
+ "Staged (",
54739
+ status.staged.length,
54740
+ ")"
54741
+ ] }),
54742
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54743
+ "button",
54744
+ {
54745
+ onClick: () => unstage(status.staged.map((f) => f.path)),
54746
+ className: "text-[10px] text-surface-500 hover:text-white transition-colors",
54747
+ children: "Unstage all"
54748
+ }
54749
+ )
54750
+ ] }),
54751
+ status.staged.map((f) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54752
+ "div",
54753
+ {
54754
+ className: `flex items-center gap-2 px-3 py-1 hover:bg-surface-800/50 cursor-pointer group text-xs ${diffFile === f.path && diffStaged ? "bg-surface-800" : ""}`,
54755
+ onClick: () => showDiff(f, true),
54756
+ children: [
54757
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBadge, { status: f.status }),
54758
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", children: f.path }),
54759
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54760
+ "button",
54761
+ {
54762
+ onClick: (e) => {
54763
+ e.stopPropagation();
54764
+ unstage([f.path]);
54765
+ },
54766
+ className: "opacity-0 group-hover:opacity-100 text-surface-500 hover:text-amber-400 transition-all text-[10px]",
54767
+ title: "Unstage",
54768
+ children: "−"
54769
+ }
54770
+ )
54771
+ ]
54772
+ },
54773
+ f.path
54774
+ ))
54775
+ ] }),
54776
+ allUnstaged.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
54777
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
54778
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: [
54779
+ "Changes (",
54780
+ allUnstaged.length,
54781
+ ")"
54782
+ ] }),
54783
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54784
+ "button",
54785
+ {
54786
+ onClick: stageAll,
54787
+ className: "text-[10px] text-surface-500 hover:text-white transition-colors",
54788
+ children: "Stage all"
54789
+ }
54790
+ )
54791
+ ] }),
54792
+ allUnstaged.map((f) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54793
+ "div",
54794
+ {
54795
+ className: `flex items-center gap-2 px-3 py-1 hover:bg-surface-800/50 cursor-pointer group text-xs ${diffFile === f.path && !diffStaged ? "bg-surface-800" : ""}`,
54796
+ onClick: () => showDiff(f, false),
54797
+ children: [
54798
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBadge, { status: f.status }),
54799
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", children: f.path }),
54800
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54801
+ "button",
54802
+ {
54803
+ onClick: (e) => {
54804
+ e.stopPropagation();
54805
+ stage([f.path]);
54806
+ },
54807
+ className: "opacity-0 group-hover:opacity-100 text-surface-500 hover:text-emerald-400 transition-all text-[10px]",
54808
+ title: "Stage",
54809
+ children: "+"
54810
+ }
54811
+ )
54812
+ ]
54813
+ },
54814
+ f.path
54815
+ ))
54816
+ ] }),
54817
+ status.staged.length === 0 && allUnstaged.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "Working tree clean." })
54818
+ ] }),
54819
+ diffFile && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-surface-800 flex flex-col max-h-48 min-h-0", children: [
54820
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1 flex items-center justify-between flex-shrink-0 border-b border-surface-800", children: [
54821
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-400 truncate", children: diffFile }),
54822
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setDiffFile(null), className: "text-surface-600 hover:text-white text-xs ml-2", children: "✕" })
54823
+ ] }),
54824
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-auto flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DiffViewer, { diff }) })
54825
+ ] }),
54826
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-surface-800 p-3 flex-shrink-0 flex flex-col gap-2", children: [
54827
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 truncate", title: error2, children: error2 }),
54828
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54829
+ "textarea",
54830
+ {
54831
+ value: message,
54832
+ onChange: (e) => setMessage(e.target.value),
54833
+ onKeyDown: (e) => {
54834
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) commit();
54835
+ },
54836
+ placeholder: "Commit message… (⌘↵ to commit)",
54837
+ rows: 2,
54838
+ className: "w-full bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs resize-none focus:outline-none focus:border-blue-500 placeholder-surface-600"
54839
+ }
54840
+ ),
54841
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54842
+ "button",
54843
+ {
54844
+ onClick: commit,
54845
+ disabled: !message.trim() || !hasStaged,
54846
+ className: "w-full py-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 disabled:cursor-not-allowed rounded text-xs font-medium transition-colors",
54847
+ children: [
54848
+ "Commit",
54849
+ hasStaged ? ` (${status.staged.length})` : ""
54850
+ ]
54851
+ }
54852
+ )
54853
+ ] })
54854
+ ] });
54855
+ }
54856
+ function LogTab() {
54857
+ const [commits, setCommits] = reactExports.useState([]);
54858
+ const [loading, setLoading] = reactExports.useState(true);
54859
+ reactExports.useEffect(() => {
54860
+ electron$1.gitLog(100).then(setCommits).catch(() => setCommits([])).finally(() => setLoading(false));
54861
+ }, []);
54862
+ if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "Loading…" });
54863
+ if (commits.length === 0) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "No commits yet." });
54864
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: commits.map((c) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 border-b border-surface-800/50 hover:bg-surface-800/30", children: [
54865
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2", children: [
54866
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-600 shrink-0 mt-0.5", children: c.short }),
54867
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-surface-200 flex-1 line-clamp-2", children: c.message })
54868
+ ] }),
54869
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-0.5 pl-9 flex gap-2 text-[10px] text-surface-600", children: [
54870
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: c.author }),
54871
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: new Date(c.date).toLocaleDateString() })
54872
+ ] })
54873
+ ] }, c.hash)) });
54874
+ }
54875
+ function BranchesTab({ onRefresh }) {
54876
+ const [branches, setBranches] = reactExports.useState([]);
54877
+ const [remotes, setRemotes] = reactExports.useState([]);
54878
+ const [loading, setLoading] = reactExports.useState(true);
54879
+ const [newBranch, setNewBranch] = reactExports.useState("");
54880
+ const [creating, setCreating] = reactExports.useState(false);
54881
+ const [addingRemote, setAddingRemote] = reactExports.useState(false);
54882
+ const [editingRemote, setEditingRemote] = reactExports.useState(null);
54883
+ const [remoteName, setRemoteName] = reactExports.useState("origin");
54884
+ const [remoteUrl, setRemoteUrl] = reactExports.useState("");
54885
+ const [editUrl, setEditUrl] = reactExports.useState("");
54886
+ const [error2, setError] = reactExports.useState(null);
54887
+ const load = reactExports.useCallback(() => {
54888
+ Promise.all([electron$1.gitBranches(), electron$1.gitRemotes()]).then(([b, r]) => {
54889
+ setBranches(b);
54890
+ setRemotes(r);
54891
+ }).catch(() => {
54892
+ }).finally(() => setLoading(false));
54893
+ }, []);
54894
+ reactExports.useEffect(() => {
54895
+ load();
54896
+ }, [load]);
54897
+ async function checkout(name2, remote2) {
54898
+ try {
54899
+ setError(null);
54900
+ const localName = remote2 ? name2.replace(/^origin\//, "") : name2;
54901
+ await electron$1.gitCheckout(localName, false);
54902
+ load();
54903
+ onRefresh();
54904
+ } catch (e) {
54905
+ setError(String(e));
54906
+ }
54907
+ }
54908
+ async function createBranch() {
54909
+ if (!newBranch.trim()) return;
54910
+ try {
54911
+ setError(null);
54912
+ await electron$1.gitCheckout(newBranch.trim(), true);
54913
+ setNewBranch("");
54914
+ setCreating(false);
54915
+ load();
54916
+ onRefresh();
54917
+ } catch (e) {
54918
+ setError(String(e));
54919
+ }
54920
+ }
54921
+ async function addRemote() {
54922
+ if (!remoteName.trim() || !remoteUrl.trim()) return;
54923
+ try {
54924
+ setError(null);
54925
+ await electron$1.gitAddRemote(remoteName.trim(), remoteUrl.trim());
54926
+ setRemoteName("origin");
54927
+ setRemoteUrl("");
54928
+ setAddingRemote(false);
54929
+ load();
54930
+ } catch (e) {
54931
+ setError(String(e));
54932
+ }
54933
+ }
54934
+ function startEdit(r) {
54935
+ setEditingRemote(r.name);
54936
+ setEditUrl(r.url);
54937
+ }
54938
+ async function saveRemoteUrl(name2) {
54939
+ if (!editUrl.trim()) return;
54940
+ try {
54941
+ setError(null);
54942
+ await electron$1.gitSetRemoteUrl(name2, editUrl.trim());
54943
+ setEditingRemote(null);
54944
+ load();
54945
+ } catch (e) {
54946
+ setError(String(e));
54947
+ }
54948
+ }
54949
+ async function removeRemote(name2) {
54950
+ try {
54951
+ setError(null);
54952
+ await electron$1.gitRemoveRemote(name2);
54953
+ load();
54954
+ } catch (e) {
54955
+ setError(String(e));
54956
+ }
54957
+ }
54958
+ const local = branches.filter((b) => !b.remote);
54959
+ const remote = branches.filter((b) => b.remote);
54960
+ if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "Loading…" });
54961
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-y-auto", children: [
54962
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-3 py-1 truncate", children: error2 }),
54963
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 border-b border-surface-800", children: creating ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5", children: [
54964
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54965
+ "input",
54966
+ {
54967
+ autoFocus: true,
54968
+ value: newBranch,
54969
+ onChange: (e) => setNewBranch(e.target.value),
54970
+ onKeyDown: (e) => {
54971
+ if (e.key === "Enter") createBranch();
54972
+ if (e.key === "Escape") setCreating(false);
54973
+ },
54974
+ placeholder: "branch-name",
54975
+ className: "flex-1 bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500 placeholder-surface-600"
54976
+ }
54977
+ ),
54978
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: createBranch, className: "text-xs text-blue-400 hover:text-blue-300 px-1", children: "Create" }),
54979
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCreating(false), className: "text-xs text-surface-500 hover:text-white px-1", children: "✕" })
54980
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCreating(true), className: "text-[11px] text-blue-400 hover:text-blue-300 transition-colors", children: "+ New branch" }) }),
54981
+ local.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
54982
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Local" }),
54983
+ local.map((b) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54984
+ "button",
54985
+ {
54986
+ onClick: () => !b.current && checkout(b.name, false),
54987
+ className: `w-full flex items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors ${b.current ? "text-blue-300 cursor-default" : "text-surface-300 hover:bg-surface-800/50"}`,
54988
+ children: [
54989
+ b.current ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400 text-[10px]", children: "●" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3" }),
54990
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: b.name })
54991
+ ]
54992
+ },
54993
+ b.name
54994
+ ))
54995
+ ] }),
54996
+ remote.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
54997
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Remote branches" }),
54998
+ remote.map((b) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54999
+ "button",
55000
+ {
55001
+ onClick: () => checkout(b.name, true),
55002
+ className: "w-full flex items-center gap-2 px-3 py-1.5 text-left text-xs text-surface-400 hover:bg-surface-800/50 hover:text-surface-200 transition-colors",
55003
+ children: [
55004
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3" }),
55005
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: b.name })
55006
+ ]
55007
+ },
55008
+ b.name
55009
+ ))
55010
+ ] }),
55011
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "border-t border-surface-800 mt-1", children: [
55012
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
55013
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Remotes" }),
55014
+ !addingRemote && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setAddingRemote(true), className: "text-[10px] text-blue-400 hover:text-blue-300 transition-colors", children: "+ Add" })
55015
+ ] }),
55016
+ remotes.map((r) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-1 group", children: editingRemote === r.name ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
55017
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-400", children: r.name }),
55018
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5", children: [
55019
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55020
+ "input",
55021
+ {
55022
+ autoFocus: true,
55023
+ value: editUrl,
55024
+ onChange: (e) => setEditUrl(e.target.value),
55025
+ onKeyDown: (e) => {
55026
+ if (e.key === "Enter") saveRemoteUrl(r.name);
55027
+ if (e.key === "Escape") setEditingRemote(null);
55028
+ },
55029
+ className: "flex-1 bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500"
55030
+ }
55031
+ ),
55032
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => saveRemoteUrl(r.name), className: "text-xs text-blue-400 hover:text-blue-300 px-1", children: "Save" }),
55033
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setEditingRemote(null), className: "text-xs text-surface-500 hover:text-white px-1", children: "✕" })
55034
+ ] })
55035
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
55036
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-surface-300 shrink-0", children: r.name }),
55037
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-600 truncate flex-1", children: r.url }),
55038
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55039
+ "button",
55040
+ {
55041
+ onClick: () => startEdit(r),
55042
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-surface-500 hover:text-blue-400 transition-all shrink-0",
55043
+ title: "Edit URL",
55044
+ children: "✎"
55045
+ }
55046
+ ),
55047
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55048
+ "button",
55049
+ {
55050
+ onClick: () => removeRemote(r.name),
55051
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-surface-500 hover:text-red-400 transition-all shrink-0",
55052
+ title: "Remove remote",
55053
+ children: "✕"
55054
+ }
55055
+ )
55056
+ ] }) }, r.name)),
55057
+ remotes.length === 0 && !addingRemote && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 pb-2 text-[11px] text-surface-600", children: "No remotes configured." }),
55058
+ addingRemote && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 pb-3 flex flex-col gap-1.5", children: [
55059
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55060
+ "input",
55061
+ {
55062
+ value: remoteName,
55063
+ onChange: (e) => setRemoteName(e.target.value),
55064
+ placeholder: "name (e.g. origin)",
55065
+ className: "w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500 placeholder-surface-600"
55066
+ }
55067
+ ),
55068
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55069
+ "input",
55070
+ {
55071
+ autoFocus: true,
55072
+ value: remoteUrl,
55073
+ onChange: (e) => setRemoteUrl(e.target.value),
55074
+ onKeyDown: (e) => {
55075
+ if (e.key === "Enter") addRemote();
55076
+ if (e.key === "Escape") setAddingRemote(false);
55077
+ },
55078
+ placeholder: "git@github.com:user/repo.git",
55079
+ className: "w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500 placeholder-surface-600"
55080
+ }
55081
+ ),
55082
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
55083
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addRemote, className: "text-xs text-blue-400 hover:text-blue-300 transition-colors", children: "Add remote" }),
55084
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setAddingRemote(false), className: "text-xs text-surface-500 hover:text-white transition-colors", children: "Cancel" })
55085
+ ] })
55086
+ ] })
55087
+ ] })
55088
+ ] });
55089
+ }
55090
+ function detectPlatform(remotes) {
55091
+ const urls = remotes.map((r) => r.url.toLowerCase()).join(" ");
55092
+ if (urls.includes("github.com")) return "github";
55093
+ if (urls.includes("gitlab.com") || urls.includes("gitlab.")) return "gitlab";
55094
+ if (urls.includes("dev.azure.com") || urls.includes("visualstudio.com")) return "azure";
55095
+ return "unknown";
55096
+ }
55097
+ const NODE_LTS = "lts/*";
55098
+ function generateCiContent(platform, envName, tags2, secretVars) {
55099
+ const runCmd = [
55100
+ "api-spector run --workspace .",
55101
+ envName ? `--environment "${envName}"` : "",
55102
+ tags2 ? `--tags "${tags2}"` : "",
55103
+ "--output results.json"
55104
+ ].filter(Boolean).join(" ");
55105
+ if (platform === "github") {
55106
+ const secretHint = secretVars.length ? ` # ⚠ Add these secrets in: Settings → Secrets and variables → Actions
55107
+ ` + secretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
55108
+ const envBlock = secretVars.length ? "\n env:\n" + secretVars.map((v) => ` ${v}: \${{ secrets.${v} }}`).join("\n") : "";
55109
+ return `name: API Tests
55110
+
55111
+ on:
55112
+ push:
55113
+ branches: [main]
55114
+ pull_request:
55115
+
55116
+ jobs:
55117
+ api-tests:
55118
+ runs-on: ubuntu-latest
55119
+ steps:
55120
+ - uses: actions/checkout@v4
55121
+ - uses: actions/setup-node@v4
55122
+ with:
55123
+ node-version: '${NODE_LTS}'
55124
+ - run: npm install -g @testsmith/api-spector
55125
+ ${secretHint} - name: Run API tests
55126
+ run: ${runCmd}${envBlock}
55127
+ - name: Upload test results
55128
+ if: always()
55129
+ uses: actions/upload-artifact@v4
55130
+ with:
55131
+ name: api-test-results
55132
+ path: results.json
55133
+ `;
55134
+ }
55135
+ if (platform === "gitlab") {
55136
+ const secretHint = secretVars.length ? ` # ⚠ Add these in: Settings → CI/CD → Variables
55137
+ ` + secretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
55138
+ const envBlock = secretVars.length ? "\n variables:\n" + secretVars.map((v) => ` ${v}: $${v}`).join("\n") : "";
55139
+ return `api-tests:
55140
+ image: node:${NODE_LTS}
55141
+ stage: test
55142
+ before_script:
55143
+ - npm install -g @testsmith/api-spector
55144
+ ${secretHint} script:
55145
+ - ${runCmd}${envBlock}
55146
+ artifacts:
55147
+ when: always
55148
+ paths:
55149
+ - results.json
55150
+ expire_in: 30 days
55151
+ `;
55152
+ }
55153
+ if (platform === "azure") {
55154
+ const secretHint = secretVars.length ? ` # ⚠ Add these in: Pipelines → Library → Variable groups (mark as secret)
55155
+ ` + secretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
55156
+ const envBlock = secretVars.length ? "\n env:\n" + secretVars.map((v) => ` ${v}: $(${v})`).join("\n") : "";
55157
+ return `trigger:
55158
+ - main
55159
+
55160
+ pool:
55161
+ vmImage: ubuntu-latest
55162
+
55163
+ steps:
55164
+ - task: NodeTool@0
55165
+ inputs:
55166
+ versionSpec: '${NODE_LTS}.x'
55167
+ displayName: 'Use Node.js ${NODE_LTS}'
55168
+ - script: npm install -g @testsmith/api-spector
55169
+ displayName: 'Install api Spector'
55170
+ ${secretHint} - script: ${runCmd}
55171
+ displayName: 'Run API tests'${envBlock}
55172
+ - publish: results.json
55173
+ artifact: api-test-results
55174
+ displayName: 'Upload test results'
55175
+ condition: always()
55176
+ `;
55177
+ }
55178
+ return `# Unsupported platform — adapt as needed
55179
+ # ${runCmd}
55180
+ `;
55181
+ }
55182
+ function ciFilePath(platform) {
55183
+ if (platform === "github") return ".github/workflows/api-tests.yml";
55184
+ if (platform === "gitlab") return ".gitlab-ci.yml";
55185
+ if (platform === "azure") return "azure-pipelines.yml";
55186
+ return "ci.yml";
55187
+ }
55188
+ function CiTab() {
55189
+ const environments = useStore((s) => s.environments);
55190
+ const envList = Object.values(environments);
55191
+ const [remotes, setRemotes] = reactExports.useState([]);
55192
+ const [platform, setPlatform] = reactExports.useState("unknown");
55193
+ const [envId, setEnvId] = reactExports.useState("");
55194
+ const [tags2, setTags] = reactExports.useState("");
55195
+ const [preview, setPreview] = reactExports.useState("");
55196
+ const [written, setWritten] = reactExports.useState(false);
55197
+ const [error2, setError] = reactExports.useState(null);
55198
+ reactExports.useEffect(() => {
55199
+ electron$1.gitRemotes().then((r) => {
55200
+ setRemotes(r);
55201
+ const detected = detectPlatform(r);
55202
+ setPlatform(detected);
55203
+ }).catch(() => {
55204
+ });
55205
+ }, []);
55206
+ reactExports.useEffect(() => {
55207
+ const env = envList.find((e) => e.data.id === envId);
55208
+ const secretVars = env ? env.data.variables.filter((v) => v.secret && v.enabled).map((v) => v.key) : [];
55209
+ const envName = env?.data.name ?? "";
55210
+ setPreview(generateCiContent(platform, envName, tags2, secretVars));
55211
+ setWritten(false);
55212
+ }, [platform, envId, tags2, environments]);
55213
+ async function write() {
55214
+ try {
55215
+ setError(null);
55216
+ await electron$1.gitWriteCiFile(ciFilePath(platform), preview);
55217
+ setWritten(true);
55218
+ } catch (e) {
55219
+ setError(String(e));
55220
+ }
55221
+ }
55222
+ const platformLabels = {
55223
+ github: "GitHub Actions",
55224
+ gitlab: "GitLab CI",
55225
+ azure: "Azure Pipelines",
55226
+ unknown: "Unknown"
55227
+ };
55228
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-y-auto", children: [
55229
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-3 flex flex-col gap-3 border-b border-surface-800", children: [
55230
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
55231
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Platform" }),
55232
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55233
+ "select",
55234
+ {
55235
+ value: platform,
55236
+ onChange: (e) => setPlatform(e.target.value),
55237
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500",
55238
+ children: ["github", "gitlab", "azure"].map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p, children: platformLabels[p] }, p))
55239
+ }
55240
+ ),
55241
+ remotes.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-surface-600", children: [
55242
+ "Detected from remote: ",
55243
+ platformLabels[detectPlatform(remotes)]
55244
+ ] })
55245
+ ] }),
55246
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
55247
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Environment" }),
55248
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
55249
+ "select",
55250
+ {
55251
+ value: envId,
55252
+ onChange: (e) => setEnvId(e.target.value),
55253
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500",
55254
+ children: [
55255
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "— none —" }),
55256
+ envList.map((e) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: e.data.id, children: e.data.name }, e.data.id))
55257
+ ]
55258
+ }
55259
+ ),
55260
+ envId && (() => {
55261
+ const env = envList.find((e) => e.data.id === envId);
55262
+ const secrets = env?.data.variables.filter((v) => v.secret && v.enabled) ?? [];
55263
+ return secrets.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-amber-400/80", children: [
55264
+ secrets.length,
55265
+ " secret variable",
55266
+ secrets.length !== 1 ? "s" : "",
55267
+ " will be mapped to CI secrets"
55268
+ ] }) : null;
55269
+ })()
55270
+ ] }),
55271
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
55272
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Tags (optional)" }),
55273
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55274
+ "input",
55275
+ {
55276
+ value: tags2,
55277
+ onChange: (e) => setTags(e.target.value),
55278
+ placeholder: "e.g. smoke, regression",
55279
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500 placeholder-surface-600"
55280
+ }
55281
+ )
55282
+ ] }),
55283
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400", children: error2 }),
55284
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
55285
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
55286
+ "button",
55287
+ {
55288
+ onClick: write,
55289
+ className: "flex-1 py-1.5 bg-blue-600 hover:bg-blue-500 rounded text-xs font-medium transition-colors",
55290
+ children: [
55291
+ "Write ",
55292
+ ciFilePath(platform)
55293
+ ]
55294
+ }
55295
+ ),
55296
+ written && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-emerald-400", children: "✓ Written" })
55297
+ ] })
55298
+ ] }),
55299
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-auto", children: [
55300
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 pt-2 pb-1 text-[10px] uppercase tracking-widest text-surface-600 font-semibold", children: "Preview" }),
55301
+ /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "px-3 pb-3 text-[10px] font-mono text-surface-400 leading-relaxed whitespace-pre-wrap", children: preview })
55302
+ ] })
55303
+ ] });
55304
+ }
55305
+ function GitPanel() {
55306
+ const [isRepo, setIsRepo] = reactExports.useState(null);
55307
+ const [status, setStatus] = reactExports.useState(null);
55308
+ const [tab, setTab] = reactExports.useState("changes");
55309
+ const [loading, setLoading] = reactExports.useState(true);
55310
+ const refresh = reactExports.useCallback(async () => {
55311
+ try {
55312
+ const repo = await electron$1.gitIsRepo();
55313
+ setIsRepo(repo);
55314
+ if (repo) setStatus(await electron$1.gitStatus());
55315
+ } catch {
55316
+ setIsRepo(false);
55317
+ } finally {
55318
+ setLoading(false);
55319
+ }
55320
+ }, []);
55321
+ reactExports.useEffect(() => {
55322
+ refresh();
55323
+ }, [refresh]);
55324
+ if (loading) {
55325
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-xs text-surface-500", children: "Loading…" });
55326
+ }
55327
+ if (!isRepo) {
55328
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 px-4 text-center", children: [
55329
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: "Not a git repository." }),
55330
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55331
+ "button",
55332
+ {
55333
+ onClick: async () => {
55334
+ await electron$1.gitInit();
55335
+ refresh();
55336
+ },
55337
+ className: "px-3 py-1.5 bg-surface-800 hover:bg-surface-700 rounded text-xs transition-colors",
55338
+ children: "git init"
55339
+ }
55340
+ )
55341
+ ] });
55342
+ }
55343
+ const totalChanges = status ? status.staged.length + status.unstaged.length + status.untracked.length : 0;
55344
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0", children: [
55345
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-surface-800 flex-shrink-0", children: [
55346
+ ["changes", "log", "branches", "ci"].map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
55347
+ "button",
55348
+ {
55349
+ onClick: () => setTab(t2),
55350
+ className: `flex-1 py-1.5 text-[11px] capitalize transition-colors border-b-2 -mb-px ${tab === t2 ? "border-blue-500 text-white" : "border-transparent text-surface-400 hover:text-white"}`,
55351
+ children: [
55352
+ t2,
55353
+ t2 === "changes" && totalChanges > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1 bg-surface-700 text-surface-300 rounded px-1 text-[9px]", children: totalChanges })
55354
+ ]
55355
+ },
55356
+ t2
55357
+ )),
55358
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55359
+ "button",
55360
+ {
55361
+ onClick: refresh,
55362
+ title: "Refresh",
55363
+ className: "px-2 text-surface-600 hover:text-surface-300 transition-colors border-b-2 border-transparent -mb-px",
55364
+ children: "↺"
55365
+ }
55366
+ )
55367
+ ] }),
55368
+ tab === "changes" && status && /* @__PURE__ */ jsxRuntimeExports.jsx(ChangesTab, { status, onRefresh: refresh }),
55369
+ tab === "log" && /* @__PURE__ */ jsxRuntimeExports.jsx(LogTab, {}),
55370
+ tab === "branches" && /* @__PURE__ */ jsxRuntimeExports.jsx(BranchesTab, { onRefresh: refresh }),
55371
+ tab === "ci" && /* @__PURE__ */ jsxRuntimeExports.jsx(CiTab, {})
55372
+ ] });
55373
+ }
54381
55374
  function CommandPalette() {
54382
55375
  const open = useStore((s) => s.commandPaletteOpen);
54383
55376
  const setOpen = useStore((s) => s.setCommandPaletteOpen);
@@ -54528,6 +55521,15 @@ function IconMock() {
54528
55521
  function IconContract() {
54529
55522
  return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 20 20", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { fillRule: "evenodd", d: "M10 2a1 1 0 011 1v1.323l3.954 1.582 1.599-.8a1 1 0 01.894 1.79l-1.233.616 1.738 5.42a1 1 0 01-.285 1.05A3.989 3.989 0 0115 14a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.715-5.349L11 5.477V17H13a1 1 0 110 2H7a1 1 0 110-2h2V5.477L6.237 6.582l1.715 5.349a1 1 0 01-.285 1.05A3.989 3.989 0 015 14a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.738-5.42-1.233-.617a1 1 0 01.894-1.788l1.599.799L9 3.323V3a1 1 0 011-1z", clipRule: "evenodd" }) });
54530
55523
  }
55524
+ function IconGit() {
55525
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "18", height: "18", children: [
55526
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "6", cy: "6", r: "2.5" }),
55527
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "6", cy: "18", r: "2.5" }),
55528
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "18", cy: "6", r: "2.5" }),
55529
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "8.5", x2: "6", y2: "15.5" }),
55530
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 8.5 C6 13 18 10 18 8.5" })
55531
+ ] });
55532
+ }
54531
55533
  function ActivityBarBtn({
54532
55534
  active,
54533
55535
  onClick,
@@ -54535,18 +55537,20 @@ function ActivityBarBtn({
54535
55537
  badge,
54536
55538
  children
54537
55539
  }) {
54538
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
54539
- "button",
54540
- {
54541
- onClick,
54542
- title: title2,
54543
- className: `relative w-10 h-10 flex items-center justify-center rounded transition-colors ${active ? "text-white bg-surface-800" : "text-surface-600 hover:text-surface-300 hover:bg-surface-800/50"}`,
54544
- children: [
54545
- children,
54546
- badge !== void 0 && badge > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute top-1 right-1 text-[9px] bg-surface-600 text-white rounded-full w-3.5 h-3.5 flex items-center justify-center font-medium leading-none", children: badge > 9 ? "9+" : badge })
54547
- ]
54548
- }
54549
- );
55540
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative group/ab", children: [
55541
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
55542
+ "button",
55543
+ {
55544
+ onClick,
55545
+ className: `relative w-10 h-10 flex items-center justify-center rounded transition-colors ${active ? "text-white bg-surface-800" : "text-surface-600 hover:text-surface-300 hover:bg-surface-800/50"}`,
55546
+ children: [
55547
+ children,
55548
+ badge !== void 0 && badge > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute top-1 right-1 text-[9px] bg-surface-600 text-white rounded-full w-3.5 h-3.5 flex items-center justify-center font-medium leading-none", children: badge > 9 ? "9+" : badge })
55549
+ ]
55550
+ }
55551
+ ),
55552
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pointer-events-none absolute left-full top-1/2 -translate-y-1/2 ml-2 z-50 hidden group-hover/ab:block", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap rounded px-2 py-1 text-[11px] bg-[#1e1b2e] text-gray-200 border border-white/10 shadow-lg", children: title2 }) })
55553
+ ] });
54550
55554
  }
54551
55555
  const TAB_METHOD_COLORS = {
54552
55556
  GET: "text-emerald-400",
@@ -54634,7 +55638,7 @@ function App() {
54634
55638
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
54635
55639
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
54636
55640
  "v",
54637
- "0.0.9"
55641
+ "0.1.1"
54638
55642
  ] })
54639
55643
  ] }) }),
54640
55644
  /* @__PURE__ */ jsxRuntimeExports.jsx(Toolbar, { onOpenDocs: () => setDocsModalOpen(true) }),
@@ -54676,11 +55680,20 @@ function App() {
54676
55680
  title: "Contract testing",
54677
55681
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconContract, {})
54678
55682
  }
55683
+ ),
55684
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55685
+ ActivityBarBtn,
55686
+ {
55687
+ active: sidebarOpen && sidebarTab === "git",
55688
+ onClick: () => selectPanel("git"),
55689
+ title: "Git",
55690
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconGit, {})
55691
+ }
54679
55692
  )
54680
55693
  ] }),
54681
55694
  sidebarOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("aside", { className: "w-64 flex-shrink-0 border-r border-surface-800 flex flex-col overflow-hidden", children: [
54682
55695
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 flex items-center justify-between border-b border-surface-800 flex-shrink-0", children: [
54683
- /* @__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" }),
55696
+ /* @__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" : sidebarTab === "git" ? "Git" : "Contracts" }),
54684
55697
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
54685
55698
  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 }),
54686
55699
  sidebarTab === "collections" && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -54703,7 +55716,7 @@ function App() {
54703
55716
  )
54704
55717
  ] })
54705
55718
  ] }),
54706
- sidebarTab === "collections" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionTree, {}) : sidebarTab === "history" ? /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryPanel, {}) : sidebarTab === "mocks" ? /* @__PURE__ */ jsxRuntimeExports.jsx(MockPanel, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractPanel, {})
55719
+ sidebarTab === "collections" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionTree, {}) : sidebarTab === "history" ? /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryPanel, {}) : sidebarTab === "mocks" ? /* @__PURE__ */ jsxRuntimeExports.jsx(MockPanel, {}) : sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx(GitPanel, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractPanel, {})
54707
55720
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
54708
55721
  "button",
54709
55722
  {