Conversation
Replace the entire UI layer with Bubbletea v2 (charm.land/bubbletea/v2), Bubbles v2, and Lipgloss v2. This is a complete rewrite of the TUI framework while preserving all Docker functionality and key bindings. Key changes: - Replace tcell/termui render loop with Bubbletea's Elm architecture - New top-level model (app/model.go) with Init/Update/View lifecycle - Shared TableModel component for all list views (containers, images, networks, volumes, nodes, services, stacks) - Overlay system: less viewer, prompt, input prompt, container menu - Docker event throttling with 250ms debounce - Monitor view with channel-based live stats - All swarm views (nodes, services, stacks, tasks) - Disk usage view with prune support - 52 new tests across app/, appui/, appui/swarm/ Removed old dependencies: gdamore/tcell, gizak/termui, nsf/termbox-go, olekukonko/tablewriter, mitchellh/go-wordwrap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove ui/color.go (550 lines of unused Color type and constants) - Remove ui/screen_dimension.go (unused Dimensions type) - Remove appui/appui.go (unused invalidRow error type) - Remove unused overlayStream constant from app/model.go - Remove unused constants from appui/ui.go (DownArrowLength, RightArrow, CalcItemWidth) - Update CLAUDE.md to reflect Bubbletea v2 architecture Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The viewport was rendering empty because SetSize only set a lipgloss Style on the viewport instead of calling SetWidth/SetHeight. The bubbles v2 viewport uses internal width/height fields to determine visible content, not the Style dimensions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Set viewport dimensions before content so soft-wrap calculates correctly - Re-apply content when SetSize is called to recalculate wrapping - Enable AltScreen for proper full-screen terminal redraws - Add g/G keybindings for go-to-top/bottom in less viewer - Forward mouse wheel events to less overlay for scroll support - Add TestModel_LessScrolling test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Forward WindowSizeMsg to overlay models (less, prompt, input, menu) and message bar so they adapt when terminal is resized - Remove "f" from viewport PageDown binding to avoid conflict with less viewer's follow toggle (space/pgdown still work for page down) - Add "f" key (follow mode) to help text for logs/inspect buffers - Fix "s" key help text: says "snapshot" instead of "live stream" - Add TestModel_ResizeWithOverlay test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unimplemented [s]:Set refresh rate from monitor help text - Remove unused ListItem field from Theme struct - Remove BUBBLETEA_MIGRATION_SPEC.md (migration is complete) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix darkgrey/grey colors: 232/233 were nearly black on dark backgrounds, changed to 242/244 for readable medium grey - Fix markup style stacking: <b><darkgrey> now produces bold+grey instead of replacing bold with grey (use lipgloss Inherit) - Add background color to footer bar using theme Footer color - Add markup rendering tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Color-code container rows: green for running, red for stopped (add StyledRow interface for row-level styling) - Expand header to show all Docker info: hostname, swarm status, cert path, API version, CPU, memory, OS/arch/kernel - Add column spacing in table rendering (PaddingRight) - Add CursorLineBg to Black256 theme (was missing) - Constrain message bar width to terminal width Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Set tea.View.BackgroundColor to DryTheme.Bg (Color234) for uniform dark grey terminal background matching the old screen fill behavior - Fix table row rendering: per-cell ANSI styling for indicator rows to avoid nested reset sequences killing row colors; full-width padding for selected row highlight and all data rows - Add StyledIndicator interface and RunningIndicatorStyle/StoppedIndicatorStyle so container status indicator (■) uses green/red while row text uses rose(181)/grey(244) - Column-aligned header grid using fixed-width cells with ansi.Truncate - Cyan (DryTheme.Key) widget headers across all views - White (DryTheme.Fg) table column headers instead of blue - Fix layout math: MainScreenHeaderSize 5→4 to match actual header line count; message bar always renders (empty line when no message) for consistent height; table pads with empty lines to fill allocated height so footer stays at screen bottom - Fix column width calculation: no double-counting of spacing, last proportional column gets remainder to avoid rounding gaps Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add RenderMarkupWithBase() that preserves a base style (including background) through tag resets, fixing the ANSI nesting bug where inner \e[m resets killed the footer's blue background mid-line - Footer now uses RenderMarkupWithBase with the footer background as base style, ensuring continuous Color25 blue across all text segments - Add PadLine() helper that pads with styled spaces to extend background - Render help text markup before passing to less viewer so tags like <white>, <yellow>, <blue> display as colored text instead of raw tags Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the old 256-color themes (Dark256/Black256) with a new Crush-inspired palette using hex colors from charmbracelet's CharmTone system. Restyle footer key shortcuts to a cleaner format with cyan-green keys, muted descriptions, and dot separators. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rework the theme system so all colors flow through DryTheme: - Expand ui.Theme with semantic fields (FgMuted, FgSubtle, Primary, Secondary, Tertiary, Success, Error, Warning, Border) - Make CharmTone palette vars unexported in appui/theme.go - Replace all direct palette references with DryTheme.X fields - Add rounded border to container menu overlay - Add progress bars to disk usage view - Wrap loading screen in a rounded border box Fix F1 sorting: TableModel.NextSort() now actually sorts rows by the active column using sort.SliceStable, making sorting work immediately in all views without requiring a data reload. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add footer key mappings for task views (ServiceTasks, Tasks, StackTasks) - Add F1 sort handler to MonitorModel and advertise it in footer - Add F5 handler to TasksModel for consistency with other swarm views Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace custom footer markup rendering with bubbles/help.Model and per-view KeyMap structs. Replace custom renderBar() in disk usage with bubbles/progress.ViewAs(). Rewrite TableModel as a wrapper around bubbles/table while preserving the same public API so all consumer models need zero changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lipgloss v2 uses full SGR reset (\x1b[m) at the end of Render(), which breaks outer background colors when ANSI is nested. This caused two visual bugs: 1. Footer: bubbles/help inserts unstyled spaces between key and desc text. Replaced help.Model.View() with manual rendering that styles every character (including spaces) with the footer background. 2. Selected row: pre-rendered ANSI in table cells (running/stopped foreground colors) reset the Selected style's background. Removed all ANSI pre-rendering from table cells — toBubblesRows() now passes plain text so the bubbles table's Selected style applies cleanly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The prompt and input prompt overlays were appended below the full screen (main + footer), pushing them past the terminal viewport. Users could not see confirmation prompts, making commands like "rm unused" appear broken. Fix by replacing the footer with the prompt view instead of appending below it. Also complete all footer key maps with missing action keys: - Containers: logs, stats, rm, rm stopped, kill, restart, stop - Images: enter (inspect), % (filter) - Networks: % (filter) - Volumes: % (filter) - Nodes: i (inspect), % (filter) - Services: enter (tasks), i (inspect) - Stacks: enter (tasks) Remove dead containerRow.running field, fix stale help.KeyMap comment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use ▶ (green) for running and ■ (red) for stopped containers. ColorFg helper uses targeted ANSI (SGR 38/39) that only resets the foreground, preserving the selected row background highlight. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oter Replace plain text widget headers with full-width accent bars featuring per-view icons, styled title/count, and filter info on a charcoal background. Change selected row color from bright purple to dark teal (#1E3D3D). Hide swarm navigation keys (5/6/7) from footer and skip swarm API calls when Docker Swarm is not active. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace CrushBlack with CrushLight (mid-tone warm background, near-black text, darkened accents). Add RefreshStyles to TableModel and all sub-models so Ctrl+0 theme rotation updates inner table styles. Use ColorFg with foreground-only ANSI resets for cell text to preserve uniform selected row background. Refine dark theme: warm gold Key (#E8A848), dark teal-blue selection (#182838), themed container indicators (gold ▶ / dim ■). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace electric purple/magenta with copper (#D08850) primary and warm rose (#E07890) secondary to create a cohesive warm-earthy palette alongside the gold key and teal-blue selection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… stats Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Ports dry’s TUI to Bubbletea/Bubbles/Lipgloss v2, removing the legacy tcell/termui rendering stack and adopting an Elm-style architecture with view sub-models and shared table behaviors.
Changes:
- Replaced the old termui/tcell UI widgets + event loop with Bubbletea v2
tea.Programand sub-models per view. - Introduced new list/table-based models (containers/images/networks/volumes + swarm views) with filter/sort/navigation, plus overlay models (prompt, input prompt, menu).
- Updated theming/styling to Lipgloss v2 and removed golden/termui-based rendering tests and helpers.
Reviewed changes
Copilot reviewed 140 out of 175 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/list_test.go | Removed legacy termui list unit test. |
| ui/list.go | Removed legacy termui list factory helper. |
| ui/less_test.go | Removed legacy less-scrolling tests tied to old screen implementation. |
| ui/key.go | Removed unused legacy Key type relying on tcell. |
| ui/focus.go | Removed legacy focus interface based on tcell events. |
| ui/expiring_message.go | Removed legacy expiring message widget tied to ActiveScreen. |
| ui/events.go | Removed legacy tcell event source abstraction. |
| ui/cursor_test.go | Removed tests for legacy cursor implementation. |
| ui/cursor.go | Removed legacy cursor implementation. |
| ui/colorize_test.go | Removed tests for markup-tag based colorizers. |
| ui/colorize.go | Replaced markup-tag colorizers with Lipgloss v2 style rendering. |
| main.go | Switched entrypoint to Bubbletea v2 program (tea.NewProgram) and removed loading/whale path. |
| go.mod | Updated Go/tooling + dependencies to Bubbletea/Bubbles/Lipgloss v2 and removed termui/tcell deps. |
| appui/volumes_test.go | Removed termui golden tests for volumes widget. |
| appui/volumes_model.go | Added Bubbletea sub-model for volumes list using shared TableModel and filter input. |
| appui/volume_row.go | Removed legacy termui-based VolumeRow widget. |
| appui/ui_events.go | Removed legacy widget event/filter/sort interfaces tied to termui. |
| appui/ui.go | Updated UI constants (header sizing) and removed legacy width calc helper. |
| appui/top.go | Removed legacy docker top termui renderer. |
| appui/theme.go | Replaced termui color themes with Lipgloss-based ui.Theme palettes. |
| appui/testing.go | Removed legacy golden-test helpers and -update flag. |
| appui/testdata/TestVolumesWidget_sort_volumes.golden | Removed legacy volumes golden output. |
| appui/testdata/TestVolumesWidget_show_last_4_volumes.golden | Removed legacy volumes golden output. |
| appui/testdata/TestVolumesWidget_show_first_4_volumes.golden | Removed legacy volumes golden output. |
| appui/testdata/TestVolumesWidget_mounted_widget_two_volumes.golden | Removed legacy volumes golden output. |
| appui/testdata/TestVolumesWidget_mounted_widget_no_volumes.golden | Removed legacy volumes golden output. |
| appui/testdata/TestVolumesWidget_filter_volumes.golden | Removed legacy volumes golden output. |
| appui/testdata/TestVolumesWidget_double_sort_volumes.golden | Removed legacy volumes golden output. |
| appui/testdata/DiskUsageTest_noPruneReport.golden | Removed legacy disk usage golden output. |
| appui/testdata/DiskUsageTest.golden | Removed legacy disk usage golden output. |
| appui/swarm/testdata/service_info.golden | Removed legacy service info golden output. |
| appui/swarm/tasks_model.go | Added Bubbletea sub-model for swarm tasks list using shared TableModel. |
| appui/swarm/task_row_test.go | Removed legacy termui-based task row test. |
| appui/swarm/task_row.go | Removed legacy termui-based task row widget. |
| appui/swarm/swarm_test.go | Added new Bubbletea-oriented tests for swarm models. |
| appui/swarm/stacks_model.go | Added Bubbletea sub-model for stacks list with filter and table. |
| appui/swarm/stack_tasks.go | Removed legacy termui-based stack tasks widget. |
| appui/swarm/stack_row_test.go | Removed legacy stack row widget test. |
| appui/swarm/stack_row.go | Removed legacy stack row widget. |
| appui/swarm/services_model.go | Added Bubbletea sub-model for services list with filter and table. |
| appui/swarm/service_tasks.go | Removed legacy termui-based service tasks widget. |
| appui/swarm/service_row.go | Removed legacy service row widget. |
| appui/swarm/service_info_test.go | Removed legacy service info golden test. |
| appui/swarm/service_info.go | Removed legacy service info widget/renderer. |
| appui/swarm/nodes_test.go | Removed legacy nodes widget tests. |
| appui/swarm/nodes_model.go | Added Bubbletea sub-model for nodes list with filter and table. |
| appui/swarm/node_tasks.go | Removed legacy termui-based node tasks widget. |
| appui/swarm/node_row_test.go | Removed legacy node row widget test. |
| appui/swarm/node_row.go | Removed legacy node row widget. |
| appui/styles.go | Added Lipgloss-derived styles + widget header rendering + targeted foreground coloring helper. |
| appui/stream.go | Removed legacy stream “less” viewer integration. |
| appui/stats_row_test.go | Removed legacy container stats row tests. |
| appui/screen.go | Removed legacy termui screen abstractions. |
| appui/run_image.go | Removed legacy run-image input widget. |
| appui/row_filter_test.go | Removed legacy row filter tests tied to termui columns. |
| appui/row_filter.go | Removed legacy row filtering helpers tied to termui columns. |
| appui/row.go | Removed legacy base row type for termui widgets. |
| appui/prompt_model.go | Added Bubbletea prompt overlay model (y/N). |
| appui/prompt.go | Removed legacy prompt widget (termui textinput). |
| appui/networks_model.go | Added Bubbletea sub-model for networks list with filter and table. |
| appui/network_row.go | Removed legacy network row widget. |
| appui/monitor_test.go | Removed legacy monitor widget test. |
| appui/monitor_header.go | Removed legacy monitor table header builder. |
| appui/message_bar.go | Added model for timed status messages (auto-expiring). |
| appui/less.go | Removed legacy less viewer helper tied to tcell events. |
| appui/inspect.go | Removed legacy JSON inspect renderer. |
| appui/input_prompt_model.go | Added Bubbletea input prompt overlay model (text input). |
| appui/input.go | Removed legacy no-op input helper. |
| appui/images_test.go | Removed legacy images widget tests (scrolling/visibility). |
| appui/images_model.go | Added Bubbletea sub-model for images list with filter and table. |
| appui/image_row.go | Removed legacy image row widget. |
| appui/image_history.go | Removed legacy image history renderer. |
| appui/header_test.go | Removed legacy widget header test. |
| appui/header_model.go | Added Bubbletea header model rendering Docker daemon info and separator. |
| appui/header.go | Removed legacy widget header implementation. |
| appui/filter_input.go | Added Bubbletea filter input model using Bubbles v2 textinput. |
| appui/events.go | Removed legacy docker events renderer. |
| appui/docker_info_test.go | Removed legacy docker info golden tests. |
| appui/docker_info.go | Removed legacy docker info widget. |
| appui/disk_usage_test.go | Removed legacy disk usage renderer tests. |
| appui/disk_usage_model.go | Added Bubbletea disk usage model with progress bars. |
| appui/containers_model.go | Added Bubbletea sub-model for containers list with sort/show-all/filter and table. |
| appui/container_row.go | Removed legacy container row widget. |
| appui/container_menu_test.go | Removed legacy container menu widget tests. |
| appui/container_menu_model.go | Added Bubbletea container command menu overlay model. |
| appui/container_menu.go | Removed legacy container menu widget. |
| appui/container_details_test.go | Removed legacy container details widget test. |
| appui/container_details.go | Removed legacy container details widget. |
| appui/container.go | Removed legacy container info renderer. |
| appui/appui.go | Removed legacy invalidRow error helper. |
| app/widget_registry.go | Removed legacy widget registry (termui widgets lifecycle). |
| app/volume_events.go | Removed legacy volumes event handler (tcell-based). |
| app/view.go | Updated view mode enum/comments for new architecture. |
| app/stacktasks_events.go | Removed legacy stack tasks event handler. |
| app/stack_events.go | Removed legacy stacks event handler. |
| app/servicetasks_events.go | Removed legacy service tasks event handler. |
| app/service_events.go | Removed legacy services event handler. |
| app/render.go | Removed legacy termui-based renderer + footer rendering. |
| app/nodetasks_events.go | Removed legacy node tasks event handler. |
| app/node_events.go | Removed legacy nodes event handler. |
| app/network_events.go | Removed legacy networks event handler. |
| app/monitor_events.go | Removed legacy monitor event handler. |
| app/misc_test.go | Removed legacy unit test for logs duration curation helper. |
| app/misc.go | Removed legacy misc helpers (events, inspect, less, logs prompt). |
| app/messages.go | Added Bubbletea message types for docker data, refresh, overlays, status messages. |
| app/loop.go | Removed legacy render loop/event loop (tcell + termui). |
| app/help_texts.go | Refactored help text generation into Help() function and updated keybind descriptions. |
| app/filter_event.go | Removed legacy filter prompt flow. |
| app/events.go | Removed legacy event handling/router and forwarder abstractions. |
| app/dry.go | Removed legacy Dry app struct tied to old UI/event loop. |
| app/df_events.go | Removed legacy disk usage event handler. |
| CLAUDE.md | Added repository guidance for Claude Code (architecture, commands, conventions). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| // SetWidth sets the input width. | ||
| func (m *FilterInputModel) SetWidth(w int) { | ||
| m.width = w |
There was a problem hiding this comment.
SetWidth only updates the outer lipgloss container width, but the underlying textinput.Model keeps its own width for rendering/editing. This can lead to truncation/overflow or awkward cursor behavior. Consider calling m.input.SetWidth(w) (or w - lipgloss.Width(prompt) depending on desired UX) inside SetWidth.
| m.width = w | |
| m.width = w | |
| inputWidth := w - lipgloss.Width(m.input.Prompt) | |
| if inputWidth < 0 { | |
| inputWidth = 0 | |
| } | |
| m.input.SetWidth(inputWidth) |
| func (m NodesModel) widgetHeader() string { | ||
| return appui.RenderWidgetHeader(appui.WidgetHeaderOpts{ | ||
| Icon: "🖥️", | ||
| Title: "Nodes", | ||
| Total: m.table.TotalRowCount(), | ||
| Filtered: m.table.TotalRowCount(), | ||
| Width: m.table.Width(), | ||
| Accent: appui.DryTheme.Success, | ||
| }) | ||
| } |
There was a problem hiding this comment.
When filtering is active, the header won’t reflect it: Filtered is set to TotalRowCount() and Filter isn’t passed. Align this with other models (e.g., containers/images) by using Filtered: m.table.RowCount() and Filter: m.table.FilterText() so the UI correctly displays filtered counts and active filter text.
| func (m ServicesModel) widgetHeader() string { | ||
| return appui.RenderWidgetHeader(appui.WidgetHeaderOpts{ | ||
| Icon: "⚙", | ||
| Title: "Services", | ||
| Total: m.table.TotalRowCount(), | ||
| Filtered: m.table.TotalRowCount(), | ||
| Width: m.table.Width(), | ||
| Accent: appui.DryTheme.Primary, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Same issue as NodesModel: Filtered should be the filtered row count (likely m.table.RowCount()), and the active filter text should be wired into the header (Filter: m.table.FilterText()). Otherwise the header won’t show filter state/count deltas.
| func (m StacksModel) widgetHeader() string { | ||
| return appui.RenderWidgetHeader(appui.WidgetHeaderOpts{ | ||
| Icon: "📚", | ||
| Title: "Stacks", | ||
| Total: m.table.TotalRowCount(), | ||
| Filtered: m.table.TotalRowCount(), | ||
| Width: m.table.Width(), | ||
| Accent: appui.DryTheme.Error, | ||
| }) | ||
| } |
There was a problem hiding this comment.
StacksModel supports filtering (FilterInputModel + SetFilter), but the header always shows Total → Total and never shows the filter text. Use Filtered: m.table.RowCount() and provide Filter: m.table.FilterText().
| // InitStyles rebuilds all derived styles from DryTheme. | ||
| // Call after rotating the color theme. | ||
| func InitStyles() { |
There was a problem hiding this comment.
Styles are derived from the global DryTheme, but there’s no guarantee they’ll be regenerated when DryTheme is rotated (the existing RotateColorTheme only swaps the pointer per the snippet). Consider updating RotateColorTheme() to call InitStyles() (or returning the new theme and having the app explicitly call InitStyles) to keep the UI consistent after theme changes.
| var ( | ||
| pepper = lipgloss.Color("#201F26") | ||
| charcoal = lipgloss.Color("#3A3943") | ||
| charple = lipgloss.Color("#D08850") |
There was a problem hiding this comment.
The name charple strongly implies a purple hue, but #D08850 is an orange/brown tone. Either adjust the hex value to match the intended palette color, or rename the variable so it reflects the actual color to avoid confusion when theming/tweaking.
| charple = lipgloss.Color("#D08850") | |
| charple = lipgloss.Color("#A550DF") |
| } | ||
|
|
||
| // SetMessage sets a status message that auto-clears after the given duration. | ||
| func (m *MessageBarModel) SetMessage(text string, duration time.Duration) { | ||
| m.text = text | ||
| m.expiry = time.Now().Add(duration) | ||
| } | ||
|
|
||
| // Message returns the active message text, or "" if expired/unset. | ||
| func (m MessageBarModel) Message() string { | ||
| if m.text == "" || time.Now().After(m.expiry) { | ||
| return "" | ||
| } |
There was a problem hiding this comment.
This introduces time-based behavior (expiry) but there’s no accompanying unit test. Adding tests for (1) message visible before expiry, (2) cleared after expiry, and (3) empty/unset behavior will help prevent regressions; if you want deterministic tests, consider injecting a clock (e.g., now func() time.Time) into the model.
| } | |
| // SetMessage sets a status message that auto-clears after the given duration. | |
| func (m *MessageBarModel) SetMessage(text string, duration time.Duration) { | |
| m.text = text | |
| m.expiry = time.Now().Add(duration) | |
| } | |
| // Message returns the active message text, or "" if expired/unset. | |
| func (m MessageBarModel) Message() string { | |
| if m.text == "" || time.Now().After(m.expiry) { | |
| return "" | |
| } | |
| now func() time.Time | |
| } | |
| // SetMessage sets a status message that auto-clears after the given duration. | |
| func (m *MessageBarModel) SetMessage(text string, duration time.Duration) { | |
| m.text = text | |
| now := time.Now | |
| if m.now != nil { | |
| now = m.now | |
| } | |
| m.expiry = now().Add(duration) | |
| } | |
| // Message returns the active message text, or "" if expired/unset. | |
| func (m MessageBarModel) Message() string { | |
| if m.text == "" { | |
| return "" | |
| } | |
| now := time.Now | |
| if m.now != nil { | |
| now = m.now | |
| } | |
| if now().After(m.expiry) { | |
| return "" | |
| } |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix race condition in RemoveDanglingImages (local err variable), split shared context for local/swarm event goroutines, signal eventsDone and stop monitor on quit, guard nil Replicas pointer in services view, guard nil ContainerSpec in TaskStringer, and sync sort mode with table column via new SetSortField method. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…annel Replace the internal done channel and coordinator goroutine in DockerDaemon.Events() with a caller-owned context.Context for cancellation. This simplifies shutdown logic in the app model (eventsCancel func vs channel send) and is more idiomatic Go. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix timestamp: use time.Unix(0, e.TimeNano) since TimeNano is a full nanosecond epoch, not a sub-second offset - Fix EventLog.Peek() panic on empty log (index at -1) - Guard against nil EventLog in showDockerEventsCmd - Remove unused error return from EventCallback signature - Fall back to Actor.Attributes["name"] when Actor.ID is empty - Fix flaky TestStreamEvents_NoPanicAfterChannelDrain (use unbuffered channel for deterministic select behavior) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The events view (F9) now shows new events in real time as they arrive, instead of requiring the user to close and reopen it. Uses the existing LessModel.AppendContent() and following mode pattern already used for streaming container logs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HIGH: Events channel now closes on daemon disconnect (innerCtx pattern); nil UsageData guard in disk usage view. MEDIUM: Filter input keys no longer intercepted by global handlers; deterministic monitor row ordering; swarm headers show correct filtered count; Prune() uses timeout; MessageBar auto-clears via tea.Tick. LOW: Remove 4 dead message types; O(n) AppendContent; thread-safe EventLog.Count(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Monitor goroutine leak: Start() now runs directly in switchView (on the returned copy) instead of inside loadViewData (nested copy whose cancel funcs were discarded). Goroutines no longer accumulate on repeated enter/leave of Monitor view. - Container store add() no-op: fix slice removal from c.c[pos:] to c.c[pos+1:] so updated containers replace the old entry in both map and slice. Add test for replacement behavior. - StackRemove timeout: move context.WithTimeout to just before the remove operations so listing calls don't consume the 10s budget. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove Config.dockerEnv(), connection headers var, ContainerFormatter. fullHeader(), defaultQuietFormat const, unused lines test const, and commented-out ansiParser.applyEscape(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove unnecessary fmt.Sprintf for plain string, suppress unused value assignments in tests with blank identifier. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove redundant embedded Annotations field from swarm Spec selectors and lowercase error string per Go conventions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix impossible len() < 0 check (SA4024) - Lowercase error strings (ST1005) - Replace deprecated certPool.Subjects() (SA1019) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the entire tcell/gizak-termui rendering stack with Bubbletea v2, Bubbles v2, and Lipgloss v2, adopting the Elm architecture with a single top-level model that delegates to sub-models for each view. A shared TableModel provides cursor navigation, column sorting, text filtering, and scroll windowing across all list views. An overlay system handles the less viewer, confirmation prompts, text input, and menu. Docker events stream in with a 250ms debounce for live refresh. The old ui/termui/ package and legacy event-loop code have been removed entirely, going from ~16k lines down to ~6.5k.