Skip to content

hs.ax: bridge attributeValue/setAttributeValue for JS-friendly AX values#38

Open
alkene5 wants to merge 1 commit intocmsj:mainfrom
alkene5:codex/ax-attribute-bridge
Open

hs.ax: bridge attributeValue/setAttributeValue for JS-friendly AX values#38
alkene5 wants to merge 1 commit intocmsj:mainfrom
alkene5:codex/ax-attribute-bridge

Conversation

@alkene5
Copy link
Copy Markdown
Contributor

@alkene5 alkene5 commented Mar 26, 2026

Summary

This PR improves hs.ax attribute interoperability between AXSwift and JavaScript by making attributeValue and setAttributeValue symmetric and JS-friendly.

What changed

  • Updated HSAXElement.attributeValue(_:) to bridge AX/native values into JS-usable Hammerspoon bridge objects.

    • UIElement -> HSAXElement
    • [UIElement] -> [HSAXElement]
    • CGPoint -> HSPoint
    • CGSize -> HSSize
    • CGRect -> HSRect
    • Recursively handles arrays and [String: Any] dictionaries
  • Updated HSAXElement.setAttributeValue(_:value:) with reverse bridging before writing attributes.

    • HSAXElement -> UIElement
    • [HSAXElement] -> [UIElement]
    • HSPoint -> CGPoint
    • HSSize -> CGSize
    • HSRect -> CGRect
    • Recursively handles arrays and [String: Any] dictionaries

Why

Previously, some AX attribute values returned by attributeValue were difficult to use directly in JS due to native AX/CoreGraphics types not being bridged consistently. Also, setAttributeValue did not perform the inverse conversion, so read/write behavior was asymmetric.

This change makes the API behavior more predictable and practical in JS scripts.

Validation

  • xcodebuild -project "Hammerspoon 2.xcodeproj" -scheme Development build CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO
  • npm run docs:test

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 26, 2026

Greptile Summary

This PR adds bridgeValue/unbridgeValue helpers inside attributeValue and setAttributeValue so that native AX/CoreGraphics types (UIElement, CGPoint, CGSize, CGRect, and arrays/dicts containing them) are automatically converted to and from their JS-friendly Hammerspoon counterparts (HSAXElement, HSPoint, HSSize, HSRect). The overall design is sound and the implementation is correct — the recursive helpers handle nested arrays and dictionaries properly, and the type-check ordering (more-specific types before [Any]) avoids accidental early matches.

Key concerns:

  • The existing value shortcut property (line 130) is not updated to bridge its return value. After this PR, element.value and element.attributeValue("AXValue") return different types for the same attribute, which is a meaningful inconsistency for JS authors.
  • A local binding if let element = value as? UIElement in bridgeValue shadows the class-level element property; renaming to uiElement would remove the ambiguity.
  • No new tests were added for the bridging/unbridging paths.

Confidence Score: 4/5

Safe to merge after addressing the value property inconsistency; the core bridging logic is correct.

The bridging implementation is well-structured and logically correct. The shadow-variable issue is cosmetic. The value property inconsistency is a real API surface problem that could confuse JS users, but it is a pre-existing property and not a regression introduced by this PR — it just becomes more visible now. Resolving it (or at least documenting the difference) before merging would make the PR fully deliver on its stated goal of a predictable, symmetric API.

Hammerspoon 2/Modules/hs.ax/HSAXElement.swift — specifically the unbridged value property around line 130.

Important Files Changed

Filename Overview
Hammerspoon 2/Modules/hs.ax/HSAXElement.swift Adds bridgeValue/unbridgeValue helpers to make attributeValue/setAttributeValue symmetric and JS-friendly; logic is sound but the value property is left unbridged, creating an inconsistency with attributeValue("AXValue"), and a local variable shadows the outer element property.

