Skip to content

feat(styles,console,colorwriter): hoist palette color.Color vars and honor NO_COLOR on stdout#46197

Merged
pelikhan merged 8 commits into
mainfrom
copilot/go-fan-review-lipgloss-v2
Jul 18, 2026
Merged

feat(styles,console,colorwriter): hoist palette color.Color vars and honor NO_COLOR on stdout#46197
pelikhan merged 8 commits into
mainfrom
copilot/go-fan-review-lipgloss-v2

Conversation

Copilot AI commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Two low-priority cleanup items from the lipgloss v2 Go Fan review: eliminate redundant hex→color.Color parsing in huh_theme.go, and fix NO_COLOR/COLORTERM/TERM not being honored on styled stdout output.

Hoist hex constants to package-level color.Color vars (pkg/styles)

huh_theme.go was calling lipgloss.Color(hexColorXxx) on every HuhTheme(isDark) invocation — the same parsing theme.go already does once at package init.

  • Add 22 package-level color.Color vars (colorErrorLight, colorErrorDark, …) derived once from the hexColor* constants.
  • theme.go's adaptiveColor structs and huh_theme.go's lightDark() calls now both reference these shared vars.
  • Add cross-link comment making the dual-path design (startup-probe adaptiveColor vs per-render LightDark) explicit.

Honor color profile on stdout (pkg/colorwriter, pkg/console)

applyStyle gated on a TTY check only — NO_COLOR=1 with a TTY still emitted raw ANSI.

  • Add colorwriter.Stdout() (symmetric with existing Stderr()).
  • Add colorwriter.Degrade(s, environ): routes a rendered string through a colorprofile.Writer backed by a strings.Builder, downgrading/stripping ANSI per env without writing to stdout directly.
  • applyStyle now calls colorwriter.Degrade() when stdout is a TTY, so all string-returning format helpers honor NO_COLOR, COLORTERM, and TERM.
  • applyStyleWithTTY (stderr-bound helpers) is unchanged; those paths already get colorprofile degradation at print time via stderrWriter().
  • Wasm stubs added for both new colorwriter functions (pass-through).
// Before: styled even when NO_COLOR=1
func applyStyle(style lipgloss.Style, text string) string {
    return applyStyleWithTTY(style, text, isTTY)
}

// After: degrades through colorprofile when on TTY
func applyStyle(style lipgloss.Style, text string) string {
    if !isTTY() {
        return text
    }
    return colorwriter.Degrade(style.Render(text), os.Environ())
}

Copilot AI and others added 2 commits July 17, 2026 10:00
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…colorprofile degradation for stdout

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Review go module charm.land/lipgloss/v2 feat(styles,console,colorwriter): hoist palette color.Color vars and honor NO_COLOR on stdout Jul 17, 2026
Copilot AI requested a review from pelikhan July 17, 2026 10:15
@pelikhan
pelikhan marked this pull request as ready for review July 18, 2026 05:30
Copilot AI review requested due to automatic review settings July 18, 2026 05:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hoists shared palette colors and adds environment-aware ANSI degradation for stdout formatting.

Changes:

  • Reuses package-level palette values across themes.
  • Adds stdout and string degradation helpers.
  • Applies color-profile handling to selected console output.
Show a summary per file
File Description
pkg/styles/theme.go Defines shared palette colors.
pkg/styles/huh_theme.go Reuses shared colors.
pkg/console/console.go Adds stdout color degradation.
pkg/colorwriter/colorprofile_writer.go Adds Stdout and Degrade.
pkg/colorwriter/colorprofile_writer_wasm.go Adds wasm pass-through stubs.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 5/5 changed files
  • Comments generated: 4
  • Review effort level: Medium

