Repository navigation
v0.7.0
Added
- The iOS SpringBoard test now also taps a shortcut nothing baked into
Info.plist. Every iOS tap CI had ever taken was on a static shortcut:
the app is installed and never launched, so the only rows in SpringBoard's
menu were the three the settings asset baked in. A runtime row cannot exist
until the app has run and published one, and the demo's own "Add" button is
IMGUI, which puts no element on screen for XCUITest to tap. So the testbed
publishes one itself when asked
(Examples~/Testbed*/Assets/Scripts/QuickActionsRuntimeSeeder.cs, one item
because iOS shows at most four and the statics take three), and
ios-springboardruns the long press twice per leg —daily_rewardfrom the
plist, thenruntime_addfromQuickActions.Add— through the new
tools~/ios-ui/run_springboard_tap.sh. The seed is asked for over both
SIMCTL_CHILD_QA_SEED_RUNTIMEand a launch argument, because which of the two
an IL2CPP player on iOS receives is not documented anywhere we could check;
the seeder records which one arrived. A seed that never lands is the harness
missing, so it reportsSKIPPED; a menu that opens without the row after a
confirmed seed is aFAILlike any other missing quick action. Run 94
(2026-09-16):PASSon both supported legs —runtime_addreached
Performed5 s after the tap on 2022.3 / iOS 26.2 and on 6000.3.21f1 /
iOS 26.5, and SpringBoard's menu held all four rows at once, three from
Info.plistand one fromQuickActions.Add. It also answered the seeding
question: the player sees theSIMCTL_CHILD_environment variable, never the
launch argument. Theunity6-xcode27canary opened the same menu and its tap
never delivered, with the app in the foreground 0 s after it (5 s on the green
legs), so it did not cold-start there and the cause is not established. - With the define off, the package's Java no longer ships at all. The
define-off APK used to carry both plugin classes inclasses.dex— dead and
unreachable, but there (4 dex references, the same as a define-on build) — and
the reason given was "Unity cannot conditionally exclude a loose native
source". That is true of Unity's mechanism (PluginImporter.defineConstraints
gates managed plugins) but it never established that this package cannot.
A CI step added in this release printed the answer: Unity stages the sources at
unityLibrary/src/main/java/com/emindeniz99/quickactions/— the very module
whoseres/xml,res/valuesandres/rawthe ungated stripper already
deletes — and that callback runs eleven seconds after the copy, before Gradle
reads the source set. SoQuickActionsTrampolineStripperAndroidnow deletes
that directory, andgate-offrequires zero references to it in the
define-off dex instead of merely counting them. Only the package's own
directory goes, never a source root, so Unity'sUnityPlayerActivity, a host
app's Java and another plugin's are untouched — three tests pin both ends. The
Java package name the directory is derived from is pinned by
tools~/check_frozen_strings.py, so a rename cannot silently un-gate it. QuickActions.SetList(IList<QuickActionItem>)— make the set exactly this
list in one call. Previously this tookRemoveAll()thenAddList(...),
which is subtly wrong:RemoveAllkeeps the in-memory list when the OS
refuses the clear, andAddListthen skips every id already in that list — so
a hand-rolled replace silently merges the stale set with the new one instead
of replacing it.SetListreconciles first, refuses withfalseand changes
nothing if the read or the clear does not land, and only then adds. It is not
atomic (no quick actions exist between the two steps, and a failed add leaves
the set empty), which the API doc states. Five tests cover the replace, the
empty-list clear, the null argument, the refused clear, and the unreadable
set.
Fixed
-
The README says how to try it on a device without building anything, and
records what a hand-run on a Moto G (Android 14, the2022.3demo APK) showed: static and runtime-added shortcuts
side by side in one real launcher menu,IconType.Nonerendering as a blank
tile, and the define-off APK from the same CI run showing no quick actions
at all — the gate on hardware rather than in an APK diff. -
A tap now reports shortcut usage to the launcher — for dynamic and pinned
shortcuts.QuickActionsTrampolineActivityrecorded the tap forPerformed
and stopped there, so Android's predictive ranking only ever saw usage a game
remembered to report by hand viaQuickActions.ReportUsed(id). It now also
callsreportShortcutUsed. Static (manifest-baked) shortcuts are not
covered: that call's ownership gate scans the dynamic and pinned sets only,
so a tap on a baked shortcut recordsPerformedas before and reports
nothing.ROADMAP.mdpreviously claimed this fix would cover static shortcuts
— it does not, and that line is corrected. The report is wrapped so it can
never affect delivery, andreportShortcutUsedis not on Android's
worker-thread-warned method list. -
Update()now says which pinned copies it cannot reach. It already
refreshes a user-pinned copy whose id is still added —addDynamicShortcuts
is documented as updating same-id dynamic and pinned entries — but a copy
surviving only as a pinned shortcut is refused as not-added, and the stale
label stays on the home screen. The package's own writes disable a dropped
managed id rather than orphaning it, so reaching that state takes outside
interference; the XML doc now names it instead of leaving it implied. -
iOS's 4-shortcut count is described as observed, not documented.
MaxShortcutCountstill returns 4 on iOS and nothing about the behaviour
changes — but Apple publishes no integer ("up to the system-defined limit")
and its own sample article says not to cap client-side, so calling 4 "the
documented Home Screen display limit" asserted something Apple does not.
Nothing in the package enforces it either: the iOS bridge accepts every item.
The comment and both XML docs now say so. -
The iOS UIScene version floor now says how to actually get it. The docs
named Unity 2022.3.72f1 as the floor for building under Xcode 27 / the iOS 27
SDK without saying that Unity's own release-catalog API — the one behind Unity
Hub's version picker — lists nothing newer than 2022.3.62f3 for that line.
2022.3.63f1 through .76f1 carry anXLTSentitlement and Enterprise/Industry
branding, so Hub never offers them to a Personal account; the installer itself
is not paywalled (direct download and theunityhub://deep link both work),
but it is off the normal path, and Unity 6 is the supported route for a
2022.3 project that must build under Xcode 27. README and
PRODUCTION_READINESS.mdnow say so. -
CI's iOS Simulator exports are ARM64 now, and the Xcode 27 leg gets a
deployment target it will accept. The four*-xcode27legs had been red
since run 76 for two reasons, both in the testbed projects and neither in the
package, which never reads or writes a deployment target or an architecture:
(1)TestbedBuilder.BuildSimulatorset the device architecture and never
PlayerSettings.iOS.simulatorSdkArchitecture, whose default isX86_64, so
every simulator export was x86_64-only — tolerated under Rosetta on the macOS
15 and 26 runners, refused outright by the arm64-only iOS 27 simulator; (2)
Xcode 27 rejects an iOS Simulator deployment target below 15.0 and
Testbed2022 carries 12.0. The architecture is now set toARM64(not
Universal, which would compile both slices and double the il2cpp phase)
behind#if UNITY_2022_1_OR_NEWER, since 2021.3 has no such API and no
simulator leg. The deployment target is overridden on that one leg's
xcodebuildcommand line through a new per-legbuild_settingsmatrix
field, deliberately not in the checked-inProjectSettings.asset: every
Library cache key hashesProjectSettings/**, and the 12.0 value is what
proves the package compiles below its own@available(iOS 13.0, *)guards.
The*-xcode27legs staycontinue-on-errorcanaries; the image still
carries Xcode 27.0 beta 6 behind a preview badge, and the 2022.3 one is
expected to stay red for a third reason (2022.3.62f3 emits no UIScene
manifest, which the iOS 27 SDK requires).
Added
-
A SpringBoard tap on the iOS Simulator, driven by XCUITest.
tools~/ios-ui
is a UI-testing bundle with no host app — generated with thexcodeprojgem,
committed, built unsigned, no Apple account — that presses Home, long-presses
the app icon (1.5 s, ±0.2 s over four attempts: the adaptive duration
Flutter's ownquick_actions_iostest settled on after two years of flakes),
taps theDaily Rewardrow and waits for the id in a marker file the two
Simulator-compiled testbeds (2022.3 and Unity 6) now append on every
Performed(Examples~/Testbed2022/Assets/Scripts/QuickActionsPerformedMarker.cs
and its identical copy underExamples~/Testbed6/; 2021.3 is export-only on
the Simulator and carries none), read from the host side of the simulator's
data container — a fact, not a log line. Verdicts follow the Android
capture:PASS/SKIPPED(the automation's own misses — no icon, no menu,
a tap that did not register; the run stays green) /FAIL(SpringBoard took
the tap and nothing arrived, or its menu opened without the app's quick
actions). One deliberate difference: a run that writes no verdict at all is
red, because that is also what a broken test bundle looks like. Every
step's accessibility tree and screenshot
ship in theios-springboard-*artifact whatever the verdict. New
ios-springboardjob on 2022.3 (macos-15, app-delegate lifecycle) and
unity6 (macos-26, scene lifecycle). Two runs on 2026-09-15. Run 76:
SKIPPEDon both legs — the bundle built, the simulator booted, the app
installed, SpringBoard's tree listed the icon (page 2 of 2 on iOS 18.6 and
26.5 alike), and the test stopped there: an icon on a page other than the
current one reports a zero frame, which the on-screen check read as inside
the screen, and theisHittableguard then said no; fixed in the same PR
(swipe until the icon has a real frame; press and tap by coordinate).
Run 77, with that fix: SpringBoard's own tap delivered on both legs.
One swipe to page 2, the first 1.5 s press opened the context menu, the
daily_rewardrow was tapped — SpringBoard exposes each quick action as a
Buttonwhose identifier is the shortcut type and whose label is
Daily Reward, Claim today's gift, identically on iOS 18.6 and 26.5 — the
app cold-started, reached the foreground within ten seconds of the touch,
anddaily_rewardappeared in the marker file: 35–43 s after the touch on
2022.3.62f3 / iOS 18.6 (Xcode 16.4, app-delegate lifecycle) and 41–54 s
after it on 6000.3.21f1 / iOS 26.5 (Xcode 26.6, scene manifest — the first
quick action UIKit itself has delivered on the UIScene export; its
x86_64-only Simulator app still runs there, where the iOS 27 canary
refuses it).
Both legs still went red: the test's delivery window was 30 s, and the
FAILit wrote quoted a marker that already held the id. Fixed in the same
PR — the window is its own knob (QA_DELIVERY_SECONDS, 120 s, counted
from the tap), the verdict quotes the read that decided it, and rows are
matched by identifier first. Run 78, with those fixes:PASSon both
legs —daily_rewardreachedPerformed41 s (2022.3) and 46 s (unity6)
after the tap. Two minutes of every leg are XCUITest's own:
it waits up to 60 s for SpringBoard to go idle around each synthesized
touch, and the context menu's blur never lets it. A physical iPhone remains
untested, and nothing in the shipped docs says otherwise. -
A second maintainer research record,
docs~/ci-cost-and-caching-research-2026-09.md
(not shipped): why theunityrun takes 40–53 minutes and what would
shorten it. The Library cache already hits on every leg; the minutes are
theneeds:chain, per-job image pulls and activation, and the simulator's
first boot behind GitHub's macOS concurrency cap. GameCI's guidance, 25
other projects' workflows, GitHub's and Unity's documentation, a
refutation pass over each recommendation, and the finding that the
testbeds export an x86_64-only simulator app by omission (the Unity 6
Xcode 27 canaries fail on exactly that). Nothing from it is implemented in
this release. -
Xcode 27 / iOS 27 canary legs.
ios-simulator,ios-simulator-coexand
ios-springboardgain matrix entries on GitHub's public-previewxcode-27
image (Xcode 27.0 beta 6 today, with the iOS 27 SDK and runtime — the SDK
App Store uploads must use from April 2027, which no Unity line documents
support for yet),continue-on-errorso a red canary cannot fail the
workflow run — its own check still shows red, and it gates nothing only
because none of these jobs is a required check. The existing macos-15 /
macos-latest legs are untouched except for onexcodebuild -versionline
printed at the top of their compile step. What the first run (run 76)
measured, Xcode 27.0 beta 6 (27A5252f) on macOS 27.0: the public
2022.3.62f3 export does not compile — "The iOS Simulator deployment target
'IPHONEOS_DEPLOYMENT_TARGET' is set to 12.0, but the range of supported
deployment target versions is 15.0 to 27.0.x" — so a 2022.3 project needs
its minimum iOS raised to 15 before Xcode 27 will build it; the 6000.3.21f1
export compiles, but the app it produces does not install on the iOS 27
simulator — "The executable has code for these platforms and architectures:
[iOS-simulator, x86_64]. This device can run code for these platforms:
iOS-simulator" — the Simulator build is x86_64-only, which the macOS 26
images run and this one no longer does. Both are the canaries' point, and
both go into the October decision. -
A research record, not shipped:
docs~/ios-toolchain-and-ui-test-research-2026-09.md— Apple's Xcode 26
upload mandate and the April 2027 iOS 27 one, what each Unity line supports,
what the GitHub macOS images carry, the ways the iOS home screen can be
automated and what other SDKs actually do, and what a real iPhone from
GitHub Actions would cost — every claim with its source and its confidence.
The decision it records: keep the Xcode 16.4 legs for now, add the canaries,
revisit in October 2026. -
The Android smoke now taps a shortcut in the launcher's own popup. Every
assertion the smoke makes has delivered its tap witham start— the intent
the launcher would build, built by the script instead — so "a real launcher
tap behaves the same" has been listed as unproven in every doc here. The
long-press capture already opened the popup and photographed it; it now
force-stops the app, clears logcat, taps the row named byCAPTURE_TAP
(dailyby default: the one id the synthetic taps never use, so the line
cannot be a leftover) and waits forPerformed quick action 'daily'. That is
the launcher starting a dead process through the trampoline. It is also the
capture's only assertion, and it is conditional:FAIL— and a red run —
requires the sheet to have closed on the tap, meaning the launcher accepted
it and nothing arrived. A drawer that never opened, a sheet that vanished on
release, or a tap the launcher ignored (the row is still on screen afterwards)
all recordSKIPPEDand leave the run green, because none of them says
anything about delivery. Every branch was dry-run against a fakeadbdriven
by the real API 30 hierarchy dumps from run 61. Its first run (2026-09-11,
PR #23) recordedPASS daily via 'Claim today'on all four legs — 2021.3,
2022.3 and 2022.3-release on the API 30 image, unity6 on API 35 — so on every
one of them the launcher opened its drawer, the long press produced the
popup, and a tap on the row cold-started the force-stopped app and delivered
daily. That includes the minified leg, where R8 had renamed classes
aroundQuickActionsBridge. It is the first time anything here has seen a
launcher deliver a quick action, on any surface. -
llms.txtat the repo root. The llmstxt.org
convention: a short markdown index an AI agent can fetch before anything else
— one paragraph on what the package is, the define that every integration
forgets, the install routes, and a curated link list that sends an agent to
AGENTS.mdfirst and the README second. It ships to nobody (it is a
repository file, likeCONTRIBUTING.md), duplicates no documentation, and
links only to raw markdown, so a small model can follow it in one hop.
Changed
- Two long-standing ROADMAP items are now recorded decisions, not plans.
The Android post-processor stays onIPostGenerateGradleAndroidProject:
Unity 6.2 still lists it as supported with no obsolete marker, and
AndroidProjectFilesModifier— 6000.0+ only — cannot write into the default
unityLibraryandlaunchermodules, where every one of the package's
outputs goes; 2021.3 and 2022.3 would keep the old path regardless, and the
incremental-build question it would answer is already measured green on every
push. The settings asset keeps showing as "missing script" when a project
is opened with the define off: the setup the README recommends (define on in
the Editor, gated only in the production Build Profile) never hits it, and the
fix would cost a permanent DTO mirroringQuickActionItem's 12 serialized
fields —QuickActionItemhas to stay gated, being a runtime type — plus an
asset-migration test and a rework of theIconTypedrawer. Measured while
deciding: an ungated Editor assembly costs zero in a player — the one that
already ships this way appears 0 times in the APK's IL2CPP metadata. Both
entries record what would re-open them.
Fixed
- The Android smoke's relaunch-once now fires when only the Java shim spoke.
Run 76 (2026-09-15, 2021.3 on the API 30 image) repeated the known emulator
failure — the activity reached the foreground and the Unity player never
initialised — but the relaunch that exists for exactly that did not fire:
UnityPlayerActivitylogs under the sameUnitytag before any native
player exists (CommandLine:,onActivityResumed:,onResume,
windowFocusChanged:), and those four lines counted as "the player has
logged".player_has_loggednow ignores the shim's messages, so a process
that said nothing else within half the budget is force-stopped and launched
once more, as intended. A player that did come up and published nothing
still gets no second chance. Run 79 (2026-09-15, 2022.3) then showed the
same stall in its other shape — the cold tap started the process, the
activity was displayed, and only the shim ever spoke for the whole 180 s
budget — so the cold tap now has the same second chance: half the budget,
then, if the player has logged nothing since that start, a force-stop and
one more cold tap (same id), then the other half. One shared helper,
relaunch_once_if_engine_silent, serves both steps.