Repository navigation
UI Consistency: 2 issues found
Reviewed the web-scoped changes in the latest commit (fix(web): align terminal interaction geometry); earlier findings on this PR were already reported in prior runs and are not repeated.
apps/web/src/terminal/ghostty/renderer.ts—measureGhosttyCellnow rounds the cell width to an integer. When the measurement rounds up,fillText(..., maxWidth)does not condense the run, so glyphs are drawn at the narrower natural advance while backgrounds, selection, cursor, and hover underline stay on the integer grid, drifting several cells apart across a long line. SuggestedMath.floorso the condense path always applies.apps/web/src/terminal-links.ts— the new bare-filename alternative inFILE_PATH_PATTERNmatcheshost:porttokens (0.0.0.0:8080,127.0.0.1:3000), which combined with hover highlighting no longer requiring the link modifier underlines ordinary server output and makes activation try to open a nonexistent path. Suggested a negative lookahead excluding all-numeric dotted tokens.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.
Scope: apps/web/src/**/*.ts, *.tsx, *.css. Files examined at head: terminal/ghostty/renderer.ts, terminal/ghostty/surface.ts, terminal/ghostty/core.ts, terminal-links.ts, components/ThreadTerminalDrawer.tsx, components/ChatView.tsx, index.css, plus the corresponding tests.
Checks performed:
- Traced
metrics.widththroughterminalGridSize, the run/background/underline/cursor draw paths, andsendMousecell geometry to confirm the rounding interaction withfillText'smaxWidthsemantics (condense-only). - Traced the widened
FILE_PATH_PATTERNthroughextractTerminalLinks→terminalLinkAtPositionWithRange→linkAt→refreshHoveredLink/handleLinkActivate→resolvePathLinkTarget, including the URL-precedence and overlap filters. - Reviewed the new Unicode block-element rendering (rect geometry, adjacency, invisible cells, run splitting) and the
canvasDevicePixelRatioguard inconfigureCanvas/renderFrame; no issues found in those. - Verified the hover-underline change keeps selection drags and mouse-reporting applications clearing the hovered link, and that the pointer cursor remains modifier-gated; treated the intentional affordance change itself as a documented product decision rather than a finding.
🔍 Agent activity — 12 tool calls
💭 I'm reviewing the changed files at head within the in-scope web app directory, checking whether previously flagged issues persist or new ones were introduced, and identifying which specific files—ChatView.tsx, ThreadTerminalDrawer.tsx, index.css, and terminal-links.ts—need closer inspection. Since prior review runs cover up through commit 12fe174 and head is now a08dae3, I want to diff those two commits to see what actually changed in between.
1. git_tools — 12fe174..a08dae3
2. git_tools
💭 The commit log seems off — it's showing an unrelated commit about disabling Acrylic on Windows rather than the PR I'm reviewing, so I suspect the log is pulling from the default branch instead of the PR head. I should check the PR head commit specifically to confirm. I'll fetch the PR files list directly through the GitHub API and read the file contents at the head ref instead of relying on the log.
3. github_api_read_only — repos/{owner}/{repo}/pulls/{pull_number}/commits?per_page=100
💭 There's a new commit, a08dae3, fixing terminal interaction geometry alignment - let me check that diff.
4. git_tools — 12fe174..a08dae3
💭 I'm examining the new bare-filename matching pattern added to FILE_PATH_PATTERN, checking whether it could cause false positives like "node:22.1" being misidentified as a link, but noting the pattern actually requires a trailing colon-digit sequence which limits that risk. However, I realize a real IP:port match like "127.0.0.1:3000" does satisfy the pattern - the digit groups and dots plus the ":3000" suffix all line up. This is concerning since dev servers constantly print addresses like this in terminal output, and with the new hover-underline behavior, clicking such a link would try to open a nonexistent file path rather than treating it as a network address, which seems like a genuine regression worth flagging. I also want to check how link hover interacts with text drag-selection and whether checking for a link on every mousemove event has any performance implications. On the rendering side, rounding cell width to an integer instead of using the fractional advance width could cause a few percent of drift between glyph advance and cell width, potentially clipping glyphs slightly since text runs get clipped to their cells.
5. browse_code — apps/web/src/terminal-links.ts
💭 I realize a bare
host:portstring like127.0.0.1:3000now matches the file path pattern too, so with hovering always underlining links, tools that print these bare addresses (like dev server output) would incorrectly resolve as local file paths when clicked, rather than treated as URLs. The old pattern required a slash for that second alternative, so this false positive is newly introduced by broadening the pattern. This does feel in scope as an interaction regression since it combines the pattern change with the hover-underline change to create unexpected clickable behavior on ordinary output text. I want to trace how the hovered link renders and how activation resolves the file path—whether it checks existence before opening—by examining the surface.ts link detection code and how ThreadTerminalDrawer handles link activation, including whether it passes an explicit environmentId.
6. git_tools — hoveredLink|linkAt|onLinkActivate|linkModifierActive
💭 I notice
refreshHoveredLinknow returns a link on plain hover, giving underline affordance without requiring the modifier key, while activation still happens through the pointerup path vialinkActivationPointerId. I want to check pointerdown to see if the modifier is required at press time, so I'm looking at surface.ts around lines 1600-1800.
7. browse_code — apps/web/src/terminal/ghostty/surface.ts
💭 I'm realizing that always tracking hover now means
linkAtgets called on every pointermove instead of only when the modifier key is active, which could be costly since it triggers full re-renders on hover changes. I should check the fulllinkAtimplementation to understand its performance cost, especially around hyperlink detection and regex extraction. There's also a UX inconsistency worth noting: the underline appears on hover without a corresponding pointer cursor, so clicking without the modifier does nothing but start a selection — likely intentional per the comment about the modifier gating activation, so I won't flag it. What concerns me more is the broadened FILE_PATH_PATTERN now underlining common non-path text like host:port strings or version numbers, which could create false positive link affordances. Real-world cases like127.0.0.1:3000,example.com:443, or0.0.0.0:8080from docker/server logs would now match the pattern and underline, but clicking them would try to open a nonexistent file at the wrong line number rather than doing anything useful — I need to check how handleLinkActivate treats these matched links to see if it validates path existence first.
8. git_tools — handleLinkActivate
💭 I'm considering whether flagging this regex change fits the UI consistency check's scope, and I think it's a legitimate finding: the pattern change combined with always-on hover now makes plain
host:portstrings like127.0.0.1:3000get misidentified as file paths. I'm also checking common cases like Docker port mappings and server logs — bare0.0.0.0:8080style strings appear all the time in dev output, so this false-positive matching is likely to trigger often in practice. I'm noting a second possible concern around integer rounding of cell widths inmeasureGhosttyCell, though the maxWidth condensation may mitigate mismatches between drawn text and grid cells. If rounding rounds up instead of down, the cell width ends up larger than the natural glyph advance, so within a long run of monospace characters, each subsequent glyph is drawn slightly left of where its cell boundary actually falls, and the drift compounds across the run — potentially tens of pixels by the end of an 80-character line. That would misalign rendered text against the selection highlighting, cursor position, and other grid-based overlays, so I want to check whether this drift already existed before the rounding change or is newly introduced by it.
9. browse_code — apps/web/src/terminal/ghostty/renderer.ts
10. browse_code — apps/web/src/terminal/ghostty/renderer.ts
💭 Since each run is drawn with a single fillText clipped to its rect, when the natural glyph advance is smaller than the cell width, no condensation happens and characters draw at their natural spacing—causing cumulative leftward drift relative to their cell backgrounds. For a 100-column run, that drift could reach 40px, roughly five cells off from where the selection highlighting expects the glyph to be. The likelihood of this depends on font metrics: whether typical monospace advance widths round down (causing drift) or round up (triggering maxWidth condensation that keeps glyphs compressed within their cells). Roughly half of common font-size combinations seem to land in the drift-prone category, and even in the condensation case I need to check whether the uniform scaling actually keeps each glyph's start position aligned to the cell boundary or introduces its own subtle error. A simple
Math.max(1, Math.floor(...))fix would guarantee condensation always applies, though it could squish narrow glyphs by up to 12% in the worst case—still uniform and aligned, which matches how Ghostty natively handles cell-width rounding. This feels like a plausible, worth-reporting regression rather than pure speculation, so I want to confirm whethermetrics.widthis also used elsewhere for grid column counts or mouse hit-testing. That's fine since floor keeps grid cells narrower, giving slightly more columns—no real issue there, and prior runs haven't flagged this since it's new in this commit. Now I'm turning to the second finding around FILE_PATH_PATTERN matching bare filenames that look like host:port strings, and a third item about hover underlines appearing without a modifier key, which seems intentional given an existing comment, so I'll skip that one. I also want to trace through the hover-link logic on pointermove to see if there's a performance or correctness issue with how hovered links get detected and rendered. Reading row text from the wasm snapshot for the wrapped line on every mousemove seems like a moderate cost but probably acceptable, so I won't flag it. The hover underline gets cleared when mouse-tracking apps are active, which is fine, though I'm wondering whether always-on hover highlighting might create noise in alt-screen TUIs like vim when mouse mode isn't enabled—but that seems to be by design. Now I want to check the linkAt path-link detection logic to confirm it applies broadly to cell text and would actually surface the host:port matching scenario. There's likely a filter requiring the path to look like a file, perhaps needing an extension, versus the newer pattern requiring a dot plus digits. I should verify the linkAt implementation around lines 2171-2260 to check this.
11. browse_code — apps/web/src/terminal/ghostty/surface.ts
💭 I'm examining the cell width rounding logic in the ghostty renderer around line 158, noting that rounding up could decouple glyph advance from the cell grid — flooring instead of rounding seems like the safer fix, though I want to double-check whether the clipping/maxWidth handling elsewhere compensates for that drift. Actually, I realize this drift isn't confined to a small area — since runs only break on style changes, a long uniformly-styled line can accumulate up to a few cells of horizontal offset by the right edge, which would visibly misalign cursor position, selection highlighting, and text placement. Using Menlo at typical sizes, I calculate this could shift text by around 3 cells over 120 columns, which is definitely noticeable, so flooring the width also happens to match the intent of aligning hit-testing with libghostty's integer-pixel approach. Next I'm looking at terminal-links.ts around line 41, where the bare-name matching alternative seems to also catch things like host:port strings and version-like tokens, which could cause false positives on common terminal output such as network listening messages. I'm trying to find a way to tighten the regex to avoid matching things like "0.0.0.0:8080" as a file link, but any simple last-segment check (numeric vs alphabetic) still lets through false positives like "example.com:8080" since the extension "com" looks legitimate. VS Code apparently solves this same ambiguity by validating file existence server-side before highlighting a link, which the current approach can't easily replicate, so I'm considering restricting matches to single dot-separated names with non-numeric extensions. Since this is a provable false positive—the pattern does match IP:port strings like "0.0.0.0:8080"—I'll propose a concrete regex fix excluding purely numeric dotted tokens, presented as a diff, while flagging host:port as a remaining edge case. Let me trace through exactly how the pattern's alternatives evaluate against that string to confirm no other branch catches it first. I'm also weighing whether to note the underline-without-cursor inconsistency, but since it's a deliberate, commented design choice, I'll leave it alone. I'm confirming that clearing hovered link state during drag selection doesn't conflict with the hover-change logic, which looks fine. Checking the block-element rendering path for consistency issues — block glyphs are drawn in foreground color separately from backgrounds and underlines, so hovered link underlines still render correctly even for block cells. I'm also verifying the eighth-block math: computing fractional coverage for characters like "▁" through "▉" against their Unicode offsets, and everything checks out for both the lower-eighths and left-eighths block sets. Now I'm confirming that block rect edges round cleanly to metrics.width so adjacent full-block cells don't leave visible seams — that lines up correctly. I'm drafting a fix suggestion for the width rounding issue, proposing to swap Math.round for Math.floor at the relevant line so the measured width behaves more consistently, and locating the exact line number to anchor the review comment. For the second issue, I'm working out a regex fix in terminal-links.ts that adds a negative lookahead to prevent purely numeric dotted tokens (like version numbers or IPs) from being misidentified as file paths. Checking the remaining index positions confirms the lookahead correctly excludes IP:port patterns while letting the file-path alternative fail when no dot follows, and I'm considering whether this could wrongly exclude legitimate filename-like strings, though that seems unlikely in practice. I'll apply the fix inline on line 41 with proper indentation, and note in my findings that there's a residual edge case worth mentioning briefly but not dwelling on. My final review comes down to two concrete issues worth flagging: the visual regression risk from glyph/cell drift, and the link false-positive — the rounding issue seems more like a consistency concern than a hard failure. I'm double-checking the fillText maxWidth spec to confirm my understanding — it only constrains rendering when the natural width exceeds the given limit, otherwise the text renders at its natural size. This confirms that rounding up the cell width causes glyphs to render narrower than their allocated space, creating a progressive drift within a text run. Using floor instead of round is the minimal fix, since it never causes text to exceed its cell (at worst it slightly under-scales), while an alternative would be drawing each character in its own cell. I'll write up the review with these two points as inline comments, checking that the specific lines in renderer.ts and terminal-links.ts are part of the diff so the comments land correctly.
12. modify_pr — Two findings in the web terminal renderer geometry and link detection introduced by fix(web): align terminal interaction geometry. Details inline.
Agent Credits: 260 credits
Token usage: 24 uncached input · 1,783,468 cache-read input · 195,453 cache-write input · 14,253 output
Agent Credits may also include non-token charges from external tools such as web research.