-
Notifications
You must be signed in to change notification settings - Fork 494
Migrate compact logs run tables from tabwriter to console.RenderTable #50850
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ab27e9b
4644d12
c307dee
7b890ee
5be7228
f40df90
9e3d8ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # ADR-50850: Migrate CLI Table Rendering from tabwriter to console.RenderTable | ||
|
|
||
| **Date**: 2026-08-06 | ||
| **Status**: Draft | ||
| **Deciders**: pelikhan (PR author), copilot-swe-agent (implementation) | ||
|
|
||
| --- | ||
|
|
||
| ### Context | ||
|
|
||
| `pkg/cli/logs_format_compact.go` rendered its two `[runs]` tables using `text/tabwriter`, writing tab-delimited lines directly to the output writer. Every other table in the CLI used the centralized `console.RenderTable` / `console.TableConfig` API, which provides lipgloss-based borders, automatic TTY detection, and consistent plain-text degradation on non-TTY writers (pipes, CI logs). The divergence meant the compact logs formatter had different visual output, bypassed the project's shared styling machinery, and required manual flush-error handling (`tw.Flush()`) not present elsewhere. | ||
|
|
||
| ### Decision | ||
|
|
||
| We will migrate both tabwriter-backed tables in `renderLogsCompactToWriter` and `renderLogsCompactVerboseToWriter` to `console.RenderTable(console.TableConfig{...})`. All column headers, ordering, fallback values (`-` for empty fields), filtering of `skipped`/`cancelled` runs, and numeric formatting are preserved; only the alignment mechanism changes from tab-padding to lipgloss borders. | ||
|
|
||
| ### Alternatives Considered | ||
|
|
||
| #### Alternative 1: Keep text/tabwriter | ||
|
|
||
| The existing tabwriter approach is simple and has no runtime dependencies beyond the standard library. However, it produces plain tab-aligned output that does not adapt to TTY vs. non-TTY contexts the same way `console.RenderTable` does, creates visible inconsistency with every other CLI table, and requires manual flush-error handling. Accepting this inconsistency indefinitely is a maintenance burden and makes the codebase harder to reason about. | ||
|
|
||
| #### Alternative 2: Write a Custom Formatter Matching console.RenderTable Behavior | ||
|
|
||
| A custom formatter could in theory produce identical output without the `console` package dependency. However, this would duplicate the TTY-detection and border-rendering logic already centralized in `console.RenderTable`, creating two sources of truth for the same behavior and increasing the risk of divergence over time. No material benefit justifies the extra code. | ||
|
|
||
| ### Consequences | ||
|
|
||
| #### Positive | ||
| - Compact logs tables now use the same bordered rendering style as all other CLI tables, eliminating a visible inconsistency in the tool's output. | ||
| - TTY-detection and plain-text degradation are handled automatically by `console.RenderTable`; no manual flush-or-error pattern is needed. | ||
| - The `text/tabwriter` import and both `tw.Flush()` error-logging branches are removed, reducing code surface area. | ||
|
|
||
| #### Negative | ||
| - Output format changes from whitespace-aligned columns to lipgloss box-drawing characters (`╭─┬─╮`, etc.), which is a breaking change for any downstream consumer parsing the raw text of these tables. | ||
| - Box-drawing border glyphs increase per-row character count, raising token density when this output is consumed by LLMs or agents that read compact logs. The PR body explicitly calls this out as a deliberate tradeoff worth monitoring. | ||
|
|
||
| #### Neutral | ||
| - A new test file `logs_format_compact_test.go` is added to cover bordered rendering, non-TTY ANSI-escape degradation, and `skipped`/`cancelled` run exclusion — behavior that was previously untested. | ||
|
|
||
| --- | ||
|
|
||
| *ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| //go:build !integration | ||
|
|
||
| package cli | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "strings" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func compactTestData() LogsData { | ||
| return LogsData{ | ||
| Summary: LogsSummary{TotalRuns: 1}, | ||
| Runs: []RunData{{ | ||
| RunID: 1234567, | ||
| WorkflowName: "Logs", | ||
| WorkflowPath: ".github/workflows/logs.lock.yml", | ||
| EngineID: "copilot", | ||
| Status: "completed", | ||
| Conclusion: "success", | ||
| Duration: "1m2s", | ||
| TokenUsage: 1200, | ||
| Turns: 4, | ||
| Event: "push", | ||
| Actor: "octocat", | ||
| Branch: "main", | ||
| CreatedAt: time.Now(), | ||
| }}, | ||
| } | ||
| } | ||
|
|
||
| func TestRenderLogsCompactRendersRunsTableWithBorders(t *testing.T) { | ||
| t.Parallel() | ||
| var buf bytes.Buffer | ||
| renderLogsCompactToWriter(&buf, compactTestData()) | ||
| out := buf.String() | ||
|
|
||
| assert.Contains(t, out, "[runs]") | ||
| assert.Contains(t, out, "╭") | ||
| assert.Contains(t, out, "RUNID") | ||
| assert.Contains(t, out, "1234567") | ||
| assert.Contains(t, out, "logs") | ||
| assert.NotContains(t, out, "\x1b[", "non-TTY output should degrade to plain text") | ||
| } | ||
|
|
||
| func TestRenderLogsCompactVerboseRendersRunsTableWithBorders(t *testing.T) { | ||
| t.Parallel() | ||
| var buf bytes.Buffer | ||
| renderLogsCompactVerboseToWriter(&buf, compactTestData()) | ||
| out := buf.String() | ||
|
|
||
| assert.Contains(t, out, "[runs]") | ||
| assert.Contains(t, out, "╭") | ||
| assert.Contains(t, out, "CLASS") | ||
| assert.Contains(t, out, "1234567") | ||
| assert.NotContains(t, out, "\x1b[", "non-TTY output should degrade to plain text") | ||
| } | ||
|
|
||
| func TestRenderLogsCompactSkipsSkippedAndCancelledRuns(t *testing.T) { | ||
| t.Parallel() | ||
| data := compactTestData() | ||
| data.Runs = append(data.Runs, | ||
| RunData{RunID: 222, WorkflowName: "skipped-wf", Status: "skipped", CreatedAt: time.Now()}, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The 💡 Suggested additionAdd two more rows that set RunData{RunID: 444, WorkflowName: "skipped-c", Conclusion: "skipped", CreatedAt: time.Now()},
RunData{RunID: 555, WorkflowName: "cancelled-c", Conclusion: "cancelled", CreatedAt: time.Now()},Then assert: assert.NotContains(t, out, "444")
assert.NotContains(t, out, "555")@copilot please address this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in 5be7228. The test now adds |
||
| RunData{RunID: 333, WorkflowName: "cancelled-wf", Status: "cancelled", CreatedAt: time.Now()}, | ||
| RunData{RunID: 444, WorkflowName: "skipped-c", Conclusion: "skipped", CreatedAt: time.Now()}, | ||
| RunData{RunID: 555, WorkflowName: "cancelled-c", Conclusion: "cancelled", CreatedAt: time.Now()}, | ||
| ) | ||
|
|
||
| var buf bytes.Buffer | ||
| renderLogsCompactToWriter(&buf, data) | ||
| out := buf.String() | ||
|
|
||
| assert.NotContains(t, out, "222") | ||
| assert.NotContains(t, out, "333") | ||
| assert.NotContains(t, out, "444") | ||
| assert.NotContains(t, out, "555") | ||
| assert.Equal(t, 1, strings.Count(out, "1234567")) | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/codebase-design]
console.RenderTablereturns a string that may or may not end with\n;fmt.Fprintis used here, but the section that follows ([errors]) is printed withfmt.Fprintln. IfRenderTablealready appends a trailing newline the output is correct, but if it doesn't the[errors]header will be run on directly after the closing border with no blank line. Worth adding a guard or verifying the contract in theRenderTabledocs.💡 Suggested guard
Or, if
RenderTableguarantees a trailing newline, add a comment to that effect so future readers don't have to trace into the package.@copilot please address this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 5be7228. I added an explicit comment at both
console.RenderTablecallsites inpkg/cli/logs_format_compact.godocumenting the trailing-newline contract so section headers remain separated without adding extra guards.