Skip to content

Address issue #110: Watch local resources and refresh on change#327

Merged
schuyler merged 5 commits into
mainfrom
claude/fix-pr-305-conflicts-2fLc7
Feb 26, 2026
Merged

Address issue #110: Watch local resources and refresh on change#327
schuyler merged 5 commits into
mainfrom
claude/fix-pr-305-conflicts-2fLc7

Conversation

@schuyler

@schuyler schuyler commented Feb 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • MPFileWatcher: Reusable single-file watcher using GCD dispatch sources (DISPATCH_SOURCE_TYPE_VNODE), extracted from inline code in MPDocument
  • MPResourceWatcherSet: Manages a set of MPFileWatcher instances with set-diffing to add/remove only changed watchers
  • MPHTMLResourceURLs: Pure utility functions that parse rendered HTML for local resource paths (img, video, audio, source, iframe src + link href) and apply cache-busting ?t=<timestamp> query parameters
  • MPRenderer integration: Stores per-path timestamps and applies cache-busting before wrapping HTML output
  • MPDocument integration: Refactored existing document file watcher to use MPFileWatcher; added MPResourceWatcherSet that updates watched paths on every render and triggers a re-render on change

When a local resource (image, video, audio, CSS) referenced in the Markdown preview is modified on disk, the preview now automatically refreshes to show the updated content.

Related Issue

Related to #110

Supersedes #305 (rebased to resolve merge conflicts with main)

