Skip to content

Fix overlapping buttons in the wide permission dialog - #10

Merged
jason810496 merged 7 commits into
mainfrom
fix/dialog-vertical-buttons
Jun 22, 2026
Merged

jason810496 merged 7 commits into
mainfrom
fix/dialog-vertical-buttons

Conversation

@jason810496

Copy link
Copy Markdown
Owner

Fix overlapping buttons in the wide permission dialog

Why

NSAlert lays its buttons out in a single horizontal row, which overlaps once the permission dialog is widened to fit a long command and the larger dialog font.

How

  • Add a stackAlertButtonsVertically pass that runs after the existing width/font layout, just before runModal, and takes over button placement from NSAlert.
  • Reparent the buttons onto the alert's content view so they can be positioned in absolute coordinates, dropping the now-empty native button container.
  • Grow the window downward by exactly the extra height the column needs, pinning the existing content to the top so the message area is not stretched.
  • Lay the buttons out as a single vertical column, centered horizontally on the dialog's mid-line and centered vertically in the space below the content.
  • Give each button its own fitted width so the centering survives the relayout NSAlert performs while presenting the dialog.
  • Keep the existing reverse add-order, so the visible top-to-bottom order still matches the button list and the return-code mapping is unchanged.

What

  • src/agent_hooks/assets/dialog.applescript:
    • New stackAlertButtonsVertically handler implementing the centered vertical column.
    • Call it as the final layout step in on run.
    • Update the reverse add-order comment to reflect the top-to-bottom column ordering.

