@decentnetwork/beagle 0.1.112 → 0.1.114

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/desktop/app.js +420 -55
  2. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.112";
1
+ window.__DK_UI_VERSION="0.1.114";
2
2
  "use strict";
3
3
  (() => {
4
4
  // packages/beagle-ui/dist/beagle-ui.js
@@ -68,8 +68,8 @@ window.__DK_UI_VERSION="0.1.112";
68
68
  speaker: '<path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M16 9a3 3 0 0 1 0 6"/><path d="M19 7a6 6 0 0 1 0 10"/>',
69
69
  micFill: '<rect x="9" y="2.5" width="6" height="11.5" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0"/><path d="M12 17.5v3.5"/>'
70
70
  };
71
- function Icon({ name, size = 24, stroke = 1.75, color = "currentColor", fill = "none", style }) {
72
- const d = ICON_PATHS[name];
71
+ function Icon({ name, size = 24, stroke = 1.75, color = "currentColor", fill = "none", style, svgPath }) {
72
+ const d = svgPath || ICON_PATHS[name];
73
73
  if (!d)
74
74
  return null;
75
75
  return /* @__PURE__ */ React.createElement(
@@ -111,7 +111,12 @@ window.__DK_UI_VERSION="0.1.112";
111
111
  zap: '<path d="M13 2 4 13h7l-1 9 9-11h-7l1-9Z"/>',
112
112
  clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/>',
113
113
  bellOff: '<path d="M8.7 3.4A6 6 0 0 1 18 8.5c0 3 .5 5 1.1 6.3M6.1 6.1A6 6 0 0 0 6 8.5c0 6.5-2.5 8-2.5 8H16"/><path d="M13.7 20.5a2 2 0 0 1-3.4 0"/><path d="m3 3 18 18"/>',
114
- plug: '<path d="M9 2v6M15 2v6"/><path d="M7 8h10v3a5 5 0 0 1-10 0V8Z"/><path d="M12 16v6"/>'
114
+ plug: '<path d="M9 2v6M15 2v6"/><path d="M7 8h10v3a5 5 0 0 1-10 0V8Z"/><path d="M12 16v6"/>',
115
+ // A form with a filled-in field: the glyph for "Fill with Beagle" and any
116
+ // other host app that puts your identity into someone else's page.
117
+ // A pane pinned to the right edge — "keep this beside the page".
118
+ dock: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M14 4v16"/><path d="M17 9.5h1.5M17 13h1.5"/>',
119
+ form: '<rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h5"/><path d="M8 16h3"/><path d="m14.5 17.5 1.6 1.6 3.4-3.6"/>'
115
120
  });
116
121
  var H = {};
117
122
  var hostNeedsWelcome = false;
@@ -119,8 +124,20 @@ window.__DK_UI_VERSION="0.1.112";
119
124
  var hostHasKeyBackup = false;
120
125
  var hostEnsWrite = true;
121
126
  var hostBrandHref = "";
127
+ var hostTabs = null;
128
+ var hostHideApps = null;
129
+ var hostApps = null;
130
+ var hostProfileSections = null;
131
+ var hostHeaderActions = null;
132
+ var hostIsProtocolMessage = null;
122
133
  function setUiHost(host2) {
123
134
  H = host2 || {};
135
+ hostTabs = Array.isArray(H.hostTabs) ? H.hostTabs : null;
136
+ hostHideApps = Array.isArray(H.hideApps) ? H.hideApps : null;
137
+ hostApps = Array.isArray(H.hostApps) ? H.hostApps : null;
138
+ hostProfileSections = Array.isArray(H.hostProfileSections) ? H.hostProfileSections : null;
139
+ hostHeaderActions = Array.isArray(H.hostHeaderActions) ? H.hostHeaderActions : null;
140
+ hostIsProtocolMessage = typeof H.isProtocolMessage === "function" ? H.isProtocolMessage : null;
124
141
  hostNeedsWelcome = !!H.hostNeedsWelcome;
125
142
  hostPrefersRtcFile = !!H.hostPrefersRtcFile;
126
143
  hostHasKeyBackup = !!H.hostHasKeyBackup;
@@ -1476,6 +1493,98 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
1476
1493
  }, [append, lang, onReloadFriends]);
1477
1494
  return { thread, handle, removeFriends, adoptServicePeers };
1478
1495
  }
1496
+ function protocolTest(rule) {
1497
+ if (typeof rule !== "function")
1498
+ return () => false;
1499
+ return (msg) => {
1500
+ if (!msg)
1501
+ return false;
1502
+ try {
1503
+ const v = rule(msg);
1504
+ return typeof v === "string" ? v || true : !!v;
1505
+ } catch (e) {
1506
+ return false;
1507
+ }
1508
+ };
1509
+ }
1510
+ function stripOwnPrefix(text) {
1511
+ return String(text == null ? "" : text).replace(/^you:\s*/i, "");
1512
+ }
1513
+ function lastMessageOf(peer) {
1514
+ const raw = String(peer && peer.lastMsg || "");
1515
+ return {
1516
+ text: stripOwnPrefix(raw),
1517
+ dir: peer && peer.lastDir || (/^you:\s*/i.test(raw) ? "out" : "in"),
1518
+ ts: peer && peer.lastTs || 0
1519
+ };
1520
+ }
1521
+ function lastIsProtocol(peer, isProto) {
1522
+ const last = lastMessageOf(peer);
1523
+ if (!last.text)
1524
+ return false;
1525
+ return isProto(last);
1526
+ }
1527
+ function rosterRow(peer, isProto) {
1528
+ const hidden = lastIsProtocol(peer, isProto);
1529
+ return {
1530
+ preview: hidden ? "" : peer && peer.lastMsg || "",
1531
+ lastTime: hidden ? "" : peer && peer.lastTime || "",
1532
+ unread: hidden ? 0 : Number(peer && peer.unread || 0),
1533
+ activityTs: hidden ? 0 : Number(peer && peer.lastTs || 0),
1534
+ hidden
1535
+ };
1536
+ }
1537
+ function rosterUnreadTotal(peers, isProto) {
1538
+ return (peers || []).reduce((n, p) => n + rosterRow(p, isProto).unread, 0);
1539
+ }
1540
+ function collapseProtocol(thread, isProto, lang) {
1541
+ const out = [];
1542
+ let run = null;
1543
+ const flush = () => {
1544
+ if (!run)
1545
+ return;
1546
+ out.push({
1547
+ protocol: true,
1548
+ id: `proto-${run.first.id || run.startIndex}`,
1549
+ count: run.items.length,
1550
+ label: run.label,
1551
+ dir: run.first.from === "me" ? "out" : "in",
1552
+ text: run.items.map((m) => m.text).join("\n"),
1553
+ line: protocolLine(run, lang)
1554
+ });
1555
+ run = null;
1556
+ };
1557
+ (thread || []).forEach((m, i) => {
1558
+ const hit = m && !m.day ? isProto({ text: m.text, dir: m && m.from === "me" ? "out" : "in", ts: m && m.ts }) : false;
1559
+ if (!hit || m && m.file) {
1560
+ flush();
1561
+ out.push(m);
1562
+ return;
1563
+ }
1564
+ if (!run)
1565
+ run = { first: m, startIndex: i, items: [], label: typeof hit === "string" ? hit : "" };
1566
+ if (!run.label && typeof hit === "string")
1567
+ run.label = hit;
1568
+ run.items.push(m);
1569
+ });
1570
+ flush();
1571
+ return out;
1572
+ }
1573
+ function protocolLine(run, lang) {
1574
+ const arrow = run.first.from === "me" ? "\u2192" : "\u2190";
1575
+ const n = run.items.length;
1576
+ if (run.label) {
1577
+ return `\u2699 ${arrow} ${run.label}${n > 1 ? ` \xB7 ${n}` : ""}`;
1578
+ }
1579
+ if (n > 1) {
1580
+ return lang === "zh" ? `\u2699 ${arrow} ${n} \u6761\u534F\u8BAE\u6D88\u606F` : `\u2699 ${arrow} ${n} protocol messages`;
1581
+ }
1582
+ return `\u2699 ${arrow} ${protocolSummary(run.first.text)}`;
1583
+ }
1584
+ function protocolSummary(text, max = 72) {
1585
+ const s = String(text == null ? "" : text).replace(/\s+/g, " ").trim();
1586
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1587
+ }
1479
1588
  var DK_CMD_HIST_KEY = "beagle.assistant.history";
