Add NEUI_API_IOS, an iOS-only host extension - #33
Merged
Merged
Conversation
UIKit exposes a lot that has no portable equivalent, and a client had no way to reach any of it: keep the screen awake through a set, stop a thumb near the bezel from swiping the app away mid-song, ask whether the device dropped into Low Power Mode, fire a haptic tick. NEUI_API_IOS (include/neui/d/ios.h) is returned only by the two iOS hosts; every other host returns NULL from get_interface, the same feature-detect contract NEUI_API_EMBED and NEUI_API_METRICS already use. Unlike the xpl host's inert NEUI_API_EMBED on iOS, it is never handed out empty: a host that returns it implements every method, and a call it cannot answer says so in its return value. Changes arrive as one NEUI_EVENT_IOS_ENVIRONMENT_CHANGED carrying a bitmask of what moved, in its own event category. Safe-area insets are deliberately NOT here. They are portable, they already exist in NEUI_API_METRICS, and an Android host can implement the same seam from WindowInsets. Entries that do have an Android counterpart are marked inline for whoever writes that host; three are marked "Android: none". Implementation is shared between the hosts (hosts/shared/ios/ios_api.h) because UIKit's globals are identical in both. They differ in two things only, and both are seams: resolving a frame to its UIViewController, and walking the host's own registry to deliver an event. Those seams are registries rather than single slots, and so are the metrics ones now. neui_init() registers the native iOS host and then xpl, so an assigned slot always ended up holding xpl's - and for a native-host client it failed silently. This was already true of NEUI_API_METRICS: safe_area_insets measured zeros on every edge in examples/ios, masked because get_client_rect computes its top inset directly rather than through the seam. Both now ADD, the frame lookups try each until one claims the frame, and the example prints its insets so a regression shows up on the next run. Verified on an iOS 26.5 simulator (351 cases / 2107 checks) and on macOS (343 / 2046), both -Werror clean. Battery, thermal, Low Power, brightness and whether haptics actually fire still need hardware. Docs: docs/host-ios.md is new and entered through the CLAUDE.md subsystem index, which is the path an agent follows. Also corrected what would otherwise mislead - CLAUDE.md's platform list and host table, TODO.md listing iOS as unported, the neui.ios.* namespace missing from attrs.h, and the "phase-2 stubs" claims in hosts/ios/host.h and platform_ios.mm for widgets that are long since implemented.
defiantnerd
force-pushed
the
feature/ios-host-extension
branch
from
September 19, 2026 18:41
1f2885e to
480891b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
UIKit exposes a lot that has no portable equivalent, and a client had no way to reach any of it: keep the screen awake through a set, stop a thumb near the bezel from swiping the app away mid-song, ask whether the device dropped into Low Power Mode, fire a haptic tick.
What this adds
NEUI_API_IOS(include/neui/d/ios.h) — a 22-method vtable returned only byneui.host.iosand the xpl host onplatform_ios.mm. Every other host returns NULL fromget_interface, the same feature-detect contractNEUI_API_EMBEDandNEUI_API_METRICSalready use. Four groups: idle timer / screen-edge gesture deferral / home indicator / brightness; status bar / orientation / forced appearance; Dynamic Type category and the accessibility switches; device / battery / thermal / Low Power / haptics / keyboard inset.Unlike the xpl host's inert
NEUI_API_EMBEDon iOS, it is never handed out empty: a host that returns it implements every method, and a call it cannot answer says so in its return value. Changes arrive as oneNEUI_EVENT_IOS_ENVIRONMENT_CHANGEDcarrying a bitmask of what moved, in its own event category — Dynamic Type stays onMETRICS_CHANGED, where clients already handle it.Safe-area insets are deliberately not here. They are portable, they already exist in
NEUI_API_METRICS, and an Android host can implement the same seam fromWindowInsets. Entries that do have an Android counterpart are marked inline for whoever writes that host; three are markedAndroid: none.Seams are registries now, including the metrics ones
neui_init()registers the native iOS host and then xpl, whoseplatform_init()runs last, so an assigned single-slot seam always ended up holding xpl's — and for a native-host client it failed silently.This was already true of
NEUI_API_METRICS.safe_area_insetsmeasured zeros on every edge in a binary linking both hosts, masked becauseget_client_rectcomputes its top inset directly rather than through the seam. Confirmed by temporarily restoring last-wins and re-runningexamples/ios:Both seam families now add rather than assign; the frame lookups try each until one claims the frame. The safe-area seam signature became
boolso "not my frame" is distinguishable from "mine, and the insets are zero". The example prints its insets permanently, so a regression shows up on the next run.Verification
-Werrortests/test_metrics.cppis new and covers the seam rule with two fake hosts —metrics.hhad no test at all before.tests/test_ios_api.cpppins the enum and bit-flag ABI on all four CI jobs;tests/test_ios_api_device.mmis iOS-only and runs on the simulator in CI.Not verified: battery, thermal, Low Power, brightness and whether haptics actually fire need hardware — the simulator can only confirm they answer in range.
Docs
docs/host-ios.mdis new and entered through theCLAUDE.mdsubsystem index, which is the path an agent actually follows. Also corrected what would otherwise mislead:CLAUDE.md's platform list and host table,TODO.mdlisting iOS as unported, theneui.ios.*namespace missing fromattrs.h, and the "phase-2 stubs" claims inhosts/ios/host.handplatform_ios.mmfor widgets that are long since implemented.