diff --git a/com.stakwork.sphinx.desktop/Helpers/ChatHelper.swift b/com.stakwork.sphinx.desktop/Helpers/ChatHelper.swift index a1611c2d..4c2b1b3d 100644 --- a/com.stakwork.sphinx.desktop/Helpers/ChatHelper.swift +++ b/com.stakwork.sphinx.desktop/Helpers/ChatHelper.swift @@ -746,7 +746,7 @@ class ChatHelper { return viewsHeight } - public static func getTextHeightFor( + @MainActor public static func getTextHeightFor( text: String, width: CGFloat, font: NSFont? = nil, @@ -761,7 +761,19 @@ class ChatHelper { let attributedString: NSAttributedString if useMarkdown { - attributedString = ChatHelper.markdownRenderer.render(text) + let rendered = NSMutableAttributedString( + attributedString: ChatHelper.markdownRenderer.render(text) + ) + // Strip .link so boundingRect applies the same slash-boundary word-breaking + // used by the displayed string. Intentionally uses bulk removeAttribute + // rather than MessageTextField.stripLinkAttributes — measurement only needs + // removal; .sphinxURL substitution is not needed here and coupling ChatHelper + // to MessageTextField for a 1-line operation adds unnecessary overhead. + // If stripLinkAttributes is ever changed (e.g. conditional skip logic or a + // different replacement attribute), audit this site to keep the two paths + // in sync. + rendered.removeAttribute(.link, range: NSRange(location: 0, length: rendered.length)) + attributedString = rendered } else { let attrs = [NSAttributedString.Key.font: font ?? Constants.kMessageFont] let mutable = NSMutableAttributedString(string: text, attributes: attrs) @@ -827,6 +839,10 @@ class ChatHelper { } } + // Strip .link before boundingRect — the display path removes .link from URL + // runs, and leaving it here causes the same undercount as the useMarkdown: true + // path. See comment in the useMarkdown: true branch above for rationale. + mutable.removeAttribute(.link, range: NSRange(location: 0, length: mutable.length)) attributedString = mutable } diff --git a/com.stakwork.sphinx.desktopTests/ChatHelperHeightTests.swift b/com.stakwork.sphinx.desktopTests/ChatHelperHeightTests.swift new file mode 100644 index 00000000..8c69cf9d --- /dev/null +++ b/com.stakwork.sphinx.desktopTests/ChatHelperHeightTests.swift @@ -0,0 +1,31 @@ +// +// ChatHelperHeightTests.swift +// com.stakwork.sphinx.desktopTests +// + +import XCTest +@testable import sphinx + +final class ChatHelperHeightTests: XCTestCase { + + @MainActor + func testGetTextHeightForMatchesBoundingRectAfterLinkStrip() { + let text = "Check this out https://hive.sphinx.chat/w/graphmindset/plan/cmt1iwce9000hjt04m755nqc5" + let width: CGFloat = 500 + + // Manually reproduce what the fixed getTextHeightFor does internally + let rendered = NSMutableAttributedString( + attributedString: ChatHelper.markdownRenderer.render(text) + ) + rendered.removeAttribute(.link, range: NSRange(location: 0, length: rendered.length)) + let expected = rendered.boundingRect( + with: CGSize(width: width, height: .greatestFiniteMagnitude), + options: [.usesLineFragmentOrigin, .usesFontLeading] + ).height + + let actual = ChatHelper.getTextHeightFor(text: text, width: width, useMarkdown: true) + + XCTAssertEqual(actual, expected, accuracy: 1.0, + "getTextHeightFor must match boundingRect on a .link-stripped string") + } +} diff --git a/com.stakwork.sphinx.desktopTests/Info.plist b/com.stakwork.sphinx.desktopTests/Info.plist index 64d65ca4..11173ad1 100644 --- a/com.stakwork.sphinx.desktopTests/Info.plist +++ b/com.stakwork.sphinx.desktopTests/Info.plist @@ -18,5 +18,7 @@ 1.0 CFBundleVersion 1 + ATSApplicationFontsPath + Fonts diff --git a/sphinx.xcodeproj/project.pbxproj b/sphinx.xcodeproj/project.pbxproj index 81d90c6c..e622327d 100644 --- a/sphinx.xcodeproj/project.pbxproj +++ b/sphinx.xcodeproj/project.pbxproj @@ -750,6 +750,8 @@ PTFT00100000000001PTFT001 /* PaddedTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = PTFT00000000000001PTFT000 /* PaddedTextFieldTests.swift */; }; SOMCT0020000000001SOMCT02 /* SphinxOnionManagerConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = SOMCT0010000000001SOMCT01 /* SphinxOnionManagerConnectionTests.swift */; }; SOMSG0020000000001SOMSG02 /* SphinxOnionManagerSeedGenerationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = SOMSG0010000000001SOMSG01 /* SphinxOnionManagerSeedGenerationTests.swift */; }; + CHHT0020000000001CHHT002 /* ChatHelperHeightTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CHHT0010000000001CHHT001 /* ChatHelperHeightTests.swift */; }; + CHHT0030000000001CHHT003 /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4703F05B2481C61B00282186 /* Roboto-Light.ttf */; }; TRTTS0020000000001TRTTS02 /* TribeTimestampTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = TRTTS0010000000001TRTTS01 /* TribeTimestampTests.swift */; }; /* End PBXBuildFile section */ @@ -1845,6 +1847,7 @@ PTFT00000000000001PTFT000 /* PaddedTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddedTextFieldTests.swift; sourceTree = ""; }; SOMCT0010000000001SOMCT01 /* SphinxOnionManagerConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SphinxOnionManagerConnectionTests.swift; sourceTree = ""; }; SOMSG0010000000001SOMSG01 /* SphinxOnionManagerSeedGenerationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SphinxOnionManagerSeedGenerationTests.swift; sourceTree = ""; }; + CHHT0010000000001CHHT001 /* ChatHelperHeightTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelperHeightTests.swift; sourceTree = ""; }; TRTTS0010000000001TRTTS01 /* TribeTimestampTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TribeTimestampTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -2262,6 +2265,7 @@ TRTTS0010000000001TRTTS01 /* TribeTimestampTests.swift */, SOMSG0010000000001SOMSG01 /* SphinxOnionManagerSeedGenerationTests.swift */, SOMCT0010000000001SOMCT01 /* SphinxOnionManagerConnectionTests.swift */, + CHHT0010000000001CHHT001 /* ChatHelperHeightTests.swift */, 4734D3AD2417E3D500D6957E /* Info.plist */, ); path = com.stakwork.sphinx.desktopTests; @@ -4489,6 +4493,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + CHHT0030000000001CHHT003 /* Roboto-Light.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5130,6 +5135,7 @@ TRTTS0020000000001TRTTS02 /* TribeTimestampTests.swift in Sources */, SOMSG0020000000001SOMSG02 /* SphinxOnionManagerSeedGenerationTests.swift in Sources */, SOMCT0020000000001SOMCT02 /* SphinxOnionManagerConnectionTests.swift in Sources */, + CHHT0020000000001CHHT002 /* ChatHelperHeightTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };