Skip to content

Conversation

@yimsk
Copy link
Contributor

@yimsk yimsk commented Jan 14, 2026

Summary

  • Add compact header mode (1-2 lines vs 5 lines) toggleable via Ctrl+E
  • Support --compact/--no-compact CLI flags
  • Add compact_header config setting with autosave support
  • Dynamic profile/region width calculation with proper truncation

Changes

  • 20 commits from feature/header-redesign
  • New CompactHeaderChangedMsg message type
  • Full test coverage for formatting functions
  • Docs: keybindings.md, configuration.md updated

* feat(config): add compact_header setting

* refactor(header): unified 5-line normal mode layout

* feat(header): add compact mode (1-line)

* feat(views): add Ctrl+E header toggle

* test(header): add tests for compact mode

* fix(header): simplify RenderHome to 2-line header

* fix(header): ctrl+e toggle layout issues

- resource_browser: add buildTable() after toggle
- detail_view: add viewport recalc after toggle
- header_panel: remove dead code branch (len>12)
- main: add --compact flag to help text

* refactor(header): centralize ctrl+e, fix styling and theme updates

- Add CompactHeaderChangedMsg for centralized header toggle
- Move ctrl+e handling from 6 views to app.go
- Remove dead code in account ID display (always 12 digits)
- Apply theme styles to compact header content
- Fix ServiceBrowser not updating on theme change

* refactor(header): extract constants and deduplicate profile formatting

* fix(header): recalc viewport on compact toggle, persist with autosave

* fix(header): dynamic profile display width and ANSI style reset

- formatProfilesWithAccounts now uses available width instead of fixed maxShow=2
- Profiles display adapts to screen width, showing (+N) when truncated
- Fix ANSI style reset issue: apply valueStyle per-part to prevent dangerStyle reset from breaking subsequent text
- formatSingleProfile also applies valueStyle for consistency

* fix(header): review fixes - off-by-one, regions truncation, settings/help updates

- Fix formatProfilesWithAccounts off-by-one in remaining calc
- Add formatRegions with (+N) truncation pattern
- Add Ctrl+E to help view Global section
- Add compact_header to :settings, remove confusing Startup section
- Fix wrapSettingsValue: use lipgloss.Width, word-wrap on delimiter
- Increase ModalWidthSettings 70→75
- Add tests for formatRegions and formatProfilesWithAccounts
- Update docs: keybindings.md, configuration.md

* feat(cli): add --no-compact flag, fix light theme text colors

- Add --no-compact flag to override config compact_header setting
- Fix settings_view text not visible on light theme
- Fix command_input using hardcoded dark theme colors
- Style command mode hints with DimStyle for theme awareness

* fix(view): add viewport ready guard, move TextInputStyles to ui pkg

- header_panel: document first-item-always-shown intent in format funcs
- detail_view: add Ready guard in recalcViewport to prevent panic
- ui/theme: add TextInputStyles() helper for textinput.Styles
- command_input: use ui.TextInputStyles() instead of inline styles

* chore(view): cleanup from review - rename label, add comments, remove unused const

* fix: review issues - test expectation, dashboard compact handler, docs

* fix(header): truncate regions/profiles on invalid width

* fix(view): remove confusing (CLI) indicators from settings view

- Remove (CLI) suffix for regions/profiles/compact_header
- Fix potential data race by eliminating cfg.Startup direct access
- Update autosave docs to include compact_header

* refactor(view): extract magic numbers to named constants

- profileWidthRatio/regionWidthRatio for 2/3 ratio in compact mode
- minViewportHeight for viewport minimum height

Addresses Claude bot review feedback on PR #151

* fix(header): use max() for availableWidth floor instead of fallback

Previous logic incorrectly expanded width to 60 when terminal was
narrow, causing overflow. Now uses max() to enforce minimum floor.
@claude
Copy link

claude bot commented Jan 14, 2026

PR Review: v0.14.0 - Compact Header Mode

Summary

This PR adds a compact header mode feature that allows users to toggle between a 5-line header (normal) and a 1-2 line header (compact) using Ctrl+E. The implementation includes CLI flags, config file support, autosave integration, and comprehensive test coverage.

Code Quality & Best Practices ✅

Strengths:

  • Well-structured code: Clean separation of concerns with dedicated formatting functions (formatProfilesWithAccounts, formatRegions, formatSingleProfile)
  • Consistent naming: Good use of descriptive names and clear constants
  • Good documentation: Helpful comments explaining complex logic (e.g., width calculations, truncation behavior)
  • Thread-safe config updates: Proper use of mutexes in config.go
  • Test coverage: Excellent test coverage with 330 lines of tests in header_panel_test.go

