@buoy-gg/network 7.0.14 → 7.0.16

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 (107) hide show
  1. package/lib/commonjs/index.web.js +395 -0
  2. package/lib/commonjs/network/components/DataViewer.web.js +879 -0
  3. package/lib/commonjs/network/components/NetworkCopyScreen.web.js +48 -0
  4. package/lib/commonjs/network/components/NetworkCopySettingsView.web.js +1242 -0
  5. package/lib/commonjs/network/components/NetworkDetailScreen.web.js +101 -0
  6. package/lib/commonjs/network/components/NetworkDetailStepper.web.js +296 -0
  7. package/lib/commonjs/network/components/NetworkEventDetailView.web.js +1498 -0
  8. package/lib/commonjs/network/components/NetworkEventItemCompact.web.js +802 -0
  9. package/lib/commonjs/network/components/NetworkFilterScreen.web.js +55 -0
  10. package/lib/commonjs/network/components/NetworkFilterViewV3.web.js +1310 -0
  11. package/lib/commonjs/network/components/NetworkHeaderLive.web.js +604 -0
  12. package/lib/commonjs/network/components/NetworkListScreen.js +2 -1
  13. package/lib/commonjs/network/components/NetworkListScreen.web.js +607 -0
  14. package/lib/commonjs/network/components/NetworkModal.js +12 -3
  15. package/lib/commonjs/network/components/NetworkNumberPrompt.web.js +232 -0
  16. package/lib/commonjs/network/components/NetworkOverrideEditor.web.js +942 -0
  17. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.web.js +133 -0
  18. package/lib/commonjs/network/components/NetworkOverridesButton.web.js +143 -0
  19. package/lib/commonjs/network/components/NetworkOverridesScreen.js +160 -126
  20. package/lib/commonjs/network/components/NetworkOverridesScreen.web.js +830 -0
  21. package/lib/commonjs/network/components/NetworkSavedScreen.web.js +459 -0
  22. package/lib/commonjs/network/components/response-editor/Explorer.web.js +1136 -0
  23. package/lib/commonjs/network/hooks/useNetworkEventList.js +12 -2
  24. package/lib/commonjs/network/overrides/applyToXhr.js +276 -15
  25. package/lib/commonjs/network/utils/generateNetworkCopyText.js +32 -5
  26. package/lib/commonjs/network/utils/networkEventStore.js +12 -4
  27. package/lib/commonjs/network/utils/networkListener.js +1 -16
  28. package/lib/module/index.web.js +81 -0
  29. package/lib/module/network/components/DataViewer.web.js +875 -0
  30. package/lib/module/network/components/NetworkCopyScreen.web.js +44 -0
  31. package/lib/module/network/components/NetworkCopySettingsView.web.js +1237 -0
  32. package/lib/module/network/components/NetworkDetailScreen.web.js +97 -0
  33. package/lib/module/network/components/NetworkDetailStepper.web.js +294 -0
  34. package/lib/module/network/components/NetworkEventDetailView.web.js +1496 -0
  35. package/lib/module/network/components/NetworkEventItemCompact.web.js +800 -0
  36. package/lib/module/network/components/NetworkFilterScreen.web.js +51 -0
  37. package/lib/module/network/components/NetworkFilterViewV3.web.js +1306 -0
  38. package/lib/module/network/components/NetworkHeaderLive.web.js +598 -0
  39. package/lib/module/network/components/NetworkListScreen.js +2 -1
  40. package/lib/module/network/components/NetworkListScreen.web.js +605 -0
  41. package/lib/module/network/components/NetworkModal.js +12 -3
  42. package/lib/module/network/components/NetworkNumberPrompt.web.js +230 -0
  43. package/lib/module/network/components/NetworkOverrideEditor.web.js +939 -0
  44. package/lib/module/network/components/NetworkOverrideHeaderButton.web.js +131 -0
  45. package/lib/module/network/components/NetworkOverridesButton.web.js +141 -0
  46. package/lib/module/network/components/NetworkOverridesScreen.js +161 -127
  47. package/lib/module/network/components/NetworkOverridesScreen.web.js +826 -0
  48. package/lib/module/network/components/NetworkSavedScreen.web.js +457 -0
  49. package/lib/module/network/components/response-editor/Explorer.web.js +1133 -0
  50. package/lib/module/network/hooks/useNetworkEventList.js +12 -2
  51. package/lib/module/network/overrides/applyToXhr.js +275 -15
  52. package/lib/module/network/utils/generateNetworkCopyText.js +28 -1
  53. package/lib/module/network/utils/networkEventStore.js +12 -4
  54. package/lib/module/network/utils/networkListener.js +1 -16
  55. package/lib/typescript/index.web.d.ts +56 -0
  56. package/lib/typescript/index.web.d.ts.map +1 -0
  57. package/lib/typescript/network/components/DataViewer.web.d.ts +78 -0
  58. package/lib/typescript/network/components/DataViewer.web.d.ts.map +1 -0
  59. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts +27 -0
  60. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts.map +1 -0
  61. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts +68 -0
  62. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts.map +1 -0
  63. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts +45 -0
  64. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts.map +1 -0
  65. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts +40 -0
  66. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts.map +1 -0
  67. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts +67 -0
  68. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts.map +1 -0
  69. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts +58 -0
  70. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts.map +1 -0
  71. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts +28 -0
  72. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts.map +1 -0
  73. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts +74 -0
  74. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts.map +1 -0
  75. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts +112 -0
  76. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts.map +1 -0
  77. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
  78. package/lib/typescript/network/components/NetworkListScreen.web.d.ts +63 -0
  79. package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +1 -0
  80. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  81. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts +38 -0
  82. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts.map +1 -0
  83. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts +43 -0
  84. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts.map +1 -0
  85. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts +32 -0
  86. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts.map +1 -0
  87. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts +26 -0
  88. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts.map +1 -0
  89. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts +9 -0
  90. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +1 -1
  91. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts +69 -0
  92. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts.map +1 -0
  93. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts +67 -0
  94. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts.map +1 -0
  95. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts +56 -0
  96. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts.map +1 -0
  97. package/lib/typescript/network/hooks/useNetworkEventList.d.ts +10 -0
  98. package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +1 -1
  99. package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts.map +1 -1
  100. package/lib/typescript/network/overrides/applyToXhr.d.ts +40 -14
  101. package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -1
  102. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts +34 -1
  103. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -1
  104. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
  105. package/lib/typescript/network/utils/networkListener.d.ts +0 -1
  106. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
  107. package/package.json +2 -2
