feat(console): render arduino-cli colour and collapse progress redraws - #1002
Conversation
Build output is captured from a pipe and pushed to the console verbatim, so
the two terminal control sequences arduino-cli uses were both mishandled.
**Carriage returns.** A download progress bar redraws by rewriting one line
with `\r`. Each redraw arrived as its own chunk and became its own timestamped
entry, so one core install produced hundreds of near-identical lines that
pushed the real output out of view:
[09:12:34]: ...54.94 MiB / 93.67 MiB [=====>-----] 58.65%
[09:12:35]: ...54.94 MiB / 93.67 MiB [=====>-----] 58.65%
[09:12:35]: ...57.68 MiB / 93.67 MiB [======>----] 61.58%
A chunk is now collapsed to the frame a terminal would leave on screen, and
the entry is marked `transient` while the line is still open. The next redraw
overwrites it; a trailing newline commits it and the following download starts
a fresh line. One live-updating line, as in a terminal.
**SGR colour.** arduino-cli colours its compile summary table (bright green
headers, yellow platform id, grey paths). The editor suppressed this with
`output.no_color` in `arduino-cli.yaml` — inherited from the 2022 Python
editor, which added `--no-color` because the raw `ESC[92m` bytes were printed
literally. The console parses SGR now, so the suppression is gone.
Colour is split off once, at the console slice: `message` always holds clean
text and `segments` carries the styling only when there was any. Search,
level filters and copy-to-clipboard keep working on `message` untouched — no
consumer besides the renderer learns that colour exists, and uncoloured logs
(the overwhelming majority) allocate nothing extra.
Dead code removed rather than left behind:
- `output.no_color` is dropped from `ARDUINO_DATA`, and existing configs are
migrated. The config was written once with `{ flag: 'wx' }` and skipped on
EEXIST forever after, so every install that ever ran an older build would
have kept colour off and made the new renderer unreachable. Reconciliation
is narrow and non-destructive: add missing board-manager URLs, drop
`no_color`, prune the `output` map only if it is left empty, and never touch
anything else. Uses the `yaml` Document API so user comments, ordering and
custom indexes survive; an unparseable config is left alone.
- `ArduinoCliConfigSchema` / `ArduinoCliConfig` deleted — a zod schema that
described the config's `no_color` shape and was imported by nothing.
Not a terminal emulator: cursor addressing, scroll regions and erase-in-line
are stripped rather than interpreted, because build output never uses them.
Tests: 29 new across the parser, the CR state machine, the slice's overwrite
rule and the config migration (including the upgrade-with-no_color path).
Verified against real captured arduino-cli bytes: 24 CR frames collapse, the
summary table maps to green/plain/green/plain/grey, and neither an escape nor
a carriage return survives into stored text. Full suite 6354 passing.
Paired with openplc-web (shared-core parity).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collapsing the carriage-return redraws put each download on a single log
entry, but a wide frame then wrapped into three visual lines, which loses
most of the benefit:
[11:50:32]: esp32:esp-rv32@2601 339.62 MiB / 562.71 MiB [==========
================================>------------------------
------] 60.35% 00m20s
Two causes, both addressed:
- **The bar is sized to a width that is not ours.** arduino-cli draws the
`[===>---]` bar against a width it guesses from its own environment —
roughly 150 glyphs — and there is no flag or env var to tell it otherwise
(COLUMNS, TERM and a PTY all make no difference; when it cannot guess it
simply omits the bar). The console panel is resizable and the side bar
moves, so no fixed width would hold either. `stripProgressBar` drops the
bar and keeps the numbers, which is exactly the compact form arduino-cli
itself emits when it cannot size one:
esp32:esp-x32@2601 44.48 MiB / 311.65 MiB 14.27% 00m26s
Nothing is lost — the bar is a redundant rendering of the percentage
printed beside it. Applied only to carriage-return redraws, so ordinary
bracketed output (`[MANUAL_OVERRIDE / body line 7]`, `array[0]`) is
untouched.
- **Progress lines wrapped like prose.** They now render `whitespace-pre`
with `overflow-x-auto`, so a frame that is still too wide scrolls within
its own line instead of breaking across several, and the scroll stays on
that one line rather than shifting the whole console. Ordinary output
keeps wrapping, which is what long compiler diagnostics want.
Tests: 8 new, built from the exact frame that wrapped (200 chars -> under
80) and covering the bracketed text that must not be touched.
Paired with openplc-web (shared-core parity).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This reverts commit 2dfbb55.
WalkthroughThe change reconciles existing Arduino CLI YAML files with shipped settings and removes obsolete ChangesArduino CLI configuration reconciliation
Console terminal output
Estimated code review effort: 4 (Complex) | ~45 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/backend/editor/services/user-service/data/__tests__/arduino-cli-config.test.ts`:
- Around line 19-20: Remove the prohibited type assertions in urlsOf and the
updated-config result checks. Add a requireUpdated helper that throws when the
update result is null, use a non-asserted type predicate to narrow parsed YAML
before accessing board_manager.additional_urls, and replace the affected result
assertions with requireUpdated-based handling.
In `@src/backend/editor/services/user-service/data/arduino-cli-config.ts`:
- Around line 28-35: Update shippedBoardManagerUrls to remove the parse(shipped)
type assertion and validate the parsed YAML value at runtime before accessing
board_manager.additional_urls. Add or reuse a type guard that confirms the
nested object shape and filters string URLs, while preserving the existing
empty-array fallback for invalid or missing data.
- Around line 53-76: Guard nested YAML operations in the configuration migration
with isMap checks: only access BOARD_MANAGER_URLS_PATH via getIn/setIn when the
board_manager parent is a map, and only call hasIn(NO_COLOR_PATH),
deleteIn(NO_COLOR_PATH), or delete('output') when output is a map. Preserve the
existing list merge and cleanup behavior for valid map nodes.
In `@src/backend/editor/services/user-service/index.ts`:
- Around line 171-177: Update the migration flow around
reconcileArduinoCliConfig to write updated content to a temporary file in the
same directory, then rename that file to pathToArduinoCliConfig only after the
write succeeds, preserving the existing early return when no update is needed
and ensuring the temporary file targets the same filesystem for atomic
replacement.
In `@src/frontend/components/_organisms/console/log.tsx`:
- Around line 89-92: Update the segment rendering in the log component so search
ranges are computed against the full message, then intersected with each
segment’s source range before rendering highlights. Preserve segment styling
while highlighting matches that cross boundaries, and add a render test covering
a search term spanning two adjacent segments.
In `@src/frontend/utils/__tests__/debugger-session.test.ts`:
- Around line 167-175: Remove the type assertion from createRedrawCollector by
declaring log with the callback type expected by logCompilerEvent when it is
defined. Return the typed log function directly while preserving its existing
writes behavior and optional redraw handling.
In `@src/frontend/utils/debugger-session.ts`:
- Around line 80-105: Update logCompilerEvent and its carriage-return handling
so complete CRLF sequences are normalized before redraw detection, while a
trailing \r is retained across events until the next chunk determines whether it
forms \r\n. Ensure CRLF is never marked as a redraw and add a regression test
covering successive events containing "Windows line\r" and "\n".
In `@src/frontend/utils/terminal-output.ts`:
- Around line 35-36: Extend CSI_PATTERN and the tokenizer used by stripAnsi and
parseAnsi to consume OSC sequences, including their terminated payloads, and
remove any remaining ESC control bytes so stored messages and rendered segments
contain no escape characters. Preserve existing CSI parsing behavior and add
coverage for at least one OSC sequence, such as a hyperlink.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 4c12d5eb-0288-491c-819c-4016c0648e91
📒 Files selected for processing (15)
src/backend/editor/compiler/types.tssrc/backend/editor/services/user-service/data/__tests__/arduino-cli-config.test.tssrc/backend/editor/services/user-service/data/arduino-cli-config.tssrc/backend/editor/services/user-service/data/types.tssrc/backend/editor/services/user-service/index.tssrc/frontend/components/_organisms/console/index.tsxsrc/frontend/components/_organisms/console/log.tsxsrc/frontend/store/__tests__/console-slice.test.tssrc/frontend/store/slices/console/slice.tssrc/frontend/store/slices/console/types.tssrc/frontend/utils/__tests__/debugger-session.test.tssrc/frontend/utils/__tests__/terminal-output.test.tssrc/frontend/utils/debugger-session.tssrc/frontend/utils/terminal-output.tssrc/middleware/shared/ports/types.ts
💤 Files with no reviewable changes (1)
- src/backend/editor/services/user-service/data/types.ts
Resolves the expected conflict in `#checkIfArduinoCliConfigExists`, where #1001 (now on development) and this branch both rewrote the same method. Took this branch's version: `reconcileArduinoCliConfig` is a superset of the regex #1001 introduced — it backfills missing board-manager URLs *and* retires the obsolete `output.no_color`, using the `yaml` Document API rather than anchored regexes. That also settles the review finding on #1001 that the regex could not see `additional_urls: []`, the shape `arduino-cli config init` writes, along with the `existing.includes(url)` whole-file substring match. The doc comment is merged rather than replaced, so the board-manager-URL rationale from #1001 survives alongside the no_color one. Everything else auto-merged. #1001's boardManagerUrl schema validation and pipeline plumbing are intact; so is this branch's console work. Editor suite: 6386 passing, typecheck / lint / prettier clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses seven of the eight CodeRabbit threads on #1002. Each was reproduced before being fixed. **Windows CRLF was classified as a redraw — losing log lines.** Splitting on `\n` leaves the `\r` of a CRLF terminator on every Windows line, which `logCompilerEvent` counted as a carriage-return redraw. Reproduced two ways: progress line, then a CRLF line in one chunk -> the CRLF line OVERWRITES the live progress line "Windows line\r" and "\n" in separate chunks, then a redraw -> "Windows line" is destroyed by the redraw arduino-cli always writes a progress CR at the START of a frame, so position separates the two cases: a trailing `\r` is a line terminator, anything else is a redraw. That fixes both without carrying state between chunks, which is what the review had assumed would be needed. **Escape sequences leaked past a documented contract.** The module promises stored messages carry no escapes, but the tokenizer matched only CSI, so OSC payloads (terminal hyperlinks carry a URL) and bare ESC bytes reached search, copy and the DOM. Verified: `stripAnsi` on an OSC hyperlink returned the ESC bytes intact. The pattern now covers CSI, OSC (BEL- or ST-terminated), other two-byte escapes, and a stray ESC. **A scalar parent aborted the whole config migration.** `board_manager: 5` made yaml's nested `getIn`/`setIn` throw ("Expected YAML collection at board_manager"). The throw is caught upstream, so the app survived — but the user silently kept `no_color` and a monochrome console with no visible cause. Parents are now fetched and checked before use; a scalar `board_manager` is left alone while the `no_color` retirement still proceeds. `NO_COLOR_PATH` became unused and is deleted rather than left behind. **The config is written atomically.** It is rewritten on every start, is owned by the user, and must stay parseable for arduino-cli; a crash mid-write left it truncated. Now written to a sibling temp file and renamed over the original. **Type assertions removed** from `arduino-cli-config.ts` and both test files (`requireUpdated()` instead of `as string`, a type guard in `urlsOf`, and the log callback typed at its declaration). Not doing #5 (search matches spanning a colour boundary) — reasoning in the thread. 12 new tests, all built from the reproductions above. Editor suite: 6398 passing; typecheck, lint and prettier clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/backend/editor/services/user-service/data/__tests__/arduino-cli-config.test.ts (1)
122-124: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert the expected outcomes for scalar YAML cases.
Both tests only check that reconciliation does not throw. The scalar
board_managercase should returnnull. The scalaradditional_urlscase should return rewritten YAML containing the shipped URLs. A regression can otherwise pass while returningnullor preservingadditional_urls: 7.Proposed assertions
- expect(() => reconcileArduinoCliConfig('board_manager: 5\n', ARDUINO_DATA)).not.toThrow() + expect(reconcileArduinoCliConfig('board_manager: 5\n', ARDUINO_DATA)).toBeNull() - expect(() => reconcileArduinoCliConfig('board_manager:\n additional_urls: 7\n', ARDUINO_DATA)).not.toThrow() + const updated = requireUpdated( + reconcileArduinoCliConfig('board_manager:\n additional_urls: 7\n', ARDUINO_DATA), + ) + const updatedUrls = urlsOf(updated) + for (const url of urlsOf(ARDUINO_DATA)) expect(updatedUrls).toContain(url)These outcomes match
src/backend/editor/services/user-service/data/arduino-cli-config.ts, Lines 49-92.Also applies to: 140-142
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/editor/services/user-service/data/__tests__/arduino-cli-config.test.ts` around lines 122 - 124, Strengthen the scalar YAML tests for reconcileArduinoCliConfig: in the board_manager scalar test, assert the result is null; in the additional_urls scalar test, assert the returned YAML is rewritten and contains the shipped URLs rather than preserving additional_urls: 7. Update the existing tests around reconcileArduinoCliConfig without changing production behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@src/backend/editor/services/user-service/data/__tests__/arduino-cli-config.test.ts`:
- Around line 122-124: Strengthen the scalar YAML tests for
reconcileArduinoCliConfig: in the board_manager scalar test, assert the result
is null; in the additional_urls scalar test, assert the returned YAML is
rewritten and contains the shipped URLs rather than preserving additional_urls:
7. Update the existing tests around reconcileArduinoCliConfig without changing
production behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e3944d17-d58b-455e-95b2-3985bbd3e3f2
📒 Files selected for processing (7)
src/backend/editor/services/user-service/data/__tests__/arduino-cli-config.test.tssrc/backend/editor/services/user-service/data/arduino-cli-config.tssrc/backend/editor/services/user-service/index.tssrc/frontend/utils/__tests__/debugger-session.test.tssrc/frontend/utils/__tests__/terminal-output.test.tssrc/frontend/utils/debugger-session.tssrc/frontend/utils/terminal-output.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- src/frontend/utils/tests/debugger-session.test.ts
- src/backend/editor/services/user-service/index.ts
- src/frontend/utils/debugger-session.ts
- src/frontend/utils/tests/terminal-output.test.ts
- src/backend/editor/services/user-service/data/arduino-cli-config.ts
Problem
Build output is captured from a pipe and pushed to the console verbatim, so the two terminal control sequences arduino-cli uses were both mishandled.
Carriage returns. A download progress bar redraws by rewriting one line with
\r. Each redraw arrived as its own chunk and became its own timestamped entry, so one core install produced hundreds of near-identical lines that pushed the real output out of view:SGR colour. arduino-cli colours its compile summary table. The editor suppressed this with
output.no_colorinarduino-cli.yaml, inherited from the 2022 Python editor, which added--no-colorbecause the rawESC[92mbytes were being printed literally.Worth recording, since it contradicts a natural assumption: arduino-cli emits colour through a pipe just as much as under a PTY — it does not TTY-gate. Measured identically on 1.0.3 (the version that prompted the original
--no-color) and the bundled 1.4.1: 10 escape sequences each, piped and under a PTY. Nothing changed upstream; the config was doing the suppressing all along.Changes
frontend/utils/terminal-output.ts(new, shared):\rcollapsing plus a small SGR parser. Explicitly not a terminal emulator — cursor addressing, scroll regions and erase-in-line are stripped rather than interpreted, because build output never uses them.transientwhile the line is still open. The next redraw overwrites it; a trailing newline commits it so the following download starts its own line. That commit step matters — without it every download in a session collapses onto one line and you lose the… downloadedrecord of each.messagealways holds clean text andsegmentscarries styling only when there was any. Search, level filters and copy therefore needed zero changes, and uncoloured logs (nearly all of them) keep their exact previous shape and allocate nothing extra.Dead code removed rather than left behind
output.no_colordropped fromARDUINO_DATA, and existing configs migrated. This was the trap: the config was written with{ flag: 'wx' }and skipped onEEXISTforever, so every machine that had ever launched an older build would have keptno_color: trueand made the new renderer unreachable. Reconciliation lives in its own testable module using theyamlDocument API, so comments, ordering and user-added indexes survive. It only adds missing URLs, dropsno_color, and prunesoutputif that leaves it empty — and bails on an unparseable file rather than clobbering it.ArduinoCliConfigSchema/ArduinoCliConfigdeleted: a zod schema describing the config'sno_colorshape, imported by nothing.Also folded a duplicated ternary in
log.tsx(thecompileErrorbranch was identical in both arms) into oneMessageBodycomponent.A fix that was tried and reverted
The bar still wraps across ~2 visual lines, because arduino-cli sizes it to a width it guesses from its own environment. I pushed a fix that stripped the ASCII bar with a regex and reverted it (
2dfbb557e→38f0f7859): pattern-matching another tool's cosmetic output is fragile and would break silently if the bar glyphs ever change. The wrap is accepted, deliberately.For anyone tempted to retry: there is no flag for it.
COLUMNS,TERMand a real PTY all make no difference, and the full global flag list has nothing for progress or width. The only source-side lever is--json, which suppresses download progress entirely (measured: zero bytes of output while the download still runs) and would require re-plumbing compile output through a JSON envelope.Tests
29 new, across the parser, the CR state machine, the slice's overwrite rule, and the config migration including the upgrade-from-
no_colorpath. Checked they are not vacuous: reverting the three source hunks fails 5 of them.Verified against real captured arduino-cli bytes: 24 CR frames collapse, the summary table maps to green/plain/green/plain/grey exactly as its 92/92/90 codes specify, and neither an escape nor a carriage return survives into stored text.
Full editor suite 6362 passing; typecheck, lint and prettier clean across all 15 changed files.
Merge note
Branched from
development, so it does not contain #1001. Both touch#checkIfArduinoCliConfigExists, so they will conflict. This branch'sreconcileArduinoCliConfigis a superset — it does #1001's URL merging and theno_colormigration — so resolution is "take this branch's version", not a hand-merge. Merge #1001 first, then this.It also resolves a review finding on #1001: the regex there could not see
additional_urls: [](the shapearduino-cli config initwrites). Theyamlrewrite handles it, and the substring-match issue with it.Paired with openplc-web (shared-core parity).
🤖 Generated with Claude Code
Summary by CodeRabbit