Skip to content

feat(mobile): add Android agent notifications and Material You theming - #456

Merged
rynfar merged 10 commits into
pylonfrom
upstream/2026-09-10-mobile-android
Sep 11, 2026
Merged

feat(mobile): add Android agent notifications and Material You theming#456
rynfar merged 10 commits into
pylonfrom
upstream/2026-09-10-mobile-android

Conversation

@rynfar

@rynfar rynfar commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Pylon Mobile was missing upstream's Android work from the 2026-09-10 cycle: Android agent alerts and ongoing activity through Pylon Connect, wallpaper colors and an optional Material You layout, the iOS Keychain access group that SecureStore needs, and the relay fix for queued APNs jobs. It also still carried the mobile provider setup screens that upstream has since removed.

How it was ported

Each source was cherry-picked with -x in upstream order against origin/pylon (1e1a43d576, since rebased onto 16a99685d8 after #458), frozen at upstream 6c583620ff7ad3235b135af7107c0543467eecfa. Conflicts were resolved by splicing only each source's hunks into Pylon's files. The Keychain change was applied by hand because Pylon's iOS config differs. The icon sources were compared with Pylon's own brand exporter instead of being imported.

Sources

Upstream SHA PR Outcome
9eb4d71681dc7d002082db2f8b4bacf7614412f4 #9721 Adopted
1d1bf50405ccca8fdb8b57879d9def379e27a550 — (app version 1.1.0) Skipped: Pylon Mobile's version is independent (1.0.1)
c0cad74bfaddf153c3adaaf4f78af2be9260c4c1 #10598 Already covered (launcher crop); T3 background artwork left to the maintainer, see below
02443335b155e46248f96d80e037603abc742a61 #3665 Adopted
892de47f0625183edd111dd36df45b156185bbf2 #10620 Already covered: Pylon's splash mark already fits the Android 12+ mask
b28471567cebe20347205059bbbdb3efe1213528 — (app version 1.1.1) Skipped: Pylon Mobile's version is independent
579266caa89238611ddb974ffebe9968c6a6e5d5 #10691 Adopted
4664c572a78231611491f63f677f0e007ebaab03 #10692 Adopted
1862686f9e42bf1d77e7f2ad03e24d7b0c7908f1 #10859 Adopted (clean pick)
6c583620ff7ad3235b135af7107c0543467eecfa #10416 Adopted with Pylon adaptations

Pylon adaptations

  • #9721 provider setup removal. SettingsProviderSetupRouteScreen, ProviderSetupLink and provider-setup-state were byte-identical to upstream's pre-removal files. They arrived with the Antigravity port (feat(providers): add the Antigravity setup surfaces #440), so they are not Pylon-original. ConnectionEnvironmentRow keeps Pylon's PrimeHostMaintenanceStatus, which already sends Prime host operations to web/desktop. Pylon's model picker, composer and queue paths never had upstream's "finish setup" alerts, so those hunks had nothing to change. docs/user/providers-antigravity.md now points setup to web or desktop. Pylon has no Antigravity section in remote-access.md.
  • #3665 Keychain access group. keychain-access-groups uses $(AppIdentifierPrefix)${iosBundleIdentifier}: the bundle ID the build actually signs with, including a T3CODE_IOS_PERSONAL_TEAM_BUNDLE_ID override. Upstream uses the variant bundle ID, which is wrong for personal-team and custom-bundle builds. Xcode resolves the team at signing time, so no T3 team or bundle ID is pinned. The group is the app's own bundle-scoped group, not an App ID service, so no Apple Developer portal change is expected. That is unverified on a signed device build here.
  • #10691 / #10692 Material You. The default theme keeps its "Pylon" label. Pylon's mobile theme defines no variables beyond upstream's (checked by diffing variable names). The Material You palette spreads the base theme, and status colours use the fixed adaptive-* palette, so attention, delegation and compaction states look the same. ComposerSurface keeps Pylon's explicit shadow style and drops it for the Material You layout, matching upstream's intent. Pylon's thread rows keep their existing hooks and props, including plan progress and pins. Docs went to docs/user/mobile-appearance.md, since Pylon has no docs/user/appearance.md. The generated Uniwind theme files match their generator (generate --check).
  • #10416 Android notifications.
    • OTA stays behind Pylon's PYLON_EAS_PROJECT_ID gate. T3CODE_MOBILE_UPDATES_ENABLED=0 now also turns updates off.
    • Scheme fallbacks (JS and Kotlin) and the new test fixtures use pylon-code. Settings copy says Pylon and Pylon Connect.
    • The relay worker keeps Pylon's hourly cleanup cron (chosen for Neon) and adds the FCM queue consumer.
    • New relay error classes use Schema.TaggedErrorClass, because Pylon is still on Effect 4.0.0-beta.103.
    • Pylon has no docs/operations/connect-setup.md. Its Android Clerk redirect table went into docs/internals/t3-connect.md with com.pylon.code* callbacks.
    • docs/operations/android-notifications.md was rewritten for Pylon: package IDs, Neon stages, -- --stage deploy syntax, and the opt-in relay workflow. docs/user/mobile-notifications.md is in Pylon voice and does not promise Android delivery in builds without push support. The ops doc documents the relay rollback hazard. Both are linked from docs/README.md.
    • deploy-relay.yml keeps Pylon's workflow name, environment and targets. The only new line passes FCM_SERVICE_ACCOUNT.
    • Relay test fixtures keep upstream's com.t3tools.t3code* bundle IDs. They are test data only, and the existing Pylon relay tests use the same values.
    • The notification small icon is expo-notifications' notification_icon, generated from Pylon's android-notification-icon.png.
    • Honest notification switches. Upstream shows Device Notifications and Ongoing Agent Activity as On when nothing can arrive: an Android build without Firebase config, or any device whose APNs/FCM token lookup failed, still registers without a token. Pylon ships no Firebase config today, so every Android build hit this.
      • app.config.ts publishes extra.androidPushConfigured (true only when T3CODE_ANDROID_GOOGLE_SERVICES_FILE is set). Android push support requires it.
      • Unconfigured builds keep both switches off and disabled with "This app build can't receive notifications". Builds with an older binary still get "Install a newer app build to enable notifications". Unconfigured builds also skip creating agent notification channels.
      • remoteRegistration now records whether the accepted registration carried a push token (getAgentAwarenessPushDeliveryReady). Device Notifications on both platforms, and Ongoing Agent Activity on Android, read On only then. The enable alerts say when a device registered without a token. iOS Live Activity Updates keep their registration gate, since they use push-to-start tokens.
    • Platform-aware copy. The web/desktop Mobile clients page labels Android's badge "Ongoing activity", and its empty state no longer says iPhone. The mobile sign-in prompt names ongoing activity on Android and Live Activity updates on iOS.
    • One assertion in remoteRegistration.test.ts, httpResponse.cookies with Hermes-style headers, was removed. It checks the Effect patch from #10851, which the parallel mobile-drafts lane is porting with its own test. The rest of that test (typed failure, no defect, no saved record) passes here.

Duplicated-feature comparison (maintainer decision)

Pylon fixed Android launcher clipping independently (eef9d3531c, 2c99cfc0a4). Its exporter renders the Pylon mark at 352px on a 432px canvas, throws if the mark crosses the 132px safe circle, and uses the same layer for monochrome. Channel background colours are solid (#00639B, #7565C7, #000000). Upstream #10598 fixes the same crop differently. It adds a new export-android-icons.ts that renders T3's Icon Composer layers: a wordmark foreground plus full-bleed dev (blueprint grid and annotations) and nightly (sky and clouds) background images. The quick-actions shortcut gets the same background. Pylon's shortcut already uses the launcher's foreground and colour, and its notification icon already comes from Pylon's mark. Adopting the artwork layers would be a branding change, and it would bring back the dark preview tile that Pylon replaced with #7565C7 on purpose. Nothing was ported.

#10620 fixed a splash crop that Pylon's artwork does not have. With imageWidth: 220, the 1024px icon is drawn at 880px on the 1152px Android 12+ splash canvas, and the circular mask has a 384px radius. Measured with sharp, Pylon's mark reaches 367.9px from the centre in the 1024px source, which is 316px on the splash, inside the mask. The rounded-square background covers the whole circle. Upstream's version draws the splash from its launcher layers. For Pylon that would mean the mark on a solid colour, which only makes sense if the launcher artwork decision above changes. These are geometry measurements, not an emulator render.

Provisioning the maintainer must do (nothing is deployed or committed)

  • Relay migration on merge. DEPLOY_RELAY is true, so merging to pylon deploys the relay. That applies the additive migration 20260906042516_android_devices (adds nullable android_api_level, makes ios_major_version nullable) to Neon prod, creates the Cloudflare queues RelayFcmDeliveryQueue and RelayFcmDeliveryDeadLetterQueue, and serves /v2/client/devices next to the frozen /v1. Only web and desktop list devices (the Mobile clients page), and builds from this branch call /v2. Mobile never lists devices. The relay deploys on merge, before those clients ship.
  • Rollback is a forward fix. Once any Android device registers, redeploying an older relay breaks the Mobile clients page (it cannot encode Android rows) and sends FCM tokens to APNs, whose BadDeviceToken handling clears them. Fix forward, or delete platform = 'android' rows from relay_mobile_devices first. This is documented in docs/operations/android-notifications.md.
  • GitHub production environment secret FCM_SERVICE_ACCOUNT. This is the Firebase service-account JSON. It is optional: without it, deploys succeed and Android deliveries log "not configured" and are skipped. APNS_ENABLED defaults to true and needs no change.
  • Firebase project. Register com.pylon.code, com.pylon.code.preview and com.pylon.code.dev, enable the FCM API, and create a service-account key. Never commit google-services.json.
  • Android builds. Set the EAS file variable T3CODE_ANDROID_GOOGLE_SERVICES_FILE per build environment. It must be available to fingerprint generation as well as the build.
  • Clerk. Android native sign-in needs clerk://com.pylon.code[.preview|.dev].callback in the mobile SSO redirect allowlist.
  • Expo fingerprint. This PR changes native inputs on both platforms: the iOS entitlement, the Kotlin composer selection theming, the new t3-agent-notifications module, minSdkVersion: 24, googleServicesFile, and expo-notifications built from source. New iOS and Android binaries are required; OTA cannot deliver this to existing installs.

Verification

  • Tests, 258 passing across 18 files:
    • vp test run, mobile, 98 tests in 9 files: Material You theme, storage, mobile theme, review diff adapter, Android notifications, notification permissions, remote registration, settings logic, thread settings sheet state. These include the unprovisioned Android build (switches off, no token lookup, no channels, registration without a token) and an iOS token-lookup failure (registered but not delivery-ready).
    • relay, 136 tests in 6 files: APNs deliveries, activity publisher, FCM client, FCM deliveries, mobile registrations, API.
    • web, 5 tests: MobileClientsUserProfilePage.logic.
    • contracts, 6 tests: relay.
    • client-runtime, 13 tests: managedRelay.
  • Typechecks: @t3tools/mobile, t3code-relay, @t3tools/web, @t3tools/contracts, @t3tools/client-runtime.
  • vp lint and vp fmt --check on every changed TS/TSX/MD/YML file.
  • vp run icons:check: 40 files current. Uniwind theme generate --check: current.
  • git grep PRIME_AGENT_DRIVER_KIND: 3 usages, same as origin/pylon.
  • vp run lint:mobile ran, but ktlint, detekt and swiftlint are not installed here (no Java runtime), so it skipped all three. The Mobile Native Static Analysis CI job covers the new Kotlin. Its first run caught one test assertion pushed past 100 columns by the Pylon scheme; that is fixed in b147efcbc1 and the job passes.
  • No local client, simulator or emulator pass was run. The orchestrator does one integrated pass later.

Upstream evidence

Ported by Claude Opus 5 in Claude Code.

@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pylon-marketing Ready Ready Preview Sep 11, 2026 1:29am UTC

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 14.1 KiB 13.9 KiB −150 B (−1.0%) 15.1 KiB
Codex Thread snapshot wire 7.2 KiB 7.2 KiB +8 B (+0.1%) 7.3 KiB
Codex Live turn WebSocket wire 6.9 KiB 6.7 KiB −158 B (−2.3%) 7.8 KiB
Codex Live turn WebSocket decoded 58.8 KiB 58.0 KiB −910 B (−1.5%) 66.4 KiB
Codex Live turn messages 10 8 −2 (−20.0%) 21
Claude Total thread wire 14.1 KiB 14.1 KiB −12 B (−0.1%) 15.1 KiB
Claude Thread snapshot wire 7.2 KiB 7.2 KiB −7 B (−0.1%) 7.3 KiB
Claude Live turn WebSocket wire 6.9 KiB 6.9 KiB −5 B (−0.1%) 7.8 KiB
Claude Live turn WebSocket decoded 59.7 KiB 59.7 KiB 0 B (0.0%) 66.4 KiB
Claude Live turn messages 10 10 0 (0.0%) 21

Baseline: 16a9968 · PR result: 0b1bdf0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 115.6 KiB
  • Claude decoded thread snapshot: 116.3 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

juliusmarminge and others added 10 commits September 10, 2026 19:27
(cherry picked from commit 9eb4d71681dc7d002082db2f8b4bacf7614412f4)
SecureStore keeps connection credentials in the iOS Keychain. Declare the
bundle-scoped access group explicitly so pairing does not fail with a missing
entitlement. The group uses $(AppIdentifierPrefix), which Xcode resolves from
the signing team, so Pylon still pins no Apple team or T3 identifier.

Adopted from 02443335b155e46248f96d80e037603abc742a61 (#3665)
Co-authored-by: Pixel Perfect <me@pixp.cc>
(cherry picked from commit 579266caa89238611ddb974ffebe9968c6a6e5d5)
Co-authored-by: Pixel Perfect <me@pixp.cc>
(cherry picked from commit 4664c572a78231611491f63f677f0e007ebaab03)
…0859)

(cherry picked from commit 1862686f9e42bf1d77e7f2ad03e24d7b0c7908f1)
…10416)

Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>
(cherry picked from commit 6c583620ff7ad3235b135af7107c0543467eecfa)
The Pylon scheme made one deep-link assertion exceed ktlint's 100-column limit.
…them

Android builds without Firebase config and devices whose APNs or FCM token
lookup failed still registered with the relay, so Settings showed Device
Notifications and Ongoing Agent Activity as on while nothing could arrive.
The app now publishes whether Android push is configured, keeps both
switches off with an honest subtitle when it is not, and reads a switch as
on only once the relay accepts a registration that carries a push token.

Also signs the Keychain access group with the bundle ID the build actually
uses, makes the Mobile clients page and sign-in prompt name Android's
ongoing activity, documents the relay rollback hazard, and links the new
notification docs from the index.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants