Repository navigation
Hold the screen's full refresh rate on any Mac: cached formulas, lighter rows; 0.16.8 - #23
Merged
Merged
Conversation
…oming in The live window scrolled to its end on every new word, and the Q&A on every token of an answer, several times a second, so scrolling up was undone at once and the page looked like it could not be scrolled. Both now follow the end only while the reader is at it: scrolling up stops following, getting back to the end resumes it, and asking a question follows the answer. macOS 14 cannot read a scroll view's position, so it follows as before. The live window's sentences are Equatable on what they show, so a word added to the line still being spoken no longer redraws every sentence on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UP49VoRGBw2wv1CEAroky7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UP49VoRGBw2wv1CEAroky7
On macOS 27 a ProMotion panel does not settle on one refresh rate while a SwiftUI view scrolls: it flips between 120 Hz and 60 Hz, each frame stays on screen for an unpredictable time, and every page judders, plain text included (FB24091347, Developer Forums thread 840214). The only confirmed workaround is setting the whole display to 60 Hz. An app can say which rate it wants: a display link with a preferred frame-rate range is that request. One now runs from the first scroll event until shortly after the last, momentum included, asking for 120 Hz, and pauses when the page stops. `defaults write com.beluga.classnote scrollFrameRate -int 60` asks for a steady 60 Hz instead and `0` turns it off, from the next launch, so the two can be compared without a new build. Version 0.16.6 (build 24). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UP49VoRGBw2wv1CEAroky7
…ter rows; 0.16.8 Formulas. MarkdownView drew every formula as two live MTMathUILabels (one in a horizontal scroll view) that parsed their LaTeX on every update and ran the whole typesetter on every size query, so a paragraph of notes with formulas built a dozen AppKit views and typeset each formula several times as it scrolled into view. MarkdownView 3.0.0 is now a copy in Packages/MarkdownView with that one part changed: a formula is typeset once with SwiftMath's MTMathImage, cached, and drawn as an image, scaled to fit when wider than its line. PATCHES.md says what changed and how to update the copy. A test renders x² through the app's Markdown view and checks from the pixels that it is drawn, typeset and the right way up. Refresh rate. The scrolling request asks for the most the window's screen can show (144 Hz on such a monitor, not 120) and asks nothing of a 60 Hz screen, which has no other rate to settle on. Transcript. Sentences are grouped once per change to the transcript, not on every redraw (a streamed token included), and a row draws its rounded background only while it is highlighted or under the pointer instead of a clear one behind every row. Session list and dates. Date formatters are made once per template and language rather than one per row, and the list groups sessions by day once per redraw instead of once per day. Version 0.16.8 (build 26). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UP49VoRGBw2wv1CEAroky7
The branch still carried #21's commits, which main has as a squash; this keeps the branch's files exactly as they were. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UP49VoRGBw2wv1CEAroky7
MarkdownView already declares it (Image++.swift), so a second one in SwiftMathView.swift did not compile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UP49VoRGBw2wv1CEAroky7
belugaming
pushed a commit
that referenced
this pull request
Oct 9, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UP49VoRGBw2wv1CEAroky7
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.
#21 fixed the refresh-rate flicker on macOS 27 ProMotion panels. To hold 120 Hz, or whatever the screen supports, every frame also has to finish in about 8 ms on the main thread. That has to hold on slower Macs too, not only on an M5 Max. This PR removes the biggest per-frame costs that are left on the scrolling pages.
Formulas: typeset once, drawn as an image
MarkdownView drew every formula as a
ViewThatFitsholding two liveMTMathUILabels (AppKit views), one of them inside a horizontal scroll view.MarkdownView 3.0.0 is now a copy in
Packages/MarkdownView(MIT, license kept), with only the formula drawing changed:SwiftMathImageCachetypesets a formula once per look (LaTeX, size, inline or display, light or dark). It uses SwiftMath's ownMTMathImage.Packages/MarkdownView/PATCHES.mdlists the three changed files and how to update the copy.MarkdownRenderingnow depends on the copy by path, still with theLaTeXtrait, so the CI check forENABLE_MATH_RENDERINGand SwiftMath's resources still applies.Refresh-rate request follows the screen
The scroll-time request from #21 now asks for the window's screen maximum, taken from
NSScreen.maximumFramesPerSecond, instead of a fixed 120 Hz. A 144 Hz monitor gets 144 Hz. A 60 Hz screen has no other rate to settle on, so nothing is asked of it. ThescrollFrameRatesetting still overrides this, capped at what the screen can show.Transcript and session list
Tests
FormulaRenderingTestsrenders$$x^{2}$$throughRichMarkdownViewwithImageRendererand checks the pixels:$$x^{2}$$source or an upside-down image would fail.The old AppKit label rendered nothing off screen.
ScrollFrameRateTestscovers the screen-dependent range: 120 Hz ProMotion, a 144 Hz monitor, 60 Hz left alone, the setting capped at the screen, and every range well formed.Not measured on hardware. CI builds and tests on macOS 15 and 27, but has no display to time scrolling against. Whether the scrolling pages now hold the full rate has to be seen on a real Mac.
Version 0.16.8 (build 26).
🤖 Generated with Claude Code
https://claude.ai/code/session_01UP49VoRGBw2wv1CEAroky7
Generated by Claude Code