@decentnetwork/beagle-ui 0.1.3 → 0.2.1

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 (2) hide show
  1. package/dist/beagle-ui.js +516 -421
  2. package/package.json +1 -1
package/dist/beagle-ui.js CHANGED
@@ -1,325 +1,4 @@
1
- export const BEAGLE_UI_VERSION = "0.1.3";
2
- // src/icons.jsx
3
- var ICON_PATHS = {
4
- // ---- tab bar (the four must feel like one set) ----
5
- 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"/>',
6
- 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"/>',
7
- 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"/>',
8
- userRound: '<circle cx="12" cy="8" r="4.5"/><path d="M20 21a8 8 0 0 0-16 0"/>',
9
- // ---- actions / chrome ----
10
- search: '<circle cx="11" cy="11" r="7.5"/><path d="m21 21-4.3-4.3"/>',
11
- plus: '<path d="M5 12h14M12 5v14"/>',
12
- chevronRight: '<path d="m9 18 6-6-6-6"/>',
13
- chevronLeft: '<path d="m15 18-6-6 6-6"/>',
14
- info: '<circle cx="12" cy="12" r="9.5"/><path d="M12 16.5v-5M12 7.6h.01"/>',
15
- 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"/>',
16
- x: '<path d="M18 6 6 18M6 6l12 12"/>',
17
- check: '<path d="M20 6 9 17l-5-5"/>',
18
- checkCheck: '<path d="M18 6 7 17l-5-5"/><path d="m22 10-7.6 7.6L13 16"/>',
19
- 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"/>',
20
- // Three linked nodes — an address is for passing ON to someone else, so it
21
- // must not wear the same glyph as the userid's plain copy.
22
- 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"/>',
23
- refresh: '<path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 4v4.5h-4.5"/>',
24
- clock: '<circle cx="12" cy="12" r="9.5"/><path d="M12 7.5V12l3 2"/>',
25
- 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"/>',
26
- 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"/>',
27
- // ---- app tiles ----
28
- 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"/>',
29
- monitor: '<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>',
30
- 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"/>',
31
- 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"/>',
32
- 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"/>',
33
- 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"/>',
34
- 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"/>',
35
- 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"/>',
36
- // ---- chat composer ----
37
- 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"/>',
38
- 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"/>',
39
- bolt: '<path d="M13 2 4 13h7l-1 9 9-11h-7l1-9Z"/>',
40
- arrowUp: '<path d="M12 20V5M6 11l6-6 6 6"/>',
41
- 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"/>',
42
- // ---- account ----
43
- 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"/>',
44
- download: '<path d="M12 3.5v11M7.5 10l4.5 4.5 4.5-4.5"/><path d="M5 20.5h14"/>',
45
- // Folder with a magnifier corner — "reveal / show in file manager".
46
- 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"/>',
47
- 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"/>',
48
- 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"/>',
49
- 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"/>',
50
- trendingUp: '<path d="M22 7.5 13.5 16l-4.5-4.5L2 18.5"/><path d="M16.5 7.5H22v5.5"/>',
51
- 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"/>',
52
- 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"/>',
53
- 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"/>',
54
- 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"/>',
55
- 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"/>',
56
- 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"/>',
57
- 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"/>',
58
- chevronDown: '<path d="m6 9 6 6 6-6"/>',
59
- 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"/>',
60
- 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"/>',
61
- 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"/>',
62
- arrowDown: '<path d="M12 4v15M6 13l6 6 6-6"/>',
63
- shrink: '<path d="M4 14h6v6"/><path d="M20 10h-6V4"/><path d="m14 10 7-7"/><path d="m3 21 7-7"/>',
64
- 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"/>',
65
- theater: '<rect x="2.5" y="6.5" width="19" height="11" rx="2"/>',
66
- 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"/>',
67
- 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"/>'
68
- };
69
- function Icon({ name, size = 24, stroke = 1.75, color = "currentColor", fill = "none", style }) {
70
- const d = ICON_PATHS[name];
71
- if (!d)
72
- return null;
73
- return /* @__PURE__ */ React.createElement(
74
- "svg",
75
- {
76
- width: size,
77
- height: size,
78
- viewBox: "0 0 24 24",
79
- fill,
80
- stroke: color,
81
- strokeWidth: stroke,
82
- strokeLinecap: "round",
83
- strokeLinejoin: "round",
84
- style: { display: "block", flexShrink: 0, ...style },
85
- dangerouslySetInnerHTML: { __html: d }
86
- }
87
- );
88
- }
89
- Object.assign(window, { Icon, ICON_PATHS });
90
-
91
- // src/rtc-signaling.jsx
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
-
1
+ export const BEAGLE_UI_VERSION = "0.2.1";
323
2
  // src/ui-host.jsx
324
3
  var H = {};
325
4
  var hostNeedsWelcome = false;
@@ -629,19 +308,108 @@ function useDaemonData() {
629
308
  setThreads((t) => Object.assign({}, t, { [peerId]: withDay }));
630
309
  } catch (e) {
631
310
  }
632
- }, []);
633
- 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
+
325
+ // src/icons.jsx
326
+ var ICON_PATHS = {
327
+ // ---- tab bar (the four must feel like one set) ----
328
+ 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"/>',
329
+ 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"/>',
330
+ 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"/>',
331
+ userRound: '<circle cx="12" cy="8" r="4.5"/><path d="M20 21a8 8 0 0 0-16 0"/>',
332
+ // ---- actions / chrome ----
333
+ search: '<circle cx="11" cy="11" r="7.5"/><path d="m21 21-4.3-4.3"/>',
334
+ plus: '<path d="M5 12h14M12 5v14"/>',
335
+ chevronRight: '<path d="m9 18 6-6-6-6"/>',
336
+ chevronLeft: '<path d="m15 18-6-6 6-6"/>',
337
+ info: '<circle cx="12" cy="12" r="9.5"/><path d="M12 16.5v-5M12 7.6h.01"/>',
338
+ 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"/>',
339
+ x: '<path d="M18 6 6 18M6 6l12 12"/>',
340
+ check: '<path d="M20 6 9 17l-5-5"/>',
341
+ checkCheck: '<path d="M18 6 7 17l-5-5"/><path d="m22 10-7.6 7.6L13 16"/>',
342
+ 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"/>',
343
+ // Three linked nodes — an address is for passing ON to someone else, so it
344
+ // must not wear the same glyph as the userid's plain copy.
345
+ 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"/>',
346
+ refresh: '<path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 4v4.5h-4.5"/>',
347
+ clock: '<circle cx="12" cy="12" r="9.5"/><path d="M12 7.5V12l3 2"/>',
348
+ 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"/>',
349
+ 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"/>',
350
+ // ---- app tiles ----
351
+ 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"/>',
352
+ monitor: '<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>',
353
+ 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"/>',
354
+ 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"/>',
355
+ 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"/>',
356
+ 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"/>',
357
+ 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"/>',
358
+ 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"/>',
359
+ // ---- chat composer ----
360
+ 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"/>',
361
+ 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"/>',
362
+ bolt: '<path d="M13 2 4 13h7l-1 9 9-11h-7l1-9Z"/>',
363
+ arrowUp: '<path d="M12 20V5M6 11l6-6 6 6"/>',
364
+ 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"/>',
365
+ // ---- account ----
366
+ 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"/>',
367
+ download: '<path d="M12 3.5v11M7.5 10l4.5 4.5 4.5-4.5"/><path d="M5 20.5h14"/>',
368
+ // Folder with a magnifier corner — "reveal / show in file manager".
369
+ 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"/>',
370
+ 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"/>',
371
+ 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"/>',
372
+ 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"/>',
373
+ trendingUp: '<path d="M22 7.5 13.5 16l-4.5-4.5L2 18.5"/><path d="M16.5 7.5H22v5.5"/>',
374
+ 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"/>',
375
+ 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"/>',
376
+ 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"/>',
377
+ 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"/>',
378
+ 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"/>',
379
+ 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"/>',
380
+ 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"/>',
381
+ chevronDown: '<path d="m6 9 6 6 6-6"/>',
382
+ 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"/>',
383
+ 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"/>',
384
+ 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"/>',
385
+ arrowDown: '<path d="M12 4v15M6 13l6 6 6-6"/>',
386
+ shrink: '<path d="M4 14h6v6"/><path d="M20 10h-6V4"/><path d="m14 10 7-7"/><path d="m3 21 7-7"/>',
387
+ 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"/>',
388
+ theater: '<rect x="2.5" y="6.5" width="19" height="11" rx="2"/>',
389
+ 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"/>',
390
+ 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"/>'
391
+ };
392
+ function Icon({ name, size = 24, stroke = 1.75, color = "currentColor", fill = "none", style }) {
393
+ const d = ICON_PATHS[name];
394
+ if (!d)
395
+ return null;
396
+ return /* @__PURE__ */ React.createElement(
397
+ "svg",
398
+ {
399
+ width: size,
400
+ height: size,
401
+ viewBox: "0 0 24 24",
402
+ fill,
403
+ stroke: color,
404
+ strokeWidth: stroke,
405
+ strokeLinecap: "round",
406
+ strokeLinejoin: "round",
407
+ style: { display: "block", flexShrink: 0, ...style },
408
+ dangerouslySetInnerHTML: { __html: d }
409
+ }
410
+ );
634
411
  }
635
- Object.assign(window, {
636
- dkHash,
637
- dkIdenticon,
638
- dkClock,
639
- dkDayLabel,
640
- dkApi,
641
- useDaemonData,
642
- DK_ME_FALLBACK,
643
- dkCopy
644
- });
412
+ Object.assign(window, { Icon, ICON_PATHS });
645
413
 
646
414
  // src/chat.jsx
647
415
  function RequestsBlock({ T, requests, onAct }) {
@@ -685,7 +453,7 @@ function RequestsBlock({ T, requests, onAct }) {
685
453
  display: "flex",
686
454
  flexDirection: "column",
687
455
  gap: 8
688
- } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, r.punkId != null ? /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: r.punkId, size: 30, radius: 7, fallbackSeed: r.carrier }) : /* @__PURE__ */ React.createElement(DkIdenticon, { seed: r.carrier, size: 30, radius: 7 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: 1 } }, r.name ? /* @__PURE__ */ React.createElement("div", { style: {
456
+ } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkEnsAvatar, { userid: r.userid || r.id, name: r.name, fallbackSeed: r.carrier, size: 30, radius: 7 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: 1 } }, r.name ? /* @__PURE__ */ React.createElement("div", { style: {
689
457
  fontFamily: "var(--ui)",
690
458
  fontSize: 13,
691
459
  fontWeight: 600,
@@ -3047,95 +2815,413 @@ function shortKey(s, head = 6, tail = 5) {
3047
2815
  return s;
3048
2816
  return s.slice(0, head) + "\u2026" + s.slice(-tail);
3049
2817
  }
3050
- function RouteTag({ peer }) {
3051
- if (!peer.online)
3052
- return /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline");
3053
- const relay = peer.via === "relay";
3054
- const via = peer.via || "online";
3055
- return /* @__PURE__ */ React.createElement(Tag, { tone: relay ? "warn" : "ok" }, peer.ping != null ? `${via} \xB7 ${peer.ping}ms` : via);
2818
+ function RouteTag({ peer }) {
2819
+ if (!peer.online)
2820
+ return /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline");
2821
+ const relay = peer.via === "relay";
2822
+ const via = peer.via || "online";
2823
+ return /* @__PURE__ */ React.createElement(Tag, { tone: relay ? "warn" : "ok" }, peer.ping != null ? `${via} \xB7 ${peer.ping}ms` : via);
2824
+ }
2825
+ function Tag({ children, tone = "neutral", style, title }) {
2826
+ const tones = {
2827
+ neutral: { bg: "var(--chip)", fg: "var(--dim)", bd: "transparent" },
2828
+ ok: { bg: "color-mix(in oklab, var(--online), transparent 86%)", fg: "var(--online)", bd: "transparent" },
2829
+ warn: { bg: "color-mix(in oklab, var(--warn), transparent 86%)", fg: "var(--warn)", bd: "transparent" },
2830
+ off: { bg: "var(--chip)", fg: "var(--faint)", bd: "transparent" },
2831
+ accent: { bg: "color-mix(in oklab, var(--accent), transparent 86%)", fg: "var(--accent)", bd: "transparent" },
2832
+ danger: { bg: "color-mix(in oklab, var(--danger), transparent 88%)", fg: "var(--danger)", bd: "transparent" }
2833
+ };
2834
+ const t = tones[tone] || tones.neutral;
2835
+ return /* @__PURE__ */ React.createElement("span", { title, style: {
2836
+ display: "inline-flex",
2837
+ alignItems: "center",
2838
+ gap: 4,
2839
+ height: 18,
2840
+ padding: "0 6px",
2841
+ borderRadius: 4,
2842
+ background: t.bg,
2843
+ color: t.fg,
2844
+ border: `1px solid ${t.bd}`,
2845
+ fontFamily: "var(--mono)",
2846
+ fontSize: 11,
2847
+ fontWeight: 600,
2848
+ letterSpacing: -0.1,
2849
+ lineHeight: 1,
2850
+ whiteSpace: "nowrap",
2851
+ ...style
2852
+ } }, children);
2853
+ }
2854
+ function Unread({ n }) {
2855
+ if (!n)
2856
+ return null;
2857
+ return /* @__PURE__ */ React.createElement("span", { style: {
2858
+ minWidth: 18,
2859
+ height: 18,
2860
+ padding: "0 5px",
2861
+ borderRadius: 999,
2862
+ flexShrink: 0,
2863
+ background: "var(--accent)",
2864
+ color: "#fff",
2865
+ fontFamily: "var(--mono)",
2866
+ fontSize: 11,
2867
+ fontWeight: 700,
2868
+ lineHeight: "18px",
2869
+ textAlign: "center"
2870
+ } }, n);
2871
+ }
2872
+ function Btn({ children, icon, tone = "ghost", onClick, size = "md", title, style }) {
2873
+ const tones = {
2874
+ ghost: { bg: "transparent", fg: "var(--text)", bd: "var(--line)" },
2875
+ solid: { bg: "var(--accent)", fg: "#fff", bd: "transparent" },
2876
+ soft: { bg: "var(--chip)", fg: "var(--text)", bd: "transparent" },
2877
+ ok: { bg: "color-mix(in oklab, var(--online), transparent 84%)", fg: "var(--online)", bd: "transparent" },
2878
+ danger: { bg: "transparent", fg: "var(--danger)", bd: "color-mix(in oklab, var(--danger), transparent 70%)" }
2879
+ };
2880
+ const t = tones[tone] || tones.ghost;
2881
+ const h = size === "sm" ? 26 : size === "lg" ? 36 : 30;
2882
+ return /* @__PURE__ */ React.createElement("button", { onClick, title, style: {
2883
+ height: h,
2884
+ padding: icon && !children ? 0 : "0 11px",
2885
+ minWidth: icon && !children ? h : "auto",
2886
+ borderRadius: 6,
2887
+ cursor: "pointer",
2888
+ fontFamily: "var(--mono)",
2889
+ fontWeight: 600,
2890
+ fontSize: size === "sm" ? 12 : 12.5,
2891
+ letterSpacing: -0.1,
2892
+ background: t.bg,
2893
+ color: t.fg,
2894
+ border: `1px solid ${t.bd}`,
2895
+ display: "inline-flex",
2896
+ alignItems: "center",
2897
+ justifyContent: "center",
2898
+ gap: 6,
2899
+ whiteSpace: "nowrap",
2900
+ ...style
2901
+ } }, icon && /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 15, stroke: 2 }), children);
2902
+ }
2903
+ function Section({ label, count, trailing, style }) {
2904
+ 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);
2905
+ }
2906
+ Object.assign(window, { DkIdenticon, DkBot, DkAvatar, DkImgAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
2907
+
2908
+ // src/apps.jsx
2909
+ var APPS = [
2910
+ {
2911
+ id: "meet",
2912
+ name: "Beagle Meet",
2913
+ origin: "https://snoopy.fi.chat",
2914
+ url: "https://snoopy.fi.chat",
2915
+ 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" },
2916
+ icon: "video"
2917
+ },
2918
+ {
2919
+ id: "interview",
2920
+ name: "interview.network",
2921
+ origin: "https://interview.network",
2922
+ url: "https://interview.network",
2923
+ 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" },
2924
+ icon: "users"
2925
+ },
2926
+ {
2927
+ id: "readyplayer",
2928
+ name: "readyplayer.network",
2929
+ origin: "https://readyplayer.network",
2930
+ url: "https://readyplayer.network",
2931
+ blurb: { en: "Membership and the services that come with it.", zh: "\u4F1A\u5458\u4E0E\u914D\u5957\u670D\u52A1\u3002" },
2932
+ icon: "starCoin"
2933
+ },
2934
+ {
2935
+ id: "btc",
2936
+ name: "btc.fi.chat",
2937
+ origin: "https://btc.fi.chat",
2938
+ url: "https://btc.fi.chat",
2939
+ blurb: { en: "Trading desk and signals.", zh: "\u4EA4\u6613\u53F0\u4E0E\u4FE1\u53F7\u3002" },
2940
+ icon: "trendingUp"
2941
+ }
2942
+ ];
2943
+ function dkB64Url(obj) {
2944
+ const json = JSON.stringify(obj);
2945
+ const b64 = btoa(unescape(encodeURIComponent(json)));
2946
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2947
+ }
2948
+ function AppsTab({ T, t }) {
2949
+ const zh = t && t.lang === "zh";
2950
+ const [busy, setBusy] = React.useState(null);
2951
+ const [err, setErr] = React.useState(null);
2952
+ const open = async (app) => {
2953
+ setBusy(app.id);
2954
+ setErr(null);
2955
+ const win = window.open("", "_blank");
2956
+ try {
2957
+ const r = await dkPost("/api/launch-token", { origin: app.origin });
2958
+ if (!r || r.ok === false)
2959
+ throw new Error(r && r.error || "could not sign");
2960
+ const { ok, ...assertion } = r;
2961
+ const url = app.url + "#beagle=" + dkB64Url(assertion);
2962
+ if (win)
2963
+ win.location = url;
2964
+ else
2965
+ window.open(url, "_blank");
2966
+ } catch (e) {
2967
+ if (win)
2968
+ win.close();
2969
+ setErr(String(e && e.message || e));
2970
+ } finally {
2971
+ setBusy(null);
2972
+ }
2973
+ };
2974
+ 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: {
2975
+ display: "flex",
2976
+ alignItems: "center",
2977
+ gap: 14,
2978
+ padding: "14px 16px",
2979
+ borderRadius: 12,
2980
+ background: "var(--panel)",
2981
+ border: "1px solid var(--line)"
2982
+ } }, /* @__PURE__ */ React.createElement("div", { style: {
2983
+ width: 38,
2984
+ height: 38,
2985
+ borderRadius: 10,
2986
+ flexShrink: 0,
2987
+ background: "var(--panel-2)",
2988
+ display: "flex",
2989
+ alignItems: "center",
2990
+ justifyContent: "center"
2991
+ } }, /* @__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")))));
2992
+ }
2993
+
2994
+ // src/rtc-signaling.jsx
2995
+ var DK_FALLBACK_ICE_SERVERS = [
2996
+ { urls: "stun:stun.l.google.com:19302" },
2997
+ { urls: "turn:tokyo.fi.chat:3478", username: "allcom", credential: "allcompass" },
2998
+ { urls: "turn:tokyo.fi.chat:3478?transport=tcp", username: "allcom", credential: "allcompass" }
2999
+ ];
3000
+ var DK_LIVE_ICE_SERVERS = [];
3001
+ var _dkIcePromise = null;
3002
+ var _dkIceWarmPromise = null;
3003
+ function dkWarmCallIce(iceServers) {
3004
+ if (_dkIceWarmPromise)
3005
+ return _dkIceWarmPromise;
3006
+ if (!window.RTCPeerConnection || !iceServers || !iceServers.length) {
3007
+ _dkIceWarmPromise = Promise.resolve();
3008
+ return _dkIceWarmPromise;
3009
+ }
3010
+ _dkIceWarmPromise = new Promise((resolve) => {
3011
+ let pc;
3012
+ let finished = false;
3013
+ const done = (why) => {
3014
+ if (finished)
3015
+ return;
3016
+ finished = true;
3017
+ try {
3018
+ if (pc)
3019
+ pc.close();
3020
+ } catch (e) {
3021
+ }
3022
+ console.log("[rtc] ICE warmup done (" + why + ")");
3023
+ resolve();
3024
+ };
3025
+ try {
3026
+ pc = new RTCPeerConnection({ iceServers, iceTransportPolicy: "all" });
3027
+ const timer = setTimeout(() => done("timeout"), 5e3);
3028
+ pc.onicecandidate = (ev) => {
3029
+ const c = ev.candidate && ev.candidate.candidate;
3030
+ if (c && /typ relay/.test(c)) {
3031
+ clearTimeout(timer);
3032
+ done("relay");
3033
+ }
3034
+ };
3035
+ pc.onicegatheringstatechange = () => {
3036
+ if (pc.iceGatheringState === "complete") {
3037
+ clearTimeout(timer);
3038
+ done("gather-complete");
3039
+ }
3040
+ };
3041
+ pc.createDataChannel("ice-warm");
3042
+ pc.createOffer().then((o) => pc.setLocalDescription(o)).catch(() => done("offer-fail"));
3043
+ } catch (e) {
3044
+ done("error");
3045
+ }
3046
+ });
3047
+ return _dkIceWarmPromise;
3048
+ }
3049
+ function dkLoadCallIceServers() {
3050
+ if (_dkIcePromise)
3051
+ return _dkIcePromise;
3052
+ _dkIcePromise = (async () => {
3053
+ try {
3054
+ const r = await fetch("/api/call-ice-servers", { headers: { "cache-control": "no-cache" } });
3055
+ const d = await r.json();
3056
+ if (d && d.ok && Array.isArray(d.iceServers) && d.iceServers.length) {
3057
+ DK_LIVE_ICE_SERVERS.splice(0, DK_LIVE_ICE_SERVERS.length, ...d.iceServers);
3058
+ console.log("[rtc] Carrier bootstrap ICE servers:", DK_LIVE_ICE_SERVERS.map((s) => s.urls));
3059
+ await dkWarmCallIce(DK_LIVE_ICE_SERVERS);
3060
+ return DK_LIVE_ICE_SERVERS;
3061
+ }
3062
+ console.warn("[rtc] call-ice-servers unavailable:", d && d.error);
3063
+ } catch (e) {
3064
+ console.warn("[rtc] call-ice-servers fetch failed", e);
3065
+ }
3066
+ console.warn("[rtc] falling back to tokyo TURN \u2014 web\u2194Android online ICE may fail");
3067
+ DK_LIVE_ICE_SERVERS.splice(0, DK_LIVE_ICE_SERVERS.length, ...DK_FALLBACK_ICE_SERVERS.map((s) => ({ ...s })));
3068
+ await dkWarmCallIce(DK_LIVE_ICE_SERVERS);
3069
+ return DK_LIVE_ICE_SERVERS;
3070
+ })();
3071
+ return _dkIcePromise;
3056
3072
  }
3057
- function Tag({ children, tone = "neutral", style, title }) {
3058
- const tones = {
3059
- neutral: { bg: "var(--chip)", fg: "var(--dim)", bd: "transparent" },
3060
- ok: { bg: "color-mix(in oklab, var(--online), transparent 86%)", fg: "var(--online)", bd: "transparent" },
3061
- warn: { bg: "color-mix(in oklab, var(--warn), transparent 86%)", fg: "var(--warn)", bd: "transparent" },
3062
- off: { bg: "var(--chip)", fg: "var(--faint)", bd: "transparent" },
3063
- accent: { bg: "color-mix(in oklab, var(--accent), transparent 86%)", fg: "var(--accent)", bd: "transparent" },
3064
- danger: { bg: "color-mix(in oklab, var(--danger), transparent 88%)", fg: "var(--danger)", bd: "transparent" }
3065
- };
3066
- const t = tones[tone] || tones.neutral;
3067
- return /* @__PURE__ */ React.createElement("span", { title, style: {
3068
- display: "inline-flex",
3069
- alignItems: "center",
3070
- gap: 4,
3071
- height: 18,
3072
- padding: "0 6px",
3073
- borderRadius: 4,
3074
- background: t.bg,
3075
- color: t.fg,
3076
- border: `1px solid ${t.bd}`,
3077
- fontFamily: "var(--mono)",
3078
- fontSize: 11,
3079
- fontWeight: 600,
3080
- letterSpacing: -0.1,
3081
- lineHeight: 1,
3082
- whiteSpace: "nowrap",
3083
- ...style
3084
- } }, children);
3073
+ function dkAwaitCallIceServers(ms) {
3074
+ const budget = typeof ms === "number" ? ms : 1e4;
3075
+ return Promise.race([
3076
+ dkLoadCallIceServers(),
3077
+ new Promise((resolve) => setTimeout(() => {
3078
+ if (!DK_LIVE_ICE_SERVERS.length) {
3079
+ DK_LIVE_ICE_SERVERS.splice(0, DK_LIVE_ICE_SERVERS.length, ...DK_FALLBACK_ICE_SERVERS.map((s) => ({ ...s })));
3080
+ console.warn("[rtc] ICE wait timed out \u2014 using fallback");
3081
+ }
3082
+ resolve(DK_LIVE_ICE_SERVERS);
3083
+ }, budget))
3084
+ ]).then(() => DK_LIVE_ICE_SERVERS);
3085
3085
  }
3086
- function Unread({ n }) {
3087
- if (!n)
3086
+ dkLoadCallIceServers();
3087
+ function dkRtcParseEnvelope(data) {
3088
+ const text = String(data || "").replace(/\0+$/u, "").trim();
3089
+ if (!text)
3088
3090
  return null;
3089
- return /* @__PURE__ */ React.createElement("span", { style: {
3090
- minWidth: 18,
3091
- height: 18,
3092
- padding: "0 5px",
3093
- borderRadius: 999,
3094
- flexShrink: 0,
3095
- background: "var(--accent)",
3096
- color: "#fff",
3097
- fontFamily: "var(--mono)",
3098
- fontSize: 11,
3099
- fontWeight: 700,
3100
- lineHeight: "18px",
3101
- textAlign: "center"
3102
- } }, n);
3091
+ try {
3092
+ const obj = JSON.parse(text);
3093
+ if (obj && obj.dkRtc === 1 && typeof obj.kind === "string")
3094
+ return obj;
3095
+ } catch (e) {
3096
+ }
3097
+ return null;
3103
3098
  }
3104
- function Btn({ children, icon, tone = "ghost", onClick, size = "md", title, style }) {
3105
- const tones = {
3106
- ghost: { bg: "transparent", fg: "var(--text)", bd: "var(--line)" },
3107
- solid: { bg: "var(--accent)", fg: "#fff", bd: "transparent" },
3108
- soft: { bg: "var(--chip)", fg: "var(--text)", bd: "transparent" },
3109
- ok: { bg: "color-mix(in oklab, var(--online), transparent 84%)", fg: "var(--online)", bd: "transparent" },
3110
- danger: { bg: "transparent", fg: "var(--danger)", bd: "color-mix(in oklab, var(--danger), transparent 70%)" }
3099
+ function dkRtcSignalBus() {
3100
+ if (window.__dkRtcSignalBus)
3101
+ return window.__dkRtcSignalBus;
3102
+ const PW = window.PeerWebRTC;
3103
+ const handlers = /* @__PURE__ */ new Map();
3104
+ const pendingCalls = [];
3105
+ const PENDING_CALL_CAP = 16;
3106
+ let started = false;
3107
+ let stopped = false;
3108
+ function emit(kind, userid, payload) {
3109
+ const set = handlers.get(kind);
3110
+ if (!set || !set.size) {
3111
+ if (kind === "call") {
3112
+ pendingCalls.push({ userid, payload });
3113
+ if (pendingCalls.length > PENDING_CALL_CAP)
3114
+ pendingCalls.shift();
3115
+ }
3116
+ return;
3117
+ }
3118
+ for (const h of Array.from(set)) {
3119
+ try {
3120
+ h(userid, payload);
3121
+ } catch (e) {
3122
+ console.warn("[rtc] signal handler failed", e);
3123
+ }
3124
+ }
3125
+ }
3126
+ async function pollLoop() {
3127
+ let cursor = null;
3128
+ while (!stopped) {
3129
+ let signals = [];
3130
+ try {
3131
+ const u = cursor == null ? "/api/call-poll" : "/api/call-poll?since=" + cursor;
3132
+ const r = await fetch(u, { headers: { "cache-control": "no-cache" } });
3133
+ const d = await r.json();
3134
+ signals = d && d.signals || [];
3135
+ if (d && typeof d.cursor === "number")
3136
+ cursor = d.cursor;
3137
+ } catch (e) {
3138
+ await new Promise((res) => setTimeout(res, 1e3));
3139
+ continue;
3140
+ }
3141
+ for (const s of signals) {
3142
+ const env = dkRtcParseEnvelope(s.data);
3143
+ if (env) {
3144
+ emit(env.kind, s.userid, env);
3145
+ continue;
3146
+ }
3147
+ if (PW) {
3148
+ try {
3149
+ emit("call", s.userid, PW.decodeSignal(s.data));
3150
+ } catch (e) {
3151
+ }
3152
+ }
3153
+ }
3154
+ }
3155
+ }
3156
+ const bus = {
3157
+ start() {
3158
+ if (started)
3159
+ return;
3160
+ started = true;
3161
+ stopped = false;
3162
+ pollLoop();
3163
+ },
3164
+ on(kind, cb) {
3165
+ if (!handlers.has(kind))
3166
+ handlers.set(kind, /* @__PURE__ */ new Set());
3167
+ handlers.get(kind).add(cb);
3168
+ this.start();
3169
+ if (kind === "call" && pendingCalls.length) {
3170
+ const queued = pendingCalls.splice(0);
3171
+ console.log("[rtc] flushing " + queued.length + " buffered call signal(s)");
3172
+ for (const q of queued) {
3173
+ try {
3174
+ cb(q.userid, q.payload);
3175
+ } catch (e) {
3176
+ console.warn("[rtc] buffered call handler failed", e);
3177
+ }
3178
+ }
3179
+ }
3180
+ return () => {
3181
+ var _a;
3182
+ return (_a = handlers.get(kind)) == null ? void 0 : _a.delete(cb);
3183
+ };
3184
+ },
3185
+ async send(userid, data) {
3186
+ const body = JSON.stringify({ userid, data: typeof data === "string" ? data : JSON.stringify(data) });
3187
+ const r = await fetch("/api/call-signal", {
3188
+ method: "POST",
3189
+ headers: { "content-type": "application/json" },
3190
+ body
3191
+ });
3192
+ const d = await r.json().catch(() => ({ ok: r.ok }));
3193
+ if (!d.ok)
3194
+ throw new Error(d.error || "call-signal send failed");
3195
+ },
3196
+ stop() {
3197
+ stopped = true;
3198
+ }
3111
3199
  };
3112
- const t = tones[tone] || tones.ghost;
3113
- const h = size === "sm" ? 26 : size === "lg" ? 36 : 30;
3114
- return /* @__PURE__ */ React.createElement("button", { onClick, title, style: {
3115
- height: h,
3116
- padding: icon && !children ? 0 : "0 11px",
3117
- minWidth: icon && !children ? h : "auto",
3118
- borderRadius: 6,
3119
- cursor: "pointer",
3120
- fontFamily: "var(--mono)",
3121
- fontWeight: 600,
3122
- fontSize: size === "sm" ? 12 : 12.5,
3123
- letterSpacing: -0.1,
3124
- background: t.bg,
3125
- color: t.fg,
3126
- border: `1px solid ${t.bd}`,
3127
- display: "inline-flex",
3128
- alignItems: "center",
3129
- justifyContent: "center",
3130
- gap: 6,
3131
- whiteSpace: "nowrap",
3132
- ...style
3133
- } }, icon && /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 15, stroke: 2 }), children);
3200
+ window.__dkRtcSignalBus = bus;
3201
+ return bus;
3134
3202
  }
3135
- function Section({ label, count, trailing, style }) {
3136
- 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);
3203
+ function makeDaemonSignaling() {
3204
+ const PW = window.PeerWebRTC;
3205
+ const bus = dkRtcSignalBus();
3206
+ let off = null;
3207
+ return {
3208
+ async send(peerId, signal) {
3209
+ await bus.send(peerId, PW.encodeSignal(signal));
3210
+ },
3211
+ onSignal(cb) {
3212
+ if (off)
3213
+ off();
3214
+ off = bus.on("call", cb);
3215
+ },
3216
+ stop() {
3217
+ if (off) {
3218
+ off();
3219
+ off = null;
3220
+ }
3221
+ }
3222
+ };
3137
3223
  }
3138
- Object.assign(window, { DkIdenticon, DkBot, DkAvatar, DkImgAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
3224
+ Object.assign(window, { dkRtcSignalBus, makeDaemonSignaling });
3139
3225
 
3140
3226
  // src/call.jsx
3141
3227
  function useCallController(selfId, onCallLog) {
@@ -4316,6 +4402,7 @@ var STR = {
4316
4402
  en: {
4317
4403
  chat: "Chat",
4318
4404
  network: "Network",
4405
+ apps: "Apps",
4319
4406
  profile: "Profile",
4320
4407
  recommended: "Discover",
4321
4408
  registered: "Names",
@@ -4447,6 +4534,8 @@ var STR = {
4447
4534
  updRestarting: "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)",
4448
4535
  updNpx: "Running via npx \u2014 restarting picks up the latest version automatically.",
4449
4536
  updDev: "Running from a source checkout \u2014 update with:",
4537
+ updPeerDaemon: "The peer version belongs to the running agentnet daemon, not to this app. Update it with:",
4538
+ updUnknown: "Update now runs the global install. If that is not how this was installed, run:",
4450
4539
  updLanNote: "agentnet (lan): one click reinstalls it and asks the running daemon to restart itself. Manual equivalent:",
4451
4540
  updCheck: "check for updates",
4452
4541
  updCheckLabel: "updates",
@@ -4493,6 +4582,7 @@ var STR = {
4493
4582
  zh: {
4494
4583
  chat: "\u804A\u5929",
4495
4584
  network: "\u7F51\u7EDC",
4585
+ apps: "\u5E94\u7528",
4496
4586
  profile: "\u6211\u7684",
4497
4587
  recommended: "\u63A8\u8350",
4498
4588
  registered: "\u540D\u5F55",
@@ -4624,6 +4714,8 @@ var STR = {
4624
4714
  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)",
4625
4715
  updNpx: "\u4F60\u662F\u7528 npx \u8FD0\u884C\u7684\u2014\u2014\u91CD\u542F\u5373\u81EA\u52A8\u83B7\u53D6\u6700\u65B0\u7248\u3002",
4626
4716
  updDev: "\u5F53\u524D\u4ECE\u6E90\u7801\u76EE\u5F55\u8FD0\u884C,\u8BF7\u624B\u52A8\u66F4\u65B0:",
4717
+ updPeerDaemon: "peer \u7684\u7248\u672C\u5C5E\u4E8E\u6B63\u5728\u8FD0\u884C\u7684 agentnet daemon,\u4E0D\u5C5E\u4E8E\u672C\u5E94\u7528\u3002\u8BF7\u7528\u8FD9\u6761\u547D\u4EE4\u66F4\u65B0:",
4718
+ 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:",
4627
4719
  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:",
4628
4720
  updCheck: "\u68C0\u67E5\u66F4\u65B0",
4629
4721
  updCheckLabel: "\u66F4\u65B0",
@@ -4689,8 +4781,10 @@ function DkUpdateModal({ T, info, onClose }) {
4689
4781
  const [phase, setPhase] = React.useState("idle");
4690
4782
  const [err, setErr] = React.useState(null);
4691
4783
  const behind = (info.updates || []).filter((u) => u.behind);
4692
- const appBehind = behind.some((u) => u.pkg !== "@decentnetwork/lan");
4784
+ const peerIsDaemons = info.peerOwner === "daemon";
4785
+ const appBehind = behind.some((u) => u.pkg !== "@decentnetwork/lan" && !(peerIsDaemons && u.pkg === "@decentnetwork/peer"));
4693
4786
  const lanBehind = behind.find((u) => u.pkg === "@decentnetwork/lan");
4787
+ const daemonPeerBehind = peerIsDaemons && behind.some((u) => u.pkg === "@decentnetwork/peer");
4694
4788
  const short = (pkg) => pkg.replace("@decentnetwork/", "");
4695
4789
  const doUpdate = () => {
4696
4790
  setPhase("running");
@@ -4746,10 +4840,10 @@ function DkUpdateModal({ T, info, onClose }) {
4746
4840
  background: tone ? "var(--accent)" : "transparent",
4747
4841
  color: tone ? "#fff" : "var(--text)"
4748
4842
  } }, label);
4749
- 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" && (() => {
4843
+ 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."), daemonPeerBehind && phase === "idle" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updPeerDaemon || "The peer version belongs to the running agentnet daemon, not to this app. Update it with:", /* @__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") }))), 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" && (() => {
4750
4844
  const m = /`([^`]+)`/.exec(err || "");
4751
4845
  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."));
4752
- })(), /* @__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))));
4846
+ })(), /* @__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))));
4753
4847
  }
4754
4848
  function DkApp() {
4755
4849
  const [t, setTweak] = useTweaks({ ...DK_DEFAULTS, lang: dkDetectLang() });
@@ -4999,9 +5093,10 @@ function DkApp() {
4999
5093
  { id: "recommended", icon: "sparkles", label: T.recommended },
5000
5094
  { id: "registered", icon: "at", label: T.registered },
5001
5095
  { id: "network", icon: "network", label: T.network },
5096
+ { id: "apps", icon: "grid", label: T.apps },
5002
5097
  { id: "profile", icon: "userRound", label: T.profile }
5003
5098
  ];
5004
- 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(
5099
+ 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(
5005
5100
  DkLockedOut,
5006
5101
  {
5007
5102
  compact: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle-ui",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "The Beagle chat UI, minus the machine it runs on — shared by the desktop install and the browser build.",
5
5
  "license": "MIT",
6
6
  "type": "module",