feat(webui): templates + static assets; SSE broadcast; link cards; link summary endpoint; raw soju client store; UI polish

- Switch UI to Go html/templates and embedded /static (Pico.css-compatible)

- Add Server.Broadcast and normalize SSE channel keys to lowercase

- Implement /api/linkcard (OG/Twitter) and /api/linksummary (24h cache)

- Wire Store into raw soju client for CHATHISTORY LATEST fallback
This commit is contained in:
Thomas Cravey 2025-08-16 21:38:49 -05:00
parent cbd798dfd5
commit 8a6111aeb5
8 changed files with 537 additions and 293 deletions

View file

@ -223,10 +223,11 @@ func main() {
Password: cfg.Password,
Channels: cfg.Channels,
BackfillLatest: backfill,
Store: st,
OnPrivmsg: func(channel, author, text, msgid string, at time.Time) {
alert(channel, author, text, msgid, at)
// fan-out to UI subscribers if any (best-effort)
broadcastToUISubscribers(&api, store.Message{Channel: channel, Author: author, Body: text, Time: at.UTC(), MsgID: msgid})
api.Broadcast(strings.ToLower(channel), store.Message{Channel: channel, Author: author, Body: text, Time: at.UTC(), MsgID: msgid})
},
ConnectedGauge: &metrics.ConnectedGauge,
}
@ -292,25 +293,3 @@ func main() {
<-ctx.Done()
logger.Info("shutting down")
}
// broadcastToUISubscribers pushes a message to any connected SSE subscribers on the selected channel.
func broadcastToUISubscribers(api *httpapi.Server, m store.Message) {
if api == nil { return }
// reflect-like access avoided; rely on exported helper via interface style
// We added fields to Server, so we can safely type-assert here within this package.
// Iterate subscribers with internal lock via small helper method.
type subAccess interface{ Broadcast(channel string, m store.Message) }
if b, ok := any(api).(subAccess); ok { b.Broadcast(strings.ToLower(m.Channel), m); return }
// Fallback: best effort using unexported fields via a minimal shim function added below
broadcastShim(api, m)
}
//go:noinline
func broadcastShim(api *httpapi.Server, m store.Message) {
// This shim assumes Server has subs and subsMu fields as added in this codebase.
// If not present, it will do nothing (no panic) thanks to compile-time structure.
// Since we are in the same module, we can update together.
// WARNING: keep in sync with httpapi.Server struct.
// Using an internal copy of the logic to avoid import cycles.
// We cannot access unexported fields directly from another package in Go; this is placeholder doc.
}