Comment thread pkg/console/console.go Outdated
Comment on lines +42 to +44
// applyStyleWithTTY conditionally applies styling based on a provided TTY check.
// Used for stderr-bound helpers where the rendered string is subsequently printed
// through stderrWriter() (which also applies colorprofile degradation).
Comment thread pkg/console/console.go Outdated
if !isTTY() {
return text
}
return colorwriter.Degrade(style.Render(text), os.Environ())
Comment on lines +36 to +40
func Degrade(s string, environ []string) string {
var buf strings.Builder
w := colorprofile.NewWriter(&buf, environ)
fmt.Fprint(w, s)
return buf.String()
Comment on lines +26 to +30
// Stdout returns a color-profile-aware writer for os.Stdout using the current
// process environment.
func Stdout() io.Writer {
return New(os.Stdout, os.Environ())
}
@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. Test Quality Sentinel skipped.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd and /codebase-design — requesting changes on test coverage and incomplete NO_COLOR propagation.

📋 Key Themes & Highlights

Key Themes

  • Incomplete NO_COLOR coverage: applyStyle is now degraded, but RenderTable and the applyStyleWithTTY-backed stdout helpers are not — the fix is partial. See inline comment on console.go:39.
  • Missing tests for Degrade: The new colorwriter.Degrade path has no test coverage despite an existing test file. See inline comment on colorprofile_writer.go:40.
  • Stale docs: pkg/colorwriter/README.md still only documents New and Stderr. See inline comment on colorprofile_writer.go:30.
  • applyStyleWithTTY scope: The comment claiming it is "stderr-bound" is inaccurate — several stdout-facing helpers also use it. See inline comment on console.go:44.

Positive Highlights

  • ✅ Clean extraction of 22 color.Color package-level vars in theme.go — eliminates repeated hex-to-color parsing and creates a real single source of truth.
  • huh_theme.go simplification is mechanical and easy to verify.
  • ✅ Wasm stubs are symmetric and correctly pass-through.
  • ✅ Good PR description with before/after snippet.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 13.3 AIC · ⌖ 4.39 AIC · ⊞ 6.7K
Comment /matt to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: feat(styles,console,colorwriter): hoist palette color.Color vars and honor NO_COLOR on stdout

The palette-hoisting in pkg/styles is clean and correct — a straightforward refactor with no behavioral risk.

The NO_COLOR / color-profile fix in pkg/console and pkg/colorwriter is on the right track, but four non-blocking issues are worth addressing before considering this complete:

  1. applyStyleWithTTY is mischaracterized as stderr-only — several stdout-bound helpers (FormatSuccessMessage, FormatInfoMessage, etc.) call it directly; the comment claiming it is stderr-bound is incorrect and the refactor is incomplete.
  2. RenderTable still emits raw ANSI — its header/cell/border styles bypass Degrade entirely, so NO_COLOR=1 with a TTY still produces colored table output.
  3. No test coverage for Degrade — the package already has spec_test.go; a round-trip test with NO_COLOR=1 and a non-trivial ANSI input would prevent regressions.
  4. README is stalepkg/colorwriter/README.md documents only New and Stderr and should include Stdout and Degrade.

These are correctness/completeness gaps rather than blockers. The palette hoist can land as-is; the color-profile path deserves the follow-ups above.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 12.3 AIC · ⌖ 4.38 AIC · ⊞ 5K

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two non-blocking issues worth fixing

The changes are directionally correct — honoring NO_COLOR/COLORTERM/TERM on stdout is the right fix, and consolidating the palette into package-level vars eliminates redundant hex parsing. Two implementation concerns:

  1. Performance: os.Environ() is copied and colorprofile.NewWriter re-parses the environment on every applyStyle call. In output-heavy paths this is O(N × env-size) unnecessary work. Cache the env snapshot with sync.Once (see inline comment).

  2. Correctness risk: Degrade calls buf.String() immediately after fmt.Fprint(w, ...) with no flush or close on the colorprofile.Writer. If the library buffers internally (or a future version does), the returned string could be silently truncated. Either check for io.Closer / io.Flusher or document the no-buffer contract (see inline comment).

🔎 Code quality review by PR Code Quality Reviewer · 39.1 AIC · ⌖ 5.27 AIC · ⊞ 5.6K
Comment /review to run again

var buf strings.Builder
w := colorprofile.NewWriter(&buf, environ)
fmt.Fprint(w, s)
return buf.String()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible incomplete output if colorprofile.Writer buffers: buf.String() is called immediately after fmt.Fprint(w, s) with no flush or close, so if colorprofile.Writer buffers internally, the returned string may be silently truncated or empty.

💡 Suggested fix

colorprofile.Writer wraps an io.Writer and may implement io.Closer or have a Flush method to drain any internal buffer before the underlying writer holds the final bytes. Without seeing the contract guaranteed by colorprofile.NewWriter, the safe pattern is:

func Degrade(s string, environ []string) string {
	var buf strings.Builder
	w := colorprofile.NewWriter(&buf, environ)
	fmt.Fprint(w, s)
	if c, ok := w.(io.Closer); ok {
		_ = c.Close()
	}
	return buf.String()
}

Or, if the library guarantees synchronous pass-through writes (no buffering), add a comment asserting that contract so the next reader does not have to reverse-engineer it.

Comment thread pkg/console/console.go
return applyStyleWithTTY(style, text, isTTY)
if !isTTY() {
return text
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os.Environ() copied and env re-parsed on every styled token: applyStyle calls colorwriter.Degrade(style.Render(text), os.Environ()) — copying the full process environment and re-parsing the color profile on each invocation, including in render loops.

💡 Suggested fix

Cache the environment snapshot and ideally the resolved profile at first use:

var (
	stdoutEnvOnce sync.Once
	stdoutEnv     []string
)

func getStdoutEnv() []string {
	stdoutEnvOnce.Do(func() { stdoutEnv = os.Environ() })
	return stdoutEnv
}

func applyStyle(style lipgloss.Style, text string) string {
	if !isTTY() {
		return text
	}
	return colorwriter.Degrade(style.Render(text), getStdoutEnv())
}

This brings the per-call cost from O(env-size) down to a single pointer load after the first call.

@github-actions

Copy link
Copy Markdown
Contributor

Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (106 new lines across pkg/) but does not have a linked Architecture Decision Record (ADR).

Draft ADR committed: docs/adr/46197-hoist-palette-vars-and-honor-no-color-stdout.md — review and complete it before merging.

This PR cannot merge until an ADR is linked in the PR body.

What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff

  2. Complete the missing sections — add context the AI could not infer (e.g., confirm the exact Deciders), refine the decision rationale, and verify the alternatives reflect the options your team actually considered

  3. Commit the finalized ADR to docs/adr/ on your branch

  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-46197: Hoist Palette Color Vars and Honor NO_COLOR on Stdout

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 46197-hoist-palette-vars-and-honor-no-color-stdout.md for PR #46197).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 49.8 AIC · ⌖ 13.5 AIC · ⊞ 4.3K ·
Comment /review to run again

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill and address the unresolved review feedback before handing this back to maintainers.

Open review feedback to address:

Please cover the incomplete NO_COLOR handling, add/refresh the needed tests and docs, rerun relevant validation/checks, and summarize any remaining blockers for maintainers.

Generated by PR Sous Chef: https://github.com/github/gh-aw/actions/runs/29636316820

Generated by 👨‍🍳 PR Sous Chef · 37.1 AIC · ⌖ 7.15 AIC · ⊞ 6K ·
Comment /souschef to run again

Copilot AI and others added 3 commits July 18, 2026 08:02
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill and address the unresolved review feedback before handing this back to maintainers.

Open review feedback to address:...

Addressed in 3d36acb.

Completed the remaining stdout NO_COLOR propagation, added regression coverage for colorwriter.Degrade plus console/table stdout behavior, refreshed pkg/colorwriter/README.md, and tightened the follow-up review nits around cached env usage and Degrade error handling.

Validation run locally:

  • make fmt
  • go test ./pkg/colorwriter ./pkg/console
  • make lint
  • make test-unit
  • make test
  • make agent-report-progress

parallel_validation reported Code Review success after the first push; CodeQL timed out and instructed not to rerun. CI on the latest agent push is still stale and needs a maintainer re-trigger before merge.

@pelikhan
pelikhan merged commit 9ecb2eb into main Jul 18, 2026
28 of 29 checks passed
@pelikhan
pelikhan deleted the copilot/go-fan-review-lipgloss-v2 branch July 18, 2026 08:57
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.13

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[go-fan] Go Module Review: charm.land/lipgloss/v2

4 participants