Minor improvements needed:

  1. Magic numbers (internal/view/header_panel.go:320-321):

    profileMaxWidth := remainingWidth * profileWidthRatio / regionWidthRatio
    regionMaxWidth := remainingWidth - profileMaxWidth

    The ratio calculation could benefit from a comment explaining the 2:1 ratio choice.

  2. Duplicate separator rendering (internal/view/header_panel.go:125, 207, 378):
    The separator pattern valueStyle.Render(", ") appears multiple times. Consider extracting to a constant or helper.

Potential Bugs & Issues ⚠️

1. Integer Division Precision Loss (header_panel.go:320)

profileMaxWidth := remainingWidth * profileWidthRatio / regionWidthRatio

When remainingWidth * 2 / 3, you could lose precision with integer division. For example, if remainingWidth = 50, profile gets 33 and region gets 17 (50-33), not the intended 2:1 ratio.

Recommendation: Add a comment or consider rounding behavior.

2. Viewport Recalculation on Uninitialized State (detail_view.go:224-232)

func (d *DetailView) recalcViewport() {
    // ... calculations ...
    if !d.vp.Ready {
        return
    }
    content := d.renderContent()
    d.vp.Model.SetContent(content)
}

The Ready check comes AFTER size calculations. If viewport is not ready, size is set but content isn't. This could cause visual glitches on first render.

Recommendation: Consider checking Ready earlier or ensuring initialization order is clear.

3. Width Constraint Edge Case (header_panel.go:128)

if maxWidth <= 0 && len(selections) > 1 {

When maxWidth <= 0 with multiple selections, the code forces truncation. However, when maxWidth == 0 with a single selection, it renders the full profile without truncation. This inconsistency could be confusing.

Recommendation: Document this behavior or handle maxWidth <= 0 uniformly.

Performance Considerations ⚡

Good:

  • Efficient truncation: The truncation logic in formatProfilesWithAccounts and formatRegions calculates width incrementally, avoiding multiple passes
  • Cached styles: Using headerPanelStyles struct avoids recreating lipgloss styles on every render
  • Early bailout: Functions check empty slices early (lines 121, 194)

Potential optimization:

  • String allocation (header_panel.go:144, 209): Multiple make([]string, 0, len(...)) allocations. These are fine, but be aware this happens on every render. Consider profiling if performance becomes an issue in tight loops.

Security Concerns 🔒

No security issues identified.

The PR deals with UI rendering and configuration management. No user input validation issues, no command injection risks, no sensitive data exposure.

Test Coverage 📊

Excellent coverage! The PR includes:

  • ✅ 330 lines of comprehensive tests in header_panel_test.go
  • ✅ Tests for both normal and compact modes
  • ✅ Tests for mode switching
  • ✅ Edge cases: empty regions, single vs. multiple items, width constraints
  • ✅ Tests for formatting functions with various truncation scenarios
  • ✅ Config persistence tests in config_test.go

Coverage gaps:

  1. No integration test for Ctrl+E keybinding triggering the mode switch
  2. No test for autosave behavior when toggling compact mode
  3. Settings view changes (internal/view/settings_view.go) have reduced test coverage (-2 lines)

Recommendation: Add a test case in app_test.go to verify the complete flow: key press → config update → autosave → UI update.

Additional Observations

Documentation

  • ✅ Updated docs/keybindings.md with Ctrl+E binding
  • ✅ Updated docs/configuration.md with compact_header setting
  • ✅ Added CLI flags to help text

Message Propagation

The CompactHeaderChangedMsg is properly propagated to all views:

  • dashboard_view.go:195
  • service_browser.go:182
  • detail_view.go:135
  • resource_browser.go:227

Good job ensuring all views respond to the mode change!

Consistency

One minor inconsistency: command_input.go:104 calls ReloadStyles() but doesn't reset compact header. This is likely fine since styles and compact mode are independent, but worth noting.

Recommendations Summary

Must Fix

None - no blocking issues

Should Fix

  1. Add comment explaining the 2:1 ratio calculation for profile/region widths
  2. Document or handle maxWidth <= 0 behavior consistently
  3. Consider checking viewport Ready state earlier in recalcViewport()

Nice to Have

  1. Extract separator rendering to a helper/constant
  2. Add integration test for the full compact mode toggle flow
  3. Profile string allocation performance if rendering becomes a bottleneck

Verdict

LGTM with minor suggestions

This is a well-implemented feature with good code quality, comprehensive tests, and thoughtful UX design. The compact header mode addresses a real user need for more screen real estate. The issues identified are minor and don't block merging.

Great work on the dynamic width calculation and truncation logic - that's non-trivial to get right!


Reviewed by Claude Code

@yimsk yimsk merged commit f5865f5 into main Jan 14, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant