@decentnetwork/beagle-ui 0.2.28 → 0.2.29

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 +193 -107
  2. package/package.json +1 -1
package/dist/beagle-ui.js CHANGED
@@ -1,4 +1,4 @@
1
- export const BEAGLE_UI_VERSION = "0.2.28";
1
+ export const BEAGLE_UI_VERSION = "0.2.29";
2
2
  // src/icons.jsx
3
3
  var ICON_PATHS = {
4
4
  // ---- tab bar (the four must feel like one set) ----
@@ -166,6 +166,142 @@ function DkBrowserNotice(props) {
166
166
  return H.DkBrowserNotice ? /* @__PURE__ */ React.createElement(H.DkBrowserNotice, { ...props }) : null;
167
167
  }
168
168
 
169
+ // src/host-messages.js
170
+ function protocolTest(rule) {
171
+ if (typeof rule !== "function")
172
+ return () => false;
173
+ return (msg) => {
174
+ if (!msg)
175
+ return false;
176
+ try {
177
+ const v = rule(msg);
178
+ return typeof v === "string" ? v || true : !!v;
179
+ } catch (e) {
180
+ return false;
181
+ }
182
+ };
183
+ }
184
+ function stripOwnPrefix(text) {
185
+ return String(text == null ? "" : text).replace(/^you:\s*/i, "");
186
+ }
187
+ function lastMessageOf(peer) {
188
+ const raw = String(peer && peer.lastMsg || "");
189
+ return {
190
+ text: stripOwnPrefix(raw),
191
+ dir: peer && peer.lastDir || (/^you:\s*/i.test(raw) ? "out" : "in"),
192
+ ts: peer && peer.lastTs || 0
193
+ };
194
+ }
195
+ function lastIsProtocol(peer, isProto) {
196
+ const last = lastMessageOf(peer);
197
+ if (!last.text)
198
+ return false;
199
+ return isProto(last);
200
+ }
201
+ function newestHumanMessage(msgs, isProto) {
202
+ const arr = msgs || [];
203
+ for (let i = arr.length - 1; i >= 0; i--) {
204
+ const m = arr[i];
205
+ if (!m)
206
+ continue;
207
+ if (m.file)
208
+ return m;
209
+ if (!m.text)
210
+ continue;
211
+ if (isProto({ text: m.text, dir: m.dir, ts: m.ts }))
212
+ continue;
213
+ return m;
214
+ }
215
+ return null;
216
+ }
217
+ function humanUnread(msgs, unread, isProto) {
218
+ const want = Number(unread) || 0;
219
+ if (want <= 0)
220
+ return { count: 0, exact: true };
221
+ const arr = msgs || [];
222
+ const window2 = arr.slice(Math.max(0, arr.length - want));
223
+ let n = 0;
224
+ for (const m of window2) {
225
+ if (!m || m.dir !== "in")
226
+ continue;
227
+ if (m.text && !m.file && isProto({ text: m.text, dir: m.dir, ts: m.ts }))
228
+ continue;
229
+ n++;
230
+ }
231
+ return { count: n, exact: window2.length >= want };
232
+ }
233
+ function rosterRow(peer, isProto, fix) {
234
+ const hidden = lastIsProtocol(peer, isProto);
235
+ if (!hidden) {
236
+ return {
237
+ preview: peer && peer.lastMsg || "",
238
+ lastTime: peer && peer.lastTime || "",
239
+ unread: Number(peer && peer.unread || 0),
240
+ activityTs: Number(peer && peer.lastTs || 0),
241
+ hidden: false
242
+ };
243
+ }
244
+ return {
245
+ preview: fix && fix.preview || "",
246
+ lastTime: fix && fix.lastTime || "",
247
+ unread: fix && fix.unreadExact ? fix.unread : 0,
248
+ activityTs: fix && fix.ts || 0,
249
+ hidden: true,
250
+ pending: !fix
251
+ };
252
+ }
253
+ function rosterUnreadTotal(peers, isProto, fixes) {
254
+ return (peers || []).reduce((n, p) => n + rosterRow(p, isProto, fixes && fixes[p.id]).unread, 0);
255
+ }
256
+ function collapseProtocol(thread, isProto, lang) {
257
+ const out = [];
258
+ let run = null;
259
+ const flush = () => {
260
+ if (!run)
261
+ return;
262
+ out.push({
263
+ protocol: true,
264
+ id: `proto-${run.first.id || run.startIndex}`,
265
+ count: run.items.length,
266
+ label: run.label,
267
+ dir: run.first.from === "me" ? "out" : "in",
268
+ text: run.items.map((m) => m.text).join("\n"),
269
+ line: protocolLine(run, lang)
270
+ });
271
+ run = null;
272
+ };
273
+ (thread || []).forEach((m, i) => {
274
+ const hit = m && !m.day ? isProto({ text: m.text, dir: m && m.from === "me" ? "out" : "in", ts: m && m.ts }) : false;
275
+ if (!hit || m && m.file) {
276
+ flush();
277
+ out.push(m);
278
+ return;
279
+ }
280
+ if (!run)
281
+ run = { first: m, startIndex: i, items: [], label: typeof hit === "string" ? hit : "" };
282
+ if (!run.label && typeof hit === "string")
283
+ run.label = hit;
284
+ run.items.push(m);
285
+ });
286
+ flush();
287
+ return out;
288
+ }
289
+ function protocolLine(run, lang) {
290
+ const arrow = run.first.from === "me" ? "\u2192" : "\u2190";
291
+ const n = run.items.length;
292
+ if (run.label) {
293
+ return `\u2699 ${arrow} ${run.label}${n > 1 ? ` \xB7 ${n}` : ""}`;
294
+ }
295
+ if (n > 1) {
296
+ return lang === "zh" ? `\u2699 ${arrow} ${n} \u6761\u534F\u8BAE\u6D88\u606F` : `\u2699 ${arrow} ${n} protocol messages`;
297
+ }
298
+ return `\u2699 ${arrow} ${protocolSummary(run.first.text)}`;
299
+ }
300
+ function protocolSummary(text, max = 72) {
301
+ const s = String(text == null ? "" : text).replace(/\s+/g, " ").trim();
302
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
303
+ }
304
+
169
305
  // src/data-ipc.jsx
170
306
  function dkHash(s) {
171
307
  let h = 2166136261;
@@ -398,6 +534,48 @@ function dkThreadMsg(m) {
398
534
  components: m.components
399
535
  };
400
536
  }
537
+ var DK_TAIL_MIN = 30;
538
+ var DK_TAIL_MAX = 200;
539
+ function useProtocolTails(peers, isProtocol, enabled) {
540
+ const [fixes, setFixes] = React.useState({});
541
+ const cache = React.useRef(/* @__PURE__ */ new Map());
542
+ const inFlight = React.useRef(/* @__PURE__ */ new Set());
543
+ React.useEffect(() => {
544
+ if (!enabled)
545
+ return;
546
+ for (const p of peers || []) {
547
+ if (!p || !lastIsProtocol(p, isProtocol))
548
+ continue;
549
+ const unread = Number(p.unread) || 0;
550
+ const key = `${p.lastTs || 0}:${unread}`;
551
+ const had = cache.current.get(p.id);
552
+ if (had && had.key === key)
553
+ continue;
554
+ if (inFlight.current.has(p.id))
555
+ continue;
556
+ const limit = Math.min(DK_TAIL_MAX, Math.max(DK_TAIL_MIN, unread + 5));
557
+ inFlight.current.add(p.id);
558
+ dkGet("/api/chat-history?peer=" + encodeURIComponent(p.id) + "&limit=" + limit).then((d) => {
559
+ const msgs = d && d.chats && d.chats[p.id] || [];
560
+ const human = newestHumanMessage(msgs, isProtocol);
561
+ const count = humanUnread(msgs, unread, isProtocol);
562
+ const fix = {
563
+ preview: human ? (human.dir === "out" ? "you: " : "") + (human.file ? human.file.name : human.text || "") : "",
564
+ lastTime: human ? dkClock(human.ts) : "",
565
+ ts: human ? human.ts : 0,
566
+ unread: count.count,
567
+ unreadExact: count.exact
568
+ };
569
+ cache.current.set(p.id, { key, fix });
570
+ setFixes((f) => Object.assign({}, f, { [p.id]: fix }));
571
+ }).catch(() => {
572
+ }).finally(() => {
573
+ inFlight.current.delete(p.id);
574
+ });
575
+ }
576
+ }, [peers, isProtocol, enabled]);
577
+ return fixes;
578
+ }
401
579
  function useDaemonData() {
402
580
  const [snap, setSnap] = React.useState({
403
581
  me: DK_ME_FALLBACK,
@@ -1506,100 +1684,6 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
1506
1684
  return { thread, handle, removeFriends, adoptServicePeers };
1507
1685
  }
1508
1686
 
1509
- // src/host-messages.js
1510
- function protocolTest(rule) {
1511
- if (typeof rule !== "function")
1512
- return () => false;
1513
- return (msg) => {
1514
- if (!msg)
1515
- return false;
1516
- try {
1517
- const v = rule(msg);
1518
- return typeof v === "string" ? v || true : !!v;
1519
- } catch (e) {
1520
- return false;
1521
- }
1522
- };
1523
- }
1524
- function stripOwnPrefix(text) {
1525
- return String(text == null ? "" : text).replace(/^you:\s*/i, "");
1526
- }
1527
- function lastMessageOf(peer) {
1528
- const raw = String(peer && peer.lastMsg || "");
1529
- return {
1530
- text: stripOwnPrefix(raw),
1531
- dir: peer && peer.lastDir || (/^you:\s*/i.test(raw) ? "out" : "in"),
1532
- ts: peer && peer.lastTs || 0
1533
- };
1534
- }
1535
- function lastIsProtocol(peer, isProto) {
1536
- const last = lastMessageOf(peer);
1537
- if (!last.text)
1538
- return false;
1539
- return isProto(last);
1540
- }
1541
- function rosterRow(peer, isProto) {
1542
- const hidden = lastIsProtocol(peer, isProto);
1543
- return {
1544
- preview: hidden ? "" : peer && peer.lastMsg || "",
1545
- lastTime: hidden ? "" : peer && peer.lastTime || "",
1546
- unread: hidden ? 0 : Number(peer && peer.unread || 0),
1547
- activityTs: hidden ? 0 : Number(peer && peer.lastTs || 0),
1548
- hidden
1549
- };
1550
- }
1551
- function rosterUnreadTotal(peers, isProto) {
1552
- return (peers || []).reduce((n, p) => n + rosterRow(p, isProto).unread, 0);
1553
- }
1554
- function collapseProtocol(thread, isProto, lang) {
1555
- const out = [];
1556
- let run = null;
1557
- const flush = () => {
1558
- if (!run)
1559
- return;
1560
- out.push({
1561
- protocol: true,
1562
- id: `proto-${run.first.id || run.startIndex}`,
1563
- count: run.items.length,
1564
- label: run.label,
1565
- dir: run.first.from === "me" ? "out" : "in",
1566
- text: run.items.map((m) => m.text).join("\n"),
1567
- line: protocolLine(run, lang)
1568
- });
1569
- run = null;
1570
- };
1571
- (thread || []).forEach((m, i) => {
1572
- const hit = m && !m.day ? isProto({ text: m.text, dir: m && m.from === "me" ? "out" : "in", ts: m && m.ts }) : false;
1573
- if (!hit || m && m.file) {
1574
- flush();
1575
- out.push(m);
1576
- return;
1577
- }
1578
- if (!run)
1579
- run = { first: m, startIndex: i, items: [], label: typeof hit === "string" ? hit : "" };
1580
- if (!run.label && typeof hit === "string")
1581
- run.label = hit;
1582
- run.items.push(m);
1583
- });
1584
- flush();
1585
- return out;
1586
- }
1587
- function protocolLine(run, lang) {
1588
- const arrow = run.first.from === "me" ? "\u2192" : "\u2190";
1589
- const n = run.items.length;
1590
- if (run.label) {
1591
- return `\u2699 ${arrow} ${run.label}${n > 1 ? ` \xB7 ${n}` : ""}`;
1592
- }
1593
- if (n > 1) {
1594
- return lang === "zh" ? `\u2699 ${arrow} ${n} \u6761\u534F\u8BAE\u6D88\u606F` : `\u2699 ${arrow} ${n} protocol messages`;
1595
- }
1596
- return `\u2699 ${arrow} ${protocolSummary(run.first.text)}`;
1597
- }
1598
- function protocolSummary(text, max = 72) {
1599
- const s = String(text == null ? "" : text).replace(/\s+/g, " ").trim();
1600
- return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1601
- }
1602
-
1603
1687
  // src/chat.jsx
1604
1688
  var DK_CMD_HIST_KEY = "beagle.assistant.history";
1605
1689
  var DK_CMD_HIST_MAX = 50;
@@ -1661,9 +1745,9 @@ function RequestsBlock({ T, requests, onAct }) {
1661
1745
  paddingLeft: 8
1662
1746
  } }, 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))))));
1663
1747
  }
1664
- function PeerRow({ peer, T, active, onClick, muted, isProtocol }) {
1748
+ function PeerRow({ peer, T, active, onClick, muted, isProtocol, fix }) {
1665
1749
  const name = peer.alias || peer.userId;
1666
- const row = rosterRow(peer, isProtocol || (() => false));
1750
+ const row = rosterRow(peer, isProtocol || (() => false), fix);
1667
1751
  return /* @__PURE__ */ React.createElement("button", { onClick, style: {
1668
1752
  display: "flex",
1669
1753
  alignItems: "center",
@@ -1694,11 +1778,11 @@ function PeerRow({ peer, T, active, onClick, muted, isProtocol }) {
1694
1778
  minWidth: 0
1695
1779
  } }, 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 })));
1696
1780
  }
1697
- function dkActivityTs(p, isProtocol) {
1698
- const row = rosterRow(p, isProtocol || (() => false));
1781
+ function dkActivityTs(p, isProtocol, fix) {
1782
+ const row = rosterRow(p, isProtocol || (() => false), fix);
1699
1783
  return Math.max(row.activityTs, p.acceptedAt || 0, p.requestedAt || 0);
1700
1784
  }
1701
- function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide, isProtocol }) {
1785
+ function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide, isProtocol, protocolFix }) {
1702
1786
  const [q, setQ] = React.useState("");
1703
1787
  const [addr, setAddr] = React.useState("");
1704
1788
  const [addState, setAddState] = React.useState(null);
@@ -1743,7 +1827,8 @@ function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, pre
1743
1827
  }).slice().sort((a, b) => {
1744
1828
  if (!!a.pinned !== !!b.pinned)
1745
1829
  return a.pinned ? -1 : 1;
1746
- const ta = dkActivityTs(a, isProtocol), tb = dkActivityTs(b, isProtocol);
1830
+ const ta = dkActivityTs(a, isProtocol, protocolFix && protocolFix[a.id]);
1831
+ const tb = dkActivityTs(b, isProtocol, protocolFix && protocolFix[b.id]);
1747
1832
  if (ta !== tb)
1748
1833
  return tb - ta;
1749
1834
  return String(a.alias || a.userId || "").localeCompare(String(b.alias || b.userId || ""));
@@ -1782,7 +1867,7 @@ function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, pre
1782
1867
  fontSize: 11.5,
1783
1868
  lineHeight: 1.35,
1784
1869
  color: addState.kind === "err" ? "var(--bad, #e5484d)" : addState.kind === "ok" ? "var(--good, #46a758)" : "var(--faint)"
1785
- } }, 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"))));
1870
+ } }, 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, fix: protocolFix && protocolFix[p.id] })), !filtered.length && /* @__PURE__ */ React.createElement("div", { style: { padding: 16, textAlign: "center", fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "no matches"))));
1786
1871
  }
1787
1872
  var inputStyle = {
1788
1873
  flex: 1,
@@ -3383,12 +3468,12 @@ function MenuItem({ icon, label, onClick, danger }) {
3383
3468
  function ChatEmpty({ T }) {
3384
3469
  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));
3385
3470
  }
3386
- 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 }) {
3471
+ function ChatTab({ T, lang, peers, requests, activeId, thread, isProtocol, protocolFix, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
3387
3472
  const peer = peers.find((p) => p.id === activeId);
3388
3473
  if (phone) {
3389
- 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 });
3474
+ 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, protocolFix, wide: true });
3390
3475
  }
3391
- 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 }));
3476
+ 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, protocolFix }), 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 }));
3392
3477
  }
3393
3478
  Object.assign(window, { ChatTab });
3394
3479
 
@@ -6924,6 +7009,7 @@ function DkApp() {
6924
7009
  const mutedIds = Array.isArray(t.alertMuted) ? t.alertMuted : [];
6925
7010
  const onToggleMute = (id) => setTweak("alertMuted", mutedIds.includes(id) ? mutedIds.filter((x) => x !== id) : mutedIds.concat(id));
6926
7011
  const isProtocol = React.useMemo(() => protocolTest(hostIsProtocolMessage), []);
7012
+ const protocolFix = useProtocolTails(peers, isProtocol, !!hostIsProtocolMessage);
6927
7013
  useIncomingAlerts({
6928
7014
  T,
6929
7015
  peers,
@@ -7185,7 +7271,7 @@ function DkApp() {
7185
7271
  const lastPeople = React.useRef("here");
7186
7272
  if (peopleTab)
7187
7273
  lastPeople.current = tab;
7188
- const unreadTotal = rosterUnreadTotal(peers, isProtocol) + (requests || []).length;
7274
+ const unreadTotal = rosterUnreadTotal(peers, isProtocol, protocolFix) + (requests || []).length;
7189
7275
  const NAV_FOR = {
7190
7276
  chat: { icon: "message", label: T.chat, active: tab === "chat", badge: unreadTotal, go: () => setTab("chat") },
7191
7277
  people: { icon: "users", label: T.people, active: peopleTab, go: () => setTab(lastPeople.current) },
@@ -7243,7 +7329,7 @@ function DkApp() {
7243
7329
  fontWeight: 600,
7244
7330
  background: tab === n.id ? "var(--accent)" : "transparent",
7245
7331
  color: tab === n.id ? "#fff" : "var(--dim)"
7246
- } }, /* @__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(
7332
+ } }, /* @__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, protocolFix, 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(
7247
7333
  ProfileTab,
7248
7334
  {
7249
7335
  T,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle-ui",
3
- "version": "0.2.28",
3
+ "version": "0.2.29",
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",