@tangle-network/blueprint-ui 0.3.0 → 0.4.0

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.
@@ -0,0 +1,514 @@
1
+ // src/iframe/testing.tsx
2
+ import {
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState
8
+ } from "react";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ function mockWallet(input = {}) {
11
+ return {
12
+ address: input.address === void 0 ? "0xd8da6bf26964af9d7eed9e03e53415d37aa96045" : input.address,
13
+ chainId: input.chainId ?? 84532,
14
+ isConnected: input.isConnected ?? input.address !== null
15
+ };
16
+ }
17
+ function mockServiceContext(input = {}) {
18
+ return {
19
+ blueprintId: input.blueprintId ?? "0",
20
+ serviceId: input.serviceId === void 0 ? null : input.serviceId,
21
+ operators: input.operators ?? [
22
+ {
23
+ address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
24
+ rpcAddress: "http://localhost:8545",
25
+ status: "active"
26
+ }
27
+ ],
28
+ jobs: input.jobs ?? [
29
+ { index: 0, name: "invoke" }
30
+ ],
31
+ mode: input.mode ?? null
32
+ };
33
+ }
34
+ var TangleParentHarness = ({
35
+ appId = "harness",
36
+ wallet = mockWallet(),
37
+ service = mockServiceContext(),
38
+ onCallJob,
39
+ showDebugPanel = false,
40
+ children
41
+ }) => {
42
+ const [currentWallet, setCurrentWallet] = useState(wallet);
43
+ const [currentService, setCurrentService] = useState(service);
44
+ const [callLog, setCallLog] = useState([]);
45
+ const callJobHandler = useRef(onCallJob);
46
+ callJobHandler.current = onCallJob;
47
+ const seenHandshake = useRef(false);
48
+ useEffect(() => {
49
+ const reply = (message) => {
50
+ window.dispatchEvent(
51
+ new MessageEvent("message", {
52
+ data: message,
53
+ origin: HARNESS_ORIGIN
54
+ })
55
+ );
56
+ };
57
+ const broadcast = () => {
58
+ const broadcastMsg = {
59
+ kind: "tangle.app.serviceContext",
60
+ blueprintId: currentService.blueprintId ?? "0",
61
+ serviceId: currentService.serviceId,
62
+ operators: currentService.operators,
63
+ jobs: currentService.jobs,
64
+ mode: currentService.mode
65
+ };
66
+ reply(broadcastMsg);
67
+ reply({
68
+ kind: "tangle.app.accountChanged",
69
+ account: currentWallet.address
70
+ });
71
+ if (currentWallet.chainId !== null) {
72
+ reply({
73
+ kind: "tangle.app.chainChanged",
74
+ chainId: currentWallet.chainId
75
+ });
76
+ }
77
+ };
78
+ const handler = async (event) => {
79
+ if (event.origin === HARNESS_ORIGIN) return;
80
+ const data = event.data;
81
+ if (typeof data !== "object" || data === null) return;
82
+ const message = data;
83
+ switch (message.kind) {
84
+ case "tangle.app.handshake": {
85
+ if (!seenHandshake.current) {
86
+ seenHandshake.current = true;
87
+ reply({
88
+ kind: "tangle.app.handshakeAck",
89
+ appId,
90
+ protocolVersion: "1"
91
+ });
92
+ broadcast();
93
+ }
94
+ return;
95
+ }
96
+ case "tangle.app.readAccount": {
97
+ if (typeof message.correlationId !== "string") return;
98
+ reply({
99
+ kind: "tangle.app.readAccountResult",
100
+ correlationId: message.correlationId,
101
+ ok: true,
102
+ data: {
103
+ account: currentWallet.address ?? "0x0000000000000000000000000000000000000000",
104
+ chainId: currentWallet.chainId ?? 0
105
+ }
106
+ });
107
+ return;
108
+ }
109
+ case "tangle.app.callJob": {
110
+ if (typeof message.correlationId !== "string") return;
111
+ const request = message;
112
+ setCallLog((prev) => [...prev, request]);
113
+ const handler2 = callJobHandler.current;
114
+ if (!handler2) {
115
+ const result = {
116
+ kind: "tangle.app.jobResult",
117
+ correlationId: request.correlationId,
118
+ status: "success",
119
+ data: { echo: request.inputs }
120
+ };
121
+ reply(result);
122
+ return;
123
+ }
124
+ try {
125
+ const outcome = await handler2(request);
126
+ for (const chunk of outcome.chunks ?? []) {
127
+ reply({
128
+ kind: "tangle.app.jobResult",
129
+ correlationId: request.correlationId,
130
+ status: "streaming",
131
+ chunk
132
+ });
133
+ }
134
+ reply({
135
+ kind: "tangle.app.jobResult",
136
+ correlationId: request.correlationId,
137
+ status: outcome.status,
138
+ ...outcome.data !== void 0 ? { data: outcome.data } : {},
139
+ ...outcome.error !== void 0 ? { error: outcome.error } : {}
140
+ });
141
+ } catch (err) {
142
+ reply({
143
+ kind: "tangle.app.jobResult",
144
+ correlationId: request.correlationId,
145
+ status: "error",
146
+ error: err instanceof Error ? err.message : String(err)
147
+ });
148
+ }
149
+ return;
150
+ }
151
+ // Wallet ops respond optimistically — tests that want to assert
152
+ // specific signatures should pre-set them via the dev handler.
153
+ case "tangle.app.signMessage": {
154
+ if (typeof message.correlationId !== "string") return;
155
+ reply({
156
+ kind: "tangle.app.signMessageResult",
157
+ correlationId: message.correlationId,
158
+ ok: true,
159
+ data: { signature: "0xdeadbeef" }
160
+ });
161
+ return;
162
+ }
163
+ case "tangle.app.signTransaction": {
164
+ if (typeof message.correlationId !== "string") return;
165
+ reply({
166
+ kind: "tangle.app.signTransactionResult",
167
+ correlationId: message.correlationId,
168
+ ok: true,
169
+ data: { txHash: "0x" + "00".repeat(32) }
170
+ });
171
+ return;
172
+ }
173
+ case "tangle.app.switchChain": {
174
+ if (typeof message.correlationId !== "string" || typeof message.chainId !== "number") {
175
+ return;
176
+ }
177
+ const chainId = message.chainId;
178
+ setCurrentWallet((w) => ({ ...w, chainId }));
179
+ reply({
180
+ kind: "tangle.app.switchChainResult",
181
+ correlationId: message.correlationId,
182
+ ok: true,
183
+ data: { chainId }
184
+ });
185
+ return;
186
+ }
187
+ }
188
+ };
189
+ window.addEventListener("message", handler);
190
+ return () => window.removeEventListener("message", handler);
191
+ }, [appId, currentWallet, currentService]);
192
+ useEffect(() => {
193
+ if (!seenHandshake.current) return;
194
+ window.dispatchEvent(
195
+ new MessageEvent("message", {
196
+ data: {
197
+ kind: "tangle.app.accountChanged",
198
+ account: currentWallet.address
199
+ },
200
+ origin: HARNESS_ORIGIN
201
+ })
202
+ );
203
+ }, [currentWallet.address]);
204
+ useEffect(() => {
205
+ if (!seenHandshake.current || currentWallet.chainId === null) return;
206
+ window.dispatchEvent(
207
+ new MessageEvent("message", {
208
+ data: {
209
+ kind: "tangle.app.chainChanged",
210
+ chainId: currentWallet.chainId
211
+ },
212
+ origin: HARNESS_ORIGIN
213
+ })
214
+ );
215
+ }, [currentWallet.chainId]);
216
+ useEffect(() => {
217
+ if (!seenHandshake.current) return;
218
+ window.dispatchEvent(
219
+ new MessageEvent("message", {
220
+ data: {
221
+ kind: "tangle.app.serviceContext",
222
+ blueprintId: currentService.blueprintId ?? "0",
223
+ serviceId: currentService.serviceId,
224
+ operators: currentService.operators,
225
+ jobs: currentService.jobs,
226
+ mode: currentService.mode
227
+ },
228
+ origin: HARNESS_ORIGIN
229
+ })
230
+ );
231
+ }, [currentService]);
232
+ const debugApi = useMemo(
233
+ () => ({
234
+ setWallet: setCurrentWallet,
235
+ setService: setCurrentService,
236
+ callLog
237
+ }),
238
+ [callLog]
239
+ );
240
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
241
+ children,
242
+ showDebugPanel && /* @__PURE__ */ jsx(DebugPanel, { api: debugApi })
243
+ ] });
244
+ };
245
+ var HARNESS_ORIGIN = "harness://tangle.local";
246
+ var DebugPanel = ({ api }) => {
247
+ const [open, setOpen] = useState(true);
248
+ const [tab, setTab] = useState("wallet");
249
+ if (!open) {
250
+ return /* @__PURE__ */ jsx(
251
+ "button",
252
+ {
253
+ type: "button",
254
+ onClick: () => setOpen(true),
255
+ style: debugStyles.collapsedTrigger,
256
+ children: "Debug"
257
+ }
258
+ );
259
+ }
260
+ return /* @__PURE__ */ jsxs("div", { style: debugStyles.panel, children: [
261
+ /* @__PURE__ */ jsxs("header", { style: debugStyles.header, children: [
262
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: 11 }, children: "TANGLE DEV HARNESS" }),
263
+ /* @__PURE__ */ jsx(
264
+ "button",
265
+ {
266
+ type: "button",
267
+ onClick: () => setOpen(false),
268
+ style: debugStyles.closeButton,
269
+ "aria-label": "Close debug panel",
270
+ children: "\xD7"
271
+ }
272
+ )
273
+ ] }),
274
+ /* @__PURE__ */ jsx("nav", { style: debugStyles.tabs, children: ["wallet", "service", "log"].map((t) => /* @__PURE__ */ jsx(
275
+ "button",
276
+ {
277
+ type: "button",
278
+ onClick: () => setTab(t),
279
+ style: {
280
+ ...debugStyles.tab,
281
+ ...tab === t ? debugStyles.tabActive : {}
282
+ },
283
+ children: t
284
+ },
285
+ t
286
+ )) }),
287
+ /* @__PURE__ */ jsxs("div", { style: debugStyles.body, children: [
288
+ tab === "wallet" && /* @__PURE__ */ jsx(WalletTab, { api }),
289
+ tab === "service" && /* @__PURE__ */ jsx(ServiceTab, { api }),
290
+ tab === "log" && /* @__PURE__ */ jsx(CallLogTab, { callLog: api.callLog })
291
+ ] })
292
+ ] });
293
+ };
294
+ var WalletTab = ({ api }) => {
295
+ const [address, setAddressInput] = useState(
296
+ "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
297
+ );
298
+ const [chainId, setChainIdInput] = useState("84532");
299
+ const applyConnect = useCallback(() => {
300
+ api.setWallet({
301
+ address,
302
+ chainId: Number(chainId) || null,
303
+ isConnected: true
304
+ });
305
+ }, [address, chainId, api]);
306
+ const disconnect = useCallback(() => {
307
+ api.setWallet({ address: null, chainId: null, isConnected: false });
308
+ }, [api]);
309
+ return /* @__PURE__ */ jsxs("div", { children: [
310
+ /* @__PURE__ */ jsx("label", { style: debugStyles.label, children: "address" }),
311
+ /* @__PURE__ */ jsx(
312
+ "input",
313
+ {
314
+ value: address,
315
+ onChange: (e) => setAddressInput(e.target.value),
316
+ style: debugStyles.input
317
+ }
318
+ ),
319
+ /* @__PURE__ */ jsx("label", { style: debugStyles.label, children: "chain id" }),
320
+ /* @__PURE__ */ jsx(
321
+ "input",
322
+ {
323
+ value: chainId,
324
+ onChange: (e) => setChainIdInput(e.target.value),
325
+ style: debugStyles.input
326
+ }
327
+ ),
328
+ /* @__PURE__ */ jsxs("div", { style: debugStyles.buttonRow, children: [
329
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: applyConnect, style: debugStyles.primary, children: "Set connected" }),
330
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: disconnect, style: debugStyles.secondary, children: "Disconnect" })
331
+ ] })
332
+ ] });
333
+ };
334
+ var ServiceTab = ({ api }) => {
335
+ const [serviceId, setServiceIdInput] = useState("1");
336
+ const [blueprintId, setBlueprintIdInput] = useState("0");
337
+ const apply = useCallback(() => {
338
+ api.setService((prev) => ({
339
+ ...prev,
340
+ serviceId: serviceId || null,
341
+ blueprintId
342
+ }));
343
+ }, [api, serviceId, blueprintId]);
344
+ const clearService = useCallback(() => {
345
+ api.setService((prev) => ({ ...prev, serviceId: null }));
346
+ }, [api]);
347
+ return /* @__PURE__ */ jsxs("div", { children: [
348
+ /* @__PURE__ */ jsx("label", { style: debugStyles.label, children: "blueprint id" }),
349
+ /* @__PURE__ */ jsx(
350
+ "input",
351
+ {
352
+ value: blueprintId,
353
+ onChange: (e) => setBlueprintIdInput(e.target.value),
354
+ style: debugStyles.input
355
+ }
356
+ ),
357
+ /* @__PURE__ */ jsx("label", { style: debugStyles.label, children: "service id (empty = not deployed)" }),
358
+ /* @__PURE__ */ jsx(
359
+ "input",
360
+ {
361
+ value: serviceId,
362
+ onChange: (e) => setServiceIdInput(e.target.value),
363
+ style: debugStyles.input
364
+ }
365
+ ),
366
+ /* @__PURE__ */ jsxs("div", { style: debugStyles.buttonRow, children: [
367
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: apply, style: debugStyles.primary, children: "Apply" }),
368
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: clearService, style: debugStyles.secondary, children: "Clear service" })
369
+ ] })
370
+ ] });
371
+ };
372
+ var CallLogTab = ({ callLog }) => {
373
+ if (callLog.length === 0) {
374
+ return /* @__PURE__ */ jsx("p", { style: debugStyles.empty, children: "No callJob requests yet." });
375
+ }
376
+ return /* @__PURE__ */ jsx("ol", { style: debugStyles.log, children: callLog.map((entry) => /* @__PURE__ */ jsxs("li", { style: debugStyles.logEntry, children: [
377
+ /* @__PURE__ */ jsxs("strong", { children: [
378
+ "job ",
379
+ entry.jobIndex
380
+ ] }),
381
+ /* @__PURE__ */ jsx("pre", { style: debugStyles.pre, children: JSON.stringify(entry.inputs, null, 2) })
382
+ ] }, entry.correlationId)) });
383
+ };
384
+ var debugStyles = {
385
+ panel: {
386
+ position: "fixed",
387
+ right: 12,
388
+ top: 12,
389
+ width: 280,
390
+ zIndex: 99999,
391
+ background: "#0b0b14",
392
+ color: "#fff",
393
+ border: "1px solid #3a3a52",
394
+ borderRadius: 10,
395
+ boxShadow: "0 14px 32px rgba(0,0,0,0.4)",
396
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Code", monospace',
397
+ fontSize: 12
398
+ },
399
+ header: {
400
+ display: "flex",
401
+ alignItems: "center",
402
+ justifyContent: "space-between",
403
+ padding: "8px 10px",
404
+ borderBottom: "1px solid #2a2a3e"
405
+ },
406
+ closeButton: {
407
+ background: "none",
408
+ border: "none",
409
+ color: "#fff",
410
+ fontSize: 18,
411
+ cursor: "pointer",
412
+ lineHeight: 1
413
+ },
414
+ tabs: {
415
+ display: "flex",
416
+ borderBottom: "1px solid #2a2a3e"
417
+ },
418
+ tab: {
419
+ flex: 1,
420
+ background: "none",
421
+ border: "none",
422
+ color: "#a0a0c0",
423
+ padding: "6px 8px",
424
+ cursor: "pointer",
425
+ fontSize: 11,
426
+ textTransform: "uppercase"
427
+ },
428
+ tabActive: {
429
+ color: "#fff",
430
+ borderBottom: "2px solid #818cf8"
431
+ },
432
+ body: {
433
+ padding: 10,
434
+ maxHeight: 320,
435
+ overflow: "auto"
436
+ },
437
+ label: {
438
+ display: "block",
439
+ color: "#a0a0c0",
440
+ fontSize: 10,
441
+ marginBottom: 4,
442
+ marginTop: 6,
443
+ textTransform: "uppercase"
444
+ },
445
+ input: {
446
+ width: "100%",
447
+ background: "#15152a",
448
+ border: "1px solid #2a2a3e",
449
+ color: "#fff",
450
+ padding: "6px 8px",
451
+ borderRadius: 4,
452
+ fontFamily: "inherit",
453
+ fontSize: 11,
454
+ boxSizing: "border-box"
455
+ },
456
+ buttonRow: { display: "flex", gap: 6, marginTop: 8 },
457
+ primary: {
458
+ flex: 1,
459
+ background: "#4f46e5",
460
+ color: "#fff",
461
+ border: "none",
462
+ padding: "6px 8px",
463
+ borderRadius: 4,
464
+ cursor: "pointer",
465
+ fontSize: 11,
466
+ fontFamily: "inherit"
467
+ },
468
+ secondary: {
469
+ flex: 1,
470
+ background: "transparent",
471
+ color: "#a0a0c0",
472
+ border: "1px solid #3a3a52",
473
+ padding: "6px 8px",
474
+ borderRadius: 4,
475
+ cursor: "pointer",
476
+ fontSize: 11,
477
+ fontFamily: "inherit"
478
+ },
479
+ collapsedTrigger: {
480
+ position: "fixed",
481
+ right: 12,
482
+ top: 12,
483
+ zIndex: 99999,
484
+ padding: "6px 10px",
485
+ background: "#0b0b14",
486
+ border: "1px solid #3a3a52",
487
+ color: "#fff",
488
+ borderRadius: 6,
489
+ fontFamily: "inherit",
490
+ fontSize: 11,
491
+ cursor: "pointer"
492
+ },
493
+ log: { listStyle: "none", padding: 0, margin: 0 },
494
+ logEntry: {
495
+ padding: 6,
496
+ borderBottom: "1px solid #2a2a3e",
497
+ fontSize: 11
498
+ },
499
+ pre: {
500
+ margin: "4px 0 0",
501
+ color: "#a0a0c0",
502
+ fontSize: 10,
503
+ whiteSpace: "pre-wrap",
504
+ wordBreak: "break-word"
505
+ },
506
+ empty: { color: "#a0a0c0", fontSize: 11, margin: 0 }
507
+ };
508
+ export {
509
+ HARNESS_ORIGIN,
510
+ TangleParentHarness,
511
+ mockServiceContext,
512
+ mockWallet
513
+ };
514
+ //# sourceMappingURL=testing-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/iframe/testing.tsx"],"sourcesContent":["// Testing harness for iframe blueprints. The promise of the SDK is that\n// publishers can iterate on their UI without running the Tangle Cloud dapp\n// — these utilities are what makes that true.\n\nimport {\n type FC,\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport type { Address } from 'viem';\n\nimport type {\n ServiceSnapshot,\n WalletSnapshot,\n} from './tangleIframeClient';\nimport type {\n CallJobRequest,\n JobInputs,\n JobResultEvent,\n ParentMessage,\n ServiceContextBroadcast,\n ServiceContextJob,\n ServiceContextOperator,\n} from '../wallet/parentBridgeProtocol';\n\nexport type MockWalletInput = Partial<{\n address: Address | null;\n chainId: number;\n isConnected: boolean;\n}>;\n\nexport type MockServiceInput = Partial<{\n blueprintId: string;\n serviceId: string | null;\n operators: readonly ServiceContextOperator[];\n jobs: readonly ServiceContextJob[];\n mode: string | null;\n}>;\n\n/**\n * Construct a deterministic wallet snapshot for tests. Defaults:\n * connected, vitalik.eth's address, Base Sepolia (84532).\n */\nexport function mockWallet(input: MockWalletInput = {}): WalletSnapshot {\n return {\n address:\n input.address === undefined\n ? '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'\n : input.address,\n chainId: input.chainId ?? 84532,\n isConnected: input.isConnected ?? input.address !== null,\n };\n}\n\n/**\n * Construct a deterministic service snapshot for tests. Defaults: blueprint\n * id `0`, no service deployed yet (serviceId null), single mock operator on\n * the canonical local sidecar URL.\n */\nexport function mockServiceContext(\n input: MockServiceInput = {},\n): ServiceSnapshot {\n return {\n blueprintId: input.blueprintId ?? '0',\n serviceId: input.serviceId === undefined ? null : input.serviceId,\n operators:\n input.operators ?? [\n {\n address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',\n rpcAddress: 'http://localhost:8545',\n status: 'active',\n },\n ],\n jobs:\n input.jobs ?? [\n { index: 0, name: 'invoke' },\n ],\n mode: input.mode ?? null,\n };\n}\n\nexport type CallJobHandler = (\n request: CallJobRequest,\n) => Promise<{\n status: 'success' | 'error';\n data?: unknown;\n error?: string;\n /** Streaming chunks emitted in order before the terminal status. */\n chunks?: readonly unknown[];\n}>;\n\ntype HarnessProps = {\n appId?: string;\n wallet?: WalletSnapshot;\n service?: ServiceSnapshot;\n /** Override callJob behavior. Default: returns a static `{ ok: true }`. */\n onCallJob?: CallJobHandler;\n /** Surface a floating debug panel that lets the developer flip state at runtime. */\n showDebugPanel?: boolean;\n children: ReactNode;\n};\n\n/**\n * Drop-in parent simulator for tests + storybook + standalone dev. Wraps\n * children in a fake parent that:\n *\n * - Acks the iframe's handshake immediately\n * - Broadcasts the configured wallet + service context on mount\n * - Intercepts `callJob` requests and routes them through `onCallJob`\n * - (Optional) Mounts a floating debug panel so the developer can\n * mutate state at runtime: change account, switch chain, set\n * serviceId, fire a custom job\n *\n * The harness runs in the same JS context as the iframe app — there's no\n * cross-frame postMessage, just same-window event dispatch. That keeps it\n * fully synchronous + assertable, but the messages still flow through the\n * exact same protocol surface the production bridge uses.\n *\n * Usage:\n *\n * <TangleParentHarness wallet={mockWallet()} service={mockServiceContext()}>\n * <TangleIframeProvider appId=\"my-app\" mode=\"bridge\" parentOrigin=\"harness://\">\n * <App />\n * </TangleIframeProvider>\n * </TangleParentHarness>\n *\n * Set `mode=\"bridge\"` + `parentOrigin=\"harness://\"` on the provider so it\n * matches the harness's synthetic origin. In production, use `mode=\"auto\"`\n * (the default).\n */\nexport const TangleParentHarness: FC<HarnessProps> = ({\n appId = 'harness',\n wallet = mockWallet(),\n service = mockServiceContext(),\n onCallJob,\n showDebugPanel = false,\n children,\n}) => {\n const [currentWallet, setCurrentWallet] = useState<WalletSnapshot>(wallet);\n const [currentService, setCurrentService] =\n useState<ServiceSnapshot>(service);\n const [callLog, setCallLog] = useState<CallJobRequest[]>([]);\n const callJobHandler = useRef<CallJobHandler | undefined>(onCallJob);\n callJobHandler.current = onCallJob;\n const seenHandshake = useRef(false);\n\n // Listen for iframe → \"parent\" messages. Since the harness shares the\n // window, `window.postMessage` with the synthetic origin is the easiest\n // wire — the iframe SDK posts to `window.parent`, which in same-window\n // mode IS this listener.\n useEffect(() => {\n const reply = (message: ParentMessage) => {\n window.dispatchEvent(\n new MessageEvent('message', {\n data: message,\n origin: HARNESS_ORIGIN,\n }),\n );\n };\n\n const broadcast = () => {\n const broadcastMsg: ServiceContextBroadcast = {\n kind: 'tangle.app.serviceContext',\n blueprintId: currentService.blueprintId ?? '0',\n serviceId: currentService.serviceId,\n operators: currentService.operators,\n jobs: currentService.jobs,\n mode: currentService.mode,\n };\n reply(broadcastMsg);\n // Also broadcast wallet — combined into accountChanged + chainChanged.\n reply({\n kind: 'tangle.app.accountChanged',\n account: currentWallet.address,\n });\n if (currentWallet.chainId !== null) {\n reply({\n kind: 'tangle.app.chainChanged',\n chainId: currentWallet.chainId,\n });\n }\n };\n\n const handler = async (event: MessageEvent) => {\n // The iframe posts via `window.parent.postMessage(msg, parentOrigin)`.\n // In same-window mode, that fires a message event on this same window\n // with origin = parentOrigin. Filter out events the harness itself\n // dispatched (origin === HARNESS_ORIGIN) — those are replies.\n if (event.origin === HARNESS_ORIGIN) return;\n const data = event.data;\n if (typeof data !== 'object' || data === null) return;\n const message = data as { kind?: string; correlationId?: string };\n\n switch (message.kind) {\n case 'tangle.app.handshake': {\n if (!seenHandshake.current) {\n seenHandshake.current = true;\n reply({\n kind: 'tangle.app.handshakeAck',\n appId,\n protocolVersion: '1',\n });\n broadcast();\n }\n return;\n }\n case 'tangle.app.readAccount': {\n if (typeof message.correlationId !== 'string') return;\n reply({\n kind: 'tangle.app.readAccountResult',\n correlationId: message.correlationId,\n ok: true,\n data: {\n account:\n currentWallet.address ??\n ('0x0000000000000000000000000000000000000000' as Address),\n chainId: currentWallet.chainId ?? 0,\n },\n });\n return;\n }\n case 'tangle.app.callJob': {\n if (typeof message.correlationId !== 'string') return;\n const request = message as unknown as CallJobRequest;\n setCallLog((prev) => [...prev, request]);\n // Default behavior when no handler: emit a single `success` with\n // a echo of the inputs so UIs render *something* in dev mode.\n const handler = callJobHandler.current;\n if (!handler) {\n const result: JobResultEvent = {\n kind: 'tangle.app.jobResult',\n correlationId: request.correlationId,\n status: 'success',\n data: { echo: request.inputs },\n };\n reply(result);\n return;\n }\n try {\n const outcome = await handler(request);\n for (const chunk of outcome.chunks ?? []) {\n reply({\n kind: 'tangle.app.jobResult',\n correlationId: request.correlationId,\n status: 'streaming',\n chunk,\n });\n }\n reply({\n kind: 'tangle.app.jobResult',\n correlationId: request.correlationId,\n status: outcome.status,\n ...(outcome.data !== undefined ? { data: outcome.data } : {}),\n ...(outcome.error !== undefined ? { error: outcome.error } : {}),\n });\n } catch (err) {\n reply({\n kind: 'tangle.app.jobResult',\n correlationId: request.correlationId,\n status: 'error',\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return;\n }\n // Wallet ops respond optimistically — tests that want to assert\n // specific signatures should pre-set them via the dev handler.\n case 'tangle.app.signMessage': {\n if (typeof message.correlationId !== 'string') return;\n reply({\n kind: 'tangle.app.signMessageResult',\n correlationId: message.correlationId,\n ok: true,\n data: { signature: '0xdeadbeef' as `0x${string}` },\n });\n return;\n }\n case 'tangle.app.signTransaction': {\n if (typeof message.correlationId !== 'string') return;\n reply({\n kind: 'tangle.app.signTransactionResult',\n correlationId: message.correlationId,\n ok: true,\n data: { txHash: ('0x' + '00'.repeat(32)) as `0x${string}` },\n });\n return;\n }\n case 'tangle.app.switchChain': {\n if (\n typeof message.correlationId !== 'string' ||\n typeof (message as unknown as { chainId?: number }).chainId !== 'number'\n ) {\n return;\n }\n const chainId = (message as unknown as { chainId: number }).chainId;\n setCurrentWallet((w) => ({ ...w, chainId }));\n reply({\n kind: 'tangle.app.switchChainResult',\n correlationId: message.correlationId,\n ok: true,\n data: { chainId },\n });\n return;\n }\n }\n };\n window.addEventListener('message', handler);\n return () => window.removeEventListener('message', handler);\n }, [appId, currentWallet, currentService]);\n\n // Re-broadcast when state changes.\n useEffect(() => {\n if (!seenHandshake.current) return;\n window.dispatchEvent(\n new MessageEvent('message', {\n data: {\n kind: 'tangle.app.accountChanged',\n account: currentWallet.address,\n },\n origin: HARNESS_ORIGIN,\n }),\n );\n }, [currentWallet.address]);\n\n useEffect(() => {\n if (!seenHandshake.current || currentWallet.chainId === null) return;\n window.dispatchEvent(\n new MessageEvent('message', {\n data: {\n kind: 'tangle.app.chainChanged',\n chainId: currentWallet.chainId,\n },\n origin: HARNESS_ORIGIN,\n }),\n );\n }, [currentWallet.chainId]);\n\n useEffect(() => {\n if (!seenHandshake.current) return;\n window.dispatchEvent(\n new MessageEvent('message', {\n data: {\n kind: 'tangle.app.serviceContext',\n blueprintId: currentService.blueprintId ?? '0',\n serviceId: currentService.serviceId,\n operators: currentService.operators,\n jobs: currentService.jobs,\n mode: currentService.mode,\n },\n origin: HARNESS_ORIGIN,\n }),\n );\n }, [currentService]);\n\n const debugApi = useMemo(\n () => ({\n setWallet: setCurrentWallet,\n setService: setCurrentService,\n callLog,\n }),\n [callLog],\n );\n\n return (\n <>\n {children}\n {showDebugPanel && <DebugPanel api={debugApi} />}\n </>\n );\n};\n\n/**\n * Synthetic origin every harness instance uses. Stable across tests so the\n * iframe SDK + the harness can pin to the same string.\n */\nexport const HARNESS_ORIGIN = 'harness://tangle.local';\n\n// ── Debug panel ──────────────────────────────────────────────────────────────\n\nconst DebugPanel: FC<{\n api: {\n setWallet: (w: WalletSnapshot | ((prev: WalletSnapshot) => WalletSnapshot)) => void;\n setService: (\n s: ServiceSnapshot | ((prev: ServiceSnapshot) => ServiceSnapshot),\n ) => void;\n callLog: readonly CallJobRequest[];\n };\n}> = ({ api }) => {\n const [open, setOpen] = useState(true);\n const [tab, setTab] = useState<'wallet' | 'service' | 'log'>('wallet');\n if (!open) {\n return (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n style={debugStyles.collapsedTrigger}\n >\n Debug\n </button>\n );\n }\n return (\n <div style={debugStyles.panel}>\n <header style={debugStyles.header}>\n <strong style={{ fontSize: 11 }}>TANGLE DEV HARNESS</strong>\n <button\n type=\"button\"\n onClick={() => setOpen(false)}\n style={debugStyles.closeButton}\n aria-label=\"Close debug panel\"\n >\n ×\n </button>\n </header>\n <nav style={debugStyles.tabs}>\n {(['wallet', 'service', 'log'] as const).map((t) => (\n <button\n key={t}\n type=\"button\"\n onClick={() => setTab(t)}\n style={{\n ...debugStyles.tab,\n ...(tab === t ? debugStyles.tabActive : {}),\n }}\n >\n {t}\n </button>\n ))}\n </nav>\n <div style={debugStyles.body}>\n {tab === 'wallet' && <WalletTab api={api} />}\n {tab === 'service' && <ServiceTab api={api} />}\n {tab === 'log' && <CallLogTab callLog={api.callLog} />}\n </div>\n </div>\n );\n};\n\nconst WalletTab: FC<{\n api: { setWallet: (w: WalletSnapshot | ((prev: WalletSnapshot) => WalletSnapshot)) => void };\n}> = ({ api }) => {\n const [address, setAddressInput] = useState(\n '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',\n );\n const [chainId, setChainIdInput] = useState('84532');\n const applyConnect = useCallback(() => {\n api.setWallet({\n address: address as Address,\n chainId: Number(chainId) || null,\n isConnected: true,\n });\n }, [address, chainId, api]);\n const disconnect = useCallback(() => {\n api.setWallet({ address: null, chainId: null, isConnected: false });\n }, [api]);\n return (\n <div>\n <label style={debugStyles.label}>address</label>\n <input\n value={address}\n onChange={(e) => setAddressInput(e.target.value)}\n style={debugStyles.input}\n />\n <label style={debugStyles.label}>chain id</label>\n <input\n value={chainId}\n onChange={(e) => setChainIdInput(e.target.value)}\n style={debugStyles.input}\n />\n <div style={debugStyles.buttonRow}>\n <button type=\"button\" onClick={applyConnect} style={debugStyles.primary}>\n Set connected\n </button>\n <button type=\"button\" onClick={disconnect} style={debugStyles.secondary}>\n Disconnect\n </button>\n </div>\n </div>\n );\n};\n\nconst ServiceTab: FC<{\n api: {\n setService: (\n s: ServiceSnapshot | ((prev: ServiceSnapshot) => ServiceSnapshot),\n ) => void;\n };\n}> = ({ api }) => {\n const [serviceId, setServiceIdInput] = useState('1');\n const [blueprintId, setBlueprintIdInput] = useState('0');\n const apply = useCallback(() => {\n api.setService((prev) => ({\n ...prev,\n serviceId: serviceId || null,\n blueprintId,\n }));\n }, [api, serviceId, blueprintId]);\n const clearService = useCallback(() => {\n api.setService((prev) => ({ ...prev, serviceId: null }));\n }, [api]);\n return (\n <div>\n <label style={debugStyles.label}>blueprint id</label>\n <input\n value={blueprintId}\n onChange={(e) => setBlueprintIdInput(e.target.value)}\n style={debugStyles.input}\n />\n <label style={debugStyles.label}>service id (empty = not deployed)</label>\n <input\n value={serviceId}\n onChange={(e) => setServiceIdInput(e.target.value)}\n style={debugStyles.input}\n />\n <div style={debugStyles.buttonRow}>\n <button type=\"button\" onClick={apply} style={debugStyles.primary}>\n Apply\n </button>\n <button type=\"button\" onClick={clearService} style={debugStyles.secondary}>\n Clear service\n </button>\n </div>\n </div>\n );\n};\n\nconst CallLogTab: FC<{ callLog: readonly CallJobRequest[] }> = ({ callLog }) => {\n if (callLog.length === 0) {\n return <p style={debugStyles.empty}>No callJob requests yet.</p>;\n }\n return (\n <ol style={debugStyles.log}>\n {callLog.map((entry) => (\n <li key={entry.correlationId} style={debugStyles.logEntry}>\n <strong>job {entry.jobIndex}</strong>\n <pre style={debugStyles.pre}>\n {JSON.stringify(entry.inputs, null, 2)}\n </pre>\n </li>\n ))}\n </ol>\n );\n};\n\n// Inline styles keep the harness style-system-agnostic — consumers may not\n// ship Tailwind, and the panel shouldn't add a dependency.\nconst debugStyles = {\n panel: {\n position: 'fixed' as const,\n right: 12,\n top: 12,\n width: 280,\n zIndex: 99999,\n background: '#0b0b14',\n color: '#fff',\n border: '1px solid #3a3a52',\n borderRadius: 10,\n boxShadow: '0 14px 32px rgba(0,0,0,0.4)',\n fontFamily:\n 'ui-monospace, SFMono-Regular, Menlo, Monaco, \"Cascadia Code\", monospace',\n fontSize: 12,\n },\n header: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '8px 10px',\n borderBottom: '1px solid #2a2a3e',\n },\n closeButton: {\n background: 'none',\n border: 'none',\n color: '#fff',\n fontSize: 18,\n cursor: 'pointer',\n lineHeight: 1,\n },\n tabs: {\n display: 'flex',\n borderBottom: '1px solid #2a2a3e',\n },\n tab: {\n flex: 1,\n background: 'none',\n border: 'none',\n color: '#a0a0c0',\n padding: '6px 8px',\n cursor: 'pointer',\n fontSize: 11,\n textTransform: 'uppercase' as const,\n },\n tabActive: {\n color: '#fff',\n borderBottom: '2px solid #818cf8',\n },\n body: {\n padding: 10,\n maxHeight: 320,\n overflow: 'auto' as const,\n },\n label: {\n display: 'block',\n color: '#a0a0c0',\n fontSize: 10,\n marginBottom: 4,\n marginTop: 6,\n textTransform: 'uppercase' as const,\n },\n input: {\n width: '100%',\n background: '#15152a',\n border: '1px solid #2a2a3e',\n color: '#fff',\n padding: '6px 8px',\n borderRadius: 4,\n fontFamily: 'inherit',\n fontSize: 11,\n boxSizing: 'border-box' as const,\n },\n buttonRow: { display: 'flex', gap: 6, marginTop: 8 },\n primary: {\n flex: 1,\n background: '#4f46e5',\n color: '#fff',\n border: 'none',\n padding: '6px 8px',\n borderRadius: 4,\n cursor: 'pointer',\n fontSize: 11,\n fontFamily: 'inherit',\n },\n secondary: {\n flex: 1,\n background: 'transparent',\n color: '#a0a0c0',\n border: '1px solid #3a3a52',\n padding: '6px 8px',\n borderRadius: 4,\n cursor: 'pointer',\n fontSize: 11,\n fontFamily: 'inherit',\n },\n collapsedTrigger: {\n position: 'fixed' as const,\n right: 12,\n top: 12,\n zIndex: 99999,\n padding: '6px 10px',\n background: '#0b0b14',\n border: '1px solid #3a3a52',\n color: '#fff',\n borderRadius: 6,\n fontFamily: 'inherit',\n fontSize: 11,\n cursor: 'pointer',\n },\n log: { listStyle: 'none', padding: 0, margin: 0 },\n logEntry: {\n padding: 6,\n borderBottom: '1px solid #2a2a3e',\n fontSize: 11,\n },\n pre: {\n margin: '4px 0 0',\n color: '#a0a0c0',\n fontSize: 10,\n whiteSpace: 'pre-wrap' as const,\n wordBreak: 'break-word' as const,\n },\n empty: { color: '#a0a0c0', fontSize: 11, margin: 0 },\n} as const;\n\nexport type { JobInputs };\n"],"mappings":"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","names":["handler"]}