Console improvements - #139
Merged
Merged
Conversation
The console body renders the output buffer as a list of rows, which is why selection there behaves nothing like a terminal's. Moving it to a single text view makes "what changed since the last render" the view's problem: rebuilding the whole document per line would be quadratic over a run and would drop the user's selection every time a line arrived. ConsoleTranscript holds the rendered lines plus their cached lengths and returns the minimal edit to catch up — every change brew makes (append, or a progress row redrawn in place) lands in a suffix, so an update is "replace from the first line that differs to the end". Offsets are UTF-16 to match NSTextStorage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018wwC5196S7pzAoAmaHev41
The console body was a List with one Text per line, so every line was its own selection scope: a drag across lines selected nothing, ⌘A had no document to select, the gaps between rows weren't text at all, and the pointer alternated between an arrow and an I-beam depending on which of those it was over. An NSTextView is a single document, so selection, ⌘A and ⌘C behave the way they do in Terminal and the I-beam covers the whole output area. ANSIConsoleText now renders AppKit attributed text (colours still named on the SwiftUI token palette, bridged with NSColor, so the design system stays the source), and new output is applied as ConsoleTranscript's minimal edit with the selection re-applied around it — a selection made mid-install survives the lines that arrive after it. The view follows the tail only while the user is parked at the bottom, so scrolling up to read isn't fought; the pin also runs on layout because SwiftUI hands over the first batch of output before the scroll view has any size. The body binds to one ConsoleBodyContent from the view model rather than reaching into the selected job. Verified against the real app with an injected driver: drag across lines selects 4 lines, selectAll: validates and selects the whole document, hit-testing the bottom edge of the output area lands on the text view, the console opens pinned to the end, a 200-character selection survives ~1100 characters of streamed output, and scrolling away leaves the reader where they were. Menu-routed ⌘A couldn't be driven — the app can't become key in a bare exec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018wwC5196S7pzAoAmaHev41
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018wwC5196S7pzAoAmaHev41
The console toolbar's Save/Copy/Clear were hand-rolled — a 3pt HStack, no visible hover, and a borderless press you can miss — while the command block's Copy button was a Label with its own copied-for-5-seconds state. One BrewActionButton now serves both, so they agree on icon-to-title spacing (the Label's, which is what the command block already used). Pressing is unmistakable: hovering fills the button with the elevated surface and darkens its title, and the press itself takes the app's selection tint. Copy and Clear confirm with a tick and "Copied" / "Cleared" for five seconds — both leave nothing on screen otherwise, so the press had no acknowledgement at all. Save doesn't confirm: its save panel is its own. The accessibility label stays the button's own title while the confirmation is up, so the swap can't move an element out from under a test or a screen reader. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018wwC5196S7pzAoAmaHev41
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018wwC5196S7pzAoAmaHev41
graeme
marked this pull request as ready for review
September 1, 2026 12:44
MikeMcQuaid
approved these changes
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Make the console output selectable like a terminal
Summary
The console body was a
Listwith oneTextper line, so every line was its own selection scope: no drag across lines, nothing for Cmd-A to select, dead gaps between rows, and a pointer that flipped between an arrow and an I-beam. This makes it one text document, and tidies up the toolbar buttons above it.Changes
Console output:
ConsoleTextViewreplaces the list with anNSTextView, so drag selection, Cmd-A, Cmd-C, clicking between lines and the I-beam behave the way they do in Terminal.ConsoleTranscript(ViewModels) returns the minimal edit for each update rather than re-rendering the buffer, which would be quadratic over a run and would drop the user's selection every time a line arrived.ANSIConsoleTextrenders AppKit attributed text, with colours still named on the SwiftUI token palette so the design system stays the source.ConsoleBodybinds to oneConsoleBodyContentfrom the view model instead of reaching into the selected job.Toolbar buttons:
BrewActionButton, used by the console toolbar and the command block header so both agree on icon-to-title spacing and highlighting. Hover fills the button; the press takes the app's selection tint.Testing
scripts/testgreen (832 tests, plus 19 for BrewUILint). SwiftFormat, SwiftLint strict and BrewUILint clean; app and UI test targets build.selectAll:selects the whole document, the console opens pinned to the end, and a selection survives streamed output.PR checklist