Comments Outside Diff (1)

  1. Hammerspoon 2/Modules/hs.ax/HSAXElement.swift, line 130-132 ([link](https://github.com/cmsj/hammerspoon2/blob/ea0f4964d9dd7f0e071bd873dc62eb88f90694b2/Hammerspoon 2/Modules/hs.ax/HSAXElement.swift#L130-L132))

    P1 value property is unbridged, inconsistent with attributeValue

    The value shortcut property returns the raw AX value directly from AXSwift without any bridging:

    @objc var value: Any? {
        return try? element.attribute(.value)  // raw — no bridgeValue()
    }

    After this PR, calling element.attributeValue("AXValue") returns a JS-friendly bridged object (e.g. HSAXElement, HSPoint), while element.value returns the unprocessed native type for the exact same attribute. A JS author using the convenience property would receive a different (and likely unusable) type compared to using the general API, which undermines the symmetry goal of this PR.

    Consider routing value through the same bridgeValue logic — or at minimum documenting that it returns a raw type — to keep the surface consistent.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: Hammerspoon 2/Modules/hs.ax/HSAXElement.swift
    Line: 130-132
    
    Comment:
    **`value` property is unbridged, inconsistent with `attributeValue`**
    
    The `value` shortcut property returns the raw AX value directly from AXSwift without any bridging:
    
    ```swift
    @objc var value: Any? {
        return try? element.attribute(.value)  // raw — no bridgeValue()
    }
    ```
    
    After this PR, calling `element.attributeValue("AXValue")` returns a JS-friendly bridged object (e.g. `HSAXElement`, `HSPoint`), while `element.value` returns the unprocessed native type for the exact same attribute. A JS author using the convenience property would receive a different (and likely unusable) type compared to using the general API, which undermines the symmetry goal of this PR.
    
    Consider routing `value` through the same `bridgeValue` logic — or at minimum documenting that it returns a raw type — to keep the surface consistent.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: Hammerspoon 2/Modules/hs.ax/HSAXElement.swift
Line: 130-132

Comment:
**`value` property is unbridged, inconsistent with `attributeValue`**

The `value` shortcut property returns the raw AX value directly from AXSwift without any bridging:

```swift
@objc var value: Any? {
    return try? element.attribute(.value)  // raw — no bridgeValue()
}
```

After this PR, calling `element.attributeValue("AXValue")` returns a JS-friendly bridged object (e.g. `HSAXElement`, `HSPoint`), while `element.value` returns the unprocessed native type for the exact same attribute. A JS author using the convenience property would receive a different (and likely unusable) type compared to using the general API, which undermines the symmetry goal of this PR.

Consider routing `value` through the same `bridgeValue` logic — or at minimum documenting that it returns a raw type — to keep the surface consistent.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: Hammerspoon 2/Modules/hs.ax/HSAXElement.swift
Line: 262-263

Comment:
**Shadow variable hides outer `element` property**

`if let element = value as? UIElement` introduces a local `element` that shadows the class-level `let element: UIElement` property. While this doesn't cause a runtime bug here, it makes the code harder to read and could introduce subtle errors if the body is ever extended to reference the instance property. A distinct name like `uiElement` would avoid the ambiguity.

```suggestion
            if let uiElement = value as? UIElement {
                return HSAXElement(element: uiElement)
            }
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "ax: bridge attribute values for JS-frien..." | Re-trigger Greptile

Comment on lines +262 to +263
if let element = value as? UIElement {
return HSAXElement(element: element)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Shadow variable hides outer element property

if let element = value as? UIElement introduces a local element that shadows the class-level let element: UIElement property. While this doesn't cause a runtime bug here, it makes the code harder to read and could introduce subtle errors if the body is ever extended to reference the instance property. A distinct name like uiElement would avoid the ambiguity.

Suggested change
if let element = value as? UIElement {
return HSAXElement(element: element)
if let uiElement = value as? UIElement {
return HSAXElement(element: uiElement)
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: Hammerspoon 2/Modules/hs.ax/HSAXElement.swift
Line: 262-263

Comment:
**Shadow variable hides outer `element` property**

`if let element = value as? UIElement` introduces a local `element` that shadows the class-level `let element: UIElement` property. While this doesn't cause a runtime bug here, it makes the code harder to read and could introduce subtle errors if the body is ever extended to reference the instance property. A distinct name like `uiElement` would avoid the ambiguity.

```suggestion
            if let uiElement = value as? UIElement {
                return HSAXElement(element: uiElement)
            }
```

How can I resolve this? If you propose a fix, please make it concise.

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