From 9de625d617c6baf3d18622ed928b5de3581ea3a8 Mon Sep 17 00:00:00 2001 From: Fredrik Johansson Date: Sun, 28 Jun 2026 23:50:48 +0200 Subject: [PATCH] feat: render history_loaded in web UI with earlier messages divider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - store: handle history_loaded event — prepend gossipped messages, dedup by mid, sort by ts, record cutoff timestamp per room - MessagePane: show "earlier messages" divider between history and live messages based on the cutoff timestamp - types: add history_loaded, room_created, create_room to IpcMsgType; add messages field to IpcMessage Co-Authored-By: Claude Sonnet 4.6 --- web/src/App.css | 2 ++ web/src/components/MessagePane.tsx | 19 ++++++++++++++----- web/src/store/index.ts | 21 +++++++++++++++++++++ web/src/types.ts | 5 +++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/web/src/App.css b/web/src/App.css index 3c284c1..e286821 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -158,3 +158,5 @@ details summary { color: var(--muted); font-size: 12px; cursor: pointer; } .file-entry-dir { cursor: pointer; } .file-entry-dir:hover { background: rgba(255,255,255,0.04); } .file-entry-icon { font-size: 12px; flex-shrink: 0; } +.history-divider { display: flex; align-items: center; gap: 8px; margin: 10px 0 6px; color: var(--muted); font-size: 11px; } +.history-divider::before, .history-divider::after { content: ''; flex: 1; height: 1px; background: var(--border); } diff --git a/web/src/components/MessagePane.tsx b/web/src/components/MessagePane.tsx index 94073d0..7cd56a4 100644 --- a/web/src/components/MessagePane.tsx +++ b/web/src/components/MessagePane.tsx @@ -2,10 +2,11 @@ import { useEffect, useRef, useState } from 'react' import { useWaste } from '../store' export function MessagePane() { - const { messages, activeRoom, activeNetworkId, localPeer, connectedPeers, send } = useWaste() + const { messages, historyCutoff, activeRoom, activeNetworkId, localPeer, connectedPeers, send } = useWaste() const [draft, setDraft] = useState('') const bottomRef = useRef(null) const roomMessages = messages[activeRoom] ?? [] + const cutoff = historyCutoff[activeRoom] ?? 0 useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) @@ -46,11 +47,19 @@ export function MessagePane() { const mine = msg.from === localPeer?.id const alias = aliasFor(msg.from) const time = new Date(msg.ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false }) + const showDivider = cutoff > 0 && i > 0 && roomMessages[i - 1].ts <= cutoff && msg.ts > cutoff return ( -
- {time} - {alias} - {msg.text} +
+ {showDivider && ( +
+ earlier messages +
+ )} +
+ {time} + {alias} + {msg.text} +
) })} diff --git a/web/src/store/index.ts b/web/src/store/index.ts index 23716e5..865026a 100644 --- a/web/src/store/index.ts +++ b/web/src/store/index.ts @@ -32,6 +32,8 @@ interface WasteState { // chat — keyed by room messages: Record + // rooms for which we have received history: room → ts of last history message + historyCutoff: Record activeRoom: string // user-created rooms, keyed by networkId customRooms: Record @@ -84,6 +86,7 @@ export const useWaste = create((set, get) => ({ activeNetworkId: null, connectedPeers: [], messages: {}, + historyCutoff: {}, activeRoom: 'general', customRooms: {}, fileLists: {}, @@ -352,6 +355,24 @@ export const useWaste = create((set, get) => ({ } break } + case 'history_loaded': { + const room = msg.room + const incoming = (msg.messages ?? []) as ChatMessage[] + if (!room || incoming.length === 0) break + set(s => { + const existing = s.messages[room] ?? [] + const existingMids = new Set(existing.map(m => m.mid).filter(Boolean)) + const fresh = incoming.filter(m => !m.mid || !existingMids.has(m.mid)) + if (fresh.length === 0) return s + const merged = [...fresh, ...existing].sort((a, b) => a.ts - b.ts) + const cutoff = fresh[fresh.length - 1]?.ts ?? 0 + return { + messages: { ...s.messages, [room]: merged }, + historyCutoff: { ...s.historyCutoff, [room]: cutoff }, + } + }) + break + } } }, })) diff --git a/web/src/types.ts b/web/src/types.ts index a612267..e3d88eb 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -82,6 +82,9 @@ export type IpcMsgType = | 'shares_list' | 'peer_status' | 'error' + | 'history_loaded' + | 'room_created' + | 'create_room' export interface IpcMessage { type: IpcMsgType @@ -124,4 +127,6 @@ export interface IpcMessage { conn_state?: PeerConnState candidate_type?: CandidateType remote_address?: string + // history_loaded + messages?: ChatMessage[] }