▍ humdrum codex / glint v1.0.2
license AGPL-3.0

fix: force true color (no system downsampling); theme-drive the whole preview

6441106b38305ed8aa1eb91b854d5ade40bfc973
humdrum <me@humdrum.me> · 2026-06-29 08:41

parent dabc84eb

fix: force true color (no system downsampling); theme-drive the whole preview

- Force lipgloss to the TrueColor profile so glint renders its exact theme hexes
  regardless of the terminal's detected profile or the OS light/dark setting —
  fixes unreadable prose when the system is light but the theme is flexoki-dark.
- Preview is now fully theme-driven: backgrounds (document, code, code block,
  table, blockquote) use the theme paper; chroma syntax styling is disabled so
  code blocks no longer show glamour's hardcoded dark panel; headings (incl. H1's
  purple-bg/yellow-text) use the theme heading color on the theme bg; prose,
  code, and links use theme colors. App passes the theme colors into the preview.

4 files changed

go.mod +1 −1
@@ -9,6 +9,7 @@ 	github.com/charmbracelet/bubbletea v1.3.10
 	github.com/charmbracelet/glamour v1.0.0
 	github.com/charmbracelet/huh v1.0.0
 	github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
+	github.com/muesli/termenv v0.16.0
 )
 
 require (
@@ -39,7 +40,6 @@ 	github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
 	github.com/muesli/cancelreader v0.2.2 // indirect
 	github.com/muesli/reflow v0.3.0 // indirect
-	github.com/muesli/termenv v0.16.0 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
 	github.com/yuin/goldmark v1.7.13 // indirect
internal/app/app.go +13 −2
@@ -86,8 +86,19 @@ 		editor:    ed,
 		saveInput: ti,
 	}
 	a.preview = preview.New(a.glamourStyle())
-	a.preview.SetBackground(string(th.Background))
+	a.preview.SetColors(previewColors(th))
 	return a
+}
+
+// previewColors maps a theme to the glamour preview's color set.
+func previewColors(th theme.Theme) preview.Colors {
+	return preview.Colors{
+		Background: string(th.Background),
+		Text:       string(th.Text),
+		Heading:    string(th.Heading),
+		Code:       string(th.Code),
+		Link:       string(th.Link),
+	}
 }
 
 // Load reads a file into the editor and switches to edit mode.
@@ -407,7 +418,7 @@ func (a *App) cycleTheme() (tea.Model, tea.Cmd) {
 	a.theme = theme.Next(a.theme.Name)
 	a.editor.SetTheme(a.theme)
 	a.preview.SetStyle(a.glamourStyle())
-	a.preview.SetBackground(string(a.theme.Background))
+	a.preview.SetColors(previewColors(a.theme))
 	// Re-render an open preview so its glamour style follows the new theme
 	// (otherwise it keeps the old light/dark block until the next toggle).
 	if a.mode == ModePreview {
internal/preview/preview.go +39 −18
@@ -12,33 +12,55 @@ 	"github.com/charmbracelet/glamour/ansi"
 	"github.com/charmbracelet/glamour/styles"
 )
 
-// paintCanvasBackground rewrites a glamour style so every block sits on the
-// theme background (and drops glamour's document margin), so code blocks,
-// inline code, and tables don't show their own darker panels in the preview.
-// The chroma config is deep-copied so glamour's shared global isn't mutated.
-func paintCanvasBackground(cfg *ansi.StyleConfig, hex string) {
+// applyTheme rewrites a glamour style so it matches the glint theme: every block
+// on the theme background (no glamour panels), headings/code/links/prose in the
+// theme colors, glamour's H1 purple-bg/yellow-text and dark code chroma removed.
+func applyTheme(cfg *ansi.StyleConfig, c Colors) {
 	var zero uint
 	cfg.Document.Margin = &zero
-	if hex == "" {
+	if c.Background == "" {
 		return
 	}
-	bg := hex
+	bg, text, heading, code, link := c.Background, c.Text, c.Heading, c.Code, c.Link
+
+	// Backgrounds → the theme paper (so nothing shows a darker panel).
 	cfg.Document.BackgroundColor = &bg
 	cfg.Code.BackgroundColor = &bg
 	cfg.CodeBlock.BackgroundColor = &bg
-	if cfg.CodeBlock.Chroma != nil {
-		chroma := *cfg.CodeBlock.Chroma
-		chroma.Background.BackgroundColor = &bg
-		cfg.CodeBlock.Chroma = &chroma
+	cfg.Table.BackgroundColor = &bg
+	cfg.BlockQuote.BackgroundColor = &bg
+	// Disable chroma syntax styling so code renders plainly on the theme bg
+	// instead of glamour's hardcoded dark code panel.
+	cfg.CodeBlock.Chroma = nil
+	cfg.CodeBlock.Color = &code
+	cfg.Code.Color = &code
+
+	// Prose + headings + links in theme colors. Clear H1's purple background.
+	cfg.Document.Color = &text
+	cfg.Text.Color = &text
+	for _, h := range []*ansi.StyleBlock{&cfg.Heading, &cfg.H1, &cfg.H2, &cfg.H3, &cfg.H4, &cfg.H5, &cfg.H6} {
+		h.Color = &heading
+		h.BackgroundColor = &bg
 	}
-	cfg.Table.BackgroundColor = &bg
+	cfg.Link.Color = &link
+	cfg.LinkText.Color = &link
 }
 
 // Model wraps a Glamour renderer and a viewport.
+// Colors are the theme hexes the preview paints glamour with, so the read view
+// matches the editor exactly (no glamour panel colors, no system reliance).
+type Colors struct {
+	Background string
+	Text       string
+	Heading    string
+	Code       string
+	Link       string
+}
+
 type Model struct {
 	vp     viewport.Model
 	style  string
-	bg     string // theme background hex, so the preview matches the canvas
+	colors Colors
 	width  int
 	height int
 }
@@ -54,10 +76,9 @@ 		height: 24,
 	}
 }
 
-// SetBackground sets the document background (a hex like "#100F0F") so the
-// glamour preview blends into the themed canvas instead of showing glamour's
-// own panel color.
-func (m *Model) SetBackground(hex string) { m.bg = hex }
+// SetColors sets the theme colors glamour renders with so the preview matches
+// the editor canvas (background, prose, headings, code, links).
+func (m *Model) SetColors(c Colors) { m.colors = c }
 
 // SetSize resizes the viewport.
 func (m *Model) SetSize(w, h int) {
@@ -130,7 +151,7 @@ 		cfg := styles.DarkStyleConfig
 		if m.style == "light" {
 			cfg = styles.LightStyleConfig
 		}
-		paintCanvasBackground(&cfg, m.bg)
+		applyTheme(&cfg, m.colors)
 		opts = append(opts, glamour.WithStyles(cfg))
 	case knownStyles[m.style]:
 		// A user-chosen named style keeps its own look.
main.go +8 −0
@@ -12,7 +12,15 @@ 	"glint/internal/configui"
 	"glint/internal/keyprobe"
 
 	tea "github.com/charmbracelet/bubbletea"
+	"github.com/charmbracelet/lipgloss"
+	"github.com/muesli/termenv"
 )
+
+func init() {
+	// Render glint's exact theme hexes regardless of the terminal's detected
+	// profile or OS appearance — never downsample or adapt to the system.
+	lipgloss.SetColorProfile(termenv.TrueColor)
+}
 
 // version is the build version, overridden at release time via
 // -ldflags "-X main.version=<v>" (the Homebrew formula sets it).