1480
1589
  var DK_CMD_HIST_MAX = 50;
1481
1590
  function RequestsBlock({ T, requests, onAct }) {
@@ -1536,8 +1645,9 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
1536
1645
  paddingLeft: 8
1537
1646
  } }, r.descr), failed[r.id] && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11, color: "var(--danger)" } }, failed[r.id]), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { tone: "ok", icon: "check", size: "sm", onClick: () => act(r, "accept"), style: { flex: 1 } }, T.accept), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "x", size: "sm", onClick: () => act(r, "reject"), style: { flex: 1 } }, T.reject))))));
1538
1647
  }
1539
- function PeerRow({ peer, T, active, onClick, muted }) {
1648
+ function PeerRow({ peer, T, active, onClick, muted, isProtocol }) {
1540
1649
  const name = peer.alias || peer.userId;
1650
+ const row = rosterRow(peer, isProtocol || (() => false));
1541
1651
  return /* @__PURE__ */ React.createElement("button", { onClick, style: {
1542
1652
  display: "flex",
1543
1653
  alignItems: "center",
@@ -1566,12 +1676,13 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
1566
1676
  textOverflow: "ellipsis",
1567
1677
  flex: 1,
1568
1678
  minWidth: 0
1569
- } }, dkRowPreview(peer.lastMsg)))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4, flexShrink: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, peer.lastTime), peer.unread ? /* @__PURE__ */ React.createElement(Unread, { n: peer.unread }) : peer.pending ? /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--warn, #d29922)", title: "friend request pending" }) : /* @__PURE__ */ React.createElement(StatusDot, { online: peer.online })));
1679
+ } }, dkRowPreview(row.preview)))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4, flexShrink: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, row.lastTime), row.unread ? /* @__PURE__ */ React.createElement(Unread, { n: row.unread }) : peer.pending ? /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--warn, #d29922)", title: "friend request pending" }) : /* @__PURE__ */ React.createElement(StatusDot, { online: peer.online })));
1570
1680
  }
1571
- function dkActivityTs(p) {
1572
- return Math.max(p.lastTs || 0, p.acceptedAt || 0, p.requestedAt || 0);
1681
+ function dkActivityTs(p, isProtocol) {
1682
+ const row = rosterRow(p, isProtocol || (() => false));
1683
+ return Math.max(row.activityTs, p.acceptedAt || 0, p.requestedAt || 0);
1573
1684
  }
1574
- function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide }) {
1685
+ function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide, isProtocol }) {
1575
1686
  const [q, setQ] = React.useState("");
1576
1687
  const [addr, setAddr] = React.useState("");
1577
1688
  const [addState, setAddState] = React.useState(null);
@@ -1616,7 +1727,7 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
1616
1727
  }).slice().sort((a, b) => {
1617
1728
  if (!!a.pinned !== !!b.pinned)
1618
1729
  return a.pinned ? -1 : 1;
1619
- const ta = dkActivityTs(a), tb = dkActivityTs(b);
1730
+ const ta = dkActivityTs(a, isProtocol), tb = dkActivityTs(b, isProtocol);
1620
1731
  if (ta !== tb)
1621
1732
  return tb - ta;
1622
1733
  return String(a.alias || a.userId || "").localeCompare(String(b.alias || b.userId || ""));
@@ -1655,7 +1766,7 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
1655
1766
  fontSize: 11.5,
1656
1767
  lineHeight: 1.35,
1657
1768
  color: addState.kind === "err" ? "var(--bad, #e5484d)" : addState.kind === "ok" ? "var(--good, #46a758)" : "var(--faint)"
1658
- } }, addState.msg), /* @__PURE__ */ React.createElement("div", { style: { position: "relative", display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "search", size: 15, color: "var(--faint)", stroke: 2, style: { position: "absolute", left: 9 } }), /* @__PURE__ */ React.createElement("input", { value: q, onChange: (e) => setQ(e.target.value), placeholder: T.search, style: { ...inputStyle, paddingLeft: 30 } }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", padding: "10px 8px 16px" } }, /* @__PURE__ */ React.createElement(RequestsBlock, { T, requests, onAct }), /* @__PURE__ */ React.createElement(Section, { label: T.peers, count: `${online}/${peers.length}`, style: { margin: "6px 4px 8px" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((p) => /* @__PURE__ */ React.createElement(PeerRow, { key: p.id, peer: p, T, active: p.id === activeId, onClick: () => onSelect(p.id), muted: !!muted && muted.includes(p.id) })), !filtered.length && /* @__PURE__ */ React.createElement("div", { style: { padding: 16, textAlign: "center", fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "no matches"))));
1769
+ } }, addState.msg), /* @__PURE__ */ React.createElement("div", { style: { position: "relative", display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "search", size: 15, color: "var(--faint)", stroke: 2, style: { position: "absolute", left: 9 } }), /* @__PURE__ */ React.createElement("input", { value: q, onChange: (e) => setQ(e.target.value), placeholder: T.search, style: { ...inputStyle, paddingLeft: 30 } }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", padding: "10px 8px 16px" } }, /* @__PURE__ */ React.createElement(RequestsBlock, { T, requests, onAct }), /* @__PURE__ */ React.createElement(Section, { label: T.peers, count: `${online}/${peers.length}`, style: { margin: "6px 4px 8px" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((p) => /* @__PURE__ */ React.createElement(PeerRow, { key: p.id, peer: p, T, active: p.id === activeId, onClick: () => onSelect(p.id), muted: !!muted && muted.includes(p.id), isProtocol })), !filtered.length && /* @__PURE__ */ React.createElement("div", { style: { padding: 16, textAlign: "center", fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "no matches"))));
1659
1770
  }
1660
1771
  var inputStyle = {
1661
1772
  flex: 1,
@@ -2655,7 +2766,7 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
2655
2766
  ) : mine && m.via === "offline" && m.status !== "queued" && m.status !== "sending" ? /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 2.2, color: "#f59e0b", title: T.viaOutOffline || "posted to the offline relay \u2014 they get it when they come online" }) : mine && m.status === "sent" && m.confirmed === false ? /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 2.2, color: "var(--faint)", title: T.sentUnconfirmed || "sent \u2014 not confirmed by the peer" }) : mine && m.status && /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 12, stroke: 2.2, color: m.status === "read" ? "var(--accent)" : "var(--faint)" })))
2656
2767
  );
2657
2768
  }
2658
- function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted, onToggleMute, phone, onBack }) {
2769
+ function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, isProtocol, muted, onToggleMute, phone, onBack }) {
2659
2770
  const scrollRef = React.useRef(null);
2660
2771
  const prependRef = React.useRef(null);
2661
2772
  const [earlierBusy, setEarlierBusy] = React.useState(false);
@@ -3018,7 +3129,7 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
3018
3129
  // The internal id is not an address anyone can use and not a name
3019
3130
  // anyone chose; saying what this row IS is more use than showing it.
3020
3131
  /* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, T && T.assistantSub || "Local \xB7 APP")
3021
- ) : phone ? /* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, display: "flex" } }, /* @__PURE__ */ React.createElement(RouteTag, { peer })) : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
3132
+ ) : phone ? /* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, display: "flex" } }, /* @__PURE__ */ React.createElement(RouteTag, { peer })) : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), onOpenNet && /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), onOpenNet && /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
3022
3133
  Mono,
3023
3134
  {
3024
3135
  size: 11.5,
@@ -3053,7 +3164,7 @@ ${peer.address}`
3053
3164
  } }), phone && onSendRtcFile && !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "file", label: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => {
3054
3165
  setMenu(false);
3055
3166
  rtcFileRef.current && rtcFileRef.current.click();
3056
- } }), phone && !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "network", label: T && T.openNet || "Network view", onClick: () => {
3167
+ } }), phone && !peer.app && onOpenNet && /* @__PURE__ */ React.createElement(MenuItem, { icon: "network", label: T && T.openNet || "Network view", onClick: () => {
3057
3168
  setMenu(false);
3058
3169
  onOpenNet(peer);
3059
3170
  } }), !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
@@ -3077,7 +3188,15 @@ ${peer.address}`
3077
3188
  fontSize: 12.5,
3078
3189
  color: "var(--text)"
3079
3190
  } }, /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 13, stroke: 2.2, color: "#d29922" }), /* @__PURE__ */ React.createElement("span", null, T.pendingFriend || "Waiting for them to accept your friend request \u2014 messages will queue and deliver once they do.")),
3080
- /* @__PURE__ */ React.createElement("div", { ref: scrollRef, onScroll: updateFollowScroll, style: { flex: 1, overflow: "auto", padding: phone ? "12px 10px" : "18px 22px", WebkitOverflowScrolling: "touch" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1200, margin: "0 auto" } }, hasEarlier && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "center", margin: "4px 0 10px" } }, /* @__PURE__ */ React.createElement(Btn, { size: "sm", onClick: loadEarlier, disabled: earlierBusy }, earlierBusy ? T.loadingEarlier || "Loading\u2026" : T.loadEarlier || "Load earlier messages")), thread.filter((m) => m.day || !hidden.has(m.id)).map((m, i) => m.day ? /* @__PURE__ */ React.createElement("div", { key: i, style: { display: "flex", justifyContent: "center", margin: "14px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 600, color: "var(--faint)", background: "var(--chip)", padding: "3px 10px", borderRadius: 999 } }, m.day)) : /* @__PURE__ */ React.createElement(
3191
+ /* @__PURE__ */ React.createElement("div", { ref: scrollRef, onScroll: updateFollowScroll, style: { flex: 1, overflow: "auto", padding: phone ? "12px 10px" : "18px 22px", WebkitOverflowScrolling: "touch" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1200, margin: "0 auto" } }, hasEarlier && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "center", margin: "4px 0 10px" } }, /* @__PURE__ */ React.createElement(Btn, { size: "sm", onClick: loadEarlier, disabled: earlierBusy }, earlierBusy ? T.loadingEarlier || "Loading\u2026" : T.loadEarlier || "Load earlier messages")), collapseProtocol(thread, isProtocol || (() => false), lang).filter((m) => m.day || m.protocol || !hidden.has(m.id)).map((m, i) => m.day ? /* @__PURE__ */ React.createElement("div", { key: i, style: { display: "flex", justifyContent: "center", margin: "14px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 600, color: "var(--faint)", background: "var(--chip)", padding: "3px 10px", borderRadius: 999 } }, m.day)) : m.protocol ? /* @__PURE__ */ React.createElement("div", { key: m.id || i, style: { display: "flex", justifyContent: "center", margin: "4px 0" } }, /* @__PURE__ */ React.createElement("span", { title: m.text, style: {
3192
+ fontFamily: "var(--mono)",
3193
+ fontSize: 10.5,
3194
+ color: "var(--faint)",
3195
+ maxWidth: "86%",
3196
+ whiteSpace: "nowrap",
3197
+ overflow: "hidden",
3198
+ textOverflow: "ellipsis"
3199
+ } }, m.line)) : /* @__PURE__ */ React.createElement(
3081
3200
  Msg,
3082
3201
  {
3083
3202
  key: m.id || i,
@@ -3248,12 +3367,12 @@ ${peer.address}`
3248
3367
  function ChatEmpty({ T }) {
3249
3368
  return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, color: "var(--faint)", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "message", size: 40, stroke: 1.4, color: "var(--line)" }), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13 } }, T.pickPeer));
3250
3369
  }
3251
- function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
3370
+ function ChatTab({ T, lang, peers, requests, activeId, thread, isProtocol, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
3252
3371
  const peer = peers.find((p) => p.id === activeId);
3253
3372
  if (phone) {
3254
- return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute, phone: true, onBack }) : /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide: true });
3373
+ return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, isProtocol, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute, phone: true, onBack }) : /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, isProtocol, wide: true });
3255
3374
  }
3256
- return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
3375
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, isProtocol }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, isProtocol, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
3257
3376
  }
3258
3377
  Object.assign(window, { ChatTab });
3259
3378
  function CopyBtn({ value, copiedText = "Copied", copyFailedText = "Copy failed", copyTitle = "Copy" }) {
@@ -4026,7 +4145,7 @@ ${peer.address}`
4026
4145
  return /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) });
4027
4146
  return /* @__PURE__ */ React.createElement("div", { onClick: onClose, style: { position: "fixed", inset: 0, zIndex: 90, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 16 } }, /* @__PURE__ */ React.createElement("div", { onClick: (e) => e.stopPropagation(), style: { width: 360, maxWidth: "100%", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: 16, display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 4 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 40, radius: 10, dot: false }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 700, color: "var(--text)" } }, zh ? "\u66F4\u6362\u5934\u50CF" : "Change avatar"), /* @__PURE__ */ React.createElement(Btn, { icon: "x", size: "sm", onClick: onClose })), item("sparkles", zh ? "\u9009\u4E00\u4E2A CryptoPunk" : "Choose a CryptoPunk", () => setPicking(true)), canUpload && item("image", zh ? "\u4E0A\u4F20\u7167\u7247" : "Upload a photo", () => fileRef.current && fileRef.current.click()), has && item("trash", zh ? "\u79FB\u9664\u5934\u50CF" : "Remove avatar", () => save(canUpload ? { punkId: null, avatarDataUrl: null } : { punkId: null }), true), /* @__PURE__ */ React.createElement("input", { ref: fileRef, type: "file", accept: "image/*", onChange: onFile, style: { display: "none" } }), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)", lineHeight: 1.5, marginTop: 2 } }, zh ? "\u4E0D\u9700\u8981\u6CE8\u518C beagles.eth \u540D\u5B57\u3002\u597D\u53CB\u4F1A\u5728\u4E0B\u6B21\u6536\u5230\u4F60\u7684\u8D44\u6599\u65F6\u770B\u5230\u65B0\u5934\u50CF\u3002" : "No beagles.eth name needed. Friends see it the next time your profile reaches them."), err && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--danger)" } }, err)));
4028
4147
  }
4029
- function ProfileTab({ T, me, onEdit, t, setTweak, onSetAvatar }) {
4148
+ function ProfileTab({ T, me, onEdit, t, setTweak, onSetAvatar, sections, sectionProps }) {
4030
4149
  var _a;
4031
4150
  const [avatarSheet, setAvatarSheet] = React.useState(false);
4032
4151
  const [qr, setQr] = React.useState(null);
@@ -4058,12 +4177,33 @@ ${peer.address}`
4058
4177
  },
4059
4178
  style: { width: 16, height: 16, accentColor: "var(--accent)" }
4060
4179
  }
4061
- ), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, T && T.autoAcceptLabel || "Auto-accept friend requests"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, T && T.autoAcceptSub || "Off: requests wait in the sidebar for your approval.")))), hostHasKeyBackup && /* @__PURE__ */ React.createElement(DkKeyBackup, { t: t || {} }), t && setTweak && /* @__PURE__ */ React.createElement(DkAlertSettings, { t, setTweak }), t && setTweak && /* @__PURE__ */ React.createElement(DkDisplaySettings, { t, setTweak }), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(DkVersionRow, { T, me }), /* @__PURE__ */ React.createElement(DkUpdateCheck, { T })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))), qr && /* @__PURE__ */ React.createElement(DkQrModal, { T, value: qr.value, label: qr.label, onClose: () => setQr(null) }), avatarSheet && /* @__PURE__ */ React.createElement(DkAvatarSheet, { T, me, zh: t && t.lang === "zh", onSet: onSetAvatar, onClose: () => setAvatarSheet(false) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
4180
+ ), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, T && T.autoAcceptLabel || "Auto-accept friend requests"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, T && T.autoAcceptSub || "Off: requests wait in the sidebar for your approval.")))), hostHasKeyBackup && /* @__PURE__ */ React.createElement(DkKeyBackup, { t: t || {} }), t && setTweak && /* @__PURE__ */ React.createElement(DkAlertSettings, { t, setTweak }), t && setTweak && /* @__PURE__ */ React.createElement(DkDisplaySettings, { t, setTweak }), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(DkVersionRow, { T, me }), /* @__PURE__ */ React.createElement(DkUpdateCheck, { T })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))), Array.isArray(sections) && sections.length > 0 && /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "0 20px 40px", display: "flex", flexDirection: "column", gap: 16 } }, sections.map((sec) => sec && typeof sec.render === "function" ? /* @__PURE__ */ React.createElement(DkHostSection, { key: sec.id, sec, props: sectionProps, lang: t && t.lang }) : null)), qr && /* @__PURE__ */ React.createElement(DkQrModal, { T, value: qr.value, label: qr.label, onClose: () => setQr(null) }), avatarSheet && /* @__PURE__ */ React.createElement(DkAvatarSheet, { T, me, zh: t && t.lang === "zh", onSet: onSetAvatar, onClose: () => setAvatarSheet(false) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
4062
4181
  if (onEdit)
4063
4182
  onEdit(name, description);
4064
4183
  setEditing(false);
4065
4184
  } }));
4066
4185
  }
4186
+ var DkHostSection = class extends React.Component {
4187
+ constructor(props) {
4188
+ super(props);
4189
+ this.state = { err: null };
4190
+ }
4191
+ static getDerivedStateFromError(err) {
4192
+ return { err };
4193
+ }
4194
+ render() {
4195
+ var _a;
4196
+ const { sec, props, lang } = this.props;
4197
+ if (this.state.err) {
4198
+ return /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, lang === "zh" ? "\u8FD9\u4E2A\u9644\u52A0\u677F\u5757\u51FA\u9519\u4E86\u3002" : "This host section failed to render.");
4199
+ }
4200
+ try {
4201
+ return (_a = sec.render(props)) != null ? _a : null;
4202
+ } catch (e) {
4203
+ return null;
4204
+ }
4205
+ }
4206
+ };
4067
4207
  Object.assign(window, { DkShareSheet, ProfileTab, DkPunkAvatar, DkEnsAvatar, DkPublicProfile });
4068
4208
  var DK_PHONE_MQ = "(max-width: 700px)";
4069
4209
  function useIsPhone() {
@@ -4277,6 +4417,82 @@ ${peer.address}`
4277
4417
  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);
4278
4418
  }
4279
4419
  Object.assign(window, { useIsPhone, DkPhoneStyles, DkIdenticon, DkBot, DkAvatar, DkImgAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
4420
+ var BUILTIN_TABS = ["chat", "people", "network", "apps", "profile"];
4421
+ var warned = /* @__PURE__ */ new Set();
4422
+ function warnOnce(what) {
4423
+ if (warned.has(what))
4424
+ return;
4425
+ warned.add(what);
4426
+ try {
4427
+ console.warn(`[beagle-ui] ignoring unknown host tab or app: ${what}`);
4428
+ } catch (e) {
4429
+ }
4430
+ }
4431
+ function pickText(value, lang, fallback = "") {
4432
+ if (typeof value === "string")
4433
+ return value;
4434
+ if (value && typeof value === "object")
4435
+ return value[lang] || value.en || value.zh || fallback;
4436
+ return fallback;
4437
+ }
4438
+ function resolveTabs(hostTabs2) {
4439
+ if (!Array.isArray(hostTabs2) || !hostTabs2.length) {
4440
+ return BUILTIN_TABS.map((id) => ({ id, kind: "builtin" }));
4441
+ }
4442
+ const out = [];
4443
+ const seen = /* @__PURE__ */ new Set();
4444
+ for (const entry of hostTabs2) {
4445
+ if (typeof entry === "string") {
4446
+ if (!BUILTIN_TABS.includes(entry)) {
4447
+ warnOnce(entry);
4448
+ continue;
4449
+ }
4450
+ if (seen.has(entry))
4451
+ continue;
4452
+ seen.add(entry);
4453
+ out.push({ id: entry, kind: "builtin" });
4454
+ continue;
4455
+ }
4456
+ if (entry && typeof entry === "object" && typeof entry.id === "string" && entry.id) {
4457
+ if (typeof entry.render !== "function") {
4458
+ warnOnce(`${entry.id} (no render function)`);
4459
+ continue;
4460
+ }
4461
+ if (BUILTIN_TABS.includes(entry.id)) {
4462
+ warnOnce(`${entry.id} (a built-in id cannot be redefined)`);
4463
+ continue;
4464
+ }
4465
+ if (seen.has(entry.id))
4466
+ continue;
4467
+ seen.add(entry.id);
4468
+ out.push({
4469
+ id: entry.id,
4470
+ kind: "host",
4471
+ label: entry.label,
4472
+ icon: entry.icon,
4473
+ render: entry.render,
4474
+ badge: typeof entry.badge === "function" ? entry.badge : void 0
4475
+ });
4476
+ continue;
4477
+ }
4478
+ warnOnce(String(entry && entry.id ? entry.id : entry));
4479
+ }
4480
+ return out.length ? out : BUILTIN_TABS.map((id) => ({ id, kind: "builtin" }));
4481
+ }
4482
+ function hasTab(tabs, id) {
4483
+ return (tabs != null ? tabs : []).some((t) => t.id === id);
4484
+ }
4485
+ function resolveApps(builtins, hideApps, hostApps2) {
4486
+ const hidden = new Set(Array.isArray(hideApps) ? hideApps : []);
4487
+ const host2 = (Array.isArray(hostApps2) ? hostApps2 : []).filter((a) => {
4488
+ if (a && typeof a.id === "string" && a.id && typeof a.open === "function")
4489
+ return true;
4490
+ warnOnce(`app ${a && a.id ? a.id : String(a)}`);
4491
+ return false;
4492
+ }).map((a) => ({ ...a, host: true }));
4493
+ const kept = (builtins != null ? builtins : []).filter((a) => !hidden.has(a.id));
4494
+ return host2.concat(kept);
4495
+ }
4280
4496
  var APPS = [
4281
4497
  {
4282
4498
  id: "meet",
@@ -4316,11 +4532,24 @@ ${peer.address}`
4316
4532
  const b64 = btoa(unescape(encodeURIComponent(json)));
4317
4533
  return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
4318
4534
  }
4319
- function AppsTab({ T, t }) {
4535
+ function AppsTab({ T, t, me, peers, phone, setTab }) {
4320
4536
  const zh = t && t.lang === "zh";
4537
+ const apps = React.useMemo(
4538
+ () => resolveApps(APPS, hostHideApps, hostApps),
4539
+ []
4540
+ );
4321
4541
  const [busy, setBusy] = React.useState(null);
4322
4542
  const [err, setErr] = React.useState(null);
4323
4543
  const open = async (app) => {
4544
+ if (app.host) {
4545
+ setErr(null);
4546
+ try {
4547
+ app.open({ T, t, me, peers, phone, setTab });
4548
+ } catch (e) {
4549
+ setErr(String(e && e.message || e));
4550
+ }
4551
+ return;
4552
+ }
4324
4553
  setBusy(app.id);
4325
4554
  setErr(null);
4326
4555
  const win = window.open("", "_blank");
@@ -4342,7 +4571,7 @@ ${peer.address}`
4342
4571
  setBusy(null);
4343
4572
  }
4344
4573
  };
4345
- 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: {
4574
+ 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: {
4346
4575
  display: "flex",
4347
4576
  alignItems: "center",
4348
4577
  gap: 14,
@@ -4359,7 +4588,7 @@ ${peer.address}`
4359
4588
  display: "flex",
4360
4589
  alignItems: "center",
4361
4590
  justifyContent: "center"
4362
- } }, /* @__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")))));
4591
+ } }, /* @__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)" } }, pickText(app.name, t && t.lang, app.id)), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 2 } }, pickText(app.blurb, t && t.lang, "")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--dim)", marginTop: 3 } }, app.host ? zh ? "\u672C\u673A\u5E94\u7528" : "runs in this app" : String(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")))));
4363
4592
  }
4364
4593
  var DK_FALLBACK_ICE_SERVERS = [
4365
4594
  { urls: "stun:stun.l.google.com:19302" },
@@ -5767,7 +5996,7 @@ ${peer.address}`
5767
5996
  function dkLooking() {
5768
5997
  return document.visibilityState === "visible" && document.hasFocus();
5769
5998
  }
5770
- function useIncomingAlerts({ T, peers, requests, loaded, activeId, sound = true, flash = true, muted }) {
5999
+ function useIncomingAlerts({ T, peers, requests, loaded, activeId, sound = true, flash = true, muted, isProtocol }) {
5771
6000
  const prev = React.useRef(null);
5772
6001
  const mutedRef = React.useRef(/* @__PURE__ */ new Set());
5773
6002
  mutedRef.current = new Set(muted || []);
@@ -5797,7 +6026,8 @@ ${peer.address}`
5797
6026
  const byId = /* @__PURE__ */ new Map();
5798
6027
  for (const p of peers || []) {
5799
6028
  const dir = p.lastDir || (String(p.lastMsg || "").startsWith("you: ") ? "out" : "in");
5800
- byId.set(p.id, { ts: p.lastTs || 0, dir });
6029
+ const quiet = typeof isProtocol === "function" && lastIsProtocol(p, isProtocol);
6030
+ byId.set(p.id, { ts: p.lastTs || 0, dir, quiet });
5801
6031
  }
5802
6032
  const reqIds = new Set((requests || []).map((r) => r.id));
5803
6033
  const was = prev.current;
@@ -5808,6 +6038,8 @@ ${peer.address}`
5808
6038
  for (const [id, cur] of byId) {
5809
6039
  if (cur.dir !== "in" || !cur.ts)
5810
6040
  continue;
6041
+ if (cur.quiet)
6042
+ continue;
5811
6043
  const old = was.byId.get(id);
5812
6044
  if (old ? cur.ts <= old.ts : Date.now() - cur.ts > 3e4)
5813
6045
  continue;
@@ -6348,7 +6580,7 @@ ${peer.address}`
6348
6580
  meetingDesc: "\u8DE8\u670D\u52A1\u5668\u521B\u5EFA\u97F3\u89C6\u9891\u623F\u95F4\uFF0C\u901A\u8FC7\u865A\u62DF IP \u9080\u8BF7\u597D\u53CB\u3002\u7FA4\u7EC4\u901A\u8BDD\u3001\u5C4F\u5E55\u5171\u4EAB\u4E0E\u5171\u4EAB\u51FA\u53E3\u8DEF\u7531\u5C06\u5728\u4E0B\u4E2A\u7248\u672C\u4E0A\u7EBF\u3002"
6349
6581
  }
6350
6582
  };
6351
- function RailBtn({ icon, label, active, soon, onClick }) {
6583
+ function RailBtn({ icon, label, active, soon, onClick, svgPath }) {
6352
6584
  return /* @__PURE__ */ React.createElement("button", { onClick, title: label, style: {
6353
6585
  width: 52,
6354
6586
  height: 52,
@@ -6363,9 +6595,9 @@ ${peer.address}`
6363
6595
  alignItems: "center",
6364
6596
  justifyContent: "center",
6365
6597
  gap: 3
6366
- } }, /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 20, stroke: active ? 2.1 : 1.8, color: active ? "var(--accent)" : "var(--dim)" }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 9, fontWeight: 600, letterSpacing: 0.2, color: active ? "var(--accent)" : "var(--faint)" } }, label), soon && /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", top: 5, right: 6, width: 6, height: 6, borderRadius: 999, background: "var(--warn)" } }));
6598
+ } }, /* @__PURE__ */ React.createElement(Icon, { name: icon, svgPath, size: 20, stroke: active ? 2.1 : 1.8, color: active ? "var(--accent)" : "var(--dim)" }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 9, fontWeight: 600, letterSpacing: 0.2, color: active ? "var(--accent)" : "var(--faint)" } }, label), soon && /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", top: 5, right: 6, width: 6, height: 6, borderRadius: 999, background: "var(--warn)" } }));
6367
6599
  }