Manual Testing Plan

  1. Create a Markdown file referencing a local image: ![test](photo.png)
  2. Open in MacDown and verify the image displays in the preview
  3. Modify photo.png externally (e.g., in Preview or with an image editor)
  4. Verify the preview updates within ~1 second without manual action
  5. Test with other resource types: <video src="clip.mp4">, <audio src="sound.mp3">, <link href="style.css">
  6. Test with subdirectory paths: ![](images/photo.png)
  7. Test with absolute paths: ![](/tmp/photo.png)
  8. Verify remote images (https://...) do not trigger file watching
  9. Switch to a different Markdown document and verify old watchers are cleaned up
  10. Delete a watched resource file and verify no crash or error

Review Notes

  • Full TDD test coverage: 20 tests for MPHTMLResourceURLs, 11 tests for MPResourceWatcherSet, 8 tests for MPFileWatcher
  • All tests pass on macOS 14, 15, 15-intel, and 26

Add file watching for local images, video, audio, and other resources
referenced in the markdown preview. When a watched file changes on disk,
append a cache-busting timestamp to its URL and re-render the preview.

- MPFileWatcher: reusable single-file watcher (extracts dispatch source
  pattern from MPDocument)
- MPResourceWatcherSet: manages a diffable set of file watchers
- MPHTMLResourceURLs: extract local file paths from HTML and apply
  cache-busting query parameters
- Refactor MPDocument to use MPFileWatcher for document auto-reload
- Integrate resource watching into the render pipeline

Related to #110
Atomic writes (atomically:YES) create a temp file and rename it,
which triggers DISPATCH_VNODE_RENAME instead of DISPATCH_VNODE_WRITE.
Use atomically:NO so the in-place write triggers the expected event.

Related to #110
Remove redundant testHandlerReceivesCorrectPath (it still had
atomically:YES and duplicated testHandlerCalledOnFileWrite).
Increase dispatch_after delays to 0.2s and timeouts to 5s for
CI runners under load.

Related to #110
- Move updateWatchedPaths call before the DOM replacement / full reload
  split so newly added image references are watched immediately
- Handle directory base URLs (unsaved documents) correctly in
  MPResolveLocalPath — check hasDirectoryPath before stripping filename
- Add test for directory base URL case

Related to #110
@github-actions

github-actions Bot commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Report

Current Coverage: 56.33%

Coverage Details (Summary)
Name                                                                                                                                   Coverage            
-------------------------------------------------------------------------------------------------------------------------------------- ------------------- 
MASPreferences.bundle                                                                                                                  0.00% (0/0)         
MacDown 3000.app                                                                                                                       59.65% (8157/13675) 
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Preferences/MPHtmlPreferencesViewController.m                              0.00% (0/112)       
        MPPrismDefaultThemeName                                                                                                        0.00% (0/2)         
        -[MPHtmlPreferencesViewController viewIdentifier]                                                                              0.00% (0/3)         
        -[MPHtmlPreferencesViewController toolbarItemImage]                                                                            0.00% (0/3)         
        -[MPHtmlPreferencesViewController toolbarItemLabel]                                                                            0.00% (0/2)         
        -[MPHtmlPreferencesViewController viewWillAppear]                                                                              0.00% (0/4)         
        -[MPHtmlPreferencesViewController changeStylesheet:]                                                                           0.00% (0/7)         
        -[MPHtmlPreferencesViewController changeHighlightingTheme:]                                                                    0.00% (0/7)         
        -[MPHtmlPreferencesViewController invokeStylesheetFunction:]                                                                   0.00% (0/23)        
        -[MPHtmlPreferencesViewController invokeHighlightingThemeFunction:]                                                            0.00% (0/33)        
        -[MPHtmlPreferencesViewController loadStylesheets]                                                                             0.00% (0/13)        
        -[MPHtmlPreferencesViewController loadHighlightingThemes]                                                                      0.00% (0/15)        
    /Users/runner/work/macdown3000/macdown3000/MacDown/Code/Document/MPDocument.m                                                      54.64% (1106/2024)  
        MPEditorPreferenceKeyWithValueKey                                                                                              85.71% (6/7)        
        MPEditorKeysToObserve                                                                                                          100.00% (15/15)     
        __MPEditorKeysToObserve_block_invoke                                                                                           100.00% (10/10)     
        MPEditorPreferencesToObserve                                                                                                   100.00% (14/14)     
        __MPEditorPreferencesToObserve_block_invoke                                                                                    100.00% (9/9)       
        MPRectStringForAutosaveName                                                                                                    100.00% (6/6)       
        MPAreNilableStringsEqual                                                                                                       100.00% (3/3)       
        MPGetWebViewBackgroundColor                                                                                                    0.00% (0/9)         
        -[NSURL(Convert) absoluteBaseURLString]                                                                                        0.00% (0/6)         
        -[WebView(Shortcut) enclosingScrollView]                                                                                       100.00% (3/3)       
        -[MPPreferences(Hoedown) extensionFlags]                                                                                       78.57% (22/28)      
        -[MPPreferences(Hoedown) rendererFlags]                                                                                        75.00% (9/12)       
        MPGetPreviewLoadingCompletionHandler                                                                                           100.00% (26/26)     
        __MPGetPreviewLoadingCompletionHandler_block_invoke                                                                            100.00% (23/23)     
        -[MPDocument preferences]                                                                                                      100.00% (3/3)       
        -[MPDocument markdown]                                                                                                         100.00% (3/3)       
        -[MPDocument setMarkdown:]                                                                                                     100.00% (3/3)       
        -[MPDocument html]                                                                                                             0.00% (0/3)         
        -[MPDocument toolbarVisible]                                                                                                   0.00% (0/3)         
        -[MPDocument previewVisible]                                                                                                   100.00% (3/3)       
        -[MPDocument editorVisible]                                                                                                    100.00% (3/3)       
        -[MPDocument needsHtml]                                                                                                        80.00% (4/5)        
        -[MPDocument setTotalWords:]                                                                                                   0.00% (0/7)         
        -[MPDocument setTotalCharacters:]                                                                                              0.00% (0/7)         
        -[MPDocument setTotalCharactersNoSpaces:]                                                                                      0.00% (0/8)         
        -[MPDocument setAutosaveName:]                                                                                                 100.00% (4/4)       
        -[MPDocument init]                                                                                                             90.00% (9/10)       
        -[MPDocument windowNibName]                                                                                                    100.00% (3/3)       
        -[MPDocument windowControllerDidLoadNib:]                                                                                      100.00% (99/99)     
        __41-[MPDocument windowControllerDidLoadNib:]_block_invoke                                                                     100.00% (3/3)       
        __41-[MPDocument windowControllerDidLoadNib:]_block_invoke.249                                                                 100.00% (6/6)       
        -[MPDocument reloadFromLoadedString]                                                                                           100.00% (8/8)       
        -[MPDocument close]                                                                                                            13.33% (4/30)       

... (2782 more lines truncated)

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

@schuyler schuyler merged commit 28ffecb into main Feb 26, 2026
8 of 9 checks passed
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