Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
d008bf6
feat: add Markdown rendering with Mermaid diagram support
helipillo Apr 19, 2026
ef00e51
Merge branch 'muxy-app:main' into feat/markdown-renderer-pr
helipillo Apr 19, 2026
248881b
Remove native Mermaid renderer
helipillo Apr 19, 2026
57eef2b
Remove leftover mmdr references
helipillo Apr 19, 2026
e62efca
Merge branch 'feat/markdown-renderer-pr' of github.com:helipillo/muxy…
helipillo Apr 19, 2026
aa84a49
Merge remote-tracking branch 'origin/main' into feat/markdown-rendere…
helipillo Apr 20, 2026
b906114
formatting fixes
helipillo Apr 20, 2026
f703db2
Merge branch 'main' into feat/markdown-renderer-pr
helipillo Apr 20, 2026
cdde987
Add markdown split scroll sync toggle
helipillo Apr 21, 2026
b2bc2e6
Make markdown sync toggle visible and bridge preview scroll
helipillo Apr 21, 2026
3077bda
Fix markdown split scroll sync behavior
helipillo Apr 21, 2026
ff7d9b5
Target markdown preview content scroll root
helipillo Apr 21, 2026
429a65f
Polish markdown scroll sync controls
helipillo Apr 21, 2026
6fa7581
Unify markdown linked scrolling
helipillo Apr 21, 2026
2f52278
Restore markdown split scroll behavior
helipillo Apr 21, 2026
5e90f30
Fix long file editor scroll range
helipillo Apr 21, 2026
878880c
Merge branch 'feat/markdown-renderer-pr' of github.com:helipillo/muxy…
helipillo Apr 21, 2026
c5f5029
Fixed scroll unfied scroll bar
helipillo Apr 21, 2026
194c3ac
Smooth block scrolling
helipillo Apr 21, 2026
88e367d
Clean up markdown scroll linting
helipillo Apr 22, 2026
4c0f3cc
Merge upstream/main into feat/markdown-renderer-pr
helipillo Apr 22, 2026
d4c516a
Refactor markdown preview model files
helipillo Apr 22, 2026
60fa2c7
Fix markdown split scroll observer regression
helipillo Apr 22, 2026
dda1e0c
Fix markdown sync after typing auto-scroll
helipillo Apr 22, 2026
5b1aed7
Fix live markdown preview refresh and scroll restore
helipillo Apr 22, 2026
fb5a22a
Debounce markdown preview refresh while typing
helipillo Apr 22, 2026
d207b85
Tune markdown preview refresh idle threshold
helipillo Apr 22, 2026
76281f0
Improve linked markdown scroll alignment
helipillo Apr 22, 2026
6133f77
Clean markdown PR scope and harden preview
helipillo Apr 23, 2026
44ce654
Fix markdown HTML and Mermaid rendering regression
helipillo Apr 23, 2026
daf9e36
Smooth markdown linked scrolling
helipillo Apr 23, 2026
e9c8e10
feat(markdown): add preview DOM anchor metadata wrappers
helipillo Apr 23, 2026
81f6d3e
Add markdown source anchor parser
helipillo Apr 23, 2026
6df8c33
Workstream D: map editor viewport to markdown source anchors
helipillo Apr 23, 2026
79d6633
Add markdown anchor sync fixtures and QA checklist
helipillo Apr 23, 2026
36e5ebf
Add markdown preview anchor geometry measurement bridge
helipillo Apr 23, 2026
452a01a
Workstream E: coordinate markdown split sync via anchors
helipillo Apr 23, 2026
ea90696
Fix markdown image preview stability and top sync
helipillo Apr 23, 2026
153d1c8
Prioritize top-visible markdown anchor sync
helipillo Apr 23, 2026
0634fd8
Make markdown split sync callbacks asynchronous
helipillo Apr 23, 2026
0a6c6b6
Defer markdown split sync state updates
helipillo Apr 23, 2026
4883998
Clamp markdown sync at editor bottom
helipillo Apr 23, 2026
5df1efa
Tune markdown bottom sync threshold
helipillo Apr 23, 2026
e442ce3
Refine markdown preview bottom alignment
helipillo Apr 23, 2026
9f3897e
Reduce markdown bottom sync aggression
helipillo Apr 23, 2026
b6aa734
Tune markdown bottom threshold to 0.3 line height
helipillo Apr 23, 2026
acad715
Reduce markdown image relayout resync churn
helipillo Apr 23, 2026
1c0a42a
Resolve local HTML image sources in markdown preview
helipillo Apr 23, 2026
3be0be7
Add base href for local HTML assets in markdown preview
helipillo Apr 23, 2026
e398d37
Resolve project-root HTML image paths in markdown preview
helipillo Apr 23, 2026
fd8a948
Revert "Resolve project-root HTML image paths in markdown preview"
helipillo Apr 23, 2026
41eeea5
Revert "Add base href for local HTML assets in markdown preview"
helipillo Apr 23, 2026
75eb541
lint fix
helipillo Apr 23, 2026
954db1f
fix(markdown): harden preview HTML and scroll bridge
helipillo Apr 23, 2026
771e8c9
lint fixes
helipillo Apr 23, 2026
75e97d2
refactor(markdown): remove duplicate anchor parsing and trim PR scope
helipillo Apr 23, 2026
da69d84
fix(markdown): stabilize html images and preview scrolling
helipillo Apr 23, 2026
6645ac1
Merge branch 'main' into feat/markdown-anchor-sync-plan
helipillo Apr 23, 2026
8bfbaec
Fix markdown editor undo and preserve preview images
helipillo Apr 24, 2026
92d9a09
Merge branch 'feat/markdown-anchor-sync-plan' of github.com:helipillo…
helipillo Apr 24, 2026
ebe4015
Route undo and redo shortcuts explicitly
helipillo Apr 24, 2026
67997f6
Restore editor first responder for undo and redo
helipillo Apr 24, 2026
47da8b6
Merged with main
helipillo Apr 24, 2026
a08cc6e
Merge remote-tracking branch 'origin/main' into feat/markdown-anchor-…
saeedvaziry Apr 24, 2026
5afb45d
merge main
saeedvaziry Apr 24, 2026
0d43216
Fixes over scrolling on split view
helipillo Apr 24, 2026
1914477
Merge branch 'main' into feat/markdown-anchor-sync-plan
helipillo Apr 24, 2026
f74279e
Merge branch 'main' into feat/markdown-anchor-sync-plan
saeedvaziry Apr 24, 2026
6a282b8
ui
saeedvaziry Apr 24, 2026
272928c
Merge branch 'main' into feat/markdown-anchor-sync-plan
saeedvaziry Apr 24, 2026
93f60e4
Fix Copilot review issues in markdown sync
helipillo Apr 25, 2026
db46e0d
Fix markdown split preview scroll flashing
helipillo Apr 25, 2026
99899a0
Fix Mermaid theme drift on markdown rerender
helipillo Apr 25, 2026
14ab153
Make synced markdown preview non-scrollable
helipillo Apr 25, 2026
f364a1c
Block native scroll input in markdown preview
helipillo Apr 25, 2026
a15a006
drop highlightjs and replace with internal syntax highlighter
saeedvaziry Apr 25, 2026
9d82fd7
bundle mermaid and marked in the app instead of cdn
saeedvaziry Apr 25, 2026
480ee47
add local image loader
saeedvaziry Apr 25, 2026
0d92894
review
saeedvaziry Apr 25, 2026
31caa2b
review 2
saeedvaziry Apr 25, 2026
466e8ed
make mermaid load strict
saeedvaziry Apr 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Muxy/Resources/markdown-assets/** linguist-vendored
scripts/** linguist-vendored
107 changes: 107 additions & 0 deletions Muxy/Models/EditorTabState.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,49 @@
import AppKit
import CoreGraphics
import Foundation

enum EditorSearchNavigationDirection {
case next
case previous
}

enum EditorMarkdownViewMode: String, CaseIterable {
case code
case preview
case split

var title: String {
switch self {
case .code: "Code"
case .preview: "Preview"
case .split: "Split"
}
}

var symbol: String {
switch self {
case .code: "curlybraces"
case .preview: "doc.richtext"
case .split: "rectangle.split.2x1"
}
}
}

enum EditorMarkdownScrollDriver {
case editor
case preview
}

@MainActor
@Observable
final class EditorTabState: Identifiable {
private static let markdownExtensions: Set<String> = ["md", "markdown", "mdown", "mkd"]

let id = UUID()
let projectPath: String
private(set) var filePath: String
var backingStoreVersion = 0
var previewRefreshVersion = 0
var isLoading = false
var isIncrementalLoading = false
var isModified = false
Expand Down Expand Up @@ -39,6 +71,25 @@ final class EditorTabState: Identifiable {
var awaitingLargeFileConfirmation = false
var largeFileSize: Int64 = 0
var backingStore: TextBackingStore?
var markdownViewMode: EditorMarkdownViewMode = .code
var markdownScrollPosition: CGFloat = 0
var markdownEditorScrollY: CGFloat = 0
var markdownEditorMaxScrollY: CGFloat = 0
var markdownScrollSyncEnabled = true
var markdownScrollDriver: EditorMarkdownScrollDriver = .editor
var markdownActiveAnchorID: String?
var markdownActiveAnchorLocalProgress: Double = 0

var markdownPreviewScrollRequestVersion: Int = 0
var markdownPreviewScrollRequest: MarkdownSyncPoint?
var markdownEditorScrollRequestVersion: Int = 0
var markdownEditorScrollRequestLine: Int?

@ObservationIgnored private weak var linkedMarkdownEditorScrollView: NSScrollView?

@ObservationIgnored let markdownSyncCoordinator = MarkdownSyncCoordinator()
@ObservationIgnored private var markdownSyncAnchorsCache: [MarkdownSyncAnchor] = []
@ObservationIgnored private var markdownSyncAnchorsCacheVersion: Int = -1
@ObservationIgnored private(set) var syntaxHighlighter: SyntaxHighlighter?

static let largeFileWarningThreshold: Int64 = 5 * 1024 * 1024
Expand All @@ -63,6 +114,10 @@ final class EditorTabState: Identifiable {
return isModified ? "\(name) \u{2022}" : name
}

var isMarkdownFile: Bool {
Self.markdownExtensions.contains(fileExtension)
}

@ObservationIgnored private var loadTask: Task<Void, Never>?

private enum FileLoadEvent {
Expand All @@ -85,6 +140,9 @@ final class EditorTabState: Identifiable {
init(projectPath: String, filePath: String) {
self.projectPath = projectPath
self.filePath = filePath
if isMarkdownFile {
markdownViewMode = .preview
}
syntaxHighlighter = Self.makeSyntaxHighlighter(for: filePath)
loadFile()
}
Expand All @@ -96,6 +154,55 @@ final class EditorTabState: Identifiable {
refreshReadOnlyStatus()
}

func registerLinkedMarkdownEditorScrollView(_ scrollView: NSScrollView) {
linkedMarkdownEditorScrollView = scrollView
}

func unregisterLinkedMarkdownEditorScrollView(_ scrollView: NSScrollView) {
guard linkedMarkdownEditorScrollView === scrollView else { return }
linkedMarkdownEditorScrollView = nil
}

func forwardLinkedMarkdownScroll(deltaY: CGFloat) {
guard let scrollView = linkedMarkdownEditorScrollView else { return }

let visibleHeight = scrollView.contentView.bounds.height
let documentHeight = scrollView.documentView?.bounds.height ?? 0
let maxScrollY = max(0, documentHeight - visibleHeight)
let currentY = scrollView.contentView.bounds.origin.y
let targetY = min(maxScrollY, max(0, currentY + deltaY))

guard abs(targetY - currentY) > 0.1 else { return }

markdownScrollDriver = .preview
scrollView.contentView.setBoundsOrigin(NSPoint(x: scrollView.contentView.bounds.origin.x, y: targetY))
scrollView.reflectScrolledClipView(scrollView.contentView)
}

func markdownSyncAnchors() -> [MarkdownSyncAnchor] {
guard isMarkdownFile else { return [] }
guard let backingStore else { return [] }
guard markdownSyncAnchorsCacheVersion != backingStoreVersion else {
return markdownSyncAnchorsCache
}
markdownSyncAnchorsCache = MarkdownAnchorParser.parseAnchors(in: backingStore.fullText())
markdownSyncAnchorsCacheVersion = backingStoreVersion
return markdownSyncAnchorsCache
}

func applyMarkdownSyncOutput(_ output: MarkdownSyncCoordinator.Output) {
if let point = output.requestPreviewScroll {
markdownScrollDriver = .editor
markdownPreviewScrollRequest = point
markdownPreviewScrollRequestVersion += 1
}
if let line = output.requestEditorScrollLine {
markdownScrollDriver = .preview
markdownEditorScrollRequestLine = line
markdownEditorScrollRequestVersion += 1
}
}

private static func makeSyntaxHighlighter(for filePath: String) -> SyntaxHighlighter? {
guard let grammar = SyntaxLanguageRegistry.grammar(forFile: filePath) else { return nil }
return SyntaxHighlighter(grammar: grammar)
Expand Down
Loading
Loading