6368
- function TabBarBtn({ icon, label, active, badge, onClick }) {
6600
+ function TabBarBtn({ icon, label, active, badge, onClick, svgPath }) {
6369
6601
  return /* @__PURE__ */ React.createElement("button", { onClick, style: {
6370
6602
  flex: 1,
6371
6603
  minWidth: 0,
@@ -6380,9 +6612,36 @@ ${peer.address}`
6380
6612
  justifyContent: "center",
6381
6613
  gap: 3,
6382
6614
  fontFamily: "var(--mono)"
6383
- } }, /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 22, stroke: active ? 2.1 : 1.8, color: active ? "var(--accent)" : "var(--dim)" }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 10, fontWeight: 600, color: active ? "var(--accent)" : "var(--faint)", whiteSpace: "nowrap" } }, label), badge ? /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", top: 4, left: "50%", marginLeft: 6 } }, /* @__PURE__ */ React.createElement(Unread, { n: badge })) : null);
6615
+ } }, /* @__PURE__ */ React.createElement(Icon, { name: icon, svgPath, size: 22, stroke: active ? 2.1 : 1.8, color: active ? "var(--accent)" : "var(--dim)" }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 10, fontWeight: 600, color: active ? "var(--accent)" : "var(--faint)", whiteSpace: "nowrap" } }, label), badge ? /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", top: 4, left: "50%", marginLeft: 6 } }, /* @__PURE__ */ React.createElement(Unread, { n: badge })) : null);
6384
6616
  }
6385
6617
  var DK_PEOPLE_TABS = ["here", "recommended", "registered"];
6618
+ var HostTabPane = class extends React.Component {
6619
+ constructor(props) {
6620
+ super(props);
6621
+ this.state = { err: null };
6622
+ }
6623
+ static getDerivedStateFromError(err) {
6624
+ return { err };
6625
+ }
6626
+ componentDidCatch(err) {
6627
+ try {
6628
+ console.error("[beagle-ui] host tab failed", err);
6629
+ } catch (e) {
6630
+ }
6631
+ }
6632
+ render() {
6633
+ var _a;
6634
+ const { tab, props, lang } = this.props;
6635
+ if (this.state.err) {
6636
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", padding: 24, textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--faint)", maxWidth: 420 } }, lang === "zh" ? `"${pickText(tab.label, lang, tab.id)}" \u8FD9\u4E2A\u6807\u7B7E\u9875\u51FA\u9519\u4E86\u3002\u5176\u5B83\u6807\u7B7E\u9875\u4E0D\u53D7\u5F71\u54CD\u3002` : `The "${pickText(tab.label, lang, tab.id)}" tab failed to render. The rest of the app is unaffected.`));
6637
+ }
6638
+ try {
6639
+ return (_a = tab.render(props)) != null ? _a : null;
6640
+ } catch (e) {
6641
+ return null;
6642
+ }
6643
+ }
6644
+ };
6386
6645
  function Brand({ T, children, style }) {
6387
6646
  if (!hostBrandHref)
6388
6647
  return /* @__PURE__ */ React.createElement("span", { style }, children);
@@ -6499,7 +6758,17 @@ ${peer.address}`
6499
6758
  }
6500
6759
  function DkApp() {
6501
6760
  const [t, setTweak] = useTweaks({ ...DK_DEFAULTS, lang: dkDetectLang() });
6502
- const [tab, setTab] = React.useState(t.startTab || "chat");
6761
+ const tabs = React.useMemo(() => resolveTabs(hostTabs), []);
6762
+ const showPeople = hasTab(tabs, "people");
6763
+ const showNetwork = hasTab(tabs, "network");
6764
+ const showProfile = hasTab(tabs, "profile");
6765
+ const firstTab = tabs[0] ? tabs[0].id === "people" ? "here" : tabs[0].id : "chat";
6766
+ const [tab, setTab] = React.useState(() => {
6767
+ const want = t.startTab || "chat";
6768
+ if (want === "chat" && hasTab(tabs, "chat"))
6769
+ return "chat";
6770
+ return hasTab(tabs, want) || DK_PEOPLE_TABS.includes(want) && showPeople ? want : firstTab;
6771
+ });
6503
6772
  const [activeId, setActiveId] = React.useState(null);
6504
6773
  const data = useDaemonData();
6505
6774
  const me = data.me;
@@ -6534,11 +6803,13 @@ ${peer.address}`
6534
6803
  });
6535
6804
  }, []);
6536
6805
  React.useEffect(() => {
6806
+ if (!showNetwork)
6807
+ return void 0;
6537
6808
  refreshBackend();
6538
6809
  const ms = backend && backend.state === "releasing" ? 1500 : 8e3;
6539
6810
  const t2 = setInterval(refreshBackend, ms);
6540
6811
  return () => clearInterval(t2);
6541
- }, [refreshBackend, backend && backend.state]);
6812
+ }, [refreshBackend, backend && backend.state, showNetwork]);
6542
6813
  const armLan = React.useCallback(async () => {
6543
6814
  await dkPost("/api/lan-arm", {});
6544
6815
  refreshBackend();
@@ -6612,6 +6883,7 @@ ${peer.address}`
6612
6883
  const rowPad = t.density === "comfortable" ? "11px 12px" : "7px 10px";
6613
6884
  const mutedIds = Array.isArray(t.alertMuted) ? t.alertMuted : [];
6614
6885
  const onToggleMute = (id) => setTweak("alertMuted", mutedIds.includes(id) ? mutedIds.filter((x) => x !== id) : mutedIds.concat(id));
6886
+ const isProtocol = React.useMemo(() => protocolTest(hostIsProtocolMessage), []);
6615
6887
  useIncomingAlerts({
6616
6888
  T,
6617
6889
  peers,
@@ -6620,7 +6892,8 @@ ${peer.address}`
6620
6892
  activeId,
6621
6893
  sound: t.alertSound !== false,
6622
6894
  flash: t.alertFlash !== false,
6623
- muted: mutedIds
6895
+ muted: mutedIds,
6896
+ isProtocol
6624
6897
  });
6625
6898
  const assistant = useAssistant({
6626
6899
  lang: t.lang,
@@ -6654,6 +6927,54 @@ ${peer.address}`
6654
6927
  });
6655
6928
  const phone = useIsPhone();
6656
6929
  const [sharing, setSharing] = React.useState(false);
6930
+ const headerActions = (hostHeaderActions || []).filter((a) => a && a.id && typeof a.onClick === "function");
6931
+ const headerActionBtns = headerActions.map((a) => {
6932
+ let on = false;
6933
+ try {
6934
+ on = typeof a.active === "function" ? !!a.active() : false;
6935
+ } catch (e) {
6936
+ on = false;
6937
+ }
6938
+ const label = pickText(a.label, t.lang, a.id);
6939
+ return /* @__PURE__ */ React.createElement(
6940
+ "button",
6941
+ {
6942
+ key: a.id,
6943
+ title: label,
6944
+ "aria-label": label,
6945
+ "aria-pressed": on,
6946
+ onClick: () => {
6947
+ try {
6948
+ a.onClick({ T, t, me, activeId, phone });
6949
+ } catch (e) {
6950
+ }
6951
+ },
6952
+ style: {
6953
+ width: 32,
6954
+ height: 32,
6955
+ flexShrink: 0,
6956
+ borderRadius: 8,
6957
+ cursor: "pointer",
6958
+ padding: 0,
6959
+ border: "1px solid " + (on ? "var(--accent)" : "var(--line)"),
6960
+ background: on ? "color-mix(in oklab, var(--accent), transparent 86%)" : "transparent",
6961
+ display: "flex",
6962
+ alignItems: "center",
6963
+ justifyContent: "center"
6964
+ }
6965
+ },
6966
+ /* @__PURE__ */ React.createElement(
6967
+ Icon,
6968
+ {
6969
+ name: typeof a.icon === "string" ? a.icon : void 0,
6970
+ svgPath: a.icon && typeof a.icon === "object" ? a.icon.svgPath : void 0,
6971
+ size: 16,
6972
+ stroke: 2,
6973
+ color: on ? "var(--accent)" : "var(--dim)"
6974
+ }
6975
+ )
6976
+ );
6977
+ });
6657
6978
  const shareBtn = /* @__PURE__ */ React.createElement(
6658
6979
  "button",
6659
6980
  {
@@ -6807,36 +7128,68 @@ ${peer.address}`
6807
7128
  setTab("chat");
6808
7129
  openConversation(id);
6809
7130
  };
6810
- const onOpenNet = () => setTab("network");
6811
- const nav = [
6812
- { id: "chat", icon: "message", label: T.chat },
6813
- { id: "here", icon: "users", label: T.here },
6814
- { id: "recommended", icon: "sparkles", label: T.recommended },
6815
- { id: "registered", icon: "at", label: T.registered },
6816
- { id: "network", icon: "network", label: T.network },
6817
- { id: "apps", icon: "grid", label: T.apps }
6818
- ];
7131
+ const onOpenNet = showNetwork ? () => setTab("network") : null;
7132
+ const RAIL_FOR = {
7133
+ chat: [{ id: "chat", icon: "message", label: T.chat }],
7134
+ people: [
7135
+ { id: "here", icon: "users", label: T.here },
7136
+ { id: "recommended", icon: "sparkles", label: T.recommended },
7137
+ { id: "registered", icon: "at", label: T.registered }
7138
+ ],
7139
+ network: [{ id: "network", icon: "network", label: T.network }],
7140
+ apps: [{ id: "apps", icon: "grid", label: T.apps }],
7141
+ profile: []
7142
+ };
7143
+ const nav = tabs.flatMap((x) => x.kind === "builtin" ? RAIL_FOR[x.id] || [] : [{ id: x.id, icon: x.icon, label: pickText(x.label, t.lang, x.id) }]);
6819
7144
  const peopleTab = DK_PEOPLE_TABS.includes(tab);
6820
7145
  const lastPeople = React.useRef("here");
6821
7146
  if (peopleTab)
6822
7147
  lastPeople.current = tab;
6823
- const unreadTotal = (peers || []).reduce((n, p) => n + (Number(p.unread) || 0), 0) + (requests || []).length;
6824
- const phoneNav = [
6825
- { id: "chat", icon: "message", label: T.chat, active: tab === "chat", badge: unreadTotal, go: () => setTab("chat") },
6826
- { id: "people", icon: "users", label: T.people, active: peopleTab, go: () => setTab(lastPeople.current) },
6827
- { id: "network", icon: "network", label: T.network, active: tab === "network", go: () => setTab("network") },
6828
- { id: "apps", icon: "grid", label: T.apps, active: tab === "apps", go: () => setTab("apps") },
6829
- { id: "profile", icon: "userRound", label: T.profile, active: tab === "profile", go: () => setTab("profile") }
6830
- ];
7148
+ const unreadTotal = rosterUnreadTotal(peers, isProtocol) + (requests || []).length;
7149
+ const NAV_FOR = {
7150
+ chat: { icon: "message", label: T.chat, active: tab === "chat", badge: unreadTotal, go: () => setTab("chat") },
7151
+ people: { icon: "users", label: T.people, active: peopleTab, go: () => setTab(lastPeople.current) },
7152
+ network: { icon: "network", label: T.network, active: tab === "network", go: () => setTab("network") },
7153
+ apps: { icon: "grid", label: T.apps, active: tab === "apps", go: () => setTab("apps") },
7154
+ profile: { icon: "userRound", label: T.profile, active: tab === "profile", go: () => setTab("profile") }
7155
+ };
7156
+ const phoneNav = tabs.map((x) => x.kind === "builtin" ? { id: x.id, ...NAV_FOR[x.id] } : {
7157
+ id: x.id,
7158
+ icon: typeof x.icon === "string" ? x.icon : void 0,
7159
+ svgPath: x.icon && typeof x.icon === "object" ? x.icon.svgPath : void 0,
7160
+ label: pickText(x.label, t.lang, x.id),
7161
+ active: tab === x.id,
7162
+ // A host badge is called on every render, like the unread count beside
7163
+ // it; a throwing one must not take the tab bar down with it.
7164
+ badge: (() => {
7165
+ try {
7166
+ return x.badge ? x.badge() : 0;
7167
+ } catch (e) {
7168
+ return 0;
7169
+ }
7170
+ })(),
7171
+ go: () => setTab(x.id)
7172
+ });
6831
7173
  const inConversation = phone && tab === "chat" && !!activeId && (peers || []).some((p) => p.id === activeId);
6832
- return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", flexDirection: phone ? "column" : "row", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement(DkPhoneStyles, null), !phone && /* @__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(Brand, { T, style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8, flexShrink: 0 } }, /* @__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("button", { onClick: () => setTab("profile"), title: T.profile, style: {
7174
+ return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", flexDirection: phone ? "column" : "row", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement(DkPhoneStyles, null), !phone && /* @__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(Brand, { T, style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8, flexShrink: 0 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(
7175
+ RailBtn,
7176
+ {
7177
+ key: n.id,
7178
+ icon: typeof n.icon === "string" ? n.icon : void 0,
7179
+ svgPath: n.icon && typeof n.icon === "object" ? n.icon.svgPath : void 0,
7180
+ label: n.label,
7181
+ active: tab === n.id,
7182
+ soon: n.soon,
7183
+ onClick: () => setTab(n.id)
7184
+ }
7185
+ )), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), showProfile && /* @__PURE__ */ React.createElement("button", { onClick: () => setTab("profile"), title: T.profile, style: {
6833
7186
  padding: 4,
6834
7187
  borderRadius: 12,
6835
7188
  cursor: "pointer",
6836
7189
  display: "flex",
6837
7190
  border: "1px solid " + (tab === "profile" ? "var(--line)" : "transparent"),
6838
7191
  background: tab === "profile" ? "var(--panel-2)" : "transparent"
6839
- } }, /* @__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" } }, !inConversation && /* @__PURE__ */ React.createElement(DkBrowserNotice, { lang: t.lang }), !inConversation && /* @__PURE__ */ React.createElement("div", { style: { height: 46, boxSizing: "content-box", paddingTop: phone ? "env(safe-area-inset-top, 0px)" : 0, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: phone ? 8 : 12, paddingLeft: phone ? 12 : 16, paddingRight: phone ? 12 : 16 } }, /* @__PURE__ */ React.createElement(Brand, { T, style: { gap: 12, flexShrink: 0 } }, /* @__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)", whiteSpace: "nowrap" } }, "\xB7 ", (nav.find((n) => n.id === tab) || { label: T.profile }).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), phone ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), shareBtn, /* @__PURE__ */ React.createElement("button", { onClick: () => setTab("profile"), title: T.profile, style: { padding: 0, border: "none", background: "transparent", cursor: "pointer", display: "flex" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 28, radius: 7, dot: false }))) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__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)" } }), shareBtn, /* @__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)))), phone && peopleTab && /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, display: "flex", gap: 6, padding: "8px 12px", borderBottom: "1px solid var(--line)", background: "var(--panel)", overflowX: "auto" } }, nav.filter((n) => DK_PEOPLE_TABS.includes(n.id)).map((n) => /* @__PURE__ */ React.createElement("button", { key: n.id, onClick: () => setTab(n.id), style: {
7192
+ } }, /* @__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" } }, !inConversation && /* @__PURE__ */ React.createElement(DkBrowserNotice, { lang: t.lang }), !inConversation && /* @__PURE__ */ React.createElement("div", { style: { height: 46, boxSizing: "content-box", paddingTop: phone ? "env(safe-area-inset-top, 0px)" : 0, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: phone ? 8 : 12, paddingLeft: phone ? 12 : 16, paddingRight: phone ? 12 : 16 } }, /* @__PURE__ */ React.createElement(Brand, { T, style: { gap: 12, flexShrink: 0 } }, /* @__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)", whiteSpace: "nowrap" } }, "\xB7 ", (nav.find((n) => n.id === tab) || { label: T.profile }).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), phone ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), headerActionBtns, shareBtn, /* @__PURE__ */ React.createElement("button", { onClick: () => setTab("profile"), title: T.profile, style: { padding: 0, border: "none", background: "transparent", cursor: "pointer", display: "flex" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 28, radius: 7, dot: false }))) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__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)" } }), headerActionBtns, shareBtn, /* @__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)))), phone && peopleTab && /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, display: "flex", gap: 6, padding: "8px 12px", borderBottom: "1px solid var(--line)", background: "var(--panel)", overflowX: "auto" } }, nav.filter((n) => DK_PEOPLE_TABS.includes(n.id)).map((n) => /* @__PURE__ */ React.createElement("button", { key: n.id, onClick: () => setTab(n.id), style: {
6840
7193
  display: "inline-flex",
6841
7194
  alignItems: "center",
6842
7195
  gap: 6,
@@ -6850,10 +7203,22 @@ ${peer.address}`
6850
7203
  fontWeight: 600,
6851
7204
  background: tab === n.id ? "var(--accent)" : "transparent",
6852
7205
  color: tab === n.id ? "#fff" : "var(--dim)"
6853
- } }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, onBack: closeConversation, peers, requests, activeId, thread: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onAssistantAdopt: activeId === ASSISTANT_ID ? assistant.adoptServicePeers : null, onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), 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, onSetAvatar: (p) => dkApi.setProfileFull(p).then((r) => {
6854
- data.refresh();
6855
- return r;
6856
- }) })), phone && !inConversation && /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, display: "flex", borderTop: "1px solid var(--line)", background: "var(--rail)", paddingBottom: "env(safe-area-inset-bottom, 0px)" } }, phoneNav.map((n) => /* @__PURE__ */ React.createElement(TabBarBtn, { key: n.id, icon: n.icon, label: n.label, active: n.active, badge: n.badge, onClick: n.go }))), data.locked && /* @__PURE__ */ React.createElement(
7206
+ } }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, isProtocol, onBack: closeConversation, peers, requests, activeId, thread: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onAssistantAdopt: activeId === ASSISTANT_ID ? assistant.adoptServicePeers : null, onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), 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, me, peers, phone, setTab }), tabs.map((x) => x.kind === "host" && tab === x.id ? /* @__PURE__ */ React.createElement(HostTabPane, { key: x.id, tab: x, props: { T, t, me, peers, phone, setTab }, lang: t.lang }) : null), tab === "profile" && /* @__PURE__ */ React.createElement(
7207
+ ProfileTab,
7208
+ {
7209
+ T,
7210
+ me,
7211
+ onEdit,
7212
+ t,
7213
+ setTweak,
7214
+ onSetAvatar: (p) => dkApi.setProfileFull(p).then((r) => {
7215
+ data.refresh();
7216
+ return r;
7217
+ }),
7218
+ sections: hostProfileSections,
7219
+ sectionProps: { T, t, me, peers, phone, setTab }
7220
+ }
7221
+ )), phone && !inConversation && /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, display: "flex", borderTop: "1px solid var(--line)", background: "var(--rail)", paddingBottom: "env(safe-area-inset-bottom, 0px)" } }, phoneNav.map((n) => /* @__PURE__ */ React.createElement(TabBarBtn, { key: n.id, icon: n.icon, svgPath: n.svgPath, label: n.label, active: n.active, badge: n.badge, onClick: n.go }))), data.locked && /* @__PURE__ */ React.createElement(
6857
7222
  DkLockedOut,
6858
7223
  {
6859
7224
  compact: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.112",
3
+ "version": "0.1.114",
4
4
  "description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -35,7 +35,7 @@
35
35
  "yargs": "^17.7.2"
36
36
  },
37
37
  "devDependencies": {
38
- "@decentnetwork/beagle-ui": "^0.2.26",
38
+ "@decentnetwork/beagle-ui": "^0.2.28",
39
39
  "@types/js-yaml": "^4.0.9",
40
40
  "@types/node": "^20.11.0",
41
41
  "@types/yargs": "^17.0.32",