From f93e79a845c336eb8c433cdde0e4dcf391c8b54b Mon Sep 17 00:00:00 2001 From: Jason Pickens Date: Mon, 21 Sep 2026 17:24:44 +1200 Subject: [PATCH 1/5] fix(ui-macos): textSetColor no longer a no-op on a Text label install_label_cell restored the factory label's text via setAttributedStringValue:, baking the default labelColor into the field. An NSTextField holding an attributed string ignores setTextColor:, so perry_ui_text_set_color was silently overridden (#10856). Restore the text as a plain stringValue and set labelColor explicitly, so setTextColor: is honored while the default appearance is unchanged. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01H24gBtwYbfkHDkGhtkvypw --- CLAUDE.md | 2 +- Cargo.lock | 128 +++++++++---------- Cargo.toml | 2 +- crates/perry-ui-macos/src/widgets/padding.rs | 53 +++++++- 4 files changed, 116 insertions(+), 69 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 646b769fd3..c75787b468 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1622 +**Current Version:** 0.5.1623 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index ba5708dc77..7bfe1d80e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5543,7 +5543,7 @@ checksum = "1473d470930ed48574515a25df34900f3af89c6fa422d903e019121312a9f13e" [[package]] name = "perry" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "base64 0.22.1", @@ -5607,7 +5607,7 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-dispatch", "serde", @@ -5615,7 +5615,7 @@ dependencies = [ [[package]] name = "perry-audio-miniaudio" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "cc", "libc", @@ -5624,7 +5624,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "aho-corasick", "anyhow", @@ -5641,7 +5641,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "perry-hir", @@ -5649,7 +5649,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "perry-hir", @@ -5657,7 +5657,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "perry-dispatch", @@ -5666,7 +5666,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "perry-hir", @@ -5674,7 +5674,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "base64 0.22.1", @@ -5686,7 +5686,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "perry-hir", @@ -5694,7 +5694,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "async-trait", "clap", @@ -5718,14 +5718,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "serde", "serde_json", @@ -5733,7 +5733,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1622" +version = "0.5.1623" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5744,7 +5744,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "clap", @@ -5759,7 +5759,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "block2", "objc2", @@ -5769,7 +5769,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "argon2", "perry-ffi", @@ -5778,7 +5778,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "bcrypt", "perry-ffi", @@ -5786,7 +5786,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-ffi", "rusqlite", @@ -5794,7 +5794,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-ffi", "scraper", @@ -5802,7 +5802,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-ffi", "rust_decimal", @@ -5810,7 +5810,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-ffi", "rand 0.10.2", @@ -5818,7 +5818,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-ffi", "perry-runtime", @@ -5826,7 +5826,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "bytes", "lazy_static", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "base64 0.22.1", "bytes", @@ -5871,7 +5871,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "lazy_static", "perry-ffi", @@ -5881,7 +5881,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "bson", "futures-util", @@ -5893,7 +5893,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "chrono", "perry-ffi", @@ -5905,7 +5905,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "bytes", "perry-ffi", @@ -5920,7 +5920,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "lettre", "perry-ffi", @@ -5930,7 +5930,7 @@ dependencies = [ [[package]] name = "perry-ext-parcel-watcher" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "notify", "perry-ffi", @@ -5942,7 +5942,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-ffi", "printpdf", @@ -5950,7 +5950,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-ffi", "sqlx", @@ -5959,7 +5959,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "fast_image_resize", "image", @@ -5970,7 +5970,7 @@ dependencies = [ [[package]] name = "perry-ext-streams" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "lazy_static", "perry-ffi", @@ -5979,7 +5979,7 @@ dependencies = [ [[package]] name = "perry-ext-typescript" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "perry-ffi", @@ -5999,7 +5999,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-ffi", "perry-runtime", @@ -6008,7 +6008,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "futures-util", "lazy_static", @@ -6021,7 +6021,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "brotli", "flate2", @@ -6031,7 +6031,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "dashmap 6.2.1", "once_cell", @@ -6041,7 +6041,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "perry-api-manifest", @@ -6061,11 +6061,11 @@ dependencies = [ [[package]] name = "perry-native-registration" -version = "0.5.1622" +version = "0.5.1623" [[package]] name = "perry-parser" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "perry-diagnostics", @@ -6078,7 +6078,7 @@ dependencies = [ [[package]] name = "perry-perex" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perex", "regex", @@ -6086,7 +6086,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "ahash", "base64 0.22.1", @@ -6144,14 +6144,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6234,21 +6234,21 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "perry-hir", ] [[package]] name = "perry-ui" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "dirs", "perry-ffi", @@ -6258,7 +6258,7 @@ dependencies = [ [[package]] name = "perry-ui-android" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "base64 0.22.1", "jni", @@ -6273,7 +6273,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "rand 0.10.2", "serde", @@ -6283,7 +6283,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "base64 0.22.1", "cairo-rs 0.22.9", @@ -6306,7 +6306,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "base64 0.22.1", "block2", @@ -6323,7 +6323,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "base64 0.22.1", "block2", @@ -6340,7 +6340,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1622" +version = "0.5.1623" [[package]] name = "perry-ui-test" @@ -6351,11 +6351,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1622" +version = "0.5.1623" [[package]] name = "perry-ui-tvos" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "base64 0.22.1", "block2", @@ -6372,7 +6372,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "base64 0.22.1", "block2", @@ -6389,7 +6389,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "block2", "libc", @@ -6403,7 +6403,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "base64 0.22.1", "libc", @@ -6422,7 +6422,7 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "base64 0.22.1", "libc", @@ -6435,7 +6435,7 @@ dependencies = [ [[package]] name = "perry-updater" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "anyhow", "base64 0.22.1", @@ -6450,7 +6450,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1622" +version = "0.5.1623" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 3aaa869ead..8bcbef189d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -317,7 +317,7 @@ codegen-units = 1 codegen-units = 1 [workspace.package] -version = "0.5.1622" +version = "0.5.1623" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/crates/perry-ui-macos/src/widgets/padding.rs b/crates/perry-ui-macos/src/widgets/padding.rs index e4e88ae435..b0dfaeb5cf 100644 --- a/crates/perry-ui-macos/src/widgets/padding.rs +++ b/crates/perry-ui-macos/src/widgets/padding.rs @@ -1,7 +1,9 @@ use objc2::rc::Retained; use objc2::runtime::{AnyClass, AnyObject}; use objc2::{define_class, msg_send, DefinedClass, MainThreadOnly}; -use objc2_app_kit::{NSEvent, NSSecureTextFieldCell, NSText, NSTextField, NSTextFieldCell, NSView}; +use objc2_app_kit::{ + NSColor, NSEvent, NSSecureTextFieldCell, NSText, NSTextField, NSTextFieldCell, NSView, +}; use objc2_core_foundation::CGRect; use objc2_foundation::{MainThreadMarker, NSEdgeInsets, NSObjectProtocol}; use std::cell::Cell; @@ -206,7 +208,13 @@ pub(crate) fn install_secure_text_field_cell(field: &NSTextField, mtm: MainThrea /// Install at label creation, before callers apply attributed text or styles. /// Keep the factory label's text, font and line-breaking defaults. pub(crate) fn install_label_cell(field: &NSTextField, mtm: MainThreadMarker) { - let value = field.attributedStringValue(); + // Restore the text as a plain stringValue, never the factory label's + // attributedStringValue. An NSTextField holding an attributed string + // ignores setTextColor: — the string's baked-in color attribute wins — so + // an attributedStringValue here silently defeats textSetColor (#10856). + // The font is restored separately below, and labelColor is set explicitly + // to keep the label's default appearance. + let text = field.stringValue(); let font = field.font(); let original = field.cell().expect("label has a cell"); install_text_field_cell(field, mtm); @@ -216,7 +224,8 @@ pub(crate) fn install_label_cell(field: &NSTextField, mtm: MainThreadMarker) { field.setSelectable(false); field.setDrawsBackground(false); field.setFont(font.as_deref()); - field.setAttributedStringValue(&value); + field.setStringValue(&text); + field.setTextColor(Some(&NSColor::labelColor())); if let Some(cell) = field.cell() { cell.setWraps(original.wraps()); cell.setScrollable(original.isScrollable()); @@ -294,6 +303,7 @@ pub(crate) fn set_edge_insets(view: &NSView, top: f64, left: f64, bottom: f64, r #[cfg(test)] mod tests { use super::*; + use objc2_foundation::NSString; #[test] fn inset_rect_uses_appkit_bottom_origin_and_clamps() { @@ -316,4 +326,41 @@ mod tests { assert_eq!(got.size.width, 0.0); assert_eq!(got.size.height, 0.0); } + + /// #10856 — a label built by install_label_cell must honor setTextColor:. + /// The regression baked the factory attributedStringValue (labelColor) + /// into the field, and an NSTextField with an attributed string ignores + /// setTextColor:, so textSetColor was a silent no-op. After the fix the + /// field holds a plain stringValue, so a red textColor takes effect and + /// the synthesized attributedStringValue carries red at index 0. + #[test] + fn install_label_cell_lets_set_text_color_win() { + // Cargo runs each test off the main thread; these AppKit calls only + // create and inspect one NSTextField, with no NSApplication or run + // loop, so an unchecked main-thread marker is sound here. + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + let field = NSTextField::labelWithString(&NSString::from_str("hi"), mtm); + install_label_cell(&field, mtm); + + let red = NSColor::colorWithSRGBRed_green_blue_alpha(1.0, 0.0, 0.0, 1.0); + field.setTextColor(Some(&red)); + + let attributed = field.attributedStringValue(); + let key = NSString::from_str("NSColor"); + let color: Option> = unsafe { + msg_send![&*attributed, attribute: &*key, atIndex: 0usize, effectiveRange: std::ptr::null_mut::()] + }; + let color = color.expect("label carries a foreground color at index 0"); + let srgb = color + .colorUsingColorSpace(&objc2_app_kit::NSColorSpace::sRGBColorSpace()) + .expect("foreground color converts to sRGB"); + let (r, g, b) = ( + srgb.redComponent(), + srgb.greenComponent(), + srgb.blueComponent(), + ); + assert!(r > 0.9, "red component {r} — setTextColor: was overridden (#10856)"); + assert!(g < 0.1, "green component {g}"); + assert!(b < 0.1, "blue component {b}"); + } } From 6acbbb9f9eda0d4e6729203babd80cc0813819af Mon Sep 17 00:00:00 2001 From: Jason Pickens Date: Mon, 21 Sep 2026 17:30:30 +1200 Subject: [PATCH 2/5] =?UTF-8?q?chore:=20drop=20version=20bump=20=E2=80=94?= =?UTF-8?q?=20fork=20PR,=20maintainer=20bumps=20at=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Perry's CLAUDE.md external-contributor rule: PRs from a fork must not touch [workspace.package] version or the Current Version line; the maintainer bumps at merge time. Reverts the bump left on the fix commit. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01H24gBtwYbfkHDkGhtkvypw --- CLAUDE.md | 2 +- Cargo.lock | 128 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c75787b468..646b769fd3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1623 +**Current Version:** 0.5.1622 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 7bfe1d80e5..ba5708dc77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5543,7 +5543,7 @@ checksum = "1473d470930ed48574515a25df34900f3af89c6fa422d903e019121312a9f13e" [[package]] name = "perry" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "base64 0.22.1", @@ -5607,7 +5607,7 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-dispatch", "serde", @@ -5615,7 +5615,7 @@ dependencies = [ [[package]] name = "perry-audio-miniaudio" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "cc", "libc", @@ -5624,7 +5624,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "aho-corasick", "anyhow", @@ -5641,7 +5641,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "perry-hir", @@ -5649,7 +5649,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "perry-hir", @@ -5657,7 +5657,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "perry-dispatch", @@ -5666,7 +5666,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "perry-hir", @@ -5674,7 +5674,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "base64 0.22.1", @@ -5686,7 +5686,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "perry-hir", @@ -5694,7 +5694,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "async-trait", "clap", @@ -5718,14 +5718,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "serde", "serde_json", @@ -5733,7 +5733,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1623" +version = "0.5.1622" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5744,7 +5744,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "clap", @@ -5759,7 +5759,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "block2", "objc2", @@ -5769,7 +5769,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "argon2", "perry-ffi", @@ -5778,7 +5778,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "bcrypt", "perry-ffi", @@ -5786,7 +5786,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-ffi", "rusqlite", @@ -5794,7 +5794,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-ffi", "scraper", @@ -5802,7 +5802,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-ffi", "rust_decimal", @@ -5810,7 +5810,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-ffi", "rand 0.10.2", @@ -5818,7 +5818,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-ffi", "perry-runtime", @@ -5826,7 +5826,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "bytes", "lazy_static", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "base64 0.22.1", "bytes", @@ -5871,7 +5871,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "lazy_static", "perry-ffi", @@ -5881,7 +5881,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "bson", "futures-util", @@ -5893,7 +5893,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "chrono", "perry-ffi", @@ -5905,7 +5905,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "bytes", "perry-ffi", @@ -5920,7 +5920,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "lettre", "perry-ffi", @@ -5930,7 +5930,7 @@ dependencies = [ [[package]] name = "perry-ext-parcel-watcher" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "notify", "perry-ffi", @@ -5942,7 +5942,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-ffi", "printpdf", @@ -5950,7 +5950,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-ffi", "sqlx", @@ -5959,7 +5959,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "fast_image_resize", "image", @@ -5970,7 +5970,7 @@ dependencies = [ [[package]] name = "perry-ext-streams" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "lazy_static", "perry-ffi", @@ -5979,7 +5979,7 @@ dependencies = [ [[package]] name = "perry-ext-typescript" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "perry-ffi", @@ -5999,7 +5999,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-ffi", "perry-runtime", @@ -6008,7 +6008,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "futures-util", "lazy_static", @@ -6021,7 +6021,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "brotli", "flate2", @@ -6031,7 +6031,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "dashmap 6.2.1", "once_cell", @@ -6041,7 +6041,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "perry-api-manifest", @@ -6061,11 +6061,11 @@ dependencies = [ [[package]] name = "perry-native-registration" -version = "0.5.1623" +version = "0.5.1622" [[package]] name = "perry-parser" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "perry-diagnostics", @@ -6078,7 +6078,7 @@ dependencies = [ [[package]] name = "perry-perex" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perex", "regex", @@ -6086,7 +6086,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "ahash", "base64 0.22.1", @@ -6144,14 +6144,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6234,21 +6234,21 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "perry-hir", ] [[package]] name = "perry-ui" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "dirs", "perry-ffi", @@ -6258,7 +6258,7 @@ dependencies = [ [[package]] name = "perry-ui-android" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "base64 0.22.1", "jni", @@ -6273,7 +6273,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "rand 0.10.2", "serde", @@ -6283,7 +6283,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "base64 0.22.1", "cairo-rs 0.22.9", @@ -6306,7 +6306,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "base64 0.22.1", "block2", @@ -6323,7 +6323,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "base64 0.22.1", "block2", @@ -6340,7 +6340,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1623" +version = "0.5.1622" [[package]] name = "perry-ui-test" @@ -6351,11 +6351,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1623" +version = "0.5.1622" [[package]] name = "perry-ui-tvos" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "base64 0.22.1", "block2", @@ -6372,7 +6372,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "base64 0.22.1", "block2", @@ -6389,7 +6389,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "block2", "libc", @@ -6403,7 +6403,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "base64 0.22.1", "libc", @@ -6422,7 +6422,7 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "base64 0.22.1", "libc", @@ -6435,7 +6435,7 @@ dependencies = [ [[package]] name = "perry-updater" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "anyhow", "base64 0.22.1", @@ -6450,7 +6450,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1623" +version = "0.5.1622" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 8bcbef189d..3aaa869ead 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -317,7 +317,7 @@ codegen-units = 1 codegen-units = 1 [workspace.package] -version = "0.5.1623" +version = "0.5.1622" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" From e98406618f390604a066d007d2d7c59ca41485c6 Mon Sep 17 00:00:00 2001 From: Jason Pickens Date: Mon, 21 Sep 2026 17:34:18 +1200 Subject: [PATCH 3/5] changelog: key fragment to #10862 (textSetColor label fix) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01H24gBtwYbfkHDkGhtkvypw --- changelog.d/10862-textsetcolor-label.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/10862-textsetcolor-label.md diff --git a/changelog.d/10862-textsetcolor-label.md b/changelog.d/10862-textsetcolor-label.md new file mode 100644 index 0000000000..5ef45a6d1b --- /dev/null +++ b/changelog.d/10862-textsetcolor-label.md @@ -0,0 +1 @@ +Fixed macOS `textSetColor` having no visible effect on a `Text()` label. `perry-ui-macos`'s `install_label_cell` restored the factory label's text with `setAttributedStringValue:`, baking the default `labelColor` into the field. An `NSTextField` that holds an attributed string ignores `setTextColor:` — the string's baked color attribute wins — so `perry_ui_text_set_color` was silently overridden and every label rendered in `labelColor`. The text is now restored as a plain `stringValue` with `labelColor` set explicitly, so `setTextColor:` is honored while the default appearance is unchanged. Covered by a `perry-ui-macos` regression that reads back the label's foreground color after `setTextColor:`. From 84f13697c79f8b3ebeb95a7689835c3db5fead0a Mon Sep 17 00:00:00 2001 From: Jason Pickens Date: Mon, 21 Sep 2026 18:14:45 +1200 Subject: [PATCH 4/5] style: rustfmt-wrap the assert in the new label-color test Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01H24gBtwYbfkHDkGhtkvypw --- crates/perry-ui-macos/src/widgets/padding.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/perry-ui-macos/src/widgets/padding.rs b/crates/perry-ui-macos/src/widgets/padding.rs index b0dfaeb5cf..22f47f12b0 100644 --- a/crates/perry-ui-macos/src/widgets/padding.rs +++ b/crates/perry-ui-macos/src/widgets/padding.rs @@ -359,7 +359,10 @@ mod tests { srgb.greenComponent(), srgb.blueComponent(), ); - assert!(r > 0.9, "red component {r} — setTextColor: was overridden (#10856)"); + assert!( + r > 0.9, + "red component {r} — setTextColor: was overridden (#10856)" + ); assert!(g < 0.1, "green component {g}"); assert!(b < 0.1, "blue component {b}"); } From 8b7c81379a565ff724002e80c2e45f6ccc9dfbed Mon Sep 17 00:00:00 2001 From: Jason Pickens Date: Mon, 21 Sep 2026 20:18:42 +1200 Subject: [PATCH 5/5] test(ui-macos): run the label-color test on the main thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit flagged the inline #[test] in padding.rs: it uses the default Cargo harness, which runs on a worker thread, so MainThreadMarker::new_unchecked() plus AppKit calls were unsound. Replace it with a harness = false integration test (tests/native_text_color.rs) whose main() is the process main thread — the pattern the other native_widget_* tests use — driving the real create + set_color path and asserting the label renders red. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01H24gBtwYbfkHDkGhtkvypw --- crates/perry-ui-macos/Cargo.toml | 5 ++ crates/perry-ui-macos/src/widgets/padding.rs | 41 ------------ .../perry-ui-macos/tests/native_text_color.rs | 64 +++++++++++++++++++ 3 files changed, 69 insertions(+), 41 deletions(-) create mode 100644 crates/perry-ui-macos/tests/native_text_color.rs diff --git a/crates/perry-ui-macos/Cargo.toml b/crates/perry-ui-macos/Cargo.toml index 31fce17283..d7528f407d 100644 --- a/crates/perry-ui-macos/Cargo.toml +++ b/crates/perry-ui-macos/Cargo.toml @@ -90,3 +90,8 @@ harness = false name = "native_widget_max_width" path = "tests/native_widget_max_width.rs" harness = false + +[[test]] +name = "native_text_color" +path = "tests/native_text_color.rs" +harness = false diff --git a/crates/perry-ui-macos/src/widgets/padding.rs b/crates/perry-ui-macos/src/widgets/padding.rs index 22f47f12b0..f53179f255 100644 --- a/crates/perry-ui-macos/src/widgets/padding.rs +++ b/crates/perry-ui-macos/src/widgets/padding.rs @@ -303,7 +303,6 @@ pub(crate) fn set_edge_insets(view: &NSView, top: f64, left: f64, bottom: f64, r #[cfg(test)] mod tests { use super::*; - use objc2_foundation::NSString; #[test] fn inset_rect_uses_appkit_bottom_origin_and_clamps() { @@ -326,44 +325,4 @@ mod tests { assert_eq!(got.size.width, 0.0); assert_eq!(got.size.height, 0.0); } - - /// #10856 — a label built by install_label_cell must honor setTextColor:. - /// The regression baked the factory attributedStringValue (labelColor) - /// into the field, and an NSTextField with an attributed string ignores - /// setTextColor:, so textSetColor was a silent no-op. After the fix the - /// field holds a plain stringValue, so a red textColor takes effect and - /// the synthesized attributedStringValue carries red at index 0. - #[test] - fn install_label_cell_lets_set_text_color_win() { - // Cargo runs each test off the main thread; these AppKit calls only - // create and inspect one NSTextField, with no NSApplication or run - // loop, so an unchecked main-thread marker is sound here. - let mtm = unsafe { MainThreadMarker::new_unchecked() }; - let field = NSTextField::labelWithString(&NSString::from_str("hi"), mtm); - install_label_cell(&field, mtm); - - let red = NSColor::colorWithSRGBRed_green_blue_alpha(1.0, 0.0, 0.0, 1.0); - field.setTextColor(Some(&red)); - - let attributed = field.attributedStringValue(); - let key = NSString::from_str("NSColor"); - let color: Option> = unsafe { - msg_send![&*attributed, attribute: &*key, atIndex: 0usize, effectiveRange: std::ptr::null_mut::()] - }; - let color = color.expect("label carries a foreground color at index 0"); - let srgb = color - .colorUsingColorSpace(&objc2_app_kit::NSColorSpace::sRGBColorSpace()) - .expect("foreground color converts to sRGB"); - let (r, g, b) = ( - srgb.redComponent(), - srgb.greenComponent(), - srgb.blueComponent(), - ); - assert!( - r > 0.9, - "red component {r} — setTextColor: was overridden (#10856)" - ); - assert!(g < 0.1, "green component {g}"); - assert!(b < 0.1, "blue component {b}"); - } } diff --git a/crates/perry-ui-macos/tests/native_text_color.rs b/crates/perry-ui-macos/tests/native_text_color.rs new file mode 100644 index 0000000000..f0a539d16e --- /dev/null +++ b/crates/perry-ui-macos/tests/native_text_color.rs @@ -0,0 +1,64 @@ +// #10856 — textSetColor must color a Text() label. install_label_cell used to +// restore the text with setAttributedStringValue:, baking the default +// labelColor into the field; an NSTextField holding an attributed string +// ignores setTextColor:, so the color was silently overridden. This drives the +// real create + set_color path (the same entry points textSetColor lowers to) +// and asserts the label's rendered foreground color is the requested red. +// +// AppKit requires the main thread, so this is a harness = false binary whose +// main() is the process main thread — the pattern the other native_widget_* +// tests use. The default #[test] harness runs on a worker thread and cannot. + +#[cfg(target_os = "macos")] +fn main() { + use objc2::msg_send; + use objc2::rc::Retained; + use objc2_app_kit::{NSColorSpace, NSTextField}; + use objc2_foundation::{MainThreadMarker, NSString}; + use perry_ui_macos::widgets; + + if std::env::args().any(|arg| arg == "--list") { + println!("native_text_color: test"); + return; + } + let _mtm = MainThreadMarker::new().expect("native text-color test runs on the main thread"); + + let text = "hi"; + let string = perry_runtime::string::js_string_from_bytes(text.as_ptr(), text.len() as u32); + let handle = widgets::text::create(string.cast()); + widgets::text::set_color(handle, 1.0, 0.0, 0.0, 1.0); + + let view = widgets::get_widget(handle).expect("Text widget is registered"); + let field = unsafe { &*(Retained::as_ptr(&view) as *const NSTextField) }; + + // A plain-string field renders in its textColor; attributedStringValue + // synthesizes that color at index 0. The old bug left a baked labelColor + // here instead of the red just set. + let attributed = field.attributedStringValue(); + let key = NSString::from_str("NSColor"); + let color: Option> = unsafe { + msg_send![ + &*attributed, + attribute: &*key, + atIndex: 0usize, + effectiveRange: std::ptr::null_mut::() + ] + }; + let color = color.expect("label carries a foreground color at index 0"); + let srgb = color + .colorUsingColorSpace(&NSColorSpace::sRGBColorSpace()) + .expect("foreground color converts to sRGB"); + let (r, g, b) = ( + srgb.redComponent(), + srgb.greenComponent(), + srgb.blueComponent(), + ); + assert!( + r > 0.9 && g < 0.1 && b < 0.1, + "label rendered ({r:.3}, {g:.3}, {b:.3}), not red — setTextColor was overridden (#10856)" + ); + println!("PASS native text-color: textSetColor colors a Text label"); +} + +#[cfg(not(target_os = "macos"))] +fn main() {}