@testsmith/api-spector 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13317,6 +13317,112 @@ function envRelPath(name2, id2) {
13317
13317
  function safeName(name2) {
13318
13318
  return name2.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
13319
13319
  }
13320
+ const WS_MESSAGE_CAP = 1e3;
13321
+ const createWsSlice = (set2) => ({
13322
+ wsConnections: {},
13323
+ setWsStatus: (requestId, status, error2) => set2((s) => {
13324
+ if (!s.wsConnections[requestId]) {
13325
+ s.wsConnections[requestId] = { status, messages: [], error: error2 };
13326
+ } else {
13327
+ s.wsConnections[requestId].status = status;
13328
+ s.wsConnections[requestId].error = error2;
13329
+ }
13330
+ }),
13331
+ addWsMessage: (requestId, message) => set2((s) => {
13332
+ const conn = s.wsConnections[requestId];
13333
+ if (!conn) {
13334
+ s.wsConnections[requestId] = { status: "connected", messages: [message] };
13335
+ return;
13336
+ }
13337
+ conn.messages.push(message);
13338
+ if (conn.messages.length > WS_MESSAGE_CAP) {
13339
+ conn.messages.splice(0, conn.messages.length - WS_MESSAGE_CAP);
13340
+ }
13341
+ }),
13342
+ clearWsMessages: (requestId) => set2((s) => {
13343
+ if (s.wsConnections[requestId]) s.wsConnections[requestId].messages = [];
13344
+ })
13345
+ });
13346
+ const HISTORY_CAP = 200;
13347
+ const createHistorySlice = (set2) => ({
13348
+ history: [],
13349
+ addHistoryEntry: (entry) => set2((s) => {
13350
+ s.history.unshift(entry);
13351
+ if (s.history.length > HISTORY_CAP) s.history.length = HISTORY_CAP;
13352
+ }),
13353
+ clearHistory: () => set2((s) => {
13354
+ s.history = [];
13355
+ })
13356
+ });
13357
+ const createRunnerSlice = (set2) => ({
13358
+ runnerModal: { open: false, collectionId: null, folderId: null, filterTags: [] },
13359
+ runnerResults: [],
13360
+ runnerRunning: false,
13361
+ openRunner: (collectionId, folderId = null, filterTags = []) => set2((s) => {
13362
+ s.runnerModal = { open: true, collectionId, folderId, filterTags };
13363
+ s.runnerResults = [];
13364
+ }),
13365
+ closeRunner: () => set2((s) => {
13366
+ s.runnerModal.open = false;
13367
+ s.runnerRunning = false;
13368
+ }),
13369
+ setRunnerResults: (results) => set2((s) => {
13370
+ s.runnerResults = results;
13371
+ }),
13372
+ patchRunnerResult: (idx, patch) => set2((s) => {
13373
+ if (s.runnerResults[idx]) Object.assign(s.runnerResults[idx], patch);
13374
+ }),
13375
+ setRunnerRunning: (v) => set2((s) => {
13376
+ s.runnerRunning = v;
13377
+ })
13378
+ });
13379
+ const createRecorderSlice = (set2) => ({
13380
+ recorderOpen: false,
13381
+ recorderRunning: false,
13382
+ recorderUpstream: "",
13383
+ recorderPort: 4001,
13384
+ recorderTargetMockId: "",
13385
+ setRecorderOpen: (open) => set2((s) => {
13386
+ s.recorderOpen = open;
13387
+ }),
13388
+ setRecorderRunning: (running) => set2((s) => {
13389
+ s.recorderRunning = running;
13390
+ }),
13391
+ setRecorderUpstream: (url) => set2((s) => {
13392
+ s.recorderUpstream = url;
13393
+ }),
13394
+ setRecorderPort: (port) => set2((s) => {
13395
+ s.recorderPort = port;
13396
+ }),
13397
+ setRecorderTargetMockId: (id2) => set2((s) => {
13398
+ s.recorderTargetMockId = id2;
13399
+ })
13400
+ });
13401
+ const createContractSlice = (set2) => ({
13402
+ lastContractReport: null,
13403
+ contractSnapshots: {},
13404
+ activeContractSnapshotRelPath: null,
13405
+ setLastContractReport: (r) => set2((s) => {
13406
+ s.lastContractReport = r;
13407
+ }),
13408
+ loadContractSnapshot: (relPath, snapshot) => set2((s) => {
13409
+ s.contractSnapshots[relPath] = snapshot;
13410
+ if (s.workspace) {
13411
+ if (!s.workspace.contracts) s.workspace.contracts = [];
13412
+ if (!s.workspace.contracts.includes(relPath)) s.workspace.contracts.push(relPath);
13413
+ }
13414
+ }),
13415
+ removeContractSnapshot: (relPath) => set2((s) => {
13416
+ delete s.contractSnapshots[relPath];
13417
+ if (s.activeContractSnapshotRelPath === relPath) s.activeContractSnapshotRelPath = null;
13418
+ if (s.workspace?.contracts) {
13419
+ s.workspace.contracts = s.workspace.contracts.filter((p2) => p2 !== relPath);
13420
+ }
13421
+ }),
13422
+ setActiveContractSnapshot: (relPath) => set2((s) => {
13423
+ s.activeContractSnapshotRelPath = relPath;
13424
+ })
13425
+ });
13320
13426
  function makeRequest(override = {}) {
13321
13427
  return {
13322
13428
  id: v4(),
@@ -13387,7 +13493,7 @@ function findFolderPath(root2, requestId) {
13387
13493
  }
13388
13494
  return [];
13389
13495
  }
13390
- function makeTab(requestId, collectionId) {
13496
+ function makeTab(requestId, collectionId, opts = {}) {
13391
13497
  return {
13392
13498
  id: v4(),
13393
13499
  requestId,
@@ -13397,12 +13503,31 @@ function makeTab(requestId, collectionId) {
13397
13503
  lastSentRequest: null,
13398
13504
  lastHookResults: null,
13399
13505
  isSending: false,
13400
- requestTab: "params",
13401
- scriptTab: "pre"
13506
+ // SOAP requests: 'params' isn't even shown for SOAP and 'body' renders the
13507
+ // WSDL-driven SoapEditor — that's the primary surface, so jump there.
13508
+ // HTTP/WebSocket: keep the existing 'params' default.
13509
+ requestTab: opts.protocol === "soap" ? "body" : "params",
13510
+ // Default to the post-response tab — that's where the typical workflow
13511
+ // (assertions, extracting tokens, saving variables) lives.
13512
+ scriptTab: "post"
13402
13513
  };
13403
13514
  }
13515
+ function protocolFor(state, requestId) {
13516
+ for (const c of Object.values(state.collections)) {
13517
+ const r = c.data.requests[requestId];
13518
+ if (r) return r.protocol;
13519
+ }
13520
+ return void 0;
13521
+ }
13404
13522
  const useStore = create()(
13405
- immer((set2) => ({
13523
+ immer((set2, get2, api) => ({
13524
+ // ── Slice composition ─────────────────────────────────────────────────────
13525
+ ...createWsSlice(set2),
13526
+ ...createHistorySlice(set2),
13527
+ ...createRunnerSlice(set2),
13528
+ ...createRecorderSlice(set2),
13529
+ ...createContractSlice(set2),
13530
+ // ── Initial state for the parts that haven't been sliced yet ──────────────
13406
13531
  workspace: null,
13407
13532
  workspacePath: null,
13408
13533
  collections: {},
@@ -13416,24 +13541,15 @@ const useStore = create()(
13416
13541
  showGeneratorPanel: false,
13417
13542
  theme: localStorage.getItem("theme") ?? "dark",
13418
13543
  zoom: Number(localStorage.getItem("zoom") ?? "1.1"),
13419
- history: [],
13420
13544
  sidebarTab: "collections",
13421
13545
  workspaceSettingsOpen: false,
13422
13546
  mocks: {},
13423
13547
  activeMockId: null,
13424
13548
  mockLogs: {},
13425
- recorderOpen: false,
13426
- recorderRunning: false,
13427
- recorderUpstream: "",
13428
- recorderPort: 4001,
13429
- recorderTargetMockId: "",
13430
- runnerModal: { open: false, collectionId: null, folderId: null, filterTags: [] },
13431
- runnerResults: [],
13432
- runnerRunning: false,
13433
13549
  commandPaletteOpen: false,
13434
13550
  pinnedResponse: null,
13435
- lastContractReport: null,
13436
- wsConnections: {},
13551
+ activeGitDiff: null,
13552
+ quickInsertsOpen: true,
13437
13553
  // ── Workspace ─────────────────────────────────────────────────────────────
13438
13554
  setWorkspace: (ws2, path) => set2((s) => {
13439
13555
  s.workspace = ws2;
@@ -13458,6 +13574,8 @@ const useStore = create()(
13458
13574
  s.wsConnections = {};
13459
13575
  s.pinnedResponse = null;
13460
13576
  s.lastContractReport = null;
13577
+ s.contractSnapshots = {};
13578
+ s.activeContractSnapshotRelPath = null;
13461
13579
  }),
13462
13580
  updateWorkspaceSettings: (settings) => set2((s) => {
13463
13581
  if (s.workspace) s.workspace.settings = settings;
@@ -13471,6 +13589,7 @@ const useStore = create()(
13471
13589
  if (!req.params) req.params = [];
13472
13590
  if (!req.body) req.body = { mode: "none" };
13473
13591
  if (!req.auth) req.auth = { type: "none" };
13592
+ if (!req.protocol && req.body.mode === "soap") req.protocol = "soap";
13474
13593
  }
13475
13594
  function sanitizeFolder(f) {
13476
13595
  if (!f.folders) f.folders = [];
@@ -13493,7 +13612,7 @@ const useStore = create()(
13493
13612
  s.activeTabId = existing.id;
13494
13613
  s.activeCollectionId = collectionId;
13495
13614
  } else {
13496
- const tab = makeTab(requestId, collectionId);
13615
+ const tab = makeTab(requestId, collectionId, { protocol: protocolFor(s, requestId) });
13497
13616
  s.tabs.push(tab);
13498
13617
  s.activeTabId = tab.id;
13499
13618
  s.activeCollectionId = collectionId;
@@ -13566,7 +13685,7 @@ const useStore = create()(
13566
13685
  s.activeTabId = existing.id;
13567
13686
  s.activeCollectionId = existing.collectionId;
13568
13687
  } else {
13569
- const tab = makeTab(id2, collectionId);
13688
+ const tab = makeTab(id2, collectionId, { protocol: protocolFor(s, id2) });
13570
13689
  s.tabs.push(tab);
13571
13690
  s.activeTabId = tab.id;
13572
13691
  s.activeCollectionId = collectionId;
@@ -13682,19 +13801,26 @@ const useStore = create()(
13682
13801
  entry.dirty = true;
13683
13802
  s.activeCollectionId = collectionId;
13684
13803
  }),
13685
- deleteCollection: (id2) => set2((s) => {
13686
- const relPath = s.collections[id2]?.relPath;
13687
- delete s.collections[id2];
13688
- if (s.workspace && relPath) {
13689
- s.workspace.collections = s.workspace.collections.filter((p2) => p2 !== relPath);
13690
- }
13691
- s.tabs = s.tabs.filter((t2) => t2.collectionId !== id2);
13692
- if (s.activeCollectionId === id2) {
13693
- s.activeCollectionId = Object.keys(s.collections)[0] ?? null;
13694
- const activeTab = s.tabs.find((t2) => t2.id === s.activeTabId);
13695
- if (!activeTab) s.activeTabId = s.tabs[0]?.id ?? null;
13804
+ deleteCollection: (id2) => {
13805
+ const relPath = useStore.getState().collections[id2]?.relPath;
13806
+ if (relPath) {
13807
+ window.electron.deleteWorkspaceFile(relPath).catch((err) => {
13808
+ console.warn("deleteCollection: could not remove file", relPath, err);
13809
+ });
13696
13810
  }
13697
- }),
13811
+ set2((s) => {
13812
+ delete s.collections[id2];
13813
+ if (s.workspace && relPath) {
13814
+ s.workspace.collections = s.workspace.collections.filter((p2) => p2 !== relPath);
13815
+ }
13816
+ s.tabs = s.tabs.filter((t2) => t2.collectionId !== id2);
13817
+ if (s.activeCollectionId === id2) {
13818
+ s.activeCollectionId = Object.keys(s.collections)[0] ?? null;
13819
+ const activeTab = s.tabs.find((t2) => t2.id === s.activeTabId);
13820
+ if (!activeTab) s.activeTabId = s.tabs[0]?.id ?? null;
13821
+ }
13822
+ });
13823
+ },
13698
13824
  updateCollectionDataSet: (id2, ds) => set2((s) => {
13699
13825
  if (!s.collections[id2]) return;
13700
13826
  s.collections[id2].data.dataSet = ds;
@@ -13826,7 +13952,7 @@ const useStore = create()(
13826
13952
  folder.requestIds.splice(idx + 1, 0, copy.id);
13827
13953
  }
13828
13954
  s.collections[collectionId].dirty = true;
13829
- const tab = makeTab(copy.id, collectionId);
13955
+ const tab = makeTab(copy.id, collectionId, { protocol: copy.protocol });
13830
13956
  s.tabs.push(tab);
13831
13957
  s.activeTabId = tab.id;
13832
13958
  }),
@@ -13918,10 +14044,13 @@ const useStore = create()(
13918
14044
  setPinnedResponse: (r) => set2((s) => {
13919
14045
  s.pinnedResponse = r;
13920
14046
  }),
13921
- // ── Contract testing ──────────────────────────────────────────────────────
13922
- setLastContractReport: (r) => set2((s) => {
13923
- s.lastContractReport = r;
14047
+ setActiveGitDiff: (d) => set2((s) => {
14048
+ s.activeGitDiff = d;
13924
14049
  }),
14050
+ setQuickInsertsOpen: (open) => set2((s) => {
14051
+ s.quickInsertsOpen = open;
14052
+ }),
14053
+ // Contract testing + contract snapshots are now in slices/contract-slice.ts.
13925
14054
  // ── Inherited auth/headers ────────────────────────────────────────────────
13926
14055
  getInheritedAuthAndHeaders: (requestId) => {
13927
14056
  const state = useStore.getState();
@@ -13954,14 +14083,37 @@ const useStore = create()(
13954
14083
  s.activeEnvironmentId = env.id;
13955
14084
  if (s.workspace) s.workspace.environments.push(relPath);
13956
14085
  }),
13957
- deleteEnvironment: (id2) => set2((s) => {
13958
- const relPath = s.environments[id2]?.relPath;
13959
- delete s.environments[id2];
13960
- if (s.activeEnvironmentId === id2) s.activeEnvironmentId = null;
13961
- if (relPath && s.workspace) {
13962
- s.workspace.environments = s.workspace.environments.filter((p2) => p2 !== relPath);
13963
- }
14086
+ duplicateEnvironment: (id2) => set2((s) => {
14087
+ const src = s.environments[id2];
14088
+ if (!src) return;
14089
+ const existingNames = Object.values(s.environments).map((e) => e.data.name);
14090
+ const newName = uniqueName(src.data.name + " (copy)", existingNames);
14091
+ const newId = v4();
14092
+ const env = {
14093
+ ...JSON.parse(JSON.stringify(src.data)),
14094
+ id: newId,
14095
+ name: newName
14096
+ };
14097
+ const relPath = `environments/${newName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}.env.json`;
14098
+ s.environments[env.id] = { relPath, data: env };
14099
+ s.activeEnvironmentId = env.id;
14100
+ if (s.workspace) s.workspace.environments.push(relPath);
13964
14101
  }),
14102
+ deleteEnvironment: (id2) => {
14103
+ const relPath = useStore.getState().environments[id2]?.relPath;
14104
+ if (relPath) {
14105
+ window.electron.deleteWorkspaceFile(relPath).catch((err) => {
14106
+ console.warn("deleteEnvironment: could not remove file", relPath, err);
14107
+ });
14108
+ }
14109
+ set2((s) => {
14110
+ delete s.environments[id2];
14111
+ if (s.activeEnvironmentId === id2) s.activeEnvironmentId = null;
14112
+ if (relPath && s.workspace) {
14113
+ s.workspace.environments = s.workspace.environments.filter((p2) => p2 !== relPath);
14114
+ }
14115
+ });
14116
+ },
13965
14117
  // ── Globals ───────────────────────────────────────────────────────────────
13966
14118
  setGlobals: (globals) => set2((s) => {
13967
14119
  s.globals = globals;
@@ -14022,32 +14174,8 @@ const useStore = create()(
14022
14174
  }
14023
14175
  window.electron.setZoomFactor(z);
14024
14176
  }),
14025
- // ── Runner modal ──────────────────────────────────────────────────────────
14026
- openRunner: (collectionId, folderId = null, filterTags = []) => set2((s) => {
14027
- s.runnerModal = { open: true, collectionId, folderId, filterTags };
14028
- s.runnerResults = [];
14029
- }),
14030
- closeRunner: () => set2((s) => {
14031
- s.runnerModal.open = false;
14032
- s.runnerRunning = false;
14033
- }),
14034
- setRunnerResults: (results) => set2((s) => {
14035
- s.runnerResults = results;
14036
- }),
14037
- patchRunnerResult: (idx, patch) => set2((s) => {
14038
- if (s.runnerResults[idx]) Object.assign(s.runnerResults[idx], patch);
14039
- }),
14040
- setRunnerRunning: (v) => set2((s) => {
14041
- s.runnerRunning = v;
14042
- }),
14043
- // ── History ───────────────────────────────────────────────────────────────
14044
- addHistoryEntry: (entry) => set2((s) => {
14045
- s.history.unshift(entry);
14046
- if (s.history.length > 200) s.history.length = 200;
14047
- }),
14048
- clearHistory: () => set2((s) => {
14049
- s.history = [];
14050
- }),
14177
+ // Runner modal lives in slices/runner-slice.ts.
14178
+ // History lives in slices/history-slice.ts.
14051
14179
  setSidebarTab: (tab) => set2((s) => {
14052
14180
  s.sidebarTab = tab;
14053
14181
  }),
@@ -14074,14 +14202,21 @@ const useStore = create()(
14074
14202
  updateMock: (id2, data) => set2((s) => {
14075
14203
  if (s.mocks[id2]) s.mocks[id2].data = data;
14076
14204
  }),
14077
- deleteMock: (id2) => set2((s) => {
14078
- const relPath = s.mocks[id2]?.relPath;
14079
- delete s.mocks[id2];
14080
- if (s.workspace?.mocks && relPath) {
14081
- s.workspace.mocks = s.workspace.mocks.filter((p2) => p2 !== relPath);
14205
+ deleteMock: (id2) => {
14206
+ const relPath = useStore.getState().mocks[id2]?.relPath;
14207
+ if (relPath) {
14208
+ window.electron.deleteWorkspaceFile(relPath).catch((err) => {
14209
+ console.warn("deleteMock: could not remove file", relPath, err);
14210
+ });
14082
14211
  }
14083
- if (s.activeMockId === id2) s.activeMockId = null;
14084
- }),
14212
+ set2((s) => {
14213
+ delete s.mocks[id2];
14214
+ if (s.workspace?.mocks && relPath) {
14215
+ s.workspace.mocks = s.workspace.mocks.filter((p2) => p2 !== relPath);
14216
+ }
14217
+ if (s.activeMockId === id2) s.activeMockId = null;
14218
+ });
14219
+ },
14085
14220
  setMockRunning: (id2, running) => set2((s) => {
14086
14221
  if (s.mocks[id2]) s.mocks[id2].running = running;
14087
14222
  }),
@@ -14095,47 +14230,12 @@ const useStore = create()(
14095
14230
  }),
14096
14231
  clearMockLogs: (serverId) => set2((s) => {
14097
14232
  s.mockLogs[serverId] = [];
14098
- }),
14099
- // ── Recorder ──────────────────────────────────────────────────────────────
14100
- setRecorderOpen: (open) => set2((s) => {
14101
- s.recorderOpen = open;
14102
- }),
14103
- setRecorderRunning: (running) => set2((s) => {
14104
- s.recorderRunning = running;
14105
- }),
14106
- setRecorderUpstream: (url) => set2((s) => {
14107
- s.recorderUpstream = url;
14108
- }),
14109
- setRecorderPort: (port) => set2((s) => {
14110
- s.recorderPort = port;
14111
- }),
14112
- setRecorderTargetMockId: (id2) => set2((s) => {
14113
- s.recorderTargetMockId = id2;
14114
- }),
14115
- // ── WebSocket ─────────────────────────────────────────────────────────────
14116
- setWsStatus: (requestId, status, error2) => set2((s) => {
14117
- if (!s.wsConnections[requestId]) {
14118
- s.wsConnections[requestId] = { status, messages: [], error: error2 };
14119
- } else {
14120
- s.wsConnections[requestId].status = status;
14121
- s.wsConnections[requestId].error = error2;
14122
- }
14123
- }),
14124
- addWsMessage: (requestId, message) => set2((s) => {
14125
- if (!s.wsConnections[requestId]) {
14126
- s.wsConnections[requestId] = { status: "connected", messages: [message] };
14127
- } else {
14128
- s.wsConnections[requestId].messages.push(message);
14129
- }
14130
- }),
14131
- clearWsMessages: (requestId) => set2((s) => {
14132
- if (s.wsConnections[requestId]) {
14133
- s.wsConnections[requestId].messages = [];
14134
- }
14135
14233
  })
14234
+ // Recorder lives in slices/recorder-slice.ts.
14235
+ // WebSocket actions live in slices/ws-slice.ts.
14136
14236
  }))
14137
14237
  );
14138
- const { electron: electron$p } = window;
14238
+ const { electron: electron$q } = window;
14139
14239
  function useAutoSave() {
14140
14240
  const collections = useStore((s) => s.collections);
14141
14241
  useStore((s) => s.environments);
@@ -14151,7 +14251,7 @@ function useAutoSave() {
14151
14251
  for (const { relPath, data, dirty } of dirtyCollections) {
14152
14252
  if (!dirty) continue;
14153
14253
  try {
14154
- await electron$p.saveCollection(relPath, data);
14254
+ await electron$q.saveCollection(relPath, data);
14155
14255
  markCollectionClean(data.id);
14156
14256
  } catch (e) {
14157
14257
  console.error("Auto-save failed for", relPath, e);
@@ -14167,7 +14267,7 @@ function useAutoSave() {
14167
14267
  if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
14168
14268
  wsTimerRef.current = setTimeout(async () => {
14169
14269
  try {
14170
- await electron$p.saveWorkspace(workspace);
14270
+ await electron$q.saveWorkspace(workspace);
14171
14271
  } catch {
14172
14272
  }
14173
14273
  }, 300);
@@ -14176,12 +14276,13 @@ function useAutoSave() {
14176
14276
  };
14177
14277
  }, [workspace]);
14178
14278
  }
14179
- const { electron: electron$o } = window;
14279
+ const { electron: electron$p } = window;
14180
14280
  function useWorkspaceLoader() {
14181
14281
  const loadCollection = useStore((s) => s.loadCollection);
14182
14282
  const loadEnvironment = useStore((s) => s.loadEnvironment);
14183
14283
  const loadMock = useStore((s) => s.loadMock);
14184
14284
  const setActiveCollection = useStore((s) => s.setActiveCollection);
14285
+ const loadContractSnapshot = useStore((s) => s.loadContractSnapshot);
14185
14286
  const setTheme = useStore((s) => s.setTheme);
14186
14287
  const setZoom = useStore((s) => s.setZoom);
14187
14288
  const applyWorkspace = reactExports.useCallback(async (ws2, path) => {
@@ -14199,62 +14300,40 @@ function useWorkspaceLoader() {
14199
14300
  if (typeof ws2.settings?.zoom === "number") setZoom(ws2.settings.zoom);
14200
14301
  for (const colPath of ws2.collections) {
14201
14302
  try {
14202
- const col = await electron$o.loadCollection(colPath);
14303
+ const col = await electron$p.loadCollection(colPath);
14203
14304
  loadCollection(colPath, col);
14204
14305
  } catch {
14205
14306
  }
14206
14307
  }
14207
14308
  for (const envPath of ws2.environments) {
14208
14309
  try {
14209
- const env = await electron$o.loadEnvironment(envPath);
14310
+ const env = await electron$p.loadEnvironment(envPath);
14210
14311
  loadEnvironment(envPath, env);
14211
14312
  } catch {
14212
14313
  }
14213
14314
  }
14214
14315
  for (const relPath of ws2.mocks ?? []) {
14215
14316
  try {
14216
- const mockData = await electron$o.loadMock(relPath);
14317
+ const mockData = await electron$p.loadMock(relPath);
14217
14318
  loadMock(relPath, mockData);
14218
14319
  } catch {
14219
14320
  }
14220
14321
  }
14322
+ try {
14323
+ const snapshots = await electron$p.listContractSnapshots(ws2.contracts ?? []);
14324
+ for (const { relPath, snapshot } of snapshots) loadContractSnapshot(relPath, snapshot);
14325
+ } catch {
14326
+ }
14221
14327
  if (ws2.collections.length > 0) {
14222
14328
  try {
14223
- const firstCol = await electron$o.loadCollection(ws2.collections[0]);
14329
+ const firstCol = await electron$p.loadCollection(ws2.collections[0]);
14224
14330
  setActiveCollection(firstCol.id);
14225
14331
  } catch {
14226
14332
  }
14227
14333
  }
14228
- }, [loadCollection, loadEnvironment, loadMock, setActiveCollection, setTheme, setZoom]);
14334
+ }, [loadCollection, loadEnvironment, loadMock, loadContractSnapshot, setActiveCollection, setTheme, setZoom]);
14229
14335
  return { applyWorkspace };
14230
14336
  }
14231
- const METHOD_COLORS$1 = {
14232
- GET: "text-emerald-400",
14233
- POST: "text-blue-400",
14234
- PUT: "text-amber-400",
14235
- PATCH: "text-orange-400",
14236
- DELETE: "text-red-400",
14237
- HEAD: "text-purple-400",
14238
- OPTIONS: "text-surface-400",
14239
- ANY: "text-surface-400"
14240
- };
14241
- function getMethodColor(method) {
14242
- return METHOD_COLORS$1[method] ?? "text-surface-400";
14243
- }
14244
- const STATUS_COLORS = {
14245
- "2": "text-emerald-400",
14246
- "3": "text-amber-400",
14247
- "4": "text-orange-400",
14248
- "5": "text-red-400"
14249
- };
14250
- function getStatusColor(status) {
14251
- return STATUS_COLORS[String(status)[0]] ?? "text-gray-400";
14252
- }
14253
- function MethodBadge({ method, size = "sm" }) {
14254
- const color = getMethodColor(method);
14255
- const cls = size === "xs" ? "text-[10px] font-bold w-10" : "text-xs font-bold w-12";
14256
- return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `${cls} ${color} shrink-0`, children: method });
14257
- }
14258
14337
  let rangeFrom = [], rangeTo = [];
14259
14338
  (() => {
14260
14339
  let numbers = "lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map((s) => s ? parseInt(s, 36) : 1);
@@ -34272,7 +34351,12 @@ const FAKER_NAMESPACES = [
34272
34351
  { label: "lorem", type: "property", info: "Lorem ipsum text" },
34273
34352
  { label: "location", type: "property", info: "Addresses, cities, countries" },
34274
34353
  { label: "finance", type: "property", info: "Credit cards, currency, etc." },
34275
- { label: "color", type: "property", info: "Color values" }
34354
+ { label: "color", type: "property", info: "Color values" },
34355
+ { label: "company", type: "property", info: "Company names, catch phrases" },
34356
+ { label: "commerce", type: "property", info: "Products, prices, departments" },
34357
+ { label: "image", type: "property", info: "Image URLs / placeholders" },
34358
+ { label: "system", type: "property", info: "File names, MIME types, semver" },
34359
+ { label: "helpers", type: "property", info: "Utility helpers (slugify, arrayElement, …)" }
34276
34360
  ];
34277
34361
  const FAKER_SUB = {
34278
34362
  string: [
@@ -34307,9 +34391,14 @@ const FAKER_SUB = {
34307
34391
  ],
34308
34392
  lorem: [
34309
34393
  { label: "word", type: "function", detail: "()", info: "Single lorem word" },
34310
- { label: "words", type: "function", detail: "(count)", info: "Multiple lorem words" },
34394
+ { label: "words", type: "function", detail: "(count?)", info: "Multiple lorem words" },
34311
34395
  { label: "sentence", type: "function", detail: "()", info: "Lorem sentence" },
34312
- { label: "paragraph", type: "function", detail: "()", info: "Lorem paragraph" }
34396
+ { label: "sentences", type: "function", detail: "(count?)", info: "Multiple lorem sentences" },
34397
+ { label: "paragraph", type: "function", detail: "()", info: "Lorem paragraph" },
34398
+ { label: "paragraphs", type: "function", detail: "(count?)", info: "Multiple lorem paragraphs" },
34399
+ { label: "slug", type: "function", detail: "(words?)", info: 'Kebab-case slug from random words (e.g. "magnam-officia-laborum")' },
34400
+ { label: "lines", type: "function", detail: "(count?)", info: "Random lines of text" },
34401
+ { label: "text", type: "function", detail: "()", info: "Random lorem ipsum text" }
34313
34402
  ],
34314
34403
  location: [
34315
34404
  { label: "city", type: "function", detail: "()", info: "Random city name" },
@@ -34337,6 +34426,39 @@ const FAKER_SUB = {
34337
34426
  { label: "hex", type: "function", detail: "()", info: "Hex color string (e.g. #a3f1c2)" },
34338
34427
  { label: "rgb", type: "function", detail: "()", info: "CSS rgb() string" },
34339
34428
  { label: "hsl", type: "function", detail: "()", info: "CSS hsl() string" }
34429
+ ],
34430
+ company: [
34431
+ { label: "name", type: "function", detail: "()", info: "Random company name" },
34432
+ { label: "catchPhrase", type: "function", detail: "()", info: "Random business catch phrase" },
34433
+ { label: "buzzPhrase", type: "function", detail: "()", info: "Marketing-style buzz phrase" }
34434
+ ],
34435
+ commerce: [
34436
+ { label: "productName", type: "function", detail: "()", info: "Random product name" },
34437
+ { label: "product", type: "function", detail: "()", info: "Single product noun" },
34438
+ { label: "department", type: "function", detail: "()", info: "Department name" },
34439
+ { label: "price", type: "function", detail: "({min, max, dec, symbol}?)", info: "Random price (string)" },
34440
+ { label: "productDescription", type: "function", detail: "()", info: "Product description" },
34441
+ { label: "isbn", type: "function", detail: "()", info: "ISBN-10 or ISBN-13" }
34442
+ ],
34443
+ image: [
34444
+ { label: "avatar", type: "function", detail: "()", info: "Avatar URL" },
34445
+ { label: "url", type: "function", detail: "({width, height}?)", info: "Placeholder image URL" },
34446
+ { label: "urlLoremFlickr", type: "function", detail: "({category}?)", info: "LoremFlickr URL by category" }
34447
+ ],
34448
+ system: [
34449
+ { label: "fileName", type: "function", detail: "()", info: "Random file name with extension" },
34450
+ { label: "mimeType", type: "function", detail: "()", info: "Random MIME type" },
34451
+ { label: "semver", type: "function", detail: "()", info: "Random semver string" },
34452
+ { label: "directoryPath", type: "function", detail: "()", info: "Random directory path" }
34453
+ ],
34454
+ helpers: [
34455
+ { label: "slugify", type: "function", detail: "(text)", info: "Convert any string to a URL slug (lower-case, dashes, no diacritics)" },
34456
+ { label: "arrayElement", type: "function", detail: "(arr)", info: "Random element from an array" },
34457
+ { label: "arrayElements", type: "function", detail: "(arr, n?)", info: "Multiple random elements" },
34458
+ { label: "shuffle", type: "function", detail: "(arr)", info: "Shuffled copy of the array" },
34459
+ { label: "replaceSymbols", type: "function", detail: '("###?")', info: "Replace # / ? / * with random chars" },
34460
+ { label: "fromRegExp", type: "function", detail: "(regex)", info: "Generate a string matching a regex" },
34461
+ { label: "multiple", type: "function", detail: "(fn, {count}?)", info: "Call a generator multiple times" }
34340
34462
  ]
34341
34463
  };
34342
34464
  function makeAtCompletionSource(varNames) {
@@ -35639,7 +35761,7 @@ function CollectionAuthPanel({ auth, onChange }) {
35639
35761
  ] }) })
35640
35762
  ] });
35641
35763
  }
35642
- const { electron: electron$n } = window;
35764
+ const { electron: electron$o } = window;
35643
35765
  function normalisePath(url) {
35644
35766
  let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
35645
35767
  if (!path.startsWith("/")) path = "/" + path;
@@ -35726,7 +35848,7 @@ function SchemaSyncModal({
35726
35848
  setLoading(true);
35727
35849
  setError(null);
35728
35850
  try {
35729
- const entries = await electron$n.extractOpenApiSchemas();
35851
+ const entries = await electron$o.extractOpenApiSchemas();
35730
35852
  if (!entries) {
35731
35853
  setLoading(false);
35732
35854
  return;
@@ -35745,7 +35867,7 @@ function SchemaSyncModal({
35745
35867
  setLoading(true);
35746
35868
  setError(null);
35747
35869
  try {
35748
- const entries = await electron$n.extractOpenApiSchemasFromUrl(trimmed);
35870
+ const entries = await electron$o.extractOpenApiSchemasFromUrl(trimmed);
35749
35871
  setSpecEntries(entries);
35750
35872
  autoSelectChanged(entries);
35751
35873
  } catch (err) {
@@ -35778,7 +35900,7 @@ function SchemaSyncModal({
35778
35900
  }
35779
35901
  const entry = useStore.getState().collections[collectionId];
35780
35902
  if (entry) {
35781
- await electron$n.saveCollection(entry.relPath, entry.data);
35903
+ await electron$o.saveCollection(entry.relPath, entry.data);
35782
35904
  markCollectionClean(collectionId);
35783
35905
  }
35784
35906
  onClose();
@@ -35982,6 +36104,197 @@ function methodColor$2(method) {
35982
36104
  return "text-surface-300";
35983
36105
  }
35984
36106
  }
36107
+ const METHOD_COLORS$1 = {
36108
+ GET: "text-emerald-400",
36109
+ POST: "text-blue-400",
36110
+ PUT: "text-amber-400",
36111
+ PATCH: "text-orange-400",
36112
+ DELETE: "text-red-400",
36113
+ HEAD: "text-purple-400",
36114
+ OPTIONS: "text-surface-400",
36115
+ ANY: "text-surface-400"
36116
+ };
36117
+ function getMethodColor(method) {
36118
+ return METHOD_COLORS$1[method] ?? "text-surface-400";
36119
+ }
36120
+ const STATUS_COLORS = {
36121
+ "2": "text-emerald-400",
36122
+ "3": "text-amber-400",
36123
+ "4": "text-orange-400",
36124
+ "5": "text-red-400"
36125
+ };
36126
+ function getStatusColor(status) {
36127
+ return STATUS_COLORS[String(status)[0]] ?? "text-gray-400";
36128
+ }
36129
+ function MethodBadge({ method, size = "sm" }) {
36130
+ const color = getMethodColor(method);
36131
+ const cls = size === "xs" ? "text-[10px] font-bold w-10" : "text-xs font-bold w-12";
36132
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `${cls} ${color} shrink-0`, children: method });
36133
+ }
36134
+ const HOOK_LABELS = {
36135
+ beforeAll: "Before All",
36136
+ before: "Before",
36137
+ after: "After",
36138
+ afterAll: "After All"
36139
+ };
36140
+ const HOOK_COLORS = {
36141
+ beforeAll: "bg-violet-700 text-white",
36142
+ before: "bg-violet-600 text-white",
36143
+ after: "bg-cyan-700 text-white",
36144
+ afterAll: "bg-cyan-800 text-white"
36145
+ };
36146
+ const AUTH_BADGE_LABELS = {
36147
+ basic: "Basic",
36148
+ bearer: "Bearer",
36149
+ apikey: "Key",
36150
+ digest: "Digest",
36151
+ ntlm: "NTLM",
36152
+ oauth2: "OAuth2"
36153
+ };
36154
+ function RequestRow({
36155
+ reqId,
36156
+ collectionId,
36157
+ folderId,
36158
+ reqIndex,
36159
+ name: name2,
36160
+ method,
36161
+ protocol,
36162
+ authType,
36163
+ hookType,
36164
+ disabled,
36165
+ tags: tags2,
36166
+ isActive,
36167
+ indent: indent2,
36168
+ autoRename = false,
36169
+ onSelect,
36170
+ onRename,
36171
+ onDelete,
36172
+ onDuplicate,
36173
+ onUpdateTags,
36174
+ onSetHookType,
36175
+ onToggleDisabled
36176
+ }) {
36177
+ const [renaming, setRenaming] = reactExports.useState(autoRename);
36178
+ const [addingTag, setAddingTag] = reactExports.useState(false);
36179
+ const [showSchemaSync, setShowSchemaSync] = reactExports.useState(false);
36180
+ const [dropPos, setDropPos] = reactExports.useState(null);
36181
+ const dragCtx = reactExports.useContext(DragCtx);
36182
+ const hookMenuItems = ["beforeAll", "before", "after", "afterAll"].map((ht) => ({
36183
+ type: "item",
36184
+ label: (hookType === ht ? "✓ " : " ") + HOOK_LABELS[ht],
36185
+ onClick: () => onSetHookType(hookType === ht ? void 0 : ht)
36186
+ }));
36187
+ function handleDragOver(e) {
36188
+ if (!dragCtx.dragging || dragCtx.dragging.type !== "request" || dragCtx.dragging.requestId === reqId) return;
36189
+ e.preventDefault();
36190
+ const rect = e.currentTarget.getBoundingClientRect();
36191
+ setDropPos(e.clientY < rect.top + rect.height / 2 ? "before" : "after");
36192
+ }
36193
+ function handleDrop(e) {
36194
+ e.preventDefault();
36195
+ e.stopPropagation();
36196
+ const insertIndex = dropPos === "before" ? reqIndex : reqIndex + 1;
36197
+ dragCtx.onDropRequest(collectionId, folderId, insertIndex);
36198
+ setDropPos(null);
36199
+ }
36200
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
36201
+ dropPos === "before" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 inset-x-0 h-0.5 bg-blue-500 z-10 pointer-events-none" }),
36202
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
36203
+ "div",
36204
+ {
36205
+ draggable: true,
36206
+ className: `group flex items-start gap-1.5 py-1 pr-1 rounded-sm cursor-pointer transition-colors ${disabled ? "opacity-40" : ""} ${isActive ? "bg-surface-800 text-[var(--text-primary)]" : "text-surface-300 hover:bg-surface-800"}`,
36207
+ style: { paddingLeft: indent2 },
36208
+ onClick: onSelect,
36209
+ onDoubleClick: () => setRenaming(true),
36210
+ onDragStart: (e) => {
36211
+ e.dataTransfer.effectAllowed = "move";
36212
+ dragCtx.setDragging({ type: "request", requestId: reqId, collectionId });
36213
+ },
36214
+ onDragEnd: () => {
36215
+ dragCtx.setDragging(null);
36216
+ setDropPos(null);
36217
+ },
36218
+ onDragOver: handleDragOver,
36219
+ onDragLeave: () => setDropPos(null),
36220
+ onDrop: handleDrop,
36221
+ children: [
36222
+ protocol === "soap" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
36223
+ "span",
36224
+ {
36225
+ className: "shrink-0 text-[9px] font-bold px-1.5 py-0.5 rounded bg-amber-700/80 text-amber-50",
36226
+ title: "SOAP request",
36227
+ children: "SOAP"
36228
+ }
36229
+ ) : protocol === "websocket" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
36230
+ "span",
36231
+ {
36232
+ className: "shrink-0 text-[9px] font-bold px-1.5 py-0.5 rounded bg-cyan-700/80 text-cyan-50",
36233
+ title: "WebSocket",
36234
+ children: "WS"
36235
+ }
36236
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(MethodBadge, { method, size: "xs" }),
36237
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
36238
+ renaming ? /* @__PURE__ */ jsxRuntimeExports.jsx(
36239
+ InlineEdit,
36240
+ {
36241
+ value: name2,
36242
+ onCommit: (v) => {
36243
+ onRename(v);
36244
+ setRenaming(false);
36245
+ },
36246
+ onCancel: () => setRenaming(false),
36247
+ className: "w-full text-xs"
36248
+ }
36249
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 min-w-0", children: [
36250
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs truncate", children: name2 }),
36251
+ hookType && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[9px] font-bold px-1 py-px rounded ${HOOK_COLORS[hookType]}`, children: HOOK_LABELS[hookType].toUpperCase() }),
36252
+ authType !== "none" && /* @__PURE__ */ jsxRuntimeExports.jsx(
36253
+ "span",
36254
+ {
36255
+ className: "shrink-0 text-[9px] px-1 py-px rounded bg-amber-800/40 text-amber-400",
36256
+ title: `Auth: ${AUTH_BADGE_LABELS[authType] ?? authType}`,
36257
+ children: AUTH_BADGE_LABELS[authType] ?? authType
36258
+ }
36259
+ )
36260
+ ] }),
36261
+ (tags2.length > 0 || addingTag) && /* @__PURE__ */ jsxRuntimeExports.jsx(
36262
+ TagChips,
36263
+ {
36264
+ tags: tags2,
36265
+ onRemove: (tag) => onUpdateTags(tags2.filter((t2) => t2 !== tag)),
36266
+ onAdd: (tag) => onUpdateTags([...tags2, tag]),
36267
+ forceAdding: addingTag,
36268
+ onDoneAdding: () => setAddingTag(false)
36269
+ }
36270
+ )
36271
+ ] }),
36272
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DotsBtn, { items: [
36273
+ { type: "item", label: "Rename", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}), onClick: () => setRenaming(true) },
36274
+ { type: "item", label: "Duplicate", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: onDuplicate },
36275
+ { type: "item", label: "Add tag", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TagIcon, {}), onClick: () => setAddingTag(true) },
36276
+ { type: "item", label: "Sync schema", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
36277
+ { type: "item", label: disabled ? "Enable" : "Disable", onClick: onToggleDisabled },
36278
+ { type: "separator" },
36279
+ { type: "header", label: "Hook type" },
36280
+ ...hookMenuItems,
36281
+ { type: "separator" },
36282
+ { type: "item", label: "Delete", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDelete }
36283
+ ] }) }),
36284
+ dropPos === "after" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 inset-x-0 h-0.5 bg-blue-500 z-10 pointer-events-none" })
36285
+ ]
36286
+ }
36287
+ ),
36288
+ showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(
36289
+ SchemaSyncModal,
36290
+ {
36291
+ collectionId,
36292
+ scope: { type: "request", requestId: reqId },
36293
+ onClose: () => setShowSchemaSync(false)
36294
+ }
36295
+ )
36296
+ ] });
36297
+ }
35985
36298
  const DragCtx = reactExports.createContext({ dragging: null, setDragging: () => {
35986
36299
  }, onDropRequest: () => {
35987
36300
  }, onDropFolder: () => {
@@ -36029,7 +36342,7 @@ function InlineEdit({
36029
36342
  if (e.key === "Escape") onCancel();
36030
36343
  e.stopPropagation();
36031
36344
  },
36032
- className: `bg-surface-700 rounded px-1 focus:outline-none focus:ring-1 w-full ${error2 ? "ring-1 ring-red-500 focus:ring-red-500" : "focus:ring-blue-500"} ${className}`
36345
+ className: `bg-surface-700 text-[var(--text-primary)] rounded px-1 focus:outline-none focus:ring-1 w-full ${error2 ? "ring-1 ring-red-500 focus:ring-red-500" : "focus:ring-blue-500"} ${className}`
36033
36346
  }
36034
36347
  ),
36035
36348
  error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-400 mt-0.5 px-1", children: error2 })
@@ -36657,6 +36970,7 @@ function FolderContents({
36657
36970
  reqIndex,
36658
36971
  name: req.name,
36659
36972
  method: req.method,
36973
+ protocol: req.protocol,
36660
36974
  authType: req.auth.type,
36661
36975
  hookType: req.hookType,
36662
36976
  disabled: req.disabled,
@@ -36677,157 +36991,6 @@ function FolderContents({
36677
36991
  })
36678
36992
  ] });
36679
36993
  }
36680
- const HOOK_LABELS = {
36681
- beforeAll: "Before All",
36682
- before: "Before",
36683
- after: "After",
36684
- afterAll: "After All"
36685
- };
36686
- const HOOK_COLORS = {
36687
- beforeAll: "bg-violet-700 text-white",
36688
- before: "bg-violet-600 text-white",
36689
- after: "bg-cyan-700 text-white",
36690
- afterAll: "bg-cyan-800 text-white"
36691
- };
36692
- const AUTH_BADGE_LABELS = {
36693
- basic: "Basic",
36694
- bearer: "Bearer",
36695
- apikey: "Key",
36696
- digest: "Digest",
36697
- ntlm: "NTLM",
36698
- oauth2: "OAuth2"
36699
- };
36700
- function RequestRow({
36701
- reqId,
36702
- collectionId,
36703
- folderId,
36704
- reqIndex,
36705
- name: name2,
36706
- method,
36707
- authType,
36708
- hookType,
36709
- disabled,
36710
- tags: tags2,
36711
- isActive,
36712
- indent: indent2,
36713
- autoRename = false,
36714
- onSelect,
36715
- onRename,
36716
- onDelete,
36717
- onDuplicate,
36718
- onUpdateTags,
36719
- onSetHookType,
36720
- onToggleDisabled
36721
- }) {
36722
- const [renaming, setRenaming] = reactExports.useState(autoRename);
36723
- const [addingTag, setAddingTag] = reactExports.useState(false);
36724
- const [showSchemaSync, setShowSchemaSync] = reactExports.useState(false);
36725
- const [dropPos, setDropPos] = reactExports.useState(null);
36726
- const dragCtx = reactExports.useContext(DragCtx);
36727
- const hookMenuItems = [
36728
- ...["beforeAll", "before", "after", "afterAll"].map((ht) => ({
36729
- type: "item",
36730
- label: (hookType === ht ? "✓ " : " ") + HOOK_LABELS[ht],
36731
- onClick: () => onSetHookType(hookType === ht ? void 0 : ht)
36732
- }))
36733
- ];
36734
- function handleDragOver(e) {
36735
- if (!dragCtx.dragging || dragCtx.dragging.type !== "request" || dragCtx.dragging.requestId === reqId) return;
36736
- e.preventDefault();
36737
- const rect = e.currentTarget.getBoundingClientRect();
36738
- setDropPos(e.clientY < rect.top + rect.height / 2 ? "before" : "after");
36739
- }
36740
- function handleDrop(e) {
36741
- e.preventDefault();
36742
- e.stopPropagation();
36743
- const insertIndex = dropPos === "before" ? reqIndex : reqIndex + 1;
36744
- dragCtx.onDropRequest(collectionId, folderId, insertIndex);
36745
- setDropPos(null);
36746
- }
36747
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
36748
- dropPos === "before" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 inset-x-0 h-0.5 bg-blue-500 z-10 pointer-events-none" }),
36749
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
36750
- "div",
36751
- {
36752
- draggable: true,
36753
- className: `group flex items-start gap-1.5 py-1 pr-1 rounded-sm cursor-pointer transition-colors ${disabled ? "opacity-40" : ""} ${isActive ? "bg-surface-800 text-[var(--text-primary)]" : "text-surface-300 hover:bg-surface-800"}`,
36754
- style: { paddingLeft: indent2 },
36755
- onClick: onSelect,
36756
- onDoubleClick: () => setRenaming(true),
36757
- onDragStart: (e) => {
36758
- e.dataTransfer.effectAllowed = "move";
36759
- dragCtx.setDragging({ type: "request", requestId: reqId, collectionId });
36760
- },
36761
- onDragEnd: () => {
36762
- dragCtx.setDragging(null);
36763
- setDropPos(null);
36764
- },
36765
- onDragOver: handleDragOver,
36766
- onDragLeave: () => setDropPos(null),
36767
- onDrop: handleDrop,
36768
- children: [
36769
- /* @__PURE__ */ jsxRuntimeExports.jsx(MethodBadge, { method, size: "xs" }),
36770
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
36771
- renaming ? /* @__PURE__ */ jsxRuntimeExports.jsx(
36772
- InlineEdit,
36773
- {
36774
- value: name2,
36775
- onCommit: (v) => {
36776
- onRename(v);
36777
- setRenaming(false);
36778
- },
36779
- onCancel: () => setRenaming(false),
36780
- className: "w-full text-xs"
36781
- }
36782
- ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 min-w-0", children: [
36783
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs truncate", children: name2 }),
36784
- hookType && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[9px] font-bold px-1 py-px rounded ${HOOK_COLORS[hookType]}`, children: HOOK_LABELS[hookType].toUpperCase() }),
36785
- authType !== "none" && /* @__PURE__ */ jsxRuntimeExports.jsx(
36786
- "span",
36787
- {
36788
- className: "shrink-0 text-[9px] px-1 py-px rounded bg-amber-800/40 text-amber-400",
36789
- title: `Auth: ${AUTH_BADGE_LABELS[authType] ?? authType}`,
36790
- children: AUTH_BADGE_LABELS[authType] ?? authType
36791
- }
36792
- )
36793
- ] }),
36794
- (tags2.length > 0 || addingTag) && /* @__PURE__ */ jsxRuntimeExports.jsx(
36795
- TagChips,
36796
- {
36797
- tags: tags2,
36798
- onRemove: (tag) => onUpdateTags(tags2.filter((t2) => t2 !== tag)),
36799
- onAdd: (tag) => onUpdateTags([...tags2, tag]),
36800
- forceAdding: addingTag,
36801
- onDoneAdding: () => setAddingTag(false)
36802
- }
36803
- )
36804
- ] }),
36805
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DotsBtn, { items: [
36806
- { type: "item", label: "Rename", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}), onClick: () => setRenaming(true) },
36807
- { type: "item", label: "Duplicate", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: onDuplicate },
36808
- { type: "item", label: "Add tag", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TagIcon, {}), onClick: () => setAddingTag(true) },
36809
- { type: "item", label: "Sync schema", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, {}), onClick: () => setShowSchemaSync(true) },
36810
- { type: "item", label: disabled ? "Enable" : "Disable", onClick: onToggleDisabled },
36811
- { type: "separator" },
36812
- { type: "header", label: "Hook type" },
36813
- ...hookMenuItems,
36814
- { type: "separator" },
36815
- { type: "item", label: "Delete", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}), danger: true, onClick: onDelete }
36816
- ] }) }),
36817
- dropPos === "after" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 inset-x-0 h-0.5 bg-blue-500 z-10 pointer-events-none" })
36818
- ]
36819
- }
36820
- ),
36821
- showSchemaSync && /* @__PURE__ */ jsxRuntimeExports.jsx(
36822
- SchemaSyncModal,
36823
- {
36824
- collectionId,
36825
- scope: { type: "request", requestId: reqId },
36826
- onClose: () => setShowSchemaSync(false)
36827
- }
36828
- )
36829
- ] });
36830
- }
36831
36994
  function DotsHorizontalIcon() {
36832
36995
  return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-3.5 h-3.5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 10a2 2 0 11-4 0 2 2 0 014 0zm6 0a2 2 0 11-4 0 2 2 0 014 0zm6 0a2 2 0 11-4 0 2 2 0 014 0z" }) });
36833
36996
  }
@@ -54093,11 +54256,59 @@ function getBaseKind(ref2) {
54093
54256
  function isLeafKind(kind) {
54094
54257
  return kind === "SCALAR" || kind === "ENUM";
54095
54258
  }
54259
+ function isRequired(ref2) {
54260
+ return !!ref2 && ref2.kind === "NON_NULL";
54261
+ }
54262
+ function defaultArgValue(arg) {
54263
+ const baseTypeName = getBaseTypeName(arg.type);
54264
+ const baseKind = getBaseKind(arg.type);
54265
+ const isList2 = arg.type.kind === "LIST" || arg.type.kind === "NON_NULL" && arg.type.ofType?.kind === "LIST";
54266
+ const name2 = arg.name.toLowerCase();
54267
+ if (baseTypeName === "Int" || baseTypeName === "Long") {
54268
+ if (["first", "last", "limit", "take", "top", "count", "size"].includes(name2)) return "5";
54269
+ if (["skip", "offset", "page"].includes(name2)) return "0";
54270
+ return "10";
54271
+ }
54272
+ if (isList2) return "[]";
54273
+ switch (baseTypeName) {
54274
+ case "Float":
54275
+ case "Double":
54276
+ return "1.0";
54277
+ case "Boolean":
54278
+ return "true";
54279
+ case "ID":
54280
+ return '"id"';
54281
+ case "String":
54282
+ return '""';
54283
+ case "DateTime":
54284
+ case "Date":
54285
+ return '""';
54286
+ }
54287
+ if (baseKind === "ENUM") return `# ${baseTypeName}`;
54288
+ return `$${arg.name}`;
54289
+ }
54096
54290
  function buildSnippet(field, typeMap) {
54097
54291
  const baseTypeName = getBaseTypeName(field.type);
54098
54292
  const baseKind = getBaseKind(field.type);
54099
54293
  const type2 = typeMap.get(baseTypeName);
54100
- const args = field.args.length > 0 ? `(${field.args.map((a) => `${a.name}: $${a.name}`).join(", ")})` : "";
54294
+ const PAGINATION_NAMES = /* @__PURE__ */ new Set([
54295
+ "first",
54296
+ "last",
54297
+ "limit",
54298
+ "take",
54299
+ "top",
54300
+ "count",
54301
+ "size",
54302
+ "skip",
54303
+ "offset",
54304
+ "page",
54305
+ "after",
54306
+ "before"
54307
+ ]);
54308
+ const includedArgs = field.args.filter(
54309
+ (a) => isRequired(a.type) || PAGINATION_NAMES.has(a.name.toLowerCase())
54310
+ );
54311
+ const args = includedArgs.length > 0 ? `(${includedArgs.map((a) => `${a.name}: ${defaultArgValue(a)}`).join(", ")})` : "";
54101
54312
  if ((baseKind === "OBJECT" || baseKind === "INTERFACE") && type2?.fields?.length) {
54102
54313
  const leaves = type2.fields.filter((f) => isLeafKind(getBaseKind(f.type))).slice(0, 4).map((f) => ` ${f.name}`).join("\n");
54103
54314
  return ` ${field.name}${args} {
@@ -54938,32 +55149,86 @@ const autoCloseTags$1 = /* @__PURE__ */ EditorView.inputHandler.of((view, from,
54938
55149
  ]);
54939
55150
  return true;
54940
55151
  });
54941
- const { electron: electron$m } = window;
55152
+ const SOAP_11_CONTENT_TYPE = "text/xml; charset=utf-8";
55153
+ const SOAP_12_CONTENT_TYPE = "application/soap+xml; charset=utf-8";
55154
+ function contentTypeForSoap(version) {
55155
+ return version === "1.2" ? SOAP_12_CONTENT_TYPE : SOAP_11_CONTENT_TYPE;
55156
+ }
55157
+ function withContentType(headers, value) {
55158
+ const idx = headers.findIndex((h) => h.key.toLowerCase() === "content-type");
55159
+ const next = { key: "Content-Type", value, enabled: true };
55160
+ if (idx === -1) return [...headers, next];
55161
+ return headers.map((h, i) => i === idx ? { ...h, value, enabled: true } : h);
55162
+ }
55163
+ const { electron: electron$n } = window;
55164
+ function ParamTree({ params, depth = 0 }) {
55165
+ if (params.length === 0) {
55166
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 italic", children: "No parameters declared in WSDL." });
55167
+ }
55168
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: depth === 0 ? "flex flex-col gap-0.5" : "flex flex-col gap-0.5 ml-4 border-l border-surface-800 pl-3 mt-1", children: params.map((p2, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "text-[11px] font-mono", children: [
55169
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-200", children: p2.name }),
55170
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600", children: ": " }),
55171
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: p2.typeHint }),
55172
+ p2.children && p2.children.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(ParamTree, { params: p2.children, depth: depth + 1 })
55173
+ ] }, `${p2.name}-${i}`)) });
55174
+ }
54942
55175
  function SoapEditor({ request, onChange }) {
54943
- const soap = request.body.soap ?? {
54944
- wsdlUrl: "",
54945
- envelope: ""
54946
- };
55176
+ const soap = request.body.soap ?? { wsdlUrl: "", envelope: "" };
54947
55177
  const [operations, setOperations] = reactExports.useState([]);
55178
+ const [endpoints, setEndpoints] = reactExports.useState([]);
55179
+ const [targetNs, setTargetNs] = reactExports.useState("");
54948
55180
  const [fetchError, setFetchError] = reactExports.useState(null);
54949
55181
  const [fetching, setFetching] = reactExports.useState(false);
55182
+ const [showXml, setShowXml] = reactExports.useState(true);
55183
+ reactExports.useEffect(() => {
55184
+ const url = soap.wsdlUrl?.trim();
55185
+ if (!url) return;
55186
+ let cancelled = false;
55187
+ (async () => {
55188
+ try {
55189
+ const result = await electron$n.wsdlFetch(url);
55190
+ if (cancelled) return;
55191
+ setOperations(result.operations);
55192
+ setEndpoints(result.endpoints);
55193
+ setTargetNs(result.targetNamespace);
55194
+ } catch {
55195
+ }
55196
+ })();
55197
+ return () => {
55198
+ cancelled = true;
55199
+ };
55200
+ }, [request.id]);
54950
55201
  function updateSoap(patch) {
54951
55202
  onChange({ body: { ...request.body, soap: { ...soap, ...patch } } });
54952
55203
  }
55204
+ function applyOperation(op) {
55205
+ const headers = withContentType(request.headers ?? [], contentTypeForSoap(op.soapVersion));
55206
+ onChange({
55207
+ method: "POST",
55208
+ headers,
55209
+ ...op.endpoint ? { url: op.endpoint } : {},
55210
+ body: {
55211
+ ...request.body,
55212
+ soap: {
55213
+ ...soap,
55214
+ operationName: op.name,
55215
+ soapAction: op.soapAction ?? "",
55216
+ envelope: op.inputTemplate
55217
+ }
55218
+ }
55219
+ });
55220
+ }
54953
55221
  async function fetchWsdl() {
54954
55222
  if (!soap.wsdlUrl.trim()) return;
54955
55223
  setFetching(true);
54956
55224
  setFetchError(null);
54957
55225
  try {
54958
- const result = await electron$m.wsdlFetch(soap.wsdlUrl.trim());
55226
+ const result = await electron$n.wsdlFetch(soap.wsdlUrl.trim());
54959
55227
  setOperations(result.operations);
55228
+ setEndpoints(result.endpoints);
55229
+ setTargetNs(result.targetNamespace);
54960
55230
  if (result.operations.length > 0) {
54961
- const first = result.operations[0];
54962
- updateSoap({
54963
- operationName: first.name,
54964
- soapAction: first.soapAction ?? "",
54965
- envelope: first.inputTemplate
54966
- });
55231
+ applyOperation(result.operations[0]);
54967
55232
  }
54968
55233
  } catch (err) {
54969
55234
  setFetchError(err instanceof Error ? err.message : String(err));
@@ -54971,23 +55236,28 @@ function SoapEditor({ request, onChange }) {
54971
55236
  setFetching(false);
54972
55237
  }
54973
55238
  }
54974
- function selectOperation(name2) {
54975
- const op = operations.find((o) => o.name === name2);
54976
- if (!op) return;
54977
- updateSoap({
54978
- operationName: op.name,
54979
- soapAction: op.soapAction ?? soap.soapAction ?? "",
54980
- envelope: op.inputTemplate
54981
- });
55239
+ function selectOperation(op) {
55240
+ applyOperation(op);
54982
55241
  }
54983
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
55242
+ const selected = operations.find((o) => o.name === soap.operationName) ?? operations[0];
55243
+ const primaryEndpoint = endpoints[0]?.address;
55244
+ const versions = Array.from(new Set(operations.map((o) => o.soapVersion))).sort();
55245
+ const hasSavedSoap = Boolean(soap.envelope?.trim() || soap.operationName || soap.wsdlUrl?.trim());
55246
+ const showFullUi = operations.length > 0;
55247
+ const showFallback = !showFullUi && hasSavedSoap;
55248
+ const showEmpty = !showFullUi && !showFallback;
55249
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 h-full min-h-0", children: [
54984
55250
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 items-center", children: [
55251
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-500 font-medium whitespace-nowrap", children: "WSDL" }),
54985
55252
  /* @__PURE__ */ jsxRuntimeExports.jsx(
54986
55253
  "input",
54987
55254
  {
54988
55255
  value: soap.wsdlUrl,
54989
55256
  onChange: (e) => updateSoap({ wsdlUrl: e.target.value }),
54990
- placeholder: "https://example.com/service.wsdl",
55257
+ placeholder: "https://example.com/service?WSDL",
55258
+ onKeyDown: (e) => {
55259
+ if (e.key === "Enter" && !fetching) fetchWsdl();
55260
+ },
54991
55261
  className: "flex-1 text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 placeholder-surface-600 font-mono"
54992
55262
  }
54993
55263
  ),
@@ -54997,54 +55267,141 @@ function SoapEditor({ request, onChange }) {
54997
55267
  onClick: fetchWsdl,
54998
55268
  disabled: fetching || !soap.wsdlUrl.trim(),
54999
55269
  className: "px-3 py-1.5 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors whitespace-nowrap",
55000
- children: fetching ? "Fetching…" : "Fetch WSDL"
55270
+ children: fetching ? "Fetching…" : operations.length > 0 ? "Refresh" : "Fetch WSDL"
55001
55271
  }
55002
55272
  )
55003
55273
  ] }),
55004
- fetchError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400", children: fetchError }),
55005
- operations.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 items-center", children: [
55006
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-surface-600 whitespace-nowrap", children: "Operation" }),
55007
- /* @__PURE__ */ jsxRuntimeExports.jsx(
55008
- "select",
55009
- {
55010
- value: soap.operationName ?? "",
55011
- onChange: (e) => selectOperation(e.target.value),
55012
- className: "flex-1 text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500",
55013
- children: operations.map((op) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: op.name, children: op.name }, op.name))
55014
- }
55015
- )
55274
+ fetchError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400 bg-red-950/30 border border-red-900 rounded px-2.5 py-1.5", children: fetchError }),
55275
+ operations.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-surface-900/40 border border-surface-800 rounded-md px-3 py-2 text-[11px] flex flex-col gap-1", children: [
55276
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-surface-500", children: [
55277
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "uppercase tracking-wider text-[9px] font-semibold text-surface-600 w-20 shrink-0", children: "Endpoint" }),
55278
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-200 font-mono truncate", children: primaryEndpoint ?? "(not declared)" })
55279
+ ] }),
55280
+ targetNs && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-surface-500", children: [
55281
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "uppercase tracking-wider text-[9px] font-semibold text-surface-600 w-20 shrink-0", children: "Namespace" }),
55282
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-200 font-mono truncate", children: targetNs })
55283
+ ] }),
55284
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-surface-500", children: [
55285
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "uppercase tracking-wider text-[9px] font-semibold text-surface-600 w-20 shrink-0", children: "Operations" }),
55286
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-300", children: [
55287
+ operations.length,
55288
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-600", children: [
55289
+ " · SOAP ",
55290
+ versions.join(", ")
55291
+ ] })
55292
+ ] })
55293
+ ] })
55016
55294
  ] }),
55017
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 items-center", children: [
55018
- /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-surface-600 whitespace-nowrap", children: "SOAPAction" }),
55019
- /* @__PURE__ */ jsxRuntimeExports.jsx(
55020
- "input",
55021
- {
55022
- value: soap.soapAction ?? "",
55023
- onChange: (e) => updateSoap({ soapAction: e.target.value }),
55024
- placeholder: "(auto-filled from WSDL)",
55025
- className: "flex-1 text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 placeholder-surface-600 font-mono"
55026
- }
55027
- )
55295
+ showFullUi && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3 min-h-0 flex-1", children: [
55296
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-44 shrink-0 flex flex-col bg-surface-900/40 border border-surface-800 rounded-md overflow-hidden", children: [
55297
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-2.5 py-1.5 text-[9px] uppercase tracking-wider font-semibold text-surface-600 border-b border-surface-800", children: "Operations" }),
55298
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-y-auto flex-1", children: operations.map((op) => {
55299
+ const active = selected && op.name === selected.name && op.soapVersion === selected.soapVersion;
55300
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
55301
+ "button",
55302
+ {
55303
+ onClick: () => selectOperation(op),
55304
+ className: `w-full text-left px-2.5 py-1.5 text-[11px] border-b border-surface-800/50 transition-colors ${active ? "bg-blue-900/30 text-blue-300" : "text-surface-300 hover:bg-surface-800/60"}`,
55305
+ children: [
55306
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono truncate", children: op.name }),
55307
+ op.soapVersion === "1.2" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[9px] text-surface-600 uppercase", children: "SOAP 1.2" })
55308
+ ]
55309
+ },
55310
+ `${op.binding ?? ""}:${op.name}:${op.soapVersion}`
55311
+ );
55312
+ }) })
55313
+ ] }),
55314
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0 flex flex-col gap-2", children: selected && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
55315
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-baseline gap-2", children: [
55316
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-semibold text-white font-mono", children: selected.name }),
55317
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] uppercase tracking-wider text-surface-500", children: [
55318
+ "SOAP ",
55319
+ selected.soapVersion
55320
+ ] })
55321
+ ] }),
55322
+ selected.soapAction && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-surface-500 font-mono truncate", children: [
55323
+ "SOAPAction: ",
55324
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300", children: selected.soapAction })
55325
+ ] }),
55326
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-surface-900/40 border border-surface-800 rounded-md px-3 py-2", children: [
55327
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[9px] uppercase tracking-wider font-semibold text-surface-600 mb-1.5", children: "Inputs" }),
55328
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ParamTree, { params: selected.params ?? [] })
55329
+ ] }),
55330
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
55331
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] uppercase tracking-wider font-semibold text-surface-600", children: "Envelope" }),
55332
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55333
+ "button",
55334
+ {
55335
+ onClick: () => setShowXml((v) => !v),
55336
+ className: "text-[10px] text-surface-500 hover:text-surface-300 transition-colors",
55337
+ children: showXml ? "▾ Hide XML" : "▸ Edit XML"
55338
+ }
55339
+ )
55340
+ ] }),
55341
+ !showXml && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 leading-relaxed", children: [
55342
+ "Envelope auto-generated from the operation's input parameters. Method (POST), endpoint URL, and Content-Type are managed for you. Click ",
55343
+ /* @__PURE__ */ jsxRuntimeExports.jsx("em", { children: "Edit XML" }),
55344
+ " to tweak."
55345
+ ] }),
55346
+ showXml && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded overflow-hidden border border-surface-700 flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
55347
+ ReactCodeMirror,
55348
+ {
55349
+ value: soap.envelope ?? "",
55350
+ height: "100%",
55351
+ theme: oneDark,
55352
+ extensions: [xml()],
55353
+ onChange: (val) => updateSoap({ envelope: val }),
55354
+ basicSetup: { lineNumbers: true, foldGutter: true }
55355
+ }
55356
+ ) })
55357
+ ] }) })
55028
55358
  ] }),
55029
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
55030
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-600 mb-1", children: "Envelope" }),
55031
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded overflow-hidden border border-surface-700", style: { minHeight: 200 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
55359
+ showFallback && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 flex-1 min-h-0", children: [
55360
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-surface-900/40 border border-surface-800 rounded-md px-3 py-2 text-[11px] flex items-center gap-3", children: [
55361
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
55362
+ soap.operationName && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-surface-200 truncate", children: soap.operationName }),
55363
+ soap.soapAction && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] text-surface-500 truncate", children: [
55364
+ "SOAPAction: ",
55365
+ soap.soapAction
55366
+ ] }),
55367
+ !soap.operationName && !soap.soapAction && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500", children: soap.wsdlUrl?.trim() ? "WSDL not loaded — the saved envelope below is still sent on Send." : "No WSDL — hand-crafted SOAP envelope." })
55368
+ ] }),
55369
+ soap.wsdlUrl?.trim() && /* @__PURE__ */ jsxRuntimeExports.jsx(
55370
+ "button",
55371
+ {
55372
+ onClick: fetchWsdl,
55373
+ disabled: fetching,
55374
+ className: "px-2.5 py-1 text-[11px] bg-surface-800 hover:bg-surface-700 disabled:opacity-50 rounded transition-colors whitespace-nowrap",
55375
+ children: fetching ? "Loading…" : "Reload WSDL"
55376
+ }
55377
+ )
55378
+ ] }),
55379
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded overflow-hidden border border-surface-700 flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
55032
55380
  ReactCodeMirror,
55033
55381
  {
55034
55382
  value: soap.envelope ?? "",
55035
- height: "200px",
55383
+ height: "100%",
55036
55384
  theme: oneDark,
55037
55385
  extensions: [xml()],
55038
55386
  onChange: (val) => updateSoap({ envelope: val }),
55039
55387
  basicSetup: { lineNumbers: true, foldGutter: true }
55040
55388
  }
55041
55389
  ) })
55390
+ ] }),
55391
+ showEmpty && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center text-center gap-2 border border-dashed border-surface-800 rounded-md py-8 px-4", children: [
55392
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-surface-400", children: [
55393
+ "Paste a WSDL URL above and click ",
55394
+ /* @__PURE__ */ jsxRuntimeExports.jsx("em", { children: "Fetch WSDL" }),
55395
+ "."
55396
+ ] }),
55397
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-sm", children: "The endpoint, SOAP version, Content-Type header, and per-operation envelope are derived from the WSDL — you only pick the operation and fill the parameters." })
55042
55398
  ] })
55043
55399
  ] });
55044
55400
  }
55045
55401
  function BodyTab({ request, onChange }) {
55046
55402
  const body = request.body;
55047
55403
  const mode = body.mode;
55404
+ const isSoap = request.protocol === "soap";
55048
55405
  const varNames = useVarNames();
55049
55406
  const varValues = useVarValues();
55050
55407
  const varExt = reactExports.useMemo(
@@ -55054,6 +55411,9 @@ function BodyTab({ request, onChange }) {
55054
55411
  function setMode(m) {
55055
55412
  onChange({ body: { ...body, mode: m } });
55056
55413
  }
55414
+ if (isSoap) {
55415
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col h-full min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request, onChange }) });
55416
+ }
55057
55417
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 h-full min-h-0", children: [
55058
55418
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-3 text-xs flex-shrink-0", children: ["none", "json", "form", "raw", "graphql", "soap"].map((m) => /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1 cursor-pointer", children: [
55059
55419
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -55133,7 +55493,7 @@ function BodyTab({ request, onChange }) {
55133
55493
  mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request, onChange }) })
55134
55494
  ] });
55135
55495
  }
55136
- const { electron: electron$l } = window;
55496
+ const { electron: electron$m } = window;
55137
55497
  const AUTH_TYPES = ["none", "bearer", "basic", "digest", "ntlm", "apikey", "oauth2"];
55138
55498
  function AuthTab({ request, onChange }) {
55139
55499
  const auth = request.auth;
@@ -55147,7 +55507,7 @@ function AuthTab({ request, onChange }) {
55147
55507
  }
55148
55508
  async function saveSecret(ref2) {
55149
55509
  if (!secretValue || !ref2) return;
55150
- await electron$l.setSecret(ref2, secretValue);
55510
+ await electron$m.setSecret(ref2, secretValue);
55151
55511
  setSaved(true);
55152
55512
  setSecretValue("");
55153
55513
  setTimeout(() => setSaved(false), 2e3);
@@ -55159,7 +55519,7 @@ function AuthTab({ request, onChange }) {
55159
55519
  setOauth2Error("");
55160
55520
  try {
55161
55521
  const vars = {};
55162
- const result = await electron$l.oauth2StartFlow(oauth2Auth, vars);
55522
+ const result = await electron$m.oauth2StartFlow(oauth2Auth, vars);
55163
55523
  setAuth({
55164
55524
  oauth2CachedToken: result.accessToken,
55165
55525
  oauth2TokenExpiry: result.expiresAt
@@ -55177,7 +55537,7 @@ function AuthTab({ request, onChange }) {
55177
55537
  setOauth2Status("fetching");
55178
55538
  setOauth2Error("");
55179
55539
  try {
55180
- const result = await electron$l.oauth2RefreshToken(oauth2Auth, {}, oauth2RefreshToken);
55540
+ const result = await electron$m.oauth2RefreshToken(oauth2Auth, {}, oauth2RefreshToken);
55181
55541
  setAuth({
55182
55542
  oauth2CachedToken: result.accessToken,
55183
55543
  oauth2TokenExpiry: result.expiresAt
@@ -56418,12 +56778,13 @@ function ScriptsTab({ request, onChange }) {
56418
56778
  const activeTabId = useStore((s) => s.activeTabId);
56419
56779
  const activeAppTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
56420
56780
  const setTabScriptTab = useStore((s) => s.setTabScriptTab);
56421
- const scriptType = activeAppTab?.scriptTab ?? "pre";
56781
+ const scriptType = activeAppTab?.scriptTab ?? "post";
56422
56782
  const setScriptType = (t2) => {
56423
56783
  if (activeTabId) setTabScriptTab(activeTabId, t2);
56424
56784
  };
56425
56785
  const [expandedGroup, setExpandedGroup] = reactExports.useState(SNIPPET_GROUPS[0].group);
56426
- const [snippetsOpen, setSnippetsOpen] = reactExports.useState(true);
56786
+ const snippetsOpen = useStore((s) => s.quickInsertsOpen);
56787
+ const setSnippetsOpen = useStore((s) => s.setQuickInsertsOpen);
56427
56788
  const varNames = useVarNames();
56428
56789
  const varValues = useVarValues();
56429
56790
  const extensions = reactExports.useMemo(
@@ -62791,7 +63152,7 @@ function SchemaTab({ request, onChange }) {
62791
63152
  ] }) })
62792
63153
  ] });
62793
63154
  }
62794
- const { electron: electron$k } = window;
63155
+ const { electron: electron$l } = window;
62795
63156
  const EMPTY = { statusCode: 200, headers: [], bodySchema: "" };
62796
63157
  function ContractTab({ request, onChange }) {
62797
63158
  const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
@@ -62805,7 +63166,7 @@ function ContractTab({ request, onChange }) {
62805
63166
  if (!lastResponse?.body) return;
62806
63167
  setInferring(true);
62807
63168
  try {
62808
- const schema = await electron$k.inferContractSchema(lastResponse.body);
63169
+ const schema = await electron$l.inferContractSchema(lastResponse.body);
62809
63170
  if (schema) update({ bodySchema: schema });
62810
63171
  } finally {
62811
63172
  setInferring(false);
@@ -62931,7 +63292,7 @@ function ContractTab({ request, onChange }) {
62931
63292
  ] })
62932
63293
  ] });
62933
63294
  }
62934
- const { electron: electron$j } = window;
63295
+ const { electron: electron$k } = window;
62935
63296
  function formatTime$1(ts) {
62936
63297
  const d = new Date(ts);
62937
63298
  const hh = String(d.getHours()).padStart(2, "0");
@@ -62951,14 +63312,14 @@ function WebSocketPanel({ request }) {
62951
63312
  const [sendText, setSendText] = reactExports.useState("");
62952
63313
  const logEndRef = reactExports.useRef(null);
62953
63314
  reactExports.useEffect(() => {
62954
- electron$j.onWsMessage(({ requestId, message }) => {
63315
+ electron$k.onWsMessage(({ requestId, message }) => {
62955
63316
  addWsMessage(requestId, message);
62956
63317
  });
62957
- electron$j.onWsStatus(({ requestId, status, error: error2 }) => {
63318
+ electron$k.onWsStatus(({ requestId, status, error: error2 }) => {
62958
63319
  setWsStatus(requestId, status, error2);
62959
63320
  });
62960
63321
  return () => {
62961
- electron$j.offWsEvents();
63322
+ electron$k.offWsEvents();
62962
63323
  };
62963
63324
  }, [addWsMessage, setWsStatus]);
62964
63325
  reactExports.useEffect(() => {
@@ -62971,19 +63332,19 @@ function WebSocketPanel({ request }) {
62971
63332
  if (h.enabled && h.key) headers[h.key] = h.value;
62972
63333
  }
62973
63334
  try {
62974
- await electron$j.wsConnect(request.id, request.url, headers);
63335
+ await electron$k.wsConnect(request.id, request.url, headers);
62975
63336
  } catch (err) {
62976
63337
  setWsStatus(request.id, "error", err instanceof Error ? err.message : String(err));
62977
63338
  }
62978
63339
  }
62979
63340
  async function disconnect() {
62980
- await electron$j.wsDisconnect(request.id);
63341
+ await electron$k.wsDisconnect(request.id);
62981
63342
  }
62982
63343
  async function sendMessage() {
62983
63344
  const text = sendText.trim();
62984
63345
  if (!text || !isConnected) return;
62985
63346
  try {
62986
- await electron$j.wsSend(request.id, text);
63347
+ await electron$k.wsSend(request.id, text);
62987
63348
  const msg = {
62988
63349
  id: crypto.randomUUID(),
62989
63350
  direction: "sent",
@@ -63086,7 +63447,7 @@ function WebSocketPanel({ request }) {
63086
63447
  ] })
63087
63448
  ] });
63088
63449
  }
63089
- const { electron: electron$i } = window;
63450
+ const { electron: electron$j } = window;
63090
63451
  const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
63091
63452
  const METHOD_COLORS = {
63092
63453
  GET: "text-emerald-400",
@@ -63173,7 +63534,7 @@ function RequestBuilder({ request }) {
63173
63534
  try {
63174
63535
  const hookEnv = activeEnvironmentId ? useStore.getState().environments[activeEnvironmentId]?.data ?? null : null;
63175
63536
  const hookSessionVars = useStore.getState().sessionVars;
63176
- const r = await electron$i.sendRequest({
63537
+ const r = await electron$j.sendRequest({
63177
63538
  ...basePayload,
63178
63539
  environment: hookEnv,
63179
63540
  request: hook,
@@ -63214,7 +63575,7 @@ function RequestBuilder({ request }) {
63214
63575
  }
63215
63576
  const freshEnv = activeEnvironmentId ? useStore.getState().environments[activeEnvironmentId]?.data ?? null : null;
63216
63577
  const freshSessionVars = useStore.getState().sessionVars;
63217
- const result = await electron$i.sendRequest({
63578
+ const result = await electron$j.sendRequest({
63218
63579
  ...basePayload,
63219
63580
  environment: freshEnv,
63220
63581
  request: mergedRequest,
@@ -63239,7 +63600,7 @@ function RequestBuilder({ request }) {
63239
63600
  try {
63240
63601
  const hookEnv = activeEnvironmentId ? useStore.getState().environments[activeEnvironmentId]?.data ?? null : null;
63241
63602
  const hookSessionVars = useStore.getState().sessionVars;
63242
- const r = await electron$i.sendRequest({
63603
+ const r = await electron$j.sendRequest({
63243
63604
  ...basePayload,
63244
63605
  environment: hookEnv,
63245
63606
  request: hook,
@@ -63288,11 +63649,13 @@ function RequestBuilder({ request }) {
63288
63649
  const hasPreScript = Boolean(request.preRequestScript?.trim());
63289
63650
  const hasPostScript = Boolean(request.postRequestScript?.trim());
63290
63651
  const isWs = request.protocol === "websocket";
63652
+ const isSoap = request.protocol === "soap";
63291
63653
  const tabs = [
63292
- { id: "params", label: "Params", count: request.params.filter((p2) => p2.enabled && p2.key).length },
63654
+ // SOAP collapses Params + Body into a single "SOAP" tab — the WSDL drives both.
63655
+ ...!isSoap ? [{ id: "params", label: "Params", count: request.params.filter((p2) => p2.enabled && p2.key).length }] : [],
63293
63656
  { id: "headers", label: "Headers", count: request.headers.filter((h) => h.enabled && h.key).length },
63294
63657
  ...!isWs ? [
63295
- { id: "body", label: "Body", count: request.body.mode !== "none" ? 1 : 0 },
63658
+ { id: "body", label: isSoap ? "SOAP" : "Body", count: request.body.mode !== "none" ? 1 : 0 },
63296
63659
  { id: "auth", label: "Auth", count: request.auth.type !== "none" ? 1 : 0 },
63297
63660
  { id: "scripts", label: "Scripts", count: (hasPreScript ? 1 : 0) + (hasPostScript ? 1 : 0) },
63298
63661
  { id: "schema", label: "Schema", count: request.schema?.trim() ? 1 : 0 },
@@ -63324,7 +63687,7 @@ function RequestBuilder({ request }) {
63324
63687
  "button",
63325
63688
  {
63326
63689
  onClick: () => update({ protocol: "http" }),
63327
- className: `px-2 py-1.5 transition-colors ${!isWs ? "bg-blue-600 text-white" : "text-surface-500 hover:text-white"}`,
63690
+ className: `px-2 py-1.5 transition-colors ${request.protocol !== "websocket" && request.protocol !== "soap" ? "bg-blue-600 text-white" : "text-surface-500 hover:text-white"}`,
63328
63691
  title: "HTTP request",
63329
63692
  children: "HTTP"
63330
63693
  }
@@ -63337,9 +63700,25 @@ function RequestBuilder({ request }) {
63337
63700
  title: "WebSocket",
63338
63701
  children: "WS"
63339
63702
  }
63703
+ ),
63704
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
63705
+ "button",
63706
+ {
63707
+ onClick: () => {
63708
+ update({
63709
+ protocol: "soap",
63710
+ method: "POST",
63711
+ body: request.body.mode === "soap" ? request.body : { ...request.body, mode: "soap", soap: request.body.soap ?? { wsdlUrl: "", envelope: "" } }
63712
+ });
63713
+ if (activeTabId) setTabRequestTab(activeTabId, "body");
63714
+ },
63715
+ className: `px-2 py-1.5 transition-colors ${isSoap ? "bg-amber-700 text-amber-100" : "text-surface-500 hover:text-white"}`,
63716
+ title: "SOAP — endpoint and method are derived from the WSDL",
63717
+ children: "SOAP"
63718
+ }
63340
63719
  )
63341
63720
  ] }),
63342
- !isWs && /* @__PURE__ */ jsxRuntimeExports.jsx(
63721
+ !isWs && !isSoap && /* @__PURE__ */ jsxRuntimeExports.jsx(
63343
63722
  "select",
63344
63723
  {
63345
63724
  value: request.method,
@@ -63348,14 +63727,22 @@ function RequestBuilder({ request }) {
63348
63727
  children: METHODS.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: m, className: "text-white", children: m }, m))
63349
63728
  }
63350
63729
  ),
63730
+ isSoap && /* @__PURE__ */ jsxRuntimeExports.jsx(
63731
+ "span",
63732
+ {
63733
+ className: "bg-surface-900 border border-surface-700 rounded px-2 py-1.5 text-xs font-bold text-amber-400 select-none",
63734
+ title: "SOAP requests are always POST",
63735
+ children: "POST"
63736
+ }
63737
+ ),
63351
63738
  /* @__PURE__ */ jsxRuntimeExports.jsx(
63352
63739
  VarInput,
63353
63740
  {
63354
63741
  value: request.url,
63355
63742
  onChange: (url) => update({ url }),
63356
- placeholder: isWs ? "ws://example.com/socket" : "https://api.example.com/endpoint",
63743
+ placeholder: isWs ? "ws://example.com/socket" : isSoap ? "Endpoint (auto-filled from WSDL <soap:address>)" : "https://api.example.com/endpoint",
63357
63744
  wrapperClassName: "flex-1",
63358
- className: "bg-surface-800 border border-surface-700 rounded px-3 py-1.5 text-sm focus:outline-none focus:border-blue-500 font-mono placeholder-surface-700"
63745
+ className: `border rounded px-3 py-1.5 text-sm focus:outline-none focus:border-blue-500 font-mono placeholder-surface-700 ${isSoap ? "bg-surface-900 border-amber-900/50 text-surface-300" : "bg-surface-800 border-surface-700"}`
63359
63746
  }
63360
63747
  ),
63361
63748
  !isWs && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
@@ -63976,7 +64363,7 @@ function HookResultsPanel({ results }) {
63976
64363
  }) })
63977
64364
  ] });
63978
64365
  }
63979
- const { electron: electron$h } = window;
64366
+ const { electron: electron$i } = window;
63980
64367
  function extractPath(url) {
63981
64368
  try {
63982
64369
  return new URL(url).pathname || "/";
@@ -64030,14 +64417,14 @@ function SaveAsMockModal({ onClose }) {
64030
64417
  const entry = state.mocks[serverId];
64031
64418
  const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
64032
64419
  updateMock(serverId, updated);
64033
- await electron$h.saveMock(entry.relPath, updated);
64420
+ await electron$i.saveMock(entry.relPath, updated);
64034
64421
  const ws2 = useStore.getState().workspace;
64035
- if (ws2) await electron$h.saveWorkspace(ws2);
64422
+ if (ws2) await electron$i.saveWorkspace(ws2);
64036
64423
  } else {
64037
64424
  const entry = useStore.getState().mocks[serverId];
64038
64425
  const updated = { ...entry.data, routes: [...entry.data.routes, route] };
64039
64426
  updateMock(serverId, updated);
64040
- await electron$h.saveMock(entry.relPath, updated);
64427
+ await electron$i.saveMock(entry.relPath, updated);
64041
64428
  }
64042
64429
  onClose();
64043
64430
  } finally {
@@ -64298,19 +64685,24 @@ function TestsPanel({ scriptResult }) {
64298
64685
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400 text-xs font-bold shrink-0", children: "POST-SCRIPT ERROR" }),
64299
64686
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-300 text-xs font-mono", children: sr.postScriptError })
64300
64687
  ] }),
64301
- sr.testResults.map((result, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
64302
- "div",
64303
- {
64304
- className: `flex items-start gap-2 p-2 rounded border ${result.passed ? "bg-emerald-900/20 border-emerald-800" : "bg-red-900/20 border-red-800"}`,
64305
- children: [
64306
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-bold shrink-0 ${result.passed ? "text-emerald-400" : "text-red-400"}`, children: result.passed ? "✓" : "✗" }),
64307
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-0.5", children: [
64308
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-white", children: result.name }),
64309
- result.error && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-red-300 font-mono", children: result.error })
64310
- ] })
64311
- ]
64312
- },
64313
- i
64688
+ sr.testResults.map((result, i) => (
64689
+ // Pass uses the bright lime-400 border at 50% (was: dark olive-800
64690
+ // border on near-invisible 20% bg) so the green test row actually
64691
+ // reads as green in dark mode.
64692
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
64693
+ "div",
64694
+ {
64695
+ className: `flex items-start gap-2 p-2 rounded border ${result.passed ? "bg-emerald-800/30 border-emerald-400/50" : "bg-red-900/30 border-red-700"}`,
64696
+ children: [
64697
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-bold shrink-0 ${result.passed ? "text-emerald-400" : "text-red-400"}`, children: result.passed ? "✓" : "✗" }),
64698
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-0.5", children: [
64699
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-white", children: result.name }),
64700
+ result.error && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-red-300 font-mono", children: result.error })
64701
+ ] })
64702
+ ]
64703
+ },
64704
+ i
64705
+ )
64314
64706
  ))
64315
64707
  ] });
64316
64708
  }
@@ -64367,7 +64759,7 @@ function ConsolePanel({ scriptResult }) {
64367
64759
  )) })
64368
64760
  ] });
64369
64761
  }
64370
- const { electron: electron$g } = window;
64762
+ const { electron: electron$h } = window;
64371
64763
  function ResponseViewer() {
64372
64764
  const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
64373
64765
  const activeTabId = useStore((s) => s.activeTabId);
@@ -64395,7 +64787,7 @@ function ResponseViewer() {
64395
64787
  const [contractToast, setContractToast] = reactExports.useState(false);
64396
64788
  async function saveAsContract() {
64397
64789
  if (!response || !requestId || !activeTabId) return;
64398
- const schema = response.body ? await electron$g.inferContractSchema(response.body) : null;
64790
+ const schema = response.body ? await electron$h.inferContractSchema(response.body) : null;
64399
64791
  const contentType2 = response.headers["content-type"];
64400
64792
  const headers = contentType2 ? [{ key: "content-type", value: contentType2, required: true }] : [];
64401
64793
  updateRequest(requestId, {
@@ -64420,6 +64812,7 @@ function ResponseViewer() {
64420
64812
  setTabRequestTab(activeTabId, "scripts");
64421
64813
  setTabScriptTab(activeTabId, "post");
64422
64814
  }
64815
+ state.setQuickInsertsOpen(false);
64423
64816
  setAssertToast(true);
64424
64817
  setTimeout(() => setAssertToast(false), 2500);
64425
64818
  }
@@ -64563,7 +64956,7 @@ function ResponseViewer() {
64563
64956
  ] }, k)) }) }) }) : tab === "tests" ? /* @__PURE__ */ jsxRuntimeExports.jsx(TestsPanel, { scriptResult }) : tab === "console" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ConsolePanel, { scriptResult }) : tab === "request" ? /* @__PURE__ */ jsxRuntimeExports.jsx(RequestPanel, { sentRequest }) : null })
64564
64957
  ] });
64565
64958
  }
64566
- const { electron: electron$f } = window;
64959
+ const { electron: electron$g } = window;
64567
64960
  const TARGETS = [
64568
64961
  { id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
64569
64962
  { id: "playwright_ts", label: "Playwright TS", description: "TypeScript page-object API classes + spec files" },
@@ -64601,7 +64994,7 @@ function GeneratorPanel() {
64601
64994
  try {
64602
64995
  const col = collections[selectedCollectionId]?.data;
64603
64996
  const env = activeEnvironmentId ? environments[activeEnvironmentId]?.data ?? null : null;
64604
- const generated = await electron$f.generateCode({ collection: col, environment: env, target });
64997
+ const generated = await electron$g.generateCode({ collection: col, environment: env, target });
64605
64998
  setFiles(generated);
64606
64999
  setSelectedFile(generated[0]?.path ?? null);
64607
65000
  } catch (e) {
@@ -64613,7 +65006,7 @@ function GeneratorPanel() {
64613
65006
  async function saveZip() {
64614
65007
  if (files.length === 0) return;
64615
65008
  const col = collections[selectedCollectionId]?.data;
64616
- await electron$f.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
65009
+ await electron$g.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
64617
65010
  }
64618
65011
  const selectedContent = files.find((f) => f.path === selectedFile)?.content ?? "";
64619
65012
  const activeTarget = TARGETS.find((t2) => t2.id === target);
@@ -64837,16 +65230,16 @@ function HistoryRow({
64837
65230
  }
64838
65231
  );
64839
65232
  }
64840
- const { electron: electron$e } = window;
65233
+ const { electron: electron$f } = window;
64841
65234
  function WelcomeScreen() {
64842
65235
  const { applyWorkspace } = useWorkspaceLoader();
64843
65236
  async function openWorkspace() {
64844
- const result = await electron$e.openWorkspace();
65237
+ const result = await electron$f.openWorkspace();
64845
65238
  if (!result) return;
64846
65239
  await applyWorkspace(result.workspace, result.workspacePath);
64847
65240
  }
64848
65241
  async function newWorkspace() {
64849
- const result = await electron$e.newWorkspace();
65242
+ const result = await electron$f.newWorkspace();
64850
65243
  if (!result) return;
64851
65244
  await applyWorkspace(result.workspace, result.workspacePath);
64852
65245
  }
@@ -64897,7 +65290,7 @@ function WelcomeScreen() {
64897
65290
  ] })
64898
65291
  ] });
64899
65292
  }
64900
- const { electron: electron$d } = window;
65293
+ const { electron: electron$e } = window;
64901
65294
  const EXAMPLES = [
64902
65295
  {
64903
65296
  label: "macOS / Linux (~/.zshrc or ~/.bashrc)",
@@ -64916,12 +65309,12 @@ function MasterKeyModal({ onSuccess, onCancel }) {
64916
65309
  const [password, setPassword] = reactExports.useState("");
64917
65310
  const [error2, setError] = reactExports.useState("");
64918
65311
  const [copied, setCopied] = reactExports.useState(null);
64919
- async function confirm() {
65312
+ async function confirm2() {
64920
65313
  if (!password.trim()) {
64921
65314
  setError("Password cannot be empty.");
64922
65315
  return;
64923
65316
  }
64924
- await electron$d.setMasterKey(password);
65317
+ await electron$e.setMasterKey(password);
64925
65318
  onSuccess(password);
64926
65319
  }
64927
65320
  function copy(idx, text) {
@@ -64962,7 +65355,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
64962
65355
  setPassword(e.target.value);
64963
65356
  setError("");
64964
65357
  },
64965
- onKeyDown: (e) => e.key === "Enter" && confirm(),
65358
+ onKeyDown: (e) => e.key === "Enter" && confirm2(),
64966
65359
  placeholder: "Enter master password…",
64967
65360
  className: "bg-surface-800 border border-surface-700 rounded px-3 py-1.5 text-sm font-mono focus:outline-none focus:border-blue-500"
64968
65361
  }
@@ -64998,7 +65391,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
64998
65391
  /* @__PURE__ */ jsxRuntimeExports.jsx(
64999
65392
  "button",
65000
65393
  {
65001
- onClick: confirm,
65394
+ onClick: confirm2,
65002
65395
  disabled: !password.trim(),
65003
65396
  className: "px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-500 disabled:opacity-40 rounded transition-colors",
65004
65397
  children: "Set Password & Continue"
@@ -65011,7 +65404,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
65011
65404
  }
65012
65405
  );
65013
65406
  }
65014
- const { electron: electron$c } = window;
65407
+ const { electron: electron$d } = window;
65015
65408
  async function shortHash(value) {
65016
65409
  const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
65017
65410
  return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
@@ -65057,6 +65450,7 @@ function EnvironmentEditor({ onClose }) {
65057
65450
  const [pendingEncryptIdx, setPendingEncryptIdx] = reactExports.useState(null);
65058
65451
  const [nameError, setNameError] = reactExports.useState(null);
65059
65452
  const deleteEnvironment = useStore((s) => s.deleteEnvironment);
65453
+ const duplicateEnvironment = useStore((s) => s.duplicateEnvironment);
65060
65454
  const envList = Object.values(environments);
65061
65455
  const env = selectedId ? environments[selectedId]?.data ?? null : null;
65062
65456
  function handleDelete(id2) {
@@ -65064,6 +65458,11 @@ function EnvironmentEditor({ onClose }) {
65064
65458
  const remaining = Object.keys(environments).filter((k) => k !== id2);
65065
65459
  setSelectedId(remaining[0] ?? "");
65066
65460
  }
65461
+ function handleDuplicate(id2) {
65462
+ duplicateEnvironment(id2);
65463
+ const next = Object.keys(useStore.getState().environments).at(-1);
65464
+ if (next) setSelectedId(next);
65465
+ }
65067
65466
  function updateVar(idx, patch) {
65068
65467
  if (!env) return;
65069
65468
  const vars = env.variables.map((v, i) => i === idx ? { ...v, ...patch } : v);
@@ -65112,7 +65511,7 @@ function EnvironmentEditor({ onClose }) {
65112
65511
  async function saveEncrypted(idx) {
65113
65512
  const plaintext = secretInputs[idx] ?? "";
65114
65513
  if (!plaintext) return;
65115
- const { set: set2 } = await electron$c.checkMasterKey();
65514
+ const { set: set2 } = await electron$d.checkMasterKey();
65116
65515
  if (!set2) {
65117
65516
  setPendingEncryptIdx(idx);
65118
65517
  return;
@@ -65149,9 +65548,9 @@ function EnvironmentEditor({ onClose }) {
65149
65548
  } : state.workspace
65150
65549
  }));
65151
65550
  const ws2 = useStore.getState().workspace;
65152
- if (ws2) await electron$c.saveWorkspace(ws2);
65551
+ if (ws2) await electron$d.saveWorkspace(ws2);
65153
65552
  }
65154
- await electron$c.saveEnvironment(newRelPath, env);
65553
+ await electron$d.saveEnvironment(newRelPath, env);
65155
65554
  }
65156
65555
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
65157
65556
  pendingEncryptIdx !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -65190,6 +65589,15 @@ function EnvironmentEditor({ onClose }) {
65190
65589
  children: e.name
65191
65590
  }
65192
65591
  ),
65592
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
65593
+ "button",
65594
+ {
65595
+ onClick: () => handleDuplicate(e.id),
65596
+ className: "opacity-0 group-hover:opacity-100 text-surface-400 hover:text-white transition-all px-1 text-xs leading-none shrink-0",
65597
+ title: "Duplicate environment",
65598
+ children: "⧉"
65599
+ }
65600
+ ),
65193
65601
  /* @__PURE__ */ jsxRuntimeExports.jsx(
65194
65602
  "button",
65195
65603
  {
@@ -65239,135 +65647,141 @@ function EnvironmentEditor({ onClose }) {
65239
65647
  /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-xs", children: [
65240
65648
  /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "text-surface-400 text-left border-b border-surface-800", children: [
65241
65649
  /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 w-8", children: "On" }),
65242
- /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-2 py-2 w-36", children: "Variable" }),
65650
+ /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-2 py-2 w-56", children: "Variable" }),
65243
65651
  /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-2 py-2", children: "Value / Encrypted / Env var" }),
65244
- /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-2 py-2 w-44", children: "Description" }),
65245
- /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-2 py-2 w-20 text-center", children: "Source" }),
65246
65652
  /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-2 py-2 w-6" })
65247
65653
  ] }) }),
65248
- /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: env.variables.map((v, idx) => {
65654
+ env.variables.map((v, idx) => {
65249
65655
  const mode = getSourceMode(v);
65250
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "group border-b border-surface-800/50 hover:bg-surface-800/30", children: [
65251
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
65252
- "input",
65253
- {
65254
- type: "checkbox",
65255
- checked: v.enabled,
65256
- onChange: (e) => updateVar(idx, { enabled: e.target.checked }),
65257
- className: "accent-blue-500"
65258
- }
65259
- ) }),
65260
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
65261
- "input",
65262
- {
65263
- value: v.key,
65264
- onChange: (e) => updateVar(idx, { key: e.target.value }),
65265
- placeholder: "variable_name",
65266
- className: "w-full bg-surface-800 rounded px-2 py-1 font-mono focus:outline-none focus:ring-1 focus:ring-blue-500"
65267
- }
65268
- ) }),
65269
- /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "px-2 py-1.5", children: [
65270
- mode === "plain" && /* @__PURE__ */ jsxRuntimeExports.jsx(
65656
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("tbody", { className: "group border-b border-surface-800/50 hover:bg-surface-800/30", children: [
65657
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
65658
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
65271
65659
  "input",
65272
65660
  {
65273
- value: v.value,
65274
- onChange: (e) => updateVar(idx, { value: e.target.value }),
65275
- placeholder: "value",
65661
+ type: "checkbox",
65662
+ checked: v.enabled,
65663
+ onChange: (e) => updateVar(idx, { enabled: e.target.checked }),
65664
+ className: "accent-blue-500"
65665
+ }
65666
+ ) }),
65667
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
65668
+ "input",
65669
+ {
65670
+ value: v.key,
65671
+ onChange: (e) => updateVar(idx, { key: e.target.value }),
65672
+ placeholder: "variable_name",
65276
65673
  className: "w-full bg-surface-800 rounded px-2 py-1 font-mono focus:outline-none focus:ring-1 focus:ring-blue-500"
65277
65674
  }
65278
- ),
65279
- mode === "encrypted" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
65280
- /* @__PURE__ */ jsxRuntimeExports.jsx(
65675
+ ) }),
65676
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "px-2 py-1.5", children: [
65677
+ mode === "plain" && /* @__PURE__ */ jsxRuntimeExports.jsx(
65281
65678
  "input",
65282
65679
  {
65283
- type: "password",
65284
- value: secretInputs[idx] ?? "",
65285
- onChange: (e) => setSecretInputs((s) => ({ ...s, [idx]: e.target.value })),
65286
- placeholder: v.secretHash ? `Encrypted · sha256: ${v.secretHash}…` : "Enter secret value to encrypt…",
65287
- className: "flex-1 bg-surface-800 rounded px-2 py-1 font-mono focus:outline-none focus:ring-1 focus:ring-blue-500"
65288
- }
65289
- ),
65290
- /* @__PURE__ */ jsxRuntimeExports.jsx(
65291
- "button",
65292
- {
65293
- onClick: () => saveEncrypted(idx),
65294
- disabled: !secretInputs[idx],
65295
- className: "px-2 py-1 bg-blue-700 hover:bg-blue-600 disabled:opacity-40 rounded whitespace-nowrap transition-colors",
65296
- children: savedIdx === idx ? "✓ Saved" : "Encrypt"
65680
+ value: v.value,
65681
+ onChange: (e) => updateVar(idx, { value: e.target.value }),
65682
+ placeholder: "value",
65683
+ className: "w-full bg-surface-800 rounded px-2 py-1 font-mono focus:outline-none focus:ring-1 focus:ring-blue-500"
65297
65684
  }
65298
65685
  ),
65299
- v.secretHash && savedIdx !== idx && /* @__PURE__ */ jsxRuntimeExports.jsxs(
65300
- "span",
65301
- {
65302
- className: "text-[10px] text-emerald-400 font-mono shrink-0",
65303
- title: `SHA-256 fingerprint of encrypted value: ${v.secretHash}…`,
65304
- children: [
65305
- "● ",
65306
- v.secretHash,
65307
- "…"
65308
- ]
65309
- }
65310
- )
65686
+ mode === "encrypted" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
65687
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
65688
+ "input",
65689
+ {
65690
+ type: "password",
65691
+ value: secretInputs[idx] ?? "",
65692
+ onChange: (e) => setSecretInputs((s) => ({ ...s, [idx]: e.target.value })),
65693
+ placeholder: v.secretHash ? `Encrypted · sha256: ${v.secretHash}…` : "Enter secret value to encrypt…",
65694
+ className: "flex-1 bg-surface-800 rounded px-2 py-1 font-mono focus:outline-none focus:ring-1 focus:ring-blue-500"
65695
+ }
65696
+ ),
65697
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
65698
+ "button",
65699
+ {
65700
+ onClick: () => saveEncrypted(idx),
65701
+ disabled: !secretInputs[idx],
65702
+ className: "px-2 py-1 bg-blue-700 hover:bg-blue-600 disabled:opacity-40 rounded whitespace-nowrap transition-colors",
65703
+ children: savedIdx === idx ? "✓ Saved" : "Encrypt"
65704
+ }
65705
+ ),
65706
+ v.secretHash && savedIdx !== idx && /* @__PURE__ */ jsxRuntimeExports.jsxs(
65707
+ "span",
65708
+ {
65709
+ className: "text-[10px] text-emerald-400 font-mono shrink-0",
65710
+ title: `SHA-256 fingerprint of encrypted value: ${v.secretHash}…`,
65711
+ children: [
65712
+ "● ",
65713
+ v.secretHash,
65714
+ "…"
65715
+ ]
65716
+ }
65717
+ )
65718
+ ] }),
65719
+ mode === "env" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 font-mono", children: [
65720
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-400 select-none", children: "$" }),
65721
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
65722
+ "input",
65723
+ {
65724
+ value: v.envRef ?? "",
65725
+ onChange: (e) => updateVar(idx, { envRef: e.target.value }),
65726
+ placeholder: "OS_ENV_VAR_NAME",
65727
+ className: "flex-1 bg-surface-800 rounded px-2 py-1 focus:outline-none focus:ring-1 focus:ring-blue-500"
65728
+ }
65729
+ ),
65730
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
65731
+ "span",
65732
+ {
65733
+ className: "text-[10px] text-surface-400 shrink-0",
65734
+ title: "Value read from OS env at send-time. Never stored in project.",
65735
+ children: "process.env"
65736
+ }
65737
+ )
65738
+ ] })
65311
65739
  ] }),
65312
- mode === "env" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 font-mono", children: [
65313
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-400 select-none", children: "$" }),
65740
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
65741
+ "button",
65742
+ {
65743
+ onClick: () => removeVar(idx),
65744
+ className: "text-surface-400 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all",
65745
+ children: "×"
65746
+ }
65747
+ ) })
65748
+ ] }),
65749
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
65750
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", {}),
65751
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: 2, className: "px-2 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
65314
65752
  /* @__PURE__ */ jsxRuntimeExports.jsx(
65315
65753
  "input",
65316
65754
  {
65317
- value: v.envRef ?? "",
65318
- onChange: (e) => updateVar(idx, { envRef: e.target.value }),
65319
- placeholder: "OS_ENV_VAR_NAME",
65320
- className: "flex-1 bg-surface-800 rounded px-2 py-1 focus:outline-none focus:ring-1 focus:ring-blue-500"
65755
+ value: v.description ?? "",
65756
+ onChange: (e) => updateVar(idx, { description: e.target.value }),
65757
+ placeholder: "Optional description…",
65758
+ className: "flex-1 bg-surface-800/50 rounded px-2 py-1 text-[10px] text-surface-300 placeholder-surface-600 focus:outline-none focus:ring-1 focus:ring-blue-500/50"
65321
65759
  }
65322
65760
  ),
65323
- /* @__PURE__ */ jsxRuntimeExports.jsx(
65324
- "span",
65761
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
65762
+ "button",
65325
65763
  {
65326
- className: "text-[10px] text-surface-400 shrink-0",
65327
- title: "Value read from OS env at send-time. Never stored in project.",
65328
- children: "process.env"
65764
+ onClick: () => cycleSource(idx),
65765
+ title: mode === "plain" ? "Plain text — click to switch to encrypted secret" : mode === "encrypted" ? "Encrypted secret — click to switch to env var ref" : "OS env var reference — click to switch to plain text",
65766
+ className: "flex items-center justify-center gap-1 px-1.5 py-0.5 rounded border transition-colors text-[10px] font-medium w-16 shrink-0 border-surface-700 hover:border-surface-500",
65767
+ children: [
65768
+ mode === "plain" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "abc" }),
65769
+ mode === "encrypted" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
65770
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: "🔒" }),
65771
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: "enc" })
65772
+ ] }),
65773
+ mode === "env" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
65774
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: "$" }),
65775
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: "env" })
65776
+ ] })
65777
+ ]
65329
65778
  }
65330
65779
  )
65331
- ] })
65332
- ] }),
65333
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
65334
- "input",
65335
- {
65336
- value: v.description ?? "",
65337
- onChange: (e) => updateVar(idx, { description: e.target.value }),
65338
- placeholder: "Optional description…",
65339
- className: "w-full bg-surface-800/50 rounded px-2 py-1 text-[10px] text-surface-300 placeholder-surface-600 focus:outline-none focus:ring-1 focus:ring-blue-500/50"
65340
- }
65341
- ) }),
65342
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1.5 text-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
65343
- "button",
65344
- {
65345
- onClick: () => cycleSource(idx),
65346
- title: mode === "plain" ? "Plain text — click to switch to encrypted secret" : mode === "encrypted" ? "Encrypted secret — click to switch to env var ref" : "OS env var reference — click to switch to plain text",
65347
- className: "flex items-center justify-center gap-1 mx-auto px-1.5 py-0.5 rounded border transition-colors text-[10px] font-medium w-16 border-surface-700 hover:border-surface-500",
65348
- children: [
65349
- mode === "plain" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "abc" }),
65350
- mode === "encrypted" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
65351
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: "🔒" }),
65352
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: "enc" })
65353
- ] }),
65354
- mode === "env" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
65355
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: "$" }),
65356
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: "env" })
65357
- ] })
65358
- ]
65359
- }
65360
- ) }),
65361
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
65362
- "button",
65363
- {
65364
- onClick: () => removeVar(idx),
65365
- className: "text-surface-400 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all",
65366
- children: "×"
65367
- }
65368
- ) })
65780
+ ] }) }),
65781
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", {})
65782
+ ] })
65369
65783
  ] }, idx);
65370
- }) })
65784
+ })
65371
65785
  ] }),
65372
65786
  /* @__PURE__ */ jsxRuntimeExports.jsx(
65373
65787
  "button",
@@ -65435,7 +65849,7 @@ function EnvironmentEditor({ onClose }) {
65435
65849
  )
65436
65850
  ] });
65437
65851
  }
65438
- const { electron: electron$b } = window;
65852
+ const { electron: electron$c } = window;
65439
65853
  function EnvironmentBar({ inline = false }) {
65440
65854
  const environments = useStore((s) => s.environments);
65441
65855
  const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
@@ -65449,7 +65863,7 @@ function EnvironmentBar({ inline = false }) {
65449
65863
  if (id2) {
65450
65864
  const hasSecrets = environments[id2]?.data.variables.some((v) => v.enabled && v.secret);
65451
65865
  if (hasSecrets) {
65452
- const { set: set2 } = await electron$b.checkMasterKey();
65866
+ const { set: set2 } = await electron$c.checkMasterKey();
65453
65867
  if (!set2) {
65454
65868
  setPendingEnvId(id2);
65455
65869
  return;
@@ -65503,7 +65917,7 @@ function EnvironmentBar({ inline = false }) {
65503
65917
  controls
65504
65918
  ] });
65505
65919
  }
65506
- const { electron: electron$a } = window;
65920
+ const { electron: electron$b } = window;
65507
65921
  const DEFAULT_PII_PATTERNS = ["authorization", "password", "token", "secret", "api-key", "x-api-key"];
65508
65922
  const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
65509
65923
  function WorkspaceSettingsModal({ onClose }) {
@@ -65558,7 +65972,7 @@ function WorkspaceSettingsModal({ onClose }) {
65558
65972
  settings.piiMaskPatterns = patterns;
65559
65973
  updateWorkspaceSettings(settings);
65560
65974
  const updated = useStore.getState().workspace;
65561
- if (updated) await electron$a.saveWorkspace(updated);
65975
+ if (updated) await electron$b.saveWorkspace(updated);
65562
65976
  onClose();
65563
65977
  }
65564
65978
  function zoomStep(dir) {
@@ -65813,7 +66227,7 @@ function WorkspaceSettingsModal({ onClose }) {
65813
66227
  }
65814
66228
  );
65815
66229
  }
65816
- const { electron: electron$9 } = window;
66230
+ const { electron: electron$a } = window;
65817
66231
  function DocsGeneratorModal({ onClose }) {
65818
66232
  const collections = useStore((s) => s.collections);
65819
66233
  const collectionList = Object.values(collections);
@@ -65833,18 +66247,29 @@ function DocsGeneratorModal({ onClose }) {
65833
66247
  });
65834
66248
  }
65835
66249
  function buildPayload() {
66250
+ const tabs = useStore.getState().tabs;
66251
+ const examples = {};
66252
+ for (const t2 of tabs) {
66253
+ if (!t2.requestId) continue;
66254
+ if (!t2.lastSentRequest && !t2.lastResponse) continue;
66255
+ examples[t2.requestId] = {
66256
+ sent: t2.lastSentRequest ?? void 0,
66257
+ response: t2.lastResponse ?? void 0
66258
+ };
66259
+ }
65836
66260
  return {
65837
66261
  collections: collectionList.filter((c) => selectedIds.has(c.data.id)).map((c) => ({ collection: c.data, requests: c.data.requests })),
65838
- format: format2
66262
+ format: format2,
66263
+ examples
65839
66264
  };
65840
66265
  }
65841
66266
  async function handleGenerateAndSave() {
65842
66267
  setGenerating(true);
65843
66268
  setError(null);
65844
66269
  try {
65845
- const content2 = await electron$9.generateDocs(buildPayload());
66270
+ const content2 = await electron$a.generateDocs(buildPayload());
65846
66271
  const filename = format2 === "html" ? "api-docs.html" : "api-docs.md";
65847
- await electron$9.saveResults(content2, filename);
66272
+ await electron$a.saveResults(content2, filename);
65848
66273
  } catch (err) {
65849
66274
  setError(err instanceof Error ? err.message : String(err));
65850
66275
  } finally {
@@ -65855,7 +66280,7 @@ function DocsGeneratorModal({ onClose }) {
65855
66280
  setGenerating(true);
65856
66281
  setError(null);
65857
66282
  try {
65858
- const content2 = await electron$9.generateDocs(buildPayload());
66283
+ const content2 = await electron$a.generateDocs(buildPayload());
65859
66284
  setPreview(content2);
65860
66285
  } catch (err) {
65861
66286
  setError(err instanceof Error ? err.message : String(err));
@@ -65971,7 +66396,7 @@ function DocsGeneratorModal({ onClose }) {
65971
66396
  ] })
65972
66397
  ] }) });
65973
66398
  }
65974
- const { electron: electron$8 } = window;
66399
+ const { electron: electron$9 } = window;
65975
66400
  const OPTIONS = [
65976
66401
  { id: "postman", label: "Postman", description: "Collection v2.1 JSON" },
65977
66402
  { id: "openapi", label: "OpenAPI", description: "JSON or YAML (v3.x)", supportsUrl: true },
@@ -66042,10 +66467,10 @@ function ImportModal({ onImport, onClose }) {
66042
66467
  setError(null);
66043
66468
  try {
66044
66469
  let col = null;
66045
- if (opt2.id === "postman") col = await electron$8.importPostman();
66046
- if (opt2.id === "openapi") col = await electron$8.importOpenApi();
66047
- if (opt2.id === "insomnia") col = await electron$8.importInsomnia();
66048
- if (opt2.id === "bruno") col = await electron$8.importBruno();
66470
+ if (opt2.id === "postman") col = await electron$9.importPostman();
66471
+ if (opt2.id === "openapi") col = await electron$9.importOpenApi();
66472
+ if (opt2.id === "insomnia") col = await electron$9.importInsomnia();
66473
+ if (opt2.id === "bruno") col = await electron$9.importBruno();
66049
66474
  if (!col) {
66050
66475
  setLoading(false);
66051
66476
  return;
@@ -66068,7 +66493,7 @@ function ImportModal({ onImport, onClose }) {
66068
66493
  setLoading(true);
66069
66494
  setError(null);
66070
66495
  try {
66071
- const col = await electron$8.importOpenApiFromUrl(trimmed);
66496
+ const col = await electron$9.importOpenApiFromUrl(trimmed);
66072
66497
  if (col) enterPreview(col);
66073
66498
  } catch (err) {
66074
66499
  setError(err instanceof Error ? err.message : String(err));
@@ -66144,7 +66569,7 @@ function ImportModal({ onImport, onClose }) {
66144
66569
  mergeIntoCollection(target, prunedRoot, prunedRequests);
66145
66570
  const entry = useStore.getState().collections[target];
66146
66571
  if (entry) {
66147
- await electron$8.saveCollection(entry.relPath, entry.data);
66572
+ await electron$9.saveCollection(entry.relPath, entry.data);
66148
66573
  markCollectionClean(target);
66149
66574
  }
66150
66575
  setActiveCollection(target);
@@ -66171,7 +66596,7 @@ function ImportModal({ onImport, onClose }) {
66171
66596
  variables: [{ key: name2, value, enabled: true }]
66172
66597
  };
66173
66598
  const relPath = envRelPath(finalName, envId);
66174
- await electron$8.saveEnvironment(relPath, env);
66599
+ await electron$9.saveEnvironment(relPath, env);
66175
66600
  useStore.setState((s) => {
66176
66601
  s.environments[envId] = { relPath, data: env };
66177
66602
  if (!s.activeEnvironmentId) s.activeEnvironmentId = envId;
@@ -66181,7 +66606,7 @@ function ImportModal({ onImport, onClose }) {
66181
66606
  return s;
66182
66607
  });
66183
66608
  const ws2 = useStore.getState().workspace;
66184
- if (ws2) await electron$8.saveWorkspace(ws2);
66609
+ if (ws2) await electron$9.saveWorkspace(ws2);
66185
66610
  } else {
66186
66611
  const entry = state.environments[envTarget];
66187
66612
  if (!entry) throw new Error("Target environment not found");
@@ -66196,7 +66621,7 @@ function ImportModal({ onImport, onClose }) {
66196
66621
  } else {
66197
66622
  updated.variables = [...updated.variables, { key: name2, value, enabled: true }];
66198
66623
  }
66199
- await electron$8.saveEnvironment(entry.relPath, updated);
66624
+ await electron$9.saveEnvironment(entry.relPath, updated);
66200
66625
  useStore.getState().updateEnvironment(envTarget, updated);
66201
66626
  }
66202
66627
  }
@@ -66544,7 +66969,7 @@ function collectRequestsByFolder(folder, src) {
66544
66969
  walk(folder);
66545
66970
  return out;
66546
66971
  }
66547
- const { electron: electron$7 } = window;
66972
+ const { electron: electron$8 } = window;
66548
66973
  function Toolbar({ onOpenDocs: _onOpenDocs }) {
66549
66974
  const { applyWorkspace } = useWorkspaceLoader();
66550
66975
  const workspace = useStore((s) => s.workspace);
@@ -66567,13 +66992,13 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
66567
66992
  try {
66568
66993
  for (const { relPath, data, dirty } of Object.values(collections)) {
66569
66994
  if (!dirty) continue;
66570
- await electron$7.saveCollection(relPath, data);
66995
+ await electron$8.saveCollection(relPath, data);
66571
66996
  markCollectionClean(data.id);
66572
66997
  }
66573
66998
  for (const { relPath, data } of Object.values(environments)) {
66574
- await electron$7.saveEnvironment(relPath, data);
66999
+ await electron$8.saveEnvironment(relPath, data);
66575
67000
  }
66576
- if (workspace) await electron$7.saveWorkspace(workspace);
67001
+ if (workspace) await electron$8.saveWorkspace(workspace);
66577
67002
  } finally {
66578
67003
  setSaving(false);
66579
67004
  }
@@ -66581,14 +67006,14 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
66581
67006
  async function afterImport(col) {
66582
67007
  if (!col) return;
66583
67008
  const relPath = colRelPath(col.name, col.id);
66584
- await electron$7.saveCollection(relPath, col);
67009
+ await electron$8.saveCollection(relPath, col);
66585
67010
  loadCollection(relPath, col);
66586
67011
  setActiveCollection(col.id);
66587
67012
  const ws2 = useStore.getState().workspace;
66588
67013
  if (ws2 && !ws2.collections.includes(relPath)) {
66589
67014
  const updated = { ...ws2, collections: [...ws2.collections, relPath] };
66590
67015
  useStore.setState({ workspace: updated });
66591
- await electron$7.saveWorkspace(updated);
67016
+ await electron$8.saveWorkspace(updated);
66592
67017
  }
66593
67018
  }
66594
67019
  if (!workspace) return null;
@@ -66677,7 +67102,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
66677
67102
  "button",
66678
67103
  {
66679
67104
  onClick: async () => {
66680
- const result = await electron$7.openWorkspace();
67105
+ const result = await electron$8.openWorkspace();
66681
67106
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
66682
67107
  },
66683
67108
  className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
@@ -66689,7 +67114,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
66689
67114
  "button",
66690
67115
  {
66691
67116
  onClick: async () => {
66692
- const result = await electron$7.newWorkspace();
67117
+ const result = await electron$8.newWorkspace();
66693
67118
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
66694
67119
  },
66695
67120
  className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
@@ -66701,7 +67126,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
66701
67126
  "button",
66702
67127
  {
66703
67128
  onClick: async () => {
66704
- await electron$7.closeWorkspace();
67129
+ await electron$8.closeWorkspace();
66705
67130
  closeWorkspace();
66706
67131
  },
66707
67132
  className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
@@ -67008,7 +67433,7 @@ function buildHtmlReport(results, summary, meta2 = {}) {
67008
67433
  const html = document.documentElement
67009
67434
  const isLight = html.classList.toggle('light')
67010
67435
  document.getElementById('themeBtn').textContent = isLight ? '🌙' : '☀️'
67011
- try { localStorage.setItem('theme', isLight ? 'light' : 'dark') } catch {}
67436
+ try { localStorage.setItem('theme', isLight ? 'light' : 'dark') } catch {} /* private mode / quota — non-fatal */
67012
67437
  }
67013
67438
  // Restore saved preference or respect OS preference
67014
67439
  (function() {
@@ -67018,7 +67443,7 @@ function buildHtmlReport(results, summary, meta2 = {}) {
67018
67443
  document.documentElement.classList.add('light')
67019
67444
  document.getElementById('themeBtn').textContent = '🌙'
67020
67445
  }
67021
- } catch {}
67446
+ } catch {} /* private mode / quota — non-fatal */
67022
67447
  })()
67023
67448
  <\/script>
67024
67449
  </body>
@@ -67152,7 +67577,7 @@ api-tests:
67152
67577
  function EmptyState({ message }) {
67153
67578
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-24 text-surface-400 text-xs", children: message });
67154
67579
  }
67155
- const { electron: electron$6 } = window;
67580
+ const { electron: electron$7 } = window;
67156
67581
  const HOOK_BADGE = {
67157
67582
  beforeAll: { label: "BEFORE ALL", cls: "bg-violet-700 text-white" },
67158
67583
  before: { label: "BEFORE", cls: "bg-violet-600 text-white" },
@@ -67272,13 +67697,13 @@ function RunnerModal() {
67272
67697
  setSummary(null);
67273
67698
  setRunnerRunning(true);
67274
67699
  progressIdxRef.current = 0;
67275
- electron$6.onRunProgress((result) => {
67700
+ electron$7.onRunProgress((result) => {
67276
67701
  const idx = progressIdxRef.current;
67277
67702
  patchRunnerResult(idx, result);
67278
67703
  if (result.status !== "running") progressIdxRef.current++;
67279
67704
  });
67280
67705
  try {
67281
- const s = await electron$6.runCollection({
67706
+ const s = await electron$7.runCollection({
67282
67707
  items: items2,
67283
67708
  environment: env,
67284
67709
  globals,
@@ -67289,7 +67714,7 @@ function RunnerModal() {
67289
67714
  });
67290
67715
  setSummary(s);
67291
67716
  } finally {
67292
- electron$6.offRunProgress();
67717
+ electron$7.offRunProgress();
67293
67718
  setRunnerRunning(false);
67294
67719
  }
67295
67720
  }, [collectionId, folderId, filterTags, selectedEnvId, environments, globals, colEntry, requestDelay, workspaceSettings, setRunnerResults, patchRunnerResult, setRunnerRunning]);
@@ -67524,7 +67949,7 @@ function RunnerModal() {
67524
67949
  };
67525
67950
  const content2 = exportFormat === "junit" ? buildJUnitReport(runnerResults, summary, meta2) : exportFormat === "html" ? buildHtmlReport(runnerResults, summary, meta2) : buildJsonReport(runnerResults, summary, meta2);
67526
67951
  const ext = exportFormat === "junit" ? "xml" : exportFormat === "html" ? "html" : "json";
67527
- electron$6.saveResults(content2, `spector-results.${ext}`);
67952
+ electron$7.saveResults(content2, `spector-results.${ext}`);
67528
67953
  },
67529
67954
  className: "px-2.5 py-0.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors text-[11px] whitespace-nowrap",
67530
67955
  children: "Export results"
@@ -67742,7 +68167,7 @@ function CollectionPanel() {
67742
68167
  ] })
67743
68168
  ] });
67744
68169
  }
67745
- const { electron: electron$5 } = window;
68170
+ const { electron: electron$6 } = window;
67746
68171
  function MockPanel() {
67747
68172
  const mocks = useStore((s) => s.mocks);
67748
68173
  const activeMockId = useStore((s) => s.activeMockId);
@@ -67750,6 +68175,7 @@ function MockPanel() {
67750
68175
  const addMock = useStore((s) => s.addMock);
67751
68176
  const deleteMock = useStore((s) => s.deleteMock);
67752
68177
  const setRunning = useStore((s) => s.setMockRunning);
68178
+ const loadMock = useStore((s) => s.loadMock);
67753
68179
  const recorderOpen = useStore((s) => s.recorderOpen);
67754
68180
  const recorderRunning = useStore((s) => s.recorderRunning);
67755
68181
  const recorderUpstream = useStore((s) => s.recorderUpstream);
@@ -67759,16 +68185,45 @@ function MockPanel() {
67759
68185
  const setRecorderUpstream = useStore((s) => s.setRecorderUpstream);
67760
68186
  const setRecorderPort = useStore((s) => s.setRecorderPort);
67761
68187
  const [recorderError, setRecorderError] = reactExports.useState("");
68188
+ const [wsdlOpen, setWsdlOpen] = reactExports.useState(false);
68189
+ const [wsdlUrl, setWsdlUrl] = reactExports.useState("");
68190
+ const [wsdlImporting, setWsdlImporting] = reactExports.useState(false);
68191
+ const [wsdlError, setWsdlError] = reactExports.useState("");
67762
68192
  const mockList = Object.values(mocks);
68193
+ async function handleImportWsdl() {
68194
+ setWsdlError("");
68195
+ if (!wsdlUrl.trim()) return;
68196
+ setWsdlImporting(true);
68197
+ try {
68198
+ const existingPorts = mockList.map((m) => m.data.port);
68199
+ const { mock } = await electron$6.wsdlImport({ url: wsdlUrl.trim(), existingMockPorts: existingPorts });
68200
+ const relPath = `mocks/${mock.id}.mock.json`;
68201
+ loadMock(relPath, mock);
68202
+ await electron$6.saveMock(relPath, mock);
68203
+ const ws2 = useStore.getState().workspace;
68204
+ if (ws2) {
68205
+ if (!ws2.mocks) ws2.mocks = [];
68206
+ ws2.mocks.push(relPath);
68207
+ await electron$6.saveWorkspace(ws2);
68208
+ }
68209
+ setActiveMock(mock.id);
68210
+ setWsdlOpen(false);
68211
+ setWsdlUrl("");
68212
+ } catch (err) {
68213
+ setWsdlError(err instanceof Error ? err.message : String(err));
68214
+ } finally {
68215
+ setWsdlImporting(false);
68216
+ }
68217
+ }
67763
68218
  async function handleAddMock() {
67764
68219
  addMock();
67765
68220
  const ws2 = useStore.getState().workspace;
67766
- if (ws2) await electron$5.saveWorkspace(ws2);
68221
+ if (ws2) await electron$6.saveWorkspace(ws2);
67767
68222
  const state = useStore.getState();
67768
68223
  const newId = state.activeMockId;
67769
68224
  if (newId) {
67770
68225
  const entry = state.mocks[newId];
67771
- await electron$5.saveMock(entry.relPath, entry.data);
68226
+ await electron$6.saveMock(entry.relPath, entry.data);
67772
68227
  setActiveMock(newId);
67773
68228
  }
67774
68229
  }
@@ -67776,15 +68231,15 @@ function MockPanel() {
67776
68231
  e.stopPropagation();
67777
68232
  const entry = useStore.getState().mocks[mockId];
67778
68233
  if (!entry) return;
67779
- if (entry.running) await electron$5.mockStop(mockId);
68234
+ if (entry.running) await electron$6.mockStop(mockId);
67780
68235
  deleteMock(mockId);
67781
68236
  const ws2 = useStore.getState().workspace;
67782
- if (ws2) await electron$5.saveWorkspace(ws2);
68237
+ if (ws2) await electron$6.saveWorkspace(ws2);
67783
68238
  }
67784
68239
  async function handleStartRecorder() {
67785
68240
  setRecorderError("");
67786
68241
  try {
67787
- await electron$5.recordStart({ upstream: recorderUpstream, port: recorderPort });
68242
+ await electron$6.recordStart({ upstream: recorderUpstream, port: recorderPort });
67788
68243
  setRecorderRunning(true);
67789
68244
  } catch (err) {
67790
68245
  setRecorderError(err instanceof Error ? err.message : String(err));
@@ -67796,12 +68251,12 @@ function MockPanel() {
67796
68251
  if (!entry) return;
67797
68252
  try {
67798
68253
  if (entry.running) {
67799
- await electron$5.mockStop(mockId);
68254
+ await electron$6.mockStop(mockId);
67800
68255
  setRunning(mockId, false);
67801
68256
  } else {
67802
68257
  const latest2 = useStore.getState().mocks[mockId].data;
67803
- await electron$5.saveMock(entry.relPath, latest2);
67804
- await electron$5.mockStart(latest2);
68258
+ await electron$6.saveMock(entry.relPath, latest2);
68259
+ await electron$6.mockStart(latest2);
67805
68260
  setRunning(mockId, true);
67806
68261
  }
67807
68262
  } catch {
@@ -67867,14 +68322,66 @@ function MockPanel() {
67867
68322
  ] }),
67868
68323
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 flex items-center justify-between border-b border-surface-800 flex-shrink-0", children: [
67869
68324
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-surface-600", children: "Mock Servers" }),
68325
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
68326
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
68327
+ "button",
68328
+ {
68329
+ onClick: () => {
68330
+ setWsdlOpen((o) => !o);
68331
+ setWsdlError("");
68332
+ },
68333
+ className: "text-[11px] text-surface-400 hover:text-blue-300 transition-colors",
68334
+ title: "Generate a mock server from a WSDL",
68335
+ children: "Import WSDL"
68336
+ }
68337
+ ),
68338
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
68339
+ "button",
68340
+ {
68341
+ onClick: handleAddMock,
68342
+ className: "text-[11px] text-blue-400 hover:text-blue-300 transition-colors",
68343
+ children: "+ New"
68344
+ }
68345
+ )
68346
+ ] })
68347
+ ] }),
68348
+ wsdlOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 flex flex-col gap-2 bg-surface-900/40 border-b border-surface-800 flex-shrink-0", children: [
67870
68349
  /* @__PURE__ */ jsxRuntimeExports.jsx(
67871
- "button",
68350
+ "input",
67872
68351
  {
67873
- onClick: handleAddMock,
67874
- className: "text-[11px] text-blue-400 hover:text-blue-300 transition-colors",
67875
- children: "+ New"
68352
+ value: wsdlUrl,
68353
+ onChange: (e) => setWsdlUrl(e.target.value),
68354
+ onKeyDown: (e) => {
68355
+ if (e.key === "Enter" && !wsdlImporting) handleImportWsdl();
68356
+ },
68357
+ placeholder: "https://example.com/service?WSDL",
68358
+ className: "w-full bg-surface-900 border border-surface-700 rounded px-2 py-1 text-[11px] font-mono placeholder-surface-600 focus:outline-none focus:border-blue-500"
67876
68359
  }
67877
- )
68360
+ ),
68361
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
68362
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
68363
+ "button",
68364
+ {
68365
+ onClick: handleImportWsdl,
68366
+ disabled: wsdlImporting || !wsdlUrl.trim(),
68367
+ className: "flex-1 py-1 rounded text-[11px] bg-blue-700 hover:bg-blue-600 disabled:opacity-50 disabled:hover:bg-blue-700 transition-colors",
68368
+ children: wsdlImporting ? "Importing…" : "Generate mock"
68369
+ }
68370
+ ),
68371
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
68372
+ "button",
68373
+ {
68374
+ onClick: () => {
68375
+ setWsdlOpen(false);
68376
+ setWsdlError("");
68377
+ },
68378
+ className: "px-2 py-1 rounded text-[11px] bg-surface-800 hover:bg-surface-700 transition-colors",
68379
+ children: "Cancel"
68380
+ }
68381
+ )
68382
+ ] }),
68383
+ wsdlError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-400", children: wsdlError }),
68384
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 leading-relaxed", children: "One dispatch route per service endpoint. SOAPAction (or operation element in the body) selects which response envelope is returned." })
67878
68385
  ] }),
67879
68386
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: mockList.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-3 text-center px-4", children: [
67880
68387
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-400 text-xs", children: "No mock servers yet." }),
@@ -67934,7 +68441,7 @@ function MockPanel() {
67934
68441
  }) })
67935
68442
  ] });
67936
68443
  }
67937
- const { electron: electron$4 } = window;
68444
+ const { electron: electron$5 } = window;
67938
68445
  const METHODS_PLUS_ANY = ["ANY", "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
67939
68446
  function RouteRow({
67940
68447
  route,
@@ -68336,20 +68843,20 @@ function MockDetailPanel({ mockId }) {
68336
68843
  const routes = mock.routes ?? [];
68337
68844
  async function save(updated) {
68338
68845
  updateMock(mock.id, updated);
68339
- await electron$4.saveMock(entry.relPath, updated);
68340
- if (running) await electron$4.mockUpdateRoutes(mock.id, updated.routes ?? []);
68341
- if (workspace) await electron$4.saveWorkspace(workspace);
68846
+ await electron$5.saveMock(entry.relPath, updated);
68847
+ if (running) await electron$5.mockUpdateRoutes(mock.id, updated.routes ?? []);
68848
+ if (workspace) await electron$5.saveWorkspace(workspace);
68342
68849
  }
68343
68850
  async function toggleRunning() {
68344
68851
  setError(null);
68345
68852
  try {
68346
68853
  if (running) {
68347
- await electron$4.mockStop(mock.id);
68854
+ await electron$5.mockStop(mock.id);
68348
68855
  setRunning(mock.id, false);
68349
68856
  } else {
68350
68857
  const latest2 = useStore.getState().mocks[mock.id].data;
68351
- await electron$4.saveMock(entry.relPath, latest2);
68352
- await electron$4.mockStart(latest2);
68858
+ await electron$5.saveMock(entry.relPath, latest2);
68859
+ await electron$5.mockStart(latest2);
68353
68860
  setRunning(mock.id, true);
68354
68861
  }
68355
68862
  } catch (e) {
@@ -68462,7 +68969,7 @@ function MockDetailPanel({ mockId }) {
68462
68969
  "button",
68463
68970
  {
68464
68971
  onClick: () => {
68465
- if (running) electron$4.mockStop(mock.id);
68972
+ if (running) electron$5.mockStop(mock.id);
68466
68973
  deleteMock(mock.id);
68467
68974
  setActive2(null);
68468
68975
  },
@@ -68529,7 +69036,7 @@ function MockDetailPanel({ mockId }) {
68529
69036
  ] })
68530
69037
  ] });
68531
69038
  }
68532
- const { electron: electron$3 } = window;
69039
+ const { electron: electron$4 } = window;
68533
69040
  function statusColor$1(status) {
68534
69041
  if (status === 0) return "text-yellow-400";
68535
69042
  if (status < 300) return "text-emerald-400";
@@ -68562,7 +69069,7 @@ function RecorderPanel({ onImportMock, onClose, defaultTargetMockId }) {
68562
69069
  const upstream = useStore((s) => s.recorderUpstream);
68563
69070
  const mockList = Object.values(useStore((s) => s.mocks));
68564
69071
  reactExports.useEffect(() => {
68565
- electron$3.onRecordHit((entry) => {
69072
+ electron$4.onRecordHit((entry) => {
68566
69073
  setEntries((prev) => {
68567
69074
  const next = [...prev, entry];
68568
69075
  setTimeout(() => {
@@ -68572,12 +69079,12 @@ function RecorderPanel({ onImportMock, onClose, defaultTargetMockId }) {
68572
69079
  });
68573
69080
  });
68574
69081
  return () => {
68575
- electron$3.offRecordHit();
69082
+ electron$4.offRecordHit();
68576
69083
  };
68577
69084
  }, []);
68578
69085
  async function handleStop() {
68579
69086
  try {
68580
- const s = await electron$3.recordStop();
69087
+ const s = await electron$4.recordStop();
68581
69088
  setSession(s);
68582
69089
  setStopped(true);
68583
69090
  } catch {
@@ -68773,7 +69280,7 @@ function HeadersTable({ headers }) {
68773
69280
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300 break-all", children: v })
68774
69281
  ] }, k)) });
68775
69282
  }
68776
- const { electron: electron$2 } = window;
69283
+ const { electron: electron$3 } = window;
68777
69284
  function ContractPanel() {
68778
69285
  const collections = useStore((s) => s.collections);
68779
69286
  const environments = useStore((s) => s.environments);
@@ -68781,11 +69288,20 @@ function ContractPanel() {
68781
69288
  const activeCollId = useStore((s) => s.activeCollectionId);
68782
69289
  const report = useStore((s) => s.lastContractReport);
68783
69290
  const setReport = useStore((s) => s.setLastContractReport);
69291
+ const snapshots = useStore((s) => s.contractSnapshots);
69292
+ const activeSnapshotRelPath = useStore((s) => s.activeContractSnapshotRelPath);
69293
+ const setActiveSnapshot = useStore((s) => s.setActiveContractSnapshot);
69294
+ const loadContractSnapshot = useStore((s) => s.loadContractSnapshot);
69295
+ const removeContractSnapshot = useStore((s) => s.removeContractSnapshot);
69296
+ const workspace = useStore((s) => s.workspace);
68784
69297
  const [mode, setMode] = reactExports.useState("consumer");
68785
69298
  const [specUrl, setSpecUrl] = reactExports.useState("");
68786
69299
  const [requestBaseUrl, setRequestBaseUrl] = reactExports.useState("");
68787
69300
  const [running, setRunning] = reactExports.useState(false);
69301
+ const [capturing, setCapturing] = reactExports.useState(false);
68788
69302
  const [error2, setError] = reactExports.useState(null);
69303
+ const snapshotList = Object.entries(snapshots).map(([relPath, snapshot]) => ({ relPath, snapshot })).sort((a, b) => b.snapshot.capturedAt.localeCompare(a.snapshot.capturedAt));
69304
+ const activeSnapshot = activeSnapshotRelPath ? snapshots[activeSnapshotRelPath] ?? null : null;
68789
69305
  const allRequests = Object.values(collections).flatMap((c) => Object.values(c.data.requests));
68790
69306
  const contractRequests = allRequests.filter(
68791
69307
  (r) => r.contract && (r.contract.statusCode !== void 0 || r.contract.bodySchema || r.contract.headers?.length)
@@ -68795,8 +69311,8 @@ function ContractPanel() {
68795
69311
  (environments[activeEnvId]?.data.variables ?? []).filter((v) => v.enabled).map((v) => [v.key, v.value])
68796
69312
  ) : {};
68797
69313
  async function runContracts() {
68798
- if (mode !== "consumer" && !specUrl.trim()) {
68799
- setError("Provide an OpenAPI spec URL for provider / bi-directional mode.");
69314
+ if (mode !== "consumer" && !specUrl.trim() && !activeSnapshotRelPath) {
69315
+ setError("Provide an OpenAPI spec URL or pick a pinned snapshot for provider / bi-directional mode.");
68800
69316
  return;
68801
69317
  }
68802
69318
  setRunning(true);
@@ -68804,12 +69320,13 @@ function ContractPanel() {
68804
69320
  setReport(null);
68805
69321
  try {
68806
69322
  const requests = mode === "provider" ? allRequests : contractRequests;
68807
- const result = await electron$2.runContracts({
69323
+ const result = await electron$3.runContracts({
68808
69324
  mode,
68809
69325
  requests,
68810
69326
  envVars,
68811
69327
  collectionVars,
68812
69328
  specUrl: specUrl.trim() || void 0,
69329
+ specSnapshotRelPath: activeSnapshotRelPath ?? void 0,
68813
69330
  requestBaseUrl: requestBaseUrl.trim() || void 0
68814
69331
  });
68815
69332
  setReport(result);
@@ -68819,6 +69336,37 @@ function ContractPanel() {
68819
69336
  setRunning(false);
68820
69337
  }
68821
69338
  }
69339
+ async function captureSnapshot() {
69340
+ if (!specUrl.trim()) {
69341
+ setError("Enter a spec URL before pinning a snapshot.");
69342
+ return;
69343
+ }
69344
+ setCapturing(true);
69345
+ setError(null);
69346
+ try {
69347
+ const { relPath, snapshot } = await electron$3.captureContractSnapshot({ specUrl: specUrl.trim() });
69348
+ loadContractSnapshot(relPath, snapshot);
69349
+ setActiveSnapshot(relPath);
69350
+ const ws2 = useStore.getState().workspace;
69351
+ if (ws2) await electron$3.saveWorkspace(ws2);
69352
+ } catch (e) {
69353
+ setError(e instanceof Error ? e.message : String(e));
69354
+ } finally {
69355
+ setCapturing(false);
69356
+ }
69357
+ }
69358
+ async function deleteActiveSnapshot() {
69359
+ if (!activeSnapshotRelPath) return;
69360
+ const relPath = activeSnapshotRelPath;
69361
+ try {
69362
+ await electron$3.deleteContractSnapshot(relPath);
69363
+ removeContractSnapshot(relPath);
69364
+ const ws2 = useStore.getState().workspace;
69365
+ if (ws2) await electron$3.saveWorkspace(ws2);
69366
+ } catch (e) {
69367
+ setError(e instanceof Error ? e.message : String(e));
69368
+ }
69369
+ }
68822
69370
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-hidden", children: [
68823
69371
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 px-3 py-3 border-b border-surface-800 flex-shrink-0", children: [
68824
69372
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-1 bg-surface-800 rounded-lg p-0.5", children: ["consumer", "provider", "bidirectional"].map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -68836,16 +69384,65 @@ function ContractPanel() {
68836
69384
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 leading-relaxed", children: mode === "consumer" ? "Sends requests to the real provider and validates each response against the contract defined in the Contract tab." : mode === "provider" ? "Static analysis — validates that your requests conform to the provider's published OpenAPI spec (no HTTP calls)." : "Checks static schema compatibility between consumer contracts and provider spec, then verifies live responses." }),
68837
69385
  mode !== "consumer" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
68838
69386
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
69387
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Spec version" }),
69388
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [
69389
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
69390
+ "select",
69391
+ {
69392
+ value: activeSnapshotRelPath ?? "",
69393
+ onChange: (e) => setActiveSnapshot(e.target.value || null),
69394
+ disabled: !workspace,
69395
+ className: "flex-1 text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1.5 focus:outline-none focus:border-blue-500 disabled:opacity-50",
69396
+ children: [
69397
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Live URL (latest from provider)" }),
69398
+ snapshotList.map(({ relPath, snapshot }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: relPath, children: [
69399
+ snapshot.name,
69400
+ snapshot.specVersion ? "" : ` — ${snapshot.capturedAt.slice(0, 10)}`
69401
+ ] }, relPath))
69402
+ ]
69403
+ }
69404
+ ),
69405
+ activeSnapshotRelPath && /* @__PURE__ */ jsxRuntimeExports.jsx(
69406
+ "button",
69407
+ {
69408
+ onClick: deleteActiveSnapshot,
69409
+ title: "Delete this snapshot",
69410
+ className: "px-2 text-xs text-surface-500 hover:text-red-400 bg-surface-800 hover:bg-surface-700 rounded transition-colors",
69411
+ children: "✕"
69412
+ }
69413
+ )
69414
+ ] }),
69415
+ activeSnapshot && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-1 font-mono truncate", children: [
69416
+ "Captured ",
69417
+ activeSnapshot.capturedAt.slice(0, 19).replace("T", " "),
69418
+ " — sha ",
69419
+ activeSnapshot.sha256.slice(0, 8)
69420
+ ] })
69421
+ ] }),
69422
+ !activeSnapshotRelPath && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
68839
69423
  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "OpenAPI Spec URL" }),
68840
- /* @__PURE__ */ jsxRuntimeExports.jsx(
68841
- "input",
68842
- {
68843
- value: specUrl,
68844
- onChange: (e) => setSpecUrl(e.target.value),
68845
- placeholder: "https://api.example.com/openapi.json",
68846
- className: "w-full text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
68847
- }
68848
- )
69424
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [
69425
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
69426
+ "input",
69427
+ {
69428
+ value: specUrl,
69429
+ onChange: (e) => setSpecUrl(e.target.value),
69430
+ placeholder: "https://api.example.com/openapi.json",
69431
+ className: "flex-1 text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
69432
+ }
69433
+ ),
69434
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
69435
+ "button",
69436
+ {
69437
+ onClick: captureSnapshot,
69438
+ disabled: capturing || !specUrl.trim() || !workspace,
69439
+ title: "Fetch and pin this spec as a versioned snapshot",
69440
+ className: "px-2.5 text-xs bg-surface-800 hover:bg-surface-700 disabled:opacity-50 disabled:hover:bg-surface-800 rounded transition-colors",
69441
+ children: capturing ? "…" : "Pin"
69442
+ }
69443
+ )
69444
+ ] }),
69445
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-1 leading-relaxed", children: "Pin a snapshot to run against a specific spec version later, even after the provider ships an update." })
68849
69446
  ] }),
68850
69447
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
68851
69448
  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: [
@@ -68870,7 +69467,7 @@ function ContractPanel() {
68870
69467
  "button",
68871
69468
  {
68872
69469
  onClick: runContracts,
68873
- disabled: running || mode !== "consumer" && !specUrl.trim(),
69470
+ disabled: running || mode !== "consumer" && !specUrl.trim() && !activeSnapshotRelPath,
68874
69471
  className: "px-3 py-1 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors font-medium",
68875
69472
  children: running ? "Running…" : "Run"
68876
69473
  }
@@ -68884,7 +69481,7 @@ function ContractPanel() {
68884
69481
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure a mode above and click Run." }),
68885
69482
  mode !== "provider" && contractRequests.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-[180px]", children: "Define a contract on a request via the Contract tab first." })
68886
69483
  ] }),
68887
- report && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-2 rounded-lg border text-xs ${report.failed === 0 ? "bg-emerald-900/20 border-emerald-700 text-emerald-300" : "bg-red-900/20 border-red-700 text-red-300"}`, children: [
69484
+ report && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-2 rounded-lg border text-xs ${report.failed === 0 ? "bg-emerald-800/30 border-emerald-400/50 text-emerald-400" : "bg-red-900/30 border-red-700 text-red-300"}`, children: [
68888
69485
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: report.failed === 0 ? "✓ All passed" : `✗ ${report.failed} failed` }),
68889
69486
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 ml-auto", children: [
68890
69487
  report.passed,
@@ -69002,7 +69599,7 @@ function ContractResultsPanel() {
69002
69599
  })() })
69003
69600
  ] });
69004
69601
  }
69005
- const { electron: electron$1 } = window;
69602
+ const { electron: electron$2 } = window;
69006
69603
  function useToast() {
69007
69604
  const [toast, setToast] = reactExports.useState(null);
69008
69605
  const timer = reactExports.useRef(null);
@@ -69047,8 +69644,9 @@ function ChangesTab({ status, onRefresh }) {
69047
69644
  async function showDiff(file, staged) {
69048
69645
  setDiffFile(file.path);
69049
69646
  setDiffStaged(staged);
69647
+ useStore.getState().setActiveGitDiff({ path: file.path, staged });
69050
69648
  try {
69051
- const d = staged ? await electron$1.gitDiffStaged(file.path) : await electron$1.gitDiff(file.path);
69649
+ const d = staged ? await electron$2.gitDiffStaged(file.path) : await electron$2.gitDiff(file.path);
69052
69650
  setDiff(d);
69053
69651
  } catch {
69054
69652
  setDiff("");
@@ -69056,7 +69654,7 @@ function ChangesTab({ status, onRefresh }) {
69056
69654
  }
69057
69655
  async function stage(paths) {
69058
69656
  try {
69059
- await electron$1.gitStage(paths);
69657
+ await electron$2.gitStage(paths);
69060
69658
  onRefresh();
69061
69659
  } catch (e) {
69062
69660
  setError(String(e));
@@ -69064,7 +69662,7 @@ function ChangesTab({ status, onRefresh }) {
69064
69662
  }
69065
69663
  async function unstage(paths) {
69066
69664
  try {
69067
- await electron$1.gitUnstage(paths);
69665
+ await electron$2.gitUnstage(paths);
69068
69666
  onRefresh();
69069
69667
  } catch (e) {
69070
69668
  setError(String(e));
@@ -69072,7 +69670,7 @@ function ChangesTab({ status, onRefresh }) {
69072
69670
  }
69073
69671
  async function stageAll() {
69074
69672
  try {
69075
- await electron$1.gitStageAll();
69673
+ await electron$2.gitStageAll();
69076
69674
  onRefresh();
69077
69675
  } catch (e) {
69078
69676
  setError(String(e));
@@ -69082,7 +69680,7 @@ function ChangesTab({ status, onRefresh }) {
69082
69680
  if (!message.trim()) return;
69083
69681
  try {
69084
69682
  setError(null);
69085
- await electron$1.gitCommit(message.trim());
69683
+ await electron$2.gitCommit(message.trim());
69086
69684
  setMessage("");
69087
69685
  onRefresh();
69088
69686
  } catch (e) {
@@ -69092,7 +69690,7 @@ function ChangesTab({ status, onRefresh }) {
69092
69690
  async function pull() {
69093
69691
  setPulling(true);
69094
69692
  try {
69095
- await electron$1.gitPull();
69693
+ await electron$2.gitPull();
69096
69694
  showToast("Pull successful", true);
69097
69695
  onRefresh();
69098
69696
  } catch (e) {
@@ -69104,7 +69702,7 @@ function ChangesTab({ status, onRefresh }) {
69104
69702
  async function push2() {
69105
69703
  setPushing(true);
69106
69704
  try {
69107
- await electron$1.gitPush(!status.remote);
69705
+ await electron$2.gitPush(!status.remote);
69108
69706
  showToast("Push successful", true);
69109
69707
  onRefresh();
69110
69708
  } catch (e) {
@@ -69115,9 +69713,9 @@ function ChangesTab({ status, onRefresh }) {
69115
69713
  }
69116
69714
  async function resolveConflict(path, mode) {
69117
69715
  try {
69118
- if (mode === "ours") await electron$1.gitResolveOurs(path);
69119
- else if (mode === "theirs") await electron$1.gitResolveTheirs(path);
69120
- else await electron$1.gitMarkResolved(path);
69716
+ if (mode === "ours") await electron$2.gitResolveOurs(path);
69717
+ else if (mode === "theirs") await electron$2.gitResolveTheirs(path);
69718
+ else await electron$2.gitMarkResolved(path);
69121
69719
  if (diffFile === path) setDiffFile(null);
69122
69720
  onRefresh();
69123
69721
  } catch (e) {
@@ -69369,7 +69967,7 @@ function LogTab() {
69369
69967
  const [commits, setCommits] = reactExports.useState([]);
69370
69968
  const [loading, setLoading] = reactExports.useState(true);
69371
69969
  reactExports.useEffect(() => {
69372
- electron$1.gitLog(100).then(setCommits).catch(() => setCommits([])).finally(() => setLoading(false));
69970
+ electron$2.gitLog(100).then(setCommits).catch(() => setCommits([])).finally(() => setLoading(false));
69373
69971
  }, []);
69374
69972
  if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "Loading…" });
69375
69973
  if (commits.length === 0) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "No commits yet." });
@@ -69396,11 +69994,16 @@ function BranchesTab({ onRefresh }) {
69396
69994
  const [remoteUrl, setRemoteUrl] = reactExports.useState("");
69397
69995
  const [editUrl, setEditUrl] = reactExports.useState("");
69398
69996
  const [error2, setError] = reactExports.useState(null);
69997
+ const [loadError, setLoadError] = reactExports.useState(null);
69998
+ const [filter, setFilter] = reactExports.useState("");
69399
69999
  const load = reactExports.useCallback(() => {
69400
- Promise.all([electron$1.gitBranches(), electron$1.gitRemotes()]).then(([b, r]) => {
70000
+ setLoadError(null);
70001
+ Promise.all([electron$2.gitBranches(), electron$2.gitRemotes()]).then(([b, r]) => {
69401
70002
  setBranches(b);
69402
70003
  setRemotes(r);
69403
- }).catch(() => {
70004
+ }).catch((err) => {
70005
+ console.warn("GitPanel: failed to load branches/remotes", err);
70006
+ setLoadError(err instanceof Error ? err.message : String(err));
69404
70007
  }).finally(() => setLoading(false));
69405
70008
  }, []);
69406
70009
  reactExports.useEffect(() => {
@@ -69410,7 +70013,7 @@ function BranchesTab({ onRefresh }) {
69410
70013
  try {
69411
70014
  setError(null);
69412
70015
  const localName = remote2 ? name2.replace(/^origin\//, "") : name2;
69413
- await electron$1.gitCheckout(localName, false);
70016
+ await electron$2.gitCheckout(localName, false);
69414
70017
  load();
69415
70018
  onRefresh();
69416
70019
  } catch (e) {
@@ -69421,7 +70024,7 @@ function BranchesTab({ onRefresh }) {
69421
70024
  if (!newBranch.trim()) return;
69422
70025
  try {
69423
70026
  setError(null);
69424
- await electron$1.gitCheckout(newBranch.trim(), true);
70027
+ await electron$2.gitCheckout(newBranch.trim(), true);
69425
70028
  setNewBranch("");
69426
70029
  setCreating(false);
69427
70030
  load();
@@ -69434,7 +70037,7 @@ function BranchesTab({ onRefresh }) {
69434
70037
  if (!remoteName.trim() || !remoteUrl.trim()) return;
69435
70038
  try {
69436
70039
  setError(null);
69437
- await electron$1.gitAddRemote(remoteName.trim(), remoteUrl.trim());
70040
+ await electron$2.gitAddRemote(remoteName.trim(), remoteUrl.trim());
69438
70041
  setRemoteName("origin");
69439
70042
  setRemoteUrl("");
69440
70043
  setAddingRemote(false);
@@ -69451,7 +70054,7 @@ function BranchesTab({ onRefresh }) {
69451
70054
  if (!editUrl.trim()) return;
69452
70055
  try {
69453
70056
  setError(null);
69454
- await electron$1.gitSetRemoteUrl(name2, editUrl.trim());
70057
+ await electron$2.gitSetRemoteUrl(name2, editUrl.trim());
69455
70058
  setEditingRemote(null);
69456
70059
  load();
69457
70060
  } catch (e) {
@@ -69461,65 +70064,132 @@ function BranchesTab({ onRefresh }) {
69461
70064
  async function removeRemote(name2) {
69462
70065
  try {
69463
70066
  setError(null);
69464
- await electron$1.gitRemoveRemote(name2);
70067
+ await electron$2.gitRemoveRemote(name2);
69465
70068
  load();
69466
70069
  } catch (e) {
69467
70070
  setError(String(e));
69468
70071
  }
69469
70072
  }
69470
- const local = branches.filter((b) => !b.remote);
69471
- const remote = branches.filter((b) => b.remote);
70073
+ async function deleteBranch(name2) {
70074
+ if (!confirm(`Delete branch "${name2}"?`)) return;
70075
+ try {
70076
+ setError(null);
70077
+ try {
70078
+ await electron$2.gitDeleteBranch(name2, false);
70079
+ } catch {
70080
+ if (!confirm(`"${name2}" isn't fully merged. Force delete?`)) return;
70081
+ await electron$2.gitDeleteBranch(name2, true);
70082
+ }
70083
+ load();
70084
+ onRefresh();
70085
+ } catch (e) {
70086
+ setError(String(e));
70087
+ }
70088
+ }
70089
+ const q = filter.trim().toLowerCase();
70090
+ const local = branches.filter((b) => !b.remote && (!q || b.name.toLowerCase().includes(q)));
70091
+ const remote = branches.filter((b) => b.remote && (!q || b.name.toLowerCase().includes(q)));
69472
70092
  if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "Loading…" });
69473
70093
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-y-auto", children: [
69474
70094
  error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-3 py-1 truncate", children: error2 }),
69475
- /* @__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: [
70095
+ loadError && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400 px-3 py-1 truncate", title: loadError, children: [
70096
+ "Couldn't load git state: ",
70097
+ loadError
70098
+ ] }),
70099
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 border-b border-surface-800 flex flex-col gap-1.5", children: [
69476
70100
  /* @__PURE__ */ jsxRuntimeExports.jsx(
69477
70101
  "input",
69478
70102
  {
69479
- autoFocus: true,
69480
- value: newBranch,
69481
- onChange: (e) => setNewBranch(e.target.value),
69482
- onKeyDown: (e) => {
69483
- if (e.key === "Enter") createBranch();
69484
- if (e.key === "Escape") setCreating(false);
69485
- },
69486
- placeholder: "branch-name",
69487
- 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"
70103
+ value: filter,
70104
+ onChange: (e) => setFilter(e.target.value),
70105
+ placeholder: "Filter branches…",
70106
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500 placeholder-surface-600"
69488
70107
  }
69489
70108
  ),
69490
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: createBranch, className: "text-xs text-blue-400 hover:text-blue-300 px-1", children: "Create" }),
69491
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCreating(false), className: "text-xs text-surface-500 hover:text-white px-1", children: "✕" })
69492
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCreating(true), className: "text-[11px] text-blue-400 hover:text-blue-300 transition-colors", children: "+ New branch" }) }),
70109
+ creating ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5", children: [
70110
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
70111
+ "input",
70112
+ {
70113
+ autoFocus: true,
70114
+ value: newBranch,
70115
+ onChange: (e) => setNewBranch(e.target.value),
70116
+ onKeyDown: (e) => {
70117
+ if (e.key === "Enter") createBranch();
70118
+ if (e.key === "Escape") setCreating(false);
70119
+ },
70120
+ placeholder: "branch-name",
70121
+ 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"
70122
+ }
70123
+ ),
70124
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: createBranch, className: "text-xs text-blue-400 hover:text-blue-300 px-1", children: "Create" }),
70125
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCreating(false), className: "text-xs text-surface-500 hover:text-white px-1", children: "✕" })
70126
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => setCreating(true), className: "text-[11px] text-blue-400 hover:text-blue-300 transition-colors text-left", children: [
70127
+ "+ New branch (from ",
70128
+ branches.find((b) => b.current)?.name ?? "HEAD",
70129
+ ")"
70130
+ ] })
70131
+ ] }),
69493
70132
  local.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
69494
70133
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Local" }),
69495
70134
  local.map((b) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
69496
- "button",
70135
+ "div",
69497
70136
  {
69498
- onClick: () => !b.current && checkout(b.name, false),
69499
- 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"}`,
70137
+ className: `group w-full flex items-center gap-2 px-3 py-1.5 text-xs transition-colors ${b.current ? "bg-blue-900/30 text-blue-200" : "text-surface-300 hover:bg-surface-800/50"}`,
69500
70138
  children: [
69501
70139
  b.current ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400 text-[10px]", children: "●" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3" }),
69502
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: b.name })
70140
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
70141
+ "button",
70142
+ {
70143
+ onClick: () => !b.current && checkout(b.name, false),
70144
+ disabled: b.current,
70145
+ className: "flex-1 text-left font-mono truncate disabled:cursor-default",
70146
+ title: b.upstream ? `tracks ${b.upstream}` : "no upstream",
70147
+ children: b.name
70148
+ }
70149
+ ),
70150
+ b.behind ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] px-1 rounded bg-amber-900/40 text-amber-300", title: `${b.behind} commits behind ${b.upstream}`, children: [
70151
+ "↓",
70152
+ b.behind
70153
+ ] }) : null,
70154
+ b.ahead ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] px-1 rounded bg-emerald-900/40 text-emerald-300", title: `${b.ahead} commits ahead of ${b.upstream}`, children: [
70155
+ "↑",
70156
+ b.ahead
70157
+ ] }) : null,
70158
+ !b.current && /* @__PURE__ */ jsxRuntimeExports.jsx(
70159
+ "button",
70160
+ {
70161
+ onClick: () => deleteBranch(b.name),
70162
+ className: "opacity-0 group-hover:opacity-100 text-surface-500 hover:text-red-400 transition-all text-xs leading-none",
70163
+ title: `Delete ${b.name}`,
70164
+ children: "✕"
70165
+ }
70166
+ )
69503
70167
  ]
69504
70168
  },
69505
70169
  b.name
69506
70170
  ))
69507
70171
  ] }),
69508
70172
  remote.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
69509
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Remote branches" }),
70173
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Remote" }),
69510
70174
  remote.map((b) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
69511
70175
  "button",
69512
70176
  {
69513
- onClick: () => checkout(b.name, true),
70177
+ onClick: () => checkout(b.name, false),
69514
70178
  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",
70179
+ title: "Click to check out — creates a local tracking branch if needed",
69515
70180
  children: [
69516
70181
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3" }),
69517
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: b.name })
70182
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate", children: b.name })
69518
70183
  ]
69519
70184
  },
69520
70185
  b.name
69521
70186
  ))
69522
70187
  ] }),
70188
+ q && local.length === 0 && remote.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "px-3 py-3 text-[11px] text-surface-500", children: [
70189
+ 'No branches match "',
70190
+ filter,
70191
+ '".'
70192
+ ] }),
69523
70193
  /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "border-t border-surface-800 mt-1", children: [
69524
70194
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
69525
70195
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Remotes" }),
@@ -69709,11 +70379,12 @@ function CiTab() {
69709
70379
  const [written, setWritten] = reactExports.useState(false);
69710
70380
  const [error2, setError] = reactExports.useState(null);
69711
70381
  reactExports.useEffect(() => {
69712
- electron$1.gitRemotes().then((r) => {
70382
+ electron$2.gitRemotes().then((r) => {
69713
70383
  setRemotes(r);
69714
70384
  const detected = detectPlatform(r);
69715
70385
  setPlatform(detected);
69716
- }).catch(() => {
70386
+ }).catch((err) => {
70387
+ console.warn("GitPanel: failed to read git remotes", err);
69717
70388
  });
69718
70389
  }, []);
69719
70390
  reactExports.useEffect(() => {
@@ -69726,7 +70397,7 @@ function CiTab() {
69726
70397
  async function write() {
69727
70398
  try {
69728
70399
  setError(null);
69729
- await electron$1.gitWriteCiFile(ciFilePath(platform), preview);
70400
+ await electron$2.gitWriteCiFile(ciFilePath(platform), preview);
69730
70401
  setWritten(true);
69731
70402
  } catch (e) {
69732
70403
  setError(String(e));
@@ -69822,9 +70493,9 @@ function GitPanel() {
69822
70493
  const [loading, setLoading] = reactExports.useState(true);
69823
70494
  const refresh = reactExports.useCallback(async () => {
69824
70495
  try {
69825
- const repo = await electron$1.gitIsRepo();
70496
+ const repo = await electron$2.gitIsRepo();
69826
70497
  setIsRepo(repo);
69827
- if (repo) setStatus(await electron$1.gitStatus());
70498
+ if (repo) setStatus(await electron$2.gitStatus());
69828
70499
  } catch {
69829
70500
  setIsRepo(false);
69830
70501
  } finally {
@@ -69844,7 +70515,7 @@ function GitPanel() {
69844
70515
  "button",
69845
70516
  {
69846
70517
  onClick: async () => {
69847
- await electron$1.gitInit();
70518
+ await electron$2.gitInit();
69848
70519
  refresh();
69849
70520
  },
69850
70521
  className: "px-3 py-1.5 bg-surface-800 hover:bg-surface-700 rounded text-xs transition-colors",
@@ -69884,6 +70555,62 @@ function GitPanel() {
69884
70555
  tab === "ci" && /* @__PURE__ */ jsxRuntimeExports.jsx(CiTab, {})
69885
70556
  ] });
69886
70557
  }
70558
+ const { electron: electron$1 } = window;
70559
+ function GitDiffPane() {
70560
+ const active = useStore((s) => s.activeGitDiff);
70561
+ const setActive2 = useStore((s) => s.setActiveGitDiff);
70562
+ const [diff, setDiff] = reactExports.useState("");
70563
+ const [loading, setLoading] = reactExports.useState(false);
70564
+ const [error2, setError] = reactExports.useState(null);
70565
+ reactExports.useEffect(() => {
70566
+ if (!active) return;
70567
+ let cancelled = false;
70568
+ setLoading(true);
70569
+ setError(null);
70570
+ (async () => {
70571
+ try {
70572
+ const d = active.staged ? await electron$1.gitDiffStaged(active.path) : await electron$1.gitDiff(active.path);
70573
+ if (!cancelled) setDiff(d);
70574
+ } catch (err) {
70575
+ if (!cancelled) setError(err instanceof Error ? err.message : String(err));
70576
+ } finally {
70577
+ if (!cancelled) setLoading(false);
70578
+ }
70579
+ })();
70580
+ return () => {
70581
+ cancelled = true;
70582
+ };
70583
+ }, [active?.path, active?.staged]);
70584
+ if (!active) {
70585
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-xs text-surface-500", children: "Select a changed file in the sidebar to view its diff." });
70586
+ }
70587
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 flex flex-col", children: [
70588
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2 border-b border-surface-800 flex items-center gap-3 flex-shrink-0", children: [
70589
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
70590
+ "span",
70591
+ {
70592
+ className: `text-[10px] uppercase tracking-wider font-semibold ${active.staged ? "text-emerald-400" : "text-amber-400"}`,
70593
+ children: active.staged ? "Staged" : "Working tree"
70594
+ }
70595
+ ),
70596
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-surface-200 truncate flex-1", children: active.path }),
70597
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
70598
+ "button",
70599
+ {
70600
+ onClick: () => setActive2(null),
70601
+ className: "text-surface-500 hover:text-white text-xs leading-none",
70602
+ title: "Close diff",
70603
+ children: "✕"
70604
+ }
70605
+ )
70606
+ ] }),
70607
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-auto min-h-0", children: [
70608
+ loading && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 p-4", children: "Loading diff…" }),
70609
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400 p-4", children: error2 }),
70610
+ !loading && !error2 && /* @__PURE__ */ jsxRuntimeExports.jsx(DiffViewer, { diff })
70611
+ ] })
70612
+ ] });
70613
+ }
69887
70614
  function CommandPalette() {
69888
70615
  const open = useStore((s) => s.commandPaletteOpen);
69889
70616
  const setOpen = useStore((s) => s.setCommandPaletteOpen);
@@ -70241,7 +70968,7 @@ function App() {
70241
70968
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
70242
70969
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
70243
70970
  "v",
70244
- "0.2.2"
70971
+ "0.2.4"
70245
70972
  ] }),
70246
70973
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
70247
70974
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -70438,7 +71165,7 @@ function App() {
70438
71165
  }
70439
71166
  )
70440
71167
  ] }),
70441
- sidebarTab === "contracts" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ContractResultsPanel, {}) }) : sidebarTab === "mocks" && recorderRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
71168
+ sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitDiffPane, {}) }) : sidebarTab === "contracts" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ContractResultsPanel, {}) }) : sidebarTab === "mocks" && recorderRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
70442
71169
  RecorderPanel,
70443
71170
  {
70444
71171
  defaultTargetMockId: recorderTargetMockId,