@@ -0,0 +1,939 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * NetworkOverrideEditor.web — DOM twin of NetworkOverrideEditor.tsx.
5
+ *
6
+ * The rule form — one decision, everything else out of the way. Same shape as
7
+ * native: a collapsed "Applies to" disclosure (URL pattern + method chips),
8
+ * the OUTCOME grid (kind + status + failKind folded into one list), the
9
+ * collapsed "Response body" disclosure (the response-editor Explorer tree, or
10
+ * the empty "Edit all" paste buffer), always-visible Delay and Fires rows,
11
+ * the top-pinned NetworkNumberPrompt for custom numbers, and the
12
+ * Cancel/Save footer (Save disabled until a URL pattern exists).
13
+ *
14
+ * REUSED, not forked (all platform-free): `OverrideRuleDraft`,
15
+ * `STATUS_MIN`/`STATUS_MAX`, `ResponseEditorContext` — plus the response
16
+ * tree itself via the Explorer DOM twin (which reuses the pure nested-data
17
+ * ops).
18
+ *
19
+ * shared-ui RN chrome is replicated as DOM with provenance (web consumers
20
+ * resolve `@buoy-gg/shared-ui` to a minimal shim):
21
+ * - `macOSColors` — inlined verbatim subset from
22
+ * shared/src/ui/gameUI/constants/macOSDesignSystemColors.ts.
23
+ * - Icons — inline lucide SVGs (shared-ui's icons are lucide re-exports).
24
+ *
25
+ * Intentional deviations (all logged):
26
+ * - `useSafeAreaInsets` is not ported: `applySafeAreaInset` is accepted for
27
+ * API compatibility and ignored — a floating web panel (and a desktop
28
+ * window) has no home indicator to clear, the exact case native gates OFF.
29
+ * - `keyboardShouldPersistTaps` is a RN-keyboard concern with no DOM
30
+ * equivalent.
31
+ *
32
+ * @platform React DOM Web
33
+ */
34
+
35
+ import { useMemo, useState } from "react";
36
+ import Explorer from "./response-editor/Explorer.web";
37
+ import { ResponseEditorContext } from "./response-editor/editorContext";
38
+ import { NetworkNumberPrompt } from "./NetworkNumberPrompt.web";
39
+ import { STATUS_MAX, STATUS_MIN } from "../overrides/types";
40
+
41
+ // =============================
42
+ // Native palette (provenance)
43
+ // =============================
44
+
45
+ /** macOSColors subset — shared/src/ui/gameUI/constants/macOSDesignSystemColors.ts, verbatim. */
46
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
47
+ const macOSColors = {
48
+ background: {
49
+ base: "#0A0A0C",
50
+ card: "#1A1A1C",
51
+ input: "#26262A"
52
+ },
53
+ border: {
54
+ default: "#2D2D2F",
55
+ input: "#3D3D42"
56
+ },
57
+ text: {
58
+ primary: "#F5F5F7",
59
+ secondary: "#A1A1A6",
60
+ muted: "#8E8E93"
61
+ },
62
+ semantic: {
63
+ error: "#FF453A",
64
+ info: "#00B8E6"
65
+ }
66
+ };
67
+
68
+ // =============================
69
+ // Inline lucide icons (shared-ui icons are lucide re-exports)
70
+ // =============================
71
+
72
+ function Svg({
73
+ size = 14,
74
+ color = "currentColor",
75
+ children
76
+ }) {
77
+ return /*#__PURE__*/_jsx("svg", {
78
+ width: size,
79
+ height: size,
80
+ viewBox: "0 0 24 24",
81
+ fill: "none",
82
+ stroke: color,
83
+ strokeWidth: 2,
84
+ strokeLinecap: "round",
85
+ strokeLinejoin: "round",
86
+ style: {
87
+ flexShrink: 0,
88
+ display: "block"
89
+ },
90
+ children: children
91
+ });
92
+ }
93
+ const ChevronDown = p => /*#__PURE__*/_jsx(Svg, {
94
+ ...p,
95
+ children: /*#__PURE__*/_jsx("path", {
96
+ d: "m6 9 6 6 6-6"
97
+ })
98
+ });
99
+ const ChevronRight = p => /*#__PURE__*/_jsx(Svg, {
100
+ ...p,
101
+ children: /*#__PURE__*/_jsx("path", {
102
+ d: "m9 18 6-6-6-6"
103
+ })
104
+ });
105
+ const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
106
+ const DELAY_PRESETS = [0, 500, 1000, 3000, 10000];
107
+
108
+ /**
109
+ * The single decision, ordered by how often a developer reaches for it — the
110
+ * OUTCOMES list, native verbatim (plain-language name AND status code).
111
+ */
112
+
113
+ const OUTCOMES = [{
114
+ id: "500",
115
+ label: "Server error",
116
+ code: "500",
117
+ danger: true,
118
+ patch: {
119
+ kind: "respond",
120
+ status: 500,
121
+ statusText: "Internal Server Error"
122
+ }
123
+ }, {
124
+ id: "401",
125
+ label: "Unauthorized",
126
+ code: "401",
127
+ danger: true,
128
+ patch: {
129
+ kind: "respond",
130
+ status: 401,
131
+ statusText: "Unauthorized"
132
+ }
133
+ }, {
134
+ id: "404",
135
+ label: "Not found",
136
+ code: "404",
137
+ danger: true,
138
+ patch: {
139
+ kind: "respond",
140
+ status: 404,
141
+ statusText: "Not Found"
142
+ }
143
+ }, {
144
+ id: "403",
145
+ label: "Forbidden",
146
+ code: "403",
147
+ danger: true,
148
+ patch: {
149
+ kind: "respond",
150
+ status: 403,
151
+ statusText: "Forbidden"
152
+ }
153
+ }, {
154
+ id: "429",
155
+ label: "Rate limited",
156
+ code: "429",
157
+ danger: true,
158
+ patch: {
159
+ kind: "respond",
160
+ status: 429,
161
+ statusText: "Too Many Requests"
162
+ }
163
+ }, {
164
+ id: "503",
165
+ label: "Unavailable",
166
+ code: "503",
167
+ danger: true,
168
+ patch: {
169
+ kind: "respond",
170
+ status: 503,
171
+ statusText: "Service Unavailable"
172
+ }
173
+ }, {
174
+ id: "400",
175
+ label: "Bad request",
176
+ code: "400",
177
+ danger: true,
178
+ patch: {
179
+ kind: "respond",
180
+ status: 400,
181
+ statusText: "Bad Request"
182
+ }
183
+ }, {
184
+ id: "200",
185
+ label: "Success",
186
+ code: "200",
187
+ patch: {
188
+ kind: "respond",
189
+ status: 200,
190
+ statusText: "OK"
191
+ }
192
+ }, {
193
+ id: "offline",
194
+ label: "Offline",
195
+ danger: true,
196
+ patch: {
197
+ kind: "fail",
198
+ failKind: "network"
199
+ }
200
+ }, {
201
+ id: "timeout",
202
+ label: "Timeout",
203
+ danger: true,
204
+ patch: {
205
+ kind: "fail",
206
+ failKind: "timeout"
207
+ }
208
+ }, {
209
+ id: "passthrough",
210
+ label: "Real response",
211
+ patch: {
212
+ kind: "delay"
213
+ }
214
+ }];
215
+ function outcomeIdFor(draft) {
216
+ if (draft.kind === "delay") return "passthrough";
217
+ if (draft.kind === "fail") {
218
+ return draft.failKind === "timeout" ? "timeout" : "offline";
219
+ }
220
+ return String(draft.status ?? 200);
221
+ }
222
+ function OutcomeChip({
223
+ outcome,
224
+ active,
225
+ onPress
226
+ }) {
227
+ const accent = outcome.danger ? macOSColors.semantic.error : macOSColors.semantic.info;
228
+ return /*#__PURE__*/_jsxs("button", {
229
+ type: "button",
230
+ onClick: onPress,
231
+ "aria-label": `${outcome.label}${outcome.code ? ` ${outcome.code}` : ""}`,
232
+ "aria-pressed": active,
233
+ style: {
234
+ ...styles.outcome,
235
+ ...(active ? {
236
+ border: `1px solid ${accent}`,
237
+ backgroundColor: `${accent}1F`
238
+ } : null)
239
+ },
240
+ children: [/*#__PURE__*/_jsx("span", {
241
+ style: {
242
+ ...styles.outcomeLabel,
243
+ ...(active ? {
244
+ color: accent
245
+ } : null)
246
+ },
247
+ children: outcome.label
248
+ }), outcome.code ? /*#__PURE__*/_jsx("span", {
249
+ style: {
250
+ ...styles.outcomeCode,
251
+ ...(active ? {
252
+ color: accent
253
+ } : null)
254
+ },
255
+ children: outcome.code
256
+ }) : null]
257
+ });
258
+ }
259
+
260
+ /** A collapsed row that opens to reveal its controls. */
261
+ function Disclosure({
262
+ title,
263
+ summary,
264
+ open,
265
+ onToggle,
266
+ children
267
+ }) {
268
+ return /*#__PURE__*/_jsxs("div", {
269
+ style: styles.disclosure,
270
+ children: [/*#__PURE__*/_jsxs("button", {
271
+ type: "button",
272
+ onClick: onToggle,
273
+ "aria-expanded": open,
274
+ style: styles.disclosureHeader,
275
+ children: [open ? /*#__PURE__*/_jsx(ChevronDown, {
276
+ size: 12,
277
+ color: macOSColors.text.muted
278
+ }) : /*#__PURE__*/_jsx(ChevronRight, {
279
+ size: 12,
280
+ color: macOSColors.text.muted
281
+ }), /*#__PURE__*/_jsx("span", {
282
+ style: styles.disclosureTitle,
283
+ children: title
284
+ }), summary && !open ? /*#__PURE__*/_jsx("span", {
285
+ style: styles.disclosureSummary,
286
+ children: summary
287
+ }) : null]
288
+ }), open ? /*#__PURE__*/_jsx("div", {
289
+ style: styles.disclosureBody,
290
+ children: children
291
+ }) : null]
292
+ });
293
+ }
294
+ function Chip({
295
+ label,
296
+ active,
297
+ onPress
298
+ }) {
299
+ return /*#__PURE__*/_jsx("button", {
300
+ type: "button",
301
+ onClick: onPress,
302
+ "aria-pressed": active,
303
+ style: {
304
+ ...styles.chip,
305
+ ...(active ? styles.chipActive : null)
306
+ },
307
+ children: /*#__PURE__*/_jsx("span", {
308
+ style: {
309
+ ...styles.chipText,
310
+ ...(active ? styles.chipTextActive : null)
311
+ },
312
+ children: label
313
+ })
314
+ });
315
+ }
316
+ export function NetworkOverrideEditor({
317
+ draft,
318
+ onChange,
319
+ onSave,
320
+ onCancel,
321
+ saveLabel = "Save",
322
+ applySafeAreaInset: _applySafeAreaInset = false
323
+ }) {
324
+ const patch = next => onChange({
325
+ ...draft,
326
+ ...next
327
+ });
328
+
329
+ // A rule made from a request arrives with its scope already right, so it
330
+ // starts closed. A blank one has nothing to match on yet, so it starts open.
331
+ const [scopeOpen, setScopeOpen] = useState(!draft.urlPattern.trim());
332
+ const [bodyOpen, setBodyOpen] = useState(false);
333
+ /**
334
+ * The "Edit all" buffer — deliberately NOT seeded with the current body
335
+ * (native comment: you only reach for this to REPLACE the whole thing, and
336
+ * starting empty means paste-and-go).
337
+ */
338
+ const [pasteText, setPasteText] = useState(null);
339
+ /** Which custom number the top-of-screen prompt is asking for, if any. */
340
+ const [prompt, setPrompt] = useState(null);
341
+ const activeOutcome = outcomeIdFor(draft);
342
+ /** True when the status is a real one that no listed outcome covers. */
343
+ const customStatusActive = draft.kind === "respond" && typeof draft.status === "number" && !OUTCOMES.some(outcome => outcome.code === String(draft.status));
344
+ const showsBody = draft.kind === "respond";
345
+ const method = draft.methods?.[0];
346
+ const scopeSummary = `${method ?? "Any"} ${draft.urlPattern || "No URL yet"}`;
347
+ const bodySummary = useMemo(() => {
348
+ const body = (draft.body ?? "").trim();
349
+ if (!body) return "Empty";
350
+ const lines = body.split("\n").length;
351
+ return `${lines} line${lines === 1 ? "" : "s"}`;
352
+ }, [draft.body]);
353
+
354
+ /**
355
+ * The body as a tree, or null when it can't be one (empty, or not JSON).
356
+ * Memoized on the raw string — an override body can be large.
357
+ */
358
+ const parsedBody = useMemo(() => {
359
+ const body = (draft.body ?? "").trim();
360
+ if (!body) return null;
361
+ try {
362
+ return JSON.parse(body);
363
+ } catch {
364
+ return null;
365
+ }
366
+ }, [draft.body]);
367
+ const bodyIsNonJsonText = (draft.body ?? "").trim().length > 0 && !parsedBody;
368
+ const delayMs = draft.delayMs ?? 0;
369
+ /** Custom fields own the value only when no preset chip already claims it. */
370
+ const customDelayActive = delayMs > 0 && !DELAY_PRESETS.includes(delayMs);
371
+ const customTimesActive = !draft.alternate && typeof draft.times === "number" && draft.times !== 1;
372
+ return /*#__PURE__*/_jsxs("div", {
373
+ style: styles.root,
374
+ children: [/*#__PURE__*/_jsx("div", {
375
+ style: styles.scroll,
376
+ children: /*#__PURE__*/_jsxs("div", {
377
+ style: styles.scrollContent,
378
+ children: [/*#__PURE__*/_jsxs(Disclosure, {
379
+ title: "Applies to",
380
+ summary: scopeSummary,
381
+ open: scopeOpen,
382
+ onToggle: () => setScopeOpen(v => !v),
383
+ children: [/*#__PURE__*/_jsx("textarea", {
384
+ style: styles.input,
385
+ value: draft.urlPattern,
386
+ onChange: e => patch({
387
+ urlPattern: e.target.value
388
+ }),
389
+ placeholder: "https://api.example.com/v1/users*",
390
+ autoCapitalize: "none",
391
+ autoCorrect: "off",
392
+ spellCheck: false,
393
+ rows: 2,
394
+ "aria-label": "URL pattern"
395
+ }), /*#__PURE__*/_jsxs("div", {
396
+ style: styles.chipRow,
397
+ children: [/*#__PURE__*/_jsx(Chip, {
398
+ label: "Any",
399
+ active: !method,
400
+ onPress: () => patch({
401
+ methods: undefined
402
+ })
403
+ }), METHODS.map(m => /*#__PURE__*/_jsx(Chip, {
404
+ label: m,
405
+ active: method === m,
406
+ onPress: () => patch({
407
+ methods: [m]
408
+ })
409
+ }, m))]
410
+ })]
411
+ }), /*#__PURE__*/_jsxs("div", {
412
+ style: styles.outcomeGrid,
413
+ children: [OUTCOMES.map(outcome => /*#__PURE__*/_jsx(OutcomeChip, {
414
+ outcome: outcome,
415
+ active: activeOutcome === outcome.id,
416
+ onPress: () => patch(outcome.patch)
417
+ }, outcome.id)), /*#__PURE__*/_jsx("button", {
418
+ type: "button",
419
+ onClick: () => setPrompt("status"),
420
+ "aria-label": "Custom status code",
421
+ style: {
422
+ ...styles.customField,
423
+ ...(customStatusActive ? styles.customFieldActive : null)
424
+ },
425
+ children: /*#__PURE__*/_jsx("span", {
426
+ style: {
427
+ ...styles.customValue,
428
+ ...(customStatusActive ? styles.customInputActive : null)
429
+ },
430
+ children: customStatusActive ? String(draft.status) : "Custom"
431
+ })
432
+ })]
433
+ }), showsBody ? /*#__PURE__*/_jsx(Disclosure, {
434
+ title: "Response body",
435
+ summary: bodySummary,
436
+ open: bodyOpen,
437
+ onToggle: () => setBodyOpen(v => !v),
438
+ children: pasteText !== null ?
439
+ /*#__PURE__*/
440
+ /* Replace-the-whole-thing mode. Starts empty — see `pasteText`. */
441
+ _jsxs(_Fragment, {
442
+ children: [/*#__PURE__*/_jsx("textarea", {
443
+ style: {
444
+ ...styles.input,
445
+ ...styles.bodyInput
446
+ },
447
+ value: pasteText,
448
+ onChange: e => setPasteText(e.target.value),
449
+ placeholder: "Paste a new body\u2026",
450
+ autoCapitalize: "none",
451
+ autoCorrect: "off",
452
+ spellCheck: false,
453
+ autoFocus: true,
454
+ "aria-label": "New response body"
455
+ }), /*#__PURE__*/_jsxs("div", {
456
+ style: styles.inlineActions,
457
+ children: [/*#__PURE__*/_jsx("button", {
458
+ type: "button",
459
+ onClick: () => setPasteText(null),
460
+ style: styles.textButton,
461
+ children: /*#__PURE__*/_jsx("span", {
462
+ style: styles.textButtonMuted,
463
+ children: "Cancel"
464
+ })
465
+ }), /*#__PURE__*/_jsx("button", {
466
+ type: "button",
467
+ onClick: () => {
468
+ const next = pasteText.trim();
469
+ // Pretty-print JSON on the way in so the tree it
470
+ // becomes is readable; anything else is kept exactly
471
+ // as pasted.
472
+ try {
473
+ patch({
474
+ body: JSON.stringify(JSON.parse(next), null, 2)
475
+ });
476
+ } catch {
477
+ patch({
478
+ body: next
479
+ });
480
+ }
481
+ setPasteText(null);
482
+ },
483
+ disabled: !pasteText.trim(),
484
+ style: styles.textButton,
485
+ children: /*#__PURE__*/_jsx("span", {
486
+ style: {
487
+ ...styles.textButtonLabel,
488
+ ...(!pasteText.trim() ? styles.textButtonDisabled : null)
489
+ },
490
+ children: "Replace"
491
+ })
492
+ })]
493
+ })]
494
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
495
+ children: [parsedBody !== null ?
496
+ /*#__PURE__*/
497
+ /* The SAME tree as the request detail — type into a value
498
+ to edit it. Editing writes the whole document back as
499
+ the rule's body, so the two surfaces behave
500
+ identically. */
501
+ _jsx(ResponseEditorContext.Provider, {
502
+ value: {
503
+ root: parsedBody,
504
+ onChange: next => patch({
505
+ body: JSON.stringify(next, null, 2)
506
+ })
507
+ },
508
+ children: /*#__PURE__*/_jsx(Explorer, {
509
+ editable: true,
510
+ label: "",
511
+ value: parsedBody,
512
+ defaultExpanded: [""]
513
+ })
514
+ }) : /*#__PURE__*/_jsx("span", {
515
+ style: styles.emptyBody,
516
+ children: bodyIsNonJsonText ? "This body isn't JSON, so there's no tree to show." : "No body yet."
517
+ }), /*#__PURE__*/_jsx("button", {
518
+ type: "button",
519
+ onClick: () => setPasteText(""),
520
+ style: styles.textButton,
521
+ children: /*#__PURE__*/_jsx("span", {
522
+ style: styles.textButtonLabel,
523
+ children: "Edit all"
524
+ })
525
+ })]
526
+ })
527
+ }) : null, /*#__PURE__*/_jsxs("div", {
528
+ style: styles.field,
529
+ children: [/*#__PURE__*/_jsx("span", {
530
+ style: styles.subLabel,
531
+ children: "Delay"
532
+ }), /*#__PURE__*/_jsxs("div", {
533
+ style: styles.chipRow,
534
+ children: [DELAY_PRESETS.map(ms => /*#__PURE__*/_jsx(Chip, {
535
+ label: ms === 0 ? "None" : `${ms / 1000}s`,
536
+ active: delayMs === ms,
537
+ onPress: () => patch({
538
+ delayMs: ms
539
+ })
540
+ }, ms)), /*#__PURE__*/_jsx("button", {
541
+ type: "button",
542
+ onClick: () => setPrompt("delay"),
543
+ "aria-label": "Custom delay in seconds",
544
+ style: {
545
+ ...styles.customField,
546
+ ...(customDelayActive ? styles.customFieldActive : null)
547
+ },
548
+ children: /*#__PURE__*/_jsx("span", {
549
+ style: {
550
+ ...styles.customValue,
551
+ ...(customDelayActive ? styles.customInputActive : null)
552
+ },
553
+ children: customDelayActive ? `${delayMs / 1000}s` : "Custom"
554
+ })
555
+ })]
556
+ })]
557
+ }), /*#__PURE__*/_jsxs("div", {
558
+ style: styles.field,
559
+ children: [/*#__PURE__*/_jsx("span", {
560
+ style: styles.subLabel,
561
+ children: "Fires"
562
+ }), /*#__PURE__*/_jsx("span", {
563
+ style: styles.subHint,
564
+ children: "A limited rule switches itself off once it's used up. Every other alternates forever \u2014 the first request through, the next overridden."
565
+ }), /*#__PURE__*/_jsxs("div", {
566
+ style: styles.chipRow,
567
+ children: [/*#__PURE__*/_jsx(Chip, {
568
+ label: "Always",
569
+ active: !draft.alternate && draft.times == null,
570
+ onPress: () => patch({
571
+ alternate: false,
572
+ times: null
573
+ })
574
+ }), /*#__PURE__*/_jsx(Chip, {
575
+ label: "Once",
576
+ active: !draft.alternate && draft.times === 1,
577
+ onPress: () => patch({
578
+ alternate: false,
579
+ times: 1
580
+ })
581
+ }), /*#__PURE__*/_jsx(Chip, {
582
+ label: "Every other",
583
+ active: draft.alternate === true,
584
+ onPress: () => patch({
585
+ alternate: true,
586
+ times: null
587
+ })
588
+ }), /*#__PURE__*/_jsx("button", {
589
+ type: "button",
590
+ onClick: () => {
591
+ patch({
592
+ alternate: false
593
+ });
594
+ setPrompt("times");
595
+ },
596
+ "aria-label": "Custom number of times",
597
+ style: {
598
+ ...styles.customField,
599
+ ...(customTimesActive ? styles.customFieldActive : null)
600
+ },
601
+ children: /*#__PURE__*/_jsx("span", {
602
+ style: {
603
+ ...styles.customValue,
604
+ ...(customTimesActive ? styles.customInputActive : null)
605
+ },
606
+ children: customTimesActive ? `${draft.times}×` : "Custom"
607
+ })
608
+ })]
609
+ })]
610
+ })]
611
+ })
612
+ }), prompt === "status" ? /*#__PURE__*/_jsx(NetworkNumberPrompt, {
613
+ title: "Custom status code",
614
+ hint: `${STATUS_MIN}–${STATUS_MAX}`,
615
+ value: customStatusActive ? draft.status : undefined,
616
+ min: STATUS_MIN,
617
+ max: STATUS_MAX,
618
+ onSubmit: next => {
619
+ patch({
620
+ kind: "respond",
621
+ status: next ?? undefined,
622
+ statusText: undefined
623
+ });
624
+ setPrompt(null);
625
+ },
626
+ onCancel: () => setPrompt(null)
627
+ }) : null, prompt === "delay" ? /*#__PURE__*/_jsx(NetworkNumberPrompt, {
628
+ title: "Custom delay",
629
+ hint: "Seconds",
630
+ value: customDelayActive ? delayMs / 1000 : undefined,
631
+ min: 0,
632
+ onSubmit: next => {
633
+ patch({
634
+ delayMs: next != null ? Math.round(next * 1000) : 0
635
+ });
636
+ setPrompt(null);
637
+ },
638
+ onCancel: () => setPrompt(null)
639
+ }) : null, prompt === "times" ? /*#__PURE__*/_jsx(NetworkNumberPrompt, {
640
+ title: "Fires how many times",
641
+ hint: "Leave empty for always",
642
+ value: customTimesActive ? draft.times : undefined,
643
+ min: 1,
644
+ onSubmit: next => {
645
+ // Empty or zero means "no limit" rather than a rule that is spent
646
+ // before it ever fires.
647
+ patch({
648
+ alternate: false,
649
+ times: next && next > 0 ? next : null
650
+ });
651
+ setPrompt(null);
652
+ },
653
+ onCancel: () => setPrompt(null)
654
+ }) : null, /*#__PURE__*/_jsxs("div", {
655
+ style: styles.footer,
656
+ children: [/*#__PURE__*/_jsx("button", {
657
+ type: "button",
658
+ onClick: onCancel,
659
+ style: {
660
+ ...styles.footerButton,
661
+ ...styles.cancelButton
662
+ },
663
+ children: /*#__PURE__*/_jsx("span", {
664
+ style: styles.cancelText,
665
+ children: "Cancel"
666
+ })
667
+ }), /*#__PURE__*/_jsx("button", {
668
+ type: "button",
669
+ onClick: onSave,
670
+ disabled: !draft.urlPattern.trim(),
671
+ style: {
672
+ ...styles.footerButton,
673
+ ...styles.saveButton,
674
+ ...(!draft.urlPattern.trim() ? styles.saveButtonDisabled : null)
675
+ },
676
+ children: /*#__PURE__*/_jsx("span", {
677
+ style: styles.saveText,
678
+ children: saveLabel
679
+ })
680
+ })]
681
+ })]
682
+ });
683
+ }
684
+
685
+ // Values mirror the native StyleSheet block 1:1.
686
+ const styles = {
687
+ root: {
688
+ position: "relative",
689
+ display: "flex",
690
+ flexDirection: "column",
691
+ flex: 1,
692
+ minHeight: 0
693
+ },
694
+ scroll: {
695
+ flex: 1,
696
+ minHeight: 0,
697
+ overflowY: "auto"
698
+ },
699
+ scrollContent: {
700
+ padding: "12px 12px 24px",
701
+ display: "flex",
702
+ flexDirection: "column",
703
+ gap: 4
704
+ },
705
+ field: {
706
+ display: "flex",
707
+ flexDirection: "column",
708
+ gap: 8,
709
+ padding: "6px 0"
710
+ },
711
+ outcomeGrid: {
712
+ display: "flex",
713
+ flexDirection: "row",
714
+ flexWrap: "wrap",
715
+ gap: 6,
716
+ padding: "10px 0"
717
+ },
718
+ outcome: {
719
+ display: "inline-flex",
720
+ flexDirection: "row",
721
+ alignItems: "center",
722
+ gap: 6,
723
+ padding: "9px 10px",
724
+ borderRadius: 6,
725
+ border: `1px solid ${macOSColors.border.default}`,
726
+ backgroundColor: macOSColors.background.card,
727
+ cursor: "pointer",
728
+ margin: 0,
729
+ boxSizing: "border-box"
730
+ },
731
+ outcomeLabel: {
732
+ fontSize: 12,
733
+ fontWeight: 600,
734
+ color: macOSColors.text.primary
735
+ },
736
+ outcomeCode: {
737
+ fontSize: 10,
738
+ fontWeight: 700,
739
+ color: macOSColors.text.muted
740
+ },
741
+ disclosure: {
742
+ borderRadius: 6,
743
+ border: `1px solid ${macOSColors.border.default}`,
744
+ backgroundColor: macOSColors.background.card,
745
+ overflow: "hidden"
746
+ },
747
+ disclosureHeader: {
748
+ display: "flex",
749
+ flexDirection: "row",
750
+ alignItems: "center",
751
+ gap: 6,
752
+ padding: "10px 10px",
753
+ width: "100%",
754
+ boxSizing: "border-box",
755
+ background: "none",
756
+ border: "none",
757
+ cursor: "pointer",
758
+ margin: 0,
759
+ textAlign: "left",
760
+ font: "inherit"
761
+ },
762
+ disclosureTitle: {
763
+ fontSize: 12,
764
+ fontWeight: 600,
765
+ color: macOSColors.text.primary,
766
+ whiteSpace: "nowrap"
767
+ },
768
+ disclosureSummary: {
769
+ flex: 1,
770
+ minWidth: 0,
771
+ textAlign: "right",
772
+ fontSize: 11,
773
+ color: macOSColors.text.muted,
774
+ whiteSpace: "nowrap",
775
+ overflow: "hidden",
776
+ textOverflow: "ellipsis"
777
+ },
778
+ disclosureBody: {
779
+ padding: "0 10px 10px",
780
+ display: "flex",
781
+ flexDirection: "column",
782
+ gap: 8
783
+ },
784
+ input: {
785
+ backgroundColor: macOSColors.background.input,
786
+ border: `1px solid ${macOSColors.border.input}`,
787
+ borderRadius: 6,
788
+ padding: "8px 10px",
789
+ color: macOSColors.text.primary,
790
+ fontSize: 12,
791
+ fontFamily: "ui-monospace, Menlo, monospace",
792
+ minHeight: 40,
793
+ outline: "none",
794
+ resize: "vertical",
795
+ boxSizing: "border-box",
796
+ width: "100%"
797
+ },
798
+ bodyInput: {
799
+ minHeight: 140
800
+ },
801
+ chipRow: {
802
+ display: "flex",
803
+ flexDirection: "row",
804
+ flexWrap: "wrap",
805
+ gap: 6
806
+ },
807
+ chip: {
808
+ padding: "6px 10px",
809
+ borderRadius: 5,
810
+ border: `1px solid ${macOSColors.border.default}`,
811
+ backgroundColor: macOSColors.background.base,
812
+ cursor: "pointer",
813
+ margin: 0,
814
+ boxSizing: "border-box"
815
+ },
816
+ chipActive: {
817
+ border: `1px solid ${macOSColors.semantic.info}`,
818
+ backgroundColor: `${macOSColors.semantic.info}1F`
819
+ },
820
+ chipText: {
821
+ fontSize: 11,
822
+ fontWeight: 600,
823
+ color: macOSColors.text.secondary
824
+ },
825
+ chipTextActive: {
826
+ color: macOSColors.semantic.info
827
+ },
828
+ customField: {
829
+ display: "inline-flex",
830
+ flexDirection: "row",
831
+ alignItems: "center",
832
+ padding: "0 8px",
833
+ borderRadius: 5,
834
+ border: `1px solid ${macOSColors.border.default}`,
835
+ backgroundColor: macOSColors.background.base,
836
+ cursor: "pointer",
837
+ margin: 0,
838
+ boxSizing: "border-box"
839
+ },
840
+ customFieldActive: {
841
+ border: `1px solid ${macOSColors.semantic.info}`,
842
+ backgroundColor: `${macOSColors.semantic.info}1F`
843
+ },
844
+ customValue: {
845
+ padding: "6px 0",
846
+ fontSize: 11,
847
+ fontWeight: 600,
848
+ color: macOSColors.text.secondary
849
+ },
850
+ customInputActive: {
851
+ color: macOSColors.semantic.info
852
+ },
853
+ subHint: {
854
+ fontSize: 10,
855
+ lineHeight: "14px",
856
+ color: macOSColors.text.muted,
857
+ marginTop: -2
858
+ },
859
+ subLabel: {
860
+ fontSize: 10,
861
+ fontWeight: 700,
862
+ letterSpacing: 0.6,
863
+ color: macOSColors.text.muted,
864
+ textTransform: "uppercase"
865
+ },
866
+ textButton: {
867
+ alignSelf: "flex-start",
868
+ padding: "4px 0",
869
+ background: "none",
870
+ border: "none",
871
+ cursor: "pointer",
872
+ margin: 0
873
+ },
874
+ textButtonLabel: {
875
+ fontSize: 11,
876
+ fontWeight: 600,
877
+ color: macOSColors.semantic.info
878
+ },
879
+ textButtonMuted: {
880
+ fontSize: 11,
881
+ fontWeight: 600,
882
+ color: macOSColors.text.secondary
883
+ },
884
+ textButtonDisabled: {
885
+ opacity: 0.4
886
+ },
887
+ inlineActions: {
888
+ display: "flex",
889
+ flexDirection: "row",
890
+ alignItems: "center",
891
+ justifyContent: "space-between"
892
+ },
893
+ emptyBody: {
894
+ fontSize: 11,
895
+ color: macOSColors.text.muted,
896
+ padding: "6px 0"
897
+ },
898
+ footer: {
899
+ display: "flex",
900
+ flexDirection: "row",
901
+ gap: 8,
902
+ padding: 12,
903
+ borderTop: `1px solid ${macOSColors.border.default}`,
904
+ backgroundColor: macOSColors.background.base
905
+ },
906
+ footerButton: {
907
+ flex: 1,
908
+ padding: "11px 0",
909
+ borderRadius: 6,
910
+ display: "inline-flex",
911
+ alignItems: "center",
912
+ justifyContent: "center",
913
+ cursor: "pointer",
914
+ margin: 0,
915
+ boxSizing: "border-box"
916
+ },
917
+ cancelButton: {
918
+ border: `1px solid ${macOSColors.border.default}`,
919
+ backgroundColor: macOSColors.background.card
920
+ },
921
+ cancelText: {
922
+ fontSize: 12,
923
+ fontWeight: 600,
924
+ color: macOSColors.text.secondary
925
+ },
926
+ saveButton: {
927
+ border: `1px solid ${macOSColors.semantic.info}`,
928
+ backgroundColor: `${macOSColors.semantic.info}22`
929
+ },
930
+ saveButtonDisabled: {
931
+ opacity: 0.4,
932
+ cursor: "default"
933
+ },
934
+ saveText: {
935
+ fontSize: 12,
936
+ fontWeight: 700,
937
+ color: macOSColors.semantic.info
938
+ }
939
+ };