@nextsparkjs/core 0.1.0-beta.181 → 0.1.0-beta.182

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.
Files changed (28) hide show
  1. package/dist/components/devtools/api-explorer/ApiEndpointsSidebar.d.ts.map +1 -1
  2. package/dist/components/devtools/api-explorer/ApiEndpointsSidebar.js +4 -1
  3. package/dist/components/devtools/api-explorer/ApiExplorer.d.ts.map +1 -1
  4. package/dist/components/devtools/api-explorer/ApiExplorer.js +92 -48
  5. package/dist/components/devtools/api-explorer/ApiRequestPanel.d.ts.map +1 -1
  6. package/dist/components/devtools/api-explorer/ApiRequestPanel.js +36 -6
  7. package/dist/components/devtools/api-explorer/JsonViewer.d.ts.map +1 -1
  8. package/dist/components/devtools/api-explorer/JsonViewer.js +13 -8
  9. package/dist/components/devtools/api-explorer/PanelDivider.d.ts +26 -0
  10. package/dist/components/devtools/api-explorer/PanelDivider.d.ts.map +1 -0
  11. package/dist/components/devtools/api-explorer/PanelDivider.js +102 -0
  12. package/dist/components/devtools/api-explorer/PresetsTab.d.ts.map +1 -1
  13. package/dist/components/devtools/api-explorer/PresetsTab.js +35 -2
  14. package/dist/components/devtools/api-explorer/preset-placeholders.d.ts +52 -0
  15. package/dist/components/devtools/api-explorer/preset-placeholders.d.ts.map +1 -0
  16. package/dist/components/devtools/api-explorer/preset-placeholders.js +62 -0
  17. package/dist/lib/selectors/core-selectors.d.ts +3 -0
  18. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  19. package/dist/lib/selectors/domains/devtools.selectors.d.ts +3 -0
  20. package/dist/lib/selectors/domains/devtools.selectors.d.ts.map +1 -1
  21. package/dist/lib/selectors/domains/devtools.selectors.js +5 -2
  22. package/dist/lib/selectors/selectors.d.ts +6 -0
  23. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  24. package/dist/styles/classes.json +10 -5
  25. package/dist/styles/ui.css +1 -1
  26. package/dist/types/api-presets.d.ts +19 -1
  27. package/dist/types/api-presets.d.ts.map +1 -1
  28. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ApiEndpointsSidebar.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiEndpointsSidebar.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAC5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AA4CrD,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAA;CACnB;AAED,UAAU,wBAAwB;IAChC,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC,CAAA;IAC9C,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACzC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAClF,WAAW,EAAE,OAAO,CAAA;IACpB,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,GACjB,EAAE,wBAAwB,2CAkQ1B"}
1
+ {"version":3,"file":"ApiEndpointsSidebar.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiEndpointsSidebar.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAC5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AA4CrD,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAA;CACnB;AAED,UAAU,wBAAwB;IAChC,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC,CAAA;IAC9C,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACzC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAClF,WAAW,EAAE,OAAO,CAAA;IACpB,gBAAgB,EAAE,MAAM,IAAI,CAAA;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,GACjB,EAAE,wBAAwB,2CAyQ1B"}
@@ -50,7 +50,10 @@ function ApiEndpointsSidebar({
50
50
  });
51
51
  const [expandedPrefixes, setExpandedPrefixes] = useState(() => {
52
52
  const saved = loadState("sidebar:expandedPrefixes");
53
- return new Set(saved ?? []);
53
+ if (saved) return new Set(saved);
54
+ return new Set(
55
+ groupByPathPrefix(routes.theme ?? []).map((group) => `theme:${group.prefix}`)
56
+ );
54
57
  });
