@testsmith/api-spector 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13983,7 +13983,7 @@ const useStore = create()(
13983
13983
  })
13984
13984
  }))
13985
13985
  );
13986
- const { electron: electron$l } = window;
13986
+ const { electron: electron$m } = window;
13987
13987
  function useAutoSave() {
13988
13988
  const collections = useStore((s) => s.collections);
13989
13989
  useStore((s) => s.environments);
@@ -13999,7 +13999,7 @@ function useAutoSave() {
13999
13999
  for (const { relPath, data, dirty } of dirtyCollections) {
14000
14000
  if (!dirty) continue;
14001
14001
  try {
14002
- await electron$l.saveCollection(relPath, data);
14002
+ await electron$m.saveCollection(relPath, data);
14003
14003
  markCollectionClean(data.id);
14004
14004
  } catch (e) {
14005
14005
  console.error("Auto-save failed for", relPath, e);
@@ -14015,7 +14015,7 @@ function useAutoSave() {
14015
14015
  if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
14016
14016
  wsTimerRef.current = setTimeout(async () => {
14017
14017
  try {
14018
- await electron$l.saveWorkspace(workspace);
14018
+ await electron$m.saveWorkspace(workspace);
14019
14019
  } catch {
14020
14020
  }
14021
14021
  }, 300);
@@ -14024,7 +14024,7 @@ function useAutoSave() {
14024
14024
  };
14025
14025
  }, [workspace]);
14026
14026
  }
14027
- const { electron: electron$k } = window;
14027
+ const { electron: electron$l } = window;
14028
14028
  function useWorkspaceLoader() {
14029
14029
  const loadCollection = useStore((s) => s.loadCollection);
14030
14030
  const loadEnvironment = useStore((s) => s.loadEnvironment);
@@ -14043,28 +14043,28 @@ function useWorkspaceLoader() {
14043
14043
  });
14044
14044
  for (const colPath of ws2.collections) {
14045
14045
  try {
14046
- const col = await electron$k.loadCollection(colPath);
14046
+ const col = await electron$l.loadCollection(colPath);
14047
14047
  loadCollection(colPath, col);
14048
14048
  } catch {
14049
14049
  }
14050
14050
  }
14051
14051
  for (const envPath of ws2.environments) {
14052
14052
  try {
14053
- const env = await electron$k.loadEnvironment(envPath);
14053
+ const env = await electron$l.loadEnvironment(envPath);
14054
14054
  loadEnvironment(envPath, env);
14055
14055
  } catch {
14056
14056
  }
14057
14057
  }
14058
14058
  for (const relPath of ws2.mocks ?? []) {
14059
14059
  try {
14060
- const mockData = await electron$k.loadMock(relPath);
14060
+ const mockData = await electron$l.loadMock(relPath);
14061
14061
  loadMock(relPath, mockData);
14062
14062
  } catch {
14063
14063
  }
14064
14064
  }
14065
14065
  if (ws2.collections.length > 0) {
14066
14066
  try {
14067
- const firstCol = await electron$k.loadCollection(ws2.collections[0]);
14067
+ const firstCol = await electron$l.loadCollection(ws2.collections[0]);
14068
14068
  setActiveCollection(firstCol.id);
14069
14069
  } catch {
14070
14070
  }
@@ -42246,7 +42246,7 @@ const autoCloseTags = /* @__PURE__ */ EditorView.inputHandler.of((view, from, to
42246
42246
  ]);
42247
42247
  return true;
42248
42248
  });
42249
- const { electron: electron$j } = window;
42249
+ const { electron: electron$k } = window;
42250
42250
  function SoapEditor({ request, onChange }) {
42251
42251
  const soap = request.body.soap ?? {
42252
42252
  wsdlUrl: "",
@@ -42263,7 +42263,7 @@ function SoapEditor({ request, onChange }) {
42263
42263
  setFetching(true);
42264
42264
  setFetchError(null);
42265
42265
  try {
42266
- const result = await electron$j.wsdlFetch(soap.wsdlUrl.trim());
42266
+ const result = await electron$k.wsdlFetch(soap.wsdlUrl.trim());
42267
42267
  setOperations(result.operations);
42268
42268
  if (result.operations.length > 0) {
42269
42269
  const first = result.operations[0];
@@ -42423,7 +42423,7 @@ function BodyTab({ request, onChange }) {
42423
42423
  mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request, onChange }) })
42424
42424
  ] });
42425
42425
  }
42426
- const { electron: electron$i } = window;
42426
+ const { electron: electron$j } = window;
42427
42427
  const AUTH_TYPES = ["none", "bearer", "basic", "digest", "ntlm", "apikey", "oauth2"];
42428
42428
  function AuthTab({ request, onChange }) {
42429
42429
  const auth = request.auth;
@@ -42437,7 +42437,7 @@ function AuthTab({ request, onChange }) {
42437
42437
  }
42438
42438
  async function saveSecret(ref2) {
42439
42439
  if (!secretValue || !ref2) return;
42440
- await electron$i.setSecret(ref2, secretValue);
42440
+ await electron$j.setSecret(ref2, secretValue);
42441
42441
  setSaved(true);
42442
42442
  setSecretValue("");
42443
42443
  setTimeout(() => setSaved(false), 2e3);
@@ -42448,14 +42448,14 @@ function AuthTab({ request, onChange }) {
42448
42448
  try {
42449
42449
  const vars = {};
42450
42450
  if (auth.oauth2Flow === "authorization_code") {
42451
- const result = await electron$i.oauth2StartFlow(auth, vars);
42451
+ const result = await electron$j.oauth2StartFlow(auth, vars);
42452
42452
  setAuth({
42453
42453
  oauth2CachedToken: result.accessToken,
42454
42454
  oauth2TokenExpiry: result.expiresAt
42455
42455
  });
42456
42456
  if (result.refreshToken) setOauth2RT(result.refreshToken);
42457
42457
  } else {
42458
- const result = await electron$i.oauth2StartFlow(auth, vars);
42458
+ const result = await electron$j.oauth2StartFlow(auth, vars);
42459
42459
  setAuth({
42460
42460
  oauth2CachedToken: result.accessToken,
42461
42461
  oauth2TokenExpiry: result.expiresAt
@@ -42473,7 +42473,7 @@ function AuthTab({ request, onChange }) {
42473
42473
  setOauth2Status("fetching");
42474
42474
  setOauth2Error("");
42475
42475
  try {
42476
- const result = await electron$i.oauth2RefreshToken(auth, {}, oauth2RefreshToken);
42476
+ const result = await electron$j.oauth2RefreshToken(auth, {}, oauth2RefreshToken);
42477
42477
  setAuth({
42478
42478
  oauth2CachedToken: result.accessToken,
42479
42479
  oauth2TokenExpiry: result.expiresAt
@@ -49470,7 +49470,7 @@ function SchemaTab({ request, onChange }) {
49470
49470
  ] }) })
49471
49471
  ] });
49472
49472
  }
49473
- const { electron: electron$h } = window;
49473
+ const { electron: electron$i } = window;
49474
49474
  const EMPTY = { statusCode: 200, headers: [], bodySchema: "" };
49475
49475
  function ContractTab({ request, onChange }) {
49476
49476
  const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
@@ -49484,7 +49484,7 @@ function ContractTab({ request, onChange }) {
49484
49484
  if (!lastResponse?.body) return;
49485
49485
  setInferring(true);
49486
49486
  try {
49487
- const schema = await electron$h.inferContractSchema(lastResponse.body);
49487
+ const schema = await electron$i.inferContractSchema(lastResponse.body);
49488
49488
  if (schema) update({ bodySchema: schema });
49489
49489
  } finally {
49490
49490
  setInferring(false);
@@ -49592,7 +49592,7 @@ function ContractTab({ request, onChange }) {
49592
49592
  ] })
49593
49593
  ] });
49594
49594
  }
