Skip to content

[codex] Improve render-path responsiveness#388

Merged
schuyler merged 4 commits into
schuyler:mainfrom
yusufm:Codex/perf-render-latency-019da736-8573-7ac1-9883-259ce39e964a
Apr 20, 2026
Merged

[codex] Improve render-path responsiveness#388
schuyler merged 4 commits into
schuyler:mainfrom
yusufm:Codex/perf-render-latency-019da736-8573-7ac1-9883-259ce39e964a

Conversation

@yusufm

@yusufm yusufm commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • stop the renderer queue from busy-spinning while the preview is still loading
  • skip Prism DOM rescans when the rendered HTML has no highlightable code blocks
  • reduce main-thread work in word counting, header-location scanning, and startup defaults cleanup

Why

The previous render scheduler could hot-loop on rendererLoading, which wasted CPU and stretched visible preview latency under load. The preview path also did extra whole-document work during live updates, and preferences cleanup was still blocking startup despite being non-urgent.

Validation

  • xcodebuild test -workspace "MacDown 3000.xcworkspace" -scheme MacDown -destination "platform=macOS" -only-testing:MacDownTests/MPRendererStateTests -only-testing:MacDownTests/MPWordCountUpdateTests
  • xcodebuild test -workspace "MacDown 3000.xcworkspace" -scheme MacDown -destination "platform=macOS" -only-testing:MacDownTests/MPMarkdownRenderingTests/testVeryLargeDocument -only-testing:MacDownTests/MPScrollSyncTests

Timing Notes

Manual scheduler benchmark for the old vs new wait loop:

  • maxDelay=0.05s, loadingDuration=1.00s: old loop 22,374,992 polls / 1.0000s, new loop 6 polls / 0.0506s
  • maxDelay=0.50s, loadingDuration=0.50s: old loop 11,163,371 polls / 0.5000s, new loop 44 polls / 0.5023s

@yusufm yusufm marked this pull request as ready for review April 19, 2026 23:31
- Fix retain cycle in parseAndRenderWithMaxDelay: by replacing
  __block NSBlockOperation *operation with __weak reference pattern.
  The old pattern created operation→block→operation cycle that leaked
  the renderer and associated WebViews.

- Move cleanupObsoleteAutosaveValues back to synchronous execution.
  Running it async on a background thread raced with app initialization
  and triggered a crash on macOS 26 when NSUserDefaults mutations
  interacted with WKWebView setup during test host startup.

Related to schuyler#388

@schuyler schuyler left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[deleted]

@schuyler schuyler left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution, @yusufm.

Issues:

openOrCreateFileForUrl: — the unsaved-document guard is missing a return, so users hit a second, misleading "Blocked file creation" alert from canAutomaticallyCreateLinkedFileAtURL: returning NO on nil fileURL.

The loop comment in updateHeaderLocations still says "splitting our document into lines", missing that the switch to NSStringEnumerationByLines now correctly handles \r\n and bare \r.

Minor suggestions: use a __strong local for the cancellation checks, add brief comments on the shared regex and 10ms poll interval, explain the cleanupObsoleteAutosaveValues reorder, add CHANGELOG.md entries for the three user-visible changes, and confirm the NSString (WordCount) header was removed.


Generated by Claude Code

yusufm added a commit to yusufm/macdown3000 that referenced this pull request Apr 20, 2026
- Update updateHeaderLocations comment to reflect NSStringEnumerationByLines
  (correctly handles \r\n and bare \r, not just splitting by \n).
- Use __strong local for weakOp cancellation checks in the renderer
  polling loop to avoid weak reference going nil between checks.
- Add brief comments on the shared regex dispatch_once and 10ms poll
  interval constant.
- Add comment explaining cleanupObsoleteAutosaveValues reorder (runs
  after migration and defaults so cleanup does not remove just-set keys).
- Add CHANGELOG.md entries for the three user-visible changes.
- Confirmed: NSString (WordCount) category had no separate header; it
  was only in DOMNode+Text.m and has been fully replaced.

Related to schuyler#388
schuyler pushed a commit that referenced this pull request Apr 20, 2026
Changelog updates for PRs are handled by the maintainer at release time.

Related to #388
@yusufm yusufm force-pushed the Codex/perf-render-latency-019da736-8573-7ac1-9883-259ce39e964a branch from bf4a41e to ba747f6 Compare April 20, 2026 02:07
yusufm added a commit to yusufm/macdown3000 that referenced this pull request Apr 20, 2026
- Update updateHeaderLocations comment to reflect NSStringEnumerationByLines
  (correctly handles \r\n and bare \r, not just splitting by \n).
- Add brief comment on the cached body-extraction regex dispatch_once
  and the 10ms poll interval constant.
- Add comment explaining cleanupObsoleteAutosaveValues reorder (runs
  after migration and defaults so cleanup does not remove just-set keys).
- Add CHANGELOG.md entries for the three user-visible changes.
- Confirmed: NSString (WordCount) category had no separate header; it
  was only in DOMNode+Text.m and has been fully replaced.
- Note: __strong local for weakOp skipped. NSOperationQueue retains
  operations during execution, so weakOp cannot go nil mid-block.

Related to schuyler#388
yusufm added a commit to yusufm/macdown3000 that referenced this pull request Apr 20, 2026
- Update updateHeaderLocations comment to reflect NSStringEnumerationByLines
  (correctly handles \r\n and bare \r, not just splitting by \n).
- Add brief comment on the cached body-extraction regex dispatch_once
  and the 10ms poll interval constant.
- Add comment explaining cleanupObsoleteAutosaveValues reorder (runs
  after migration and defaults so cleanup does not remove just-set keys).
- Add CHANGELOG.md entries for the three user-visible changes.
- Confirmed: NSString (WordCount) category had no separate header; it
  was only in DOMNode+Text.m and has been fully replaced.
- Note: __strong local for weakOp skipped. NSOperationQueue retains
  operations during execution, so weakOp cannot go nil mid-block.

Related to schuyler#388
@yusufm yusufm force-pushed the Codex/perf-render-latency-019da736-8573-7ac1-9883-259ce39e964a branch from ba747f6 to af0c688 Compare April 20, 2026 02:21
yusufm added a commit to yusufm/macdown3000 that referenced this pull request Apr 20, 2026
- Update updateHeaderLocations comment to reflect NSStringEnumerationByLines
  (correctly handles \r\n and bare \r, not just splitting by \n).
- Add brief comment on the cached body-extraction regex dispatch_once
  and the 10ms poll interval constant.
- Add comment explaining cleanupObsoleteAutosaveValues reorder (runs
  after migration and defaults so cleanup does not remove just-set keys).
- Add CHANGELOG.md entries for the three user-visible changes.
- Confirmed: NSString (WordCount) category had no separate header; it
  was only in DOMNode+Text.m and has been fully replaced.
- Note: __strong local for weakOp skipped. NSOperationQueue retains
  operations during execution, so weakOp cannot go nil mid-block.

Related to schuyler#388
@yusufm yusufm force-pushed the Codex/perf-render-latency-019da736-8573-7ac1-9883-259ce39e964a branch from af0c688 to 4b6134f Compare April 20, 2026 02:28
yusufm added a commit to yusufm/macdown3000 that referenced this pull request Apr 20, 2026
- Update updateHeaderLocations comment to reflect NSStringEnumerationByLines
  (correctly handles \r\n and bare \r, not just splitting by \n).
- Add brief comment on the cached body-extraction regex dispatch_once
  and the 10ms poll interval constant.
- Add comment explaining cleanupObsoleteAutosaveValues reorder (runs
  after migration and defaults so cleanup does not remove just-set keys).
- Add CHANGELOG.md entries for the three user-visible changes.
- Confirmed: NSString (WordCount) category had no separate header; it
  was only in DOMNode+Text.m and has been fully replaced.
- Note: __strong local for weakOp skipped. NSOperationQueue retains
  operations during execution, so weakOp cannot go nil mid-block.

Related to schuyler#388
@yusufm yusufm force-pushed the Codex/perf-render-latency-019da736-8573-7ac1-9883-259ce39e964a branch from 4b6134f to 6a0bb5c Compare April 20, 2026 03:12
- Update updateHeaderLocations comment to reflect NSStringEnumerationByLines
  (correctly handles \r\n and bare \r, not just splitting by \n).
- Add brief comment on the cached body-extraction regex dispatch_once
  and the 10ms poll interval constant.
- Add comment explaining cleanupObsoleteAutosaveValues reorder (runs
  after migration and defaults so cleanup does not remove just-set keys).
- Add CHANGELOG.md entries for the three user-visible changes.
- Confirmed: NSString (WordCount) category had no separate header; it
  was only in DOMNode+Text.m and has been fully replaced.
- Note: __strong local for weakOp skipped. NSOperationQueue retains
  operations during execution, so weakOp cannot go nil mid-block.

Related to schuyler#388
@yusufm yusufm force-pushed the Codex/perf-render-latency-019da736-8573-7ac1-9883-259ce39e964a branch from 6a0bb5c to d512190 Compare April 20, 2026 03:31
@schuyler

Copy link
Copy Markdown
Owner

Looks good! Thank you @yusufm

@schuyler schuyler merged commit 28f32aa into schuyler:main Apr 20, 2026
6 checks passed
@schuyler schuyler added the rc-pending Included in a release candidate awaiting validation label Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rc-pending Included in a release candidate awaiting validation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants