Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .github/issues/001-action-binding-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Action-Binding Refactor

## Summary

Refactor keybinding architecture to co-locate bindings with their actions, replacing the switch statement in `Update()` with a simple binding iteration pattern.

## Motivation

Currently, keybindings are defined in `keys.go` but their actions are scattered across a large switch statement in `app.go`. This creates:
- Duplication between binding definition and action
- Help text that can drift from actual behavior
- Boilerplate for each new binding

The action-binding pattern unifies these: each binding knows its own action.

## Implementation

### New Types (`internal/ui/help/types.go`)

```go
type Category string

const (
CategoryNavigation Category = "Navigation"
CategoryActions Category = "Actions"
CategoryDiff Category = "Diff"
)

type Action func(m *Model) tea.Cmd

type HelpBinding struct {
Binding key.Binding
Category Category
Order int // lower = higher priority for inline status bar
Action Action // nil = display-only
}

type HelpKeyMap interface {
HelpBindings() []HelpBinding
}
```

### Refactored Dispatch (`app.go`)

```go
case tea.KeyMsg:
for _, hb := range m.activeBindings() {
if key.Matches(msg, hb.Binding) && hb.Action != nil {
return hb.Action(m)
}
}
```

## Tasks

- [ ] Create `internal/ui/help/` package with `types.go`
- [ ] Define `Category` enum with initial categories
- [ ] Define `HelpBinding` struct with `Action` field
- [ ] Define `HelpKeyMap` interface
- [ ] Refactor `internal/app/keys.go` to return `[]HelpBinding`
- [ ] Add `activeBindings()` method to Model
- [ ] Replace switch statement in `Update()` with binding iteration
- [ ] Ensure all existing keybindings work as before

## Tests

| Test | Description |
|------|-------------|
| `TestDispatch_MatchesAndExecutes` | Matching key pressed → correct action executes |
| `TestDispatch_NoMatchNoAction` | Unbound key pressed → no action |
| `TestDispatch_NilActionSkipped` | Binding with nil action → no panic |
| `TestDispatch_FirstMatchWins` | Overlapping bindings → first match wins |
| `TestDispatch_DisabledBindingSkipped` | Disabled binding → skipped |

## Acceptance Criteria

- [ ] All existing keybindings function identically
- [ ] No switch statement for key dispatch in `Update()`
- [ ] `HelpBinding` includes category, order, and action
- [ ] Tests pass
102 changes: 102 additions & 0 deletions .github/issues/002-inline-status-bar-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Inline Status Bar Help

## Summary

Display context-sensitive keybinding hints in the status bar, showing as many bindings as fit (ordered by priority) with graceful truncation.

## Motivation

Users shouldn't have to memorize keybindings. The status bar should show relevant actions for the current context, similar to how `lazygit` and `htop` display contextual help.

## Design

```
j/k up/down • h/l pane • enter drill • ? help chado v0.1.0
└─────────────────────────────────────┘ └───────────┘
bindings (left) version (right)
```

- Bindings sorted by `Order` (ascending)
- Renders left-to-right until width exhausted
- Truncates with `…` if bindings don't fit
- Version stays right-aligned
- Context-sensitive: shows global + focused panel bindings

## Implementation

### StatusBarHelp Component (`internal/ui/help/statusbar.go`)

```go
type StatusBarHelp struct {
width int
version string
bindings []HelpBinding
}

func (s *StatusBarHelp) SetBindings(bindings []HelpBinding)
func (s *StatusBarHelp) SetWidth(width int)
func (s StatusBarHelp) View() string
```

### Context Collection

Each panel implements `HelpKeyMap`:

```go
func (p *LogPanel) HelpBindings() []HelpBinding
func (p *FilesPanel) HelpBindings() []HelpBinding
func (p *DiffPanel) HelpBindings() []HelpBinding
```

App merges based on context:

```go
func (m *Model) activeBindings() []HelpBinding {
bindings := m.globalBindings()
switch m.focusedPane {
case PaneLog:
if m.viewMode == ViewLog {
bindings = append(bindings, m.logPanel.HelpBindings()...)
} else {
bindings = append(bindings, m.filesPanel.HelpBindings()...)
}
case PaneDiff:
bindings = append(bindings, m.diffPanel.HelpBindings()...)
}
return bindings
}
```

## Tasks

- [ ] Create `StatusBarHelp` component in `internal/ui/help/statusbar.go`
- [ ] Implement order-based sorting
- [ ] Implement width-aware truncation with `…`
- [ ] Add `HelpBindings()` to `LogPanel`
- [ ] Add `HelpBindings()` to `FilesPanel`
- [ ] Add `HelpBindings()` to `DiffPanel`
- [ ] Add `activeBindings()` method to Model
- [ ] Integrate into `renderStatusBar()`
- [ ] Style bindings (key style, desc style, separator)

## Tests

| Test | Description |
|------|-------------|
| `TestStatusBar_OrderedByPriority` | Bindings with orders [3,1,2] → renders [1,2,3] |
| `TestStatusBar_TruncatesWithEllipsis` | Width=40, 10 bindings → shows as many as fit + `…` |
| `TestStatusBar_VersionRightAligned` | Version stays right-aligned |
| `TestStatusBar_EmptyBindings` | No bindings → just shows version |
| `TestStatusBar_ContextSensitive` | Focused panel → shows global + panel bindings |

## Acceptance Criteria

- [ ] Status bar shows keybinding hints
- [ ] Hints change based on focused panel
- [ ] Gracefully truncates when terminal is narrow
- [ ] Version remains visible and right-aligned
- [ ] Tests pass

## Dependencies

- #1 Action-Binding Refactor (provides `HelpBinding` types)
141 changes: 141 additions & 0 deletions .github/issues/003-floating-help-modal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Floating Help Modal

## Summary

Add a `?` toggle to show a floating modal with all keybindings organized by category in a column-flow layout.

## Motivation

The inline status bar can only show a few bindings. Power users need a way to see all available keybindings at once, organized logically.

## Design

```
┌─ Help ────────────────────────────────────────────────────┐
│ │
│ Navigation Actions │
│ j/k up/down enter drill down / select │
│ h/l switch pane esc go back │
│ g top q quit │
│ G bottom │
│ 0/1 focus pane Diff │
│ {/} prev/next hunk │
│ gg/G top/bottom │
│ │
│ ? to close │
└───────────────────────────────────────────────────────────┘
```

- Toggle with `?` (same key opens and closes)
- Renders as overlay on top of existing UI
- Groups bindings by `Category`
- Categories ordered by enum value
- Column-flow layout: fills top-to-bottom, then wraps to next column
- Footer shows dismiss hint

## Implementation

### FloatingHelp Component (`internal/ui/help/floating.go`)

```go
type FloatingHelp struct {
width int
height int
bindings []HelpBinding
}

func (f *FloatingHelp) SetSize(width, height int)
func (f *FloatingHelp) SetBindings(bindings []HelpBinding)
func (f FloatingHelp) View() string
```

### Column-Flow Algorithm

1. Group bindings by category
2. Calculate total rows needed per category (header + items)
3. Determine available rows per column
4. Flow categories into columns, allowing category to split across columns if needed

### Toggle Integration (`app.go`)

```go
type Model struct {
// ...
showHelp bool
}

// In Update():
case tea.KeyMsg:
if msg.String() == "?" {
m.showHelp = !m.showHelp
return m, nil
}
if m.showHelp {
return m, nil // absorb other keys while modal open
}
// ... normal dispatch

// In View():
if m.showHelp {
return m.renderWithOverlay(content, m.floatingHelp.View())
}
```

### Overlay Rendering

```go
func (m Model) renderWithOverlay(base, overlay string) string {
// Center overlay on base content
// Use lipgloss.Place or manual positioning
}
```

## Pinned Status Bar Binding

The `?` binding should **always** appear in the status bar, never truncated:

```
j/k up • h/l pane • … • ? help chado v1.0.0
^^^^^^^^
always visible
```

Add `Pinned bool` to `HelpBinding` or handle specially in StatusBar rendering.

## Tasks

- [ ] Create `FloatingHelp` component in `internal/ui/help/floating.go`
- [ ] Implement category grouping
- [ ] Implement column-flow layout algorithm
- [ ] Style modal (border, title, footer)
- [ ] Add `showHelp` state to Model
- [ ] Handle `?` toggle in Update()
- [ ] Absorb keypresses while modal is open
- [ ] Implement overlay rendering in View()
- [ ] Pass bindings to FloatingHelp on toggle
- [ ] Pin `?` binding in status bar (never truncated)

## Tests

| Test | Description |
|------|-------------|
| `TestFloating_GroupsByCategory` | Mixed category bindings → grouped correctly |
| `TestFloating_CategoryOrder` | Categories ordered by enum value |
| `TestFloating_ColumnFlow` | Many bindings → flows top-to-bottom, left-to-right |
| `TestFloating_ToggleOpensCloses` | `?` pressed → toggles visibility |
| `TestFloating_OverlayRendering` | Modal open → renders on top of content |

## Acceptance Criteria

- [ ] `?` opens floating help modal
- [ ] `?` again closes it
- [ ] Bindings grouped by category
- [ ] Column-flow layout works for various terminal sizes
- [ ] Modal is centered and styled
- [ ] Other keys absorbed while modal open
- [ ] Tests pass

## Dependencies

- #1 Action-Binding Refactor (provides `HelpBinding` types)
- #2 Inline Status Bar Help (provides panel `HelpBindings()` methods)
43 changes: 43 additions & 0 deletions .github/issues/004-overlay-compositing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Consider bubbletea-overlay for floating help modal

## Summary

Evaluate using [bubbletea-overlay](https://github.com/rmhubbert/bubbletea-overlay) for the floating help modal to achieve true compositing (see-through) effect.

## Current Behavior

The floating help modal uses `lipgloss.Place` which centers the modal and fills the background with a solid color, obscuring the view behind it.

## Proposed Enhancement

Use the overlay library to composite the help modal on top of the existing view, allowing the panels behind to remain partially visible.

**Visual concept:** The modal would "float" in the bottom-left corner (above the status bar), creating a visual connection to the inline help while letting you see the panels behind/around it.

## Implementation

```go
import overlay "github.com/rmhubbert/bubbletea-overlay"

// Position: bottom-left with slight offset
overlay.New(helpModel, baseModel, overlay.Left, overlay.Bottom, 2, 1)
```

## Trade-offs

**Pros:**
- True compositing effect
- Clean positioning API (Left/Right/Top/Bottom/Center + offsets)
- Well-maintained (v0.6.4, MIT license)

**Cons:**
- Additional dependency
- Visual polish, not functional improvement

## Priority

Low - nice-to-have polish. Current implementation is functional.

## Labels

`enhancement`, `polish`, `low-priority`
Loading
Loading