Skip to content

ActionUI demo apps v0.8 Pre-release

Pre-release
Pre-release

Choose a tag to compare

@abra-code abra-code released this 10 Sep 00:50
· 3 commits to main since this release

Covers everything between tag v0.7 (2026-07-24) and v0.8 (2026-09-05).

Summary: out-of-process access: a new ActionUIRemote bridge lets short-lived child processes, test harnesses, and agents read and drive a running app's windows over JSON-RPC, with Python, Node, and shell clients. Navigation containers gain a persistent toolbar, stacks become tappable cells, hidden finally means the same thing on all three renderers, and Chat/RichText gain find plus new runtime states.

At a glance

Area Change
ActionUIRemote New. JSON-RPC 2.0 bridge over Unix socket + Python, Node, /bin/sh + zsh clients, token auth (+11,800 lines)
Navigation New persistentToolbar on NavigationStack / NavigationSplitView, all three hosts
Stacks VStack / HStack / ZStack with actionID are tappable whole cells, all three hosts
ProgressView New progressViewStyle: automatic / linear / circular
hidden parity Web reserves space (visibility), Android stops leaking hit-test + TalkBack, toolbar hidden works
Chat add-on states["append"], states["lead"], states["search"] + showFindBar, resumeCheckpointActionID, acp-remote transport
RichText add-on showFindBar + host-driven states["search"]
Core New public ActionUIJSON, window enumeration, runtime inserts respect :platform suffixes
Docs / tooling JSON Guide + Architecture rewritten cross-platform, Skill/verifier updated, AGP 9.3.1 / Gradle 9.5.0

1. ActionUIRemote - drive a running app from another process

The largest piece in this release. A host that embeds ActionUI starts ActionUIRemoteServer on a Unix domain socket; any same-user process can then read and mutate its windows over newline-delimited JSON-RPC 2.0 with the same verbs and value encoding as the in-process C adapter. Built for hosts where the UI lives in the app and logic runs in short-lived child handlers (OMC applets), but equally usable for test automation and agents driving a running app.

  • actionui.* method table: values, strings, properties, state (with the engine's type-coercion rule), rows/selection, insertElement / insertRow / removeElement, modal/alert/confirm/toast, getElementInfo, listWindows, content-size limits. Setters return true so batch replies scan easily. Batches run in one main-thread turn (cap 4096).
  • Normative PROTOCOL.md (v1) + README.md: transport, framing (64 MiB line cap), envelope rules (named params only, null id = notification), error codes (-32700...-32603 plus app codes 1001 unknown window / 1002 unknown view / 1003 engine refusal / 1004 host refusal / 1005 main-thread timeout / 1006 unauthenticated), value encoding, environment contract (ACTIONUI_REMOTE_ENDPOINT, ACTIONUI_WINDOW_UUID, ACTIONUI_REMOTE_TOKEN).
  • Process-wide startShared / stopShared singleton with environment export, C entry points (actionUIRemoteStartServer, mint/add/revoke tokens, unexport), auto-start from ActionUIAppKitApplication and from the Python (app.start_remote_server()) and Node (app.startRemoteServer()) hosts.
  • Clients, all dependency-free: Python (actionui_remote.py, importable + python3 -m actionui_remote CLI with exit codes 0/1/2/3, FakeServer test double, 118 unit tests + live-server integration test), Node (actionui_remote.js, CommonJS, same surface), shell (actionui_remote.sh for /bin/sh + actionui_remote.zsh with persistent connection, awk helpers in their own files, 358-check ps-visibility matrix).
  • Security, measured not assumed: 0600 socket, getpeereid uid check, FD_CLOEXEC, optional per-work-unit token (required by default on startShared). The docs state the limit honestly: python3/node expose their environment to same-uid ps (no CS_RESTRICT), so shell clients and the ACTIONUI_REMOTE_TOKEN_FD descriptor handoff exist to keep the token off ps. No token-file form by design.

2. persistentToolbar on navigation containers

NavigationStack and NavigationSplitView gain a persistentToolbar array (same ToolbarItem / ToolbarItemGroup shapes) that stays in the bar on every screen inside the container and composes with each screen's own toolbar. A toolbar declared directly on either container is now a deprecated alias that warns once.

Implemented on all hosts with per-host mechanics (Apple: environment publish + per-screen apply, macOS window-toolbar shortcut; Android: LocalPersistentToolbarItems merge into each Scaffold; Web: container-owned nodes moved into the visible pane's bar so ids stay single). One documented divergence: on Web, persistent secondaryAction items get their own overflow menu rather than joining the screen's.

3. Tappable VStack / HStack / ZStack

A stack carrying actionID now dispatches as one tap target on all three hosts - the only way to author a rich cell (avatar + name + status), since Button renders title + systemImage only. In a data-driven template row it reports the owning container's id as viewID and the row index as viewPartID (the Button convention); otherwise its own id + 0. Innermost action wins, disabled is inert, cells are exposed as one button for accessibility (keyboard-reachable on Web). Also fixes a live Web bug where template clones all dispatched id 0.

4. ProgressView.progressViewStyle

automatic (default, preserves the old value-present = bar / absent = spinner pairing, and fixes macOS applying no style at all), linear (the only way to ask for an indeterminate linear bar), circular. Android matches; Web accepts the key for portability (circular warns, stays linear - native <progress> is linear in both states).

5. hidden means the same everywhere now

  • Web: hidden was display: none (collapsed). Now visibility: hidden - reserves space like SwiftUI .hidden() and Android hiddenSubtree(). Also fixes hidden on toolbar items doing nothing, and drops the display-reset bug that could reveal hidden TabView / SplitView panes.
  • Android: hidden was alpha(0) only - still hit-testable and TalkBack-visible. Now suppresses input via the enabled environment so taps pass through to what is behind (matching Apple/Web) instead of being swallowed. Nine gesture surfaces (Canvas, TabView, VideoPlayer, popover/context-menu/swipe/searchable, sidebar rows, etc.) now honor hidden and disabled for the first time.
  • Skill now states it plainly: hidden reserves space on every platform; in a toolbar it removes the item instead. No collapse semantic - use the documented ZStack panel-switcher idiom.

6. Chat + RichText add-ons

  • Both gain showFindBar + host-driven states["search"] (String; "" dismisses; re-set "" then term to re-apply the same term). Chat defaults the bar on; RichText defaults it off. Chat Cmd-F opens the transcript bar, Cmd-G walks hits across messages (opening folded thoughts/tool cards), scope covers thoughts/tool calls.
  • Chat states["append"]: add one ChatItem to the end with no transport traffic and no re-prime (vs states["content"] which replaces + re-primes). states["lead"]: hold lines until the next user send, then place them in front of that message (session markers that must precede, not follow).
  • Chat resumeCheckpointActionID: the second half of the incremental-persistence contract entryActionID starts. Emitted quiescent-only as {afterSeq, sessionId}; persist atomically with entries; feed back as acp-remote's resumeAfterSeq. Only the new acp-remote transport (ACP agent on another machine over WebSocket, no subprocess, all platforms) produces one.
  • Chat transports are re-configurable in place (identical re-inject = no-op; different viable config closes the in-flight turn, stops the old transport, attaches the new one primed from the on-screen transcript). ChatView bumped 0.2.3 -> 0.5.6 along the way (scroll-follow fix, checkpoint events, append/lead/search support).

7. Core fixes

  • New public ActionUIJSON: the C adapter's JSON conversion factored into core so the remote bridge encodes exactly the same way; adds insert-position and modal-style parsers; ActionUIModel gains windowUUIDs / hasWindow / hasElement so unknown window/view report distinctly.
  • Runtime insertElement / insertRow on Apple now run through PlatformFilter - :platform overrides resolve for runtime-inserted subtrees, closing an Apple-only gap (Android/Web already did).
  • ContentUnavailableView on Web honors the materialName escape hatch; Android lazy containers use bound-if-unbounded extents (fixes letterboxed full-screen grids) and ScrollView > LazyVGrid defers the scroll axis; Web demo tree validates clean under --strict.

8. Docs, schemas, tooling

  • ActionUI-JSON-Guide.md rewritten cross-platform (three renderers, same-intent-native-appearance, :platform override rules, graceful degradation); Architecture.md gains Android + Web sections and a Remote Binding section; Comparison-vs-ReactNative.md / vs-tkinter / README.md updated for three renderers + add-ons + zero-dependency web demo; Skill documents hidden, whole-cell tap, persistentToolbar, corrected ProgressView key; verifier schemas gain persistentToolbar, progressViewStyle, browserHistory:web, role:web / systemImage:web on CommandMenu.
  • Android: AGP 9.2.1 -> 9.3.1, Gradle wrapper 9.4.1 -> 9.5.0 in lockstep. Scripts/test-viewer.sh repointed at Apps/ActionUIViewer (was silently testing a stale binary).

Breaking changes

  • Web hidden no longer collapses layout. It reserves space like Apple/Android. If you relied on the collapse, switch to conditional structure instead of hidden.
  • toolbar on NavigationStack / NavigationSplitView is deprecated (still works, warns once). Move screen toolbars onto the screen element; use persistentToolbar only for items that must persist.

Notable fixes

  • Android hidden element staying hit-testable + TalkBack-visible; then hidden eating taps meant for behind it.
  • Web ContentUnavailableView ignoring materialName; hidden on toolbar buttons doing nothing; display-reset revealing hidden TabView/SplitView panes.
  • macOS ProgressView applying no style (fell back to spinner); no way to ask for indeterminate linear bar.
  • Apple runtime inserts ignoring :platform suffixes.
  • Web demo frame mixed fixed + flexible forms; missing verifier schemas for implemented browserHistory:web, role:web, systemImage:web.
  • Stale ChatView pin shipped inside AppletBuilder via Apps/ActionUIViewer/Package.resolved.

Platform support

Platform Minimum
macOS 14.6+
iOS / iPadOS 17.6+
watchOS 10.6+
tvOS 17.6+
visionOS 2.6+
Android 12.0+
Web Any modern browser (ES modules, no build step)

Known gaps

  • ActionUIRemote is macOS-only (Unix domain socket server lives in the Apple host).
  • ActionUIChat has not been ported to Android or Web.
  • Web persistent secondaryAction items use their own overflow menu rather than joining the screen's (documented divergence).