Lipgloss Layers: Proper Styles, Proper Keybindings#13
Merged
JasonWarrenUK merged 9 commits intomainfrom Mar 25, 2026
Merged
Conversation
All five view renderers (schedule, timeline, prose, budget, list) had palette structs with `string` fields for hex colours. Change them to `color.Color` so callers no longer need to wrap values in `lipgloss.Color()`. `Default*Palette()` functions now return typed values. Render sites updated to use palette fields directly. This establishes the correct type boundary — when view pane models are wired into the `tui` layer, construction sites will override palette fields from `ActiveTheme`, following the same pattern as `detail.go`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The command palette selected row previously only changed the foreground colour and added bold — the background remained unchanged, making the selection invisible at a glance. Now all three style elements (cursor prefix, name, hint) use `theme.Selection()` as the background when the row is active. Also documents that the manual left-pad in View() is intentionally unstyled — those spaces sit outside the modal border and should be transparent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…bar (VS.6) Three improvements to the status bar: - Separator: a full-width horizontal rule in the theme border colour is prepended above the bar, giving a clear visual boundary. - Keybind hints: a compact hints section (up to 4 bindings) is shown between the capture text and the centre node info, using FgMuted(). Updated by the root model via SetKeyHints() on every focus change. - Spacer fix: replace spacerStyle.Render(strings.Repeat(...)) with the Spacer() utility from render.go, preventing background bleed. statusBarHeight in layout.go updated to 2 to account for the new separator line. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The palette overlay previously worked by splitting the frame into lines and replacing them starting at index 2 — brittle and hard to reason about. Replace with lipgloss v2's Compositor API: - Frame becomes Layer.Z(0) at position (0, 0) - Palette overlay becomes Layer.Z(1) at (centreX, 2) - NewCompositor composites them cell-by-cell Palette.View() no longer manually centres the modal box — horizontal position is now handled by Layer.X(). The splitLines/joinLines helpers are removed as they are no longer used. Also wires syncKeyHints() calls: every focus change and pane mount now updates the status bar's keybind hints for the newly focused pane. Adds lipgloss import to app.go (previously unused there). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
VS.3, VS.4, VS.5, VS.9 unblocked (VS.1 dependency resolved). VS.10 unblocked similarly. VS.8 remains blocked (needs CP.1). Prune completed VS nodes from Mermaid progress graph. Update status summary table. Regenerate roadmap-tui.html dashboard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove custom KeyAction enum, keyRule table, and KeyMap struct. Replace with idiomatic bubbles/key.Binding declarations (AppKeyMap) that integrate directly with Bubble Tea's key handling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ender glamour.NewTermRenderer() probes terminal capabilities and builds a full goldmark pipeline — far too expensive to call on every node selection. Cache the renderer instance and only recreate when Width changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store a long-lived DetailRenderer on the Model instead of recreating it per selection. Add detailReadyMsg for async detail rendering. Switch keyMap field from *KeyMap to AppKeyMap to match the new bubbles/key bindings. Remove unused jumpMsg type. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…binding system Align PaneModel interface and all pane implementations (node list, capture bar, palette, layout) with the bubbles/key dispatch pattern. Remove vim-style jump handlers in favour of standard key bindings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Replaces the TUI's ad-hoc styling and vim-style keybinding system with idiomatic Lipgloss v2 patterns and the standard bubbles/key library. View palettes use typed
color.Colorinstead of raw hex strings, the command palette overlay uses Lipgloss Compositor instead of a brittle line-replacement hack, the Glamour markdown renderer is cached for performance, and all single-char vim bindings (j/k/q/:) are replaced with modifier-based keys (ctrl+c,ctrl+n,ctrl+p, arrows) to eliminate collisions with terminal escape sequences.Summary
Imagine you've been navigating your house by shouting room names through a megaphone while wearing mismatched shoes. This PR replaces the megaphone with actual doors, gives you matching shoes, and stops the neighbours complaining about the noise. The house is the same; you just look less unhinged walking through it.
Tip
No manual steps required. Keybindings have changed —
qno longer quits (usectrl+c),:no longer opens palette (usectrl+p),ino longer captures (usectrl+n). Arrow keys replacej/k/h/lthroughout.Changes
Keybinding system rewrite —
keyboard.go,app.go,app_test.goKeyActionenum,keyRuletable, andKeyMapstruct with idiomaticbubbles/key.Bindingdeclarations (AppKeyMap)key.Matches()switch instead ofhandleAction()indirectionjumpMsgtype — jump-to-top/bottom handled directly by list/viewport KeyMap configurationq→ctrl+c,:→ctrl+p,i→ctrl+n,j/k→ arrowsPane keybinding configuration —
node_list_pane.go,pane.go,capture.gonodeListPane: reconfigures bubbles/list KeyMap directly — removesj/k/g/G, keeps arrows/pgup/pgdn/home/endviewportPane: reconfigures bubbles/viewport KeyMap — removesj/k/d/u/f/b/space, keeps arrows/pgup/pgdn/ctrl+d/ctrl+uitoctrl+n; placeholder text updatedKeyBindings()return values updated across both pane typesLipgloss v2 Compositor overlay —
app.go,palette.golipgloss.NewCompositor()withNewLayer().X().Y().Z()instead ofsplitLines/joinLinesreplacementView()no longer pads itself horizontally — centring delegated to Compositor viaLayer.X()splitLinesandjoinLineshelper functions fromapp.goSelection()background (VS.7)Status bar polish —
statusbar.go,layout.go─) + content barSetKeyHints()— focused pane's bindings shown as compact hintsstatusBarHeightupdated from 1 → 2; border calculation adjusted for Lipgloss v2 outer dimensionsSpacer()used for all internal gaps (no morestrings.Repeat)syncKeyHints()called on focus change, form open/close, and pane mountView palette type safety —
views/budget.go,views/list.go,views/prose.go,views/schedule.go,views/timeline.gostringtocolor.Color(VS.1)Default*Palette()functions returnlipgloss.Color(...)typed valueslipgloss.Color(stringField)wrapper calls at render sites — fields used directlyGlamour renderer caching —
detail.go,app.goDetailRenderernow caches itsglamour.TermRendererinstance, only recreating whenWidthchangesModelholds a long-liveddetailRendererfield instead of creating one per selectionrenderDetailAsync()/detailReadyMsgto keep the event loop responsiveRoadmap updates —
docs/roadmaps/tui.md,docs/diagrams/roadmap-tui.html🤖 Generated with Claude Code