Skip to content

mac: the caret sits on the same line as the words - #303

Merged
stupart merged 1 commit into
mainfrom
feat/design-comb
Sep 16, 2026
Merged

stupart merged 1 commit into
mainfrom
feat/design-comb

Conversation

@stupart

@stupart stupart commented Sep 16, 2026

Copy link
Copy Markdown
Owner

"still lots of lil bugs like see how the cursor isn't lined up with the preview text?"

The caret

#ta{...font:var(--read)/22px...} is a 22 pt line box, and nothing in the app set one. The caret is drawn to the line fragment, so it was an 18 pt bar at the top of the box while the placeholder — a SwiftUI Text centred in a 22 pt frame — drew its glyphs 2 pt lower.

Measured with a compiled probe, not reasoned about:

line fragment ink starts caret
no paragraph style (before) 18 pt y 0.5 18 pt
22 pt line box (now) 22 pt y 4.5 22 pt
placeholder centred in 22 pt (before) — y 6 —
placeholder top-aligned (now) — y 4 —

One change fixes three things: the caret's alignment, the placeholder's alignment, and typed multi-line spacing (18 pt → the lab's 22).

Three more, found on the way

was now
fieldHeight growth measured at systemFont(12.5), line height 15 measured at system 15 in the 22 pt box — it was 7 pt short per wrapped line
dictation line (#cLive) 12.5, padding 6/8 readingBody + the editor's own insets — words no longer change size and jump when transcription lands
ComposerView.lineHeight in a View extension resolved to SwiftUI's lineHeight(_:) modifier passed in as a parameter (the compiler caught this one)

The dictation line's cyan is left alone — the lab uses --text there and --text3 while transcribing, which is a state-colour decision for you, like the send button.

Gate: bun test 2315/0 · tsc clean · swift test 138/0 · Mac + iOS both build · 11 mutations, 11 killed.

One of those eleven survived the first pass: asserting storage.addAttribute( existed still passed with its branch inverted to length < 0 — the call was there, it just could never run. The guard pins the condition now.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SfEX6bfwCGXtThk1JRNmsE

Tyler, with a screenshot: "see how the cursor isn't lined up with the preview
text?"

`#ta{...font:var(--read)/22px...}` — the lab's composer is a 22 pt LINE BOX, and
nothing in this app set one. Two things followed. Typed lines sat at the reading
font's natural 18 pt rather than 22. And the caret is drawn to the line
fragment, so it was an 18 pt bar at the top of the box while the placeholder — a
SwiftUI Text centred in a 22 pt frame — drew its glyphs 2 pt lower.

Measured with a compiled probe rather than reasoned about, because reasoning
from a bad grep is what produced the --shRaised mistake earlier today:

    no paragraph style : 18 pt fragment, ink at y 0.5, caret 18 pt
    22 pt line box     : 22 pt fragment, ink at y 4.5, caret 22 pt
    placeholder centred in 22 pt : ink at y 6
    placeholder topped  in 22 pt : ink at y 4

So the editor gets the lab's line box and the placeholder is top-aligned: both
land at ~4, and the caret finally spans the line its text sits on.

Three more, found while looking:

- `fieldHeight` measured the draft at systemFont(12.5) — line height 15 — while
  the editor renders system 15 in a 22 pt box. Every wrapped line was measured
  7 pt short, so the field grew less than the text it had to hold.
- `#cLive{padding:8px 10px 4px;font:var(--read)/22px}`: the live dictation line
  shares the editor's font and insets in the lab, because this text becomes that
  text. It was 12.5 with 6/8 padding, so the words changed size and jumped the
  instant transcription landed — the one moment you are watching them.
- `ComposerView.lineHeight` inside a `View` extension resolves to SwiftUI's own
  `lineHeight(_:)` modifier, not the struct's constant. The value is passed in
  now; the compiler found this one.

The dictation line's cyan is left alone: the lab uses `--text` there (and
`--text3` while transcribing), which is a state-colour decision, not a
measurement.

Gated: bun test 2315/0, tsc clean, swift test 138/0, Mac and iOS both build (CI
builds neither). Eleven mutations, eleven killed — including one that survived
the first pass, because asserting `storage.addAttribute(` existed still passed
with its branch inverted to `length < 0`. The guard now pins the condition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfEX6bfwCGXtThk1JRNmsE
@stupart
stupart merged commit 0dbcfd2 into main Sep 16, 2026
2 checks passed
stupart added a commit that referenced this pull request Sep 17, 2026
Tyler, twice: "the cursor in the text input box still isn't lined up with the
preview text."

#303 was my fix and it was wrong. It reasoned from a probe of a SIMPLIFIED case
and shipped `minimumLineHeight = maximumLineHeight = 22`. CSS splits a line box's
extra leading half above and half below the text; AppKit puts ALL of it above the
baseline. So a 22 pt line box pushed the first line DOWN and grew the caret with
it, while the placeholder — top-aligned, untouched — stayed where it was. That
made the gap different, not smaller.

Measured against a real NSTextView, rendered and read back pixel by pixel:

    no paragraph style   caret 18 pt, text ink at 4 pt
    min/max 22           caret 22 pt, text ink at 8 pt   <- what #303 shipped
    lineSpacing 4        caret 18 pt, text ink at 4 pt

The placeholder draws its ink at 4 pt. The middle row was off by exactly 4.

So: `ConchType.readingLineSpacing` — the same 4 pt the transcript already uses,
so the composer and the messages it answers cannot drift apart.
`#ta{font:var(--read)/22px}` is 15 pt of type plus 4 pt of leading, and 22 still
bounds the field at one line and at eight (`max-height:calc(22px*8+12px)`).

Two hypotheses were tested and discarded first. A harness hosting a real SwiftUI
TextEditor showed the paragraph style SURVIVES every update (22/22 before and
after a keystroke), so "SwiftUI clobbers it" was wrong and the introspector
running once was not the bug.

The lesson, which cost three attempts: a probe of a simplified case is not a
measurement of the thing itself. The first probe measured a SwiftUI Text in a
frame and an NSLayoutManager in isolation. Neither was the composer.

Verified: bun test 2345/0, tsc clean, swift test 138/0, the Mac app builds. Five
mutations, five killed — including one that re-ships #303's exact mistake, so it
cannot come back quietly.


Claude-Session: https://claude.ai/code/session_01SfEX6bfwCGXtThk1JRNmsE

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant