sound
ci / test (push) Successful in 3m6s
ci / release (push) Failing after 1m5s

This commit is contained in:
2026-07-03 06:47:07 -04:00
parent 38b2abd71b
commit d2c574c614
12 changed files with 517 additions and 11 deletions
+8 -2
View File
@@ -252,7 +252,7 @@ func writeWrappedField(b *strings.Builder, label, value string, width, labelWidt
}
}
func renderFooter(t Theme, filtering, searching bool, filter string, follow bool, detailOpen bool, mouseEnabled bool, tab ViewTab, feedMeshVisible bool, historyNote string) string {
func renderFooter(t Theme, filtering, searching bool, filter string, follow bool, detailOpen bool, mouseEnabled bool, tab ViewTab, feedMeshVisible, soundEnabled bool, historyNote string) string {
var hints []string
if searching {
hints = []string{"type id…", "Enter lookup", "Esc cancel"}
@@ -269,6 +269,7 @@ func renderFooter(t Theme, filtering, searching bool, filter string, follow bool
if tab == TabFeed {
hints = append(hints, "v mesh")
}
hints = append(hints, "e sound")
hints = append(hints, "m mouse")
if filtering {
hints = []string{"type filter…", "Enter apply", "Esc cancel"}
@@ -286,6 +287,9 @@ func renderFooter(t Theme, filtering, searching bool, filter string, follow bool
if tab == TabFeed && feedMeshVisible {
line += " " + lipgloss.NewStyle().Foreground(t.Teal).Render("[mesh]")
}
if soundEnabled {
line += " " + lipgloss.NewStyle().Foreground(t.Teal).Render("[sound]")
}
if historyNote != "" {
line += " " + lipgloss.NewStyle().Foreground(t.Muted).Render("[" + historyNote + "]")
}
@@ -304,7 +308,8 @@ func renderHelp(t Theme) string {
PgUp Load older attacks (Feed) / moderation (Incidents) / more posts (Blog)
Feed tab
v Toggle peer mesh animation above the feed
v Toggle peer mesh animation below the feed
e Toggle event sounds (attack, block, peer, moderation)
Blog tab
Enter Read selected briefing
@@ -346,6 +351,7 @@ func renderHelp(t Theme) string {
r Force SSE reconnect
General
e Toggle synthesized event sounds (matches web viz)
? Toggle this help
q, Ctrl+C Quit
`
+1 -1
View File
@@ -48,7 +48,7 @@ func (m Model) chromeBottom() string {
if m.statusErr != "" {
parts = append(parts, renderStatusErr(m.theme, m.statusErr))
}
parts = append(parts, renderFooter(m.theme, m.filtering, m.searching, m.filterQuery, m.follow, m.detailOpen, m.mouseEnabled, m.tab, m.feedMeshVisible, m.historyFooterNote()))
parts = append(parts, renderFooter(m.theme, m.filtering, m.searching, m.filterQuery, m.follow, m.detailOpen, m.mouseEnabled, m.tab, m.feedMeshVisible, m.sound.Enabled(), m.historyFooterNote()))
if m.filtering {
parts = append(parts, m.filterInput.View())
}
+30 -6
View File
@@ -14,6 +14,7 @@ import (
"github.com/honeypeer/cli-viz/internal/config"
"github.com/honeypeer/cli-viz/internal/mesh"
"github.com/honeypeer/cli-viz/internal/sound"
"github.com/honeypeer/cli-viz/internal/transport"
"github.com/honeypeer/cli-viz/internal/viz"
)
@@ -63,6 +64,7 @@ type keyMap struct {
Up, Down, Quit, Help, Tab, Follow, Filter, Search, Enter, Reconnect, Close, Mouse key.Binding
PgUp, PgDn key.Binding
MeshView key.Binding
Sound key.Binding
}
func defaultKeyMap() keyMap {
@@ -82,6 +84,7 @@ func defaultKeyMap() keyMap {
PgUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("pgup", "history")),
PgDn: key.NewBinding(key.WithKeys("pgdn"), key.WithHelp("pgdn", "down")),
MeshView: key.NewBinding(key.WithKeys("v"), key.WithHelp("v", "mesh")),
Sound: key.NewBinding(key.WithKeys("e"), key.WithHelp("e", "sound")),
}
}
@@ -146,6 +149,7 @@ type Model struct {
peerMesh *mesh.State
meshTickActive bool
feedMeshVisible bool
sound *sound.Engine
}
func NewModel(cfg config.Config) Model {
@@ -179,6 +183,7 @@ func NewModel(cfg config.Config) Model {
pm := mesh.NewState()
pm.SetReducedMotion(cfg.NoColor || envTruthy("HP_VIZ_REDUCED_MOTION"))
m.peerMesh = pm
m.sound = sound.New()
return m
}
@@ -261,9 +266,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
case key.Matches(msg, m.keys.Mouse):
return m.toggleMouse()
case key.Matches(msg, m.keys.Sound):
m.sound.Toggle()
return m, nil
case key.Matches(msg, m.keys.Quit):
m.sse.Stop()
return m, tea.Quit
return m, m.quit()
}
return m, nil
}
@@ -284,9 +291,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
case key.Matches(msg, m.keys.Mouse):
return m.toggleMouse()
case key.Matches(msg, m.keys.Sound):
m.sound.Toggle()
return m, nil
case key.Matches(msg, m.keys.Quit):
m.sse.Stop()
return m, tea.Quit
return m, m.quit()
}
return m, nil
}
@@ -294,8 +303,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keys.Mouse):
return m.toggleMouse()
case key.Matches(msg, m.keys.Quit):
m.sse.Stop()
return m, tea.Quit
return m, m.quit()
case key.Matches(msg, m.keys.Help):
m.showHelp = !m.showHelp
case key.Matches(msg, m.keys.Tab):
@@ -324,6 +332,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.meshTickActive = false
}
}
case key.Matches(msg, m.keys.Sound):
m.sound.Toggle()
case key.Matches(msg, m.keys.Filter):
m.cancelSearch()
m.filtering = true
@@ -577,6 +587,7 @@ func (m *Model) handleSSE(evt transport.Event) {
fmt.Print("\a")
}
m.peerMesh.TriggerAttack(a.PeerID, a.Service, m.meshPalette())
m.sound.PlayAttack()
case "peer":
p := data.(viz.PeerEvent)
m.store.Push(viz.FeedFromPeer(p))
@@ -589,6 +600,11 @@ func (m *Model) handleSSE(evt transport.Event) {
net := viz.ApplyPeerEvent(m.network, p)
m.network = net
m.syncMeshFromNetwork()
if p.Status == "online" {
m.sound.PlayPeerOnline()
} else if p.Status == "offline" {
m.sound.PlayPeerOffline()
}
case "block":
b := data.(viz.BlockEvent)
m.store.Push(viz.FeedFromBlock(b))
@@ -596,6 +612,7 @@ func (m *Model) handleSSE(evt transport.Event) {
m.cursor = 0
}
m.peerMesh.TriggerBlock(m.meshPalette())
m.sound.PlayBlock()
case "moderation":
mod := data.(viz.ModerationEvent)
item := viz.FeedFromModeration(mod)
@@ -609,6 +626,7 @@ func (m *Model) handleSSE(evt transport.Event) {
if mod.TierAfter == "block" && mod.TierBefore != "block" {
m.peerMesh.TriggerBlock(m.meshPalette())
}
m.sound.PlayModeration()
case "stats":
s := data.(viz.Stats)
m.stats = &s
@@ -685,6 +703,12 @@ func (m *Model) openDetail() tea.Cmd {
return nil
}
func (m *Model) quit() tea.Cmd {
m.sse.Stop()
m.sound.Close()
return tea.Quit
}
func (m Model) toggleMouse() (Model, tea.Cmd) {
if m.mouseEnabled {
m.mouseEnabled = false