Enter on a site now opens the detail sidebar instead of a
full-page takeover. Enter on a group expands/collapses children
(same as space). Logs panel moves to a toggleable bottom strip
via the l key with j/k scrolling when focused.
SLA and History views now render inside the detail sidebar panel
instead of taking over the full screen. Press s/h to switch modes,
Esc to return to default detail view. Cursor movement resets to
default mode.
- Add detailMode state (detailDefault/detailSLA/detailHistory)
- SLA sidebar: period selector, uptime bars, daily breakdown
- History sidebar: sparkline density, transition list, stats
- Panel title shows "SLA · name" or "History · name" in sub-modes
- Period keys [1]-[4] work in SLA mode from dashboard state
Replace 3-tab navigation with a single monitoring dashboard.
- Replace tab bar with minimal status line (pulse + counts + version)
- Maintenance becomes a toggleable left sidebar (m key)
- Compact list: active (●) and scheduled (○) windows
- Enter opens full-screen detail with end/delete actions
- Settings becomes a centered floating overlay (S key)
- Alerts/Nodes/Users sub-sections with left/right navigation
- Full CRUD support inside overlay with returnState tracking
- Remove currentTab, switchTab, tab constants, tab click zones
- Replace deleteTab with deleteKind string for cleaner dispatch
- Separate settingsCursor/settingsOffset from main cursor
- Panel focus: maint → monitors → logs → detail
- Context-sensitive footer key hints per focused panel
- Hide nonsense latency/histogram data for group monitors in detail view
- Add missing status colors to fmtStatusWord (LATE, STALE, PENDING, etc)
- Wrap Maint and Settings tabs in titled panels to match Monitors tab
- Standardize headers: breadcrumb style (History > Name, SLA > Name)
- Standardize footer key order: actions first, [q/Esc] Back last
- Add # column to Nodes table for consistency with all other tables
- Use titleStyle for section headers (CONFIGURATION, DAILY BREAKDOWN)
- Fix panel focus not switching when clicking monitor rows from logs panel
Delete confirmation dialog used wrong magic numbers for entity kind
mapping — maintenance windows showed as "alert", settings deletions
showed as "monitor". Now uses tab/section constants to match correctly.
Also: consistent [q/Esc] Back across all views (detail inline, logs,
SLA, history headers), extract detailKeys(), remove dead maxSections
variable, add nodes empty state hint, use models.Status constants in
siteOrder, delete orphaned theme.png.
- Click Alerts/Nodes/Users sub-tabs in settings to switch sections
- Click STATUS/NAME/LATENCY column headers to sort (click again
to reverse direction)
- Logs panel show/hide: [l] toggles sidebar visibility, click
focused panel to hide. Monitors expand to full width when hidden.
- Logs full-screen: Enter when focused on logs opens scrollable
full-screen log view with severity tags, filter toggle [f],
and live updates
Replace inline numeric literals with named constants across 14 files:
server timeouts/rate limits, cluster thresholds/intervals, DB pool
sizes, alert/dial timeouts, TUI uptime thresholds, node status
thresholds, and state history limits.
Settings tab warn count now includes failed alert channels — alerts
where the last send failed (LastSendOK=false, LastSendAt not zero).
Shows as a red badge number alongside offline node count. Surfaces
broken alert channels without navigating to Settings.
Press < / > to cycle sort column (Status, Name, Latency). Press r to
reverse direction. Sorted column shows ▲/▼ arrow in the header.
Groups always float to top. Sort applies to ungrouped monitors and
group children independently. Default: Status descending (DOWN first).
Click any panel (Monitors, Logs, Detail) to focus it — accent border
follows focus. Mouse wheel scrolls the focused panel.
Keyboard: l toggles log panel focus. Arrow keys scroll logs when
focused, navigate monitors when not. Esc returns focus to monitors.
Log sidebar now supports scroll offset — tracks position across
renders without a viewport. Mouse wheel scrolls 3 lines, keyboard
scrolls 1.
All panels wrapped in titled rounded borders (╭─ Title ──╮). Focused
panel gets accent-colored border, unfocused panels get muted border.
- Monitors panel: titled "Monitors", focused when detail is closed
- Logs panel: titled "Logs", always unfocused (passive display)
- Detail panel: titled with monitor name, focused when open
Table's own RoundedBorder replaced with HiddenBorder — the titled
panel border provides the visual frame, table uses space-separated
columns internally. Consistent chrome across all panels.
Log sidebar wrapped in rounded border (no left/bottom edge — shared
with monitors table). Creates visual separation between panels.
Enter on a monitor opens the full-screen detail view (existing
stateDetail) for deep dive — history, SLA, probe results, connection
chain. i stays as inline detail toggle.
Footer key hints now context-sensitive: show h/s/Enter when detail
is open, show full keybindings when closed.
Log sidebar was rendering all lines regardless of table height. When
detail panel was open and table shrank, the sidebar stayed tall, pushing
the detail panel past MaxHeight (clipped to empty). Now sidebar accepts
a maxLines parameter capped to table row count.
Press i to toggle a compact detail panel below the monitors+logs
split. Shows status, latency, uptime, state changes, sparkline, and
key hints in ~6 lines. Auto-updates when cursor moves between
monitors. h/s/e keys work from the inline detail for history, SLA,
and edit. Escape closes the panel.
No more full-screen detail takeover for the common case. The old
stateDetail path remains for h/s sub-views which still go full-screen.
Table columns were computed from terminal width, causing row wrapping
when the monitors panel only gets 70% of the space. Introduced
contentWidth field set per-tab in viewDashboard. computeLayout,
isWide, and renderTable now use contentWidth for column visibility,
available space, and max table width calculations.
Columns gracefully hide (SSL, RETRIES, TYPE, UPTIME) when the panel
is narrower, matching the existing responsive breakpoint behavior.
Replace full viewLogsTab with compact sidebar renderer for the 70/30
monitors split. Single-char severity icons (▼▲◆●·), truncated messages,
no header chrome. Renders directly from engine logs without viewport.
Tab bar: Monitors | Maint | Settings (was 6 tabs).
Settings tab merges Alerts, Nodes, Users as sub-sections with
left/right arrow navigation. Each section keeps its own cursor,
keybindings, and CRUD operations.
Monitors tab now shows a log sidebar at >= 120 cols (70/30 split).
Under 120 cols, monitors render full-width without logs.
- Introduced tab constants (tabMonitors, tabMaint, tabSettings)
- Introduced section constants (sectionAlerts, sectionNodes, sectionUsers)
- Removed stateLogs and stateUsers states
- All magic tab numbers replaced with named constants
Full-width horizontal rules above and below the content area. Tab bar
sits above the top divider, status/keys bar sits below the bottom
divider. Creates three clear visual zones: navigation, content, status.
Revert upper-third centering — inconsistent start positions across tabs
felt jumpy. Back to standard top-align with consistent table placement.
Demo GIF now pauses on Nodes tab (cluster view is a selling point) and
skips Maint/Users quickly instead of sprinting through all three.
Tables on tabs with few rows (Alerts, Nodes, Maint, Users) now sit in
the upper third of the viewport instead of flush against the tab bar.
Dense tabs like Monitors and Logs fill naturally and are unaffected.
Strip Go module pseudo-version suffix (timestamp+hash+dirty) from the
footer version string — shows "v0.1.0" instead of the full build
metadata. Rename "Sites" tab and breadcrumbs to "Monitors" for
consistency with README, CLI help, and user-facing docs.
1. UpdateSite handles token-read Scan error instead of ignoring it.
sql.ErrNoRows (nonexistent site) passes through; real DB errors
surface.
2. RunCheck allowPrivate changed from variadic to real bool param.
Dead maxRequestBody duplicate removed from sqlstore.go.
3. Footer help bar documents [Space] for group collapse.
4. adjustCursor unified with clampCursor — one clamping path
instead of two with different semantics.
5. Compose cluster/probe example files annotate hardcoded secrets
with "EXAMPLE ONLY — rotate before use".
6. huhForm.WithHeight moved from View() to handleResize — no longer
mutates form state during render.
7. maxTableRows recalculated on filter enter/exit via recalcLayout()
— was only recalculated on resize, causing off-by-one when the
filter bar appeared/disappeared.
Replace ~150 bare status string comparisons with typed models.Status
constants (StatusUp, StatusDown, StatusPending, StatusLate, StatusStale,
StatusSSLExp). Single IsBroken() method replaces the duplicated
isBroken lambda in monitor.go and isDown function in sla.go.
Adding a new status value (e.g. DEGRADED) now requires one constant
definition instead of grep-and-pray across 16 files.
CheckResult.Status stays string — the checker is the boundary between
raw protocol results and typed status. Cast happens at the edge in
handleStatusChange.
applyTheme mutated ~18 package-global lipgloss styles while every SSH
session's tea.Program read them concurrently from its own goroutine.
Pressing T or opening a new connection raced other sessions' View and
bled themes across users.
Styles now live in an immutable per-Model struct built by newStyles;
free formatter helpers that consumed the globals became Model methods.
Click any sparkline character to see data point details — approximate
time, latency, and up/down status. Esc dismisses tooltip without
leaving detail view. Uses existing BubbleZone infrastructure with
zone-relative coordinate math for index resolution.
Sites table with many rows exceeded the fixed content height,
pushing footer down. MaxHeight now clips content that overflows
while Height still pads shorter content upward.
Each tab returned different leading newlines (Sites/tables: 1,
Logs: 3, empty states: varies). TrimSpace content before layout
so JoinVertical controls all spacing. Remove leading \n from
footer since JoinVertical handles gaps.
Replace string concatenation layout with lipgloss.JoinVertical
and fixed-height content area. Footer now stays at the same
vertical position regardless of tab content height. Uses
lipgloss.Height() to dynamically measure header/footer and
fill remaining space.
- Extract divider() and emptyState() helpers to format.go
- All empty states now use bordered box with accent color
- Detail and alert detail panels get header/section dividers
- SLA label width 14→16 to match detail/alert panels
- Logs key hints moved from content to dashboard footer
- History/SLA panels use shared divider helper
- Show version in dashboard footer (wired from goreleaser ldflags)
- Cap name column at 35, raise sparkline minimum to 15 chars
- Preserve zebra background on group rows (was lost by style override)
- Group sparkline uses bullet • instead of heavy circle ●
Full-screen SLA report accessible via [s] from detail panel.
Computes uptime%, downtime, outage count, longest outage, MTTR,
and MTBF from state_changes table. Includes daily breakdown with
bar chart, switchable time periods (24h/7d/30d/90d), and
scrollable viewport. LATE/STALE treated as UP for SLA purposes.
Full-screen scrollable history view accessible via [h] from detail
panel. Shows all state transitions with computed outage durations,
event density sparkline for flapping detection, and summary stats.
- Detail panel STATE CHANGES now shows outage duration per recovery
- Event density sparkline highlights flapping periods
- Summary footer: event count, outage count, avg outage duration
- Vim-style navigation (j/k/g/G) + mouse scroll in history view
tui.go (1032→164) and tab_sites.go (993→482) violated "small functions"
and "testable in isolation" standards. Extracted 6 new files by concern:
- format.go: pure formatting functions (fmtLatency, fmtUptime, etc.)
- sparkline.go: sparkline rendering (latency, heartbeat, group)
- update.go: Update method decomposed into 15 named handlers
- view_dashboard.go: View, dashboard composition, tab bar, footer
- view_detail.go: site detail panel
- data.go: data refresh with extracted sortSitesForDisplay/filterSites
Added 17 unit tests for the newly-testable pure functions covering
format, sparkline, sort ordering, and filter logic. No behavioral
changes — strict move-and-extract refactor.