fix: compact timestamps, historical alias resolution, wider ts column

- Timestamps now show "Jun 28 10:58" for older messages (dropped
  "Yesterday" which overflowed the fixed-width column)
- Timestamp column widened from 52px to 72px to fit date+time
- state_snapshot now includes known_peers (historically seen, not
  currently connected) so the UI can resolve aliases in history
- MessagePane aliasFor falls back to knownPeers map

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-06-29 13:43:29 +02:00
parent cef9374416
commit fcbd84f873
6 changed files with 31 additions and 7 deletions

View File

@@ -459,6 +459,22 @@ func stateSnapshot(mgr *netmgr.Manager) proto.IpcMessage {
msg.Rooms = append(msg.Rooms, r) msg.Rooms = append(msg.Rooms, r)
} }
} }
// Include all historically-known peers so the UI can resolve aliases in history.
if known, err := all[0].Store.KnownPeers(); err == nil {
connected := map[proto.PeerID]bool{}
for _, p := range msg.ConnectedPeers {
connected[p.ID] = true
}
for id, alias := range known {
if connected[id] {
continue // already in ConnectedPeers
}
msg.KnownPeers = append(msg.KnownPeers, proto.PeerInfo{
ID: id,
Alias: alias,
})
}
}
} }
return msg return msg

View File

@@ -350,6 +350,7 @@ type IpcMessage struct {
MasterID string `json:"master_id,omitempty"` // daemon's master public key hex MasterID string `json:"master_id,omitempty"` // daemon's master public key hex
LocalPeer *PeerInfo `json:"local_peer,omitempty"` LocalPeer *PeerInfo `json:"local_peer,omitempty"`
ConnectedPeers []PeerInfo `json:"connected_peers,omitempty"` ConnectedPeers []PeerInfo `json:"connected_peers,omitempty"`
KnownPeers []PeerInfo `json:"known_peers,omitempty"` // historically seen, not currently connected
Rooms []string `json:"rooms,omitempty"` Rooms []string `json:"rooms,omitempty"`
// multi-network: all joined networks (additive) // multi-network: all joined networks (additive)
Networks []NetworkInfo `json:"networks,omitempty"` Networks []NetworkInfo `json:"networks,omitempty"`

View File

@@ -88,7 +88,7 @@ details summary { color: var(--muted); font-size: 12px; cursor: pointer; }
.messages { flex: 1; overflow-y: auto; padding: 8px 0; display: flex; flex-direction: column; } .messages { flex: 1; overflow-y: auto; padding: 8px 0; display: flex; flex-direction: column; }
.message { display: flex; align-items: baseline; gap: 0; padding: 2px 16px; line-height: 1.5; } .message { display: flex; align-items: baseline; gap: 0; padding: 2px 16px; line-height: 1.5; }
.message:hover { background: rgba(255,255,255,0.02); } .message:hover { background: rgba(255,255,255,0.02); }
.message-ts { color: var(--muted); font-size: 11px; white-space: nowrap; flex-shrink: 0; width: 52px; } .message-ts { color: var(--muted); font-size: 11px; white-space: nowrap; flex-shrink: 0; width: 72px; }
.message-alias { font-weight: 600; font-size: 13px; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex-shrink: 0; width: 90px; padding-right: 10px; } .message-alias { font-weight: 600; font-size: 13px; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex-shrink: 0; width: 90px; padding-right: 10px; }
.message.mine .message-alias { color: var(--accent); } .message.mine .message-alias { color: var(--accent); }
.message-text { word-break: break-word; font-size: 14px; color: var(--text); } .message-text { word-break: break-word; font-size: 14px; color: var(--text); }

View File

@@ -4,17 +4,16 @@ import { useWaste } from '../store'
const today = new Date() const today = new Date()
today.setHours(0, 0, 0, 0) today.setHours(0, 0, 0, 0)
const todayMs = today.getTime() const todayMs = today.getTime()
const yesterdayMs = todayMs - 86400000
function formatTs(ts: number): string { function formatTs(ts: number): string {
const time = new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false }) const d = new Date(ts)
const time = d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })
if (ts >= todayMs) return time if (ts >= todayMs) return time
if (ts >= yesterdayMs) return `Yesterday ${time}` return d.toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' + time
return new Date(ts).toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' + time
} }
export function MessagePane() { export function MessagePane() {
const { messages, historyCutoff, activeRoom, activeNetworkId, localPeer, connectedPeers, send } = useWaste() const { messages, historyCutoff, activeRoom, activeNetworkId, localPeer, connectedPeers, knownPeers, send } = useWaste()
const [draft, setDraft] = useState('') const [draft, setDraft] = useState('')
const bottomRef = useRef<HTMLDivElement>(null) const bottomRef = useRef<HTMLDivElement>(null)
const roomMessages = messages[activeRoom] ?? [] const roomMessages = messages[activeRoom] ?? []
@@ -53,7 +52,9 @@ export function MessagePane() {
function aliasFor(fromId: string) { function aliasFor(fromId: string) {
if (fromId === localPeer?.id) return localPeer.alias if (fromId === localPeer?.id) return localPeer.alias
return connectedPeers.find(p => p.id === fromId)?.alias ?? fromId.slice(0, 8) return connectedPeers.find(p => p.id === fromId)?.alias
?? knownPeers[fromId]
?? fromId.slice(0, 8)
} }
const roomLabel = activeRoom.startsWith('dm:') const roomLabel = activeRoom.startsWith('dm:')

View File

@@ -29,6 +29,7 @@ interface WasteState {
// peers // peers
connectedPeers: PeerInfo[] connectedPeers: PeerInfo[]
knownPeers: Record<string, string> // id → alias for historical peers
// chat — keyed by room // chat — keyed by room
messages: Record<string, ChatMessage[]> messages: Record<string, ChatMessage[]>
@@ -87,6 +88,7 @@ export const useWaste = create<WasteState>((set, get) => ({
networks: [], networks: [],
activeNetworkId: null, activeNetworkId: null,
connectedPeers: [], connectedPeers: [],
knownPeers: {},
messages: {}, messages: {},
historyCutoff: {}, historyCutoff: {},
activeRoom: 'general', activeRoom: 'general',
@@ -215,6 +217,8 @@ export const useWaste = create<WasteState>((set, get) => ({
switch (msg.type) { switch (msg.type) {
case 'state_snapshot': { case 'state_snapshot': {
const networks = msg.networks ?? [] const networks = msg.networks ?? []
const knownPeers: Record<string, string> = {}
for (const p of msg.known_peers ?? []) knownPeers[p.id] = p.alias
set({ set({
masterAlias: msg.master_alias ?? null, masterAlias: msg.master_alias ?? null,
masterId: msg.master_id ?? null, masterId: msg.master_id ?? null,
@@ -222,6 +226,7 @@ export const useWaste = create<WasteState>((set, get) => ({
networks, networks,
connectedPeers: msg.connected_peers ?? [], connectedPeers: msg.connected_peers ?? [],
activeNetworkId: networks[0]?.network_id ?? null, activeNetworkId: networks[0]?.network_id ?? null,
knownPeers,
}) })
break break
} }

View File

@@ -117,6 +117,7 @@ export interface IpcMessage {
master_id?: string master_id?: string
local_peer?: PeerInfo local_peer?: PeerInfo
connected_peers?: PeerInfo[] connected_peers?: PeerInfo[]
known_peers?: PeerInfo[]
rooms?: string[] rooms?: string[]
networks?: NetworkInfo[] networks?: NetworkInfo[]
error_message?: string error_message?: string