@decentnetwork/beagle 0.1.60 → 0.1.63

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.
@@ -1,324 +1,7 @@
1
- window.__DK_UI_VERSION="0.1.60";
1
+ window.__DK_UI_VERSION="0.1.63";
2
2
  "use strict";
3
3
  (() => {
4
4
  // packages/beagle-ui/dist/beagle-ui.js
5
- var ICON_PATHS = {
6
- // ---- tab bar (the four must feel like one set) ----
7
- users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
8
- grid: '<rect x="3" y="3" width="7.5" height="7.5" rx="1.6"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.6"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.6"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.6"/>',
9
- wallet: '<path d="M19 7V6a2 2 0 0 0-2-2H6a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h12a2 2 0 0 0 2-2v-1"/><path d="M3 7a3 3 0 0 0 3 3h13a2 2 0 0 1 2 2v0a2 2 0 0 1-2 2h-3"/><circle cx="16.5" cy="14" r="1.15" fill="currentColor" stroke="none"/>',
10
- userRound: '<circle cx="12" cy="8" r="4.5"/><path d="M20 21a8 8 0 0 0-16 0"/>',
11
- // ---- actions / chrome ----
12
- search: '<circle cx="11" cy="11" r="7.5"/><path d="m21 21-4.3-4.3"/>',
13
- plus: '<path d="M5 12h14M12 5v14"/>',
14
- chevronRight: '<path d="m9 18 6-6-6-6"/>',
15
- chevronLeft: '<path d="m15 18-6-6 6-6"/>',
16
- info: '<circle cx="12" cy="12" r="9.5"/><path d="M12 16.5v-5M12 7.6h.01"/>',
17
- more: '<circle cx="5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.4" fill="currentColor" stroke="none"/>',
18
- x: '<path d="M18 6 6 18M6 6l12 12"/>',
19
- check: '<path d="M20 6 9 17l-5-5"/>',
20
- checkCheck: '<path d="M18 6 7 17l-5-5"/><path d="m22 10-7.6 7.6L13 16"/>',
21
- copy: '<rect x="9" y="9" width="12" height="12" rx="2.4"/><path d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1"/>',
22
- // Three linked nodes — an address is for passing ON to someone else, so it
23
- // must not wear the same glyph as the userid's plain copy.
24
- share: '<circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.3M8.3 13.2l7.4 4.3"/>',
25
- refresh: '<path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 4v4.5h-4.5"/>',
26
- clock: '<circle cx="12" cy="12" r="9.5"/><path d="M12 7.5V12l3 2"/>',
27
- edit: '<path d="M11 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4Z"/>',
28
- qr: '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><path d="M14 14h3v3M21 14v.01M14 21h.01M17 21h.01M21 17v4"/>',
29
- // ---- app tiles ----
30
- video: '<path d="m22 8.5-5.4 3.5 5.4 3.5V8.5Z"/><rect x="2" y="6" width="14.5" height="12" rx="3"/>',
31
- monitor: '<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>',
32
- sparkles: '<path d="M12 3.5 13.6 9 19 10.5 13.6 12 12 17.5 10.4 12 5 10.5 10.4 9 12 3.5Z"/><path d="M19 3.5v3.2M20.6 5.1h-3.2"/><path d="M5 16v2.4M6.2 17.2H3.8"/>',
33
- mic: '<rect x="9" y="2.5" width="6" height="11.5" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0"/><path d="M12 17.5v3.5"/>',
34
- flask: '<path d="M9.5 3v6.2L4.8 17.4A2 2 0 0 0 6.5 20.5h11a2 2 0 0 0 1.7-3.1L14.5 9.2V3"/><path d="M8.5 3h7"/><path d="M7.3 15h9.4"/>',
35
- headphones: '<path d="M3.5 14v-1.5a8.5 8.5 0 0 1 17 0V14"/><path d="M3.5 14.5a2.5 2.5 0 0 1 2.5-2.5h0a1.5 1.5 0 0 1 1.5 1.5v3.5a1.5 1.5 0 0 1-1.5 1.5h0a2.5 2.5 0 0 1-2.5-2.5Z"/><path d="M20.5 14.5a2.5 2.5 0 0 0-2.5-2.5h0a1.5 1.5 0 0 0-1.5 1.5v3.5a1.5 1.5 0 0 0 1.5 1.5h0a2.5 2.5 0 0 0 2.5-2.5Z"/>',
36
- globe: '<circle cx="12" cy="12" r="9.5"/><path d="M2.5 12h19"/><path d="M12 2.5a14 14 0 0 1 0 19 14 14 0 0 1 0-19Z"/>',
37
- store: '<path d="M4 9.5V19a1.5 1.5 0 0 0 1.5 1.5h13A1.5 1.5 0 0 0 20 19V9.5"/><path d="M3 6.5 4.5 3.5h15L21 6.5a2.5 2.5 0 0 1-4.5 1.5 2.5 2.5 0 0 1-4.5 0 2.5 2.5 0 0 1-4.5 0A2.5 2.5 0 0 1 3 6.5Z"/>',
38
- // ---- chat composer ----
39
- camera: '<path d="M14.5 4.5h-5L7.2 7.2H4.5A2.5 2.5 0 0 0 2 9.7V18a2.5 2.5 0 0 0 2.5 2.5h15A2.5 2.5 0 0 0 22 18V9.7a2.5 2.5 0 0 0-2.5-2.5h-2.7L14.5 4.5Z"/><circle cx="12" cy="13.5" r="3.2"/>',
40
- phone: '<path d="M16.5 21a2 2 0 0 0 2-2.2 1.9 1.9 0 0 0-.6-1.2l-2-1.7a2 2 0 0 0-2.4-.1l-.9.6a14 14 0 0 1-5-5l.6-.9a2 2 0 0 0-.1-2.4l-1.7-2a1.9 1.9 0 0 0-1.2-.6A2 2 0 0 0 3 5.5 16 16 0 0 0 16.5 21Z"/>',
41
- bolt: '<path d="M13 2 4 13h7l-1 9 9-11h-7l1-9Z"/>',
42
- arrowUp: '<path d="M12 20V5M6 11l6-6 6 6"/>',
43
- send: '<path d="M21.5 4.5 10.5 13.5"/><path d="M21.5 4.5 14.8 21.5 11 13l-8.5-3.8 19-4.7Z"/>',
44
- // ---- account ----
45
- shield: '<path d="M12 21.5s7.5-3.8 7.5-9.5V5.5L12 2.8 4.5 5.5V12c0 5.7 7.5 9.5 7.5 9.5Z"/>',
46
- download: '<path d="M12 3.5v11M7.5 10l4.5 4.5 4.5-4.5"/><path d="M5 20.5h14"/>',
47
- // Folder with a magnifier corner — "reveal / show in file manager".
48
- folderOpen: '<path d="M3 7.5a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/><circle cx="12" cy="13" r="2.4"/><path d="M13.9 14.9 15.5 16.5"/>',
49
- migrate: '<path d="M8 4 4 8l4 4"/><path d="M4 8h13a3 3 0 0 1 3 3v1"/><path d="m16 20 4-4-4-4"/><path d="M20 16H7a3 3 0 0 1-3-3v-1"/>',
50
- starCoin: '<circle cx="12" cy="12" r="9"/><path d="m12 7.2 1.4 2.9 3.2.4-2.3 2.2.6 3.1-2.9-1.5-2.9 1.5.6-3.1L8.4 10.5l3.2-.4L12 7.2Z" fill="currentColor" stroke="none"/>',
51
- link: '<path d="M10.5 13.5a4 4 0 0 0 5.7 0l2.3-2.3a4 4 0 0 0-5.7-5.7l-1 1"/><path d="M13.5 10.5a4 4 0 0 0-5.7 0l-2.3 2.3a4 4 0 0 0 5.7 5.7l1-1"/>',
52
- trendingUp: '<path d="M22 7.5 13.5 16l-4.5-4.5L2 18.5"/><path d="M16.5 7.5H22v5.5"/>',
53
- bell: '<path d="M18 8.5a6 6 0 0 0-12 0c0 6.5-2.5 8-2.5 8h17S18 15 18 8.5Z"/><path d="M13.7 20.5a2 2 0 0 1-3.4 0"/>',
54
- bot: '<rect x="3.5" y="9" width="17" height="11" rx="3"/><path d="M12 4.5v4.5"/><circle cx="12" cy="3" r="1.4"/><path d="M8.5 14.5h.01M15.5 14.5h.01"/><path d="M2 13.5v3M22 13.5v3"/>',
55
- scan: '<path d="M3 8V6a3 3 0 0 1 3-3h2"/><path d="M16 3h2a3 3 0 0 1 3 3v2"/><path d="M21 16v2a3 3 0 0 1-3 3h-2"/><path d="M8 21H6a3 3 0 0 1-3-3v-2"/><path d="M7 12h10"/>',
56
- userPlus: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M19 8v6M22 11h-6"/>',
57
- at: '<circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.9 7.9"/>',
58
- keyRound: '<circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/>',
59
- alert: '<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/><path d="M12 9v4M12 17h.01"/>',
60
- chevronDown: '<path d="m6 9 6 6 6-6"/>',
61
- eye: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>',
62
- book: '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"/>',
63
- trash: '<path d="M3 6h18"/><path d="M8 6V4.5A1.5 1.5 0 0 1 9.5 3h5A1.5 1.5 0 0 1 16 4.5V6"/><path d="M18.5 6l-1 13.5a2 2 0 0 1-2 1.5h-7a2 2 0 0 1-2-1.5L5.5 6"/><path d="M10 10.5v6M14 10.5v6"/>',
64
- arrowDown: '<path d="M12 4v15M6 13l6 6 6-6"/>',
65
- shrink: '<path d="M4 14h6v6"/><path d="M20 10h-6V4"/><path d="m14 10 7-7"/><path d="m3 21 7-7"/>',
66
- maximize: '<path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/>',
67
- theater: '<rect x="2.5" y="6.5" width="19" height="11" rx="2"/>',
68
- speaker: '<path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M16 9a3 3 0 0 1 0 6"/><path d="M19 7a6 6 0 0 1 0 10"/>',
69
- micFill: '<rect x="9" y="2.5" width="6" height="11.5" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0"/><path d="M12 17.5v3.5"/>'
70
- };
71
- function Icon({ name, size = 24, stroke = 1.75, color = "currentColor", fill = "none", style }) {
72
- const d = ICON_PATHS[name];
73
- if (!d)
74
- return null;
75
- return /* @__PURE__ */ React.createElement(
76
- "svg",
77
- {
78
- width: size,
79
- height: size,
80
- viewBox: "0 0 24 24",
81
- fill,
82
- stroke: color,
83
- strokeWidth: stroke,
84
- strokeLinecap: "round",
85
- strokeLinejoin: "round",
86
- style: { display: "block", flexShrink: 0, ...style },
87
- dangerouslySetInnerHTML: { __html: d }
88
- }
89
- );
90
- }
91
- Object.assign(window, { Icon, ICON_PATHS });
92
- var DK_FALLBACK_ICE_SERVERS = [
93
- { urls: "stun:stun.l.google.com:19302" },
94
- { urls: "turn:tokyo.fi.chat:3478", username: "allcom", credential: "allcompass" },
95
- { urls: "turn:tokyo.fi.chat:3478?transport=tcp", username: "allcom", credential: "allcompass" }
96
- ];
97
- var DK_LIVE_ICE_SERVERS = [];
98
- var _dkIcePromise = null;
99
- var _dkIceWarmPromise = null;
100
- function dkWarmCallIce(iceServers) {
101
- if (_dkIceWarmPromise)
102
- return _dkIceWarmPromise;
103
- if (!window.RTCPeerConnection || !iceServers || !iceServers.length) {
104
- _dkIceWarmPromise = Promise.resolve();
105
- return _dkIceWarmPromise;
106
- }
107
- _dkIceWarmPromise = new Promise((resolve) => {
108
- let pc;
109
- let finished = false;
110
- const done = (why) => {
111
- if (finished)
112
- return;
113
- finished = true;
114
- try {
115
- if (pc)
116
- pc.close();
117
- } catch (e) {
118
- }
119
- console.log("[rtc] ICE warmup done (" + why + ")");
120
- resolve();
121
- };
122
- try {
123
- pc = new RTCPeerConnection({ iceServers, iceTransportPolicy: "all" });
124
- const timer = setTimeout(() => done("timeout"), 5e3);
125
- pc.onicecandidate = (ev) => {
126
- const c = ev.candidate && ev.candidate.candidate;
127
- if (c && /typ relay/.test(c)) {
128
- clearTimeout(timer);
129
- done("relay");
130
- }
131
- };
132
- pc.onicegatheringstatechange = () => {
133
- if (pc.iceGatheringState === "complete") {
134
- clearTimeout(timer);
135
- done("gather-complete");
136
- }
137
- };
138
- pc.createDataChannel("ice-warm");
139
- pc.createOffer().then((o) => pc.setLocalDescription(o)).catch(() => done("offer-fail"));
140
- } catch (e) {
141
- done("error");
142
- }
143
- });
144
- return _dkIceWarmPromise;
145
- }
146
- function dkLoadCallIceServers() {
147
- if (_dkIcePromise)
148
- return _dkIcePromise;
149
- _dkIcePromise = (async () => {
150
- try {
151
- const r = await fetch("/api/call-ice-servers", { headers: { "cache-control": "no-cache" } });
152
- const d = await r.json();
153
- if (d && d.ok && Array.isArray(d.iceServers) && d.iceServers.length) {
154
- DK_LIVE_ICE_SERVERS.splice(0, DK_LIVE_ICE_SERVERS.length, ...d.iceServers);
155
- console.log("[rtc] Carrier bootstrap ICE servers:", DK_LIVE_ICE_SERVERS.map((s) => s.urls));
156
- await dkWarmCallIce(DK_LIVE_ICE_SERVERS);
157
- return DK_LIVE_ICE_SERVERS;
158
- }
159
- console.warn("[rtc] call-ice-servers unavailable:", d && d.error);
160
- } catch (e) {
161
- console.warn("[rtc] call-ice-servers fetch failed", e);
162
- }
163
- console.warn("[rtc] falling back to tokyo TURN \u2014 web\u2194Android online ICE may fail");
164
- DK_LIVE_ICE_SERVERS.splice(0, DK_LIVE_ICE_SERVERS.length, ...DK_FALLBACK_ICE_SERVERS.map((s) => ({ ...s })));
165
- await dkWarmCallIce(DK_LIVE_ICE_SERVERS);
166
- return DK_LIVE_ICE_SERVERS;
167
- })();
168
- return _dkIcePromise;
169
- }
170
- function dkAwaitCallIceServers(ms) {
171
- const budget = typeof ms === "number" ? ms : 1e4;
172
- return Promise.race([
173
- dkLoadCallIceServers(),
174
- new Promise((resolve) => setTimeout(() => {
175
- if (!DK_LIVE_ICE_SERVERS.length) {
176
- DK_LIVE_ICE_SERVERS.splice(0, DK_LIVE_ICE_SERVERS.length, ...DK_FALLBACK_ICE_SERVERS.map((s) => ({ ...s })));
177
- console.warn("[rtc] ICE wait timed out \u2014 using fallback");
178
- }
179
- resolve(DK_LIVE_ICE_SERVERS);
180
- }, budget))
181
- ]).then(() => DK_LIVE_ICE_SERVERS);
182
- }
183
- dkLoadCallIceServers();
184
- function dkRtcParseEnvelope(data) {
185
- const text = String(data || "").replace(/\0+$/u, "").trim();
186
- if (!text)
187
- return null;
188
- try {
189
- const obj = JSON.parse(text);
190
- if (obj && obj.dkRtc === 1 && typeof obj.kind === "string")
191
- return obj;
192
- } catch (e) {
193
- }
194
- return null;
195
- }
196
- function dkRtcSignalBus() {
197
- if (window.__dkRtcSignalBus)
198
- return window.__dkRtcSignalBus;
199
- const PW = window.PeerWebRTC;
200
- const handlers = /* @__PURE__ */ new Map();
201
- const pendingCalls = [];
202
- const PENDING_CALL_CAP = 16;
203
- let started = false;
204
- let stopped = false;
205
- function emit(kind, userid, payload) {
206
- const set = handlers.get(kind);
207
- if (!set || !set.size) {
208
- if (kind === "call") {
209
- pendingCalls.push({ userid, payload });
210
- if (pendingCalls.length > PENDING_CALL_CAP)
211
- pendingCalls.shift();
212
- }
213
- return;
214
- }
215
- for (const h of Array.from(set)) {
216
- try {
217
- h(userid, payload);
218
- } catch (e) {
219
- console.warn("[rtc] signal handler failed", e);
220
- }
221
- }
222
- }
223
- async function pollLoop() {
224
- let cursor = null;
225
- while (!stopped) {
226
- let signals = [];
227
- try {
228
- const u = cursor == null ? "/api/call-poll" : "/api/call-poll?since=" + cursor;
229
- const r = await fetch(u, { headers: { "cache-control": "no-cache" } });
230
- const d = await r.json();
231
- signals = d && d.signals || [];
232
- if (d && typeof d.cursor === "number")
233
- cursor = d.cursor;
234
- } catch (e) {
235
- await new Promise((res) => setTimeout(res, 1e3));
236
- continue;
237
- }
238
- for (const s of signals) {
239
- const env = dkRtcParseEnvelope(s.data);
240
- if (env) {
241
- emit(env.kind, s.userid, env);
242
- continue;
243
- }
244
- if (PW) {
245
- try {
246
- emit("call", s.userid, PW.decodeSignal(s.data));
247
- } catch (e) {
248
- }
249
- }
250
- }
251
- }
252
- }
253
- const bus = {
254
- start() {
255
- if (started)
256
- return;
257
- started = true;
258
- stopped = false;
259
- pollLoop();
260
- },
261
- on(kind, cb) {
262
- if (!handlers.has(kind))
263
- handlers.set(kind, /* @__PURE__ */ new Set());
264
- handlers.get(kind).add(cb);
265
- this.start();
266
- if (kind === "call" && pendingCalls.length) {
267
- const queued = pendingCalls.splice(0);
268
- console.log("[rtc] flushing " + queued.length + " buffered call signal(s)");
269
- for (const q of queued) {
270
- try {
271
- cb(q.userid, q.payload);
272
- } catch (e) {
273
- console.warn("[rtc] buffered call handler failed", e);
274
- }
275
- }
276
- }
277
- return () => {
278
- var _a;
279
- return (_a = handlers.get(kind)) == null ? void 0 : _a.delete(cb);
280
- };
281
- },
282
- async send(userid, data) {
283
- const body = JSON.stringify({ userid, data: typeof data === "string" ? data : JSON.stringify(data) });
284
- const r = await fetch("/api/call-signal", {
285
- method: "POST",
286
- headers: { "content-type": "application/json" },
287
- body
288
- });
289
- const d = await r.json().catch(() => ({ ok: r.ok }));
290
- if (!d.ok)
291
- throw new Error(d.error || "call-signal send failed");
292
- },
293
- stop() {
294
- stopped = true;
295
- }
296
- };
297
- window.__dkRtcSignalBus = bus;
298
- return bus;
299
- }
300
- function makeDaemonSignaling() {
301
- const PW = window.PeerWebRTC;
302
- const bus = dkRtcSignalBus();
303
- let off = null;
304
- return {
305
- async send(peerId, signal) {
306
- await bus.send(peerId, PW.encodeSignal(signal));
307
- },
308
- onSignal(cb) {
309
- if (off)
310
- off();
311
- off = bus.on("call", cb);
312
- },
313
- stop() {
314
- if (off) {
315
- off();
316
- off = null;
317
- }
318
- }
319
- };
320
- }
321
- Object.assign(window, { dkRtcSignalBus, makeDaemonSignaling });
322
5
  var H = {};
323
6
  var hostNeedsWelcome = false;
324
7
  var hostPrefersRtcFile = false;
@@ -625,19 +308,106 @@ window.__DK_UI_VERSION="0.1.60";
625
308
  setThreads((t) => Object.assign({}, t, { [peerId]: withDay }));
626
309
  } catch (e) {
627
310
  }
628
- }, []);
629
- return Object.assign({}, snap, { threads, refresh, loadThread });
311
+ }, []);
312
+ return Object.assign({}, snap, { threads, refresh, loadThread });
313
+ }
314
+ Object.assign(window, {
315
+ dkHash,
316
+ dkIdenticon,
317
+ dkClock,
318
+ dkDayLabel,
319
+ dkApi,
320
+ useDaemonData,
321
+ DK_ME_FALLBACK,
322
+ dkCopy
323
+ });
324
+ var ICON_PATHS = {
325
+ // ---- tab bar (the four must feel like one set) ----
326
+ users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
327
+ grid: '<rect x="3" y="3" width="7.5" height="7.5" rx="1.6"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.6"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.6"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.6"/>',
328
+ wallet: '<path d="M19 7V6a2 2 0 0 0-2-2H6a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h12a2 2 0 0 0 2-2v-1"/><path d="M3 7a3 3 0 0 0 3 3h13a2 2 0 0 1 2 2v0a2 2 0 0 1-2 2h-3"/><circle cx="16.5" cy="14" r="1.15" fill="currentColor" stroke="none"/>',
329
+ userRound: '<circle cx="12" cy="8" r="4.5"/><path d="M20 21a8 8 0 0 0-16 0"/>',
330
+ // ---- actions / chrome ----
331
+ search: '<circle cx="11" cy="11" r="7.5"/><path d="m21 21-4.3-4.3"/>',
332
+ plus: '<path d="M5 12h14M12 5v14"/>',
333
+ chevronRight: '<path d="m9 18 6-6-6-6"/>',
334
+ chevronLeft: '<path d="m15 18-6-6 6-6"/>',
335
+ info: '<circle cx="12" cy="12" r="9.5"/><path d="M12 16.5v-5M12 7.6h.01"/>',
336
+ more: '<circle cx="5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.4" fill="currentColor" stroke="none"/>',
337
+ x: '<path d="M18 6 6 18M6 6l12 12"/>',
338
+ check: '<path d="M20 6 9 17l-5-5"/>',
339
+ checkCheck: '<path d="M18 6 7 17l-5-5"/><path d="m22 10-7.6 7.6L13 16"/>',
340
+ copy: '<rect x="9" y="9" width="12" height="12" rx="2.4"/><path d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1"/>',
341
+ // Three linked nodes — an address is for passing ON to someone else, so it
342
+ // must not wear the same glyph as the userid's plain copy.
343
+ share: '<circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.3M8.3 13.2l7.4 4.3"/>',
344
+ refresh: '<path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 4v4.5h-4.5"/>',
345
+ clock: '<circle cx="12" cy="12" r="9.5"/><path d="M12 7.5V12l3 2"/>',
346
+ edit: '<path d="M11 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4Z"/>',
347
+ qr: '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><path d="M14 14h3v3M21 14v.01M14 21h.01M17 21h.01M21 17v4"/>',
348
+ // ---- app tiles ----
349
+ video: '<path d="m22 8.5-5.4 3.5 5.4 3.5V8.5Z"/><rect x="2" y="6" width="14.5" height="12" rx="3"/>',
350
+ monitor: '<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>',
351
+ sparkles: '<path d="M12 3.5 13.6 9 19 10.5 13.6 12 12 17.5 10.4 12 5 10.5 10.4 9 12 3.5Z"/><path d="M19 3.5v3.2M20.6 5.1h-3.2"/><path d="M5 16v2.4M6.2 17.2H3.8"/>',
352
+ mic: '<rect x="9" y="2.5" width="6" height="11.5" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0"/><path d="M12 17.5v3.5"/>',
353
+ flask: '<path d="M9.5 3v6.2L4.8 17.4A2 2 0 0 0 6.5 20.5h11a2 2 0 0 0 1.7-3.1L14.5 9.2V3"/><path d="M8.5 3h7"/><path d="M7.3 15h9.4"/>',
354
+ headphones: '<path d="M3.5 14v-1.5a8.5 8.5 0 0 1 17 0V14"/><path d="M3.5 14.5a2.5 2.5 0 0 1 2.5-2.5h0a1.5 1.5 0 0 1 1.5 1.5v3.5a1.5 1.5 0 0 1-1.5 1.5h0a2.5 2.5 0 0 1-2.5-2.5Z"/><path d="M20.5 14.5a2.5 2.5 0 0 0-2.5-2.5h0a1.5 1.5 0 0 0-1.5 1.5v3.5a1.5 1.5 0 0 0 1.5 1.5h0a2.5 2.5 0 0 0 2.5-2.5Z"/>',
355
+ globe: '<circle cx="12" cy="12" r="9.5"/><path d="M2.5 12h19"/><path d="M12 2.5a14 14 0 0 1 0 19 14 14 0 0 1 0-19Z"/>',
356
+ store: '<path d="M4 9.5V19a1.5 1.5 0 0 0 1.5 1.5h13A1.5 1.5 0 0 0 20 19V9.5"/><path d="M3 6.5 4.5 3.5h15L21 6.5a2.5 2.5 0 0 1-4.5 1.5 2.5 2.5 0 0 1-4.5 0 2.5 2.5 0 0 1-4.5 0A2.5 2.5 0 0 1 3 6.5Z"/>',
357
+ // ---- chat composer ----
358
+ camera: '<path d="M14.5 4.5h-5L7.2 7.2H4.5A2.5 2.5 0 0 0 2 9.7V18a2.5 2.5 0 0 0 2.5 2.5h15A2.5 2.5 0 0 0 22 18V9.7a2.5 2.5 0 0 0-2.5-2.5h-2.7L14.5 4.5Z"/><circle cx="12" cy="13.5" r="3.2"/>',
359
+ phone: '<path d="M16.5 21a2 2 0 0 0 2-2.2 1.9 1.9 0 0 0-.6-1.2l-2-1.7a2 2 0 0 0-2.4-.1l-.9.6a14 14 0 0 1-5-5l.6-.9a2 2 0 0 0-.1-2.4l-1.7-2a1.9 1.9 0 0 0-1.2-.6A2 2 0 0 0 3 5.5 16 16 0 0 0 16.5 21Z"/>',
360
+ bolt: '<path d="M13 2 4 13h7l-1 9 9-11h-7l1-9Z"/>',
361
+ arrowUp: '<path d="M12 20V5M6 11l6-6 6 6"/>',
362
+ send: '<path d="M21.5 4.5 10.5 13.5"/><path d="M21.5 4.5 14.8 21.5 11 13l-8.5-3.8 19-4.7Z"/>',
363
+ // ---- account ----
364
+ shield: '<path d="M12 21.5s7.5-3.8 7.5-9.5V5.5L12 2.8 4.5 5.5V12c0 5.7 7.5 9.5 7.5 9.5Z"/>',
365
+ download: '<path d="M12 3.5v11M7.5 10l4.5 4.5 4.5-4.5"/><path d="M5 20.5h14"/>',
366
+ // Folder with a magnifier corner — "reveal / show in file manager".
367
+ folderOpen: '<path d="M3 7.5a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/><circle cx="12" cy="13" r="2.4"/><path d="M13.9 14.9 15.5 16.5"/>',
368
+ migrate: '<path d="M8 4 4 8l4 4"/><path d="M4 8h13a3 3 0 0 1 3 3v1"/><path d="m16 20 4-4-4-4"/><path d="M20 16H7a3 3 0 0 1-3-3v-1"/>',
369
+ starCoin: '<circle cx="12" cy="12" r="9"/><path d="m12 7.2 1.4 2.9 3.2.4-2.3 2.2.6 3.1-2.9-1.5-2.9 1.5.6-3.1L8.4 10.5l3.2-.4L12 7.2Z" fill="currentColor" stroke="none"/>',
370
+ link: '<path d="M10.5 13.5a4 4 0 0 0 5.7 0l2.3-2.3a4 4 0 0 0-5.7-5.7l-1 1"/><path d="M13.5 10.5a4 4 0 0 0-5.7 0l-2.3 2.3a4 4 0 0 0 5.7 5.7l1-1"/>',
371
+ trendingUp: '<path d="M22 7.5 13.5 16l-4.5-4.5L2 18.5"/><path d="M16.5 7.5H22v5.5"/>',
372
+ bell: '<path d="M18 8.5a6 6 0 0 0-12 0c0 6.5-2.5 8-2.5 8h17S18 15 18 8.5Z"/><path d="M13.7 20.5a2 2 0 0 1-3.4 0"/>',
373
+ bot: '<rect x="3.5" y="9" width="17" height="11" rx="3"/><path d="M12 4.5v4.5"/><circle cx="12" cy="3" r="1.4"/><path d="M8.5 14.5h.01M15.5 14.5h.01"/><path d="M2 13.5v3M22 13.5v3"/>',
374
+ scan: '<path d="M3 8V6a3 3 0 0 1 3-3h2"/><path d="M16 3h2a3 3 0 0 1 3 3v2"/><path d="M21 16v2a3 3 0 0 1-3 3h-2"/><path d="M8 21H6a3 3 0 0 1-3-3v-2"/><path d="M7 12h10"/>',
375
+ userPlus: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M19 8v6M22 11h-6"/>',
376
+ at: '<circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.9 7.9"/>',
377
+ keyRound: '<circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/>',
378
+ alert: '<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/><path d="M12 9v4M12 17h.01"/>',
379
+ chevronDown: '<path d="m6 9 6 6 6-6"/>',
380
+ eye: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>',
381
+ book: '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"/>',
382
+ trash: '<path d="M3 6h18"/><path d="M8 6V4.5A1.5 1.5 0 0 1 9.5 3h5A1.5 1.5 0 0 1 16 4.5V6"/><path d="M18.5 6l-1 13.5a2 2 0 0 1-2 1.5h-7a2 2 0 0 1-2-1.5L5.5 6"/><path d="M10 10.5v6M14 10.5v6"/>',
383
+ arrowDown: '<path d="M12 4v15M6 13l6 6 6-6"/>',
384
+ shrink: '<path d="M4 14h6v6"/><path d="M20 10h-6V4"/><path d="m14 10 7-7"/><path d="m3 21 7-7"/>',
385
+ maximize: '<path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/>',
386
+ theater: '<rect x="2.5" y="6.5" width="19" height="11" rx="2"/>',
387
+ speaker: '<path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M16 9a3 3 0 0 1 0 6"/><path d="M19 7a6 6 0 0 1 0 10"/>',
388
+ micFill: '<rect x="9" y="2.5" width="6" height="11.5" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0"/><path d="M12 17.5v3.5"/>'
389
+ };
390
+ function Icon({ name, size = 24, stroke = 1.75, color = "currentColor", fill = "none", style }) {
391
+ const d = ICON_PATHS[name];
392
+ if (!d)
393
+ return null;
394
+ return /* @__PURE__ */ React.createElement(
395
+ "svg",
396
+ {
397
+ width: size,
398
+ height: size,
399
+ viewBox: "0 0 24 24",
400
+ fill,
401
+ stroke: color,
402
+ strokeWidth: stroke,
403
+ strokeLinecap: "round",
404
+ strokeLinejoin: "round",
405
+ style: { display: "block", flexShrink: 0, ...style },
406
+ dangerouslySetInnerHTML: { __html: d }
407
+ }
408
+ );
630
409
  }
631
- Object.assign(window, {
632
- dkHash,
633
- dkIdenticon,
634
- dkClock,
635
- dkDayLabel,
636
- dkApi,
637
- useDaemonData,
638
- DK_ME_FALLBACK,
639
- dkCopy
640
- });
410
+ Object.assign(window, { Icon, ICON_PATHS });
641
411
  function RequestsBlock({ T, requests, onAct }) {
642
412
  const [acted, setActed] = React.useState({});
643
413
  const [failed, setFailed] = React.useState({});
@@ -2925,11 +2695,14 @@ ${peer.address}`
2925
2695
  return /* @__PURE__ */ React.createElement(Card, { label: zh ? "\u663E\u793A" : "Display" }, /* @__PURE__ */ React.createElement("div", { style: { ...row, borderBottom: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u8BED\u8A00" : "Language"), seg("lang", t.lang, (v) => setTweak("lang", v), [["en", "EN"], ["zh", "\u4E2D\u6587"]])), /* @__PURE__ */ React.createElement("div", { style: row }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u4E3B\u9898" : "Theme"), seg("theme", t.theme, (v) => setTweak("theme", v), [["dark", zh ? "\u6DF1\u8272" : "Dark"], ["light", zh ? "\u6D45\u8272" : "Light"]])));
2926
2696
  }
2927
2697
  function ProfileTab({ T, me, onEdit, t, setTweak }) {
2698
+ var _a;
2928
2699
  const [qr, setQr] = React.useState(null);
2929
2700
  const [editing, setEditing] = React.useState(false);
2930
2701
  const [ensRec, setEnsRec] = React.useState(null);
2931
- const punkId = ensRec && ensRec.nft === "CryptoPunks" && ensRec.nftid > 0 ? ensRec.nftid : null;
2932
- const upAvatar = ensRec && ensRec.texts && ensRec.texts.avatar || null;
2702
+ const pubPunk = ensRec && ensRec.nft === "CryptoPunks" && ensRec.nftid > 0 ? ensRec.nftid : null;
2703
+ const pubAvatar = ensRec && ensRec.texts && ensRec.texts.avatar || null;
2704
+ const punkId = (_a = me.punkId) != null ? _a : pubPunk;
2705
+ const upAvatar = me.avatarUrl || pubAvatar;
2933
2706
  return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 18, padding: "20px 22px", borderRadius: 14, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, upAvatar ? /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 68, height: 68, style: { width: 68, height: 68, borderRadius: 16, objectFit: "cover", background: "var(--panel-2)" } }) : punkId != null ? /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 68, radius: 16, fallbackSeed: me.userId }) : /* @__PURE__ */ React.createElement(DkIdenticon, { seed: me.userId, size: 68, radius: 16 }), /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -3, bottom: -3, width: 18, height: 18, borderRadius: 999, background: "var(--online)", border: "3px solid var(--panel)" } })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 22, fontWeight: 700, letterSpacing: -0.5, color: "var(--text)" } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: "ok" }, "online"), me.isExit && /* @__PURE__ */ React.createElement(Tag, { tone: "warn" }, "exit", me.exitRegion ? ` \xB7 ${me.exitRegion.toUpperCase()}` : "")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13, color: "var(--dim)", marginTop: 4 } }, me.handle), me.description ? /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 3 } }, me.description) : null), /* @__PURE__ */ React.createElement(Btn, { icon: "edit", onClick: () => setEditing(true) }, T.editProfile)), /* @__PURE__ */ React.createElement(Card, { label: T.identity }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.userId, value: me.userId, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.carrierAddr, value: me.carrier, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.netKey, value: me.netKey, copy: true, last: true })), /* @__PURE__ */ React.createElement(DkEnsCard, { T, me, onRecord: setEnsRec }), typeof me.autoAccept === "boolean" && /* @__PURE__ */ React.createElement(Card, { label: T && T.friendsSettings || "Friends" }, /* @__PURE__ */ React.createElement("label", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px", cursor: "pointer" } }, /* @__PURE__ */ React.createElement(
2934
2707
  "input",
2935
2708
  {
@@ -3034,95 +2807,409 @@ ${peer.address}`
3034
2807
  return s;
3035
2808
  return s.slice(0, head) + "\u2026" + s.slice(-tail);
3036
2809
  }
3037
- function RouteTag({ peer }) {
3038
- if (!peer.online)
3039
- return /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline");
3040
- const relay = peer.via === "relay";
3041
- const via = peer.via || "online";
3042
- return /* @__PURE__ */ React.createElement(Tag, { tone: relay ? "warn" : "ok" }, peer.ping != null ? `${via} \xB7 ${peer.ping}ms` : via);
2810
+ function RouteTag({ peer }) {
2811
+ if (!peer.online)
2812
+ return /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline");
2813
+ const relay = peer.via === "relay";
2814
+ const via = peer.via || "online";
2815
+ return /* @__PURE__ */ React.createElement(Tag, { tone: relay ? "warn" : "ok" }, peer.ping != null ? `${via} \xB7 ${peer.ping}ms` : via);
2816
+ }
2817
+ function Tag({ children, tone = "neutral", style, title }) {
2818
+ const tones = {
2819
+ neutral: { bg: "var(--chip)", fg: "var(--dim)", bd: "transparent" },
2820
+ ok: { bg: "color-mix(in oklab, var(--online), transparent 86%)", fg: "var(--online)", bd: "transparent" },
2821
+ warn: { bg: "color-mix(in oklab, var(--warn), transparent 86%)", fg: "var(--warn)", bd: "transparent" },
2822
+ off: { bg: "var(--chip)", fg: "var(--faint)", bd: "transparent" },
2823
+ accent: { bg: "color-mix(in oklab, var(--accent), transparent 86%)", fg: "var(--accent)", bd: "transparent" },
2824
+ danger: { bg: "color-mix(in oklab, var(--danger), transparent 88%)", fg: "var(--danger)", bd: "transparent" }
2825
+ };
2826
+ const t = tones[tone] || tones.neutral;
2827
+ return /* @__PURE__ */ React.createElement("span", { title, style: {
2828
+ display: "inline-flex",
2829
+ alignItems: "center",
2830
+ gap: 4,
2831
+ height: 18,
2832
+ padding: "0 6px",
2833
+ borderRadius: 4,
2834
+ background: t.bg,
2835
+ color: t.fg,
2836
+ border: `1px solid ${t.bd}`,
2837
+ fontFamily: "var(--mono)",
2838
+ fontSize: 11,
2839
+ fontWeight: 600,
2840
+ letterSpacing: -0.1,
2841
+ lineHeight: 1,
2842
+ whiteSpace: "nowrap",
2843
+ ...style
2844
+ } }, children);
2845
+ }
2846
+ function Unread({ n }) {
2847
+ if (!n)
2848
+ return null;
2849
+ return /* @__PURE__ */ React.createElement("span", { style: {
2850
+ minWidth: 18,
2851
+ height: 18,
2852
+ padding: "0 5px",
2853
+ borderRadius: 999,
2854
+ flexShrink: 0,
2855
+ background: "var(--accent)",
2856
+ color: "#fff",
2857
+ fontFamily: "var(--mono)",
2858
+ fontSize: 11,
2859
+ fontWeight: 700,
2860
+ lineHeight: "18px",
2861
+ textAlign: "center"
2862
+ } }, n);
2863
+ }
2864
+ function Btn({ children, icon, tone = "ghost", onClick, size = "md", title, style }) {
2865
+ const tones = {
2866
+ ghost: { bg: "transparent", fg: "var(--text)", bd: "var(--line)" },
2867
+ solid: { bg: "var(--accent)", fg: "#fff", bd: "transparent" },
2868
+ soft: { bg: "var(--chip)", fg: "var(--text)", bd: "transparent" },
2869
+ ok: { bg: "color-mix(in oklab, var(--online), transparent 84%)", fg: "var(--online)", bd: "transparent" },
2870
+ danger: { bg: "transparent", fg: "var(--danger)", bd: "color-mix(in oklab, var(--danger), transparent 70%)" }
2871
+ };
2872
+ const t = tones[tone] || tones.ghost;
2873
+ const h = size === "sm" ? 26 : size === "lg" ? 36 : 30;
2874
+ return /* @__PURE__ */ React.createElement("button", { onClick, title, style: {
2875
+ height: h,
2876
+ padding: icon && !children ? 0 : "0 11px",
2877
+ minWidth: icon && !children ? h : "auto",
2878
+ borderRadius: 6,
2879
+ cursor: "pointer",
2880
+ fontFamily: "var(--mono)",
2881
+ fontWeight: 600,
2882
+ fontSize: size === "sm" ? 12 : 12.5,
2883
+ letterSpacing: -0.1,
2884
+ background: t.bg,
2885
+ color: t.fg,
2886
+ border: `1px solid ${t.bd}`,
2887
+ display: "inline-flex",
2888
+ alignItems: "center",
2889
+ justifyContent: "center",
2890
+ gap: 6,
2891
+ whiteSpace: "nowrap",
2892
+ ...style
2893
+ } }, icon && /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 15, stroke: 2 }), children);
2894
+ }
2895
+ function Section({ label, count, trailing, style }) {
2896
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "0 2px", ...style } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", color: "var(--faint)" } }, label), count != null && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, color: "var(--faint)" } }, count), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, height: 1, background: "var(--line)" } }), trailing);
2897
+ }
2898
+ Object.assign(window, { DkIdenticon, DkBot, DkAvatar, DkImgAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
2899
+ var APPS = [
2900
+ {
2901
+ id: "meet",
2902
+ name: "Beagle Meet",
2903
+ origin: "https://snoopy.fi.chat",
2904
+ url: "https://snoopy.fi.chat",
2905
+ blurb: { en: "Video calls and rooms \u2014 your Beagle identity is the login.", zh: "\u89C6\u9891\u4F1A\u8BAE\u548C\u623F\u95F4 \u2014\u2014 \u7528 Beagle \u8EAB\u4EFD\u76F4\u63A5\u767B\u5F55\u3002" },
2906
+ icon: "video"
2907
+ },
2908
+ {
2909
+ id: "interview",
2910
+ name: "interview.network",
2911
+ origin: "https://interview.network",
2912
+ url: "https://interview.network",
2913
+ blurb: { en: "Interviews, scheduled and held against a verified identity.", zh: "\u9762\u8BD5\u5B89\u6392\u4E0E\u8FDB\u884C\uFF0C\u8EAB\u4EFD\u53EF\u9A8C\u8BC1\u3002" },
2914
+ icon: "users"
2915
+ },
2916
+ {
2917
+ id: "readyplayer",
2918
+ name: "readyplayer.network",
2919
+ origin: "https://readyplayer.network",
2920
+ url: "https://readyplayer.network",
2921
+ blurb: { en: "Membership and the services that come with it.", zh: "\u4F1A\u5458\u4E0E\u914D\u5957\u670D\u52A1\u3002" },
2922
+ icon: "starCoin"
2923
+ },
2924
+ {
2925
+ id: "btc",
2926
+ name: "btc.fi.chat",
2927
+ origin: "https://btc.fi.chat",
2928
+ url: "https://btc.fi.chat",
2929
+ blurb: { en: "Trading desk and signals.", zh: "\u4EA4\u6613\u53F0\u4E0E\u4FE1\u53F7\u3002" },
2930
+ icon: "trendingUp"
2931
+ }
2932
+ ];
2933
+ function dkB64Url(obj) {
2934
+ const json = JSON.stringify(obj);
2935
+ const b64 = btoa(unescape(encodeURIComponent(json)));
2936
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2937
+ }
2938
+ function AppsTab({ T, t }) {
2939
+ const zh = t && t.lang === "zh";
2940
+ const [busy, setBusy] = React.useState(null);
2941
+ const [err, setErr] = React.useState(null);
2942
+ const open = async (app) => {
2943
+ setBusy(app.id);
2944
+ setErr(null);
2945
+ const win = window.open("", "_blank");
2946
+ try {
2947
+ const r = await dkPost("/api/launch-token", { origin: app.origin });
2948
+ if (!r || r.ok === false)
2949
+ throw new Error(r && r.error || "could not sign");
2950
+ const { ok, ...assertion } = r;
2951
+ const url = app.url + "#beagle=" + dkB64Url(assertion);
2952
+ if (win)
2953
+ win.location = url;
2954
+ else
2955
+ window.open(url, "_blank");
2956
+ } catch (e) {
2957
+ if (win)
2958
+ win.close();
2959
+ setErr(String(e && e.message || e));
2960
+ } finally {
2961
+ setBusy(null);
2962
+ }
2963
+ };
2964
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 16 } }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase", color: "var(--faint)" } }, zh ? "\u5E94\u7528" : "Apps"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 6, lineHeight: 1.6 } }, zh ? "\u8FD9\u4E9B\u5E94\u7528\u63A5\u53D7 Beagle \u8EAB\u4EFD\u3002\u4ECE\u8FD9\u91CC\u6253\u5F00\u4E0D\u9700\u8981\u518D\u767B\u5F55\u4E00\u6B21 \u2014\u2014 \u4F1A\u5E26\u4E0A\u4E00\u4E2A\u4E24\u5206\u949F\u5185\u6709\u6548\u7684\u7B7E\u540D\u3002" : "These accept a Beagle identity. Opening one from here does not ask you to sign in again \u2014 it carries a signature that is good for two minutes.")), err && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "#f85149" } }, err), APPS.map((app) => /* @__PURE__ */ React.createElement("div", { key: app.id, style: {
2965
+ display: "flex",
2966
+ alignItems: "center",
2967
+ gap: 14,
2968
+ padding: "14px 16px",
2969
+ borderRadius: 12,
2970
+ background: "var(--panel)",
2971
+ border: "1px solid var(--line)"
2972
+ } }, /* @__PURE__ */ React.createElement("div", { style: {
2973
+ width: 38,
2974
+ height: 38,
2975
+ borderRadius: 10,
2976
+ flexShrink: 0,
2977
+ background: "var(--panel-2)",
2978
+ display: "flex",
2979
+ alignItems: "center",
2980
+ justifyContent: "center"
2981
+ } }, /* @__PURE__ */ React.createElement(Icon, { name: app.icon, size: 19, color: "var(--accent)", stroke: 1.8 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 14, fontWeight: 600, color: "var(--text)" } }, app.name), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 2 } }, zh ? app.blurb.zh : app.blurb.en), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--dim)", marginTop: 3 } }, app.origin.replace(/^https?:\/\//, ""))), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", onClick: busy ? void 0 : () => open(app) }, busy === app.id ? zh ? "\u6253\u5F00\u4E2D\u2026" : "opening\u2026" : zh ? "\u6253\u5F00" : "Open")))));
2982
+ }
2983
+ var DK_FALLBACK_ICE_SERVERS = [
2984
+ { urls: "stun:stun.l.google.com:19302" },
2985
+ { urls: "turn:tokyo.fi.chat:3478", username: "allcom", credential: "allcompass" },
2986
+ { urls: "turn:tokyo.fi.chat:3478?transport=tcp", username: "allcom", credential: "allcompass" }
2987
+ ];
2988
+ var DK_LIVE_ICE_SERVERS = [];
2989
+ var _dkIcePromise = null;
2990
+ var _dkIceWarmPromise = null;
2991
+ function dkWarmCallIce(iceServers) {
2992
+ if (_dkIceWarmPromise)
2993
+ return _dkIceWarmPromise;
2994
+ if (!window.RTCPeerConnection || !iceServers || !iceServers.length) {
2995
+ _dkIceWarmPromise = Promise.resolve();
2996
+ return _dkIceWarmPromise;
2997
+ }
2998
+ _dkIceWarmPromise = new Promise((resolve) => {
2999
+ let pc;
3000
+ let finished = false;
3001
+ const done = (why) => {
3002
+ if (finished)
3003
+ return;
3004
+ finished = true;
3005
+ try {
3006
+ if (pc)
3007
+ pc.close();
3008
+ } catch (e) {
3009
+ }
3010
+ console.log("[rtc] ICE warmup done (" + why + ")");
3011
+ resolve();
3012
+ };
3013
+ try {
3014
+ pc = new RTCPeerConnection({ iceServers, iceTransportPolicy: "all" });
3015
+ const timer = setTimeout(() => done("timeout"), 5e3);
3016
+ pc.onicecandidate = (ev) => {
3017
+ const c = ev.candidate && ev.candidate.candidate;
3018
+ if (c && /typ relay/.test(c)) {
3019
+ clearTimeout(timer);
3020
+ done("relay");
3021
+ }
3022
+ };
3023
+ pc.onicegatheringstatechange = () => {
3024
+ if (pc.iceGatheringState === "complete") {
3025
+ clearTimeout(timer);
3026
+ done("gather-complete");
3027
+ }
3028
+ };
3029
+ pc.createDataChannel("ice-warm");
3030
+ pc.createOffer().then((o) => pc.setLocalDescription(o)).catch(() => done("offer-fail"));
3031
+ } catch (e) {
3032
+ done("error");
3033
+ }
3034
+ });
3035
+ return _dkIceWarmPromise;
3036
+ }
3037
+ function dkLoadCallIceServers() {
3038
+ if (_dkIcePromise)
3039
+ return _dkIcePromise;
3040
+ _dkIcePromise = (async () => {
3041
+ try {
3042
+ const r = await fetch("/api/call-ice-servers", { headers: { "cache-control": "no-cache" } });
3043
+ const d = await r.json();
3044
+ if (d && d.ok && Array.isArray(d.iceServers) && d.iceServers.length) {
3045
+ DK_LIVE_ICE_SERVERS.splice(0, DK_LIVE_ICE_SERVERS.length, ...d.iceServers);
3046
+ console.log("[rtc] Carrier bootstrap ICE servers:", DK_LIVE_ICE_SERVERS.map((s) => s.urls));
3047
+ await dkWarmCallIce(DK_LIVE_ICE_SERVERS);
3048
+ return DK_LIVE_ICE_SERVERS;
3049
+ }
3050
+ console.warn("[rtc] call-ice-servers unavailable:", d && d.error);
3051
+ } catch (e) {
3052
+ console.warn("[rtc] call-ice-servers fetch failed", e);
3053
+ }
3054
+ console.warn("[rtc] falling back to tokyo TURN \u2014 web\u2194Android online ICE may fail");
3055
+ DK_LIVE_ICE_SERVERS.splice(0, DK_LIVE_ICE_SERVERS.length, ...DK_FALLBACK_ICE_SERVERS.map((s) => ({ ...s })));
3056
+ await dkWarmCallIce(DK_LIVE_ICE_SERVERS);
3057
+ return DK_LIVE_ICE_SERVERS;
3058
+ })();
3059
+ return _dkIcePromise;
3043
3060
  }