Verification

  • Rendered the real dialog modally and confirmed the column renders without overlap and clicks map back to the correct label (Allow Once, Always Allow, Deny).
  • Checked geometry across normal-width, wide (long command + suggestion rules), and 2-button / larger-font dialogs: buttons stay centered (each button's center on the dialog mid-line) and never overlap.
  • osacompile parses the script; all 25 tests/test_transport.py tests pass.

NSAlert lays its buttons out in a horizontal row, which overlaps once the
dialog is widened to fit a long command and the larger dialog font. Add a
stackAlertButtonsVertically pass that reparents the buttons onto the content
view, grows the window to make room, and lays them out as a single vertical
column centered horizontally and vertically. Each button keeps its own fitted
width so the layout survives the relayout NSAlert runs while presenting, and
the existing reverse add-order keeps the top-to-bottom order matching the
button list and the return-code mapping intact.
@jason810496

Copy link
Copy Markdown
Owner Author

/gemini review

@jason810496

Copy link
Copy Markdown
Owner Author

@codex review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new AppleScript handler stackAlertButtonsVertically to lay out dialog buttons in a single vertical column, preventing overlap when the dialog is widened. The review feedback highlights three key areas for improvement: robustly extracting the content view's height, defensively checking if buttonContainer is missing value before removing it from its superview to prevent runtime errors, and ignoring full-span background or visual effect views when calculating the content floor to ensure correct vertical centering on all macOS versions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +52 to +54
set theWindow to alert's |window|()
set contentView to theWindow's contentView()
set contentWidth to (item 1 of item 2 of (contentView's frame())) as real

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To support robustly calculating the content floor and ignoring full-height background or visual effect views, we should extract the content view's height as well.

    set theWindow to alert's |window|()
    set contentView to theWindow's contentView()
    set contentViewFrame to contentView's frame()
    set contentWidth to (item 1 of item 2 of contentViewFrame) as real
    set contentHeight to (item 2 of item 2 of contentViewFrame) as real

Comment on lines +81 to +88
set buttonContainer to (item 1 of buttonViews)'s superview()
if buttonContainer is not contentView then
repeat with theButton in buttonViews
theButton's removeFromSuperview()
contentView's addSubview:theButton
end repeat
buttonContainer's removeFromSuperview()
end if

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If buttonContainer is missing value, calling removeFromSuperview() on it will result in a runtime error. We should defensively check that buttonContainer is not missing value before attempting to remove it.

    set buttonContainer to (item 1 of buttonViews)'s superview()
    if buttonContainer is not missing value and buttonContainer is not contentView then
        repeat with theButton in buttonViews
            theButton's removeFromSuperview()
            contentView's addSubview:theButton
        end repeat
        buttonContainer's removeFromSuperview()
    end if

Comment on lines +104 to +110
set contentFloor to missing value
repeat with childView in (contentView's subviews())
if not ((childView's isKindOfClass:(current application's NSButton)) as boolean) then
set childBottom to (item 2 of item 1 of (childView's frame())) as real
if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
end if
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In some macOS versions, the content view contains full-span background or visual effect views that are not buttons. If we don't ignore them, contentFloor will be incorrectly set to 0, which breaks the vertical centering of the button column. We should ignore any subviews that span the full height of the content view.

    set contentFloor to missing value
    repeat with childView in (contentView's subviews())
        if not ((childView's isKindOfClass:(current application's NSButton)) as boolean) then
            set childFrame to childView's frame()
            set childHeight to (item 2 of item 2 of childFrame) as real
            if childHeight is greater than 1 and childHeight is less than (contentHeight - 5) then
                set childBottom to (item 2 of item 1 of childFrame) as real
                if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
            end if
        end if
    end repeat

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new AppleScript handler, stackAlertButtonsVertically, to lay out NSAlert buttons in a single vertical column, preventing overlapping issues when the dialog is widened. The review feedback highlights two critical issues with this implementation: first, modifying the autoresizing mask of all subviews indiscriminately prevents the background container view from resizing, leading to visual defects; second, on modern macOS versions, the direct subviews of the content view are wrapped in an NSVisualEffectView, which breaks the coordinate calculations for centering the buttons. Code suggestions are provided to resolve both issues.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +92 to +99
if extraHeight is greater than 0 then
repeat with childView in (contentView's subviews())
childView's setAutoresizingMask:8 -- NSViewMinYMargin: stay anchored to the top
end repeat
set windowFrame to theWindow's frame()
set grownFrame to current application's NSMakeRect((item 1 of item 1 of windowFrame), ((item 2 of item 1 of windowFrame) - extraHeight), (item 1 of item 2 of windowFrame), ((item 2 of item 2 of windowFrame) + extraHeight))
theWindow's setFrame:grownFrame display:false
end if

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Overwriting the autoresizing mask of the background container view (such as NSVisualEffectView) to NSViewMinYMargin (8) prevents it from resizing vertically when the window grows. This causes a visual defect where the newly added bottom area of the dialog lacks the vibrant background effect.

We should only apply the NSViewMinYMargin mask to actual content views whose height is less than the content view's height, allowing the background view to resize and fill the window correctly.

    if extraHeight is greater than 0 then
        set contentHeight to (item 2 of item 2 of (contentView's frame())) as real
        repeat with childView in (contentView's subviews())
            set childHeight to (item 2 of item 2 of (childView's frame())) as real
            if childHeight < contentHeight then
                childView's setAutoresizingMask:8 -- NSViewMinYMargin: stay anchored to the top
            end if
        end repeat
        set windowFrame to theWindow's frame()
        set grownFrame to current application's NSMakeRect((item 1 of item 1 of windowFrame), ((item 2 of item 1 of windowFrame) - extraHeight), (item 1 of item 2 of windowFrame), ((item 2 of item 2 of windowFrame) + extraHeight))
        theWindow's setFrame:grownFrame display:false
    end if

Comment on lines +104 to +110
set contentFloor to missing value
repeat with childView in (contentView's subviews())
if not ((childView's isKindOfClass:(current application's NSButton)) as boolean) then
set childBottom to (item 2 of item 1 of (childView's frame())) as real
if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
end if
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

On modern macOS versions, NSAlert wraps its content views (text fields, icon, etc.) inside an NSVisualEffectView container. Because the current loop only inspects the direct subviews of contentView and does not traverse recursively, it will match the NSVisualEffectView (which is not an NSButton and has a bottom coordinate of 0). This sets contentFloor to 0, completely bypassing the vertical centering logic and pinning the buttons to the bottom.

To fix this, we should inspect the subviews of the NSVisualEffectView container if it exists, and convert the coordinates of the child views to the contentView coordinate space to find the true lowest content view.

    set targetContainer to contentView
    repeat with childView in (contentView's subviews())
        if ((childView's isKindOfClass:(current application's NSVisualEffectView)) as boolean) then
            set targetContainer to childView
            exit repeat
        end if
    end repeat

    set contentFloor to missing value
    repeat with childView in (targetContainer's subviews())
        if not ((childView's isKindOfClass:(current application's NSButton)) as boolean) then
            set childRect to targetContainer's convertRect:(childView's frame()) toView:contentView
            set childBottom to (item 2 of item 1 of childRect) as real
            if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
        end if
    end repeat

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: c86a87069b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

- Guard buttonContainer against missing value before removeFromSuperview so a
  button with no superview can never trigger a runtime error.
- Capture the content view height and skip any near-full-height background or
  visual-effect view when computing the content floor, so a full-span view
  (present on some macOS versions) cannot drag the floor to 0 and collapse the
  vertical centering. Hairline dividers are kept as a valid floor.
@jason810496

Copy link
Copy Markdown
Owner Author

Round 1 review addressed in 96d55c2:

Gemini (all 3, valid):

  • Guard buttonContainer against missing value (line 88): added buttonContainer is not missing value before removeFromSuperview, so a button without a superview can't cause a runtime error.
  • Capture content view height (line 54): now read the content view frame once and keep both width and height.
  • Ignore full-height background/visual-effect views (line 110): the content-floor scan now skips any non-button subview whose height is within 5pt of the content view height, so a full-span background view (present on some macOS versions) can't drag the floor to 0 and collapse the centering. I kept the hairline divider as a valid floor (it marks the content/button boundary), so only genuinely full-height views are excluded.

Codex: no findings (reacted on the trigger).

Verified: osacompile parses the script, the centered column is preserved (buttons centered on the dialog mid-line, no overlap), the layout survives runModal, and all 25 transport tests pass.

@jason810496

Copy link
Copy Markdown
Owner Author

/gemini review

@jason810496

Copy link
Copy Markdown
Owner Author

@codex review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new AppleScript handler, stackAlertButtonsVertically, to lay out dialog buttons in a single vertical column to prevent overlapping issues when the dialog is widened. A critical runtime issue was identified in the new handler where alert's buttons() returns an NSArray, which will cause a runtime error when accessed with AppleScript list operations unless explicitly coerced to a native AppleScript list.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

-- packs buttons into a horizontal row by default, which overlaps once the dialog
-- is widened to fit a long message and the larger dialog font. Stacking top to
-- bottom removes that failure mode regardless of dialog width or button count.
set buttonViews to alert's buttons()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

In AppleScript-ObjC, alert's buttons() returns an NSArray. Attempting to use AppleScript list operations (such as item 1 of buttonViews or item buttonIndex of buttonViews) directly on an NSArray without explicit coercion will result in a runtime error (e.g., Can’t get item 1 of ...).

Coerce the returned NSArray to a native AppleScript list to ensure that list indexing and counting work reliably.

    set buttonViews to (alert's buttons()) as list

- Exclude near-full-height views from the top-pinning autoresize pass so a
  full-height background/visual-effect view keeps resizing and fills the grown
  window instead of leaving a blank strip.
- When NSAlert nests the icon and text inside an NSVisualEffectView, descend
  into that container and measure the content floor in content-view
  coordinates, so the wrapper itself no longer collapses the floor to 0 and the
  column stays vertically centered.
@jason810496

Copy link
Copy Markdown
Owner Author

Round 2 review addressed in b700d69:

Gemini (both HIGH, valid — defensive cross-version hardening):

  • Autoresize mask clobbers the background view (line 101): the top-pinning pass now skips any near-full-height subview, so a full-height NSVisualEffectView keeps its own mask and resizes to fill the grown window instead of leaving a blank/non-vibrant strip at the bottom.
  • NSVisualEffectView wrapper breaks the content floor (line 117): when NSAlert nests the icon/text inside an NSVisualEffectView, the scan now descends into that container and measures each child in content-view coordinates (via convertRect:toView:), so the wrapper no longer sets contentFloor to 0 and pins the buttons to the bottom.

Note: on the target macOS (26) I verified by direct measurement that NSAlert does not wrap content in an NSVisualEffectView here, and the round-1 full-height filter already degraded this case gracefully (bottom-anchored, never overlapping). These changes are guarded no-ops on that path and add robustness for other macOS versions.

Codex: no major issues ("Chef's kiss").

Verified: osacompile parses, the centered column is unchanged on the tested macOS (buttons centered on the mid-line, no overlap, normal + wide), layout survives runModal, and all 25 transport tests pass.

@jason810496

Copy link
Copy Markdown
Owner Author

/gemini review

@jason810496

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 96d55c2e3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new AppleScript handler, stackAlertButtonsVertically, to lay out NSAlert buttons in a single vertical column, preventing layout overlap issues when the dialog is widened. The review feedback identifies two important improvement opportunities: first, preserving existing autoresizing mask flags rather than overwriting them entirely with NSViewMinYMargin (8); and second, using a more precise check (containsObject:) to exclude only the alert's action buttons when calculating the content floor, rather than ignoring all NSButton instances.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +97 to +101
repeat with childView in (contentView's subviews())
if ((item 2 of item 2 of (childView's frame())) as real) < (contentHeight - 5) then
childView's setAutoresizingMask:8 -- NSViewMinYMargin: stay anchored to the top
end if
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Overwriting the autoresizing mask entirely to 8 (NSViewMinYMargin) discards other critical resizing behaviors (such as NSViewWidthSizable / 2), which can cause layout issues or text truncation when the window is presented or resized. Instead, preserve the existing mask flags and only add the NSViewMinYMargin (8) flag if it is not already set.

        repeat with childView in (contentView's subviews())
            if ((item 2 of item 2 of (childView's frame())) as real) < (contentHeight - 5) then
                set currentMask to childView's autoresizingMask() as integer
                if (currentMask div 8) mod 2 is 0 then
                    childView's setAutoresizingMask:(currentMask + 8)
                end if
            end if
        end repeat

Comment on lines +122 to +130
repeat with childView in (floorContainer's subviews())
if not ((childView's isKindOfClass:(current application's NSButton)) as boolean) then
set childRect to floorContainer's convertRect:(childView's frame()) toView:contentView
if ((item 2 of item 2 of childRect) as real) < (contentHeight - 5) then
set childBottom to (item 2 of item 1 of childRect) as real
if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
end if
end if
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Skipping all NSButton views can lead to layout issues if the dialog contains other buttons (such as a help button or custom checkboxes in an accessory view), as they will be ignored when calculating the content floor. Checking if the view is one of the alert's actual buttons using containsObject: is much safer and more precise.

    repeat with childView in (floorContainer's subviews())
        if not (buttonViews's containsObject:childView) then
            set childRect to floorContainer's convertRect:(childView's frame()) toView:contentView
            if ((item 2 of item 2 of childRect) as real) < (contentHeight - 5) then
                set childBottom to (item 2 of item 1 of childRect) as real
                if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
            end if
        end if
    end repeat

alert's buttons() returns an NSArray; coerce it to an AppleScript list so the
count and item-index operations in stackAlertButtonsVertically are reliable
across AppleScript-ObjC versions.
@jason810496

Copy link
Copy Markdown
Owner Author

Round 3 review addressed in 18f8ca0:

Gemini (CRITICAL, line 48 — adopted): coerced alert's buttons() to a native AppleScript list ((alert's buttons()) as list) so the count/index operations are reliable across AppleScript-ObjC versions.

  • Note: the two HIGH threads still showing (NSViewMinYMargin mask + NSVisualEffectView traversal) are the round-2 comments; they were already addressed in b700d69 and Gemini just doesn't auto-resolve the threads.
  • On the runtime-error point: the direct item N of buttons() form actually ran fine across all my test invocations on macOS 26 (AppleScript-ObjC bridges NSArray indexing), but as list is the idiomatic, zero-risk form so I took it.

Codex: no major issues on the prior commits ("Chef's kiss" / "Breezy!").

Verified: osacompile parses, click-to-label mapping still correct (button 1 -> Always Allow, button 3 -> Deny), and all 25 transport tests pass.

@jason810496

Copy link
Copy Markdown
Owner Author

/gemini review

@jason810496

Copy link
Copy Markdown
Owner Author

@codex review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new helper function stackAlertButtonsVertically in dialog.applescript to arrange alert buttons in a vertical column, preventing overlap in wider dialogs. The review feedback highlights a critical AppleScript-ObjC issue where loop variables in repeat with ... in ... blocks are treated as references rather than direct values. Failing to explicitly dereference these variables using contents of can cause runtime coercion errors or silent failures, particularly when passing them as arguments to Objective-C methods like addSubview:. Applying the suggested dereferencing across all five loops will ensure robust and crash-free execution across different macOS versions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +68 to +77
repeat with theButton in buttonViews
theButton's sizeToFit()
set bandRect to (theButton's superview())'s convertRect:(theButton's frame()) toView:contentView
set rectBottom to (item 2 of item 1 of bandRect) as real
set rectHeight to (item 2 of item 2 of bandRect) as real
set rectTop to rectBottom + rectHeight
if bandBottom is missing value or rectBottom < bandBottom then set bandBottom to rectBottom
if bandTop is missing value or rectTop > bandTop then set bandTop to rectTop
if rectHeight > buttonHeight then set buttonHeight to rectHeight
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In AppleScript-ObjC, when using a repeat with x in list loop, the loop variable x is an AppleScript reference (e.g., item 1 of list) rather than the direct value. While some target method calls can be resolved, passing these references directly as arguments or using them in complex expressions can sometimes lead to runtime coercion errors or crashes depending on the macOS version. It is safer and more robust to explicitly dereference the loop variable using contents of.

    repeat with theButtonRef in buttonViews
        set theButton to contents of theButtonRef
        theButton's sizeToFit()
        set bandRect to (theButton's superview())'s convertRect:(theButton's frame()) toView:contentView
        set rectBottom to (item 2 of item 1 of bandRect) as real
        set rectHeight to (item 2 of item 2 of bandRect) as real
        set rectTop to rectBottom + rectHeight
        if bandBottom is missing value or rectBottom < bandBottom then set bandBottom to rectBottom
        if bandTop is missing value or rectTop > bandTop then set bandTop to rectTop
        if rectHeight > buttonHeight then set buttonHeight to rectHeight
    end repeat

Comment on lines +87 to +90
repeat with theButton in buttonViews
theButton's removeFromSuperview()
contentView's addSubview:theButton
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This is particularly critical here because theButton is passed directly as an argument to addSubview:. In AppleScript-ObjC, passing a loop reference variable directly as an argument to an Objective-C method can cause a runtime crash or fail silently because the bridge receives the reference wrapper instead of the actual NSView object. Explicitly dereferencing it using contents of ensures the actual NSButton object is passed.

        repeat with theButtonRef in buttonViews
            set theButton to contents of theButtonRef
            theButton's removeFromSuperview()
            contentView's addSubview:theButton
        end repeat

Comment on lines +99 to +103
repeat with childView in (contentView's subviews())
if ((item 2 of item 2 of (childView's frame())) as real) < (contentHeight - 5) then
childView's setAutoresizingMask:8 -- NSViewMinYMargin: stay anchored to the top
end if
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Explicitly dereference the loop variable childView using contents of to ensure robust behavior across different macOS versions.

        repeat with childViewRef in (contentView's subviews())
            set childView to contents of childViewRef
            if ((item 2 of item 2 of (childView's frame())) as real) < (contentHeight - 5) then
                childView's setAutoresizingMask:8 -- NSViewMinYMargin: stay anchored to the top
            end if
        end repeat

Comment on lines +117 to +122
repeat with childView in (contentView's subviews())
if ((childView's isKindOfClass:(current application's NSVisualEffectView)) as boolean) then
set floorContainer to childView
exit repeat
end if
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Explicitly dereference the loop variable childView using contents of to ensure robust behavior across different macOS versions.

    repeat with childViewRef in (contentView's subviews())
        set childView to contents of childViewRef
        if ((childView's isKindOfClass:(current application's NSVisualEffectView)) as boolean) then
            set floorContainer to childView
            exit repeat
        end if
    end repeat

Comment on lines +124 to +132
repeat with childView in (floorContainer's subviews())
if not ((childView's isKindOfClass:(current application's NSButton)) as boolean) then
set childRect to floorContainer's convertRect:(childView's frame()) toView:contentView
if ((item 2 of item 2 of childRect) as real) < (contentHeight - 5) then
set childBottom to (item 2 of item 1 of childRect) as real
if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
end if
end if
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Explicitly dereference the loop variable childView using contents of to ensure robust behavior across different macOS versions.

    repeat with childViewRef in (floorContainer's subviews())
        set childView to contents of childViewRef
        if not ((childView's isKindOfClass:(current application's NSButton)) as boolean) then
            set childRect to floorContainer's convertRect:(childView's frame()) toView:contentView
            if ((item 2 of item 2 of childRect) as real) < (contentHeight - 5) then
                set childBottom to (item 2 of item 1 of childRect) as real
                if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
            end if
        end if
    end repeat

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 18f8ca0254

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

- When anchoring content to the top, add the NSViewMinYMargin flag to each
  view's existing mask instead of overwriting it, so flags such as
  NSViewWidthSizable are retained.
- Use the alert's buttons() NSArray with containsObject: to exclude only the
  alert's own action buttons from the content-floor scan, so any other buttons
  (e.g. a help button or accessory checkboxes) still count as content.
@jason810496

Copy link
Copy Markdown
Owner Author

Round 4 review addressed in 57debdd:

Gemini (both valid):

  • Don't clobber the autoresize mask (line 103, HIGH): the top-anchoring pass now reads each view's current mask and only adds the NSViewMinYMargin (8) bit when it isn't already set, preserving other flags like NSViewWidthSizable.
  • Target only the alert's action buttons (line 132, MEDIUM): the content-floor scan now excludes views via buttonArray's containsObject: (the alert's own buttons) instead of skipping every NSButton, so a help button or accessory checkbox would still count as content. Kept the round-3 as list coercion for indexing by retaining the raw NSArray (buttonArray) alongside it for the membership test.

Codex: no major issues ("Breezy!").

Verified: osacompile parses, the centered column is unchanged on the tested macOS (buttons at the mid-line, no overlap), and all 25 transport tests pass.

@jason810496

Copy link
Copy Markdown
Owner Author

/gemini review

@jason810496

Copy link
Copy Markdown
Owner Author

@codex review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new helper function stackAlertButtonsVertically in dialog.applescript to arrange alert buttons in a single vertical column, preventing overlapping issues when the dialog is widened. The review feedback highlights a critical issue where comparing Objective-C objects using AppleScript's native is not operator is unreliable and could inadvertently remove the main content view; using isEqual: is recommended instead.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

-- Reparent the buttons directly onto the content view so they can be positioned
-- in absolute coordinates, then drop the now-empty native button container.
set buttonContainer to (item 1 of buttonViews)'s superview()
if buttonContainer is not missing value and buttonContainer is not contentView then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In AppleScriptObjC, comparing two Objective-C objects (such as NSView instances) using the native AppleScript is or is not operators is unreliable. AppleScriptObjC wraps Objective-C objects in AppleScript wrappers, and two different wrapper instances pointing to the same underlying Objective-C object may not be considered equal by AppleScript's identity operators.

If buttonContainer and contentView refer to the same underlying view, buttonContainer is not contentView can evaluate to true. This would lead to calling buttonContainer's removeFromSuperview(), which would remove the entire contentView from the window, resulting in a blank dialog.

To safely compare Objective-C objects for equality, use the isEqual: method.

    if buttonContainer is not missing value and not (buttonContainer's isEqual:contentView) then

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 57debdd6d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

- Compare the button container against the content view with isEqual: instead
  of the AppleScript identity operator, which is unreliable for ObjC object
  wrappers and could otherwise remove the content view and blank the dialog.
- Dereference each repeat-loop variable with 'contents of' before using it, so
  the underlying NSView is passed to ObjC methods (notably addSubview:) rather
  than an AppleScript reference wrapper.
@jason810496

Copy link
Copy Markdown
Owner Author

Round 5 review addressed in b9e62b4:

Gemini (all valid — AppleScript-ObjC correctness):

  • isEqual: for object comparison (line 87, HIGH): the container/content-view check now uses not (buttonContainer's isEqual:contentView) instead of is not, so an unreliable wrapper-identity comparison can't cause contentView to be removed and blank the dialog.
  • Dereference loop variables (lines 78/91/107/126/136): each repeat with x in <list> now does set x to contents of xRef first, so the real NSView is passed to ObjC methods (notably addSubview:) rather than an AppleScript reference wrapper.

Codex: no major issues on 57debdd.

Verified end-to-end, including the stack-view reparent path (exercised only by wide dialogs, which is where addSubview: and the container comparison matter): osacompile parses; wide + medium dialogs stay centered with no overlap; click-to-label mapping is correct (button 1 -> Always Allow, button 3 -> Deny); all 25 transport tests pass.

@jason810496

Copy link
Copy Markdown
Owner Author

/gemini review

@jason810496

Copy link
Copy Markdown
Owner Author

@codex review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a vertical button layout mechanism (stackAlertButtonsVertically) in the AppleScript dialog implementation to prevent button overlap when dialogs are widened. The reviewer suggests a more robust and safer calculation of the content floor to avoid potential overlaps with other subviews and prevent runtime exceptions by checking the class name as a string instead of using isKindOfClass:.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +123 to +141
set floorContainer to contentView
repeat with childViewRef in (contentView's subviews())
set childView to contents of childViewRef
if ((childView's isKindOfClass:(current application's NSVisualEffectView)) as boolean) then
set floorContainer to childView
exit repeat
end if
end repeat
set contentFloor to missing value
repeat with childViewRef in (floorContainer's subviews())
set childView to contents of childViewRef
if not ((buttonArray's containsObject:childView) as boolean) then
set childRect to floorContainer's convertRect:(childView's frame()) toView:contentView
if ((item 2 of item 2 of childRect) as real) < (contentHeight - 5) then
set childBottom to (item 2 of item 1 of childRect) as real
if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
end if
end if
end repeat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Improvement: Robust and Safe Content Floor Calculation

Currently, the script descends strictly into the NSVisualEffectView if found, which means any other direct subviews of contentView (such as the accessory view/spacer view, or potential future custom accessory views) are completely ignored when calculating the content floor. This can lead to buttons overlapping with those views.

Additionally, using isKindOfClass: with current application's NSVisualEffectView can potentially fail or throw a runtime exception if the class cannot be resolved dynamically in the AppleScript-ObjC context. Checking the class name as a string via className() is much safer.

We can refactor this to check both direct subviews of contentView and nested subviews of NSVisualEffectView in a single, robust pass.

    set contentFloor to missing value
    repeat with childViewRef in (contentView's subviews())
        set childView to contents of childViewRef
        if not ((buttonArray's containsObject:childView) as boolean) then
            set childRect to childView's frame()
            if ((item 2 of item 2 of childRect) as real) < (contentHeight - 5) then
                set childBottom to (item 2 of item 1 of childRect) as real
                if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
            end if
            if (childView's className() as text) is "NSVisualEffectView" then
                repeat with nestedViewRef in (childView's subviews())
                    set nestedView to contents of nestedViewRef
                    set childRect to childView's convertRect:(nestedView's frame()) toView:contentView
                    if ((item 2 of item 2 of childRect) as real) < (contentHeight - 5) then
                        set childBottom to (item 2 of item 1 of childRect) as real
                        if contentFloor is missing value or childBottom < contentFloor then set contentFloor to childBottom
                    end if
                end repeat
            end if
        end if
    end repeat

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: b9e62b43e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Gather the content view's own subviews together with any NSVisualEffectView
container's subviews into a single candidate list, measuring each via its own
superview, so a direct accessory view is no longer ignored when the icon/text
are nested inside an effect view. Keeps isKindOfClass: for the container check
(subclass-aware, unlike a className string match).
@jason810496

Copy link
Copy Markdown
Owner Author

Round 6 review addressed in af6b2aa:

Gemini (line 141, MEDIUM):

  • Adopted — scan both levels: the content-floor calculation now gathers the content view's own subviews and any NSVisualEffectView container's subviews into one candidate list, measuring each via its own superview. A direct accessory/spacer view is no longer skipped when the icon/text happen to be nested inside an effect view.
  • Not adopted — className() string check instead of isKindOfClass:: isKindOfClass: is subclass-aware, whereas matching className() as the literal string "NSVisualEffectView" would miss the private _NS… subclasses NSAlert actually uses, making it less robust, not more. NSVisualEffectView has shipped in AppKit since 10.10, so current application's NSVisualEffectView always resolves on any macOS that runs this script; the "class cannot be resolved" risk doesn't apply here.

Codex: no major issues.

Verified: osacompile parses; medium + wide dialogs stay centered (buttons on the mid-line, no overlap); all 25 transport tests pass.

@jason810496
jason810496 merged commit 6988825 into main Jun 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant