render message reply threads in dashboard and per-agent inbox
- MessageEvent and DashboardEvent Sent/Delivered now carry id and in_reply_to - broker.send() includes last_insert_rowid in the emitted event - recent_all() and recv_batch() include id and in_reply_to from the DB - deliver_reminders_batch() tracks per-row rowids within the transaction - dashboard message flow: reply rows are indented with a border-left and a clickable '↳ reply' tag that scroll-jumps + briefly highlights the parent - per-agent inbox: reply messages get a '↳ reply ·' prefix and indent Closes #26
This commit is contained in:
parent
804875d670
commit
b1f10b1d1b
8 changed files with 132 additions and 22 deletions
|
|
@ -1631,8 +1631,16 @@
|
|||
if (bannerOffTimer) clearTimeout(bannerOffTimer);
|
||||
bannerOffTimer = setTimeout(() => banner.classList.remove('active'), 4000);
|
||||
}
|
||||
// Map of broker row id → rendered row element. Lets reply rows add
|
||||
// a visual "↳ in reply to" indicator that links back to the parent.
|
||||
// Bounded by the history window (~200 msgs from /dashboard/history),
|
||||
// well within normal memory.
|
||||
const msgRowMap = new Map();
|
||||
|
||||
function renderMsg(ev, api, glyph) {
|
||||
const row = api.row('msgrow ' + ev.kind, '');
|
||||
const isReply = ev.in_reply_to != null;
|
||||
const cls = 'msgrow ' + ev.kind + (isReply ? ' msg-reply' : '');
|
||||
const row = api.row(cls, '');
|
||||
// Build via DOM so path anchors stay live + escape rules are
|
||||
// automatic (text nodes don't need esc()).
|
||||
const ts = document.createElement('span');
|
||||
|
|
@ -1648,7 +1656,35 @@
|
|||
const body = document.createElement('span');
|
||||
body.className = 'msg-body';
|
||||
appendLinkified(body, ev.body, ev.file_refs);
|
||||
row.append(ts, ' ', arrow, ' ', from, ' ', sep, ' ', to, ' ', body);
|
||||
// Reply thread indicator: a small "↳ reply to <from>" hint that
|
||||
// shows which message this is responding to. If we have the parent
|
||||
// in our row map, clicking scrolls it into view.
|
||||
if (isReply) {
|
||||
const replyTag = document.createElement('span');
|
||||
replyTag.className = 'msg-reply-tag';
|
||||
const parentRow = msgRowMap.get(ev.in_reply_to);
|
||||
if (parentRow) {
|
||||
const link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.textContent = '↳ reply';
|
||||
link.title = 'scroll to parent message';
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
parentRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
parentRow.classList.add('msg-highlight');
|
||||
setTimeout(() => parentRow.classList.remove('msg-highlight'), 1500);
|
||||
});
|
||||
replyTag.append(link);
|
||||
} else {
|
||||
replyTag.textContent = '↳ reply';
|
||||
}
|
||||
row.prepend(replyTag);
|
||||
row.append(ts, ' ', arrow, ' ', from, ' ', sep, ' ', to, ' ', body);
|
||||
} else {
|
||||
row.append(ts, ' ', arrow, ' ', from, ' ', sep, ' ', to, ' ', body);
|
||||
}
|
||||
// Register this row so future replies can reference it.
|
||||
if (ev.id != null && ev.id > 0) msgRowMap.set(ev.id, row);
|
||||
}
|
||||
HiveTerminal.create({
|
||||
logEl: flow,
|
||||
|
|
|
|||
|
|
@ -726,6 +726,30 @@ summary:hover { color: var(--purple); }
|
|||
}
|
||||
.live .msgrow.sent .msg-arrow { color: var(--cyan); }
|
||||
.live .msgrow.delivered .msg-arrow { color: var(--green); }
|
||||
/* Reply-thread rendering: indented border-left + muted reply tag. */
|
||||
.live .msgrow.msg-reply {
|
||||
padding-left: 1.2em;
|
||||
border-left: 2px solid var(--border);
|
||||
margin-left: 0.6em;
|
||||
}
|
||||
.msg-reply-tag {
|
||||
color: var(--muted);
|
||||
font-size: 0.8em;
|
||||
white-space: nowrap;
|
||||
order: -1; /* prepend before other flex items */
|
||||
}
|
||||
.msg-reply-tag a {
|
||||
color: var(--muted);
|
||||
text-shadow: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
.msg-reply-tag a:hover { color: var(--fg); }
|
||||
/* Flash highlight when scrolled to from a reply link. */
|
||||
@keyframes msg-highlight-fade {
|
||||
from { background: rgba(203, 166, 247, 0.18); }
|
||||
to { background: transparent; }
|
||||
}
|
||||
.msg-highlight { animation: msg-highlight-fade 1.5s ease-out forwards; }
|
||||
.msg-ts { color: var(--muted); font-size: 0.85em; }
|
||||
.msg-arrow { font-weight: bold; }
|
||||
.msg-from { color: var(--amber); }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue