mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-13 06:51:27 -10:00
* feat(tui): interactive theme picker + color theme catalog
Bare `/theme` now opens a popup that live-previews each palette as the cursor
moves and applies on select (Esc reverts), matching /model and /effort. Adds ten
built-in color themes alongside the dark/light built-ins — dracula, nord, gruvbox,
tokyo-night, catppuccin, one-dark, solarized-dark, rose-pine, everforest, and
solarized-light — plus a reworked light palette. Every palette is contrast-audited
to WCAG AA; the contrast tests now loop the whole registry.
Palettes and an ordered registry live in a new internal/tui/theme_palettes.go (now
the sole home of hex literals); selection, the picker, /theme <name>, --theme, and
ZERO_THEME all resolve through it. The whole frame is painted with the active
theme's surface (tea.View.BackgroundColor) so every theme is legible on any
terminal — a light theme's dark text no longer lands invisibly on a dark terminal,
and color themes show their real surface.
The chosen theme persists to user config (preferences.theme) and is reapplied at
startup, below the --theme flag and ZERO_THEME.
* fix(tui): address CodeRabbit review on the theme catalog
- CHANGELOG: reconcile the --theme bullet with the named themes (was still
documenting {auto|dark|light}).
- /theme usage string advertises the list and auto forms (/theme list is still
accepted) so /help matches the command surface.
- Theme-persistence test parses the written config and asserts preferences.theme
directly instead of a raw substring, so a wrong-key regression fails.
73 lines
1.9 KiB
Go
73 lines
1.9 KiB
Go
package tui
|
|
|
|
import "strings"
|
|
|
|
func (m model) toggleDetailedTranscript() model {
|
|
m.transcriptDetailed = !m.transcriptDetailed
|
|
m.clearSuggestions()
|
|
if m.picker != nil && m.picker.kind == pickerTheme {
|
|
// Dropping a theme picker mid-preview must undo the live palette, or the
|
|
// previewed theme would stick while m.themeMode still holds the old one.
|
|
m.restoreCommittedTheme()
|
|
}
|
|
m.picker = nil
|
|
return m
|
|
}
|
|
|
|
func (m model) detailedTranscriptView() string {
|
|
width := chatWidth(m.width)
|
|
rc := buildRowContext(m.transcript)
|
|
renderer := m
|
|
renderer.pending = false
|
|
renderer.activeRunID = 0
|
|
|
|
var builder strings.Builder
|
|
builder.WriteString(detailedTranscriptHeader(width))
|
|
builder.WriteString("\n")
|
|
builder.WriteString(zeroTheme.line.Render(strings.Repeat("-", width)))
|
|
builder.WriteString("\n")
|
|
|
|
shownAny := false
|
|
var previousKind rowKind
|
|
for _, row := range m.transcript {
|
|
if rc.skip(row) {
|
|
continue
|
|
}
|
|
rendered := ""
|
|
if row.kind == rowWelcome {
|
|
rendered = fitStyledLine(zeroTheme.faint.Render(row.text), width)
|
|
} else {
|
|
rendered = renderer.renderRowDetailed(row, width, rc)
|
|
}
|
|
if rendered == "" {
|
|
continue
|
|
}
|
|
if shownAny && startsTurn(row.kind) {
|
|
builder.WriteString("\n")
|
|
}
|
|
if shownAny && previousKind == rowUser && row.kind == rowReasoning {
|
|
builder.WriteString("\n")
|
|
}
|
|
builder.WriteString(rendered)
|
|
builder.WriteString("\n")
|
|
shownAny = true
|
|
previousKind = row.kind
|
|
}
|
|
|
|
if !shownAny {
|
|
builder.WriteString(zeroTheme.faint.Render("No transcript rows."))
|
|
builder.WriteString("\n")
|
|
}
|
|
|
|
builder.WriteString(zeroTheme.line.Render(strings.Repeat("-", width)))
|
|
builder.WriteString("\n")
|
|
builder.WriteString(fitStyledLine(zeroTheme.faint.Render("Esc close | Ctrl+O toggle"), width))
|
|
return builder.String()
|
|
}
|
|
|
|
func detailedTranscriptHeader(width int) string {
|
|
title := zeroTheme.ink.Bold(true).Render("Transcript")
|
|
hint := zeroTheme.faint.Render("detailed")
|
|
return fitStyledLine(joinHeaderLine(title, hint, width), width)
|
|
}
|