feat(mesh-ui): Telegram-style action menu + Forward/Edit/Delete/ReadReceipt/rotate/outbox

* Replaces click-anywhere-on-bubble with a tiny ⋯ trigger in the meta row
  that fades in on hover (always visible on touch devices). Outside-click
  closes the menu, bubble gets a `menu-open` class so the trigger stays lit.
* Action menu gains Forward (any message) + Edit + Delete (own messages
  only, delete is red). Reaction spinner + reply preview upgraded to handle
  typed targets (attachment/invoice/location/alert) via summarizeForPreview.
* Pending-edit banner with ✎ icon mirrors the reply banner; Send flushes as
  mesh.edit-message when pendingEdit is set.
* Forwarded bubbles render "↪ Forwarded from {orig_name}" header; tombstone
  + (edited) markers; pending-reply close button upsized (28px, red hover).
* Scroll + message-arrival watcher fires a debounced 400ms read receipt
  with per-peer seq dedup so we never double-ack.
* Chat header: ⟲ rotate-prekeys button next to the shield badge; 📤 outbox
  count when mesh.outbox reports queued messages. Blob-store test widget
  removed and chat list now sorts by most-recent message timestamp.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-13 18:50:08 -04:00
co-authored by Claude Opus 4.6
parent 8ef7af985d
commit bdacc06a2b
3 changed files with 345 additions and 104 deletions
+72
View File
@@ -138,6 +138,10 @@
}
.mesh-session-badge { font-size: 0.75rem; margin-right: 6px; opacity: 0.7; }
.mesh-session-rotate { background: transparent; border: 1px solid rgba(255,255,255,0.15); color: rgba(255,255,255,0.7); font-size: 0.75rem; line-height: 1; padding: 2px 6px; margin-right: 8px; border-radius: 10px; cursor: pointer; transition: background 0.15s ease, color 0.15s ease; }
.mesh-session-rotate:hover:not(:disabled) { background: rgba(251,146,60,0.2); color: #fff; border-color: rgba(251,146,60,0.4); }
.mesh-session-rotate:disabled { opacity: 0.5; cursor: wait; }
.mesh-outbox-badge { font-size: 0.7rem; padding: 2px 7px; margin-right: 8px; border-radius: 10px; background: rgba(251,146,60,0.2); border: 1px solid rgba(251,146,60,0.4); color: #fff; }
.session-ratchet { color: #4ade80; opacity: 1; }
.session-static { color: #fbbf24; }
.session-none { color: rgba(255,255,255,0.3); }
@@ -229,3 +233,71 @@ select.mesh-bitcoin-input option { background: #1a1a2e; color: rgba(255,255,255,
.mesh-deadman-field { display: flex; flex-direction: column; gap: 4px; }
.mesh-deadman-info { display: flex; gap: 12px; flex-wrap: wrap; }
.mesh-deadman-info-item { font-size: 0.75rem; color: rgba(255,255,255,0.4); }
/* Reaction chips and action menu (Phase 2a) */
.mesh-chat-reactions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.mesh-chat-reaction-chip { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; border-radius: 12px; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12); font-size: 0.85rem; line-height: 1.1; }
.mesh-chat-reaction-chip.by-self { background: rgba(251,146,60,0.15); border-color: rgba(251,146,60,0.4); }
.mesh-chat-reaction-count { font-size: 0.7rem; color: rgba(255,255,255,0.55); font-weight: 600; }
.mesh-chat-action-menu { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 8px; padding: 8px 10px; border-radius: 10px; background: rgba(0,0,0,0.35); border: 1px solid rgba(255,255,255,0.1); }
.mesh-chat-action-btn { background: transparent; border: none; color: rgba(255,255,255,0.75); font-size: 0.8rem; padding: 4px 8px; border-radius: 6px; cursor: pointer; }
.mesh-chat-action-btn:hover { background: rgba(255,255,255,0.08); color: #fff; }
.mesh-chat-reaction-btn { background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1); color: #fff; font-size: 1.15rem; line-height: 1; padding: 6px 10px; border-radius: 8px; cursor: pointer; transition: transform 0.1s ease, background 0.15s ease; }
.mesh-chat-reaction-btn:hover { background: rgba(251,146,60,0.2); transform: scale(1.1); }
.mesh-chat-action-danger { color: rgba(248, 113, 113, 0.9) !important; }
.mesh-chat-action-danger:hover { background: rgba(239,68,68,0.2) !important; color: #fff !important; }
.mesh-chat-forward-header { font-size: 0.75rem; color: rgba(251,146,60,0.85); font-style: italic; margin-bottom: 3px; }
.mesh-chat-forward-body { }
.mesh-chat-deleted { font-style: italic; opacity: 0.55; }
.mesh-chat-edited { font-size: 0.7rem; opacity: 0.55; font-style: italic; }
/* Telegram-style ⋯ action trigger: tiny, ghosted in the meta row, expands on hover or when menu is open */
.mesh-chat-action-trigger {
background: transparent;
border: none;
color: rgba(255,255,255,0.45);
font-size: 1rem;
line-height: 1;
padding: 2px 6px;
margin-left: 2px;
border-radius: 10px;
cursor: pointer;
opacity: 0;
transform: scale(0.85);
transition: opacity 0.15s ease, transform 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.mesh-chat-bubble:hover .mesh-chat-action-trigger,
.mesh-chat-bubble.menu-open .mesh-chat-action-trigger,
.mesh-chat-action-trigger.active,
.mesh-chat-action-trigger:focus-visible {
opacity: 1;
transform: scale(1);
}
.mesh-chat-action-trigger:hover,
.mesh-chat-action-trigger.active {
background: rgba(255,255,255,0.1);
color: #fff;
}
@media (hover: none) {
.mesh-chat-action-trigger { opacity: 0.7; transform: scale(1); }
}
/* Generic inline spinner for busy buttons */
.mesh-spinner { display: inline-block; width: 1em; height: 1em; border: 2px solid rgba(255,255,255,0.25); border-top-color: #fb923c; border-radius: 50%; animation: mesh-spin 0.7s linear infinite; vertical-align: middle; }
@keyframes mesh-spin { to { transform: rotate(360deg); } }
.mesh-chat-attach-btn.is-busy { opacity: 0.8; cursor: wait; }
.mesh-chat-reaction-btn.is-busy { background: rgba(251,146,60,0.25); }
.mesh-chat-reaction-btn:disabled { opacity: 0.6; cursor: wait; }
/* Reply / attachment pending banner */
.mesh-chat-pending-reply,
.mesh-chat-pending-attachment { display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px; margin: 6px 0; border-radius: 10px; background: rgba(251,146,60,0.1); border: 1px solid rgba(251,146,60,0.25); font-size: 0.85rem; }
.mesh-chat-pending-reply .mesh-typed-icon,
.mesh-chat-pending-attachment .mesh-typed-icon { color: #fb923c; font-size: 1rem; line-height: 1.4; flex: 0 0 auto; }
.mesh-chat-pending-name { flex: 1 1 auto; min-width: 0; color: rgba(255,255,255,0.85); overflow-wrap: anywhere; word-break: break-word; line-height: 1.35; }
.mesh-chat-pending-size { flex: 0 0 auto; color: rgba(255,255,255,0.45); font-size: 0.75rem; margin-left: 4px; }
.mesh-chat-pending-clear { flex: 0 0 auto; align-self: center; margin-left: auto; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.15); color: rgba(255,255,255,0.85); width: 28px; height: 28px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; font-size: 0.95rem; line-height: 1; transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.1s ease; }
.mesh-chat-pending-clear:hover { background: rgba(239,68,68,0.3); color: #fff; border-color: rgba(239,68,68,0.6); transform: scale(1.08); }
.mesh-chat-pending-clear:active { transform: scale(0.92); }