Skip to content

Make CLI lesson output easier to read - #227

Open
skovranek wants to merge 3 commits into
mainfrom
8533_ms_feat_readable_cli_output
Open

skovranek wants to merge 3 commits into
mainfrom
8533_ms_feat_readable_cli_output

Conversation

@skovranek

@skovranek skovranek commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

resolves bootdotdev/go-api-gate#8533.

Improve CLI output readability with numbered step headings, clearer grouping, and configurable colors.

  • Keep lesson headers visible in the final report.
  • Show commands, requests, and actual output during runs without checks or grading.
  • Keep successful submissions compact, with detailed output for failures or --verbose.
  • Show expected and actual values for checks, including JSON values.
  • Include failed-step context in the final summary.
  • Use semantic color settings with coordinated defaults.
  • Add local-test --run for previewing execution without grading.

Color configuration

Colors can be configured through bootdev config colors or the config file.

  • Rename gray, red, green, magenta, and yellow to secondary, fail, pass, accent, and warning.
  • Add header, subheader, and information.
  • Support ANSI palette indexes and hex colors.

Existing color-name settings must be renamed to preserve custom colors.

Run — correct application

All 33 steps are displayed with commands, requests, and actual output, without checks or grading labels. Screenshots are browser-rendered captures of real local CLI output.

Full run output — correct application

Long lesson run, correct application

Run — incorrect application

All 33 steps still appear. The changed checkout response is visible as output, but the run does not mark checks as passing or failing.

Full run output — incorrect application

Long lesson run, incorrect application

Submit — passing checks

Passing — default

All 33 successful steps are summarized.

Long lesson submit passing, default

Passing — verbose

All 33 steps are expanded with checks, response bodies, and captured variables.

Full verbose submit output — all checks pass

Long lesson submit passing, verbose

Submit — failing check

Failing — default

Steps 1–15 remain compact. Step 16 expands to show the request and its expected/actual HTTP status, followed by the failure summary.

Long lesson submit failing, default

Failing — verbose

The successful steps, failing step, and later ungraded checks retain their detailed output. The final summary identifies step 16.

Full verbose submit output — failure at step 16

Long lesson submit failing, verbose

@skovranek
skovranek force-pushed the 8533_ms_feat_readable_cli_output branch from fc48dc2 to 7464fc0 Compare November 10, 2026 16:45

@squashd squashd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this looks awesome — tokyo night for the people!

mostly nits/suggestions, and one smol regression. i'm assuming the images will get removed 🙏

Comment thread render/header.go
fmt.Fprintln(&str, lipgloss.NewStyle().Bold(true).Render(context.LessonTitle))
if context.BaseURL != "" {
fmt.Fprintf(&str, "%s %s\n", secondaryStyle.Render("Target:"), context.BaseURL)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you think we could add part of the override warning here? I like the simplification, just worried we'll have students who forgot they'd added an override

		fmt.Printf("Using overridden base_url: %v\n", overrideBaseURL)
		fmt.Printf("You can reset to the default with `bootdev config base_url --reset`\n\n")

Comment thread render/step.go
Comment on lines +61 to +64
heading := "Checks"
if isSubmit {
heading = "Checks not evaluated"
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we omit actual if it isn't evaluated?

Screenshot 2026-11-12 at 17 50 03

Comment thread render/http.go
func printHTTPRequestResult(result api.HTTPRequestResult) string {
if result.Err != "" {
return fmt.Sprintf(" Err: %v\n\n", result.Err)
return fmt.Sprintf("%s\n%s\n", warningStyle.Render("Request error"), indentOutput(result.Err, 2))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is "Request" meant as the name of the step type? I wonder if this could make someone think they didn't get the request (but it waws their response that was malformed)

Comment thread render/view.go
Comment on lines +65 to +69
if showAllDetails || (failed && m.finalized) {
dividerWidth := m.terminalWidth
if dividerWidth <= 0 {
dividerWidth = 80
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: The step title and command / request get repeated now. Do you think the summary could be shorter (maybe if not verbose)?

Screenshot 2026-11-12 at 18 07 51

Comment thread render/json.go
default:
return ""
}
expectedJSON, err := json.Marshal(expected)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

clanker pointed out this would now be html-escaped

Comment thread render/variables.go
Comment on lines +30 to +32
if title == "Variables Missing" {
headingText = warningStyle.Render(title + ":")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: matching on the string feels iffy

Comment thread render/step.go
Comment on lines +45 to +49
check := renderCheck(step, index, status)
check = renderStatus(status) + strings.TrimPrefix(check, status)
check = strings.ReplaceAll(check, "\n Expected:", "\n "+secondaryStyle.Render("Expected:"))
check = strings.ReplaceAll(check, "\n Actual:", "\n "+secondaryStyle.Render("Actual:"))
fmt.Fprintf(&str, "\n%s\n", indentOutput(check, 2))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

splitting on a string and reconstructing it for rendering feels a bit brittle. could it be set where it's built?

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.

2 participants