fix(ui-macos): build text field cells through cellClass and pad them as CSS does (#11661) - #11665
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (14)
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe macOS text-field implementation now uses custom field subclasses and inset cells. It handles line breaks in programmatic values and user input, applies TextField backgrounds through the view layer, and adds native tests and a smoke fixture for rendering and input behavior. ChangesmacOS Text Fields
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Low Merge Risk: ⚪ Minimal · up to The changes align macOS field rendering and define single-line input behavior. No actionable merge-blocking issue was established; merging is reasonable after normal macOS checks. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change affects how text and passwords are entered and displayed. Secure rendering remains delegated to the native password control, and no new disclosure or privilege-gain path was established. Editing recovery and credential lifetime are not fully verified. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Out of Scope Changes checkExplanation The baseline and inset-cell changes relate to the historical context in [ Resolution Remove the unrelated newline behavior and test, the
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…1665) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
…TS#11661) In single-line mode, AppKit draws a cell's idle text on the baseline of the system font for the control size and ignores the cell's own font. The field editor uses the cell's font, so a TextField with a custom font moved when editing started. At 16pt Helvetica the text moved 2pt. From about 20pt, the idle text clipped at the top. The inset cells now draw their interior with single-line mode off. Single-line mode still governs the field editor, so newline input still becomes spaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
The secure field never uses single-line mode, so its override never changed the drawing. The baseline test now also asserts that an idle draw leaves single-line mode as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
…1665) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
Text fields and labels replaced the cell that AppKit builds, so that setPadding could inset their text. The new cell lost the factory setup. The fields then set it again by hand, and the label factories restored it one property at a time. Single-line mode, which one of those settings turned on, drew idle text on the baseline of the system font. PerryInsetTextField and PerryInsetSecureTextField now return the inset cells from cellClass. textFieldWithString: and labelWithString: then build the inset cell with the factory setup. This removes the cell swap, the label restore, the one-line settings, and the draw override that turned single-line mode off. SecureField now uses textFieldWithString:, so it is one line, like TextField. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
…ved helper (PerryTS#11661) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
…he smoke test (PerryTS#11661) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
…rrySecureTextField (PerryTS#11661) Every TextField, SecureField, Text and AttributedText widget uses these classes, so their names do not need to say that the cell insets text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
…#11661) Every layer background, border and shadow colour panicked in a debug build. native_dynamic_colors failed on that panic, and now passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
A padded TextField or SecureField with a bezel or a border moved its text by the padding when editing started. Its background also stopped at the padding, where a web input's background fills it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
…nput does (PerryTS#11661) Without single-line mode, a pasted multi-line value kept its newlines and the field grew taller. The rules match Chrome's for an input element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
…ryTS#11661) padding.rs keeps the inset cells. PerryTextField moves to textfield.rs, PerrySecureTextField to securefield.rs, and the label factory to text.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
Text and AttributedText were built as PerryTextField, so they took the
input's one-line rules. Text("a\nb") showed "ab". Labels now use
PerryLabel, which takes only the inset cell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
…ments (PerryTS#11661) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
58ee08c to
b5ae184
Compare
PerryTS#11661) js_closure_alloc takes a JsFunctionInfo, and a body takes `this`. The test passed a bare function pointer, so a call read a garbage arity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2cPfS5Hty6uwVHHayNJqQ
…ryTS#11661) strip_line_breaks and replace_line_breaks_with_spaces take and return an NSString, so one name covers each operation. The doc comment on is_line_break_command says that Return still submits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2cPfS5Hty6uwVHHayNJqQ
…#11665) The CGColor panic hit only a debug build of perry-ui-macos, which no release ships. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2cPfS5Hty6uwVHHayNJqQ
…S#11661) The long value was a Jira query from an app, which has no place in perry's tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2cPfS5Hty6uwVHHayNJqQ
…11661) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2cPfS5Hty6uwVHHayNJqQ
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The unsafe Objective-C subclassing and AppKit rendering behavior require final macOS-based human validation.
Review effort: Balanced
Findings: None
What changed in this PR
Fixes macOS text-field baseline shifts while preserving AppKit factory behavior and CSS-like padding.
Changes:
- Creates inset cells through
cellClassand updates padding/background drawing. - Enforces single-line
TextField/SecureFieldnewline behavior. - Adds typed
CGColorhandling and macOS regression tests.
| File | Description |
|---|---|
test-parity/known_failures.json |
Allow-lists the macOS fixture on Linux. |
test-files/test_issue_11661_textfield_baseline.ts |
Adds a visual smoke fixture. |
crates/perry-ui-macos/tests/native_textfield_newlines.rs |
Tests single-line input behavior. |
crates/perry-ui-macos/tests/native_textfield_cell.rs |
Tests baseline, padding, sizing, and backgrounds. |
crates/perry-ui-macos/tests/native_text_color.rs |
Updates label-color regression context. |
crates/perry-ui-macos/tests/native_dynamic_colors.rs |
Uses typed CGColor pointers. |
crates/perry-ui-macos/src/widgets/textfield.rs |
Adds factory-built inset cells and newline filtering. |
crates/perry-ui-macos/src/widgets/text.rs |
Builds labels through PerryLabel. |
crates/perry-ui-macos/src/widgets/securefield.rs |
Applies equivalent secure-field behavior. |
crates/perry-ui-macos/src/widgets/padding.rs |
Insets drawing and editing frames. |
crates/perry-ui-macos/src/widgets/attributed_text.rs |
Reuses the factory-built label path. |
crates/perry-ui-macos/src/srgb.rs |
Defines a correctly encoded CGColor pointer. |
crates/perry-ui-macos/Cargo.toml |
Registers the new native tests. |
changelog.d/11665-macos-textfield-baseline.md |
Documents the fixes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgNvfPkqQmm5JRZUuEXv5y
The CGColor panic hit only a debug build of perry-ui-macos, which no release ships. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2cPfS5Hty6uwVHHayNJqQ
These are in scope. Dropping
The functions without docstrings are AppKit overrides ( |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline crates/perry-ui-macos/src/widgets/textfield.rs --items all
ast-grep outline crates/perry-ui-macos/src/widgets/padding.rs --items all
ast-grep outline crates/perry-ui-macos/src/widgets/securefield.rs --items all
ast-grep outline crates/perry-ui-macos/src/srgb.rs --items all
sed -n '1,270p' crates/perry-ui-macos/src/widgets/textfield.rs
sed -n '560,615p' crates/perry-ui-macos/src/widgets/textfield.rs
sed -n '1,230p' crates/perry-ui-macos/src/widgets/padding.rs
sed -n '1,135p' crates/perry-ui-macos/src/widgets/securefield.rs
sed -n '1,95p' crates/perry-ui-macos/src/srgb.rs
rg -n -C6 'test_issue_11661_textfield_baseline|test_issue_10155_textfield_singleline' test-parity/known_failures.json
ast-grep outline crates/perry-ui-macos/tests/native_textfield_newlines.rs --items all
sed -n '1,240p' crates/perry-ui-macos/tests/native_textfield_newlines.rsLength of output: 47903
✏️ Learnings added
You are interacting with an AI system. |
|
@coderabbitai run pre-merge checks |
✅ Actions performedRunning Pre-merge checks. Results will be posted in the summary comment once complete. |
Fixes #11661
Problem
On macOS, a
TextFieldwith a custom font draws its idle text at a different height from its editing text, so the text moves on focus. The cause is the waysetPaddingwas added to text fields and labels: Perry replaced the cell AppKit builds with a new inset cell. The new cell lost the factory setup, and the fixes that restored it led to #10155, #10856 and this issue.Solution
Let AppKit build the inset cell, and make
setPaddingon a text field work as CSS padding does on the web: the text moves in by the padding, and the background fills the padding.Screenshot
The first field is focused, so the field editor draws it. The other rows are idle.
Changes
Build the inset cell through
cellClass—textfield.rsPerryTextField(textfield.rs) andPerrySecureTextField(securefield.rs) return the inset cells fromcellClass.text_fieldandsecure_text_fieldcalltextFieldWithString:on those classes.TextandAttributedTextusePerryLabelintext.rs. It takes only the inset cell, so a label keeps the line breaks in its value.install_text_field_cell,install_secure_text_field_cellandinstall_label_cellare gone.initTextCell:, because AppKit now allocates it.perry/crates/perry-ui-macos/src/widgets/textfield.rs
Lines 205 to 214 in a743dd4
View in diff →
Pad the frames AppKit hands the cell —
padding.rsThe cell pads
drawInteriorWithFrame:inView:andeditWithFrame:/selectWithFrame:, and no longer overridesdrawingRectForBounds:. For a bezeled or bordered cell, AppKit's editing path runs the frame throughdrawingRectForBounds:. With the padding there, the editing text moved by the padding a second time.perry/crates/perry-ui-macos/src/widgets/padding.rs
Lines 58 to 66 in a743dd4
View in diff →
Paint the text field background on its layer —
textfield.rstextfieldSetBackgroundColoruses the layer path ofwidgetSetBackgroundColorand turns the cell background off. The cell background filled only the area inside the padding.perry/crates/perry-ui-macos/src/widgets/textfield.rs
Lines 588 to 597 in a743dd4
View in diff →
Pass layer colours as a typed
CGColorpointer —srgb.rsA
c_voidpointer failed objc2's message check in a debug build, so every layer background, border and shadow colour panicked there.perry/crates/perry-ui-macos/src/srgb.rs
Lines 10 to 23 in a743dd4
View in diff →
Drop the manual setup in the widget builders —
textfield.rs,securefield.rs,text.rs,attributed_text.rsThe factory already makes the fields editable, bezeled and one line.
TextFieldno longer uses single-line mode, which placed the idle baseline on the system font for the control size.SecureFieldusestextFieldWithString:in place ofinitWithFrame:, so a long value stays on one line and scrolls.Keep the value to one line, as a web
<input>does —textfield.rsPerryTextFieldandPerrySecureTextFieldapply Chrome's rules for an input element, without single-line mode.setStringValue:drops CR and LF, asinput.value = …does.perry/crates/perry-ui-macos/src/widgets/textfield.rs
Lines 636 to 656 in a743dd4
View in diff →
Verification
cargo test -p perry-ui-macospasses,native_dynamic_colorsincluded.native_textfield_cellchecksTextFieldandSecureField, borderless, bezeled and bordered: a stable baseline on focus across five fonts, a long value on one line, padding by the exact amount, and a layer background. It fails onmain.test-files/test_issue_11661_textfield_baseline.tswas compiled with a release build on macOS 26.6. The screenshots above come from that app, onmainand on this branch.native_textfield_newlinesputs line breaks intoTextFieldandSecureFieldfrom code, typing, paste, drop and the newline commands, and checks the value, the height, and that Return submits.textfieldSetBackgroundColorwas captured on screen idle and while editing. The text sat 3.5pt from the top and 1.5pt from the left in both.Summary by CodeRabbit