Skip to content

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 23:57
4d9835c

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-springboard runs the long press twice per leg — daily_reward from the
    plist, then runtime_add from QuickActions.Add — through the new
    tools~/ios-ui/run_springboard_tap.sh. The seed is asked for over both
    SIMCTL_CHILD_QA_SEED_RUNTIME and 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 reports SKIPPED; a menu that opens without the row after a
    confirmed seed is a FAIL like any other missing quick action. Run 94
    (2026-09-16): PASS on both supported legs
    — runtime_add reached
    Performed 5 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.plist and one from QuickActions.Add. It also answered the seeding
    question: the player sees the SIMCTL_CHILD_ environment variable, never the
    launch argument. The unity6-xcode27 canary 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 in classes.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
    whose res/xml, res/values and res/raw the ungated stripper already
    deletes — and that callback runs eleven seconds after the copy, before Gradle
    reads the source set. So QuickActionsTrampolineStripperAndroid now deletes
    that directory, and gate-off requires 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's UnityPlayerActivity, 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 took RemoveAll() then AddList(...),
    which is subtly wrong: RemoveAll keeps the in-memory list when the OS
    refuses the clear, and AddList then 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. SetList reconciles first, refuses with false and 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, the 2022.3 demo APK) showed: static and runtime-added shortcuts
    side by side in one real launcher menu, IconType.None rendering 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.
    QuickActionsTrampolineActivity recorded the tap for Performed
    and stopped there, so Android's predictive ranking only ever saw usage a game
    remembered to report by hand via QuickActions.ReportUsed(id). It now also
    calls reportShortcutUsed. 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 records Performed as before and reports
    nothing. ROADMAP.md previously 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, and reportShortcutUsed is 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.
    MaxShortcutCount still 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 an XLTS entitlement and Enterprise/Industry
    branding, so Hub never offers them to a Personal account; the installer itself
    is not paywalled (direct download and the unityhub:// 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.md now say so.

  • CI's iOS Simulator exports are ARM64 now, and the Xcode 27 leg gets a
    deployment target it will accept.
    The four *-xcode27 legs 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.BuildSimulator set the device architecture and never
    PlayerSettings.iOS.simulatorSdkArchitecture, whose default is X86_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 to ARM64 (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
    xcodebuild command line through a new per-leg build_settings matrix
    field, deliberately not in the checked-in ProjectSettings.asset: every
    Library cache key hashes ProjectSettings/**, and the 12.0 value is what
    proves the package compiles below its own @available(iOS 13.0, *) guards.
    The *-xcode27 legs stay continue-on-error canaries; 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 the xcodeproj gem,
    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 own quick_actions_ios test settled on after two years of flakes),
    taps the Daily Reward row 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 under Examples~/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 the ios-springboard-* artifact whatever the verdict. New
    ios-springboard job on 2022.3 (macos-15, app-delegate lifecycle) and
    unity6 (macos-26, scene lifecycle). Two runs on 2026-09-15. Run 76:
    SKIPPED on 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 the isHittable guard 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_reward row was tapped — SpringBoard exposes each quick action as a
    Button whose 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,
    and daily_reward appeared 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
    FAIL it 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: PASS on both
    legs
    — daily_reward reached Performed 41 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 the unity run takes 40–53 minutes and what would
    shorten it. The Library cache already hits on every leg; the minutes are
    the needs: 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-coex and
    ios-springboard gain matrix entries on GitHub's public-preview xcode-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-error so 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 one xcodebuild -version line
    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 with am 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 by CAPTURE_TAP
    (daily by default: the one id the synthetic taps never use, so the line
    cannot be a leftover) and waits for Performed 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 record SKIPPED and leave the run green, because none of them says
    anything about delivery. Every branch was dry-run against a fake adb driven
    by the real API 30 hierarchy dumps from run 61. Its first run (2026-09-11,
    PR #23) recorded PASS 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
    around QuickActionsBridge. It is the first time anything here has seen a
    launcher deliver a quick action, on any surface.

  • llms.txt at 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.md first and the README second. It ships to nobody (it is a
    repository file, like CONTRIBUTING.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 on IPostGenerateGradleAndroidProject:
    Unity 6.2 still lists it as supported with no obsolete marker, and
    AndroidProjectFilesModifier — 6000.0+ only — cannot write into the default
    unityLibrary and launcher modules, 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 mirroring QuickActionItem's 12 serialized
    fields — QuickActionItem has to stay gated, being a runtime type — plus an
    asset-migration test and a rework of the IconType drawer. 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:
    UnityPlayerActivity logs under the same Unity tag before any native
    player exists (CommandLine:, onActivityResumed:, onResume,
    windowFocusChanged:), and those four lines counted as "the player has
    logged". player_has_logged now 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.