3044
- function Tag({ children, tone = "neutral", style, title }) {
3045
- const tones = {
3046
- neutral: { bg: "var(--chip)", fg: "var(--dim)", bd: "transparent" },
3047
- ok: { bg: "color-mix(in oklab, var(--online), transparent 86%)", fg: "var(--online)", bd: "transparent" },
3048
- warn: { bg: "color-mix(in oklab, var(--warn), transparent 86%)", fg: "var(--warn)", bd: "transparent" },
3049
- off: { bg: "var(--chip)", fg: "var(--faint)", bd: "transparent" },
3050
- accent: { bg: "color-mix(in oklab, var(--accent), transparent 86%)", fg: "var(--accent)", bd: "transparent" },
3051
- danger: { bg: "color-mix(in oklab, var(--danger), transparent 88%)", fg: "var(--danger)", bd: "transparent" }
3052
- };
3053
- const t = tones[tone] || tones.neutral;
3054
- return /* @__PURE__ */ React.createElement("span", { title, style: {
3055
- display: "inline-flex",
3056
- alignItems: "center",
3057
- gap: 4,
3058
- height: 18,
3059
- padding: "0 6px",
3060
- borderRadius: 4,
3061
- background: t.bg,
3062
- color: t.fg,
3063
- border: `1px solid ${t.bd}`,
3064
- fontFamily: "var(--mono)",
3065
- fontSize: 11,
3066
- fontWeight: 600,
3067
- letterSpacing: -0.1,
3068
- lineHeight: 1,
3069
- whiteSpace: "nowrap",
3070
- ...style
3071
- } }, children);
3061
+ function dkAwaitCallIceServers(ms) {
3062
+ const budget = typeof ms === "number" ? ms : 1e4;
3063
+ return Promise.race([
3064
+ dkLoadCallIceServers(),
3065
+ new Promise((resolve) => setTimeout(() => {
3066
+ if (!DK_LIVE_ICE_SERVERS.length) {
3067
+ DK_LIVE_ICE_SERVERS.splice(0, DK_LIVE_ICE_SERVERS.length, ...DK_FALLBACK_ICE_SERVERS.map((s) => ({ ...s })));
3068
+ console.warn("[rtc] ICE wait timed out \u2014 using fallback");
3069
+ }
3070
+ resolve(DK_LIVE_ICE_SERVERS);
3071
+ }, budget))
3072
+ ]).then(() => DK_LIVE_ICE_SERVERS);
3072
3073
  }
3073
- function Unread({ n }) {
3074
- if (!n)
3074
+ dkLoadCallIceServers();
3075
+ function dkRtcParseEnvelope(data) {
3076
+ const text = String(data || "").replace(/\0+$/u, "").trim();
3077
+ if (!text)
3075
3078
  return null;
3076
- return /* @__PURE__ */ React.createElement("span", { style: {
3077
- minWidth: 18,
3078
- height: 18,
3079
- padding: "0 5px",
3080
- borderRadius: 999,
3081
- flexShrink: 0,
3082
- background: "var(--accent)",
3083
- color: "#fff",
3084
- fontFamily: "var(--mono)",
3085
- fontSize: 11,
3086
- fontWeight: 700,
3087
- lineHeight: "18px",
3088
- textAlign: "center"
3089
- } }, n);
3079
+ try {
3080
+ const obj = JSON.parse(text);
3081
+ if (obj && obj.dkRtc === 1 && typeof obj.kind === "string")
3082
+ return obj;
3083
+ } catch (e) {
3084
+ }
3085
+ return null;
3090
3086
  }
3091
- function Btn({ children, icon, tone = "ghost", onClick, size = "md", title, style }) {
3092
- const tones = {
3093
- ghost: { bg: "transparent", fg: "var(--text)", bd: "var(--line)" },
3094
- solid: { bg: "var(--accent)", fg: "#fff", bd: "transparent" },
3095
- soft: { bg: "var(--chip)", fg: "var(--text)", bd: "transparent" },
3096
- ok: { bg: "color-mix(in oklab, var(--online), transparent 84%)", fg: "var(--online)", bd: "transparent" },
3097
- danger: { bg: "transparent", fg: "var(--danger)", bd: "color-mix(in oklab, var(--danger), transparent 70%)" }
3087
+ function dkRtcSignalBus() {
3088
+ if (window.__dkRtcSignalBus)
3089
+ return window.__dkRtcSignalBus;
3090
+ const PW = window.PeerWebRTC;
3091
+ const handlers = /* @__PURE__ */ new Map();
3092
+ const pendingCalls = [];
3093
+ const PENDING_CALL_CAP = 16;
3094
+ let started = false;
3095
+ let stopped = false;
3096
+ function emit(kind, userid, payload) {
3097
+ const set = handlers.get(kind);
3098
+ if (!set || !set.size) {
3099
+ if (kind === "call") {
3100
+ pendingCalls.push({ userid, payload });
3101
+ if (pendingCalls.length > PENDING_CALL_CAP)
3102
+ pendingCalls.shift();
3103
+ }
3104
+ return;
3105
+ }
3106
+ for (const h of Array.from(set)) {
3107
+ try {
3108
+ h(userid, payload);
3109
+ } catch (e) {
3110
+ console.warn("[rtc] signal handler failed", e);
3111
+ }
3112
+ }
3113
+ }
3114
+ async function pollLoop() {
3115
+ let cursor = null;
3116
+ while (!stopped) {
3117
+ let signals = [];
3118
+ try {
3119
+ const u = cursor == null ? "/api/call-poll" : "/api/call-poll?since=" + cursor;
3120
+ const r = await fetch(u, { headers: { "cache-control": "no-cache" } });
3121
+ const d = await r.json();
3122
+ signals = d && d.signals || [];
3123
+ if (d && typeof d.cursor === "number")
3124
+ cursor = d.cursor;
3125
+ } catch (e) {
3126
+ await new Promise((res) => setTimeout(res, 1e3));
3127
+ continue;
3128
+ }
3129
+ for (const s of signals) {
3130
+ const env = dkRtcParseEnvelope(s.data);
3131
+ if (env) {
3132
+ emit(env.kind, s.userid, env);
3133
+ continue;
3134
+ }
3135
+ if (PW) {
3136
+ try {
3137
+ emit("call", s.userid, PW.decodeSignal(s.data));
3138
+ } catch (e) {
3139
+ }
3140
+ }
3141
+ }
3142
+ }
3143
+ }
3144
+ const bus = {
3145
+ start() {
3146
+ if (started)
3147
+ return;
3148
+ started = true;
3149
+ stopped = false;
3150
+ pollLoop();
3151
+ },
3152
+ on(kind, cb) {
3153
+ if (!handlers.has(kind))
3154
+ handlers.set(kind, /* @__PURE__ */ new Set());
3155
+ handlers.get(kind).add(cb);
3156
+ this.start();
3157
+ if (kind === "call" && pendingCalls.length) {
3158
+ const queued = pendingCalls.splice(0);
3159
+ console.log("[rtc] flushing " + queued.length + " buffered call signal(s)");
3160
+ for (const q of queued) {
3161
+ try {
3162
+ cb(q.userid, q.payload);
3163
+ } catch (e) {
3164
+ console.warn("[rtc] buffered call handler failed", e);
3165
+ }
3166
+ }
3167
+ }
3168
+ return () => {
3169
+ var _a;
3170
+ return (_a = handlers.get(kind)) == null ? void 0 : _a.delete(cb);
3171
+ };
3172
+ },
3173
+ async send(userid, data) {
3174
+ const body = JSON.stringify({ userid, data: typeof data === "string" ? data : JSON.stringify(data) });
3175
+ const r = await fetch("/api/call-signal", {
3176
+ method: "POST",
3177
+ headers: { "content-type": "application/json" },
3178
+ body
3179
+ });
3180
+ const d = await r.json().catch(() => ({ ok: r.ok }));
3181
+ if (!d.ok)
3182
+ throw new Error(d.error || "call-signal send failed");
3183
+ },
3184
+ stop() {
3185
+ stopped = true;
3186
+ }
3098
3187
  };
3099
- const t = tones[tone] || tones.ghost;
3100
- const h = size === "sm" ? 26 : size === "lg" ? 36 : 30;
3101
- return /* @__PURE__ */ React.createElement("button", { onClick, title, style: {
3102
- height: h,
3103
- padding: icon && !children ? 0 : "0 11px",
3104
- minWidth: icon && !children ? h : "auto",
3105
- borderRadius: 6,
3106
- cursor: "pointer",
3107
- fontFamily: "var(--mono)",
3108
- fontWeight: 600,
3109
- fontSize: size === "sm" ? 12 : 12.5,
3110
- letterSpacing: -0.1,
3111
- background: t.bg,
3112
- color: t.fg,
3113
- border: `1px solid ${t.bd}`,
3114
- display: "inline-flex",
3115
- alignItems: "center",
3116
- justifyContent: "center",
3117
- gap: 6,
3118
- whiteSpace: "nowrap",
3119
- ...style
3120
- } }, icon && /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 15, stroke: 2 }), children);
3188
+ window.__dkRtcSignalBus = bus;
3189
+ return bus;
3121
3190
  }
3122
- function Section({ label, count, trailing, style }) {
3123
- return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "0 2px", ...style } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", color: "var(--faint)" } }, label), count != null && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, color: "var(--faint)" } }, count), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, height: 1, background: "var(--line)" } }), trailing);
3191
+ function makeDaemonSignaling() {
3192
+ const PW = window.PeerWebRTC;
3193
+ const bus = dkRtcSignalBus();
3194
+ let off = null;
3195
+ return {
3196
+ async send(peerId, signal) {
3197
+ await bus.send(peerId, PW.encodeSignal(signal));
3198
+ },
3199
+ onSignal(cb) {
3200
+ if (off)
3201
+ off();
3202
+ off = bus.on("call", cb);
3203
+ },
3204
+ stop() {
3205
+ if (off) {
3206
+ off();
3207
+ off = null;
3208
+ }
3209
+ }
3210
+ };
3124
3211
  }
3125
- Object.assign(window, { DkIdenticon, DkBot, DkAvatar, DkImgAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
3212
+ Object.assign(window, { dkRtcSignalBus, makeDaemonSignaling });
3126
3213
  function useCallController(selfId, onCallLog) {
3127
3214
  const [incoming, setIncoming] = React.useState(null);
3128
3215
  const [active, setActive] = React.useState(null);
@@ -4291,6 +4378,7 @@ ${peer.address}`
4291
4378
  en: {
4292
4379
  chat: "Chat",
4293
4380
  network: "Network",
4381
+ apps: "Apps",
4294
4382
  profile: "Profile",
4295
4383
  recommended: "Discover",
4296
4384
  registered: "Names",
@@ -4422,6 +4510,7 @@ ${peer.address}`
4422
4510
  updRestarting: "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)",
4423
4511
  updNpx: "Running via npx \u2014 restarting picks up the latest version automatically.",
4424
4512
  updDev: "Running from a source checkout \u2014 update with:",
4513
+ updUnknown: "Update now runs the global install. If that is not how this was installed, run:",
4425
4514
  updLanNote: "agentnet (lan): one click reinstalls it and asks the running daemon to restart itself. Manual equivalent:",
4426
4515
  updCheck: "check for updates",
4427
4516
  updCheckLabel: "updates",
@@ -4468,6 +4557,7 @@ ${peer.address}`
4468
4557
  zh: {
4469
4558
  chat: "\u804A\u5929",
4470
4559
  network: "\u7F51\u7EDC",
4560
+ apps: "\u5E94\u7528",
4471
4561
  profile: "\u6211\u7684",
4472
4562
  recommended: "\u63A8\u8350",
4473
4563
  registered: "\u540D\u5F55",
@@ -4599,6 +4689,7 @@ ${peer.address}`
4599
4689
  updRestarting: "\u6B63\u5728\u91CD\u542F\u2026\u5B8C\u6210\u540E\u672C\u9875\u4F1A\u81EA\u52A8\u5237\u65B0\u3002(\u5982\u679C beagle \u662F\u5728\u7EC8\u7AEF\u91CC\u624B\u52A8\u542F\u52A8\u7684,\u8BF7\u91CD\u65B0\u8FD0\u884C\u4E00\u6B21\u3002)",
4600
4690
  updNpx: "\u4F60\u662F\u7528 npx \u8FD0\u884C\u7684\u2014\u2014\u91CD\u542F\u5373\u81EA\u52A8\u83B7\u53D6\u6700\u65B0\u7248\u3002",
4601
4691
  updDev: "\u5F53\u524D\u4ECE\u6E90\u7801\u76EE\u5F55\u8FD0\u884C,\u8BF7\u624B\u52A8\u66F4\u65B0:",
4692
+ updUnknown: "\u300C\u7ACB\u5373\u66F4\u65B0\u300D\u4F1A\u6267\u884C\u5168\u5C40\u5B89\u88C5\u3002\u5982\u679C\u4E0D\u662F\u8FD9\u6837\u88C5\u7684,\u8BF7\u8FD0\u884C:",
4602
4693
  updLanNote: "agentnet(lan):\u4E00\u952E\u91CD\u88C5\u5E76\u8BA9\u8FD0\u884C\u4E2D\u7684 daemon \u81EA\u6211\u91CD\u542F\u3002\u7B49\u4EF7\u624B\u52A8\u547D\u4EE4:",
4603
4694
  updCheck: "\u68C0\u67E5\u66F4\u65B0",
4604
4695
  updCheckLabel: "\u66F4\u65B0",
@@ -4721,10 +4812,10 @@ ${peer.address}`
4721
4812
  background: tone ? "var(--accent)" : "transparent",
4722
4813
  color: tone ? "#fff" : "var(--text)"
4723
4814
  } }, label);
4724
- return /* @__PURE__ */ React.createElement("div", { style: { position: "fixed", inset: 0, zIndex: 95, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 430, maxWidth: "94vw", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: "20px 22px", display: "flex", flexDirection: "column", gap: 14 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 700, color: "var(--text)" } }, T.updTitle || "Update available"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, behind.map((u) => /* @__PURE__ */ React.createElement("div", { key: u.pkg, style: { display: "flex", alignItems: "center", gap: 10, fontFamily: "var(--mono)", fontSize: 12.5 } }, /* @__PURE__ */ React.createElement("span", { style: { width: 70, color: "var(--faint)" } }, short(u.pkg)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--dim)" } }, u.current), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)" } }, "\u2192"), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--accent)", fontWeight: 700 } }, u.latest)))), phase === "idle" && appBehind && info.mode === "dev" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updDev || "Running from a source checkout \u2014 update with:", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("code", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)" } }, "git pull && npm run build")), phase === "idle" && appBehind && info.mode === "npx" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updNpx || "Running via npx \u2014 restarting picks up the latest version automatically."), lanBehind && phase === "idle" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updLanNote || "agentnet (lan): one click reinstalls it and asks the running daemon to restart itself. Manual equivalent:", /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 } }, /* @__PURE__ */ React.createElement("code", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 8, padding: "8px 10px", userSelect: "all", wordBreak: "break-all" } }, "npm i -g @decentnetwork/lan@latest && agentnet restart"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "copy", title: T.copy || "Copy", onClick: () => dkCopy("npm i -g @decentnetwork/lan@latest && agentnet restart") }))), phase === "lan-done" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--online)" } }, T.updLanDone || "lan updated \u2014 daemon restarted with the new version \u2713"), phase === "lan-norestart" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--warn, #f59e0b)" } }, T.updLanNoRestart || "lan updated, but the daemon was not running (or refused) \u2014 the new version applies on its next start."), phase === "running" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updBusy || "Updating\u2026 (about a minute)"), phase === "done" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--online)" } }, T.updDone || "Updated \u2014 restart to apply."), phase === "restarting" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updRestarting || "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)"), phase === "error" && (() => {
4815
+ return /* @__PURE__ */ React.createElement("div", { style: { position: "fixed", inset: 0, zIndex: 95, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 430, maxWidth: "94vw", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: "20px 22px", display: "flex", flexDirection: "column", gap: 14 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 700, color: "var(--text)" } }, T.updTitle || "Update available"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, behind.map((u) => /* @__PURE__ */ React.createElement("div", { key: u.pkg, style: { display: "flex", alignItems: "center", gap: 10, fontFamily: "var(--mono)", fontSize: 12.5 } }, /* @__PURE__ */ React.createElement("span", { style: { width: 70, color: "var(--faint)" } }, short(u.pkg)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--dim)" } }, u.current), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)" } }, "\u2192"), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--accent)", fontWeight: 700 } }, u.latest)))), phase === "idle" && appBehind && info.mode === "dev" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updDev || "Running from a source checkout \u2014 update with:", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("code", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)" } }, "git pull && npm run build")), phase === "idle" && appBehind && info.mode === "unknown" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updUnknown || "Update now runs the global install. If that is not how this was installed, run:", /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 } }, /* @__PURE__ */ React.createElement("code", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 8, padding: "8px 10px", userSelect: "all", wordBreak: "break-all" } }, "npm i -g @decentnetwork/beagle@latest"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "copy", title: T.copy || "Copy", onClick: () => dkCopy("npm i -g @decentnetwork/beagle@latest") }))), phase === "idle" && appBehind && info.mode === "npx" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updNpx || "Running via npx \u2014 restarting picks up the latest version automatically."), lanBehind && phase === "idle" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updLanNote || "agentnet (lan): one click reinstalls it and asks the running daemon to restart itself. Manual equivalent:", /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 } }, /* @__PURE__ */ React.createElement("code", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 8, padding: "8px 10px", userSelect: "all", wordBreak: "break-all" } }, "npm i -g @decentnetwork/lan@latest && agentnet restart"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "copy", title: T.copy || "Copy", onClick: () => dkCopy("npm i -g @decentnetwork/lan@latest && agentnet restart") }))), phase === "lan-done" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--online)" } }, T.updLanDone || "lan updated \u2014 daemon restarted with the new version \u2713"), phase === "lan-norestart" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--warn, #f59e0b)" } }, T.updLanNoRestart || "lan updated, but the daemon was not running (or refused) \u2014 the new version applies on its next start."), phase === "running" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updBusy || "Updating\u2026 (about a minute)"), phase === "done" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--online)" } }, T.updDone || "Updated \u2014 restart to apply."), phase === "restarting" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updRestarting || "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)"), phase === "error" && (() => {
4725
4816
  const m = /`([^`]+)`/.exec(err || "");
4726
4817
  return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--danger)", wordBreak: "break-word" } }, m ? err.replace("`" + m[1] + "`", "") : err), m && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("code", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 8, padding: "8px 10px", userSelect: "all", wordBreak: "break-all" } }, m[1]), /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "copy", title: T.copy || "Copy", onClick: () => dkCopy(m[1]) })), m && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)" } }, T.updManualHint || "Run it in a terminal, then click Restart to apply."));
4727
- })(), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 } }, (phase === "idle" || phase === "error" || phase === "lan-done" || phase === "lan-norestart") && btn(phase === "idle" ? T.updLater || "Later" : T.receivedClose || "Close", onClose), phase === "idle" && lanBehind && btn(T.updLanNow || "Update lan", doLanUpdate, !appBehind), phase === "idle" && appBehind && info.mode === "global" && btn(T.updNow || "Update now", doUpdate, true), phase === "idle" && appBehind && info.mode === "npx" && btn(T.updRestart || "Restart", doRestart, true), phase === "error" && btn(T.updRestart || "Restart", doRestart, true), phase === "done" && btn(T.updRestart || "Restart", doRestart, true))));
4818
+ })(), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 } }, (phase === "idle" || phase === "error" || phase === "lan-done" || phase === "lan-norestart") && btn(phase === "idle" ? T.updLater || "Later" : T.receivedClose || "Close", onClose), phase === "idle" && lanBehind && btn(T.updLanNow || "Update lan", doLanUpdate, !appBehind), phase === "idle" && appBehind && (info.mode === "global" || info.mode === "unknown") && btn(T.updNow || "Update now", doUpdate, true), phase === "idle" && appBehind && info.mode === "npx" && btn(T.updRestart || "Restart", doRestart, true), phase === "error" && btn(T.updRestart || "Restart", doRestart, true), phase === "done" && btn(T.updRestart || "Restart", doRestart, true))));
4728
4819
  }
4729
4820
  function DkApp() {
4730
4821
  const [t, setTweak] = useTweaks({ ...DK_DEFAULTS, lang: dkDetectLang() });
@@ -4974,11 +5065,13 @@ ${peer.address}`
4974
5065
  { id: "recommended", icon: "sparkles", label: T.recommended },
4975
5066
  { id: "registered", icon: "at", label: T.registered },
4976
5067
  { id: "network", icon: "network", label: T.network },
5068
+ { id: "apps", icon: "grid", label: T.apps },
4977
5069
  { id: "profile", icon: "userRound", label: T.profile }
4978
5070
  ];
4979
- return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(DkBrowserNotice, { lang: t.lang }), /* @__PURE__ */ React.createElement("div", { style: { height: 46, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px" } }, /* @__PURE__ */ React.createElement("svg", { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "var(--accent)", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", style: { display: "block", flexShrink: 0 } }, /* @__PURE__ */ React.createElement("path", { d: "m4.5 17 6-6-6-6" }), /* @__PURE__ */ React.createElement("path", { d: "M12 18.5h7.5" })), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, letterSpacing: -0.3, color: "var(--text)" } }, "beagle"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "\xB7 ", nav.find((n) => n.id === tab).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel, " \xB7 lan ", me.lanVer), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, padding: "0 4px" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, copy: me.ip }, me.ip)), /* @__PURE__ */ React.createElement("span", { style: { width: 1, height: 22, background: "var(--line)" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 26, radius: 7 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text)" } }, me.name))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr("") }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit, t, setTweak })), data.locked && /* @__PURE__ */ React.createElement(
5071
+ return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(DkBrowserNotice, { lang: t.lang }), /* @__PURE__ */ React.createElement("div", { style: { height: 46, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px" } }, /* @__PURE__ */ React.createElement("svg", { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "var(--accent)", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", style: { display: "block", flexShrink: 0 } }, /* @__PURE__ */ React.createElement("path", { d: "m4.5 17 6-6-6-6" }), /* @__PURE__ */ React.createElement("path", { d: "M12 18.5h7.5" })), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, letterSpacing: -0.3, color: "var(--text)" } }, "beagle"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "\xB7 ", nav.find((n) => n.id === tab).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel, " \xB7 lan ", me.lanVer), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, padding: "0 4px" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, copy: me.ip }, me.ip)), /* @__PURE__ */ React.createElement("span", { style: { width: 1, height: 22, background: "var(--line)" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 26, radius: 7 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text)" } }, me.name))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr("") }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit, t, setTweak })), data.locked && /* @__PURE__ */ React.createElement(
4980
5072
  DkLockedOut,
4981
5073
  {
5074
+ compact: true,
4982
5075
  lang: t.lang,
4983
5076
  onTakeover: () => window.BeagleWeb.takeover().then((st) => {
4984
5077
  data.refresh();