From 1a58d91bb36ca20f93dedd5a1aebbf98a1adea41 Mon Sep 17 00:00:00 2001 From: psmattas Date: Thu, 20 Aug 2026 15:04:50 +0100 Subject: [PATCH] fix: images resize with window, blend toolbar into content Images sized themselves once at restyle time and never got re-measured on a pure window/pane resize (no text change, no image fingerprint change) - so they'd stay whatever width they were last styled at. The engine already had the fix for exactly this shape of problem for wide tables (a stamped .scrollableBlockFullRange attribute triggers a targeted restyle on width change), and the shared image-rendering helper already had a restyleOnWidthChange flag to opt into it - just never passed at the image call sites. Wired it on for both ![]() and ![[embed]] rendering. Also makes the titlebar/toolbar strip blend into the sidebar's background instead of reading as a separate bar, matching Mail/Notes/ Finder - titlebarAppearsTransparent + fullSizeContentView via a small NSViewRepresentable, same "reach into NSWindow directly" pattern applyMacAppearance() already uses since SwiftUI's WindowGroup has no direct API for either. --- Outpost/OutpostApp.swift | 25 +++++++++++++++++++ .../Styling/MarkdownStyler+Images.swift | 4 +++ .../NativeTextView+FrameAndOverscroll.swift | 14 +++++++---- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Outpost/OutpostApp.swift b/Outpost/OutpostApp.swift index 73e86fc..3ad9c4a 100644 --- a/Outpost/OutpostApp.swift +++ b/Outpost/OutpostApp.swift @@ -34,6 +34,7 @@ struct OutpostApp: App { .onAppear { applyMacAppearance() } .onChange(of: appearance) { _, _ in applyMacAppearance() } .logoutConfirmationDialog(isPresented: $isShowingLogoutConfirmation, session: session) + .background(TransparentTitlebarWindowAccessor()) #endif } #if os(macOS) @@ -101,3 +102,27 @@ struct OutpostApp: App { } #endif } + +#if os(macOS) +/// Makes the titlebar/toolbar strip blend into the sidebar's own background +/// instead of reading as a separate bar — same look as Mail/Notes/Finder. +/// SwiftUI's `WindowGroup` exposes no direct hook for this, so this reaches +/// into the underlying `NSWindow` the way `applyMacAppearance()` above +/// reaches into `NSApp` for the same reason (no SwiftUI-level API exists). +/// A `View` (not the window itself) is what actually needs to extend under +/// the now-transparent titlebar — `.fullSizeContentView` just makes room; +/// the sidebar's `.background` already does the rest with no other change. +private struct TransparentTitlebarWindowAccessor: NSViewRepresentable { + func makeNSView(context: Context) -> NSView { + let view = NSView() + DispatchQueue.main.async { + guard let window = view.window else { return } + window.titlebarAppearsTransparent = true + window.styleMask.insert(.fullSizeContentView) + } + return view + } + + func updateNSView(_ nsView: NSView, context: Context) {} +} +#endif diff --git a/Vendor/swift-markdown-engine/Sources/MarkdownEngine/Styling/MarkdownStyler+Images.swift b/Vendor/swift-markdown-engine/Sources/MarkdownEngine/Styling/MarkdownStyler+Images.swift index dad93d1..09b412d 100644 --- a/Vendor/swift-markdown-engine/Sources/MarkdownEngine/Styling/MarkdownStyler+Images.swift +++ b/Vendor/swift-markdown-engine/Sources/MarkdownEngine/Styling/MarkdownStyler+Images.swift @@ -75,6 +75,7 @@ extension MarkdownStyler { paragraphSpacing: imageEmbedConfig.paragraphSpacing, alignment: .left, mode: .visibleSource(imageGap: imageEmbedConfig.imageGap), + restyleOnWidthChange: true, ctx: ctx, attrs: &attrs ) @@ -88,6 +89,7 @@ extension MarkdownStyler { paragraphSpacing: imageEmbedConfig.paragraphSpacing, alignment: .left, mode: .collapsedSource(markerTexts: ["![", "]", "(", ")"]), + restyleOnWidthChange: true, ctx: ctx, attrs: &attrs ) @@ -166,6 +168,7 @@ extension MarkdownStyler { paragraphSpacing: imageEmbedConfig.paragraphSpacing, alignment: .left, mode: .visibleSource(imageGap: imageEmbedConfig.imageGap), + restyleOnWidthChange: true, ctx: ctx, attrs: &attrs ) @@ -179,6 +182,7 @@ extension MarkdownStyler { paragraphSpacing: imageEmbedConfig.paragraphSpacing, alignment: .left, mode: .collapsedSource(markerTexts: ["![[", "]]"]), + restyleOnWidthChange: true, ctx: ctx, attrs: &attrs ) diff --git a/Vendor/swift-markdown-engine/Sources/MarkdownEngine/TextView/NativeTextView/NativeTextView+FrameAndOverscroll.swift b/Vendor/swift-markdown-engine/Sources/MarkdownEngine/TextView/NativeTextView/NativeTextView+FrameAndOverscroll.swift index a5aff80..7c4e6ff 100644 --- a/Vendor/swift-markdown-engine/Sources/MarkdownEngine/TextView/NativeTextView/NativeTextView+FrameAndOverscroll.swift +++ b/Vendor/swift-markdown-engine/Sources/MarkdownEngine/TextView/NativeTextView/NativeTextView+FrameAndOverscroll.swift @@ -269,21 +269,25 @@ extension NativeTextView { recalcOverscroll(for: scrollView, targetWidth: newSize.width, debugTag: "setFrameSize") - // Width change → only rendered table paragraphs need restyling. Their image - // width can change, and an initially narrow table can become scrollable. + // Width change → only paragraphs tagged `.scrollableBlockFullRange` + // (rendered tables and standalone images — see appendRenderedStandaloneBlock's + // `restyleOnWidthChange`) need restyling. Their display size is measured + // off the container width, and an initially narrow table can become + // scrollable. if widthChanged { DispatchQueue.main.async { [weak self] in guard let self = self else { return } if self.configuration.readingWidth == nil { - self.restyleTableParagraphsForWidthChange() + self.restyleWidthDependentParagraphsForWidthChange() } self.updateWideTableOverlays() } } } - /// Restyle only table paragraphs via stamped anchor ranges; avoids re-tokenizing the doc. - private func restyleTableParagraphsForWidthChange() { + /// Restyle only width-dependent paragraphs (tables, standalone images) via + /// stamped anchor ranges; avoids re-tokenizing the doc. + private func restyleWidthDependentParagraphsForWidthChange() { guard let storage = textStorage, let coord = delegate as? NativeTextViewCoordinator else { return } var ranges: [NSRange] = []