Skip to content

Route remaining compile stderr styling through console helpers#43586

Open
pelikhan with Copilot wants to merge 3 commits into
mainfrom
copilot/replace-direct-styles-render-calls
Open

Route remaining compile stderr styling through console helpers#43586
pelikhan with Copilot wants to merge 3 commits into
mainfrom
copilot/replace-direct-styles-render-calls

Conversation

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Two compile CLI paths were still calling styles.*.Render() directly instead of using the shared console styling layer. This change removes those last direct render calls in compile_schedule_calendar.go and compile_stats.go while preserving existing stderr/TTY behavior.

  • What changed

    • Added narrow stderr-specific console helpers for arbitrary styled text:
      • console.FormatTableHeaderStderr
      • console.FormatErrorTextStderr
    • Both helpers delegate through the existing applyStyleWithTTY path rather than rendering styles directly at call sites.
  • CLI call site cleanup

    • pkg/cli/compile_schedule_calendar.go
      • Replaced direct styles.TableHeader.Render(...) usage for the hour header row and day labels with console.FormatTableHeaderStderr(...).
    • pkg/cli/compile_stats.go
      • Replaced direct styles.Error.Render(...) usage for oversized workflow rows with console.FormatErrorTextStderr(...).
  • Behavior preserved

    • Styling still applies only when stderr is a TTY.
    • Non-TTY output remains plain text, including the existing prefix behavior for large workflows.
  • Compatibility

    • Added matching no-op WASM stubs for the new console helpers.
    • Added focused console formatting tests covering TTY and non-TTY paths.
// Before
workflowName = styles.Error.Render("✗ ") + styles.Error.Render(stats.Workflow)

// After
workflowName = console.FormatErrorTextStderr("✗ " + stats.Workflow)

Copilot AI and others added 2 commits July 5, 2026 16:14
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor styles render calls to applyStyleWithTTY Route remaining compile stderr styling through console helpers Jul 5, 2026
Copilot AI requested a review from pelikhan July 5, 2026 16:18
@pelikhan pelikhan marked this pull request as ready for review July 5, 2026 16:24
Copilot AI review requested due to automatic review settings July 5, 2026 16:24

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

This PR finishes migrating remaining compile-related stderr styling call sites to the shared pkg/console formatting layer, avoiding direct styles.*.Render() usage while keeping TTY-aware behavior for stderr output.

Changes:

  • Added stderr-focused console helpers (FormatTableHeaderStderr, FormatErrorTextStderr) that route through applyStyleWithTTY(..., isStderrTTY).
  • Updated compile CLI outputs to use these helpers instead of direct style rendering in compile_schedule_calendar.go and compile_stats.go.
  • Added unit tests covering TTY vs non-TTY formatting behavior, plus WASM no-op stubs for the new helpers.
Show a summary per file
File Description
pkg/console/console.go Adds stderr-specific formatting helpers for table headers and error-styled text.
pkg/console/console_wasm.go Adds WASM stubs for the new stderr helpers.
pkg/console/console_formatting_test.go Adds focused tests for the new TTY-gated formatting helpers.
pkg/cli/compile_stats.go Routes oversized-workflow error styling through the new console helper.
pkg/cli/compile_schedule_calendar.go Routes header/label styling through the new stderr table header helper.

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: 1
  • Review effort level: Low

Comment thread pkg/console/console.go
@@ -298,6 +307,15 @@ func FormatErrorMessage(message string) string {
return applyStyle(styles.Error, "✗ ") + message
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

No ADR enforcement needed: PR #43586 does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (60 additions across 5 files, threshold is 100).

@github-actions github-actions Bot mentioned this pull request Jul 5, 2026
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 85/100 — Excellent

Analyzed 4 test(s): 4 design, 0 implementation, 0 violation(s).

📊 Metrics (4 tests)
Metric Value
Analyzed 4 (Go: 4, JS: 0)
✅ Design 4 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 2 (50%)
Duplicate clusters 0
Inflation No (36 test lines / 18 prod lines = 2.0×, threshold >2:1)
🚨 Violations 0
Test File Classification Issues
TestFormatTableHeaderWithTTY / plain text when not tty console_formatting_test.go:291 design_test / behavioral_contract None
TestFormatTableHeaderWithTTY / styled text when tty console_formatting_test.go:297 design_test / behavioral_contract None
TestFormatErrorTextWithTTY / plain text when not tty console_formatting_test.go:307 design_test / behavioral_contract None
TestFormatErrorTextWithTTY / styled text when tty console_formatting_test.go:313 design_test / behavioral_contract None

Verdict

Passed. 0% implementation tests (threshold: 30%). No violations. All four new tests verify user-visible TTY-conditional styling behavior: the non-TTY subtest asserts the plain-text passthrough contract, and the TTY subtest asserts the expected lipgloss-styled output — both are behavioral contracts that would catch regressions in styling dispatch logic.

References:

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 57.5 AIC · ⌖ 12.3 AIC · ⊞ 6.8K ·
Comment /review 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.

✅ Test Quality Sentinel: 85/100. 0% implementation tests (threshold: 30%). No violations.

@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: Route remaining compile stderr styling through console helpers

Clean refactor that correctly consolidates the two remaining direct styles.*.Render() call sites into the shared console layer.

Correctness ✅ — Both new helpers (FormatTableHeaderStderr, FormatErrorTextStderr) delegate through applyStyleWithTTY(..., isStderrTTY), exactly mirroring the pattern used by all other *Stderr helpers in this file. Behavior is identical to the old inline if isTerminal branches.

Behavioral edge ✅ — compile_stats.go: the previous non-TTY branch left fileSize unstyled entirely. The new path passes the already-formatted file-size string through FormatErrorTextStderr, which returns it unchanged on non-TTY — correct.

WASM stubs ✅ — Both new public functions have matching no-op stubs in console_wasm.go, consistent with every other stub in that file.

Tests ✅ — TestFormatTableHeaderWithTTY and TestFormatErrorTextWithTTY cover both TTY and non-TTY paths against the internal helpers, following the existing test structure.

Minor observation (non-blocking): compile_schedule_calendar.go still calls console.FormatInfoMessage (stdout-TTY variant) at line 237 for output sent to stderr. This pre-existing inconsistency is outside the diff scope but worth a follow-up to align with FormatInfoMessageStderr.

No blocking issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 51.8 AIC · ⌖ 5.81 AIC · ⊞ 4.9K

@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 /codebase-design and /tdd — commenting only; no blocking issues, but a few naming and test-coverage items worth addressing.

📋 Key Themes & Highlights

Key Themes

  • Naming inconsistency: FormatErrorTextStderr uses Text while all other paired helpers use Message, and it silently omits the prefix that FormatErrorMessage includes. This is the most likely source of future misuse.
  • Public API untested: New tests exercise private helpers (formatTableHeaderWithTTY, formatErrorTextWithTTY) but the exported functions themselves have no coverage, leaving the TTY-check wiring unverified.
  • Single-render vs. dual-render behaviour: The old code called styles.Error.Render() twice (prefix + name separately); the new code renders them in a single call. Worth a targeted TTY-mode assertion to confirm no visual difference.
  • WASM stub ordering: Two stubs inserted out-of-sequence in the otherwise ordered block — minor but adds diff noise.

Positive Highlights

  • ✅ Clean elimination of duplicated inline TTY branching — the call sites are now clearly simpler.
  • applyStyleWithTTY reuse is correct and consistent with every other helper in the module.
  • ✅ WASM stubs are present and correct (return identity / no-op).
  • ✅ Behaviour preserved: non-TTY output stays plain text, prefix still appears at the call site.

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

Comment thread pkg/cli/compile_stats.go
// In non-TTY mode, just add the icon without color
workflowName = "✗ " + stats.Workflow
}
workflowName = console.FormatErrorTextStderr("✗ " + stats.Workflow)

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.

[/codebase-design] The old code rendered "✗ " and stats.Workflow as two separate styles.Error.Render() calls; the new code concatenates them first, then passes the combined string to FormatErrorTextStderr. With lipgloss styles that include padding or width constraints, rendering one concatenated string vs. two independently styled chunks can produce different visual output — e.g. the error prefix and workflow name may share the same styled block width rather than each being independently sized.

💡 Suggested verification

Add a TTY-mode compile_stats integration test (capturing stderr via an os.Pipe) that asserts the large-workflow row contains the styled ✗ workflow string. Compare against styles.Error.Render("✗ " + stats.Workflow) to confirm the single-render path produces the expected ANSI sequence.

If style definitions never include padding/truncation this is a no-op, but an explicit test locks in that invariant.

@copilot please address this.

Comment thread pkg/console/console.go
}

func formatTableHeaderWithTTY(text string, ttyCheck func() bool) string {
return applyStyleWithTTY(styles.TableHeader, text, ttyCheck)

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.

[/codebase-design] FormatTableHeaderStderr does not follow the same naming symmetry as the other helpers. All existing paired helpers are FormatXMessage / FormatXMessageStderr (e.g., FormatSuccessMessage / FormatSuccessMessageStderr, FormatInfoMessage / FormatInfoMessageStderr). The new function is FormatTableHeaderStderr with no FormatTableHeader stdout counterpart, and the name says "text" vs. "message" in FormatErrorTextStderr. The inconsistency will make the API harder to discover and navigate.

💡 Suggested rename

Follow the existing pattern:

// Consistent with FormatSuccessMessage / FormatSuccessMessageStderr
func FormatTableHeaderStderr(text string) string // keep name, but also add stdout variant if needed
func FormatErrorStderr(text string) string       // instead of FormatErrorTextStderr

At minimum, align the ...Text... vs. ...Message... naming: either rename FormatErrorTextStderrFormatErrorMessageStderr, or document why this helper intentionally omits the prefix icon that FormatErrorMessage adds.

@copilot please address this.

Comment thread pkg/console/console.go
return formatErrorTextWithTTY(text, isStderrTTY)
}

func formatErrorTextWithTTY(text string, ttyCheck func() bool) 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.

[/codebase-design] FormatErrorMessage (line 305) adds a "✗ " prefix; FormatErrorTextStderr does not. This creates two public styling paths for the Error style with subtly different semantics, and the distinction only shows up in the comment. A caller who reaches for FormatErrorText... when they needed the prefixed version will silently produce wrong output.

💡 Suggestion

Make the semantic difference explicit in the name or doc comment. Options:

  1. Rename to FormatErrorRawStderr to signal no prefix is added.
  2. Add a doc comment that explicitly calls out: // Unlike FormatErrorMessage, this function does not prepend a "✗ " prefix.
  3. If the only call site already prepends the prefix manually ("✗ " + stats.Workflow), consider whether a prefixed variant (FormatErrorMessageStderr) would be cleaner and remove the manual concatenation at the call site.

@copilot please address this.

t.Fatalf("formatTableHeaderWithTTY() = %q, want %q", result, "Header")
}
})

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.

[/tdd] The tests directly exercise the unexported formatTableHeaderWithTTY and formatErrorTextWithTTY helpers — but the public surface (FormatTableHeaderStderr, FormatErrorTextStderr) is not tested at all. If a future change swaps the isStderrTTY function for a different TTY check, the public function will silently break while these tests still pass.

💡 Suggestion

Add smoke tests for the exported functions to lock in the wiring between public function → private helper → TTY check. They do not need to simulate a real TTY; even a non-TTY assertion is enough:

func TestFormatTableHeaderStderr_NonTTY(t *testing.T) {
    // Stdout/stderr are not terminals in test, so plain text is expected
    got := FormatTableHeaderStderr("Mon")
    if got != "Mon" {
        t.Errorf("FormatTableHeaderStderr() = %q, want %q", got, "Mon")
    }
}

This protects the public API contract separately from the internal implementation.

@copilot please address this.


func FormatSuccessMessage(message string) string { return "✓ " + message }
func FormatInfoMessage(message string) string { return "i " + message }
func FormatTableHeaderStderr(text string) string { 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.

[/codebase-design] The two new WASM stubs are inserted in the middle of the alphabetically-ordered block, breaking the ordering. FormatTableHeaderStderr is at line 70 (between FormatInfoMessage and FormatWarningMessage) and FormatErrorTextStderr is at line 73 (between FormatErrorMessage and FormatLocationMessage). This is a minor but recurring source of diff noise and makes it harder to verify completeness at a glance.

💡 Suggestion

Group the two stderr-specific stubs together or keep them adjacent to their non-stderr counterparts. If the block is intended to be alphabetical, place them in alpha order; otherwise add a short comment separating stderr-specific stubs from the prefix-bearing ones.

@copilot please address this.

@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.

Three issues to address before merging

The refactor direction is correct — removing direct styles.*.Render() calls from call sites is the right call. But the new console API has two structural deficiencies, and one test file only covers internal helpers while leaving public entrypoint wiring untested.

🔍 Blocking findings (3)

1. Incomplete API pairs (console.go)FormatTableHeaderStderr and FormatErrorTextStderr are Stderr-only helpers with no stdout counterpart, violating the FormatXxx/FormatXxxStderr pairing that every other function in this file follows. Future callers writing to stdout will be forced to reach into styles directly.

2. Untested public entrypoints (console_formatting_test.go) — The new tests only inject fake TTY checkers into the unexported formatXxxWithTTY helpers. The exported FormatTableHeaderStderr/FormatErrorTextStderr functions are never called from any test, so incorrect TTY function wiring (e.g., isTTY instead of isStderrTTY) would be invisible.

3. Fragile single-render substitution (compile_stats.go) — Joining "✗ " + workflow name before a single Render() call is not semantically identical to the original two-call approach when styles.Error acquires layout properties (width, padding, margin) in the future. Needs a clarifying comment documenting the assumption.

🔎 Code quality review by PR Code Quality Reviewer · 192.9 AIC · ⌖ 5.68 AIC · ⊞ 5.4K
Comment /review to run again

Comment thread pkg/console/console.go
}

// FormatErrorTextStderr formats plain error-styled text for stderr output.
func FormatErrorTextStderr(text string) 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.

Broken API symmetry: FormatErrorTextStderr has no stdout sibling, but every other Stderr formatter in this package has one.

💡 Details and suggested fix

The established pattern throughout console.go is a matched pair:

Func FormatXxx(...)         // stdout (calls isTTY)
Func FormatXxxStderr(...)   // stderr (calls isStderrTTY)

This holds for FormatSuccessMessage/FormatSuccessMessageStderr, FormatInfoMessage/FormatInfoMessageStderr, FormatListItem/FormatListItemStderr, and FormatSectionHeader/FormatSectionHeaderStderr.

FormatErrorTextStderr breaks that contract — there is no FormatErrorText. Callers that need to style error text for stdout have no symmetric helper and must reach into styles directly, defeating the purpose of the abstraction.

FormatTableHeaderStderr in the same commit has the same gap.

Suggested fix — add the missing stdout counterparts:

func FormatErrorText(text string) string {
    return formatErrorTextWithTTY(text, isTTY)
}

func FormatTableHeader(text string) string {
    return formatTableHeaderWithTTY(text, isTTY)
}

And add the corresponding no-op WASM stubs that return text unchanged.

}
}

func TestFormatTableHeaderWithTTY(t *testing.T) {

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.

Test gap: the public FormatTableHeaderStderr / FormatErrorTextStderr entrypoints are never called, so a wrong TTY function wired inside them would pass all tests silently.

💡 Details and suggested fix

Both new tests (TestFormatTableHeaderWithTTY, TestFormatErrorTextWithTTY) inject a fake ttyCheck directly into the unexported formatXxxWithTTY helpers. They do not call the exported FormatTableHeaderStderr or FormatErrorTextStderr even once.

The only thing the tests verify is that applyStyleWithTTY works when given any arbitrary TTY function. They do not verify that the public API delegates to isStderrTTY rather than isTTY. If someone accidentally wrote:

func FormatTableHeaderStderr(text string) string {
    return formatTableHeaderWithTTY(text, isTTY)  // wrong TTY function
}

...the tests would still pass, and stderr output would silently use stdout TTY detection.

Suggested fix — add lightweight entrypoint tests that exercise the real functions, for example:

func TestFormatTableHeaderStderr_NonTTY(t *testing.T) {
    // isStderrTTY returns false in a CI environment
    got := FormatTableHeaderStderr("Header")
    if got != "Header" {
        t.Fatalf("FormatTableHeaderStderr() = %q; want plain text in non-TTY env", got)
    }
}

func TestFormatErrorTextStderr_NonTTY(t *testing.T) {
    got := FormatErrorTextStderr("boom")
    if got != "boom" {
        t.Fatalf("FormatErrorTextStderr() = %q; want plain text in non-TTY env", got)
    }
}

These work because CI never has a real TTY on stderr, guaranteeing the non-TTY path without mocking.

Comment thread pkg/cli/compile_stats.go
// In non-TTY mode, just add the icon without color
workflowName = "✗ " + stats.Workflow
}
workflowName = console.FormatErrorTextStderr("✗ " + stats.Workflow)

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.

Subtle behavioral change: the original code called styles.Error.Render("✗ ") + styles.Error.Render(stats.Workflow) as two independent render operations; the new code renders a single concatenated string "✗ " + stats.Workflow.

💡 Why this matters

The old code:

workflowName = styles.Error.Render("✗ ") + styles.Error.Render(stats.Workflow)

The new code:

workflowName = console.FormatErrorTextStderr("✗ " + stats.Workflow)

lipgloss.Style.Render on a multi-word string can behave differently from two separate renders when the style carries Width, MaxWidth, Padding, or Margin properties, because those layout directives apply to the rendered block as a whole. With today's styles.Error (bold + foreground only), the output is identical. But this is now fragile: any future addition of layout properties to styles.Error will silently produce truncated or misaligned output for this combined string without a compile-time signal.

The PR description even calls out the old approach as a pattern to replace, but the behavioral equivalence is style-configuration-dependent — it should be noted as an assumption rather than treated as a safe automatic substitution.

If you want to keep the single-call form, add a comment:

// styles.Error carries no layout properties, so a single Render is equivalent
// to the former two-call approach.
workflowName = console.FormatErrorTextStderr("✗ " + stats.Workflow)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[plan] Replace direct styles.X.Render() calls in compile_schedule_calendar.go and compile_stats.go with applyStyleWithTTY

3 participants