55
58
  useEffect(() => {
56
59
  saveState("sidebar:expandedCategories", Array.from(expandedCategories));
@@ -1 +1 @@
1
- {"version":3,"file":"ApiExplorer.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiExplorer.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAqB5F,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC,CAAA;IAC9C,eAAe,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC1D;AAwBD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,gBAAgB,2CAibxE"}
1
+ {"version":3,"file":"ApiExplorer.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiExplorer.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AA2B5F,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC,CAAA;IAC9C,eAAe,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC1D;AAwBD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,gBAAgB,2CA4dxE"}
@@ -8,12 +8,17 @@ import { sel } from "../../../lib/test/index.js";
8
8
  import { ApiEndpointsSidebar } from "./ApiEndpointsSidebar.js";
9
9
  import { ApiRequestPanel } from "./ApiRequestPanel.js";
10
10
  import { ApiResponsePanel } from "./ApiResponsePanel.js";
11
+ import { PanelDivider } from "./PanelDivider.js";
11
12
  import { useApiRequest } from "../api-tester/hooks/useApiRequest.js";
12
13
  import { useApiExplorerNavigation } from "../../../hooks/useApiExplorerNavigation.js";
13
14
  import { extractPathParams, buildUrl } from "../api-tester/utils/url-builder.js";
14
15
  import { ApiPresetsService } from "../../../lib/services/api-presets.service.js";
15
16
  import { ApiDocsService } from "../../../lib/services/api-docs.service.js";
16
17
  import { loadState, saveState, draftKey } from "./explorer-storage.js";
18
+ import {
19
+ createPresetPlaceholderValues,
20
+ resolvePresetPlaceholders
21
+ } from "./preset-placeholders.js";
17
22
  function findRouteByPath(routes, path) {
18
23
  for (const category of Object.keys(routes)) {
19
24
  const found = routes[category].find((r) => r.path === path);
@@ -50,6 +55,24 @@ function ApiExplorer({ routes, initialEndpoint }) {
50
55
  });
51
56
  const [body, setBody] = useState("");
52
57
  const [actAsUserId, setActAsUserId] = useState(() => loadState("actAsUserId") ?? "");
58
+ const [splitPercent, setSplitPercent] = useState(55);
59
+ const splitRowRef = useRef(null);
60
+ useEffect(() => {
61
+ const saved = loadState("splitPercent");
62
+ if (typeof saved === "number") setSplitPercent(saved);
63
+ }, []);
64
+ const handleSplitChange = useCallback((percent) => {
65
+ setSplitPercent(percent);
66
+ saveState("splitPercent", percent);
67
+ }, []);
68
+ const [isSideBySide, setIsSideBySide] = useState(false);
69
+ useEffect(() => {
70
+ const query = window.matchMedia("(min-width: 1024px)");
71
+ const sync = () => setIsSideBySide(query.matches);
72
+ sync();
73
+ query.addEventListener("change", sync);
74
+ return () => query.removeEventListener("change", sync);
75
+ }, []);
53
76
  const [tabsModifiedByPreset, setTabsModifiedByPreset] = useState(
54
77
  () => /* @__PURE__ */ new Set()
55
78
  );
@@ -126,7 +149,16 @@ function ApiExplorer({ routes, initialEndpoint }) {
126
149
  apiKey: authType === "apiKey" ? apiKey : void 0
127
150
  });
128
151
  }, [selectedEndpoint, pathParams, queryParams, headers, method, body, authType, apiKey, bypassMode, selectedTeamId, actAsUserId, execute]);
129
- const handleApplyPreset = useCallback((preset) => {
152
+ const handleApplyPreset = useCallback((rawPreset) => {
153
+ const values = createPresetPlaceholderValues();
154
+ const preset = {
155
+ ...rawPreset,
156
+ pathParams: resolvePresetPlaceholders(rawPreset.pathParams, values),
157
+ params: resolvePresetPlaceholders(rawPreset.params, values),
158
+ headers: resolvePresetPlaceholders(rawPreset.headers, values),
159
+ payload: resolvePresetPlaceholders(rawPreset.payload, values),
160
+ sessionConfig: resolvePresetPlaceholders(rawPreset.sessionConfig, values)
161
+ };
130
162
  const modifiedTabs = /* @__PURE__ */ new Set();
131
163
  if (preset.method) {
132
164
  setMethod(preset.method);
@@ -172,11 +204,7 @@ function ApiExplorer({ routes, initialEndpoint }) {
172
204
  setBypassMode(preset.sessionConfig.crossTeam);
173
205
  }
174
206
  if (preset.sessionConfig.teamId) {
175
- let teamId = preset.sessionConfig.teamId;
176
- if (teamId === "{{FIRST_TEAM_ID}}" && typeof window !== "undefined") {
177
- teamId = localStorage.getItem("firstTeamId") || localStorage.getItem("activeTeamId") || "";
178
- }
179
- setSelectedTeamId(teamId);
207
+ setSelectedTeamId(preset.sessionConfig.teamId);
180
208
  }
181
209
  if (preset.sessionConfig.authType) {
182
210
  setAuthType(preset.sessionConfig.authType);
@@ -258,7 +286,7 @@ function ApiExplorer({ routes, initialEndpoint }) {
258
286
  }
259
287
  }, [routes, initialEndpoint, urlPath, urlMethod, navigateToEndpoint, applyEndpointState]);
260
288
  const totalRoutes = Object.values(routes).reduce((sum, arr) => sum + arr.length, 0);
261
- return /* @__PURE__ */ jsxs("div", { className: "flex h-[calc(100vh-4rem)] w-full overflow-hidden", "data-cy": sel("devtools.apiExplorer.container"), children: [
289
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full overflow-hidden", "data-cy": sel("devtools.apiExplorer.container"), children: [
262
290
  /* @__PURE__ */ jsxs("div", { className: "lg:hidden fixed top-0 left-0 right-0 z-50 h-14 bg-background border-b flex items-center px-4 gap-3", children: [
263
291
  /* @__PURE__ */ jsx(
264
292
  Button,
@@ -307,49 +335,65 @@ function ApiExplorer({ routes, initialEndpoint }) {
307
335
  )
308
336
  }
309
337
  ),
310
- /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col lg:flex-row min-w-0 pt-14 lg:pt-0", children: selectedEndpoint ? /* @__PURE__ */ jsxs(Fragment, { children: [
311
- /* @__PURE__ */ jsx("div", { className: "flex-1 lg:flex-[1.2] min-h-0 border-b lg:border-b-0 lg:border-r overflow-hidden", children: /* @__PURE__ */ jsx(
312
- ApiRequestPanel,
338
+ /* @__PURE__ */ jsx("div", { ref: splitRowRef, className: "flex-1 flex flex-col lg:flex-row min-w-0 pt-14 lg:pt-0", children: selectedEndpoint ? /* @__PURE__ */ jsxs(Fragment, { children: [
339
+ /* @__PURE__ */ jsx(
340
+ "div",
313
341
  {
314
- route: selectedEndpoint.route,
315
- basePath: selectedEndpoint.path,
316
- method,
317
- pathParams,
318
- queryParams,
319
- headers,
320
- authType,
321
- apiKey,
322
- bypassMode,
323
- selectedTeamId,
324
- actAsUserId,
325
- body,
326
- status,
327
- onMethodChange: setMethod,
328
- onPathParamsChange: setPathParams,
329
- onQueryParamsChange: setQueryParams,
330
- onHeadersChange: setHeaders,
331
- onAuthTypeChange: setAuthType,
332
- onApiKeyChange: setApiKey,
333
- onBypassModeChange: setBypassMode,
334
- onTeamChange: setSelectedTeamId,
335
- onActAsUserChange: setActAsUserId,
336
- onBodyChange: setBody,
337
- onSend: handleSend,
338
- onCancel: cancel,
339
- endpointPresets,
340
- endpointDoc,
341
- onApplyPreset: handleApplyPreset,
342
- tabsModifiedByPreset,
343
- onClearPresetIndicator: (tab) => {
344
- setTabsModifiedByPreset((prev) => {
345
- const next = new Set(prev);
346
- next.delete(tab);
347
- return next;
348
- });
349
- }
342
+ className: "flex-1 min-h-0 min-w-0 border-b lg:border-b-0 overflow-hidden",
343
+ style: isSideBySide ? { flex: `0 0 ${splitPercent}%` } : void 0,
344
+ "data-split-panel": "request",
345
+ children: /* @__PURE__ */ jsx(
346
+ ApiRequestPanel,
347
+ {
348
+ route: selectedEndpoint.route,
349
+ basePath: selectedEndpoint.path,
350
+ method,
351
+ pathParams,
352
+ queryParams,
353
+ headers,
354
+ authType,
355
+ apiKey,
356
+ bypassMode,
357
+ selectedTeamId,
358
+ actAsUserId,
359
+ body,
360
+ status,
361
+ onMethodChange: setMethod,
362
+ onPathParamsChange: setPathParams,
363
+ onQueryParamsChange: setQueryParams,
364
+ onHeadersChange: setHeaders,
365
+ onAuthTypeChange: setAuthType,
366
+ onApiKeyChange: setApiKey,
367
+ onBypassModeChange: setBypassMode,
368
+ onTeamChange: setSelectedTeamId,
369
+ onActAsUserChange: setActAsUserId,
370
+ onBodyChange: setBody,
371
+ onSend: handleSend,
372
+ onCancel: cancel,
373
+ endpointPresets,
374
+ endpointDoc,
375
+ onApplyPreset: handleApplyPreset,
376
+ tabsModifiedByPreset,
377
+ onClearPresetIndicator: (tab) => {
378
+ setTabsModifiedByPreset((prev) => {
379
+ const next = new Set(prev);
380
+ next.delete(tab);
381
+ return next;
382
+ });
383
+ }
384
+ }
385
+ )
350
386
  }
351
- ) }),
352
- /* @__PURE__ */ jsx("div", { className: "h-[40vh] lg:h-auto lg:flex-1 min-h-[200px] overflow-hidden", children: /* @__PURE__ */ jsx(
387
+ ),
388
+ /* @__PURE__ */ jsx(
389
+ PanelDivider,
390
+ {
391
+ value: splitPercent,
392
+ onChange: handleSplitChange,
393
+ containerRef: splitRowRef
394
+ }
395
+ ),
396
+ /* @__PURE__ */ jsx("div", { className: "h-[40vh] lg:h-auto lg:flex-1 min-h-[200px] min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx(
353
397
  ApiResponsePanel,
354
398
  {
355
399
  status,
@@ -1 +1 @@
1
- {"version":3,"file":"ApiRequestPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiRequestPanel.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACvG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAa/E,UAAU,oBAAoB;IAC5B,KAAK,EAAE,aAAa,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,UAAU,CAAA;IAClB,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,WAAW,EAAE,YAAY,EAAE,CAAA;IAC3B,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,QAAQ,EAAE,QAAQ,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,OAAO,CAAA;IACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,aAAa,CAAA;IACrB,cAAc,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAA;IAC5C,kBAAkB,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IACjD,mBAAmB,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,IAAI,CAAA;IACrD,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAA;IAClD,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAA;IAC1C,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7C,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,QAAQ,EAAE,MAAM,IAAI,CAAA;IAEpB,eAAe,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC3C,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACpD,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;IAE3C,oBAAoB,CAAC,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC,CAAA;IACzD,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAA;CACtE;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,WAAW,EACX,OAAO,EACP,QAAQ,EACR,MAAM,EACN,UAAU,EACV,cAAc,EACd,WAAW,EACX,IAAI,EACJ,MAAM,EACN,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,eAAe,EACf,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,sBAAsB,GACvB,EAAE,oBAAoB,2CAgOtB"}
1
+ {"version":3,"file":"ApiRequestPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/ApiRequestPanel.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACvG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAc/E,UAAU,oBAAoB;IAC5B,KAAK,EAAE,aAAa,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,UAAU,CAAA;IAClB,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,WAAW,EAAE,YAAY,EAAE,CAAA;IAC3B,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,QAAQ,EAAE,QAAQ,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,OAAO,CAAA;IACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,aAAa,CAAA;IACrB,cAAc,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAA;IAC5C,kBAAkB,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IACjD,mBAAmB,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,IAAI,CAAA;IACrD,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAA;IAClD,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAA;IAC1C,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7C,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,QAAQ,EAAE,MAAM,IAAI,CAAA;IAEpB,eAAe,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC3C,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACpD,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;IAE3C,oBAAoB,CAAC,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC,CAAA;IACzD,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAA;CACtE;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,WAAW,EACX,OAAO,EACP,QAAQ,EACR,MAAM,EACN,UAAU,EACV,cAAc,EACd,WAAW,EACX,IAAI,EACJ,MAAM,EACN,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,eAAe,EACf,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,sBAAsB,GACvB,EAAE,oBAAoB,2CAiQtB"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useMemo } from "react";
4
- import { Send, X } from "lucide-react";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { ChevronRight, Send, X } from "lucide-react";
5
5
  import { Button } from "../../ui/button.js";
6
6
  import { Badge } from "../../ui/badge.js";
7
7
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs.js";
@@ -17,6 +17,7 @@ import { PayloadEditor } from "../api-tester/PayloadEditor.js";
17
17
  import { buildUrl, validatePathParams, validateJsonBody } from "../api-tester/utils/url-builder.js";
18
18
  import { PresetsTab } from "./PresetsTab.js";
19
19
  import { ApiDocsModal } from "./ApiDocsModal.js";
20
+ import { loadState, saveState } from "./explorer-storage.js";
20
21
  const methodColors = {
21
22
  GET: "bg-emerald-500/20 text-emerald-600 dark:text-emerald-400 border-emerald-500/30",
22
23
  POST: "bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/30",
@@ -68,6 +69,20 @@ function ApiRequestPanel({
68
69
  return pathErrors.length === 0 && !bodyError;
69
70
  }, [pathParams, method, body]);
70
71
  const showPayloadEditor = ["POST", "PATCH", "PUT"].includes(method);
72
+ const [sessionOpen, setSessionOpen] = useState(true);
73
+ useEffect(() => {
74
+ setSessionOpen(loadState("sessionOpen") ?? true);
75
+ }, []);
76
+ useEffect(() => {
77
+ saveState("sessionOpen", sessionOpen);
78
+ }, [sessionOpen]);
79
+ const sessionSummary = useMemo(() => {
80
+ const parts = [authType === "apiKey" ? "API key" : "session"];
81
+ if (bypassMode) parts.push("cross-team");
82
+ if (selectedTeamId) parts.push(selectedTeamId);
83
+ if (actAsUserId.trim()) parts.push(`as ${actAsUserId.trim()}`);
84
+ return parts.join(" \xB7 ");
85
+ }, [authType, bypassMode, selectedTeamId, actAsUserId]);
71
86
  const handlePathParamsChange = (params) => {
72
87
  onPathParamsChange(params);
73
88
  onClearPresetIndicator == null ? void 0 : onClearPresetIndicator("params");
@@ -127,7 +142,22 @@ function ApiRequestPanel({
127
142
  /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground uppercase tracking-wide mb-1", children: t("urlPreview") }),
128
143
  /* @__PURE__ */ jsx("code", { className: "font-mono text-xs break-all text-foreground", children: previewUrl })
129
144
  ] }),
130
- /* @__PURE__ */ jsx(
145
+ /* @__PURE__ */ jsxs(
146
+ "button",
147
+ {
148
+ type: "button",
149
+ onClick: () => setSessionOpen((open) => !open),
150
+ className: "flex w-full items-center gap-2 pt-2 border-t text-left text-xs text-muted-foreground hover:text-foreground",
151
+ "aria-expanded": sessionOpen,
152
+ "data-cy": sel("devtools.apiExplorer.request.toggleSession"),
153
+ children: [
154
+ /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-3.5 w-3.5 transition-transform", sessionOpen && "rotate-90") }),
155
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Session" }),
156
+ !sessionOpen && /* @__PURE__ */ jsx("span", { className: "truncate", children: sessionSummary })
157
+ ]
158
+ }
159
+ ),
160
+ /* @__PURE__ */ jsx("div", { className: cn(!sessionOpen && "hidden"), children: /* @__PURE__ */ jsx(
131
161
  AuthSelector,
132
162
  {
133
163
  authType,
@@ -141,9 +171,9 @@ function ApiRequestPanel({
141
171
  onTeamChange,
142
172
  onActAsUserChange
143
173
  }
144
- )
174
+ ) })
145
175
  ] }),
146
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "params", className: "flex-1 flex flex-col", children: [
176
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "params", className: "flex-1 flex flex-col min-h-0", children: [
147
177
  /* @__PURE__ */ jsx("div", { className: "border-b px-4", children: /* @__PURE__ */ jsxs(TabsList, { className: "h-9 bg-transparent p-0 border-0", children: [
148
178
  /* @__PURE__ */ jsxs(
149
179
  TabsTrigger,
@@ -196,7 +226,7 @@ function ApiRequestPanel({
196
226
  }
197
227
  )
198
228
  ] }) }),
199
- /* @__PURE__ */ jsxs(ScrollArea, { className: "flex-1", children: [
229
+ /* @__PURE__ */ jsxs(ScrollArea, { className: "flex-1 min-h-0", children: [
200
230
  /* @__PURE__ */ jsxs(TabsContent, { value: "params", className: "m-0 p-4 space-y-4", children: [
201
231
  pathParams.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
202
232
  /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium mb-2", children: "Path Parameters" }),
@@ -1 +1 @@
1
- {"version":3,"file":"JsonViewer.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/JsonViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,qCAAqC,CAAA;AAM5C,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,CAAA;AACpD,KAAK,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1C,UAAU,eAAe;IACvB,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,eAAmB,EAAE,EAAE,eAAe,2CAqBnF;AAED,UAAU,2BAA2B;IACnC,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,CAAA;IAC7B,mCAAmC;IACnC,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,SAAS,EACT,eAAmB,EACnB,YAAmB,GACpB,EAAE,2BAA2B,2CA+E7B"}
1
+ {"version":3,"file":"JsonViewer.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/JsonViewer.tsx"],"names":[],"mappings":"AAIA,OAAO,qCAAqC,CAAA;AAM5C,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,CAAA;AACpD,KAAK,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1C,UAAU,eAAe;IACvB,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,eAAmB,EAAE,EAAE,eAAe,2CA4BnF;AAED,UAAU,2BAA2B;IACnC,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,CAAA;IAC7B,mCAAmC;IACnC,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,SAAS,EACT,eAAmB,EACnB,YAAmB,GACpB,EAAE,2BAA2B,2CAoF7B"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useState, useCallback } from "react";
3
+ import { useCallback, useMemo, useState } from "react";
4
4
  import { JsonView, allExpanded, collapseAllNested, defaultStyles, darkStyles } from "react-json-view-lite";
5
5
  import "react-json-view-lite/dist/index.css";
6
6
  import { useTheme } from "next-themes";
@@ -10,7 +10,10 @@ import { Button } from "../../ui/button.js";
10
10
  function JsonViewer({ data, className, initialExpanded = 2 }) {
11
11
  const { resolvedTheme } = useTheme();
12
12
  const isDark = resolvedTheme === "dark";
13
- const shouldExpandNode = initialExpanded === "all" ? allExpanded : initialExpanded === "none" ? collapseAllNested : (level) => level < initialExpanded;
13
+ const shouldExpandNode = useMemo(
14
+ () => initialExpanded === "all" ? allExpanded : initialExpanded === "none" ? collapseAllNested : (level) => level < initialExpanded,
15
+ [initialExpanded]
16
+ );
14
17
  return /* @__PURE__ */ jsx("div", { className: cn("font-mono text-sm", className), children: /* @__PURE__ */ jsx(
15
18
  JsonView,
16
19
  {
@@ -29,19 +32,22 @@ function JsonViewerWithControls({
29
32
  const { resolvedTheme } = useTheme();
30
33
  const isDark = resolvedTheme === "dark";
31
34
  const [expandState, setExpandState] = useState(initialExpanded);
32
- const [viewKey, setViewKey] = useState(0);
35
+ const [applyNonce, setApplyNonce] = useState(0);
33
36
  const handleExpandAll = useCallback(() => {
34
37
  setExpandState("all");
35
- setViewKey((k) => k + 1);
38
+ setApplyNonce((n) => n + 1);
36
39
  }, []);
37
40
  const handleCollapseAll = useCallback(() => {
38
41
  setExpandState("none");
39
- setViewKey((k) => k + 1);
42
+ setApplyNonce((n) => n + 1);
40
43
  }, []);
41
44
  const handleCopy = useCallback(() => {
42
45
  navigator.clipboard.writeText(JSON.stringify(data, null, 2));
43
46
  }, [data]);
44
- const shouldExpandNode = expandState === "all" ? allExpanded : expandState === "none" ? collapseAllNested : (level) => level < expandState;
47
+ const shouldExpandNode = useMemo(
48
+ () => expandState === "all" ? allExpanded : expandState === "none" ? collapseAllNested : (level) => level < expandState,
49
+ [expandState, applyNonce]
50
+ );
45
51
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
46
52
  showControls && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mb-2", "data-cy": "json-viewer-controls", children: [
47
53
  /* @__PURE__ */ jsxs(
@@ -94,8 +100,7 @@ function JsonViewerWithControls({
94
100
  data,
95
101
  shouldExpandNode,
96
102
  style: isDark ? darkStyles : defaultStyles
97
- },
98
- viewKey
103
+ }
99
104
  ) })
100
105
  ] });
101
106
  }
@@ -0,0 +1,26 @@
1
+ interface PanelDividerProps {
2
+ /** Current split, as the percentage of the container the LEFT panel takes. */
3
+ value: number;
4
+ onChange: (percent: number) => void;
5
+ /** The element the percentage is measured against. */
6
+ containerRef: React.RefObject<HTMLElement | null>;
7
+ min?: number;
8
+ max?: number;
9
+ label?: string;
10
+ }
11
+ /**
12
+ * Drag handle between two horizontally-split panels.
13
+ *
14
+ * Hand-rolled rather than pulled from a panel library: it needs one number, and a devtools
15
+ * screen is not worth a dependency in every app the framework ships.
16
+ *
17
+ * It reports a PERCENTAGE, not pixels, so a split chosen on a wide monitor still looks
18
+ * deliberate on a laptop. Pointer capture means a fast drag that outruns the cursor keeps
19
+ * working instead of being dropped the moment the pointer leaves the 6px strip.
20
+ *
21
+ * It is also a real `separator` widget: focusable, moved with the arrow keys, and it reports
22
+ * its position — dragging cannot be the only way to reach a layout.
23
+ */
24
+ export declare function PanelDivider({ value, onChange, containerRef, min, max, label, }: PanelDividerProps): import("react/jsx-runtime").JSX.Element;
25
+ export {};
26
+ //# sourceMappingURL=PanelDivider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PanelDivider.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/PanelDivider.tsx"],"names":[],"mappings":"AAMA,UAAU,iBAAiB;IACzB,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,sDAAsD;IACtD,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IACjD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,GAAQ,EACR,GAAQ,EACR,KAAuB,GACxB,EAAE,iBAAiB,2CAiGnB"}
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useRef } from "react";
4
+ import { cn } from "../../../lib/utils/index.js";
5
+ import { sel } from "../../../lib/test/index.js";
6
+ function PanelDivider({
7
+ value,
8
+ onChange,
9
+ containerRef,
10
+ min = 25,
11
+ max = 75,
12
+ label = "Resize panels"
13
+ }) {
14
+ const draggingRef = useRef(false);
15
+ const clamp = useCallback((percent) => Math.min(max, Math.max(min, percent)), [min, max]);
16
+ const percentFromClientX = useCallback(
17
+ (clientX) => {
18
+ const container = containerRef.current;
19
+ if (!container) return null;
20
+ const rect = container.getBoundingClientRect();
21
+ if (rect.width === 0) return null;
22
+ return clamp((clientX - rect.left) / rect.width * 100);
23
+ },
24
+ [containerRef, clamp]
25
+ );
26
+ const handlePointerDown = useCallback((event) => {
27
+ draggingRef.current = true;
28
+ event.currentTarget.setPointerCapture(event.pointerId);
29
+ document.body.style.userSelect = "none";
30
+ document.body.style.cursor = "col-resize";
31
+ }, []);
32
+ const handlePointerMove = useCallback(
33
+ (event) => {
34
+ if (!draggingRef.current) return;
35
+ const next = percentFromClientX(event.clientX);
36
+ if (next !== null) onChange(next);
37
+ },
38
+ [percentFromClientX, onChange]
39
+ );
40
+ const endDrag = useCallback((event) => {
41
+ if (!draggingRef.current) return;
42
+ draggingRef.current = false;
43
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
44
+ event.currentTarget.releasePointerCapture(event.pointerId);
45
+ }
46
+ document.body.style.userSelect = "";
47
+ document.body.style.cursor = "";
48
+ }, []);
49
+ useEffect(
50
+ () => () => {
51
+ document.body.style.userSelect = "";
52
+ document.body.style.cursor = "";
53
+ },
54
+ []
55
+ );
56
+ const handleKeyDown = useCallback(
57
+ (event) => {
58
+ const step = event.shiftKey ? 10 : 2;
59
+ if (event.key === "ArrowLeft") {
60
+ event.preventDefault();
61
+ onChange(clamp(value - step));
62
+ } else if (event.key === "ArrowRight") {
63
+ event.preventDefault();
64
+ onChange(clamp(value + step));
65
+ } else if (event.key === "Home") {
66
+ event.preventDefault();
67
+ onChange(min);
68
+ } else if (event.key === "End") {
69
+ event.preventDefault();
70
+ onChange(max);
71
+ }
72
+ },
73
+ [value, onChange, clamp, min, max]
74
+ );
75
+ return /* @__PURE__ */ jsx(
76
+ "div",
77
+ {
78
+ role: "separator",
79
+ "aria-orientation": "vertical",
80
+ "aria-label": label,
81
+ "aria-valuenow": Math.round(value),
82
+ "aria-valuemin": min,
83
+ "aria-valuemax": max,
84
+ tabIndex: 0,
85
+ onPointerDown: handlePointerDown,
86
+ onPointerMove: handlePointerMove,
87
+ onPointerUp: endDrag,
88
+ onPointerCancel: endDrag,
89
+ onKeyDown: handleKeyDown,
90
+ className: cn(
91
+ "hidden lg:flex shrink-0 w-1.5 cursor-col-resize items-center justify-center",
92
+ "bg-border/40 hover:bg-primary/60 focus-visible:bg-primary/60 transition-colors",
93
+ "focus-visible:outline-none"
94
+ ),
95
+ "data-cy": sel("devtools.apiExplorer.panelDivider"),
96
+ children: /* @__PURE__ */ jsx("div", { className: "h-8 w-0.5 rounded-full bg-foreground/25" })
97
+ }
98
+ );
99
+ }
100
+ export {
101
+ PanelDivider
102
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"PresetsTab.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/PresetsTab.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAErD,UAAU,eAAe;IACvB,eAAe,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC1C,aAAa,EAAE,UAAU,CAAA;IACzB,aAAa,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CAC3C;AAED,wBAAgB,UAAU,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,EAAE,eAAe,2CA8H5F"}
1
+ {"version":3,"file":"PresetsTab.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/PresetsTab.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AA0CrD,UAAU,eAAe;IACvB,eAAe,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC1C,aAAa,EAAE,UAAU,CAAA;IACzB,aAAa,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CAC3C;AAED,wBAAgB,UAAU,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,EAAE,eAAe,2CAgI5F"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
4
- import { Play, Eye, BookOpen } from "lucide-react";
3
+ import { useEffect, useState } from "react";
4
+ import { Play, Eye, BookOpen, RefreshCw } from "lucide-react";
5
5
  import { Button } from "../../ui/button.js";
6
6
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../../ui/card.js";
7
7
  import { Badge } from "../../ui/badge.js";
@@ -15,6 +15,38 @@ import {
15
15
  } from "../../ui/dialog.js";
16
16
  import { ScrollArea } from "../../ui/scroll-area.js";
17
17
  import { sel } from "../../../lib/test/index.js";
18
+ import { readPresetRunToken, regeneratePresetRunToken } from "./preset-placeholders.js";
19
+ function RunTokenBar({ presets }) {
20
+ const [token, setToken] = useState(null);
21
+ useEffect(() => {
22
+ setToken(readPresetRunToken());
23
+ }, []);
24
+ const usesRunToken = presets.some((preset) => JSON.stringify(preset).includes("{{RUN}}"));
25
+ if (!usesRunToken || !token) return null;
26
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md border bg-muted/40 px-3 py-2", children: [
27
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Run" }),
28
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs text-foreground", children: token }),
29
+ /* @__PURE__ */ jsxs(
30
+ Button,
31
+ {
32
+ variant: "ghost",
33
+ size: "sm",
34
+ className: "h-6 px-2",
35
+ onClick: () => setToken(regeneratePresetRunToken()),
36
+ "data-cy": sel("devtools.apiExplorer.presets.regenerateRunToken"),
37
+ children: [
38
+ /* @__PURE__ */ jsx(RefreshCw, { className: "h-3 w-3 mr-1" }),
39
+ "New run"
40
+ ]
41
+ }
42
+ ),
43
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
44
+ "Every ",
45
+ "{{RUN}}",
46
+ " in these presets resolves to this. Start a new run to send values nothing has used before."
47
+ ] })
48
+ ] });
49
+ }
18
50
  function PresetsTab({ endpointPresets, currentMethod, onApplyPreset }) {
19
51
  const [selectedPreset, setSelectedPreset] = useState(null);
20
52
  const [isDetailOpen, setIsDetailOpen] = useState(false);
@@ -44,6 +76,7 @@ function PresetsTab({ endpointPresets, currentMethod, onApplyPreset }) {
44
76
  };
45
77
  return /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-4", "data-cy": sel("devtools.apiExplorer.presets.tab"), children: [
46
78
  endpointPresets.summary && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: endpointPresets.summary }),
79
+ /* @__PURE__ */ jsx(RunTokenBar, { presets: endpointPresets.presets }),
47
80
  filteredPresets.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground p-4 bg-muted/30 rounded-lg", children: [
48
81
  /* @__PURE__ */ jsxs("p", { children: [
49
82
  "No presets for ",
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Placeholders a preset payload can carry, resolved when the preset is APPLIED.
3
+ *
4
+ * A write preset is usually single-use: it creates something with an email, a reference or a
5
+ * slug that cannot be reused, so the second press either replays the first or is refused, and
6
+ * whoever is testing has to hand-edit the values before every run.
7
+ *
8
+ * Some presets need the opposite, though: one proving an idempotent replay has to send the value
9
+ * the previous request sent, and one proving that a reference cannot be reused across two buyers
10
+ * has to reuse it. Resolve a token per REQUEST and those stop testing anything while still
11
+ * answering 200, which reads as a pass.
12
+ *
13
+ * So there are two kinds of token, and which one a preset wants depends on what it is proving:
14
+ *
15
+ * - `{{UNIQUE}}` is fresh on every apply. This is the default for a preset that creates
16
+ * something: press play, get values nobody has used, press play again, get another set. Two
17
+ * people testing at the same time never collide with each other either.
18
+ * - `{{RUN}}` is stable until someone starts a new run, and shared by every preset of the
19
+ * endpoint. This is for the rarer case where two DIFFERENT presets have to meet on the same
20
+ * value — a reference one preset creates and another tries to reuse.
21
+ *
22
+ * Resolution happens when the preset is APPLIED, not when the request is sent, and that is what
23
+ * lets a single preset prove an idempotent replay even with `{{UNIQUE}}`: apply once, then send
24
+ * twice. The second send reuses the body already in the editor, which is exactly what a retried
25
+ * webhook does.
26
+ *
27
+ * Every token resolves once per apply, so an email and a reference in the same payload agree
28
+ * with each other.
29
+ */
30
+ /** Token → value for a single apply. Built once so every occurrence resolves identically. */
31
+ export interface PresetPlaceholderValues {
32
+ UNIQUE: string;
33
+ RUN: string;
34
+ TIMESTAMP: string;
35
+ UUID: string;
36
+ FIRST_TEAM_ID: string;
37
+ }
38
+ export declare const PRESET_RUN_TOKEN_KEY = "devtools.presetRunToken";
39
+ /** The current run token, minting one on first use. Survives reloads: a run outlives a refresh. */
40
+ export declare function readPresetRunToken(): string;
41
+ /** Starts a new run. Presets applied from here on get values nothing has used before. */
42
+ export declare function regeneratePresetRunToken(): string;
43
+ export declare function createPresetPlaceholderValues(): PresetPlaceholderValues;
44
+ /**
45
+ * Replaces every known token inside a preset value, walking strings, arrays and objects.
46
+ *
47
+ * An unknown token is left exactly as written rather than blanked: a preset that names something
48
+ * this version does not provide should show that in the editor, where it can be read and fixed,
49
+ * instead of silently sending an empty string that the endpoint then rejects for the wrong reason.
50
+ */
51
+ export declare function resolvePresetPlaceholders<T>(value: T, values?: PresetPlaceholderValues): T;
52
+ //# sourceMappingURL=preset-placeholders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preset-placeholders.d.ts","sourceRoot":"","sources":["../../../../src/components/devtools/api-explorer/preset-placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,6FAA6F;AAC7F,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,oBAAoB,4BAA4B,CAAA;AAgB7D,mGAAmG;AACnG,wBAAgB,kBAAkB,IAAI,MAAM,CAO3C;AAED,yFAAyF;AACzF,wBAAgB,wBAAwB,IAAI,MAAM,CAMjD;AAOD,wBAAgB,6BAA6B,IAAI,uBAAuB,CASvE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EACzC,KAAK,EAAE,CAAC,EACR,MAAM,GAAE,uBAAyD,GAChE,CAAC,CAkBH"}