Skip to content

Address issue #441: honor Sync Panes toggle mid-session#463

Merged
schuyler merged 2 commits into
mainfrom
claude/charming-tesla-3ktymh
Jun 22, 2026
Merged

Address issue #441: honor Sync Panes toggle mid-session#463
schuyler merged 2 commits into
mainfrom
claude/charming-tesla-3ktymh

Conversation

@schuyler

@schuyler schuyler commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the reported bug where, after turning Sync Panes off while a document is open, typing in the editor still auto-scrolled the Preview toward the editor. Restarting with sync already off avoided it — the tell-tale sign that this was a mid-session state problem, not an initialization one.

Root cause

While sync was ON, every forward sync wrote an editor-derived value into lastPreviewScrollTop (syncScrollers). The full-reload completion handler (MPGetPreviewLoadingCompletionHandler) restores the preview to lastPreviewScrollTop unconditionally, regardless of the sync preference. After disabling sync, that stale editor-derived position kept getting restored, plus scroll ownership could linger as Editor — so the panes were never truly independent until the document was reopened.

Fix

Detect the editorSyncScrolling transition in userDefaultsDidChange: (the only native hook that fires for the XIB-bound checkbox) and react immediately:

  • On disable (handleSyncScrollingDisabled): reset scroll ownership to Neither and recapture the Preview's actual current position into lastPreviewScrollTop, so the panes become fully independent with no jump. The full-reload path also keeps lastPreviewScrollTop aligned with the Preview's real position whenever sync is off, covering the "scroll the Preview independently, then type" case.
  • On enable (handleSyncScrollingEnabled): re-sync immediately, editor-authoritative (Preview moves to match the editor), bracketed in Editor ownership and returned to Neither — mirroring the existing editor-reveal sync in setSplitViewDividerLocation:.

A cached lastKnownSyncScrolling (seeded in init from the live preference) drives edge detection only; gating still always reads the live preference, so no preference value is ever cached for decision-making.

Audit

Confirmed every syncScrollers / syncScrollersReverse call site reads the live editorSyncScrolling value; the only non-sync preview-moving write was the unconditional lastPreviewScrollTop restore, which this change neutralizes when sync is off.

Files Changed

  • MacDown/Code/Document/MPDocument.m — transition detection + handleSyncScrollingEnabled/handleSyncScrollingDisabled; full-reload position capture when sync is off; cached value seeded in init.
  • MacDownTests/MPScrollSyncTests.m — new Group O regression tests.
  • CHANGELOG.md — user-facing entry.

Related Issue

Related to #441

Manual Testing Plan

  1. Launch with Sync Panes ON. Open a longer document (e.g. the bundled help). Scroll/edit so the panes are synced.
  2. Open Settings → uncheck Sync Panes.
  3. Scroll each pane independently — they stay independent (already worked).
  4. Type in the editor — the Preview must now stay put (this was the bug; it previously jumped toward the editor).
  5. Scroll the Preview to a new position, type again — Preview stays where you left it.
  6. Re-check Sync Panes — the Preview should immediately jump to match the editor's current position, and continue syncing on subsequent edits/scrolls.
  7. Repeat 1–6 having launched with Sync Panes OFF — behavior should be identical to the post-fix mid-session case.
  8. Edge check: toggle the checkbox rapidly a few times — no crashes, panes settle correctly.

Review Notes

Automated review found no critical issues. The unit tests lock the fix at the state level (ownership reset, position recapture, edge detection, nil-safety); the rendered end-to-end jump requires a live WebView and isn't headless-testable, which is consistent with the existing scroll-sync test suite's approach.

claude added 2 commits June 22, 2026 09:26
Turning Sync Panes off while a document was open did not fully detach the
panes: typing in the editor still scrolled the Preview toward the editor.
The cause was a stale, editor-derived lastPreviewScrollTop (written while
sync was on) that the full-reload completion handler restores
unconditionally, plus lingering Editor scroll ownership.

Detect the editorSyncScrolling transition in userDefaultsDidChange: and
react immediately:
- On disable: reset scroll ownership to Neither and recapture the
  preview's actual position so the panes become fully independent with no
  jump. Also keep lastPreviewScrollTop aligned with the preview's real
  position on the full-reload path whenever sync is off.
- On enable: re-sync immediately, editor-authoritative.

A cached lastKnownSyncScrolling (seeded at init from the live preference)
drives edge detection only; gating still always reads the live preference.

Adds Group O regression tests in MPScrollSyncTests.m covering the
disable/enable transitions, ownership handling, nil-safety, and edge
detection.

Related to #441
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Report

Current Coverage: 61.18%

Coverage Details (Summary)
Name                                                                                                                                   Coverage            
-------------------------------------------------------------------------------------------------------------------------------------- ------------------- 
MASPreferences.bundle                                                                                                                  0.00% (0/0)         
MacDown 3000.app                                                                                                                       62.43% (8880/14225) 
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Utility/MPMathJaxListener.m                                                61.54% (16/26)      
        -[MPMathJaxListener callbacks]                                                                                                 100.00% (5/5)       
        -[MPMathJaxListener addCallback:forKey:]                                                                                       100.00% (3/3)       
        -[MPMathJaxListener invokeCallbackForKey:]                                                                                     100.00% (8/8)       
        +[MPMathJaxListener isSelectorExcludedFromWebScript:]                                                                          0.00% (0/5)         
        +[MPMathJaxListener isKeyExcludedFromWebScript:]                                                                               0.00% (0/5)         
    /Users/runner/work/macdown3000/macdown3000/Dependency/peg-markdown-highlight/HGMarkdownHighlighter.m                               82.47% (461/559)    
        styleparsing_error_callback                                                                                                    90.00% (9/10)       
        -[HGMarkdownHighlighter init]                                                                                                  92.86% (13/14)      
        -[HGMarkdownHighlighter initWithTextView:]                                                                                     83.33% (5/6)        
        -[HGMarkdownHighlighter initWithTextView:waitInterval:]                                                                        83.33% (5/6)        
        -[HGMarkdownHighlighter initWithTextView:waitInterval:styles:]                                                                 83.33% (5/6)        
        -[HGMarkdownHighlighter parseText:]                                                                                            100.00% (9/9)       
        -[HGMarkdownHighlighter convertOffsets:text:]                                                                                  26.19% (11/42)      
        -[HGMarkdownHighlighter requestParsing]                                                                                        100.00% (15/15)     
        __39-[HGMarkdownHighlighter requestParsing]_block_invoke                                                                       100.00% (12/12)     
        __39-[HGMarkdownHighlighter requestParsing]_block_invoke.11                                                                    100.00% (3/3)       
        __39-[HGMarkdownHighlighter requestParsing]_block_invoke.13                                                                    100.00% (4/4)       
        -[HGMarkdownHighlighter getClearFontTraitMask:]                                                                                88.89% (16/18)      
        -[HGMarkdownHighlighter clearHighlightingForRange:]                                                                            100.00% (31/31)     
        __51-[HGMarkdownHighlighter clearHighlightingForRange:]_block_invoke                                                           100.00% (8/8)       
        -[HGMarkdownHighlighter readClearTextStylesFromTextView]                                                                       100.00% (17/17)     
        -[HGMarkdownHighlighter applyHighlighting:withRange:]                                                                          80.00% (60/75)      
        __53-[HGMarkdownHighlighter applyHighlighting:withRange:]_block_invoke                                                         92.31% (12/13)      
        -[HGMarkdownHighlighter applyVisibleRangeHighlighting]                                                                         100.00% (16/16)     
        -[HGMarkdownHighlighter clearHighlighting]                                                                                     100.00% (3/3)       
        -[HGMarkdownHighlighter cacheElementList:]                                                                                     100.00% (6/6)       
        -[HGMarkdownHighlighter clearElementsCache]                                                                                    100.00% (2/2)       
        -[HGMarkdownHighlighter textViewTextDidChange:]                                                                                0.00% (0/11)        
        __47-[HGMarkdownHighlighter textViewTextDidChange:]_block_invoke                                                               0.00% (0/3)         
        -[HGMarkdownHighlighter textViewDidScroll:]                                                                                    27.27% (3/11)       
        __43-[HGMarkdownHighlighter textViewDidScroll:]_block_invoke                                                                   0.00% (0/6)         
        __43-[HGMarkdownHighlighter textViewDidScroll:]_block_invoke_2                                                                 0.00% (0/3)         
        -[HGMarkdownHighlighter getDefaultStyles]                                                                                      100.00% (27/27)     
        -[HGMarkdownHighlighter applyStyleDependenciesToTargetTextView]                                                                92.86% (13/14)      
        -[HGMarkdownHighlighter setStyles:]                                                                                            100.00% (8/8)       
        -[HGMarkdownHighlighter getDefaultSelectedTextAttributes]                                                                      100.00% (7/7)       
        -[HGMarkdownHighlighter handleStyleParsingError:]                                                                              100.00% (12/12)     
        -[HGMarkdownHighlighter applyStylesFromStylesheet:withErrorHandler:]                                                           86.05% (74/86)      
        -[HGMarkdownHighlighter setTargetTextView:]                                                                                    100.00% (7/7)       
        -[HGMarkdownHighlighter parseAndHighlightNow]                                                                                  100.00% (3/3)       
        -[HGMarkdownHighlighter highlightNow]                                                                                          100.00% (3/3)       
        -[HGMarkdownHighlighter activate]                                                                                              100.00% (24/24)     
        -[HGMarkdownHighlighter deactivate]                                                                                            100.00% (18/18)     
    /Users/runner/work/macdown3000/macdown3000/Dependency/peg-markdown-highlight/HGMarkdownHighlightingStyle.m                         89.23% (58/65)      
        +[HGMarkdownHighlightingStyle colorFromARGBColor:]                                                                             100.00% (6/6)       

... (3243 more lines truncated)

📊 **Full coverage report available in workflow artifacts**

@schuyler schuyler merged commit 244ac11 into main Jun 22, 2026
10 checks passed
@schuyler schuyler deleted the claude/charming-tesla-3ktymh branch June 22, 2026 10:18
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.

2 participants