feat(ui): adopt Charm v2 components for TUI primitives#110
feat(ui): adopt Charm v2 components for TUI primitives#110flexiondotorg wants to merge 5 commits into
Conversation
Replace hand-rolled spinner, progress bars, metric table, and level meter with first-party Charm v2 components (bubbles/spinner, bubbles/progress, lipgloss/table, harmonica springs). Centralise colour palette to cli/styles.go with adaptive downsampling via lipgloss v2. CLI help and version now honour NO_COLOR and COLORPROFILE. Spinner, progress bars, and eased meter work as intended; progress bar now fits terminal cleanly in brand red. All tests pass. Metric table retains all formatters and edge-case coverage; 50+ lines of manual column-width maths eliminated. Signed-off-by: Martin Wimpress <code@wimpress.io>
There was a problem hiding this comment.
No issues found across 13 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Auto-approved: The PR replaces hand-rolled UI components with well-tested Charm v2 libraries for spinner, progress bars, metric table, and level meter, centralizes the colour palette with proper downsampling for NO_COLOR support, and includes thorough test coverage confirming correct behaviour and no...
Re-trigger cubic
- Add cyan→violet gradient via Charm WithColors + WithScaled - Apply harmonica spring easing for smooth per-file animation - Constrain bar width to 40 cells for alignment with VU meter - Add accent colours to centralised palette - Lock behaviour in progress_bar_test.go Signed-off-by: Martin Wimpress <code@wimpress.io>
There was a problem hiding this comment.
0 issues found across 5 files (changes from recent commits).
Auto-approved: This PR replaces hand-rolled TUI components (spinner, progress bar, meter, table) with well-tested Charm v2 libraries and centralises the colour palette; changes are confined to presentation logic, add thorough tests (downsampling, gradients, message routing), and pose no risk to core business...
Re-trigger cubic
…ation - Replace hard green/orange/red zones with gradient ramp (green → yellow → orange → red) - Use lipgloss.Blend1D for per-cell CIELAB colour interpolation - Position threshold stops to preserve prior dB feel (green-dominant low range, warm colours compressed into hot end) - Add ColorYellow to centralised palette - Assert gradient quality in progress_bar_test.go (>3 distinct colours, vivid endpoints and midpoint) Signed-off-by: Martin Wimpress <code@wimpress.io>
There was a problem hiding this comment.
0 issues found across 3 files (changes from recent commits).
Requires human review: This PR refactors core UI and CLI components—spinner, progress bars, table rendering, color system, and output formatting—with 1270 lines changed across multiple critical files, which carries a high risk of breaking CLI output, TUI display, or color handling; the blast radius and impact are too...
Re-trigger cubic
…der and orange peak - Lower VU meter display and audio level reporting from -60 dB floor to -70 dB - Add meterLevelFloorDB constant in processor to mirror UI meterFloorDB, prevent drift - Change panel border from sky blue, peak-marker pulse to shades of orange - Update test assertions for new level floor and marker colours Signed-off-by: Martin Wimpress <code@wimpress.io>
There was a problem hiding this comment.
2 issues found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/ui/progress_bar_test.go">
<violation number="1" location="internal/ui/progress_bar_test.go:190">
P2: The test can pass even when no meter bar line is rendered, masking regressions.</violation>
</file>
<file name="internal/ui/views.go">
<violation number="1" location="internal/ui/views.go:226">
P3: Peak marker has an off-by-one alignment bug at 0 dB and can render outside the meter width.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| if strings.ContainsRune(barLine, '|') { | ||
| t.Errorf("bar line still contains in-bar peak glyph '|':\n%q", barLine) | ||
| } |
There was a problem hiding this comment.
P2: The test can pass even when no meter bar line is rendered, masking regressions.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/ui/progress_bar_test.go, line 190:
<comment>The test can pass even when no meter bar line is rendered, masking regressions.</comment>
<file context>
@@ -142,6 +174,94 @@ func TestMeterIsGradient(t *testing.T) {
+ break
+ }
+ }
+ if strings.ContainsRune(barLine, '|') {
+ t.Errorf("bar line still contains in-bar peak glyph '|':\n%q", barLine)
+ }
</file context>
| if strings.ContainsRune(barLine, '|') { | |
| t.Errorf("bar line still contains in-bar peak glyph '|':\n%q", barLine) | |
| } | |
| if barLine == "" { | |
| t.Fatalf("no meter bar line found in output:\n%q", bar) | |
| } | |
| if strings.ContainsRune(barLine, '|') { | |
| t.Errorf("bar line still contains in-bar peak glyph '|':\n%q", barLine) | |
| } |
| // (peak still at the silence floor), so no stray triangle sits at column 0. | ||
| if peakLevel > minDB { | ||
| b.WriteByte('\n') | ||
| b.WriteString(strings.Repeat(" ", peakPos)) |
There was a problem hiding this comment.
P3: Peak marker has an off-by-one alignment bug at 0 dB and can render outside the meter width.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/ui/views.go, line 226:
<comment>Peak marker has an off-by-one alignment bug at 0 dB and can render outside the meter width.</comment>
<file context>
@@ -221,9 +218,43 @@ func renderAudioLevelMeter(currentLevel, peakLevel float64) string {
+ // (peak still at the silence floor), so no stray triangle sits at column 0.
+ if peakLevel > minDB {
+ b.WriteByte('\n')
+ b.WriteString(strings.Repeat(" ", peakPos))
+ b.WriteString(lipgloss.NewStyle().Foreground(peakMarkerColor(elapsed)).Render("▲"))
+ }
</file context>
| b.WriteString(strings.Repeat(" ", peakPos)) | |
| b.WriteString(strings.Repeat(" ", max(0, min(peakPos, width-1)))) |
…ector Replace Time/ETA line with mini timeline + realtime speed badge, showing progress visually while computing on-the-fly processing speed (×). Move peak dB value from header to elbow connector tethering it to the peak marker, strengthening visual hierarchy. Plumb audio Duration through processor to UI (ProgressUpdate → ProgressMsg → FileProgress) to enable realtime calculation. - Add Duration field to ProgressUpdate, ProgressMsg, FileProgress - Capture Duration from audio.Metadata in analyzer Pass 1; carry through all processor passes - Redesign Time block: ⏱ HH:MM ▰▰▱▱▱▱▱▱ HH:MM · ⚡ N.N× (with guards for stale data) - Reposition Peak dB as elbow connector (└ value) beneath the ▲ marker, both pulsing orange - Handle right-edge overflow: flip connector for peak positions > 34 - Add focused tests for timeline fill, clock projection, realtime badge guards, elbow alignment Signed-off-by: Martin Wimpress <code@wimpress.io>
There was a problem hiding this comment.
2 issues found across 9 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/ui/views.go">
<violation number="1" location="internal/ui/views.go:282">
P2: Peak label alignment uses byte length (`len`) instead of display width, which misplaces the new elbow/label layout with Unicode text.</violation>
</file>
<file name="internal/ui/progress_bar_test.go">
<violation number="1" location="internal/ui/progress_bar_test.go:280">
P2: Peak-elbow alignment test uses byte indices instead of display columns, making it unreliable with multibyte glyphs like `㏈`.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| if peakPos+len(" "+value)+1 <= width { | ||
| b.WriteString(strings.Repeat(" ", peakPos)) | ||
| b.WriteString(elbowStyle.Render("└")) | ||
| b.WriteByte(' ') | ||
| b.WriteString(valueStyle.Render(value)) | ||
| } else { | ||
| // value then a right-elbow ┘ ending under the peak column. | ||
| lead := max(peakPos-(len(value)+1), 0) | ||
| b.WriteString(strings.Repeat(" ", lead)) | ||
| b.WriteString(valueStyle.Render(value)) | ||
| b.WriteByte(' ') | ||
| b.WriteString(elbowStyle.Render("┘")) | ||
| } |
There was a problem hiding this comment.
P2: Peak label alignment uses byte length (len) instead of display width, which misplaces the new elbow/label layout with Unicode text.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/ui/views.go, line 282:
<comment>Peak label alignment uses byte length (`len`) instead of display width, which misplaces the new elbow/label layout with Unicode text.</comment>
<file context>
@@ -218,13 +261,37 @@ func renderAudioLevelMeter(currentLevel, peakLevel float64, elapsed time.Duratio
+ // Default: elbow drops to the right (└ value). When the right-elbow form
+ // would overflow the bar, flip to the left (value ┘) so the label stays
+ // within meterWidth.
+ if peakPos+len(" "+value)+1 <= width {
+ b.WriteString(strings.Repeat(" ", peakPos))
+ b.WriteString(elbowStyle.Render("└"))
</file context>
| if peakPos+len(" "+value)+1 <= width { | |
| b.WriteString(strings.Repeat(" ", peakPos)) | |
| b.WriteString(elbowStyle.Render("└")) | |
| b.WriteByte(' ') | |
| b.WriteString(valueStyle.Render(value)) | |
| } else { | |
| // value then a right-elbow ┘ ending under the peak column. | |
| lead := max(peakPos-(len(value)+1), 0) | |
| b.WriteString(strings.Repeat(" ", lead)) | |
| b.WriteString(valueStyle.Render(value)) | |
| b.WriteByte(' ') | |
| b.WriteString(elbowStyle.Render("┘")) | |
| } | |
| valueW := lipgloss.Width(value) | |
| if peakPos+1+1+valueW <= width { | |
| b.WriteString(strings.Repeat(" ", peakPos)) | |
| b.WriteString(elbowStyle.Render("└")) | |
| b.WriteByte(' ') | |
| b.WriteString(valueStyle.Render(value)) | |
| } else { | |
| // value then a right-elbow ┘ ending under the peak column. | |
| lead := max(peakPos-(valueW+1), 0) | |
| b.WriteString(strings.Repeat(" ", lead)) | |
| b.WriteString(valueStyle.Render(value)) | |
| b.WriteByte(' ') | |
| b.WriteString(elbowStyle.Render("┘")) | |
| } |
| triCol := strings.IndexRune(triLine, '▲') | ||
| var elbowCol int | ||
| if tc.wantLeft { | ||
| elbowCol = strings.IndexRune(elbowLine, '┘') | ||
| if !strings.HasSuffix(strings.TrimRight(elbowLine, " "), "┘") { | ||
| t.Errorf("peak=%g: left-flip elbow not ending in '┘': %q", tc.peak, elbowLine) | ||
| } | ||
| } else { | ||
| elbowCol = strings.IndexRune(elbowLine, '└') |
There was a problem hiding this comment.
P2: Peak-elbow alignment test uses byte indices instead of display columns, making it unreliable with multibyte glyphs like ㏈.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/ui/progress_bar_test.go, line 280:
<comment>Peak-elbow alignment test uses byte indices instead of display columns, making it unreliable with multibyte glyphs like `㏈`.</comment>
<file context>
@@ -226,6 +226,94 @@ func TestMeterPeakTriangleAlignsBeneathBar(t *testing.T) {
+ }
+
+ // Elbow glyph aligns at the peak column (same as the triangle).
+ triCol := strings.IndexRune(triLine, '▲')
+ var elbowCol int
+ if tc.wantLeft {
</file context>
| triCol := strings.IndexRune(triLine, '▲') | |
| var elbowCol int | |
| if tc.wantLeft { | |
| elbowCol = strings.IndexRune(elbowLine, '┘') | |
| if !strings.HasSuffix(strings.TrimRight(elbowLine, " "), "┘") { | |
| t.Errorf("peak=%g: left-flip elbow not ending in '┘': %q", tc.peak, elbowLine) | |
| } | |
| } else { | |
| elbowCol = strings.IndexRune(elbowLine, '└') | |
| triIdx := strings.IndexRune(triLine, '▲') | |
| triCol := ansi.StringWidth(triLine[:triIdx]) | |
| var elbowCol int | |
| if tc.wantLeft { | |
| elbowIdx := strings.IndexRune(elbowLine, '┘') | |
| elbowCol = ansi.StringWidth(elbowLine[:elbowIdx]) | |
| if !strings.HasSuffix(strings.TrimRight(elbowLine, " "), "┘") { | |
| t.Errorf("peak=%g: left-flip elbow not ending in '┘': %q", tc.peak, elbowLine) | |
| } | |
| } else { | |
| elbowIdx := strings.IndexRune(elbowLine, '└') | |
| elbowCol = ansi.StringWidth(elbowLine[:elbowIdx]) | |
| } |
Replace hand-rolled spinner, progress bars, metric table, and level meter with first-party Charm v2 components (bubbles/spinner, bubbles/progress, lipgloss/table, harmonica springs). Centralise colour palette to cli/styles.go with adaptive downsampling via lipgloss v2. CLI help and version now honour NO_COLOR and COLORPROFILE. Spinner, progress bars, and eased meter work as intended; progress bar now fits terminal cleanly in brand red.
All tests pass. Metric table retains all formatters and edge-case coverage; 50+ lines of manual column-width maths eliminated.