@decentnetwork/beagle-ui 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/beagle-ui.js +507 -417
  2. package/package.json +1 -1
package/dist/beagle-ui.js CHANGED
@@ -1,325 +1,4 @@
1
- export const BEAGLE_UI_VERSION = "0.1.2";
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.0";
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 }) {
@@ -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",
@@ -4493,6 +4580,7 @@ var STR = {
4493
4580
  zh: {
4494
4581
  chat: "\u804A\u5929",
4495
4582
  network: "\u7F51\u7EDC",
4583
+ apps: "\u5E94\u7528",
4496
4584
  profile: "\u6211\u7684",
4497
4585
  recommended: "\u63A8\u8350",
4498
4586
  registered: "\u540D\u5F55",
@@ -4999,11 +5087,13 @@ function DkApp() {
4999
5087
  { id: "recommended", icon: "sparkles", label: T.recommended },
5000
5088
  { id: "registered", icon: "at", label: T.registered },
5001
5089
  { id: "network", icon: "network", label: T.network },
5090
+ { id: "apps", icon: "grid", label: T.apps },
5002
5091
  { id: "profile", icon: "userRound", label: T.profile }
5003
5092
  ];
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(
5093
+ 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
5094
  DkLockedOut,
5006
5095
  {
5096
+ compact: true,
5007
5097
  lang: t.lang,
5008
5098
  onTakeover: () => window.BeagleWeb.takeover().then((st) => {
5009
5099
  data.refresh();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle-ui",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
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",