@huakirax/dsh-plugin-pocket 0.0.0-stage → 0.1.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.
Files changed (44) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +237 -2
  3. package/bin/pocket.js +31 -0
  4. package/cordis.patch.yml +6 -0
  5. package/index.js +360 -0
  6. package/lib/client.js +2453 -0
  7. package/package.json +91 -4
  8. package/src/advertise-host.js +69 -0
  9. package/src/approval-hook.js +50 -0
  10. package/src/client/ConfirmDialog.tsx +80 -0
  11. package/src/client/HostUiLauncher.tsx +101 -0
  12. package/src/client/ManagePanel.tsx +218 -0
  13. package/src/client/PhoneIcon.tsx +48 -0
  14. package/src/client/PocketSettings.tsx +13 -0
  15. package/src/client/api.ts +97 -0
  16. package/src/client/completion-sync.js +289 -0
  17. package/src/client/host-ui-styles.ts +62 -0
  18. package/src/client/index.ts +47 -0
  19. package/src/client/locales.ts +75 -0
  20. package/src/client/nav-icon.ts +31 -0
  21. package/src/client/qr.ts +15 -0
  22. package/src/client/styles.ts +76 -0
  23. package/src/completion-route.js +87 -0
  24. package/src/completion-tracker.js +95 -0
  25. package/src/devices.js +60 -0
  26. package/src/extra-ops.js +604 -0
  27. package/src/host-ui.js +466 -0
  28. package/src/instance-id.js +33 -0
  29. package/src/iroh.js +260 -0
  30. package/src/manage.js +122 -0
  31. package/src/mdns-advertise.js +75 -0
  32. package/src/office-preview.js +129 -0
  33. package/src/pairing-uri.js +42 -0
  34. package/src/pairing.js +34 -0
  35. package/src/parity-ops.js +250 -0
  36. package/src/server.js +761 -0
  37. package/src/settings-ops.js +387 -0
  38. package/src/task-events.js +77 -0
  39. package/src/task-fallback.js +177 -0
  40. package/src/token.js +18 -0
  41. package/src/transport-pref.js +47 -0
  42. package/src/trust.js +25 -0
  43. package/src/user-question-hook.js +93 -0
  44. package/src/web-route.js +116 -0
package/lib/client.js ADDED
@@ -0,0 +1,2453 @@
1
+ window.__ModuleLoader__.load({ id: "dsh-plugin-pocket", factory: (require) => {
2
+ var module = { exports: {} }; var exports = module.exports;
3
+ //#region rolldown:runtime
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
21
+ value: mod,
22
+ enumerable: true
23
+ }) : target, mod));
24
+
25
+ //#endregion
26
+ let react_jsx_runtime = require("react/jsx-runtime");
27
+ react_jsx_runtime = __toESM(react_jsx_runtime);
28
+ let react = require("react");
29
+ react = __toESM(react);
30
+ let react_dom = require("react-dom");
31
+ react_dom = __toESM(react_dom);
32
+
33
+ //#region src/client/api.ts
34
+ async function readBody(res) {
35
+ if (typeof res?.json === "function") try {
36
+ return await res.json();
37
+ } catch {}
38
+ if (typeof res?.text === "function") try {
39
+ return await res.text();
40
+ } catch {}
41
+ }
42
+ async function parse(res) {
43
+ if (!res || res.ok !== true) {
44
+ const body = await readBody(res);
45
+ const detail = body === void 0 ? `HTTP ${res?.status ?? "?"}` : typeof body === "string" ? body : JSON.stringify(body);
46
+ throw new Error(detail || `HTTP ${res?.status ?? "?"}`);
47
+ }
48
+ return await readBody(res);
49
+ }
50
+ /** Same-origin fetch helpers for the Host management routes. */
51
+ function createPocketApi(base = "") {
52
+ const get = async (path, init) => parse(await fetch(`${base}${path}`, init));
53
+ const networkPath = (path, network) => network ? `${path}?network=${network}` : path;
54
+ return {
55
+ status: (network) => get(networkPath("/pocket/status", network)),
56
+ pairStatus: (network) => get(networkPath("/pocket/pair", network)),
57
+ openPair: (network) => get(networkPath("/pocket/pair", network), { method: "POST" }),
58
+ closePair: () => get("/pocket/pair", { method: "DELETE" }),
59
+ listDevices: () => get("/pocket/devices").then((r) => r.devices),
60
+ revoke: (id) => get("/pocket/devices", {
61
+ method: "POST",
62
+ headers: { "content-type": "application/json" },
63
+ body: JSON.stringify({ id })
64
+ }),
65
+ rename: (id, name) => get("/pocket/devices", {
66
+ method: "PATCH",
67
+ headers: { "content-type": "application/json" },
68
+ body: JSON.stringify({
69
+ id,
70
+ name
71
+ })
72
+ })
73
+ };
74
+ }
75
+
76
+ //#endregion
77
+ //#region src/client/locales.ts
78
+ const zh = {
79
+ entry: "手机连接",
80
+ title: "手机连接",
81
+ statusRunning: "服务就绪",
82
+ statusStopped: "服务未就绪",
83
+ newDevice: "连接新设备",
84
+ network: "连接网络",
85
+ lan: "局域网",
86
+ tailscale: "Tailscale",
87
+ lanAddress: "局域网地址",
88
+ tailscaleAddress: "Tailscale 地址",
89
+ noNetwork: "没有可用的网络地址",
90
+ pair: "开始配对",
91
+ pairAgain: "重新配对",
92
+ pairClose: "关闭配对",
93
+ code: "配对码",
94
+ expiresIn: "{n} 秒后过期",
95
+ copy: "复制配对链接",
96
+ copyAddress: "复制地址",
97
+ copied: "已复制",
98
+ devices: "已配对设备",
99
+ empty: "暂无已配对设备",
100
+ online: "在线",
101
+ offline: "离线",
102
+ lastSeen: "上次在线",
103
+ neverSeen: "尚未连接",
104
+ rename: "重命名",
105
+ renameSave: "保存",
106
+ renameCancel: "取消",
107
+ revoke: "解除配对",
108
+ revokeTitle: "解除配对",
109
+ revokeBody: "解除与「{name}」的配对?该手机需重新配对才能连接。",
110
+ cancel: "取消",
111
+ loading: "加载中…",
112
+ refresh: "刷新"
113
+ };
114
+ const en = {
115
+ entry: "Phone connection",
116
+ title: "Phone connection",
117
+ statusRunning: "Service ready",
118
+ statusStopped: "Service unavailable",
119
+ newDevice: "Connect a device",
120
+ network: "Connection network",
121
+ lan: "LAN",
122
+ tailscale: "Tailscale",
123
+ lanAddress: "LAN address",
124
+ tailscaleAddress: "Tailscale address",
125
+ noNetwork: "No network address available",
126
+ pair: "Start pairing",
127
+ pairAgain: "Pair again",
128
+ pairClose: "Close pairing",
129
+ code: "Pairing code",
130
+ expiresIn: "Expires in {n}s",
131
+ copy: "Copy pairing link",
132
+ copyAddress: "Copy address",
133
+ copied: "Copied",
134
+ devices: "Paired devices",
135
+ empty: "No paired devices",
136
+ online: "Online",
137
+ offline: "Offline",
138
+ lastSeen: "Last online",
139
+ neverSeen: "Never connected",
140
+ rename: "Rename",
141
+ renameSave: "Save",
142
+ renameCancel: "Cancel",
143
+ revoke: "Unpair",
144
+ revokeTitle: "Unpair device",
145
+ revokeBody: "Unpair “{name}”? The phone will need to pair again to reconnect.",
146
+ cancel: "Cancel",
147
+ loading: "Loading…",
148
+ refresh: "Refresh"
149
+ };
150
+
151
+ //#endregion
152
+ //#region src/client/PhoneIcon.tsx
153
+ /** Matches dsh ui-primitives Medium outline icons: 16×16 viewBox, 1.3 stroke. */
154
+ const ICON_MEDIUM_STROKE = 1.3;
155
+ /** Markup used when swapping the shell's default gear in the nav cell. */
156
+ function phoneIconSvgHtml(className) {
157
+ return `<svg width="16" height="16" class="${className}" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" stroke-width="${ICON_MEDIUM_STROKE}" data-pocket-icon="1"><rect x="4.75" y="1.75" width="6.5" height="12.5" rx="1.25" stroke="currentColor"></rect><path d="M7 12.25h2" stroke="currentColor" stroke-linecap="round"></path></svg>`;
158
+ }
159
+
160
+ //#endregion
161
+ //#region src/client/nav-icon.ts
162
+ const LABELS = new Set(["手机连接", "Phone connection"]);
163
+ /**
164
+ * Settings nav icons are hardcoded by section id in
165
+ * `@deepseek-ai/dsh-client-ui-settings-general` (`navIcon`): unknown ids fall
166
+ * back to the gear. `settings.section` has no `icon` register option, and the
167
+ * primitives icon set has no phone glyph. Swap the gear SVG on our nav cell
168
+ * for a Medium-outline smartphone that matches shipped stroke/size.
169
+ */
170
+ function installPocketNavIcon() {
171
+ const sync = () => {
172
+ for (const cell of document.querySelectorAll("[class*=\"navCell\"]")) {
173
+ const label = (cell.innerText || "").trim();
174
+ if (!LABELS.has(label)) continue;
175
+ const svg = cell.querySelector("svg");
176
+ if (!svg || svg.getAttribute("data-pocket-icon") === "1") continue;
177
+ const cls = svg.getAttribute("class") || "";
178
+ const wrap = document.createElement("span");
179
+ wrap.innerHTML = phoneIconSvgHtml(cls);
180
+ const next = wrap.firstElementChild;
181
+ if (next) svg.replaceWith(next);
182
+ }
183
+ };
184
+ const mo = new MutationObserver(() => sync());
185
+ mo.observe(document.documentElement, {
186
+ childList: true,
187
+ subtree: true,
188
+ characterData: true
189
+ });
190
+ sync();
191
+ return () => mo.disconnect();
192
+ }
193
+
194
+ //#endregion
195
+ //#region src/client/styles.ts
196
+ const text = "var(--dsw-alias-label-primary)";
197
+ const muted = "var(--dsw-alias-label-secondary)";
198
+ const border = "1px solid var(--dsw-alias-border-l2)";
199
+ const surface = "var(--dsw-alias-bg-module-platform, var(--dsw-alias-bg-base))";
200
+ const button = {
201
+ appearance: "none",
202
+ display: "inline-flex",
203
+ alignItems: "center",
204
+ justifyContent: "center",
205
+ gap: 8,
206
+ border: "none",
207
+ borderRadius: 8,
208
+ background: "transparent",
209
+ color: muted,
210
+ cursor: "pointer",
211
+ font: "inherit",
212
+ fontSize: 13,
213
+ lineHeight: "20px",
214
+ padding: "8px 10px"
215
+ };
216
+ const styles = {
217
+ settingsRoot: {
218
+ width: "100%",
219
+ padding: "8px 0 24px",
220
+ color: text,
221
+ containerType: "inline-size"
222
+ },
223
+ header: {
224
+ display: "flex",
225
+ alignItems: "center",
226
+ justifyContent: "space-between",
227
+ gap: 16,
228
+ marginBottom: 24
229
+ },
230
+ title: {
231
+ margin: 0,
232
+ fontSize: 24,
233
+ fontWeight: 650,
234
+ lineHeight: "32px"
235
+ },
236
+ service: {
237
+ display: "inline-flex",
238
+ alignItems: "center",
239
+ gap: 6,
240
+ fontSize: 12,
241
+ color: muted
242
+ },
243
+ pairingCard: {
244
+ border,
245
+ borderRadius: 16,
246
+ background: surface,
247
+ padding: "24px 24px 0"
248
+ },
249
+ pairingTop: {
250
+ display: "flex",
251
+ alignItems: "center",
252
+ justifyContent: "space-between",
253
+ gap: 24,
254
+ minHeight: 168,
255
+ paddingBottom: 24
256
+ },
257
+ pairControls: {
258
+ display: "flex",
259
+ flexDirection: "column",
260
+ alignItems: "flex-start",
261
+ gap: 18,
262
+ minWidth: 0
263
+ },
264
+ rowTitle: {
265
+ margin: 0,
266
+ fontSize: 16,
267
+ lineHeight: "24px",
268
+ fontWeight: 600,
269
+ color: text
270
+ },
271
+ illustration: {
272
+ width: 180,
273
+ maxWidth: "30%",
274
+ flexShrink: 0,
275
+ color: "var(--dsw-alias-label-tertiary)",
276
+ opacity: .55,
277
+ marginRight: 20
278
+ },
279
+ segments: {
280
+ display: "inline-flex",
281
+ padding: 3,
282
+ borderRadius: 10,
283
+ gap: 2,
284
+ background: "var(--dsw-alias-bg-overlay)"
285
+ },
286
+ segment: {
287
+ ...button,
288
+ minWidth: 88,
289
+ padding: "6px 16px"
290
+ },
291
+ segmentActive: {
292
+ color: text,
293
+ background: "var(--dsw-alias-bg-base)",
294
+ boxShadow: "0 1px 4px rgba(0,0,0,.08)"
295
+ },
296
+ footer: {
297
+ display: "flex",
298
+ flexWrap: "wrap",
299
+ alignItems: "center",
300
+ justifyContent: "space-between",
301
+ gap: "8px 16px",
302
+ borderTop: border,
303
+ padding: "14px 0"
304
+ },
305
+ address: {
306
+ display: "flex",
307
+ flexWrap: "wrap",
308
+ alignItems: "center",
309
+ gap: 8,
310
+ minWidth: 0,
311
+ fontSize: 12,
312
+ color: muted
313
+ },
314
+ addressValue: {
315
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
316
+ fontSize: 12,
317
+ color: text,
318
+ overflowWrap: "anywhere"
319
+ },
320
+ footerActions: {
321
+ display: "flex",
322
+ alignItems: "center",
323
+ gap: 10,
324
+ marginLeft: "auto"
325
+ },
326
+ divider: {
327
+ width: 1,
328
+ height: 16,
329
+ background: "var(--dsw-alias-border-l2)",
330
+ flexShrink: 0
331
+ },
332
+ iconBtn: {
333
+ ...button,
334
+ padding: 8
335
+ },
336
+ textBtn: button,
337
+ primaryBtn: {
338
+ ...button,
339
+ height: 40,
340
+ padding: "0 18px",
341
+ fontSize: 14,
342
+ fontWeight: 550,
343
+ borderRadius: 10,
344
+ background: "var(--dsw-alias-button-primary-fill)",
345
+ color: "var(--dsw-alias-label-primary-foreground, var(--dsw-alias-bg-base))"
346
+ },
347
+ outlineBtn: {
348
+ ...button,
349
+ border,
350
+ color: text
351
+ },
352
+ outlineBtnSm: {
353
+ ...button,
354
+ padding: "5px 8px",
355
+ color: text
356
+ },
357
+ destructiveBtn: {
358
+ ...button,
359
+ background: "var(--dsw-alias-state-error-primary)",
360
+ color: "#fff",
361
+ padding: "8px 14px"
362
+ },
363
+ ghostBtn: {
364
+ ...button,
365
+ border,
366
+ color: text
367
+ },
368
+ hint: {
369
+ margin: 0,
370
+ fontSize: 12,
371
+ lineHeight: "20px",
372
+ color: muted
373
+ },
374
+ errorText: {
375
+ margin: "0 0 16px",
376
+ fontSize: 12,
377
+ lineHeight: "20px",
378
+ color: "var(--dsw-alias-state-error-primary)"
379
+ },
380
+ stateDotSlot: (state) => ({
381
+ display: "inline-flex",
382
+ alignItems: "center",
383
+ justifyContent: "center",
384
+ width: 10,
385
+ height: 10,
386
+ flexShrink: 0,
387
+ color: state === "done" ? "var(--dsw-alias-state-success-primary)" : state === "error" ? "var(--dsw-alias-state-error-primary)" : "var(--dsw-alias-label-tertiary)"
388
+ }),
389
+ stateDotCore: {
390
+ width: 6,
391
+ height: 6,
392
+ borderRadius: "50%",
393
+ background: "currentColor"
394
+ },
395
+ pairingContent: {
396
+ display: "flex",
397
+ flexWrap: "wrap",
398
+ alignItems: "center",
399
+ gap: 24,
400
+ padding: "0 0 24px"
401
+ },
402
+ code: {
403
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
404
+ fontSize: 28,
405
+ letterSpacing: ".16em",
406
+ fontWeight: 600,
407
+ margin: "4px 0 8px"
408
+ },
409
+ qrBox: {
410
+ padding: 10,
411
+ borderRadius: 10,
412
+ background: "#fff",
413
+ lineHeight: 0
414
+ },
415
+ actionRow: {
416
+ display: "flex",
417
+ flexWrap: "wrap",
418
+ gap: 8,
419
+ marginTop: 16
420
+ },
421
+ devicesSection: { marginTop: 30 },
422
+ devicesHeader: {
423
+ display: "flex",
424
+ alignItems: "center",
425
+ gap: 10,
426
+ marginBottom: 16
427
+ },
428
+ count: {
429
+ background: surface,
430
+ color: muted,
431
+ borderRadius: 8,
432
+ minWidth: 24,
433
+ textAlign: "center",
434
+ fontSize: 12,
435
+ padding: "2px 4px"
436
+ },
437
+ deviceList: {
438
+ display: "flex",
439
+ flexDirection: "column",
440
+ gap: 10
441
+ },
442
+ deviceRow: {
443
+ display: "flex",
444
+ flexWrap: "wrap",
445
+ alignItems: "center",
446
+ gap: 14,
447
+ padding: "18px 20px",
448
+ border,
449
+ borderRadius: 14
450
+ },
451
+ deviceIcon: {
452
+ display: "flex",
453
+ alignItems: "center",
454
+ justifyContent: "center",
455
+ width: 44,
456
+ height: 48,
457
+ borderRadius: 11,
458
+ background: surface,
459
+ color: muted
460
+ },
461
+ deviceInfo: {
462
+ flex: "1 1 120px",
463
+ minWidth: 0
464
+ },
465
+ deviceHeading: {
466
+ display: "flex",
467
+ alignItems: "center",
468
+ gap: 10,
469
+ minWidth: 0
470
+ },
471
+ deviceName: {
472
+ fontSize: 14,
473
+ lineHeight: "22px",
474
+ fontWeight: 550,
475
+ color: text,
476
+ overflow: "hidden",
477
+ textOverflow: "ellipsis",
478
+ whiteSpace: "nowrap"
479
+ },
480
+ deviceMeta: {
481
+ marginTop: 4,
482
+ fontSize: 12,
483
+ lineHeight: "18px",
484
+ color: muted
485
+ },
486
+ deviceActions: {
487
+ display: "flex",
488
+ alignItems: "center",
489
+ gap: 6,
490
+ marginLeft: "auto"
491
+ },
492
+ input: {
493
+ boxSizing: "border-box",
494
+ width: "100%",
495
+ minWidth: 0,
496
+ height: 34,
497
+ border,
498
+ borderRadius: 8,
499
+ padding: "0 10px",
500
+ background: "var(--dsw-alias-bg-base)",
501
+ color: text,
502
+ font: "inherit",
503
+ fontSize: 13
504
+ },
505
+ dialogMask: {
506
+ position: "fixed",
507
+ inset: 0,
508
+ zIndex: 1100,
509
+ display: "flex",
510
+ alignItems: "center",
511
+ justifyContent: "center",
512
+ pointerEvents: "auto",
513
+ background: "rgba(0,0,0,.45)",
514
+ padding: 24
515
+ },
516
+ dialogCard: {
517
+ width: "min(560px, calc(100vw - 48px))",
518
+ maxHeight: "80vh",
519
+ overflow: "auto",
520
+ borderRadius: 14,
521
+ border,
522
+ background: "var(--dsw-alias-bg-base)",
523
+ color: text,
524
+ boxShadow: "0 12px 40px rgba(0,0,0,.2)",
525
+ padding: 24,
526
+ display: "flex",
527
+ flexDirection: "column",
528
+ gap: 12
529
+ },
530
+ dialogTitle: {
531
+ margin: 0,
532
+ fontSize: 16,
533
+ fontWeight: 600,
534
+ lineHeight: "24px"
535
+ },
536
+ dialogBody: {
537
+ margin: 0,
538
+ fontSize: 14,
539
+ lineHeight: "22px",
540
+ color: muted
541
+ },
542
+ dialogActions: {
543
+ display: "flex",
544
+ justifyContent: "flex-end",
545
+ gap: 8,
546
+ marginTop: 8
547
+ }
548
+ };
549
+ const panelCss = `
550
+ [data-pocket-panel] button:disabled { opacity: .45; cursor: not-allowed !important; }
551
+ [data-pocket-panel] button:not(:disabled):hover { filter: brightness(.94); }
552
+ [data-pocket-panel] button:focus-visible { outline: 2px solid var(--dsw-alias-label-secondary); outline-offset: 3px; }
553
+ @container (max-width: 440px) {
554
+ [data-pocket-panel] .pocket-illustration { display: none; }
555
+ [data-pocket-panel] .pocket-device-actions { width: 100%; justify-content: flex-end; }
556
+ }
557
+ `;
558
+
559
+ //#endregion
560
+ //#region src/client/ConfirmDialog.tsx
561
+ /**
562
+ * Lightweight confirm dialog matching dsh Modal / SignOutDialog chrome
563
+ * (mask + card + title + body + action row), built with theme tokens only.
564
+ * Does not `require` `@deepseek-ai/dsh-client-ui-primitives` (plugin hard rule).
565
+ */
566
+ function ConfirmDialog({ open, title, body, cancelLabel, confirmLabel, busy = false, onCancel, onConfirm }) {
567
+ (0, react.useEffect)(() => {
568
+ if (!open) return;
569
+ const onKey = (e) => {
570
+ if (e.key === "Escape" && !busy) onCancel();
571
+ };
572
+ document.addEventListener("keydown", onKey);
573
+ return () => document.removeEventListener("keydown", onKey);
574
+ }, [
575
+ open,
576
+ busy,
577
+ onCancel
578
+ ]);
579
+ if (!open || typeof document === "undefined") return null;
580
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
581
+ style: styles.dialogMask,
582
+ role: "presentation",
583
+ onClick: (e) => {
584
+ if (e.target === e.currentTarget && !busy) onCancel();
585
+ },
586
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
587
+ role: "dialog",
588
+ "aria-modal": "true",
589
+ "aria-label": title,
590
+ style: styles.dialogCard,
591
+ onClick: (e) => e.stopPropagation(),
592
+ children: [
593
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
594
+ style: styles.dialogTitle,
595
+ children: title
596
+ }),
597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
598
+ style: styles.dialogBody,
599
+ children: body
600
+ }),
601
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
602
+ style: styles.dialogActions,
603
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
604
+ type: "button",
605
+ style: styles.ghostBtn,
606
+ disabled: busy,
607
+ "data-modal-autofocus": true,
608
+ onClick: onCancel,
609
+ children: cancelLabel
610
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
611
+ type: "button",
612
+ style: styles.destructiveBtn,
613
+ disabled: busy,
614
+ onClick: onConfirm,
615
+ children: confirmLabel
616
+ })]
617
+ })
618
+ ]
619
+ })
620
+ }), document.body);
621
+ }
622
+
623
+ //#endregion
624
+ //#region node_modules/uqr/dist/index.mjs
625
+ var QrCodeDataType = /* @__PURE__ */ ((QrCodeDataType2) => {
626
+ QrCodeDataType2[QrCodeDataType2["Border"] = -1] = "Border";
627
+ QrCodeDataType2[QrCodeDataType2["Data"] = 0] = "Data";
628
+ QrCodeDataType2[QrCodeDataType2["Function"] = 1] = "Function";
629
+ QrCodeDataType2[QrCodeDataType2["Position"] = 2] = "Position";
630
+ QrCodeDataType2[QrCodeDataType2["Timing"] = 3] = "Timing";
631
+ QrCodeDataType2[QrCodeDataType2["Alignment"] = 4] = "Alignment";
632
+ return QrCodeDataType2;
633
+ })(QrCodeDataType || {});
634
+ const LOW = [0, 1];
635
+ const MEDIUM = [1, 0];
636
+ const QUARTILE = [2, 3];
637
+ const HIGH = [3, 2];
638
+ const EccMap = {
639
+ L: LOW,
640
+ M: MEDIUM,
641
+ Q: QUARTILE,
642
+ H: HIGH
643
+ };
644
+ const NUMERIC_REGEX = /^\d*$/;
645
+ const ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+./:-]*$/;
646
+ const ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
647
+ const MIN_VERSION = 1;
648
+ const MAX_VERSION = 40;
649
+ const PENALTY_N1 = 3;
650
+ const PENALTY_N2 = 3;
651
+ const PENALTY_N3 = 40;
652
+ const PENALTY_N4 = 10;
653
+ const ECC_CODEWORDS_PER_BLOCK = [
654
+ [
655
+ -1,
656
+ 7,
657
+ 10,
658
+ 15,
659
+ 20,
660
+ 26,
661
+ 18,
662
+ 20,
663
+ 24,
664
+ 30,
665
+ 18,
666
+ 20,
667
+ 24,
668
+ 26,
669
+ 30,
670
+ 22,
671
+ 24,
672
+ 28,
673
+ 30,
674
+ 28,
675
+ 28,
676
+ 28,
677
+ 28,
678
+ 30,
679
+ 30,
680
+ 26,
681
+ 28,
682
+ 30,
683
+ 30,
684
+ 30,
685
+ 30,
686
+ 30,
687
+ 30,
688
+ 30,
689
+ 30,
690
+ 30,
691
+ 30,
692
+ 30,
693
+ 30,
694
+ 30,
695
+ 30
696
+ ],
697
+ [
698
+ -1,
699
+ 10,
700
+ 16,
701
+ 26,
702
+ 18,
703
+ 24,
704
+ 16,
705
+ 18,
706
+ 22,
707
+ 22,
708
+ 26,
709
+ 30,
710
+ 22,
711
+ 22,
712
+ 24,
713
+ 24,
714
+ 28,
715
+ 28,
716
+ 26,
717
+ 26,
718
+ 26,
719
+ 26,
720
+ 28,
721
+ 28,
722
+ 28,
723
+ 28,
724
+ 28,
725
+ 28,
726
+ 28,
727
+ 28,
728
+ 28,
729
+ 28,
730
+ 28,
731
+ 28,
732
+ 28,
733
+ 28,
734
+ 28,
735
+ 28,
736
+ 28,
737
+ 28,
738
+ 28
739
+ ],
740
+ [
741
+ -1,
742
+ 13,
743
+ 22,
744
+ 18,
745
+ 26,
746
+ 18,
747
+ 24,
748
+ 18,
749
+ 22,
750
+ 20,
751
+ 24,
752
+ 28,
753
+ 26,
754
+ 24,
755
+ 20,
756
+ 30,
757
+ 24,
758
+ 28,
759
+ 28,
760
+ 26,
761
+ 30,
762
+ 28,
763
+ 30,
764
+ 30,
765
+ 30,
766
+ 30,
767
+ 28,
768
+ 30,
769
+ 30,
770
+ 30,
771
+ 30,
772
+ 30,
773
+ 30,
774
+ 30,
775
+ 30,
776
+ 30,
777
+ 30,
778
+ 30,
779
+ 30,
780
+ 30,
781
+ 30
782
+ ],
783
+ [
784
+ -1,
785
+ 17,
786
+ 28,
787
+ 22,
788
+ 16,
789
+ 22,
790
+ 28,
791
+ 26,
792
+ 26,
793
+ 24,
794
+ 28,
795
+ 24,
796
+ 28,
797
+ 22,
798
+ 24,
799
+ 24,
800
+ 30,
801
+ 28,
802
+ 28,
803
+ 26,
804
+ 28,
805
+ 30,
806
+ 24,
807
+ 30,
808
+ 30,
809
+ 30,
810
+ 30,
811
+ 30,
812
+ 30,
813
+ 30,
814
+ 30,
815
+ 30,
816
+ 30,
817
+ 30,
818
+ 30,
819
+ 30,
820
+ 30,
821
+ 30,
822
+ 30,
823
+ 30,
824
+ 30
825
+ ]
826
+ ];
827
+ const NUM_ERROR_CORRECTION_BLOCKS = [
828
+ [
829
+ -1,
830
+ 1,
831
+ 1,
832
+ 1,
833
+ 1,
834
+ 1,
835
+ 2,
836
+ 2,
837
+ 2,
838
+ 2,
839
+ 4,
840
+ 4,
841
+ 4,
842
+ 4,
843
+ 4,
844
+ 6,
845
+ 6,
846
+ 6,
847
+ 6,
848
+ 7,
849
+ 8,
850
+ 8,
851
+ 9,
852
+ 9,
853
+ 10,
854
+ 12,
855
+ 12,
856
+ 12,
857
+ 13,
858
+ 14,
859
+ 15,
860
+ 16,
861
+ 17,
862
+ 18,
863
+ 19,
864
+ 19,
865
+ 20,
866
+ 21,
867
+ 22,
868
+ 24,
869
+ 25
870
+ ],
871
+ [
872
+ -1,
873
+ 1,
874
+ 1,
875
+ 1,
876
+ 2,
877
+ 2,
878
+ 4,
879
+ 4,
880
+ 4,
881
+ 5,
882
+ 5,
883
+ 5,
884
+ 8,
885
+ 9,
886
+ 9,
887
+ 10,
888
+ 10,
889
+ 11,
890
+ 13,
891
+ 14,
892
+ 16,
893
+ 17,
894
+ 17,
895
+ 18,
896
+ 20,
897
+ 21,
898
+ 23,
899
+ 25,
900
+ 26,
901
+ 28,
902
+ 29,
903
+ 31,
904
+ 33,
905
+ 35,
906
+ 37,
907
+ 38,
908
+ 40,
909
+ 43,
910
+ 45,
911
+ 47,
912
+ 49
913
+ ],
914
+ [
915
+ -1,
916
+ 1,
917
+ 1,
918
+ 2,
919
+ 2,
920
+ 4,
921
+ 4,
922
+ 6,
923
+ 6,
924
+ 8,
925
+ 8,
926
+ 8,
927
+ 10,
928
+ 12,
929
+ 16,
930
+ 12,
931
+ 17,
932
+ 16,
933
+ 18,
934
+ 21,
935
+ 20,
936
+ 23,
937
+ 23,
938
+ 25,
939
+ 27,
940
+ 29,
941
+ 34,
942
+ 34,
943
+ 35,
944
+ 38,
945
+ 40,
946
+ 43,
947
+ 45,
948
+ 48,
949
+ 51,
950
+ 53,
951
+ 56,
952
+ 59,
953
+ 62,
954
+ 65,
955
+ 68
956
+ ],
957
+ [
958
+ -1,
959
+ 1,
960
+ 1,
961
+ 2,
962
+ 4,
963
+ 4,
964
+ 4,
965
+ 5,
966
+ 6,
967
+ 8,
968
+ 8,
969
+ 11,
970
+ 11,
971
+ 16,
972
+ 16,
973
+ 18,
974
+ 16,
975
+ 19,
976
+ 21,
977
+ 25,
978
+ 25,
979
+ 25,
980
+ 34,
981
+ 30,
982
+ 32,
983
+ 35,
984
+ 37,
985
+ 40,
986
+ 42,
987
+ 45,
988
+ 48,
989
+ 51,
990
+ 54,
991
+ 57,
992
+ 60,
993
+ 63,
994
+ 66,
995
+ 70,
996
+ 74,
997
+ 77,
998
+ 81
999
+ ]
1000
+ ];
1001
+ var QrCode = class {
1002
+ constructor(version, ecc, dataCodewords, msk) {
1003
+ this.version = version;
1004
+ this.ecc = ecc;
1005
+ if (version < MIN_VERSION || version > MAX_VERSION) throw new RangeError("Version value out of range");
1006
+ if (msk < -1 || msk > 7) throw new RangeError("Mask value out of range");
1007
+ this.size = version * 4 + 17;
1008
+ const row = Array.from({ length: this.size }).fill(false);
1009
+ for (let i = 0; i < this.size; i++) {
1010
+ this.modules.push(row.slice());
1011
+ this.types.push(row.map(() => 0));
1012
+ }
1013
+ this.drawFunctionPatterns();
1014
+ const allCodewords = this.addEccAndInterleave(dataCodewords);
1015
+ this.drawCodewords(allCodewords);
1016
+ if (msk === -1) {
1017
+ let minPenalty = 1e9;
1018
+ for (let i = 0; i < 8; i++) {
1019
+ this.applyMask(i);
1020
+ this.drawFormatBits(i);
1021
+ const penalty = this.getPenaltyScore();
1022
+ if (penalty < minPenalty) {
1023
+ msk = i;
1024
+ minPenalty = penalty;
1025
+ }
1026
+ this.applyMask(i);
1027
+ }
1028
+ }
1029
+ this.mask = msk;
1030
+ this.applyMask(msk);
1031
+ this.drawFormatBits(msk);
1032
+ }
1033
+ size;
1034
+ mask;
1035
+ modules = [];
1036
+ types = [];
1037
+ getModule(x, y) {
1038
+ return x >= 0 && x < this.size && y >= 0 && y < this.size && this.modules[y][x];
1039
+ }
1040
+ drawFunctionPatterns() {
1041
+ for (let i = 0; i < this.size; i++) {
1042
+ this.setFunctionModule(6, i, i % 2 === 0, QrCodeDataType.Timing);
1043
+ this.setFunctionModule(i, 6, i % 2 === 0, QrCodeDataType.Timing);
1044
+ }
1045
+ this.drawFinderPattern(3, 3);
1046
+ this.drawFinderPattern(this.size - 4, 3);
1047
+ this.drawFinderPattern(3, this.size - 4);
1048
+ const alignPatPos = this.getAlignmentPatternPositions();
1049
+ const numAlign = alignPatPos.length;
1050
+ for (let i = 0; i < numAlign; i++) for (let j = 0; j < numAlign; j++) if (!(i === 0 && j === 0 || i === 0 && j === numAlign - 1 || i === numAlign - 1 && j === 0)) this.drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
1051
+ this.drawFormatBits(0);
1052
+ this.drawVersion();
1053
+ }
1054
+ drawFormatBits(mask) {
1055
+ const data = this.ecc[1] << 3 | mask;
1056
+ let rem = data;
1057
+ for (let i = 0; i < 10; i++) rem = rem << 1 ^ (rem >>> 9) * 1335;
1058
+ const bits = (data << 10 | rem) ^ 21522;
1059
+ for (let i = 0; i <= 5; i++) this.setFunctionModule(8, i, getBit(bits, i));
1060
+ this.setFunctionModule(8, 7, getBit(bits, 6));
1061
+ this.setFunctionModule(8, 8, getBit(bits, 7));
1062
+ this.setFunctionModule(7, 8, getBit(bits, 8));
1063
+ for (let i = 9; i < 15; i++) this.setFunctionModule(14 - i, 8, getBit(bits, i));
1064
+ for (let i = 0; i < 8; i++) this.setFunctionModule(this.size - 1 - i, 8, getBit(bits, i));
1065
+ for (let i = 8; i < 15; i++) this.setFunctionModule(8, this.size - 15 + i, getBit(bits, i));
1066
+ this.setFunctionModule(8, this.size - 8, true);
1067
+ }
1068
+ drawVersion() {
1069
+ if (this.version < 7) return;
1070
+ let rem = this.version;
1071
+ for (let i = 0; i < 12; i++) rem = rem << 1 ^ (rem >>> 11) * 7973;
1072
+ const bits = this.version << 12 | rem;
1073
+ for (let i = 0; i < 18; i++) {
1074
+ const color = getBit(bits, i);
1075
+ const a = this.size - 11 + i % 3;
1076
+ const b = Math.floor(i / 3);
1077
+ this.setFunctionModule(a, b, color);
1078
+ this.setFunctionModule(b, a, color);
1079
+ }
1080
+ }
1081
+ drawFinderPattern(x, y) {
1082
+ for (let dy = -4; dy <= 4; dy++) for (let dx = -4; dx <= 4; dx++) {
1083
+ const dist = Math.max(Math.abs(dx), Math.abs(dy));
1084
+ const xx = x + dx;
1085
+ const yy = y + dy;
1086
+ if (xx >= 0 && xx < this.size && yy >= 0 && yy < this.size) this.setFunctionModule(xx, yy, dist !== 2 && dist !== 4, QrCodeDataType.Position);
1087
+ }
1088
+ }
1089
+ drawAlignmentPattern(x, y) {
1090
+ for (let dy = -2; dy <= 2; dy++) for (let dx = -2; dx <= 2; dx++) this.setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1, QrCodeDataType.Alignment);
1091
+ }
1092
+ setFunctionModule(x, y, isDark, type = QrCodeDataType.Function) {
1093
+ this.modules[y][x] = isDark;
1094
+ this.types[y][x] = type;
1095
+ }
1096
+ addEccAndInterleave(data) {
1097
+ const ver = this.version;
1098
+ const ecl = this.ecc;
1099
+ if (data.length !== getNumDataCodewords(ver, ecl)) throw new RangeError("Invalid argument");
1100
+ const numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecl[0]][ver];
1101
+ const blockEccLen = ECC_CODEWORDS_PER_BLOCK[ecl[0]][ver];
1102
+ const rawCodewords = Math.floor(getNumRawDataModules(ver) / 8);
1103
+ const numShortBlocks = numBlocks - rawCodewords % numBlocks;
1104
+ const shortBlockLen = Math.floor(rawCodewords / numBlocks);
1105
+ const blocks = [];
1106
+ const rsDiv = reedSolomonComputeDivisor(blockEccLen);
1107
+ for (let i = 0, k = 0; i < numBlocks; i++) {
1108
+ const dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
1109
+ k += dat.length;
1110
+ const ecc = reedSolomonComputeRemainder(dat, rsDiv);
1111
+ if (i < numShortBlocks) dat.push(0);
1112
+ blocks.push(dat.concat(ecc));
1113
+ }
1114
+ const result = [];
1115
+ for (let i = 0; i < blocks[0].length; i++) blocks.forEach((block, j) => {
1116
+ if (i !== shortBlockLen - blockEccLen || j >= numShortBlocks) result.push(block[i]);
1117
+ });
1118
+ return result;
1119
+ }
1120
+ drawCodewords(data) {
1121
+ if (data.length !== Math.floor(getNumRawDataModules(this.version) / 8)) throw new RangeError("Invalid argument");
1122
+ let i = 0;
1123
+ for (let right = this.size - 1; right >= 1; right -= 2) {
1124
+ if (right === 6) right = 5;
1125
+ for (let vert = 0; vert < this.size; vert++) for (let j = 0; j < 2; j++) {
1126
+ const x = right - j;
1127
+ const y = (right + 1 & 2) === 0 ? this.size - 1 - vert : vert;
1128
+ if (!this.types[y][x] && i < data.length * 8) {
1129
+ this.modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
1130
+ i++;
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+ applyMask(mask) {
1136
+ if (mask < 0 || mask > 7) throw new RangeError("Mask value out of range");
1137
+ for (let y = 0; y < this.size; y++) for (let x = 0; x < this.size; x++) {
1138
+ let invert;
1139
+ switch (mask) {
1140
+ case 0:
1141
+ invert = (x + y) % 2 === 0;
1142
+ break;
1143
+ case 1:
1144
+ invert = y % 2 === 0;
1145
+ break;
1146
+ case 2:
1147
+ invert = x % 3 === 0;
1148
+ break;
1149
+ case 3:
1150
+ invert = (x + y) % 3 === 0;
1151
+ break;
1152
+ case 4:
1153
+ invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0;
1154
+ break;
1155
+ case 5:
1156
+ invert = x * y % 2 + x * y % 3 === 0;
1157
+ break;
1158
+ case 6:
1159
+ invert = (x * y % 2 + x * y % 3) % 2 === 0;
1160
+ break;
1161
+ case 7:
1162
+ invert = ((x + y) % 2 + x * y % 3) % 2 === 0;
1163
+ break;
1164
+ default: throw new Error("Unreachable");
1165
+ }
1166
+ if (!this.types[y][x] && invert) this.modules[y][x] = !this.modules[y][x];
1167
+ }
1168
+ }
1169
+ getPenaltyScore() {
1170
+ let result = 0;
1171
+ for (let y = 0; y < this.size; y++) {
1172
+ let runColor = false;
1173
+ let runX = 0;
1174
+ const runHistory = [
1175
+ 0,
1176
+ 0,
1177
+ 0,
1178
+ 0,
1179
+ 0,
1180
+ 0,
1181
+ 0
1182
+ ];
1183
+ for (let x = 0; x < this.size; x++) if (this.modules[y][x] === runColor) {
1184
+ runX++;
1185
+ if (runX === 5) result += PENALTY_N1;
1186
+ else if (runX > 5) result++;
1187
+ } else {
1188
+ this.finderPenaltyAddHistory(runX, runHistory);
1189
+ if (!runColor) result += this.finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
1190
+ runColor = this.modules[y][x];
1191
+ runX = 1;
1192
+ }
1193
+ result += this.finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3;
1194
+ }
1195
+ for (let x = 0; x < this.size; x++) {
1196
+ let runColor = false;
1197
+ let runY = 0;
1198
+ const runHistory = [
1199
+ 0,
1200
+ 0,
1201
+ 0,
1202
+ 0,
1203
+ 0,
1204
+ 0,
1205
+ 0
1206
+ ];
1207
+ for (let y = 0; y < this.size; y++) if (this.modules[y][x] === runColor) {
1208
+ runY++;
1209
+ if (runY === 5) result += PENALTY_N1;
1210
+ else if (runY > 5) result++;
1211
+ } else {
1212
+ this.finderPenaltyAddHistory(runY, runHistory);
1213
+ if (!runColor) result += this.finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
1214
+ runColor = this.modules[y][x];
1215
+ runY = 1;
1216
+ }
1217
+ result += this.finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3;
1218
+ }
1219
+ for (let y = 0; y < this.size - 1; y++) for (let x = 0; x < this.size - 1; x++) {
1220
+ const color = this.modules[y][x];
1221
+ if (color === this.modules[y][x + 1] && color === this.modules[y + 1][x] && color === this.modules[y + 1][x + 1]) result += PENALTY_N2;
1222
+ }
1223
+ let dark = 0;
1224
+ for (const row of this.modules) dark = row.reduce((sum, color) => sum + (color ? 1 : 0), dark);
1225
+ const total = this.size * this.size;
1226
+ const k = Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1;
1227
+ result += k * PENALTY_N4;
1228
+ return result;
1229
+ }
1230
+ getAlignmentPatternPositions() {
1231
+ if (this.version === 1) return [];
1232
+ else {
1233
+ const numAlign = Math.floor(this.version / 7) + 2;
1234
+ const step = this.version === 32 ? 26 : Math.ceil((this.version * 4 + 4) / (numAlign * 2 - 2)) * 2;
1235
+ const result = [6];
1236
+ for (let pos = this.size - 7; result.length < numAlign; pos -= step) result.splice(1, 0, pos);
1237
+ return result;
1238
+ }
1239
+ }
1240
+ finderPenaltyCountPatterns(runHistory) {
1241
+ const n = runHistory[1];
1242
+ const core = n > 0 && runHistory[2] === n && runHistory[3] === n * 3 && runHistory[4] === n && runHistory[5] === n;
1243
+ return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0);
1244
+ }
1245
+ finderPenaltyTerminateAndCount(currentRunColor, currentRunLength, runHistory) {
1246
+ if (currentRunColor) {
1247
+ this.finderPenaltyAddHistory(currentRunLength, runHistory);
1248
+ currentRunLength = 0;
1249
+ }
1250
+ currentRunLength += this.size;
1251
+ this.finderPenaltyAddHistory(currentRunLength, runHistory);
1252
+ return this.finderPenaltyCountPatterns(runHistory);
1253
+ }
1254
+ finderPenaltyAddHistory(currentRunLength, runHistory) {
1255
+ if (runHistory[0] === 0) currentRunLength += this.size;
1256
+ runHistory.pop();
1257
+ runHistory.unshift(currentRunLength);
1258
+ }
1259
+ };
1260
+ function appendBits(val, len, bb) {
1261
+ if (len < 0 || len > 31 || val >>> len !== 0) throw new RangeError("Value out of range");
1262
+ for (let i = len - 1; i >= 0; i--) bb.push(val >>> i & 1);
1263
+ }
1264
+ function getBit(x, i) {
1265
+ return (x >>> i & 1) !== 0;
1266
+ }
1267
+ var QrSegment = class {
1268
+ constructor(mode, numChars, bitData) {
1269
+ this.mode = mode;
1270
+ this.numChars = numChars;
1271
+ this.bitData = bitData;
1272
+ if (numChars < 0) throw new RangeError("Invalid argument");
1273
+ this.bitData = bitData.slice();
1274
+ }
1275
+ getData() {
1276
+ return this.bitData.slice();
1277
+ }
1278
+ };
1279
+ const MODE_NUMERIC = [
1280
+ 1,
1281
+ 10,
1282
+ 12,
1283
+ 14
1284
+ ];
1285
+ const MODE_ALPHANUMERIC = [
1286
+ 2,
1287
+ 9,
1288
+ 11,
1289
+ 13
1290
+ ];
1291
+ const MODE_BYTE = [
1292
+ 4,
1293
+ 8,
1294
+ 16,
1295
+ 16
1296
+ ];
1297
+ function numCharCountBits(mode, ver) {
1298
+ return mode[Math.floor((ver + 7) / 17) + 1];
1299
+ }
1300
+ function makeBytes(data) {
1301
+ const bb = [];
1302
+ for (const b of data) appendBits(b, 8, bb);
1303
+ return new QrSegment(MODE_BYTE, data.length, bb);
1304
+ }
1305
+ function makeNumeric(digits) {
1306
+ if (!isNumeric(digits)) throw new RangeError("String contains non-numeric characters");
1307
+ const bb = [];
1308
+ for (let i = 0; i < digits.length;) {
1309
+ const n = Math.min(digits.length - i, 3);
1310
+ appendBits(Number.parseInt(digits.substring(i, i + n), 10), n * 3 + 1, bb);
1311
+ i += n;
1312
+ }
1313
+ return new QrSegment(MODE_NUMERIC, digits.length, bb);
1314
+ }
1315
+ function makeAlphanumeric(text$1) {
1316
+ if (!isAlphanumeric(text$1)) throw new RangeError("String contains unencodable characters in alphanumeric mode");
1317
+ const bb = [];
1318
+ let i;
1319
+ for (i = 0; i + 2 <= text$1.length; i += 2) {
1320
+ let temp = ALPHANUMERIC_CHARSET.indexOf(text$1.charAt(i)) * 45;
1321
+ temp += ALPHANUMERIC_CHARSET.indexOf(text$1.charAt(i + 1));
1322
+ appendBits(temp, 11, bb);
1323
+ }
1324
+ if (i < text$1.length) appendBits(ALPHANUMERIC_CHARSET.indexOf(text$1.charAt(i)), 6, bb);
1325
+ return new QrSegment(MODE_ALPHANUMERIC, text$1.length, bb);
1326
+ }
1327
+ function makeSegments(text$1) {
1328
+ if (text$1 === "") return [];
1329
+ else if (isNumeric(text$1)) return [makeNumeric(text$1)];
1330
+ else if (isAlphanumeric(text$1)) return [makeAlphanumeric(text$1)];
1331
+ else return [makeBytes(toUtf8ByteArray(text$1))];
1332
+ }
1333
+ function isNumeric(text$1) {
1334
+ return NUMERIC_REGEX.test(text$1);
1335
+ }
1336
+ function isAlphanumeric(text$1) {
1337
+ return ALPHANUMERIC_REGEX.test(text$1);
1338
+ }
1339
+ function getTotalBits(segs, version) {
1340
+ let result = 0;
1341
+ for (const seg of segs) {
1342
+ const ccbits = numCharCountBits(seg.mode, version);
1343
+ if (seg.numChars >= 1 << ccbits) return Number.POSITIVE_INFINITY;
1344
+ result += 4 + ccbits + seg.bitData.length;
1345
+ }
1346
+ return result;
1347
+ }
1348
+ function toUtf8ByteArray(str) {
1349
+ str = encodeURI(str);
1350
+ const result = [];
1351
+ for (let i = 0; i < str.length; i++) if (str.charAt(i) !== "%") result.push(str.charCodeAt(i));
1352
+ else {
1353
+ result.push(Number.parseInt(str.substring(i + 1, i + 3), 16));
1354
+ i += 2;
1355
+ }
1356
+ return result;
1357
+ }
1358
+ function getNumRawDataModules(ver) {
1359
+ if (ver < MIN_VERSION || ver > MAX_VERSION) throw new RangeError("Version number out of range");
1360
+ let result = (16 * ver + 128) * ver + 64;
1361
+ if (ver >= 2) {
1362
+ const numAlign = Math.floor(ver / 7) + 2;
1363
+ result -= (25 * numAlign - 10) * numAlign - 55;
1364
+ if (ver >= 7) result -= 36;
1365
+ }
1366
+ return result;
1367
+ }
1368
+ function getNumDataCodewords(ver, ecl) {
1369
+ return Math.floor(getNumRawDataModules(ver) / 8) - ECC_CODEWORDS_PER_BLOCK[ecl[0]][ver] * NUM_ERROR_CORRECTION_BLOCKS[ecl[0]][ver];
1370
+ }
1371
+ function reedSolomonComputeDivisor(degree) {
1372
+ if (degree < 1 || degree > 255) throw new RangeError("Degree out of range");
1373
+ const result = [];
1374
+ for (let i = 0; i < degree - 1; i++) result.push(0);
1375
+ result.push(1);
1376
+ let root = 1;
1377
+ for (let i = 0; i < degree; i++) {
1378
+ for (let j = 0; j < result.length; j++) {
1379
+ result[j] = reedSolomonMultiply(result[j], root);
1380
+ if (j + 1 < result.length) result[j] ^= result[j + 1];
1381
+ }
1382
+ root = reedSolomonMultiply(root, 2);
1383
+ }
1384
+ return result;
1385
+ }
1386
+ function reedSolomonComputeRemainder(data, divisor) {
1387
+ const result = divisor.map((_) => 0);
1388
+ for (const b of data) {
1389
+ const factor = b ^ result.shift();
1390
+ result.push(0);
1391
+ divisor.forEach((coef, i) => result[i] ^= reedSolomonMultiply(coef, factor));
1392
+ }
1393
+ return result;
1394
+ }
1395
+ function reedSolomonMultiply(x, y) {
1396
+ if (x >>> 8 !== 0 || y >>> 8 !== 0) throw new RangeError("Byte out of range");
1397
+ let z = 0;
1398
+ for (let i = 7; i >= 0; i--) {
1399
+ z = z << 1 ^ (z >>> 7) * 285;
1400
+ z ^= (y >>> i & 1) * x;
1401
+ }
1402
+ return z;
1403
+ }
1404
+ function encodeSegments(segs, ecl, minVersion = 1, maxVersion = 40, mask = -1, boostEcl = true) {
1405
+ if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) throw new RangeError("Invalid value");
1406
+ let version;
1407
+ let dataUsedBits;
1408
+ for (version = minVersion;; version++) {
1409
+ const dataCapacityBits2 = getNumDataCodewords(version, ecl) * 8;
1410
+ const usedBits = getTotalBits(segs, version);
1411
+ if (usedBits <= dataCapacityBits2) {
1412
+ dataUsedBits = usedBits;
1413
+ break;
1414
+ }
1415
+ if (version >= maxVersion) throw new RangeError("Data too long");
1416
+ }
1417
+ for (const newEcl of [
1418
+ MEDIUM,
1419
+ QUARTILE,
1420
+ HIGH
1421
+ ]) if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) ecl = newEcl;
1422
+ const bb = [];
1423
+ for (const seg of segs) {
1424
+ appendBits(seg.mode[0], 4, bb);
1425
+ appendBits(seg.numChars, numCharCountBits(seg.mode, version), bb);
1426
+ for (const b of seg.getData()) bb.push(b);
1427
+ }
1428
+ const dataCapacityBits = getNumDataCodewords(version, ecl) * 8;
1429
+ appendBits(0, Math.min(4, dataCapacityBits - bb.length), bb);
1430
+ appendBits(0, (8 - bb.length % 8) % 8, bb);
1431
+ for (let padByte = 236; bb.length < dataCapacityBits; padByte ^= 253) appendBits(padByte, 8, bb);
1432
+ const dataCodewords = Array.from({ length: Math.ceil(bb.length / 8) }, () => 0);
1433
+ bb.forEach((b, i) => dataCodewords[i >>> 3] |= b << 7 - (i & 7));
1434
+ return new QrCode(version, ecl, dataCodewords, mask);
1435
+ }
1436
+ function encode(data, options) {
1437
+ const { ecc = "L", boostEcc = false, minVersion = 1, maxVersion = 40, maskPattern = -1, border: border$1 = 1 } = options || {};
1438
+ const segment = typeof data === "string" ? makeSegments(data) : Array.isArray(data) ? [makeBytes(data)] : void 0;
1439
+ if (!segment) throw new Error(`uqr only supports encoding string and binary data, but got: ${typeof data}`);
1440
+ const qr = encodeSegments(segment, EccMap[ecc], minVersion, maxVersion, maskPattern, boostEcc);
1441
+ const result = addBorder({
1442
+ version: qr.version,
1443
+ maskPattern: qr.mask,
1444
+ size: qr.size,
1445
+ data: qr.modules,
1446
+ types: qr.types
1447
+ }, border$1);
1448
+ if (options?.invert) result.data = result.data.map((row) => row.map((mod) => !mod));
1449
+ options?.onEncoded?.(result);
1450
+ return result;
1451
+ }
1452
+ function addBorder(input, border$1 = 1) {
1453
+ if (!border$1) return input;
1454
+ const { size } = input;
1455
+ const newSize = size + border$1 * 2;
1456
+ input.size = newSize;
1457
+ input.data.forEach((row) => {
1458
+ for (let i = 0; i < border$1; i++) {
1459
+ row.unshift(false);
1460
+ row.push(false);
1461
+ }
1462
+ });
1463
+ for (let i = 0; i < border$1; i++) {
1464
+ input.data.unshift(Array.from({ length: newSize }, (_) => false));
1465
+ input.data.push(Array.from({ length: newSize }, (_) => false));
1466
+ }
1467
+ const b = QrCodeDataType.Border;
1468
+ input.types.forEach((row) => {
1469
+ for (let i = 0; i < border$1; i++) {
1470
+ row.unshift(b);
1471
+ row.push(b);
1472
+ }
1473
+ });
1474
+ for (let i = 0; i < border$1; i++) {
1475
+ input.types.unshift(Array.from({ length: newSize }, (_) => b));
1476
+ input.types.push(Array.from({ length: newSize }, (_) => b));
1477
+ }
1478
+ return input;
1479
+ }
1480
+ function escapeAttr(value) {
1481
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1482
+ }
1483
+ function renderSVG(data, options = {}) {
1484
+ const result = encode(data, options);
1485
+ const { pixelSize = 10, whiteColor = "white", blackColor = "black" } = options;
1486
+ const height = result.size * pixelSize;
1487
+ const width = result.size * pixelSize;
1488
+ let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}">`;
1489
+ const paths = [];
1490
+ for (let row = 0; row < result.size; row++) for (let col = 0; col < result.size; col++) {
1491
+ const x = col * pixelSize;
1492
+ const y = row * pixelSize;
1493
+ if (result.data[row][col]) paths.push(`M${x},${y}h${pixelSize}v${pixelSize}h-${pixelSize}z`);
1494
+ }
1495
+ svg += `<rect fill="${escapeAttr(whiteColor)}" width="${width}" height="${height}"/>`;
1496
+ svg += `<path fill="${escapeAttr(blackColor)}" d="${paths.join("")}"/>`;
1497
+ svg += "</svg>";
1498
+ return svg;
1499
+ }
1500
+
1501
+ //#endregion
1502
+ //#region src/client/qr.ts
1503
+ /** Render a pairing URI as an SVG QR string. */
1504
+ function qrSvg(text$1, size = 180) {
1505
+ return renderSVG(text$1, {
1506
+ ecc: "M",
1507
+ pixelSize: 8,
1508
+ border: 2,
1509
+ blackColor: "#111",
1510
+ whiteColor: "#fff"
1511
+ }).replace("<svg ", `<svg width="${size}" height="${size}" `);
1512
+ }
1513
+
1514
+ //#endregion
1515
+ //#region src/client/ManagePanel.tsx
1516
+ function Icon({ name, size = 18 }) {
1517
+ const paths = {
1518
+ phone: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1519
+ x: "6",
1520
+ y: "2",
1521
+ width: "12",
1522
+ height: "20",
1523
+ rx: "3"
1524
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M11 18h2" })] }),
1525
+ copy: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1526
+ x: "8",
1527
+ y: "8",
1528
+ width: "12",
1529
+ height: "14",
1530
+ rx: "2"
1531
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M15 4V2H4v14h1" })] }),
1532
+ refresh: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M20 7a9 9 0 1 0 0 10M20 2v5h-5" }) }),
1533
+ plus: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 4v16M4 12h16" }),
1534
+ check: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m5 12 4 4L19 6" })
1535
+ };
1536
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1537
+ width: size,
1538
+ height: size,
1539
+ viewBox: "0 0 24 24",
1540
+ fill: "none",
1541
+ stroke: "currentColor",
1542
+ strokeWidth: "1.6",
1543
+ strokeLinecap: "round",
1544
+ strokeLinejoin: "round",
1545
+ "aria-hidden": "true",
1546
+ children: paths[name]
1547
+ });
1548
+ }
1549
+ function StatusDot({ state }) {
1550
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1551
+ style: styles.stateDotSlot(state),
1552
+ "aria-hidden": "true",
1553
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: styles.stateDotCore })
1554
+ });
1555
+ }
1556
+ function DeviceItem({ device, t, api, onChanged, onError }) {
1557
+ const [editing, setEditing] = (0, react.useState)(false);
1558
+ const [name, setName] = (0, react.useState)(device.name);
1559
+ const [confirmOpen, setConfirmOpen] = (0, react.useState)(false);
1560
+ const [busy, setBusy] = (0, react.useState)(false);
1561
+ (0, react.useEffect)(() => {
1562
+ setName(device.name);
1563
+ }, [device.name]);
1564
+ const save = async () => {
1565
+ const trimmed = name.trim();
1566
+ if (!trimmed || trimmed === device.name) {
1567
+ setEditing(false);
1568
+ return;
1569
+ }
1570
+ setBusy(true);
1571
+ try {
1572
+ onChanged((await api.rename(device.id, trimmed)).devices);
1573
+ setEditing(false);
1574
+ } catch (e) {
1575
+ onError(String(e?.message ?? e));
1576
+ } finally {
1577
+ setBusy(false);
1578
+ }
1579
+ };
1580
+ const revoke = async () => {
1581
+ setBusy(true);
1582
+ try {
1583
+ onChanged((await api.revoke(device.id)).devices);
1584
+ setConfirmOpen(false);
1585
+ } catch (e) {
1586
+ onError(String(e?.message ?? e));
1587
+ } finally {
1588
+ setBusy(false);
1589
+ }
1590
+ };
1591
+ const lastSeen = device.lastSeenAt == null ? t("neverSeen") : new Date(device.lastSeenAt).toLocaleString(void 0, {
1592
+ month: "2-digit",
1593
+ day: "2-digit",
1594
+ hour: "2-digit",
1595
+ minute: "2-digit",
1596
+ hour12: false
1597
+ });
1598
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1599
+ style: styles.deviceRow,
1600
+ children: [
1601
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1602
+ style: styles.deviceIcon,
1603
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
1604
+ name: "phone",
1605
+ size: 28
1606
+ })
1607
+ }),
1608
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1609
+ style: styles.deviceInfo,
1610
+ children: [editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1611
+ style: styles.input,
1612
+ "aria-label": t("rename"),
1613
+ value: name,
1614
+ disabled: busy,
1615
+ onChange: (e) => setName(e.target.value),
1616
+ autoFocus: true,
1617
+ onKeyDown: (e) => {
1618
+ if (e.key === "Enter") save();
1619
+ if (e.key === "Escape") {
1620
+ setEditing(false);
1621
+ setName(device.name);
1622
+ }
1623
+ }
1624
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1625
+ style: styles.deviceHeading,
1626
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1627
+ style: styles.deviceName,
1628
+ title: device.name,
1629
+ children: device.name
1630
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1631
+ style: styles.service,
1632
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusDot, { state: device.online ? "done" : "idle" }), device.online ? t("online") : t("offline")]
1633
+ })]
1634
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1635
+ style: styles.deviceMeta,
1636
+ children: [
1637
+ t("lastSeen"),
1638
+ " ",
1639
+ lastSeen
1640
+ ]
1641
+ })]
1642
+ }),
1643
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1644
+ className: "pocket-device-actions",
1645
+ style: styles.deviceActions,
1646
+ children: editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1647
+ type: "button",
1648
+ style: styles.outlineBtnSm,
1649
+ disabled: busy,
1650
+ onClick: () => void save(),
1651
+ children: t("renameSave")
1652
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1653
+ type: "button",
1654
+ style: styles.textBtn,
1655
+ disabled: busy,
1656
+ onClick: () => {
1657
+ setEditing(false);
1658
+ setName(device.name);
1659
+ },
1660
+ children: t("renameCancel")
1661
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1662
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1663
+ type: "button",
1664
+ style: styles.textBtn,
1665
+ disabled: busy,
1666
+ onClick: () => setEditing(true),
1667
+ children: t("rename")
1668
+ }),
1669
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: styles.divider }),
1670
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1671
+ type: "button",
1672
+ style: styles.textBtn,
1673
+ disabled: busy,
1674
+ onClick: () => setConfirmOpen(true),
1675
+ children: t("revoke")
1676
+ })
1677
+ ] })
1678
+ }),
1679
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
1680
+ open: confirmOpen,
1681
+ title: t("revokeTitle"),
1682
+ body: t("revokeBody", { name: device.name }).replaceAll("{name}", device.name),
1683
+ cancelLabel: t("cancel"),
1684
+ confirmLabel: t("revoke"),
1685
+ busy,
1686
+ onCancel: () => {
1687
+ if (!busy) setConfirmOpen(false);
1688
+ },
1689
+ onConfirm: () => void revoke()
1690
+ })
1691
+ ]
1692
+ });
1693
+ }
1694
+ function ManagePanel({ t, api }) {
1695
+ const [status, setStatus] = (0, react.useState)(null);
1696
+ const [error, setError] = (0, react.useState)(null);
1697
+ const [loading, setLoading] = (0, react.useState)(true);
1698
+ const [busy, setBusy] = (0, react.useState)(false);
1699
+ const [copied, setCopied] = (0, react.useState)(null);
1700
+ const network = (0, react.useRef)(void 0);
1701
+ const request = (0, react.useRef)(0);
1702
+ const copyTimer = (0, react.useRef)(void 0);
1703
+ const refresh = (0, react.useCallback)(async (background = false) => {
1704
+ const id = ++request.current;
1705
+ if (!background) setLoading(true);
1706
+ try {
1707
+ const s = await api.status(network.current);
1708
+ if (id !== request.current) return;
1709
+ setStatus(s);
1710
+ setError(null);
1711
+ } catch (e) {
1712
+ if (id !== request.current) return;
1713
+ setStatus(null);
1714
+ setError(String(e?.message ?? e));
1715
+ } finally {
1716
+ if (id === request.current) setLoading(false);
1717
+ }
1718
+ }, [api]);
1719
+ (0, react.useEffect)(() => {
1720
+ refresh();
1721
+ return () => {
1722
+ request.current++;
1723
+ clearTimeout(copyTimer.current);
1724
+ };
1725
+ }, [refresh]);
1726
+ (0, react.useEffect)(() => {
1727
+ if (!status?.pairing.open) return;
1728
+ const timer = setInterval(() => {
1729
+ refresh(true);
1730
+ }, 1e3);
1731
+ return () => clearInterval(timer);
1732
+ }, [status?.pairing.open, refresh]);
1733
+ const changeNetwork = (next) => {
1734
+ network.current = next;
1735
+ setCopied(null);
1736
+ setStatus((s) => s ? {
1737
+ ...s,
1738
+ pairing: { open: false }
1739
+ } : s);
1740
+ refresh();
1741
+ };
1742
+ const updatePairing = async (open) => {
1743
+ setBusy(true);
1744
+ setError(null);
1745
+ try {
1746
+ if (open) await api.openPair(network.current);
1747
+ else await api.closePair();
1748
+ await refresh();
1749
+ } catch (e) {
1750
+ setError(String(e?.message ?? e));
1751
+ } finally {
1752
+ setBusy(false);
1753
+ }
1754
+ };
1755
+ const copy = async (text$1, key) => {
1756
+ try {
1757
+ await navigator.clipboard.writeText(text$1);
1758
+ clearTimeout(copyTimer.current);
1759
+ setCopied(key);
1760
+ copyTimer.current = window.setTimeout(() => setCopied(null), 1500);
1761
+ } catch (e) {
1762
+ setError(String(e?.message ?? e));
1763
+ }
1764
+ };
1765
+ const pairing = status?.pairing;
1766
+ const selected = status?.connectionType;
1767
+ const pairReady = Boolean(status?.running && status.networks.length && status.host);
1768
+ const pairingOpen = Boolean(pairReady && pairing?.open && pairing.code && pairing.uri);
1769
+ const address = status?.host ? `${status.host}:${status.port}` : "";
1770
+ const disabled = loading || busy;
1771
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1772
+ "data-pocket-panel": true,
1773
+ style: styles.settingsRoot,
1774
+ children: [
1775
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: panelCss }),
1776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1777
+ style: styles.header,
1778
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1779
+ style: styles.title,
1780
+ children: t("title")
1781
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1782
+ style: styles.service,
1783
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusDot, { state: pairReady ? "done" : "idle" }), loading && !status ? t("loading") : t(pairReady ? "statusRunning" : "statusStopped")]
1784
+ })]
1785
+ }),
1786
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1787
+ role: "alert",
1788
+ style: styles.errorText,
1789
+ children: error
1790
+ }) : null,
1791
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1792
+ style: styles.pairingCard,
1793
+ "aria-label": t("newDevice"),
1794
+ children: [
1795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1796
+ style: {
1797
+ ...styles.pairingTop,
1798
+ ...pairingOpen ? { minHeight: 0 } : {}
1799
+ },
1800
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1801
+ style: styles.pairControls,
1802
+ children: [
1803
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1804
+ style: styles.rowTitle,
1805
+ children: t("newDevice")
1806
+ }),
1807
+ status && status.networks.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1808
+ style: styles.segments,
1809
+ role: "group",
1810
+ "aria-label": t("network"),
1811
+ children: status.networks.map((n) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1812
+ type: "button",
1813
+ "aria-pressed": selected === n.type,
1814
+ style: {
1815
+ ...styles.segment,
1816
+ ...selected === n.type ? styles.segmentActive : {}
1817
+ },
1818
+ disabled,
1819
+ onClick: () => changeNetwork(n.type),
1820
+ children: t(n.type)
1821
+ }, n.type))
1822
+ }) : null,
1823
+ !pairingOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1824
+ type: "button",
1825
+ style: styles.primaryBtn,
1826
+ disabled: disabled || !pairReady,
1827
+ onClick: () => void updatePairing(true),
1828
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "plus" }), t("pair")]
1829
+ }) : null
1830
+ ]
1831
+ }), !pairingOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1832
+ className: "pocket-illustration",
1833
+ style: styles.illustration,
1834
+ viewBox: "0 0 180 130",
1835
+ fill: "none",
1836
+ stroke: "currentColor",
1837
+ strokeWidth: "2",
1838
+ strokeLinecap: "round",
1839
+ strokeLinejoin: "round",
1840
+ "aria-hidden": "true",
1841
+ children: [
1842
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M20 94V22a8 8 0 0 1 8-8h105a8 8 0 0 1 8 8v11M121 94H7v8h114" }),
1843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1844
+ x: "132",
1845
+ y: "43",
1846
+ width: "40",
1847
+ height: "75",
1848
+ rx: "8"
1849
+ }),
1850
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M148 50h9m-8 59h7" })
1851
+ ]
1852
+ }) : null]
1853
+ }),
1854
+ pairingOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1855
+ style: styles.pairingContent,
1856
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1857
+ style: styles.qrBox,
1858
+ role: "img",
1859
+ "aria-label": t("code"),
1860
+ dangerouslySetInnerHTML: { __html: qrSvg(pairing.uri, 168) }
1861
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
1862
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1863
+ style: styles.code,
1864
+ children: pairing.code
1865
+ }),
1866
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1867
+ style: styles.hint,
1868
+ children: t("expiresIn", { n: Math.max(0, pairing.expiresInSec ?? 0) })
1869
+ }),
1870
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1871
+ style: styles.actionRow,
1872
+ children: [
1873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1874
+ type: "button",
1875
+ style: styles.outlineBtn,
1876
+ onClick: () => void copy(pairing.pairingString || pairing.uri, "pair"),
1877
+ children: t(copied === "pair" ? "copied" : "copy")
1878
+ }),
1879
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1880
+ type: "button",
1881
+ style: styles.textBtn,
1882
+ disabled,
1883
+ onClick: () => void updatePairing(false),
1884
+ children: t("pairClose")
1885
+ }),
1886
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1887
+ type: "button",
1888
+ style: styles.textBtn,
1889
+ disabled,
1890
+ onClick: () => void updatePairing(true),
1891
+ children: t("pairAgain")
1892
+ })
1893
+ ]
1894
+ })
1895
+ ] })]
1896
+ }) : null,
1897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
1898
+ style: styles.footer,
1899
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1900
+ style: styles.address,
1901
+ children: address ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1902
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(selected === "tailscale" ? "tailscaleAddress" : "lanAddress") }),
1903
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1904
+ style: styles.addressValue,
1905
+ children: address
1906
+ }),
1907
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1908
+ type: "button",
1909
+ style: styles.iconBtn,
1910
+ title: t(copied === "address" ? "copied" : "copyAddress"),
1911
+ "aria-label": t(copied === "address" ? "copied" : "copyAddress"),
1912
+ onClick: () => void copy(address, "address"),
1913
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
1914
+ name: copied === "address" ? "check" : "copy",
1915
+ size: 16
1916
+ })
1917
+ })
1918
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(loading ? "loading" : "noNetwork") })
1919
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1920
+ style: styles.footerActions,
1921
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1922
+ type: "button",
1923
+ style: styles.iconBtn,
1924
+ disabled,
1925
+ title: t("refresh"),
1926
+ "aria-label": t("refresh"),
1927
+ onClick: () => {
1928
+ if (!status) network.current = void 0;
1929
+ refresh();
1930
+ },
1931
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "refresh" })
1932
+ })
1933
+ })]
1934
+ })
1935
+ ]
1936
+ }),
1937
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1938
+ style: styles.devicesSection,
1939
+ "aria-label": t("devices"),
1940
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1941
+ style: styles.devicesHeader,
1942
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1943
+ style: styles.rowTitle,
1944
+ children: t("devices")
1945
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1946
+ style: styles.count,
1947
+ children: status?.devices.length ?? 0
1948
+ })]
1949
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1950
+ style: styles.deviceList,
1951
+ children: status?.devices.length ? status.devices.map((device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItem, {
1952
+ device,
1953
+ t,
1954
+ api,
1955
+ onError: setError,
1956
+ onChanged: (devices) => setStatus((s) => s ? {
1957
+ ...s,
1958
+ devices
1959
+ } : s)
1960
+ }, device.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1961
+ style: styles.hint,
1962
+ children: t(loading ? "loading" : "empty")
1963
+ })
1964
+ })]
1965
+ })
1966
+ ]
1967
+ });
1968
+ }
1969
+
1970
+ //#endregion
1971
+ //#region src/client/PocketSettings.tsx
1972
+ function PocketSettings({ t, api }) {
1973
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ManagePanel, {
1974
+ t,
1975
+ api
1976
+ });
1977
+ }
1978
+
1979
+ //#endregion
1980
+ //#region src/client/host-ui-styles.ts
1981
+ /** Scoped to this WebView window; no generated classes or plugin-specific selectors. */
1982
+ const css = `
1983
+ @media (max-width: 700px) {
1984
+ html[data-pocket-ui] [data-shortcut-modal="settings"] {
1985
+ box-sizing: border-box;
1986
+ flex-direction: column;
1987
+ width: 100%;
1988
+ max-width: 100%;
1989
+ height: 100dvh;
1990
+ max-height: 100dvh;
1991
+ padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
1992
+ border-radius: 0;
1993
+ }
1994
+ html[data-pocket-ui] [data-shortcut-modal="settings"] > nav {
1995
+ width: 100%;
1996
+ min-width: 0;
1997
+ padding: 12px 12px 0;
1998
+ gap: 8px;
1999
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
2000
+ }
2001
+ html[data-pocket-ui] [data-shortcut-modal="settings"] > nav > div:last-child {
2002
+ flex-direction: row;
2003
+ overflow-x: auto;
2004
+ overflow-y: hidden;
2005
+ padding-bottom: 8px;
2006
+ overscroll-behavior-x: contain;
2007
+ }
2008
+ html[data-pocket-ui] [data-shortcut-modal="settings"] > nav button {
2009
+ flex: 0 0 auto;
2010
+ min-height: 44px;
2011
+ white-space: nowrap;
2012
+ }
2013
+ html[data-pocket-ui] [data-shortcut-modal="settings"] > nav + div {
2014
+ min-width: 0;
2015
+ min-height: 0;
2016
+ width: 100%;
2017
+ }
2018
+ html[data-pocket-ui] [data-shortcut-modal="settings"] > nav + div > div:last-child {
2019
+ min-width: 0;
2020
+ min-height: 0;
2021
+ padding-left: 16px;
2022
+ padding-right: 16px;
2023
+ overflow: auto;
2024
+ overscroll-behavior: contain;
2025
+ }
2026
+ }
2027
+ `;
2028
+ function installHostUiStyles() {
2029
+ const root = document.documentElement;
2030
+ const previous = root.getAttribute("data-pocket-ui");
2031
+ const style = document.createElement("style");
2032
+ style.dataset.pocketUiStyles = "";
2033
+ style.textContent = css;
2034
+ root.setAttribute("data-pocket-ui", "");
2035
+ document.head.append(style);
2036
+ return () => {
2037
+ style.remove();
2038
+ if (previous === null) root.removeAttribute("data-pocket-ui");
2039
+ else root.setAttribute("data-pocket-ui", previous);
2040
+ };
2041
+ }
2042
+
2043
+ //#endregion
2044
+ //#region src/client/HostUiLauncher.tsx
2045
+ /**
2046
+ * The shell publishes its store on the settings.launcher-owning sidebar entry.
2047
+ * Its create() returns the SAME root-scoped instance used by the renderer and
2048
+ * keyboard command (ui-settings-general), not a second private React tree.
2049
+ */
2050
+ function sectionNavigator(slots) {
2051
+ const store = slots.entries("sidebar.settings").find((entry) => entry.children?.["settings.launcher"])?.store;
2052
+ if (typeof store !== "object" || store === null || !("create" in store) || typeof store.create !== "function") return void 0;
2053
+ const instance = store.create();
2054
+ if (typeof instance?.actions?.openSection !== "function") return void 0;
2055
+ return (id) => {
2056
+ instance.actions.openSection(id);
2057
+ };
2058
+ }
2059
+ /** A temporary launcher occupant; the official launcher resumes after navigation. */
2060
+ function HostUiLauncher({ section, slots, finish, openSettings, useSessionReadiness }) {
2061
+ const ready = useSessionReadiness((state) => state.phase === "ready");
2062
+ (0, react.useEffect)(() => {
2063
+ let finished = false;
2064
+ let shown = false;
2065
+ const navigate = () => {
2066
+ if (!ready || finished) return;
2067
+ if (!shown) {
2068
+ shown = true;
2069
+ queueMicrotask(() => {
2070
+ if (shown) openSettings();
2071
+ });
2072
+ }
2073
+ if (!slots.entries("settings.section").some((entry) => entry.options.id === section)) return;
2074
+ const openSection = sectionNavigator(slots);
2075
+ if (!openSection) return;
2076
+ finished = true;
2077
+ queueMicrotask(() => {
2078
+ if (!finished) return;
2079
+ openSection(section);
2080
+ finish();
2081
+ });
2082
+ };
2083
+ const offSections = slots.subscribe("settings.section", navigate);
2084
+ const offShell = slots.subscribe("sidebar.settings", navigate);
2085
+ navigate();
2086
+ return () => {
2087
+ finished = false;
2088
+ shown = false;
2089
+ offSections();
2090
+ offShell();
2091
+ };
2092
+ }, [
2093
+ section,
2094
+ slots,
2095
+ finish,
2096
+ openSettings,
2097
+ ready
2098
+ ]);
2099
+ return null;
2100
+ }
2101
+ /** Returns false on ordinary desktop windows, leaving all existing registrations intact. */
2102
+ function installHostUi(ctx) {
2103
+ const query = new URLSearchParams(window.location.search);
2104
+ if (query.get("pocket-ui") !== "1") return false;
2105
+ ctx.effect(() => installHostUiStyles(), "pocket:host-ui-styles");
2106
+ const section = query.get("section");
2107
+ if (!section) return true;
2108
+ let completed = false;
2109
+ ctx.inject(["sessions"], (scope) => {
2110
+ scope.slots.inject("settings.launcher", () => {
2111
+ if (completed) return;
2112
+ const finish = () => {
2113
+ completed = true;
2114
+ unregister();
2115
+ };
2116
+ const unregister = scope.slots.register({
2117
+ name: "settings.launcher",
2118
+ priority: -100,
2119
+ inject: () => ({
2120
+ section,
2121
+ slots: scope.slots,
2122
+ finish,
2123
+ hooks: { sessionReadiness: scope.sessions.list }
2124
+ })
2125
+ }, HostUiLauncher);
2126
+ return unregister;
2127
+ });
2128
+ });
2129
+ return true;
2130
+ }
2131
+
2132
+ //#endregion
2133
+ //#region src/client/completion-sync.js
2134
+ /** Overlay Host unread onto official sessionStatus; Host is source of truth for the green-dot bit. */
2135
+ function overlayCompletionUnread(official, unread) {
2136
+ const next = /* @__PURE__ */ new Map();
2137
+ const ids = new Set(unread);
2138
+ if (official && typeof official.keys === "function") for (const id of official.keys()) ids.add(id);
2139
+ for (const id of ids) {
2140
+ const base = official?.get?.(id);
2141
+ next.set(id, {
2142
+ running: base?.running,
2143
+ pendingInteraction: base?.pendingInteraction,
2144
+ completionUnread: unread.has(id)
2145
+ });
2146
+ }
2147
+ return next;
2148
+ }
2149
+ /**
2150
+ * Official ui-session@0.2.1-alpha.2 has no setCompletionUnreadSource. Its public
2151
+ * sessionStatus HostObservable (and the instance listener Set it closes over)
2152
+ * is the mounted extension point: keep running/pending from official, replace
2153
+ * completionUnread with the Host snapshot, and notify the same subscribers
2154
+ * StateDot already uses. Keep the derived snapshot stable until either input
2155
+ * snapshot changes. Missing that surface is a hard error — do not no-op.
2156
+ */
2157
+ function overlaySessionStatusCompletionUnread(sessionStatus, source, listenerSet) {
2158
+ if (!sessionStatus || typeof sessionStatus.getSnapshot !== "function" || typeof sessionStatus.subscribe !== "function") throw new Error("uiSession.sessionStatus is not an overlayable HostObservable");
2159
+ if (!(listenerSet instanceof Set)) throw new Error("uiSession status listeners are not reachable; cannot sync completion unread");
2160
+ const origGet = sessionStatus.getSnapshot.bind(sessionStatus);
2161
+ let official = origGet();
2162
+ let unread = source.getSnapshot();
2163
+ let snapshot = overlayCompletionUnread(official, unread);
2164
+ sessionStatus.getSnapshot = () => {
2165
+ const nextOfficial = origGet();
2166
+ const nextUnread = source.getSnapshot();
2167
+ if (nextOfficial !== official || nextUnread !== unread) {
2168
+ official = nextOfficial;
2169
+ unread = nextUnread;
2170
+ snapshot = overlayCompletionUnread(official, unread);
2171
+ }
2172
+ return snapshot;
2173
+ };
2174
+ const notify = () => {
2175
+ for (const listener of [...listenerSet]) listener();
2176
+ };
2177
+ const off = source.subscribe(notify);
2178
+ notify();
2179
+ return () => {
2180
+ off();
2181
+ sessionStatus.getSnapshot = origGet;
2182
+ };
2183
+ }
2184
+ /** Prefer an official hook when present; otherwise overlay mounted sessionStatus. */
2185
+ function bindCompletionUnreadSource(uiSession, source) {
2186
+ if (typeof uiSession?.setCompletionUnreadSource === "function") return uiSession.setCompletionUnreadSource(source);
2187
+ return overlaySessionStatusCompletionUnread(uiSession?.sessionStatus, source, uiSession?.statusListeners);
2188
+ }
2189
+ /** Host completion snapshots and view presence, independent of rendering and transport mocks. */
2190
+ function createCompletionSync({ connect, postView, installSource, visibleSession, reportError = console.error }) {
2191
+ let snapshot;
2192
+ let unread = /* @__PURE__ */ new Set();
2193
+ const listeners = /* @__PURE__ */ new Set();
2194
+ const source = {
2195
+ getSnapshot: () => unread,
2196
+ subscribe: (listener) => {
2197
+ listeners.add(listener);
2198
+ return () => listeners.delete(listener);
2199
+ }
2200
+ };
2201
+ let uninstall;
2202
+ let clientId = null;
2203
+ let generation = 0;
2204
+ let disposed = false;
2205
+ let suspended = false;
2206
+ let stream;
2207
+ let carrier = 0;
2208
+ let desired;
2209
+ let queue = [];
2210
+ let pending;
2211
+ const accept = (value, allowEpoch) => {
2212
+ if (!value || typeof value.epoch !== "string" || !value.epoch || !Number.isSafeInteger(value.revision) || value.revision < 0 || !Array.isArray(value.unreadIds) || value.unreadIds.some((id) => typeof id !== "string" || !id)) throw new Error("Invalid completion snapshot");
2213
+ if (snapshot?.epoch !== value.epoch && snapshot && !allowEpoch) return false;
2214
+ if (snapshot?.epoch === value.epoch && snapshot.revision > value.revision) return false;
2215
+ if (snapshot?.epoch !== value.epoch || snapshot?.revision !== value.revision) {
2216
+ snapshot = value;
2217
+ unread = new Set(value.unreadIds);
2218
+ for (const listener of listeners) listener();
2219
+ }
2220
+ return true;
2221
+ };
2222
+ const disconnect = () => {
2223
+ generation++;
2224
+ clientId = null;
2225
+ desired = void 0;
2226
+ queue = [];
2227
+ pending?.controller.abort();
2228
+ pending = void 0;
2229
+ };
2230
+ const drain = async () => {
2231
+ if (disposed || !clientId || pending || !queue.length) return;
2232
+ const request = {
2233
+ generation,
2234
+ controller: new AbortController()
2235
+ };
2236
+ pending = request;
2237
+ const id = clientId;
2238
+ const sessionId = queue.shift();
2239
+ let received = false;
2240
+ try {
2241
+ const value = await postView(id, sessionId, request.controller.signal);
2242
+ received = true;
2243
+ if (!disposed && generation === request.generation && clientId === id) accept(value, false);
2244
+ } catch (error) {
2245
+ if (!disposed && generation === request.generation) {
2246
+ disconnect();
2247
+ carrier++;
2248
+ stream.close();
2249
+ reportError(error);
2250
+ if (!received) openStream();
2251
+ }
2252
+ } finally {
2253
+ if (pending === request) {
2254
+ pending = void 0;
2255
+ drain();
2256
+ }
2257
+ }
2258
+ };
2259
+ const updateView = () => {
2260
+ if (disposed || !clientId) return;
2261
+ const next = visibleSession();
2262
+ if (next === desired) return;
2263
+ desired = next;
2264
+ queue.push(next);
2265
+ drain();
2266
+ };
2267
+ const openStream = () => {
2268
+ const token = ++carrier;
2269
+ stream = connect({
2270
+ message(value) {
2271
+ if (disposed || suspended || token !== carrier) return;
2272
+ try {
2273
+ if (typeof value?.clientId !== "string" || !value.clientId) throw new Error("Invalid completion client identity");
2274
+ if (clientId !== null && clientId !== value.clientId) return;
2275
+ if (!accept(value, clientId === null)) return;
2276
+ clientId = value.clientId;
2277
+ if (!uninstall) uninstall = installSource(source);
2278
+ updateView();
2279
+ } catch (error) {
2280
+ disconnect();
2281
+ carrier++;
2282
+ stream.close();
2283
+ reportError(error);
2284
+ }
2285
+ },
2286
+ disconnect() {
2287
+ if (!disposed && !suspended && token === carrier) disconnect();
2288
+ }
2289
+ });
2290
+ };
2291
+ openStream();
2292
+ return {
2293
+ updateView,
2294
+ suspend() {
2295
+ if (disposed || suspended) return;
2296
+ suspended = true;
2297
+ disconnect();
2298
+ carrier++;
2299
+ stream.close();
2300
+ },
2301
+ resume() {
2302
+ if (disposed || !suspended) return;
2303
+ suspended = false;
2304
+ openStream();
2305
+ },
2306
+ dispose() {
2307
+ if (disposed) return;
2308
+ disposed = true;
2309
+ disconnect();
2310
+ stream.close();
2311
+ uninstall?.();
2312
+ listeners.clear();
2313
+ }
2314
+ };
2315
+ }
2316
+ /** Observe only already-retained, loaded main-view content; never open or select a Session. */
2317
+ function installCompletionSync({ uiSession, sessions, layout, slots, window: win = window, document: doc = document, fetch: request = fetch, EventSource: Events = EventSource, reportError = console.error }) {
2318
+ let binding;
2319
+ let settings;
2320
+ let offSession = () => {};
2321
+ let offRetain = () => {};
2322
+ let offSettings = () => {};
2323
+ const visibleSession = () => {
2324
+ if (doc.visibilityState !== "visible" || !doc.hasFocus() || sessions.list.getSnapshot().phase !== "ready") return null;
2325
+ if (layout.panelInfo.getSnapshot().activePanelId !== null || settings?.getSnapshot().open) return null;
2326
+ if (!binding || (sessions.retainInfo(binding.sessionId).getSnapshot().retainedBy.mainView ?? 0) <= 0) return null;
2327
+ const state = binding.session.getSnapshot();
2328
+ return state.openState === "open" && !state.removed ? binding.sessionId : null;
2329
+ };
2330
+ const sync = createCompletionSync({
2331
+ visibleSession,
2332
+ reportError,
2333
+ installSource: (source) => bindCompletionUnreadSource(uiSession, source),
2334
+ connect({ message, disconnect }) {
2335
+ const events = new Events("/pocket/completion/events");
2336
+ events.onmessage = (event) => {
2337
+ try {
2338
+ message(JSON.parse(event.data));
2339
+ } catch (error) {
2340
+ disconnect();
2341
+ events.close();
2342
+ reportError(error);
2343
+ }
2344
+ };
2345
+ events.onerror = disconnect;
2346
+ return { close: () => events.close() };
2347
+ },
2348
+ async postView(clientId, sessionId, signal) {
2349
+ const response = await request("/pocket/completion/view", {
2350
+ method: "POST",
2351
+ headers: { "content-type": "application/json" },
2352
+ body: JSON.stringify({
2353
+ clientId,
2354
+ sessionId
2355
+ }),
2356
+ signal
2357
+ });
2358
+ if (!response.ok) throw new Error(`Completion view failed: HTTP ${response.status}`);
2359
+ return response.json();
2360
+ }
2361
+ });
2362
+ const watchMain = () => {
2363
+ const id = uiSession.adapter.current.getSnapshot().key;
2364
+ const next = id === void 0 ? void 0 : sessions.binding(id);
2365
+ if (next !== binding) {
2366
+ offSession();
2367
+ offRetain();
2368
+ binding = next;
2369
+ offSession = binding ? binding.session.subscribe(sync.updateView) : () => {};
2370
+ offRetain = binding ? sessions.retainInfo(binding.sessionId).subscribe(sync.updateView) : () => {};
2371
+ }
2372
+ sync.updateView();
2373
+ };
2374
+ const watchSettings = () => {
2375
+ const next = slots.entries("sidebar.settings").find((entry) => entry.children?.["settings.launcher"])?.store?.create();
2376
+ if (next !== settings) {
2377
+ offSettings();
2378
+ settings = next;
2379
+ offSettings = settings ? settings.subscribe(sync.updateView) : () => {};
2380
+ }
2381
+ sync.updateView();
2382
+ };
2383
+ const offMain = uiSession.adapter.current.subscribe(watchMain);
2384
+ const offList = sessions.list.subscribe(watchMain);
2385
+ const offSettingsOwner = slots.subscribe("sidebar.settings", watchSettings);
2386
+ const offPanel = layout.panelInfo.subscribe(sync.updateView);
2387
+ doc.addEventListener("visibilitychange", sync.updateView);
2388
+ win.addEventListener("focus", sync.updateView);
2389
+ win.addEventListener("blur", sync.updateView);
2390
+ win.addEventListener("pagehide", sync.suspend);
2391
+ win.addEventListener("pageshow", sync.resume);
2392
+ watchSettings();
2393
+ watchMain();
2394
+ return () => {
2395
+ offMain();
2396
+ offList();
2397
+ offSettingsOwner();
2398
+ offSettings();
2399
+ offPanel();
2400
+ offSession();
2401
+ offRetain();
2402
+ doc.removeEventListener("visibilitychange", sync.updateView);
2403
+ win.removeEventListener("focus", sync.updateView);
2404
+ win.removeEventListener("blur", sync.updateView);
2405
+ win.removeEventListener("pagehide", sync.suspend);
2406
+ win.removeEventListener("pageshow", sync.resume);
2407
+ sync.dispose();
2408
+ };
2409
+ }
2410
+
2411
+ //#endregion
2412
+ //#region src/client/index.ts
2413
+ /** Must be a static array — cordis rejects function-form inject. */
2414
+ const inject = ["slots", "locale"];
2415
+ const NS = "pocket";
2416
+ function apply(ctx) {
2417
+ if (installHostUi(ctx)) return;
2418
+ ctx.inject([
2419
+ "uiSession",
2420
+ "sessions",
2421
+ "layout"
2422
+ ], (scope) => {
2423
+ const uiSession = scope.get("uiSession");
2424
+ const sessions = scope.get("sessions");
2425
+ scope.effect(() => installCompletionSync({
2426
+ uiSession,
2427
+ sessions,
2428
+ layout: scope.get("layout"),
2429
+ slots: scope.slots
2430
+ }), "pocket:completion-sync");
2431
+ });
2432
+ ctx.effect(() => ctx.locale.register(NS, {
2433
+ zh,
2434
+ en
2435
+ }), "pocket:locale");
2436
+ ctx.effect(() => installPocketNavIcon(), "pocket:nav-icon");
2437
+ const t = ctx.locale.bind(NS);
2438
+ const api = createPocketApi();
2439
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
2440
+ name: "settings.section",
2441
+ id: "pocket",
2442
+ order: 55,
2443
+ label: () => t("entry"),
2444
+ locale: NS,
2445
+ inject: () => ({ api })
2446
+ }, PocketSettings));
2447
+ }
2448
+
2449
+ //#endregion
2450
+ exports.apply = apply;
2451
+ exports.inject = inject;
2452
+ return module.exports; } });
2453
+ //# sourceMappingURL=client.js.map