fix(editor): stabilize themes and line numbers for v0.4.19

This commit is contained in:
h3p 2026-02-16 12:04:09 +01:00
parent 62e4a9d65e
commit 0e8c91e8de
8 changed files with 117 additions and 24 deletions

View file

@ -4,6 +4,18 @@ All notable changes to **Neon Vision Editor** are documented in this file.
The format follows *Keep a Changelog*. Versions use semantic versioning with prerelease tags.
## [v0.4.19] - 2026-02-16
### Added
- Added adaptive theme background normalization so selected themes follow appearance mode (light in Light mode, dark in Dark/System-dark mode) without changing theme identity.
### Improved
- Improved cross-platform editor readability by enforcing mode-aware base/background contrast for all built-in themes, including Neon Glow.
### Fixed
- Fixed macOS line-number ruler behavior where line numbers could disappear near end-of-document when scrolling to the bottom.
- Fixed iOS line-number gutter sync at bottom scroll positions by clamping gutter content offset to valid bounds.
## [v0.4.18] - 2026-02-15
### Added

View file

@ -358,7 +358,7 @@
CODE_SIGNING_ALLOWED = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 234;
CURRENT_PROJECT_VERSION = 235;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = CS727NF72U;
ENABLE_APP_SANDBOX = YES;
@ -438,7 +438,7 @@
CODE_SIGNING_ALLOWED = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 234;
CURRENT_PROJECT_VERSION = 235;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = CS727NF72U;
ENABLE_APP_SANDBOX = YES;

View file

@ -398,10 +398,8 @@ extension ContentView {
if enabled {
// Keep toolbar material blended with the titlebar instead of rendering as a separate solid strip.
window.toolbarStyle = .unified
window.toolbar?.showsBaselineSeparator = false
window.styleMask.insert(.fullSizeContentView)
} else {
window.toolbar?.showsBaselineSeparator = true
window.styleMask.remove(.fullSizeContentView)
}
if #available(macOS 13.0, *) {

View file

@ -2092,6 +2092,7 @@ final class LineNumberedTextViewContainer: UIView {
lineNumberView.isEditable = false
lineNumberView.isSelectable = false
lineNumberView.isScrollEnabled = true
lineNumberView.bounces = false
lineNumberView.isUserInteractionEnabled = false
lineNumberView.backgroundColor = UIColor.secondarySystemBackground.withAlphaComponent(0.65)
lineNumberView.textColor = .secondaryLabel
@ -2132,6 +2133,7 @@ final class LineNumberedTextViewContainer: UIView {
let numbers = (1...lineCount).map(String.init).joined(separator: "\n")
lineNumberView.font = UIFont.monospacedDigitSystemFont(ofSize: max(11, fontSize - 1), weight: .regular)
lineNumberView.text = numbers
lineNumberView.layoutIfNeeded()
}
}
@ -2532,7 +2534,11 @@ struct CustomTextEditor: UIViewRepresentable {
func syncLineNumberScroll() {
guard let textView, let lineView = container?.lineNumberView else { return }
lineView.contentOffset = CGPoint(x: 0, y: textView.contentOffset.y)
let targetY = textView.contentOffset.y + textView.adjustedContentInset.top - lineView.adjustedContentInset.top
let minY = -lineView.adjustedContentInset.top
let maxY = max(minY, lineView.contentSize.height - lineView.bounds.height + lineView.adjustedContentInset.bottom)
let clampedY = min(max(targetY, minY), maxY)
lineView.setContentOffset(CGPoint(x: 0, y: clampedY), animated: false)
}
}
}

View file

@ -77,7 +77,7 @@ final class LineNumberRulerView: NSRulerView {
let visibleRectInContainer = visibleRect.offsetBy(dx: -tcOrigin.x, dy: -tcOrigin.y)
let visibleGlyphRange = lm.glyphRange(forBoundingRect: visibleRectInContainer, in: textContainer)
guard visibleGlyphRange.location != NSNotFound, visibleGlyphRange.length > 0 else { return }
guard visibleGlyphRange.location != NSNotFound else { return }
var drawnLineStarts = Set<Int>()
lm.enumerateLineFragments(forGlyphRange: visibleGlyphRange) { [self] _, usedRect, _, glyphRange, _ in
@ -111,6 +111,21 @@ final class LineNumberRulerView: NSRulerView {
let drawPoint = NSPoint(x: self.bounds.maxX - size.width - self.inset, y: drawY)
numberString.draw(at: drawPoint, withAttributes: attributes)
}
// Keep the last line number visible near end-of-document/bottom-scroll edge cases
// where AppKit can report an empty visible glyph range.
if drawnLineStarts.isEmpty, textLength > 0 {
let lastLineNumber = fullString.components(separatedBy: .newlines).count
let numberString = NSString(string: "\(lastLineNumber)")
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: textColor
]
let size = numberString.size(withAttributes: attributes)
let drawY = max(bounds.minY + 2, bounds.maxY - size.height - 6)
let drawPoint = NSPoint(x: bounds.maxX - size.width - inset, y: drawY)
numberString.draw(at: drawPoint, withAttributes: attributes)
}
}
private func installObservers(textView: NSTextView) {

View file

@ -220,12 +220,12 @@ struct WelcomeTourView: View {
private let pages: [TourPage] = [
TourPage(
title: "Whats New in This Release",
subtitle: "Major changes since v0.4.17:",
subtitle: "Major changes since v0.4.18:",
bullets: [
"Added iOS/macOS regression coverage in the editor refresh path so syntax highlighting remains stable across toolbar/menu and focus transitions.",
"Improved editor rendering consistency by preventing view-update color assignments from overriding attributed syntax token colors.",
"Fixed iOS issue where opening the toolbar `...` menu could temporarily drop syntax highlighting.",
"Fixed macOS issue where moving focus away from the editor/window could temporarily drop syntax highlighting."
"Added adaptive theme background normalization so selected themes follow appearance mode (light in Light mode, dark in Dark/System-dark mode) without changing theme identity.",
"Improved cross-platform editor readability by enforcing mode-aware base/background contrast for all built-in themes, including Neon Glow.",
"Fixed macOS line-number ruler behavior where line numbers could disappear near end-of-document when scrolling to the bottom.",
"Fixed iOS line-number gutter sync at bottom scroll positions by clamping gutter content offset to valid bounds."
],
iconName: "sparkles.rectangle.stack",
colors: [Color(red: 0.40, green: 0.28, blue: 0.90), Color(red: 0.96, green: 0.46, blue: 0.55)],

View file

@ -44,6 +44,69 @@ struct ThemePaletteColors {
let builtin: Color
}
private struct RGBColorComponents {
let red: Double
let green: Double
let blue: Double
}
private func colorComponents(_ color: Color) -> RGBColorComponents? {
#if os(macOS)
let platform = PlatformColor(color)
guard let srgb = platform.usingColorSpace(.sRGB) else { return nil }
return RGBColorComponents(
red: Double(srgb.redComponent),
green: Double(srgb.greenComponent),
blue: Double(srgb.blueComponent)
)
#else
let platform = PlatformColor(color)
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
guard platform.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else { return nil }
return RGBColorComponents(red: Double(red), green: Double(green), blue: Double(blue))
#endif
}
private func relativeLuminance(_ components: RGBColorComponents) -> Double {
(0.2126 * components.red) + (0.7152 * components.green) + (0.0722 * components.blue)
}
private func blend(_ source: Color, with target: Color, amount: Double) -> Color {
guard let sourceComponents = colorComponents(source), let targetComponents = colorComponents(target) else {
return source
}
let clamped = min(1.0, max(0.0, amount))
return Color(
red: sourceComponents.red + ((targetComponents.red - sourceComponents.red) * clamped),
green: sourceComponents.green + ((targetComponents.green - sourceComponents.green) * clamped),
blue: sourceComponents.blue + ((targetComponents.blue - sourceComponents.blue) * clamped)
)
}
private func modeAdjustedEditorBackground(_ background: Color, colorScheme: ColorScheme) -> Color {
guard let components = colorComponents(background) else { return background }
let luminance = relativeLuminance(components)
if colorScheme == .light {
// Keep all themes readable in light/system-light by lifting dark palettes.
if luminance >= 0.78 { return background }
let targetLuminance = 0.96
let normalized = (targetLuminance - luminance) / max(0.0001, 1.0 - luminance)
let mixAmount = min(0.95, max(0.70, normalized))
return blend(background, with: .white, amount: mixAmount)
}
// Keep all themes readable in dark/system-dark by lowering bright palettes.
if luminance <= 0.28 { return background }
let targetLuminance = 0.14
let normalized = (luminance - targetLuminance) / max(0.0001, luminance)
let mixAmount = min(0.90, max(0.55, normalized))
return blend(background, with: .black, amount: mixAmount)
}
///MARK: Theme Name Canonicalization
// Canonical theme names shown in settings and used for palette lookup.
@ -322,7 +385,7 @@ func currentEditorTheme(colorScheme: ColorScheme) -> EditorTheme {
return EditorTheme(
text: baseTextColor,
background: palette.background,
background: modeAdjustedEditorBackground(palette.background, colorScheme: colorScheme),
cursor: palette.cursor,
selection: palette.selection,
syntax: syntax

View file

@ -17,7 +17,7 @@
</p>
> Status: **active release**
> Latest release: **v0.4.18**
> Latest release: **v0.4.19**
> Platform target: **macOS 26 (Tahoe)** compatible with **macOS Sequoia**
> Apple Silicon: tested / Intel: not tested
@ -25,7 +25,7 @@
Prebuilt binaries are available on [GitHub Releases](https://github.com/h3pdesign/Neon-Vision-Editor/releases).
- Latest release: **v0.4.18**
- Latest release: **v0.4.19**
- Apple AppStore [On the AppStore](https://apps.apple.com/de/app/neon-vision-editor/id6758950965)
- TestFlight beta: [Join here](https://testflight.apple.com/join/YWB2fGAP)
- Architecture: Apple Silicon (Intel not tested)
@ -123,6 +123,13 @@ If macOS blocks first launch:
## Changelog
### v0.4.19 (summary)
- Added adaptive theme background normalization so selected themes follow appearance mode (light in Light mode, dark in Dark/System-dark mode) without changing theme identity.
- Improved cross-platform editor readability by enforcing mode-aware base/background contrast for all built-in themes, including Neon Glow.
- Fixed macOS line-number ruler behavior where line numbers could disappear near end-of-document when scrolling to the bottom.
- Fixed iOS line-number gutter sync at bottom scroll positions by clamping gutter content offset to valid bounds.
### v0.4.18 (summary)
- Added iOS/macOS regression coverage in the editor refresh path so syntax highlighting remains stable across toolbar/menu and focus transitions.
@ -136,14 +143,6 @@ If macOS blocks first launch:
- Improved syntax-highlighting stability during appearance/translucency transitions by forcing an immediate refresh instead of waiting for unrelated edits.
- Fixed a macOS editor bug where toggling translucent window mode could temporarily hide syntax highlighting until another action (for example changing font size) forced a rehighlight.
### v0.4.16 (summary)
- Added a release-doc synchronization gate to `release_all.sh` via `prepare_release_docs.py --check` so releases fail fast when docs are stale.
- Added a delegate-based updater download service that reports live progress into the update dialog.
- Improved updater install flow to stay user-driven/manual after verification, with Finder handoff instead of in-place app replacement.
- Improved editor appearance switching so base text colors are enforced immediately on light/dark mode changes across macOS and iOS.
- Fixed light-mode editor base text color to consistently use dark text across themes.
Full release history: [`CHANGELOG.md`](CHANGELOG.md)
## Known Limitations
@ -163,12 +162,12 @@ Full release history: [`CHANGELOG.md`](CHANGELOG.md)
## Release Integrity
- Tag: `v0.4.18`
- Tag: `v0.4.19`
- Tagged commit: `TBD`
- Verify local tag target:
```bash
git rev-parse --verify v0.4.18
git rev-parse --verify v0.4.19
```
- Verify downloaded artifact checksum locally: