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

fix: preview table borders use theme foreground (TASK-026 follow-up)

53b3a190a4bc8ec9a3f33eeca2ae382f78b42ae2
Kevin Kortum <kevinkortum@me.com> · 2026-06-29 16:18

parent 391d0272

fix: preview table borders use theme foreground (TASK-026 follow-up)

fillBackground now re-asserts the theme text foreground (not just the paper
background) after resets, so glamour's unstyled table border/separator glyphs
render in the theme color instead of the terminal default — otherwise borders
were invisible when the terminal foreground matched the paper (light terminal +
dark theme, and the reverse).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KjNrdAWUdkaxFyGdrPHaBj

5 files changed

- → Wrap-selection-in-markdown-formatting.md +3 −1
@@ -4,7 +4,7 @@ title: Wrap selection in markdown formatting
 status: "\U0001F7E2 In progress"
 assignee: []
 created_date: '2026-06-29 16:26'
-updated_date: '2026-06-29 22:27'
+updated_date: '2026-06-29 23:12'
 labels:
   - feature
   - release-1
@@ -30,4 +30,6 @@ ## Implementation Notes
 
 <!-- SECTION:NOTES:BEGIN -->
 Impl editor inline-format ops (internal/editor/format.go): WrapBold/WrapItalic/WrapCode (symmetric toggle, markers inside or outside selection) + WrapLink ([sel]() cursor in parens, toggle off full link). No selection inserts empty pair, cursor between. Bound Alt+s(bold/strong) Alt+i(italic) Alt+c(code) Alt+k(link) — no clash with Alt+b/f/d or Ctrl set; routed via editor dispatch, single undo group (editKindOf structural). Documented in README Keys table. 10 TDD tests in format_test.go; full suite green.
+
+Added surround-on-type: with a selection, typing * _ ` [ ( { < " ' ~ wraps the selection (nests on repeat, *→**). Complements Alt+s/i/c/k. wrapPair+surroundSelection in format.go; dispatch routes it. 6 tests. (commit wvztmpxz)
 <!-- SECTION:NOTES:END -->
- → Status-bar-line-col-and-word-count.md +3 −1
@@ -4,7 +4,7 @@ title: 'Status bar: directory, theme, line:col, word count, help, selection counts'
 status: "\U0001F7E2 In progress"
 assignee: []
 created_date: '2026-06-29 16:26'
-updated_date: '2026-06-29 22:38'
+updated_date: '2026-06-29 23:12'
 labels:
   - feature
   - release-1
@@ -34,4 +34,6 @@ ## Implementation Notes
 
 <!-- SECTION:NOTES:BEGIN -->
 Reworked statusBar (internal/app/app.go): left = dir/filename + dirty dot (transient a.status takes over when set); right = [N chars · M words selected ·] Ln r:c · N words · theme · ?. layoutStatus() right-justifies and drops trailing ' · ' segments then truncates to fit — no overflow at narrow widths. Editor stats helpers WordCount()/SelectionStats() in internal/editor/stats.go. Theme StatusFg/StatusBg preserved. '?' indicator shown; opening the overlay is wired in TASK-011. TDD: 3 editor + 6 app tests; full suite green.
+
+Left segment now shows the git repo root name (folder with .git) or the immediate parent folder when not in a repo, plus filename. repoOrParent() in app.go; 2 tests. (commit unrvrwqk)
 <!-- SECTION:NOTES:END -->
- → Preview-table-backgrounds-H1-highlight-dont-match-theme-paper.md +7 −1
@@ -4,7 +4,7 @@ title: 'Preview: table backgrounds + H1 highlight don''t match theme paper'
 status: "\U0001F3C1 Done"
 assignee: []
 created_date: '2026-06-29 22:55'
-updated_date: '2026-06-29 22:55'
+updated_date: '2026-06-29 23:12'
 labels:
   - bug
 dependencies: []
@@ -29,3 +29,9 @@ - [x] #2 Table cell padding and borders render on the theme paper across all themes
 - [x] #3 Inline code in cells uses paper bg (no dark blocks)
 - [x] #4 Regression tests cover H1 bg and post-reset paper re-assert
 <!-- AC:END -->
+
+## Implementation Notes
+
+<!-- SECTION:NOTES:BEGIN -->
+Follow-up: real-terminal bleed where unstyled regions adopted the TERMINAL bg (cream-over-dark / dark-over-light). Root: glamour/viewport emit bare \x1b[m resets and viewport padding added AFTER Render. Fixed by re-asserting paper after bare resets too and moving fillBackground to View(). Also left Text.Color unset so H1 text is legible (uses legibleText on the heading bar). 2 more tests. (commit qkvsmknk)
+<!-- SECTION:NOTES:END -->
internal/preview/preview.go +19 −14
@@ -87,21 +87,26 @@ 		}
 	}
 }
 
-// fillBackground re-asserts the paper background after every ANSI reset and at
-// the start of every line, so glyphs glamour emits without a background (table
-// borders, cell padding, list bullets) render on the theme paper instead of the
-// terminal default. Spans that set their own background still override it
-// immediately, so colored text is unaffected.
-func fillBackground(s, bgHex string) string {
-	rgb := hexToRGB(bgHex)
-	if rgb == "" {
+// fillBackground re-asserts the theme paper background and text foreground after
+// every ANSI reset and at the start of every line, so glyphs glamour emits with
+// no styling (table borders, cell padding, list bullets) render in the theme
+// colors instead of the terminal default — otherwise borders are invisible when
+// the terminal foreground matches the paper (light terminal + dark theme, and
+// the reverse). Spans that set their own colors still override immediately, so
+// styled text is unaffected.
+func fillBackground(s, bgHex, fgHex string) string {
+	bg := hexToRGB(bgHex)
+	if bg == "" {
 		return s
 	}
-	paper := "\x1b[48;2;" + rgb + "m"
-	// Re-assert paper after every reset. Glamour/termenv emit both "\x1b[0m" and
-	// the bare "\x1b[m"; regions after an unhandled reset fall back to the
-	// terminal's own background (cream bleed on a dark theme over a light
-	// terminal, and the reverse) — so both forms must be covered.
+	base := "\x1b[48;2;" + bg + "m"
+	if fg := hexToRGB(fgHex); fg != "" {
+		base = "\x1b[38;2;" + fg + ";48;2;" + bg + "m"
+	}
+	paper := base
+	// Re-assert after every reset. Glamour/termenv emit both "\x1b[0m" and the
+	// bare "\x1b[m"; regions after an unhandled reset fall back to the terminal's
+	// own colors — so both forms must be covered.
 	s = strings.ReplaceAll(s, "\x1b[0m", "\x1b[0m"+paper)
 	s = strings.ReplaceAll(s, "\x1b[m", "\x1b[m"+paper)
 	lines := strings.Split(s, "\n")
@@ -282,7 +287,7 @@ // Render) so the viewport's own padding/resets are covered too.
 func (m *Model) View() string {
 	out := m.vp.View()
 	if m.colors.Background != "" && (m.style == "" || m.style == "light" || m.style == "dark") {
-		out = fillBackground(out, m.colors.Background)
+		out = fillBackground(out, m.colors.Background, m.colors.Text)
 	}
 	return out
 }
internal/preview/theme_test.go +2 −2
@@ -37,7 +37,7 @@ // unstyled regions (table borders, cell padding) never fall back to the
 // terminal default — the table-background-between-themes bug.
 func TestPaperBackgroundReassertedAfterResets(t *testing.T) {
 	out := renderPreview(t, "| Key | Action |\n| --- | --- |\n| `Tab` | indent |\n")
-	paper := "\x1b[48;2;16;15;15m"
+	paper := "\x1b[38;2;206;205;195;48;2;16;15;15m" // text fg + paper bg
 	resets := strings.Count(out, "\x1b[0m")
 	reasserted := strings.Count(out, "\x1b[0m"+paper)
 	if resets == 0 {
@@ -53,7 +53,7 @@ // regions fall back to the terminal background (cream-over-dark / dark-over-light
 // bleed reported on real terminals).
 func TestBareResetAlsoReassertsPaper(t *testing.T) {
 	out := renderPreview(t, "# glint\n\n| Key | Action |\n| --- | --- |\n| `Tab` | indent |\n")
-	paper := "\x1b[48;2;16;15;15m"
+	paper := "\x1b[38;2;206;205;195;48;2;16;15;15m" // text fg + paper bg
 	// Walk every ESC[m occurrence; each must be immediately followed by paper.
 	for i := 0; ; {
 		j := strings.Index(out[i:], "\x1b[m")