49595
- const { electron: electron$g } = window;
49595
+ const { electron: electron$h } = window;
49596
49596
  function formatTime$1(ts) {
49597
49597
  const d = new Date(ts);
49598
49598
  const hh = String(d.getHours()).padStart(2, "0");
@@ -49612,14 +49612,14 @@ function WebSocketPanel({ request }) {
49612
49612
  const [sendText, setSendText] = reactExports.useState("");
49613
49613
  const logEndRef = reactExports.useRef(null);
49614
49614
  reactExports.useEffect(() => {
49615
- electron$g.onWsMessage(({ requestId, message }) => {
49615
+ electron$h.onWsMessage(({ requestId, message }) => {
49616
49616
  addWsMessage(requestId, message);
49617
49617
  });
49618
- electron$g.onWsStatus(({ requestId, status, error: error2 }) => {
49618
+ electron$h.onWsStatus(({ requestId, status, error: error2 }) => {
49619
49619
  setWsStatus(requestId, status, error2);
49620
49620
  });
49621
49621
  return () => {
49622
- electron$g.offWsEvents();
49622
+ electron$h.offWsEvents();
49623
49623
  };
49624
49624
  }, [addWsMessage, setWsStatus]);
49625
49625
  reactExports.useEffect(() => {
@@ -49632,19 +49632,19 @@ function WebSocketPanel({ request }) {
49632
49632
  if (h.enabled && h.key) headers[h.key] = h.value;
49633
49633
  }
49634
49634
  try {
49635
- await electron$g.wsConnect(request.id, request.url, headers);
49635
+ await electron$h.wsConnect(request.id, request.url, headers);
49636
49636
  } catch (err) {
49637
49637
  setWsStatus(request.id, "error", err instanceof Error ? err.message : String(err));
49638
49638
  }
49639
49639
  }
49640
49640
  async function disconnect() {
49641
- await electron$g.wsDisconnect(request.id);
49641
+ await electron$h.wsDisconnect(request.id);
49642
49642
  }
49643
49643
  async function sendMessage() {
49644
49644
  const text = sendText.trim();
49645
49645
  if (!text || !isConnected) return;
49646
49646
  try {
49647
- await electron$g.wsSend(request.id, text);
49647
+ await electron$h.wsSend(request.id, text);
49648
49648
  const msg = {
49649
49649
  id: crypto.randomUUID(),
49650
49650
  direction: "sent",
@@ -49747,7 +49747,7 @@ function WebSocketPanel({ request }) {
49747
49747
  ] })
49748
49748
  ] });
49749
49749
  }
49750
- const { electron: electron$f } = window;
49750
+ const { electron: electron$g } = window;
49751
49751
  const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
49752
49752
  const METHOD_COLORS = {
49753
49753
  GET: "text-emerald-400",
@@ -49801,7 +49801,7 @@ function RequestBuilder({ request }) {
49801
49801
  auth: mergedAuth,
49802
49802
  headers: mergedHeaders
49803
49803
  };
49804
- const result = await electron$f.sendRequest({
49804
+ const result = await electron$g.sendRequest({
49805
49805
  request: mergedRequest,
49806
49806
  environment: activeEnv,
49807
49807
  collectionVars,
@@ -50450,7 +50450,7 @@ function InteractiveBody({ body, contentType, onAssert }) {
50450
50450
  treeContent
50451
50451
  ] });
50452
50452
  }
50453
- const { electron: electron$e } = window;
50453
+ const { electron: electron$f } = window;
50454
50454
  function prettyJson(raw) {
50455
50455
  try {
50456
50456
  return JSON.stringify(JSON.parse(raw), null, 2);
@@ -50536,14 +50536,14 @@ function SaveAsMockModal({ onClose }) {
50536
50536
  const entry = state.mocks[serverId];
50537
50537
  const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
50538
50538
  updateMock(serverId, updated);
50539
- await electron$e.saveMock(entry.relPath, updated);
50539
+ await electron$f.saveMock(entry.relPath, updated);
50540
50540
  const ws2 = useStore.getState().workspace;
50541
- if (ws2) await electron$e.saveWorkspace(ws2);
50541
+ if (ws2) await electron$f.saveWorkspace(ws2);
50542
50542
  } else {
50543
50543
  const entry = useStore.getState().mocks[serverId];
50544
50544
  const updated = { ...entry.data, routes: [...entry.data.routes, route] };
50545
50545
  updateMock(serverId, updated);
50546
- await electron$e.saveMock(entry.relPath, updated);
50546
+ await electron$f.saveMock(entry.relPath, updated);
50547
50547
  }
50548
50548
  onClose();
50549
50549
  } finally {
@@ -50774,7 +50774,7 @@ function ResponseViewer() {
50774
50774
  const [contractToast, setContractToast] = reactExports.useState(false);
50775
50775
  async function saveAsContract() {
50776
50776
  if (!response || !requestId || !activeTabId) return;
50777
- const schema = response.body ? await electron$e.inferContractSchema(response.body) : null;
50777
+ const schema = response.body ? await electron$f.inferContractSchema(response.body) : null;
50778
50778
  const contentType2 = response.headers["content-type"];
50779
50779
  const headers = contentType2 ? [{ key: "content-type", value: contentType2, required: true }] : [];
50780
50780
  updateRequest(requestId, {
@@ -51017,7 +51017,7 @@ function ConsolePanel({ scriptResult }) {
51017
51017
  i
51018
51018
  )) });
51019
51019
  }
51020
- const { electron: electron$d } = window;
51020
+ const { electron: electron$e } = window;
51021
51021
  const TARGETS = [
51022
51022
  { id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
51023
51023
  { id: "playwright_ts", label: "Playwright TS", description: "TypeScript page-object API classes + spec files" },
@@ -51048,7 +51048,7 @@ function GeneratorPanel() {
51048
51048
  try {
51049
51049
  const col = collections[selectedCollectionId]?.data;
51050
51050
  const env = activeEnvironmentId ? environments[activeEnvironmentId]?.data ?? null : null;
51051
- const generated = await electron$d.generateCode({ collection: col, environment: env, target });
51051
+ const generated = await electron$e.generateCode({ collection: col, environment: env, target });
51052
51052
  setFiles(generated);
51053
51053
  setSelectedFile(generated[0]?.path ?? null);
51054
51054
  } catch (e) {
@@ -51060,7 +51060,7 @@ function GeneratorPanel() {
51060
51060
  async function saveZip() {
51061
51061
  if (files.length === 0) return;
51062
51062
  const col = collections[selectedCollectionId]?.data;
51063
- await electron$d.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
51063
+ await electron$e.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
51064
51064
  }
51065
51065
  const selectedContent = files.find((f) => f.path === selectedFile)?.content ?? "";
51066
51066
  const activeTarget = TARGETS.find((t2) => t2.id === target);
@@ -51284,16 +51284,16 @@ function HistoryRow({
51284
51284
  }
51285
51285
  );
51286
51286
  }
51287
- const { electron: electron$c } = window;
51287
+ const { electron: electron$d } = window;
51288
51288
  function WelcomeScreen() {
51289
51289
  const { applyWorkspace } = useWorkspaceLoader();
51290
51290
  async function openWorkspace() {
51291
- const result = await electron$c.openWorkspace();
51291
+ const result = await electron$d.openWorkspace();
51292
51292
  if (!result) return;
51293
51293
  await applyWorkspace(result.workspace, result.workspacePath);
51294
51294
  }
51295
51295
  async function newWorkspace() {
51296
- const result = await electron$c.newWorkspace();
51296
+ const result = await electron$d.newWorkspace();
51297
51297
  if (!result) return;
51298
51298
  await applyWorkspace(result.workspace, result.workspacePath);
51299
51299
  }
@@ -51327,7 +51327,7 @@ function WelcomeScreen() {
51327
51327
  ] })
51328
51328
  ] });
51329
51329
  }
51330
- const { electron: electron$b } = window;
51330
+ const { electron: electron$c } = window;
51331
51331
  const EXAMPLES = [
51332
51332
  {
51333
51333
  label: "macOS / Linux (~/.zshrc or ~/.bashrc)",
@@ -51351,7 +51351,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
51351
51351
  setError("Password cannot be empty.");
51352
51352
  return;
51353
51353
  }
51354
- await electron$b.setMasterKey(password);
51354
+ await electron$c.setMasterKey(password);
51355
51355
  onSuccess(password);
51356
51356
  }
51357
51357
  function copy(idx, text) {
@@ -51441,7 +51441,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
51441
51441
  }
51442
51442
  );
51443
51443
  }
51444
- const { electron: electron$a } = window;
51444
+ const { electron: electron$b } = window;
51445
51445
  async function shortHash(value) {
51446
51446
  const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
51447
51447
  return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
@@ -51542,7 +51542,7 @@ function EnvironmentEditor({ onClose }) {
51542
51542
  async function saveEncrypted(idx) {
51543
51543
  const plaintext = secretInputs[idx] ?? "";
51544
51544
  if (!plaintext) return;
51545
- const { set: set2 } = await electron$a.checkMasterKey();
51545
+ const { set: set2 } = await electron$b.checkMasterKey();
51546
51546
  if (!set2) {
51547
51547
  setPendingEncryptIdx(idx);
51548
51548
  return;
@@ -51579,9 +51579,9 @@ function EnvironmentEditor({ onClose }) {
51579
51579
  } : state.workspace
51580
51580
  }));
51581
51581
  const ws2 = useStore.getState().workspace;
51582
- if (ws2) await electron$a.saveWorkspace(ws2);
51582
+ if (ws2) await electron$b.saveWorkspace(ws2);
51583
51583
  }
51584
- await electron$a.saveEnvironment(newRelPath, env);
51584
+ await electron$b.saveEnvironment(newRelPath, env);
51585
51585
  }
51586
51586
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
51587
51587
  pendingEncryptIdx !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -51865,7 +51865,7 @@ function EnvironmentEditor({ onClose }) {
51865
51865
  )
51866
51866
  ] });
51867
51867
  }
51868
- const { electron: electron$9 } = window;
51868
+ const { electron: electron$a } = window;
51869
51869
  function EnvironmentBar({ inline = false }) {
51870
51870
  const environments = useStore((s) => s.environments);
51871
51871
  const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
@@ -51879,7 +51879,7 @@ function EnvironmentBar({ inline = false }) {
51879
51879
  if (id2) {
51880
51880
  const hasSecrets = environments[id2]?.data.variables.some((v) => v.enabled && v.secret);
51881
51881
  if (hasSecrets) {
51882
- const { set: set2 } = await electron$9.checkMasterKey();
51882
+ const { set: set2 } = await electron$a.checkMasterKey();
51883
51883
  if (!set2) {
51884
51884
  setPendingEnvId(id2);
51885
51885
  return;
@@ -51933,7 +51933,7 @@ function EnvironmentBar({ inline = false }) {
51933
51933
  controls
51934
51934
  ] });
51935
51935
  }
51936
- const { electron: electron$8 } = window;
51936
+ const { electron: electron$9 } = window;
51937
51937
  const DEFAULT_PII_PATTERNS = ["authorization", "password", "token", "secret", "api-key", "x-api-key"];
51938
51938
  function WorkspaceSettingsModal({ onClose }) {
51939
51939
  const workspace = useStore((s) => s.workspace);
@@ -51979,7 +51979,7 @@ function WorkspaceSettingsModal({ onClose }) {
51979
51979
  settings.piiMaskPatterns = patterns;
51980
51980
  updateWorkspaceSettings(settings);
51981
51981
  const updated = useStore.getState().workspace;
51982
- if (updated) await electron$8.saveWorkspace(updated);
51982
+ if (updated) await electron$9.saveWorkspace(updated);
51983
51983
  onClose();
51984
51984
  }
51985
51985
  const tabs = [
@@ -52177,7 +52177,7 @@ function WorkspaceSettingsModal({ onClose }) {
52177
52177
  }
52178
52178
  );
52179
52179
  }
52180
- const { electron: electron$7 } = window;
52180
+ const { electron: electron$8 } = window;
52181
52181
  function DocsGeneratorModal({ onClose }) {
52182
52182
  const collections = useStore((s) => s.collections);
52183
52183
  const collectionList = Object.values(collections);
@@ -52206,9 +52206,9 @@ function DocsGeneratorModal({ onClose }) {
52206
52206
  setGenerating(true);
52207
52207
  setError(null);
52208
52208
  try {
52209
- const content2 = await electron$7.generateDocs(buildPayload());
52209
+ const content2 = await electron$8.generateDocs(buildPayload());
52210
52210
  const filename = format2 === "html" ? "api-docs.html" : "api-docs.md";
52211
- await electron$7.saveResults(content2, filename);
52211
+ await electron$8.saveResults(content2, filename);
52212
52212
  } catch (err) {
52213
52213
  setError(err instanceof Error ? err.message : String(err));
52214
52214
  } finally {
@@ -52219,7 +52219,7 @@ function DocsGeneratorModal({ onClose }) {
52219
52219
  setGenerating(true);
52220
52220
  setError(null);
52221
52221
  try {
52222
- const content2 = await electron$7.generateDocs(buildPayload());
52222
+ const content2 = await electron$8.generateDocs(buildPayload());
52223
52223
  setPreview(content2);
52224
52224
  } catch (err) {
52225
52225
  setError(err instanceof Error ? err.message : String(err));
@@ -52335,7 +52335,7 @@ function DocsGeneratorModal({ onClose }) {
52335
52335
  ] })
52336
52336
  ] }) });
52337
52337
  }
52338
- const { electron: electron$6 } = window;
52338
+ const { electron: electron$7 } = window;
52339
52339
  const OPTIONS = [
52340
52340
  { id: "postman", label: "Postman", description: "Collection v2.1 JSON" },
52341
52341
  { id: "openapi", label: "OpenAPI", description: "JSON or YAML (v3.x)", supportsUrl: true },
@@ -52353,10 +52353,10 @@ function ImportModal({ onImport, onClose }) {
52353
52353
  setError(null);
52354
52354
  try {
52355
52355
  let col = null;
52356
- if (opt.id === "postman") col = await electron$6.importPostman();
52357
- if (opt.id === "openapi") col = await electron$6.importOpenApi();
52358
- if (opt.id === "insomnia") col = await electron$6.importInsomnia();
52359
- if (opt.id === "bruno") col = await electron$6.importBruno();
52356
+ if (opt.id === "postman") col = await electron$7.importPostman();
52357
+ if (opt.id === "openapi") col = await electron$7.importOpenApi();
52358
+ if (opt.id === "insomnia") col = await electron$7.importInsomnia();
52359
+ if (opt.id === "bruno") col = await electron$7.importBruno();
52360
52360
  onImport(col);
52361
52361
  onClose();
52362
52362
  } catch (err) {
@@ -52371,7 +52371,7 @@ function ImportModal({ onImport, onClose }) {
52371
52371
  setLoading(true);
52372
52372
  setError(null);
52373
52373
  try {
52374
- const col = await electron$6.importOpenApiFromUrl(trimmed);
52374
+ const col = await electron$7.importOpenApiFromUrl(trimmed);
52375
52375
  onImport(col);
52376
52376
  onClose();
52377
52377
  } catch (err) {
@@ -52466,7 +52466,7 @@ function ImportModal({ onImport, onClose }) {
52466
52466
  }
52467
52467
  ) });
52468
52468
  }
52469
- const { electron: electron$5 } = window;
52469
+ const { electron: electron$6 } = window;
52470
52470
  const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
52471
52471
  function PreferencesPopover() {
52472
52472
  const theme2 = useStore((s) => s.theme);
@@ -52572,13 +52572,13 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52572
52572
  try {
52573
52573
  for (const { relPath, data, dirty } of Object.values(collections)) {
52574
52574
  if (!dirty) continue;
52575
- await electron$5.saveCollection(relPath, data);
52575
+ await electron$6.saveCollection(relPath, data);
52576
52576
  markCollectionClean(data.id);
52577
52577
  }
52578
52578
  for (const { relPath, data } of Object.values(environments)) {
52579
- await electron$5.saveEnvironment(relPath, data);
52579
+ await electron$6.saveEnvironment(relPath, data);
52580
52580
  }
52581
- if (workspace) await electron$5.saveWorkspace(workspace);
52581
+ if (workspace) await electron$6.saveWorkspace(workspace);
52582
52582
  } finally {
52583
52583
  setSaving(false);
52584
52584
  }
@@ -52586,14 +52586,14 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52586
52586
  async function afterImport(col) {
52587
52587
  if (!col) return;
52588
52588
  const relPath = colRelPath(col.name, col.id);
52589
- await electron$5.saveCollection(relPath, col);
52589
+ await electron$6.saveCollection(relPath, col);
52590
52590
  loadCollection(relPath, col);
52591
52591
  setActiveCollection(col.id);
52592
52592
  const ws2 = useStore.getState().workspace;
52593
52593
  if (ws2 && !ws2.collections.includes(relPath)) {
52594
52594
  const updated = { ...ws2, collections: [...ws2.collections, relPath] };
52595
52595
  useStore.setState({ workspace: updated });
52596
- await electron$5.saveWorkspace(updated);
52596
+ await electron$6.saveWorkspace(updated);
52597
52597
  }
52598
52598
  }
52599
52599
  if (!workspace) return null;
@@ -52659,7 +52659,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52659
52659
  "button",
52660
52660
  {
52661
52661
  onClick: async () => {
52662
- const result = await electron$5.openWorkspace();
52662
+ const result = await electron$6.openWorkspace();
52663
52663
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
52664
52664
  },
52665
52665
  className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
@@ -52671,7 +52671,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52671
52671
  "button",
52672
52672
  {
52673
52673
  onClick: async () => {
52674
- const result = await electron$5.newWorkspace();
52674
+ const result = await electron$6.newWorkspace();
52675
52675
  if (result) await applyWorkspace(result.workspace, result.workspacePath);
52676
52676
  },
52677
52677
  className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
@@ -52683,7 +52683,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
52683
52683
  "button",
52684
52684
  {
52685
52685
  onClick: async () => {
52686
- await electron$5.closeWorkspace();
52686
+ await electron$6.closeWorkspace();
52687
52687
  closeWorkspace();
52688
52688
  },
52689
52689
  className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
@@ -53080,7 +53080,7 @@ api-tests:
53080
53080
  function EmptyState({ message }) {
53081
53081
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-24 text-surface-400 text-xs", children: message });
53082
53082
  }
53083
- const { electron: electron$4 } = window;
53083
+ const { electron: electron$5 } = window;
53084
53084
  function collectRequests(collectionId, folderId, filterTags) {
53085
53085
  const state = useStore.getState();
53086
53086
  const col = state.collections[collectionId]?.data;
@@ -53176,13 +53176,13 @@ function RunnerModal() {
53176
53176
  setSummary(null);
53177
53177
  setRunnerRunning(true);
53178
53178
  progressIdxRef.current = 0;
53179
- electron$4.onRunProgress((result) => {
53179
+ electron$5.onRunProgress((result) => {
53180
53180
  const idx = progressIdxRef.current;
53181
53181
  patchRunnerResult(idx, result);
53182
53182
  if (result.status !== "running") progressIdxRef.current++;
53183
53183
  });
53184
53184
  try {
53185
- const s = await electron$4.runCollection({
53185
+ const s = await electron$5.runCollection({
53186
53186
  items: items2,
53187
53187
  environment: env,
53188
53188
  globals,
@@ -53193,7 +53193,7 @@ function RunnerModal() {
53193
53193
  });
53194
53194
  setSummary(s);
53195
53195
  } finally {
53196
- electron$4.offRunProgress();
53196
+ electron$5.offRunProgress();
53197
53197
  setRunnerRunning(false);
53198
53198
  }
53199
53199
  }, [collectionId, folderId, filterTags, selectedEnvId, environments, globals, colEntry, requestDelay, workspaceSettings, setRunnerResults, patchRunnerResult, setRunnerRunning]);
@@ -53406,7 +53406,7 @@ function RunnerModal() {
53406
53406
  };
53407
53407
  const content2 = exportFormat === "junit" ? buildJUnitReport(runnerResults, summary, meta2) : exportFormat === "html" ? buildHtmlReport(runnerResults, summary, meta2) : buildJsonReport(runnerResults, summary, meta2);
53408
53408
  const ext = exportFormat === "junit" ? "xml" : exportFormat === "html" ? "html" : "json";
53409
- electron$4.saveResults(content2, `spector-results.${ext}`);
53409
+ electron$5.saveResults(content2, `spector-results.${ext}`);
53410
53410
  },
53411
53411
  className: "px-2.5 py-0.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors text-[11px] whitespace-nowrap",
53412
53412
  children: "Export results"
@@ -53624,7 +53624,7 @@ function CollectionPanel() {
53624
53624
  ] })
53625
53625
  ] });
53626
53626
  }
53627
- const { electron: electron$3 } = window;
53627
+ const { electron: electron$4 } = window;
53628
53628
  function MockPanel() {
53629
53629
  const mocks = useStore((s) => s.mocks);
53630
53630
  const activeMockId = useStore((s) => s.activeMockId);
@@ -53636,12 +53636,12 @@ function MockPanel() {
53636
53636
  async function handleAddMock() {
53637
53637
  addMock();
53638
53638
  const ws2 = useStore.getState().workspace;
53639
- if (ws2) await electron$3.saveWorkspace(ws2);
53639
+ if (ws2) await electron$4.saveWorkspace(ws2);
53640
53640
  const state = useStore.getState();
53641
53641
  const newId = state.activeMockId;
53642
53642
  if (newId) {
53643
53643
  const entry = state.mocks[newId];
53644
- await electron$3.saveMock(entry.relPath, entry.data);
53644
+ await electron$4.saveMock(entry.relPath, entry.data);
53645
53645
  setActiveMock(newId);
53646
53646
  }
53647
53647
  }
@@ -53649,10 +53649,10 @@ function MockPanel() {
53649
53649
  e.stopPropagation();
53650
53650
  const entry = useStore.getState().mocks[mockId];
53651
53651
  if (!entry) return;
53652
- if (entry.running) await electron$3.mockStop(mockId);
53652
+ if (entry.running) await electron$4.mockStop(mockId);
53653
53653
  deleteMock(mockId);
53654
53654
  const ws2 = useStore.getState().workspace;
53655
- if (ws2) await electron$3.saveWorkspace(ws2);
53655
+ if (ws2) await electron$4.saveWorkspace(ws2);
53656
53656
  }
53657
53657
  async function toggleRunning(e, mockId) {
53658
53658
  e.stopPropagation();
@@ -53660,12 +53660,12 @@ function MockPanel() {
53660
53660
  if (!entry) return;
53661
53661
  try {
53662
53662
  if (entry.running) {
53663
- await electron$3.mockStop(mockId);
53663
+ await electron$4.mockStop(mockId);
53664
53664
  setRunning(mockId, false);
53665
53665
  } else {
53666
53666
  const latest2 = useStore.getState().mocks[mockId].data;
53667
- await electron$3.saveMock(entry.relPath, latest2);
53668
- await electron$3.mockStart(latest2);
53667
+ await electron$4.saveMock(entry.relPath, latest2);
53668
+ await electron$4.mockStart(latest2);
53669
53669
  setRunning(mockId, true);
53670
53670
  }
53671
53671
  } catch {
@@ -53741,7 +53741,7 @@ function MockPanel() {
53741
53741
  }) })
53742
53742
  ] });
53743
53743
  }
53744
- const { electron: electron$2 } = window;
53744
+ const { electron: electron$3 } = window;
53745
53745
  const METHODS_PLUS_ANY = ["ANY", "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
53746
53746
  function RouteRow({
53747
53747
  route,
@@ -54143,20 +54143,20 @@ function MockDetailPanel({ mockId }) {
54143
54143
  const routes = mock.routes ?? [];
54144
54144
  async function save(updated) {
54145
54145
  updateMock(mock.id, updated);
54146
- await electron$2.saveMock(entry.relPath, updated);
54147
- if (running) await electron$2.mockUpdateRoutes(mock.id, updated.routes ?? []);
54148
- if (workspace) await electron$2.saveWorkspace(workspace);
54146
+ await electron$3.saveMock(entry.relPath, updated);
54147
+ if (running) await electron$3.mockUpdateRoutes(mock.id, updated.routes ?? []);
54148
+ if (workspace) await electron$3.saveWorkspace(workspace);
54149
54149
  }
54150
54150
  async function toggleRunning() {
54151
54151
  setError(null);
54152
54152
  try {
54153
54153
  if (running) {
54154
- await electron$2.mockStop(mock.id);
54154
+ await electron$3.mockStop(mock.id);
54155
54155
  setRunning(mock.id, false);
54156
54156
  } else {
54157
54157
  const latest2 = useStore.getState().mocks[mock.id].data;
54158
- await electron$2.saveMock(entry.relPath, latest2);
54159
- await electron$2.mockStart(latest2);
54158
+ await electron$3.saveMock(entry.relPath, latest2);
54159
+ await electron$3.mockStart(latest2);
54160
54160
  setRunning(mock.id, true);
54161
54161
  }
54162
54162
  } catch (e) {
@@ -54269,7 +54269,7 @@ function MockDetailPanel({ mockId }) {
54269
54269
  "button",
54270
54270
  {
54271
54271
  onClick: () => {
54272
- if (running) electron$2.mockStop(mock.id);
54272
+ if (running) electron$3.mockStop(mock.id);
54273
54273
  deleteMock(mock.id);
54274
54274
  setActive2(null);
54275
54275
  },
@@ -54336,7 +54336,7 @@ function MockDetailPanel({ mockId }) {
54336
54336
  ] })
54337
54337
  ] });
54338
54338
  }
54339
- const { electron: electron$1 } = window;
54339
+ const { electron: electron$2 } = window;
54340
54340
  function ContractPanel() {
54341
54341
  const collections = useStore((s) => s.collections);
54342
54342
  const environments = useStore((s) => s.environments);
@@ -54367,7 +54367,7 @@ function ContractPanel() {
54367
54367
  setReport(null);
54368
54368
  try {
54369
54369
  const requests = mode === "provider" ? allRequests : contractRequests;
54370
- const result = await electron$1.runContracts({
54370
+ const result = await electron$2.runContracts({
54371
54371
  mode,
54372
54372
  requests,
54373
54373
  envVars,
@@ -54565,6 +54565,812 @@ function ContractResultsPanel() {
54565
54565
  })() })
54566
54566
  ] });
54567
54567
  }
54568
+ const { electron: electron$1 } = window;
54569
+ function useToast() {
54570
+ const [toast, setToast] = reactExports.useState(null);
54571
+ const timer = reactExports.useRef(null);
54572
+ function show(msg, ok) {
54573
+ if (timer.current) clearTimeout(timer.current);
54574
+ setToast({ msg, ok });
54575
+ timer.current = setTimeout(() => setToast(null), 3e3);
54576
+ }
54577
+ return { toast, show };
54578
+ }
54579
+ function Toast({ toast }) {
54580
+ if (!toast) return null;
54581
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mx-3 mb-2 px-2 py-1.5 rounded text-[11px] flex-shrink-0 ${toast.ok ? "bg-emerald-900/50 text-emerald-300 border border-emerald-800/50" : "bg-red-900/50 text-red-300 border border-red-800/50"}`, children: toast.msg });
54582
+ }
54583
+ function StatusBadge({ status }) {
54584
+ const map = {
54585
+ modified: { label: "M", color: "text-amber-400" },
54586
+ added: { label: "A", color: "text-emerald-400" },
54587
+ deleted: { label: "D", color: "text-red-400" },
54588
+ renamed: { label: "R", color: "text-blue-400" },
54589
+ untracked: { label: "?", color: "text-surface-500" }
54590
+ };
54591
+ const { label, color } = map[status] ?? { label: "?", color: "text-surface-500" };
54592
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono text-[11px] font-bold w-4 shrink-0 ${color}`, children: label });
54593
+ }
54594
+ function DiffViewer({ diff }) {
54595
+ if (!diff) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 p-3", children: "No changes." });
54596
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono overflow-auto p-3 leading-relaxed", children: diff.split("\n").map((line, i) => {
54597
+ const cls = line.startsWith("+") && !line.startsWith("+++") ? "text-emerald-400" : line.startsWith("-") && !line.startsWith("---") ? "text-red-400" : line.startsWith("@@") ? "text-blue-400" : "text-surface-400";
54598
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cls, children: line || " " }, i);
54599
+ }) });
54600
+ }
54601
+ function ChangesTab({ status, onRefresh }) {
54602
+ const [message, setMessage] = reactExports.useState("");
54603
+ const [diffFile, setDiffFile] = reactExports.useState(null);
54604
+ const [diff, setDiff] = reactExports.useState("");
54605
+ const [diffStaged, setDiffStaged] = reactExports.useState(false);
54606
+ const [error2, setError] = reactExports.useState(null);
54607
+ const [pushing, setPushing] = reactExports.useState(false);
54608
+ const [pulling, setPulling] = reactExports.useState(false);
54609
+ const { toast, show: showToast } = useToast();
54610
+ async function showDiff(file, staged) {
54611
+ setDiffFile(file.path);
54612
+ setDiffStaged(staged);
54613
+ try {
54614
+ const d = staged ? await electron$1.gitDiffStaged(file.path) : await electron$1.gitDiff(file.path);
54615
+ setDiff(d);
54616
+ } catch {
54617
+ setDiff("");
54618
+ }
54619
+ }
54620
+ async function stage(paths) {
54621
+ try {
54622
+ await electron$1.gitStage(paths);
54623
+ onRefresh();
54624
+ } catch (e) {
54625
+ setError(String(e));
54626
+ }
54627
+ }
54628
+ async function unstage(paths) {
54629
+ try {
54630
+ await electron$1.gitUnstage(paths);
54631
+ onRefresh();
54632
+ } catch (e) {
54633
+ setError(String(e));
54634
+ }
54635
+ }
54636
+ async function stageAll() {
54637
+ try {
54638
+ await electron$1.gitStageAll();
54639
+ onRefresh();
54640
+ } catch (e) {
54641
+ setError(String(e));
54642
+ }
54643
+ }
54644
+ async function commit() {
54645
+ if (!message.trim()) return;
54646
+ try {
54647
+ setError(null);
54648
+ await electron$1.gitCommit(message.trim());
54649
+ setMessage("");
54650
+ onRefresh();
54651
+ } catch (e) {
54652
+ setError(String(e));
54653
+ }
54654
+ }
54655
+ async function pull() {
54656
+ setPulling(true);
54657
+ try {
54658
+ await electron$1.gitPull();
54659
+ showToast("Pull successful", true);
54660
+ onRefresh();
54661
+ } catch (e) {
54662
+ showToast(String(e), false);
54663
+ } finally {
54664
+ setPulling(false);
54665
+ }
54666
+ }
54667
+ async function push2() {
54668
+ setPushing(true);
54669
+ try {
54670
+ await electron$1.gitPush(!status.remote);
54671
+ showToast("Push successful", true);
54672
+ onRefresh();
54673
+ } catch (e) {
54674
+ showToast(String(e), false);
54675
+ } finally {
54676
+ setPushing(false);
54677
+ }
54678
+ }
54679
+ const allUnstaged = [...status.unstaged, ...status.untracked];
54680
+ const hasStaged = status.staged.length > 0;
54681
+ const needsPush = status.ahead > 0;
54682
+ const needsPull = status.behind > 0;
54683
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0", children: [
54684
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 border-b border-surface-800 flex items-center gap-2 flex-shrink-0", children: [
54685
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] font-mono text-blue-300 truncate flex-1", children: [
54686
+ "⎇ ",
54687
+ status.branch || "no branch"
54688
+ ] }),
54689
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54690
+ "button",
54691
+ {
54692
+ onClick: pull,
54693
+ disabled: pulling,
54694
+ title: needsPull ? `Pull (${status.behind} behind)` : "Pull",
54695
+ className: `flex items-center gap-0.5 text-xs px-1.5 py-0.5 rounded transition-colors disabled:opacity-40 ${needsPull ? "text-amber-300 bg-amber-900/30 hover:bg-amber-900/50" : "text-surface-400 hover:text-white"}`,
54696
+ children: [
54697
+ "↓",
54698
+ needsPull ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px]", children: status.behind }) : null
54699
+ ]
54700
+ }
54701
+ ),
54702
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54703
+ "button",
54704
+ {
54705
+ onClick: push2,
54706
+ disabled: pushing,
54707
+ title: needsPush ? `Push (${status.ahead} unpushed)` : status.remote ? "Push" : "Push & set upstream",
54708
+ className: `flex items-center gap-0.5 text-xs px-1.5 py-0.5 rounded transition-colors disabled:opacity-40 ${needsPush ? "text-blue-300 bg-blue-900/30 hover:bg-blue-900/50" : "text-surface-400 hover:text-white"}`,
54709
+ children: [
54710
+ "↑",
54711
+ needsPush ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px]", children: status.ahead }) : null
54712
+ ]
54713
+ }
54714
+ )
54715
+ ] }),
54716
+ status.remote && (needsPush ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mx-3 mt-2 px-2 py-1.5 rounded text-[11px] bg-blue-900/20 text-blue-300 border border-blue-800/40 flex items-center justify-between flex-shrink-0", children: [
54717
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
54718
+ status.ahead,
54719
+ " commit",
54720
+ status.ahead !== 1 ? "s" : "",
54721
+ " to push"
54722
+ ] }),
54723
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54724
+ "button",
54725
+ {
54726
+ onClick: push2,
54727
+ disabled: pushing,
54728
+ className: "text-blue-400 hover:text-blue-200 font-medium disabled:opacity-40",
54729
+ children: "Push ↑"
54730
+ }
54731
+ )
54732
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-3 mt-2 px-2 py-1 rounded text-[11px] text-surface-600 border border-surface-800 flex-shrink-0", children: "✓ Nothing to push" })),
54733
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
54734
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto", children: [
54735
+ hasStaged && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
54736
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
54737
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: [
54738
+ "Staged (",
54739
+ status.staged.length,
54740
+ ")"
54741
+ ] }),
54742
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54743
+ "button",
54744
+ {
54745
+ onClick: () => unstage(status.staged.map((f) => f.path)),
54746
+ className: "text-[10px] text-surface-500 hover:text-white transition-colors",
54747
+ children: "Unstage all"
54748
+ }
54749
+ )
54750
+ ] }),
54751
+ status.staged.map((f) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54752
+ "div",
54753
+ {
54754
+ className: `flex items-center gap-2 px-3 py-1 hover:bg-surface-800/50 cursor-pointer group text-xs ${diffFile === f.path && diffStaged ? "bg-surface-800" : ""}`,
54755
+ onClick: () => showDiff(f, true),
54756
+ children: [
54757
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBadge, { status: f.status }),
54758
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", children: f.path }),
54759
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54760
+ "button",
54761
+ {
54762
+ onClick: (e) => {
54763
+ e.stopPropagation();
54764
+ unstage([f.path]);
54765
+ },
54766
+ className: "opacity-0 group-hover:opacity-100 text-surface-500 hover:text-amber-400 transition-all text-[10px]",
54767
+ title: "Unstage",
54768
+ children: "−"
54769
+ }
54770
+ )
54771
+ ]
54772
+ },
54773
+ f.path
54774
+ ))
54775
+ ] }),
54776
+ allUnstaged.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
54777
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
54778
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: [
54779
+ "Changes (",
54780
+ allUnstaged.length,
54781
+ ")"
54782
+ ] }),
54783
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54784
+ "button",
54785
+ {
54786
+ onClick: stageAll,
54787
+ className: "text-[10px] text-surface-500 hover:text-white transition-colors",
54788
+ children: "Stage all"
54789
+ }
54790
+ )
54791
+ ] }),
54792
+ allUnstaged.map((f) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54793
+ "div",
54794
+ {
54795
+ className: `flex items-center gap-2 px-3 py-1 hover:bg-surface-800/50 cursor-pointer group text-xs ${diffFile === f.path && !diffStaged ? "bg-surface-800" : ""}`,
54796
+ onClick: () => showDiff(f, false),
54797
+ children: [
54798
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusBadge, { status: f.status }),
54799
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", children: f.path }),
54800
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54801
+ "button",
54802
+ {
54803
+ onClick: (e) => {
54804
+ e.stopPropagation();
54805
+ stage([f.path]);
54806
+ },
54807
+ className: "opacity-0 group-hover:opacity-100 text-surface-500 hover:text-emerald-400 transition-all text-[10px]",
54808
+ title: "Stage",
54809
+ children: "+"
54810
+ }
54811
+ )
54812
+ ]
54813
+ },
54814
+ f.path
54815
+ ))
54816
+ ] }),
54817
+ status.staged.length === 0 && allUnstaged.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "Working tree clean." })
54818
+ ] }),
54819
+ diffFile && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-surface-800 flex flex-col max-h-48 min-h-0", children: [
54820
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1 flex items-center justify-between flex-shrink-0 border-b border-surface-800", children: [
54821
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-400 truncate", children: diffFile }),
54822
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setDiffFile(null), className: "text-surface-600 hover:text-white text-xs ml-2", children: "✕" })
54823
+ ] }),
54824
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-auto flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DiffViewer, { diff }) })
54825
+ ] }),
54826
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-surface-800 p-3 flex-shrink-0 flex flex-col gap-2", children: [
54827
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 truncate", title: error2, children: error2 }),
54828
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54829
+ "textarea",
54830
+ {
54831
+ value: message,
54832
+ onChange: (e) => setMessage(e.target.value),
54833
+ onKeyDown: (e) => {
54834
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) commit();
54835
+ },
54836
+ placeholder: "Commit message… (⌘↵ to commit)",
54837
+ rows: 2,
54838
+ className: "w-full bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs resize-none focus:outline-none focus:border-blue-500 placeholder-surface-600"
54839
+ }
54840
+ ),
54841
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
54842
+ "button",
54843
+ {
54844
+ onClick: commit,
54845
+ disabled: !message.trim() || !hasStaged,
54846
+ className: "w-full py-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 disabled:cursor-not-allowed rounded text-xs font-medium transition-colors",
54847
+ children: [
54848
+ "Commit",
54849
+ hasStaged ? ` (${status.staged.length})` : ""
54850
+ ]
54851
+ }
54852
+ )
54853
+ ] })
54854
+ ] });
54855
+ }
54856
+ function LogTab() {
54857
+ const [commits, setCommits] = reactExports.useState([]);
54858
+ const [loading, setLoading] = reactExports.useState(true);
54859
+ reactExports.useEffect(() => {
54860
+ electron$1.gitLog(100).then(setCommits).catch(() => setCommits([])).finally(() => setLoading(false));
54861
+ }, []);
54862
+ if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "Loading…" });
54863
+ if (commits.length === 0) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "No commits yet." });
54864
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: commits.map((c) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 border-b border-surface-800/50 hover:bg-surface-800/30", children: [
54865
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2", children: [
54866
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-600 shrink-0 mt-0.5", children: c.short }),
54867
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-surface-200 flex-1 line-clamp-2", children: c.message })
54868
+ ] }),
54869
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-0.5 pl-9 flex gap-2 text-[10px] text-surface-600", children: [
54870
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: c.author }),
54871
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: new Date(c.date).toLocaleDateString() })
54872
+ ] })
54873
+ ] }, c.hash)) });
54874
+ }
54875
+ function BranchesTab({ onRefresh }) {
54876
+ const [branches, setBranches] = reactExports.useState([]);
54877
+ const [remotes, setRemotes] = reactExports.useState([]);
54878
+ const [loading, setLoading] = reactExports.useState(true);
54879
+ const [newBranch, setNewBranch] = reactExports.useState("");
54880
+ const [creating, setCreating] = reactExports.useState(false);
54881
+ const [addingRemote, setAddingRemote] = reactExports.useState(false);
54882
+ const [editingRemote, setEditingRemote] = reactExports.useState(null);
54883
+ const [remoteName, setRemoteName] = reactExports.useState("origin");
54884
+ const [remoteUrl, setRemoteUrl] = reactExports.useState("");
54885
+ const [editUrl, setEditUrl] = reactExports.useState("");
54886
+ const [error2, setError] = reactExports.useState(null);
54887
+ const load = reactExports.useCallback(() => {
54888
+ Promise.all([electron$1.gitBranches(), electron$1.gitRemotes()]).then(([b, r]) => {
54889
+ setBranches(b);
54890
+ setRemotes(r);
54891
+ }).catch(() => {
54892
+ }).finally(() => setLoading(false));
54893
+ }, []);
54894
+ reactExports.useEffect(() => {
54895
+ load();
54896
+ }, [load]);
54897
+ async function checkout(name2, remote2) {
54898
+ try {
54899
+ setError(null);
54900
+ const localName = remote2 ? name2.replace(/^origin\//, "") : name2;
54901
+ await electron$1.gitCheckout(localName, false);
54902
+ load();
54903
+ onRefresh();
54904
+ } catch (e) {
54905
+ setError(String(e));
54906
+ }
54907
+ }
54908
+ async function createBranch() {
54909
+ if (!newBranch.trim()) return;
54910
+ try {
54911
+ setError(null);
54912
+ await electron$1.gitCheckout(newBranch.trim(), true);
54913
+ setNewBranch("");
54914
+ setCreating(false);
54915
+ load();
54916
+ onRefresh();
54917
+ } catch (e) {
54918
+ setError(String(e));
54919
+ }
54920
+ }
54921
+ async function addRemote() {
54922
+ if (!remoteName.trim() || !remoteUrl.trim()) return;
54923
+ try {
54924
+ setError(null);
54925
+ await electron$1.gitAddRemote(remoteName.trim(), remoteUrl.trim());
54926
+ setRemoteName("origin");
54927
+ setRemoteUrl("");
54928
+ setAddingRemote(false);
54929
+ load();
54930
+ } catch (e) {
54931
+ setError(String(e));
54932
+ }
54933
+ }
54934
+ function startEdit(r) {
54935
+ setEditingRemote(r.name);
54936
+ setEditUrl(r.url);
54937
+ }
54938
+ async function saveRemoteUrl(name2) {
54939
+ if (!editUrl.trim()) return;
54940
+ try {
54941
+ setError(null);
54942
+ await electron$1.gitSetRemoteUrl(name2, editUrl.trim());
54943
+ setEditingRemote(null);
54944
+ load();
54945
+ } catch (e) {
54946
+ setError(String(e));
54947
+ }
54948
+ }
54949
+ async function removeRemote(name2) {
54950
+ try {
54951
+ setError(null);
54952
+ await electron$1.gitRemoveRemote(name2);
54953
+ load();
54954
+ } catch (e) {
54955
+ setError(String(e));
54956
+ }
54957
+ }
54958
+ const local = branches.filter((b) => !b.remote);
54959
+ const remote = branches.filter((b) => b.remote);
54960
+ if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 px-3 py-4", children: "Loading…" });
54961
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-y-auto", children: [
54962
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-3 py-1 truncate", children: error2 }),
54963
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 border-b border-surface-800", children: creating ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5", children: [
54964
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
54965
+ "input",
54966
+ {
54967
+ autoFocus: true,
54968
+ value: newBranch,
54969
+ onChange: (e) => setNewBranch(e.target.value),
54970
+ onKeyDown: (e) => {
54971
+ if (e.key === "Enter") createBranch();
54972
+ if (e.key === "Escape") setCreating(false);
54973
+ },
54974
+ placeholder: "branch-name",
54975
+ className: "flex-1 bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500 placeholder-surface-600"
54976
+ }
54977
+ ),
54978
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: createBranch, className: "text-xs text-blue-400 hover:text-blue-300 px-1", children: "Create" }),
54979
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCreating(false), className: "text-xs text-surface-500 hover:text-white px-1", children: "✕" })
54980
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCreating(true), className: "text-[11px] text-blue-400 hover:text-blue-300 transition-colors", children: "+ New branch" }) }),
54981
+ local.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
54982
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Local" }),
54983
+ local.map((b) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54984
+ "button",
54985
+ {
54986
+ onClick: () => !b.current && checkout(b.name, false),
54987
+ className: `w-full flex items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors ${b.current ? "text-blue-300 cursor-default" : "text-surface-300 hover:bg-surface-800/50"}`,
54988
+ children: [
54989
+ b.current ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400 text-[10px]", children: "●" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3" }),
54990
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: b.name })
54991
+ ]
54992
+ },
54993
+ b.name
54994
+ ))
54995
+ ] }),
54996
+ remote.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
54997
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-1.5 text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Remote branches" }),
54998
+ remote.map((b) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
54999
+ "button",
55000
+ {
55001
+ onClick: () => checkout(b.name, true),
55002
+ className: "w-full flex items-center gap-2 px-3 py-1.5 text-left text-xs text-surface-400 hover:bg-surface-800/50 hover:text-surface-200 transition-colors",
55003
+ children: [
55004
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3" }),
55005
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: b.name })
55006
+ ]
55007
+ },
55008
+ b.name
55009
+ ))
55010
+ ] }),
55011
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "border-t border-surface-800 mt-1", children: [
55012
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 flex items-center justify-between", children: [
55013
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Remotes" }),
55014
+ !addingRemote && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setAddingRemote(true), className: "text-[10px] text-blue-400 hover:text-blue-300 transition-colors", children: "+ Add" })
55015
+ ] }),
55016
+ remotes.map((r) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-1 group", children: editingRemote === r.name ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
55017
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-400", children: r.name }),
55018
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5", children: [
55019
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55020
+ "input",
55021
+ {
55022
+ autoFocus: true,
55023
+ value: editUrl,
55024
+ onChange: (e) => setEditUrl(e.target.value),
55025
+ onKeyDown: (e) => {
55026
+ if (e.key === "Enter") saveRemoteUrl(r.name);
55027
+ if (e.key === "Escape") setEditingRemote(null);
55028
+ },
55029
+ className: "flex-1 bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500"
55030
+ }
55031
+ ),
55032
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => saveRemoteUrl(r.name), className: "text-xs text-blue-400 hover:text-blue-300 px-1", children: "Save" }),
55033
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setEditingRemote(null), className: "text-xs text-surface-500 hover:text-white px-1", children: "✕" })
55034
+ ] })
55035
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
55036
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-surface-300 shrink-0", children: r.name }),
55037
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-600 truncate flex-1", children: r.url }),
55038
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55039
+ "button",
55040
+ {
55041
+ onClick: () => startEdit(r),
55042
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-surface-500 hover:text-blue-400 transition-all shrink-0",
55043
+ title: "Edit URL",
55044
+ children: "✎"
55045
+ }
55046
+ ),
55047
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55048
+ "button",
55049
+ {
55050
+ onClick: () => removeRemote(r.name),
55051
+ className: "opacity-0 group-hover:opacity-100 text-[10px] text-surface-500 hover:text-red-400 transition-all shrink-0",
55052
+ title: "Remove remote",
55053
+ children: "✕"
55054
+ }
55055
+ )
55056
+ ] }) }, r.name)),
55057
+ remotes.length === 0 && !addingRemote && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 pb-2 text-[11px] text-surface-600", children: "No remotes configured." }),
55058
+ addingRemote && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 pb-3 flex flex-col gap-1.5", children: [
55059
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55060
+ "input",
55061
+ {
55062
+ value: remoteName,
55063
+ onChange: (e) => setRemoteName(e.target.value),
55064
+ placeholder: "name (e.g. origin)",
55065
+ className: "w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500 placeholder-surface-600"
55066
+ }
55067
+ ),
55068
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55069
+ "input",
55070
+ {
55071
+ autoFocus: true,
55072
+ value: remoteUrl,
55073
+ onChange: (e) => setRemoteUrl(e.target.value),
55074
+ onKeyDown: (e) => {
55075
+ if (e.key === "Enter") addRemote();
55076
+ if (e.key === "Escape") setAddingRemote(false);
55077
+ },
55078
+ placeholder: "git@github.com:user/repo.git",
55079
+ className: "w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500 placeholder-surface-600"
55080
+ }
55081
+ ),
55082
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
55083
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addRemote, className: "text-xs text-blue-400 hover:text-blue-300 transition-colors", children: "Add remote" }),
55084
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setAddingRemote(false), className: "text-xs text-surface-500 hover:text-white transition-colors", children: "Cancel" })
55085
+ ] })
55086
+ ] })
55087
+ ] })
55088
+ ] });
55089
+ }
55090
+ function detectPlatform(remotes) {
55091
+ const urls = remotes.map((r) => r.url.toLowerCase()).join(" ");
55092
+ if (urls.includes("github.com")) return "github";
55093
+ if (urls.includes("gitlab.com") || urls.includes("gitlab.")) return "gitlab";
55094
+ if (urls.includes("dev.azure.com") || urls.includes("visualstudio.com")) return "azure";
55095
+ return "unknown";
55096
+ }
55097
+ const NODE_LTS = "lts/*";
55098
+ function generateCiContent(platform, envName, tags2, secretVars) {
55099
+ const runCmd = [
55100
+ "api-spector run --workspace .",
55101
+ envName ? `--environment "${envName}"` : "",
55102
+ tags2 ? `--tags "${tags2}"` : "",
55103
+ "--output results.json"
55104
+ ].filter(Boolean).join(" ");
55105
+ if (platform === "github") {
55106
+ const secretHint = secretVars.length ? ` # ⚠ Add these secrets in: Settings → Secrets and variables → Actions
55107
+ ` + secretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
55108
+ const envBlock = secretVars.length ? "\n env:\n" + secretVars.map((v) => ` ${v}: \${{ secrets.${v} }}`).join("\n") : "";
55109
+ return `name: API Tests
55110
+
55111
+ on:
55112
+ push:
55113
+ branches: [main]
55114
+ pull_request:
55115
+
55116
+ jobs:
55117
+ api-tests:
55118
+ runs-on: ubuntu-latest
55119
+ steps:
55120
+ - uses: actions/checkout@v4
55121
+ - uses: actions/setup-node@v4
55122
+ with:
55123
+ node-version: '${NODE_LTS}'
55124
+ - run: npm install -g @testsmith/api-spector
55125
+ ${secretHint} - name: Run API tests
55126
+ run: ${runCmd}${envBlock}
55127
+ - name: Upload test results
55128
+ if: always()
55129
+ uses: actions/upload-artifact@v4
55130
+ with:
55131
+ name: api-test-results
55132
+ path: results.json
55133
+ `;
55134
+ }
55135
+ if (platform === "gitlab") {
55136
+ const secretHint = secretVars.length ? ` # ⚠ Add these in: Settings → CI/CD → Variables
55137
+ ` + secretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
55138
+ const envBlock = secretVars.length ? "\n variables:\n" + secretVars.map((v) => ` ${v}: $${v}`).join("\n") : "";
55139
+ return `api-tests:
55140
+ image: node:${NODE_LTS}
55141
+ stage: test
55142
+ before_script:
55143
+ - npm install -g @testsmith/api-spector
55144
+ ${secretHint} script:
55145
+ - ${runCmd}${envBlock}
55146
+ artifacts:
55147
+ when: always
55148
+ paths:
55149
+ - results.json
55150
+ expire_in: 30 days
55151
+ `;
55152
+ }
55153
+ if (platform === "azure") {
55154
+ const secretHint = secretVars.length ? ` # ⚠ Add these in: Pipelines → Library → Variable groups (mark as secret)
55155
+ ` + secretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
55156
+ const envBlock = secretVars.length ? "\n env:\n" + secretVars.map((v) => ` ${v}: $(${v})`).join("\n") : "";
55157
+ return `trigger:
55158
+ - main
55159
+
55160
+ pool:
55161
+ vmImage: ubuntu-latest
55162
+
55163
+ steps:
55164
+ - task: NodeTool@0
55165
+ inputs:
55166
+ versionSpec: '${NODE_LTS}.x'
55167
+ displayName: 'Use Node.js ${NODE_LTS}'
55168
+ - script: npm install -g @testsmith/api-spector
55169
+ displayName: 'Install api Spector'
55170
+ ${secretHint} - script: ${runCmd}
55171
+ displayName: 'Run API tests'${envBlock}
55172
+ - publish: results.json
55173
+ artifact: api-test-results
55174
+ displayName: 'Upload test results'
55175
+ condition: always()
55176
+ `;
55177
+ }
55178
+ return `# Unsupported platform — adapt as needed
55179
+ # ${runCmd}
55180
+ `;
55181
+ }
55182
+ function ciFilePath(platform) {
55183
+ if (platform === "github") return ".github/workflows/api-tests.yml";
55184
+ if (platform === "gitlab") return ".gitlab-ci.yml";
55185
+ if (platform === "azure") return "azure-pipelines.yml";
55186
+ return "ci.yml";
55187
+ }
55188
+ function CiTab() {
55189
+ const environments = useStore((s) => s.environments);
55190
+ const envList = Object.values(environments);
55191
+ const [remotes, setRemotes] = reactExports.useState([]);
55192
+ const [platform, setPlatform] = reactExports.useState("unknown");
55193
+ const [envId, setEnvId] = reactExports.useState("");
55194
+ const [tags2, setTags] = reactExports.useState("");
55195
+ const [preview, setPreview] = reactExports.useState("");
55196
+ const [written, setWritten] = reactExports.useState(false);
55197
+ const [error2, setError] = reactExports.useState(null);
55198
+ reactExports.useEffect(() => {
55199
+ electron$1.gitRemotes().then((r) => {
55200
+ setRemotes(r);
55201
+ const detected = detectPlatform(r);
55202
+ setPlatform(detected);
55203
+ }).catch(() => {
55204
+ });
55205
+ }, []);
55206
+ reactExports.useEffect(() => {
55207
+ const env = envList.find((e) => e.data.id === envId);
55208
+ const secretVars = env ? env.data.variables.filter((v) => v.secret && v.enabled).map((v) => v.key) : [];
55209
+ const envName = env?.data.name ?? "";
55210
+ setPreview(generateCiContent(platform, envName, tags2, secretVars));
55211
+ setWritten(false);
55212
+ }, [platform, envId, tags2, environments]);
55213
+ async function write() {
55214
+ try {
55215
+ setError(null);
55216
+ await electron$1.gitWriteCiFile(ciFilePath(platform), preview);
55217
+ setWritten(true);
55218
+ } catch (e) {
55219
+ setError(String(e));
55220
+ }
55221
+ }
55222
+ const platformLabels = {
55223
+ github: "GitHub Actions",
55224
+ gitlab: "GitLab CI",
55225
+ azure: "Azure Pipelines",
55226
+ unknown: "Unknown"
55227
+ };
55228
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-y-auto", children: [
55229
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-3 flex flex-col gap-3 border-b border-surface-800", children: [
55230
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
55231
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Platform" }),
55232
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55233
+ "select",
55234
+ {
55235
+ value: platform,
55236
+ onChange: (e) => setPlatform(e.target.value),
55237
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500",
55238
+ children: ["github", "gitlab", "azure"].map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p, children: platformLabels[p] }, p))
55239
+ }
55240
+ ),
55241
+ remotes.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-surface-600", children: [
55242
+ "Detected from remote: ",
55243
+ platformLabels[detectPlatform(remotes)]
55244
+ ] })
55245
+ ] }),
55246
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
55247
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Environment" }),
55248
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
55249
+ "select",
55250
+ {
55251
+ value: envId,
55252
+ onChange: (e) => setEnvId(e.target.value),
55253
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500",
55254
+ children: [
55255
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "— none —" }),
55256
+ envList.map((e) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: e.data.id, children: e.data.name }, e.data.id))
55257
+ ]
55258
+ }
55259
+ ),
55260
+ envId && (() => {
55261
+ const env = envList.find((e) => e.data.id === envId);
55262
+ const secrets = env?.data.variables.filter((v) => v.secret && v.enabled) ?? [];
55263
+ return secrets.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-amber-400/80", children: [
55264
+ secrets.length,
55265
+ " secret variable",
55266
+ secrets.length !== 1 ? "s" : "",
55267
+ " will be mapped to CI secrets"
55268
+ ] }) : null;
55269
+ })()
55270
+ ] }),
55271
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
55272
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-widest text-surface-500 font-semibold", children: "Tags (optional)" }),
55273
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55274
+ "input",
55275
+ {
55276
+ value: tags2,
55277
+ onChange: (e) => setTags(e.target.value),
55278
+ placeholder: "e.g. smoke, regression",
55279
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500 placeholder-surface-600"
55280
+ }
55281
+ )
55282
+ ] }),
55283
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400", children: error2 }),
55284
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
55285
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
55286
+ "button",
55287
+ {
55288
+ onClick: write,
55289
+ className: "flex-1 py-1.5 bg-blue-600 hover:bg-blue-500 rounded text-xs font-medium transition-colors",
55290
+ children: [
55291
+ "Write ",
55292
+ ciFilePath(platform)
55293
+ ]
55294
+ }
55295
+ ),
55296
+ written && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-emerald-400", children: "✓ Written" })
55297
+ ] })
55298
+ ] }),
55299
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-auto", children: [
55300
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 pt-2 pb-1 text-[10px] uppercase tracking-widest text-surface-600 font-semibold", children: "Preview" }),
55301
+ /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "px-3 pb-3 text-[10px] font-mono text-surface-400 leading-relaxed whitespace-pre-wrap", children: preview })
55302
+ ] })
55303
+ ] });
55304
+ }
55305
+ function GitPanel() {
55306
+ const [isRepo, setIsRepo] = reactExports.useState(null);
55307
+ const [status, setStatus] = reactExports.useState(null);
55308
+ const [tab, setTab] = reactExports.useState("changes");
55309
+ const [loading, setLoading] = reactExports.useState(true);
55310
+ const refresh = reactExports.useCallback(async () => {
55311
+ try {
55312
+ const repo = await electron$1.gitIsRepo();
55313
+ setIsRepo(repo);
55314
+ if (repo) setStatus(await electron$1.gitStatus());
55315
+ } catch {
55316
+ setIsRepo(false);
55317
+ } finally {
55318
+ setLoading(false);
55319
+ }
55320
+ }, []);
55321
+ reactExports.useEffect(() => {
55322
+ refresh();
55323
+ }, [refresh]);
55324
+ if (loading) {
55325
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-xs text-surface-500", children: "Loading…" });
55326
+ }
55327
+ if (!isRepo) {
55328
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 px-4 text-center", children: [
55329
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: "Not a git repository." }),
55330
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55331
+ "button",
55332
+ {
55333
+ onClick: async () => {
55334
+ await electron$1.gitInit();
55335
+ refresh();
55336
+ },
55337
+ className: "px-3 py-1.5 bg-surface-800 hover:bg-surface-700 rounded text-xs transition-colors",
55338
+ children: "git init"
55339
+ }
55340
+ )
55341
+ ] });
55342
+ }
55343
+ const totalChanges = status ? status.staged.length + status.unstaged.length + status.untracked.length : 0;
55344
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col flex-1 min-h-0", children: [
55345
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-surface-800 flex-shrink-0", children: [
55346
+ ["changes", "log", "branches", "ci"].map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
55347
+ "button",
55348
+ {
55349
+ onClick: () => setTab(t2),
55350
+ className: `flex-1 py-1.5 text-[11px] capitalize transition-colors border-b-2 -mb-px ${tab === t2 ? "border-blue-500 text-white" : "border-transparent text-surface-400 hover:text-white"}`,
55351
+ children: [
55352
+ t2,
55353
+ t2 === "changes" && totalChanges > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1 bg-surface-700 text-surface-300 rounded px-1 text-[9px]", children: totalChanges })
55354
+ ]
55355
+ },
55356
+ t2
55357
+ )),
55358
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55359
+ "button",
55360
+ {
55361
+ onClick: refresh,
55362
+ title: "Refresh",
55363
+ className: "px-2 text-surface-600 hover:text-surface-300 transition-colors border-b-2 border-transparent -mb-px",
55364
+ children: "↺"
55365
+ }
55366
+ )
55367
+ ] }),
55368
+ tab === "changes" && status && /* @__PURE__ */ jsxRuntimeExports.jsx(ChangesTab, { status, onRefresh: refresh }),
55369
+ tab === "log" && /* @__PURE__ */ jsxRuntimeExports.jsx(LogTab, {}),
55370
+ tab === "branches" && /* @__PURE__ */ jsxRuntimeExports.jsx(BranchesTab, { onRefresh: refresh }),
55371
+ tab === "ci" && /* @__PURE__ */ jsxRuntimeExports.jsx(CiTab, {})
55372
+ ] });
55373
+ }
54568
55374
  function CommandPalette() {
54569
55375
  const open = useStore((s) => s.commandPaletteOpen);
54570
55376
  const setOpen = useStore((s) => s.setCommandPaletteOpen);
@@ -54715,6 +55521,15 @@ function IconMock() {
54715
55521
  function IconContract() {
54716
55522
  return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 20 20", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { fillRule: "evenodd", d: "M10 2a1 1 0 011 1v1.323l3.954 1.582 1.599-.8a1 1 0 01.894 1.79l-1.233.616 1.738 5.42a1 1 0 01-.285 1.05A3.989 3.989 0 0115 14a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.715-5.349L11 5.477V17H13a1 1 0 110 2H7a1 1 0 110-2h2V5.477L6.237 6.582l1.715 5.349a1 1 0 01-.285 1.05A3.989 3.989 0 015 14a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.738-5.42-1.233-.617a1 1 0 01.894-1.788l1.599.799L9 3.323V3a1 1 0 011-1z", clipRule: "evenodd" }) });
54717
55523
  }
55524
+ function IconGit() {
55525
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", width: "18", height: "18", children: [
55526
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "6", cy: "6", r: "2.5" }),
55527
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "6", cy: "18", r: "2.5" }),
55528
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "18", cy: "6", r: "2.5" }),
55529
+ /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "6", y1: "8.5", x2: "6", y2: "15.5" }),
55530
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 8.5 C6 13 18 10 18 8.5" })
55531
+ ] });
55532
+ }
54718
55533
  function ActivityBarBtn({
54719
55534
  active,
54720
55535
  onClick,
@@ -54722,18 +55537,20 @@ function ActivityBarBtn({
54722
55537
  badge,
54723
55538
  children
54724
55539
  }) {
54725
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
54726
- "button",
54727
- {
54728
- onClick,
54729
- title: title2,
54730
- className: `relative w-10 h-10 flex items-center justify-center rounded transition-colors ${active ? "text-white bg-surface-800" : "text-surface-600 hover:text-surface-300 hover:bg-surface-800/50"}`,
54731
- children: [
54732
- children,
54733
- badge !== void 0 && badge > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute top-1 right-1 text-[9px] bg-surface-600 text-white rounded-full w-3.5 h-3.5 flex items-center justify-center font-medium leading-none", children: badge > 9 ? "9+" : badge })
54734
- ]
54735
- }
54736
- );
55540
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative group/ab", children: [
55541
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
55542
+ "button",
55543
+ {
55544
+ onClick,
55545
+ className: `relative w-10 h-10 flex items-center justify-center rounded transition-colors ${active ? "text-white bg-surface-800" : "text-surface-600 hover:text-surface-300 hover:bg-surface-800/50"}`,
55546
+ children: [
55547
+ children,
55548
+ badge !== void 0 && badge > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute top-1 right-1 text-[9px] bg-surface-600 text-white rounded-full w-3.5 h-3.5 flex items-center justify-center font-medium leading-none", children: badge > 9 ? "9+" : badge })
55549
+ ]
55550
+ }
55551
+ ),
55552
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pointer-events-none absolute left-full top-1/2 -translate-y-1/2 ml-2 z-50 hidden group-hover/ab:block", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap rounded px-2 py-1 text-[11px] bg-[#1e1b2e] text-gray-200 border border-white/10 shadow-lg", children: title2 }) })
55553
+ ] });
54737
55554
  }
54738
55555
  const TAB_METHOD_COLORS = {
54739
55556
  GET: "text-emerald-400",
@@ -54821,7 +55638,7 @@ function App() {
54821
55638
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
54822
55639
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
54823
55640
  "v",
54824
- "0.1.0"
55641
+ "0.1.1"
54825
55642
  ] })
54826
55643
  ] }) }),
54827
55644
  /* @__PURE__ */ jsxRuntimeExports.jsx(Toolbar, { onOpenDocs: () => setDocsModalOpen(true) }),
@@ -54863,11 +55680,20 @@ function App() {
54863
55680
  title: "Contract testing",
54864
55681
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconContract, {})
54865
55682
  }
55683
+ ),
55684
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
55685
+ ActivityBarBtn,
55686
+ {
55687
+ active: sidebarOpen && sidebarTab === "git",
55688
+ onClick: () => selectPanel("git"),
55689
+ title: "Git",
55690
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconGit, {})
55691
+ }
54866
55692
  )
54867
55693
  ] }),
54868
55694
  sidebarOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("aside", { className: "w-64 flex-shrink-0 border-r border-surface-800 flex flex-col overflow-hidden", children: [
54869
55695
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 flex items-center justify-between border-b border-surface-800 flex-shrink-0", children: [
54870
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-surface-600", children: sidebarTab === "collections" ? "Collections" : sidebarTab === "history" ? "History" : sidebarTab === "mocks" ? "Mocks" : "Contracts" }),
55696
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-surface-600", children: sidebarTab === "collections" ? "Collections" : sidebarTab === "history" ? "History" : sidebarTab === "mocks" ? "Mocks" : sidebarTab === "git" ? "Git" : "Contracts" }),
54871
55697
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
54872
55698
  sidebarTab === "history" && historyCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] bg-surface-700 text-surface-400 rounded px-1.5 py-0.5", children: historyCount }),
54873
55699
  sidebarTab === "collections" && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -54890,7 +55716,7 @@ function App() {
54890
55716
  )
54891
55717
  ] })
54892
55718
  ] }),
54893
- sidebarTab === "collections" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionTree, {}) : sidebarTab === "history" ? /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryPanel, {}) : sidebarTab === "mocks" ? /* @__PURE__ */ jsxRuntimeExports.jsx(MockPanel, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractPanel, {})
55719
+ sidebarTab === "collections" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionTree, {}) : sidebarTab === "history" ? /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryPanel, {}) : sidebarTab === "mocks" ? /* @__PURE__ */ jsxRuntimeExports.jsx(MockPanel, {}) : sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx(GitPanel, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractPanel, {})
54894
55720
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
54895
55721
  "button",
54896
55722
  {