• v2.0.79-dev 646e0abd84

    Phandroid v2.0.79-dev Pre-release

    thoralf released this 2026-10-15 22:31:56 +02:00 | 0 commits to main since this release

    Changed

    • Merged upstream Phanpy — 2 commit(s); no Android-fork code changes (see CHANGELOG.phanpy.md for upstream detail):
      • b4804d90 Fix notification dot color
      • 36f9f259 Try remove @babel/plugin-transform-runtime this time
    Downloads
  • v2.0.78-dev 16cd2ebb2a

    Phandroid v2.0.78-dev Pre-release

    thoralf released this 2026-10-15 04:30:55 +02:00 | 4 commits to main since this release

    Changed

    • Merged upstream Phanpy — 1 commit(s); no Android-fork code changes (see CHANGELOG.phanpy.md for upstream detail):
      • 2fb534db Experiment: pagination dots for boosts carousel
    Downloads
  • v2.0.77-dev accfbd3148

    Phandroid v2.0.77-dev Pre-release

    thoralf released this 2026-10-14 10:32:00 +02:00 | 7 commits to main since this release

    Changed

    • Merged upstream Phanpy — 4 commit(s); no Android-fork code changes (see CHANGELOG.phanpy.md for upstream detail):
      • 56a8e3fc Add one more 'View Quotes' menu
      • fa9ba209 Reduce gap between boosts in carousel
      • ee4bd4e5 Try macroTransform
      • 1e4e7d06 i18n updates (fi-FI,tr-TR) (#1613)
    Downloads
  • v2.0.76-dev c4eb5f48da

    Phandroid v2.0.76-dev Pre-release

    thoralf released this 2026-10-13 10:30:54 +02:00 | 13 commits to main since this release

    Changed

    • Merged upstream Phanpy — 6 commit(s); no Android-fork code changes (see CHANGELOG.phanpy.md for upstream detail):
      • 6dcd33cf Fix style for long polls
      • 058c5c6a less dashes
      • 7180c006 Migrate to action-crowdin-contributors
      • d7b3dd7f Update README.md
      • dc6b6c9a Use more defaults, rebuild table when yml changes
      • 7f83a066 Update README.md
    Downloads
  • v2.0.75-dev 1ad19484cc

    Phandroid v2.0.75-dev Pre-release

    thoralf released this 2026-10-12 16:30:48 +02:00 | 21 commits to main since this release

    Changed

    • Merged upstream Phanpy — 6 commit(s); no Android-fork code changes (see CHANGELOG.phanpy.md for upstream detail):
      • d69189f0 Save is in
      • 96c8e5ee Style changes for polls, QPs, thread badges, alt badges
      • e41b2c70 Fix nested QPs not updating
      • 0842a013 Simplify translation block styles
      • 5c912dc9 i18n updates (ar-SA,ca-ES,cs-CZ,de-DE,eo-UY,es-ES,eu-ES,fa-IR,fi-FI,fr-FR,gl-ES,he-IL,hu-HU,it-IT,ja-JP,kab,ko-KR,lt-LT,nb-NO,nl-NL,oc-FR,pl-PL,pt-BR,pt-PT,ru-RU,th-TH,tok,tr-TR,uk-UA,zh-CN,zh-TW) (#1612)
      • c3281eff Sharper underlines
    Downloads
  • v2.0.24 89d18c8aa7

    thoralf released this 2026-10-11 23:17:52 +02:00 | 4484 commits to main since this release

    Same fixes as dev 2.0.74 — the findings of the 2026-10-11 whole-project review.

    Fixed

    • Camera capture works again. The native camera component never forwarded the
      ref that the composer clicks, so "Take photo or video" and the toolbar button
      did nothing at all — while the listing advertised the feature. The component is
      patched in place now instead of overridden, so it also produces upstream's current
      attachment shape and cannot silently drift behind upstream again.
    • No more source maps in the APK. Upstream builds them for crash-report
      symbolication, which Phandroid does not ship; Capacitor copied them into the
      assets anyway, so every APK carried its own complete unminified source. They are
      excluded now and the release verifies on the artifact that none remain.
    • Sharing into the running app works, and a share no longer repeats itself. The
      event that signals a share to an already-running app was dispatched on iOS only,
      and the share intent stayed the activity's current intent for its whole life — so
      a warm share did nothing, while every WebView reload re-opened the composer with
      the same old share (by then without its image).
    • A share into a logged-out app waits instead of vanishing. It is kept until you
      log in, and a toast says why nothing happened yet.
    • The status bar follows the theme. It was set once at startup, so a light system
      with a dark app theme gave same-coloured icons on a same-coloured header.
    • Multi-account push no longer kills the other accounts. Turning push off for one
      account unregistered the endpoint every account shares, an endpoint rotation healed
      only the current account, and logging out left the preference behind — so a later
      login silently re-enabled push.
    • Account export: a cancelled share sheet is no longer reported as "Export
      failed"
      , and the cache file is cleaned up by the next export instead of racing
      the app you shared to.
    • German F-Droid clients get a "What's New" text (English content under a German
      heading) instead of an empty update note.

    Added

    • A crash shows a recovery screen instead of a blank app, with a Reload button and
      the stack behind "Error details" — the app has no telemetry, so a screenshot of that
      screen is the only diagnostic you can send.
    • Failures you can act on now say so: camera failure, an unreadable shared image
      and failed push (re-)subscribes show a toast instead of only a console message that
      release builds strip.

    Security

    • The FileProvider no longer exposes all of external storage — only the two
      app-private roots the camera and the export actually use.
    • A shared item's name can no longer make Phandroid delete arbitrary files in its
      private directory; only names that look like the plugin's own image copy are removed.

    Changed

    • Pipeline hardening (no user-visible effect): no secrets while apt-get installs
      fdroidserver, concurrency guards so overlapping runs cannot corrupt each other, job
      timeouts, the signer fingerprint checked before publishing, failure reporting for the
      iPhanpy watcher, and correct release-note labelling.
    Downloads
  • v2.0.74-dev 20e3819396

    Phandroid v2.0.74-dev Pre-release

    thoralf released this 2026-10-11 18:18:29 +02:00 | 29 commits to main since this release

    Changed

    • CI hardening batch (six independent fixes, pipeline-only): the
      upstream-sync's instance guard moved to the first step — the public instance
      no longer clones the repo and fetches github.com 4x/day just to refuse; both
      build workflows assert that all four fork workflows exist in
      .forgejo/workflows/ (Forgejo prefers it over .github/workflows, and
      losing it would silently activate upstream's unguarded workflows);
      iphanpy-watch reports its failures as issues like every other workflow (it
      exits non-zero at three points nobody would ever hear about); every job now
      carries a timeout-minutes (the single runner hosts the keys and is also the
      work machine — a hung job blocked everything, invisibly); the failure-dedup
      in notify-failure.sh paginates beyond the first 50 open issues (same bug
      class the release-pruning script fixed); and the fdroid config's sdk_path
      comes from $ANDROID_HOME via a template placeholder instead of a hardcoded
      runner-image path.

    Added

    • German F-Droid clients now get a "What's New" text. The promote wrote
      en-US/changelogs/<versionCode>.txt only, so de-DE had none at all
      (measured live in the index for both apps, while the German title and
      descriptions are maintained) — German users saw an empty update note.
      CHANGELOG.md is English-only and translating every release is not
      feasible, so de-DE now gets the English text under an honest German
      heading ("Änderungen (englisch):"), fitted to a smaller budget because
      fdroidserver cuts whatsNew at 500 characters. Verified by running the
      real block against the real changelog: en-US 256, de-DE 281 characters.
    • A crash now shows a recovery screen instead of a blank app. A render error
      used to replace the whole app tree with nothing — and since release builds
      strip every console.* and the WebView ships with debugging disabled, the
      error vanished without a trace; the only way out was killing the app. <App />
      is now wrapped in a fork error boundary: a "Something broke" screen with a
      Reload button, and the stack behind an expandable "Error details" — visible in
      release builds too, deliberately, because the app has no telemetry: a
      screenshot of that screen is the only diagnostic a user can send. Verified by
      injecting a render crash in the test environment.
    • Failures that matter to the user now say so. Camera capture failure,
      an unreadable shared image, and failed push (re-)subscribes used to be
      console.error only — stripped in release builds, i.e. invisible exactly
      where they happen. They now also show a toast (a cancelled camera pick stays
      silent, that is not an error).
    • Diagnosis builds can keep console output. PHANPY_KEEP_CONSOLE=1 npm run build skips Vite's removeConsole, so console output survives for on-device
      debugging via chrome://inspect (combined with temporarily enabling
      webContentsDebuggingEnabled, which should be reverted afterwards — see the
      handover §13.6). Release builds are unchanged: verified 0 console.* in the
      normal bundle, output present with the flag set.

    Fixed

    • The status bar now follows the theme instead of being set once. It was
      Style.Default at mount and never again: Phanpy's own light/dark/auto setting
      is independent of the system's, and AndroidManifest lists uiMode in
      configChanges, so neither an in-app theme switch nor a system theme change
      recreated the activity — a light system with a dark app theme (or the reverse)
      gave same-coloured icons on a same-coloured header, which the fork's own frosted
      deck header makes plainly visible. The style is now derived from the effective
      theme (app setting, falling back to the system scheme in auto mode) and
      re-applied on both sources of change: a prefers-color-scheme listener and a
      class-attribute observer on <html> — the attribute settings.jsx toggles, so
      no upstream file needed a patch. Style.Dark/Style.Light are set explicitly,
      and their meaning was verified in the plugin's Java source
      (setAppearanceLightStatusBars(!style.equals("DARK")), i.e. DARK = light icons)
      rather than assumed, since inverting them would be worse than not setting them.
      Four Playwright tests cover dark, light, runtime switching and auto; all four
      fail against the old behaviour.
    • PHANDROID_CUSTOMIZATIONS.md tells the truth again. The fork's entry
      document had drifted into fiction in nine places: it referenced a
      check-oauth-scheme.sh that does not exist, claimed every change was gated on
      Capacitor.isNativePlatform() (the timeline cap, error boundary, share-intent
      cleanup and build switch are not), described push with an enablePush() that
      does not exist and a VAPID restriction that is deliberately absent, called push
      auto-enabled on login (it is opt-in through the settings), claimed the pipeline
      lives in a separate private repository (it lives here, in
      .forgejo/workflows/), gave an incomplete one-time setup that fails on the
      first merge (two of three merge drivers were missing), had an unpaired code
      fence that rendered half the document as code, and an override-file inventory
      missing three files plus the whole "modified upstream files" list being 5
      files short. All corrected; the file now also carries the rule that every new
      patch site and override file is registered in the same commit.
    • Sharing into the running app works, and a share no longer repeats itself.
      Two bugs in the share target (#378): (1) the sendIntentReceived event that
      signals a share into an already-running app is dispatched by the send-intent
      plugin only on iOS — on Android the warm-start listener never fired, sharing
      into the running app did nothing until it was killed. MainActivity.onNewIntent
      now dispatches the event itself and calls setIntent(). (2) AOSP does not
      update the activity's intent on its own, so the SEND intent stayed current for
      the activity's whole life — every WebView reload (OAuth redirect, process
      restore, crash) read the same old share and re-opened the composer with it, by
      then without its image (the plugin's file copy had been deleted). The web layer
      now consumes the intent after handing a share to the composer, via a small
      ShareIntentPlugin that replaces it with a plain ACTION_MAIN (send-intent's
      own finish() closes the whole app, which is why it exists). Covered by
      Playwright tests for the cold start, the warm start, and the reload.
    • Multi-account push no longer kills other accounts. The UnifiedPush endpoint
      is app-wide — exactly one for all accounts, like the browser's single
      PushSubscription — but the native paths had none of upstream's multi-account
      protections: turning push off for one account unregistered the endpoint and
      silently killed delivery for every other account; an endpoint rotation healed
      only the current account, leaving the others' instances pushing into the dead
      endpoint; and logout/account-removal never cleaned up (upstream's
      removeAccountPushSettings read the browser-shaped settings, found nothing,
      returned early) — the removed account's pref survived, and a re-login silently
      re-subscribed push without the user ever enabling it. The per-account prefs now
      mirror upstream's pushSubscriptionSettings shape (one localStorage blob keyed
      by account NS, endpoint recorded): push is only unregistered when no other
      account uses the endpoint, rotations re-subscribe every subscribed account with
      its own instance, and account removal drops the pref, removes the instance-side
      subscription while the token still works, and unregisters only as the last
      user — where "user" means an account that still exists, so prefs left behind
      by removals from before this fix cannot keep the endpoint registered
      forever. Covered by Playwright tests against two logged-in accounts. Still open
      (needs a product decision, see the handover): notifications carry no account
      identity — a tap always opens the notifications of whichever account is
      current, and per-notification routing is not built.
    • Overlapping pipeline runs can no longer corrupt each other. release.yml,
      promote.yml and upstream-sync.yml are workflow_dispatch-driven, and Forgejo
      only manages concurrency automatically for push/pull_request events — two
      simultaneous release dispatches would prune each other's release assets, two
      promotes would force-push the F-Droid pages branch concurrently (one index
      lost), and an overlapping sync's push would fail as non-fast-forward, losing
      its merge. All three workflows now carry explicit concurrency groups with
      cancel-in-progress: false — nothing is ever cancelled mid-signing or
      mid-merge; overlapping runs queue instead.
    • The F-Droid deploy no longer runs apt-get with all the release secrets in the
      environment.
      promote.yml's F-Droid step installed fdroidserver via apt-get
      (unpinned, from the Debian mirror, postinst scripts as root) while carrying the
      F-Droid keystore, both of its passwords AND the write token for
      code.umrath.dev — exactly the mixing that release.yml's security model
      refuses for the APK key. The step is now split into four secret-scoped steps:
      install (no secrets), prepare (git only, code.umrath.dev token), update
      (fdroidserver with the F-Droid secrets only — a compromised fdroidserver can no
      longer write to code.umrath.dev and replace the APK users install), deploy
      (git only). A failed promote now also trips the leftover-dir part of the leak
      check, because the working tree survives mid-pipeline failures until the
      deploy step's cleanup — key material itself is still shredded by the update
      step's trap in every case. The real fix, pinning fdroidserver in the runner
      image, stays open (handover §13.5).
    • The web app's service worker no longer runs in the native app. Upstream's
      PWA service worker registered unguarded in the WebView: it laid a second,
      app-update-surviving cache layer (Workbox + IndexedDB, coupled to Workbox
      internals) over assets that already ship inside the APK, ran push and
      share-target handlers that can never fire there, and a broken SW state
      has no user-facing way out in a native app — "clear app data" also wipes
      every account and token. VitePWA injects the registration at build time,
      so a guard in index.html was impossible; a post-order transform strips
      the injected script from the app build (verified in the built HTML, and
      the full Playwright suite runs against the stripped build). Installs that
      already registered a worker are cleaned up too: stripping the
      registration does not unregister one that exists, so on native the app
      now unregisters any leftover worker and drops its caches once at startup
      — otherwise the second cache layer would survive for the rest of that
      install's life. The WebView's own HTTP cache still covers remote images.
    • The share-intent delete path only accepts names the plugin's image copy
      could have.
      An image/* share still let any installed app make
      Phandroid overwrite and then delete a file of any single-segment name in
      the app's private files dir (confused deputy — harmless today only
      because nothing fork-owned lives there). The name check now sits on the
      DELETE, not on the read: attaching is safe because the content is
      validated (a blob that is not an image is dropped, the sender's declared
      MIME is never trusted) and the path is confined to the app's own files
      dir — while deleting is the operation a foreign app must not get to
      choose. So an image attaches under any name, and only copies whose name
      looks like an image are removed; anything else stays behind as bounded
      litter. A malformed share (URI without a display name — the plugin's Java
      side throws on those) now shows a toast instead of silently doing
      nothing. Covered by Playwright tests for all three cases (extensionless
      name attaches, image name attaches and is cleaned up, foreign-chosen name
      attaches and is NOT deleted).
    • The FileProvider no longer exposes all of external storage.
      file_paths.xml mapped <external-path path="."/> — everything on the
      shared storage — plus the cache dir. Any JS path handing a foreign
      file:// to @capacitor/share would have been a cross-storage file read
      with Phandroid's UID. What the fork actually needs was derived from the
      plugin sources rather than guessed: cache-path for getCacheDir()
      (save-file.js exports via Directory.Cache, plus CameraPlugin's own cache
      temp file) and external-files-path scoped to Pictures/ for
      getExternalFilesDir(DIRECTORY_PICTURES), which is where
      CameraUtils.createImageFile() puts the photo that CameraPlugin then hands
      through THIS app's FileProvider. An earlier, narrower version of this fix
      omitted the second entry and would have broken camera capture with an
      IllegalArgumentException at runtime — invisible to the web-layer tests,
      which is why the file now documents which plugin needs which root. Both
      roots are the app's own private directories, not shared storage.
      @capacitor/filesystem (plain file:// URIs) and send-intent
      (Uri.fromFile) need no entry. Camera capture and export are on the
      device re-test list (PHANDROID_CUSTOMIZATIONS.md).
    • A share into a logged-out app waits instead of vanishing. The composer
      only renders when logged in, so a share while logged out deleted the
      image copy and set shared data nobody would ever see — lost without a
      word. The share is now kept (the intent is not consumed): after logging
      in it lands in the composer — the OAuth reload re-runs the handler — and
      until then a toast says why nothing happened. Covered by a Playwright
      test for the logged-out state.
    • Export: a cancelled share sheet is no longer an error, and the cache file
      is cleaned up.
      The system share sheet runs as a full activity, so
      cancelling usually resolves cleanly — but OEM sheets that are dialogs
      reject with "Share canceled", and the export showed "Export failed" with
      the modal stuck open even though the file was written fine. The cancel
      case is now swallowed; everything else still throws. The cache file is
      cleaned up by the NEXT export instead of immediately: the sheet resolves
      as soon as the chooser returns, which is exactly when the chosen app
      starts reading the file, so deleting right away would race the receiver
      ("Save to Files", Nextcloud and mail all read lazily) and could hand it a
      dead URI. At most one stale file sits in the cache dir, and it carries no
      tokens either way (the export strips the accessToken).
    • Notification taps no longer depend on a hashCode for their pending intent.
      Android caches PendingIntents by (requestCode, Intent); with all
      notifications carrying the same extras this was harmless, but the moment
      they gain notification-specific paths (per-account routing, still open), a
      hashCode-derived requestCode would make one notification's extras overwrite
      another's. The requestCode is now a constant, and the comment claiming
      hashCode collisions could silently replace pushes — while the code did
      exactly that hashCode thing — is corrected: the notification's identity is
      the (tag, id) pair, and it always was.
    • Release changelogs are labelled correctly now. The automatic upstream-merge
      entry was inserted directly after the ## [Unreleased] heading — in front of
      whatever hand-written entries sat there — so those entries were silently
      absorbed into the release while its header claimed "no Android-fork code
      changes" (2.0.66 shipped the whole test infrastructure under that claim, 2.0.69
      the whatsNew tests; the contradiction reached users through the release notes
      and the F-Droid "What's New" text). The new phandroid-changelog-bump.py
      inserts the section behind the Unreleased block and moves hand-written entries
      into the release, dropping the "no Android-fork code changes" claim when they
      are present. It also refuses to run when the [Unreleased] heading is missing
      or the version section already exists — the awk it replaced exited 0 in both
      cases and shipped releases without their changelog entry. Unit-tested in the
      test gate (10 tests, alongside the whatsnew converter's).
    • No more source maps in the shipped APK. Upstream builds with
      sourcemap: true (Rollbar symbolication, which Phandroid does not ship), and
      Capacitor copies dist/ into the APK's assets verbatim — 5.7 MB of .map files
      (55 % of the web payload, the complete unminified source including comments) were
      riding along in every signed APK. ignoreAssetsPattern now excludes *.map at
      the aapt level, which also covers local builds, and the release verification
      additionally asserts on the signed artifact itself that it contains no .map
      entries — so the claim is machine-checked, not assumed.
    • Native camera capture works again. The camera capture had been dead in the app:
      the fork's override of camera-capture-input never forwarded the ref that
      compose.jsx clicks, so the "Take photo or video" menu entry and toolbar button did
      nothing (Preact hands ref to function components as a plain prop — the override
      ignored it). The override had also missed three upstream refactors since 2.0.66
      (the fileData/fileName attachment shape, the Mobile Safari MIME filter, the
      hidden 1×1 styling), which is the structural problem: an override drifts silently.
      The override is now gone. Upstream's own camera-capture-input.jsx is patched in
      place (// PHANDROID:) with a native branch: a hidden trigger button — with the
      forwarded ref — that opens the camera via @capacitor/camera and hands the photo
      to the composer in upstream's attachment shape. Upstream refactors of the file now
      merge through instead of drifting, and a Playwright test clicks the composer's
      camera menu entry against the bridge and asserts the Camera.getPhoto call and the
      resulting attachment, so this class of breakage cannot ship silently again.
    Downloads
  • v2.0.73-dev e65d3cd186

    Phandroid v2.0.73-dev Pre-release

    thoralf released this 2026-10-10 10:30:03 +02:00 | 78 commits to main since this release

    Changed

    • Merged upstream Phanpy — 3 commit(s); no Android-fork code changes (see CHANGELOG.phanpy.md for upstream detail):
      • 96917632 Hide excessive br(s) AFTER .quote-inline
      • ca95af2d Fixes for ALT folder tabs
      • dbb0e55e Adjust indents
    Downloads
  • v2.0.72-dev 2b5f2a7085

    Phandroid v2.0.72-dev Pre-release

    thoralf released this 2026-10-09 22:30:28 +02:00 | 83 commits to main since this release

    Changed

    • Merged upstream Phanpy — 4 commit(s); no Android-fork code changes (see CHANGELOG.phanpy.md for upstream detail):
      • 362918d3 It's time to change things a little
      • 2f21e3a3 Better grids for sections in Settings
      • 3a32083d Experiment with this rotated ALT badge
      • b5a0f8cb Slight adjustments
    Downloads
  • v2.0.71-dev 702ed68656

    Phandroid v2.0.71-dev Pre-release

    thoralf released this 2026-10-09 04:41:00 +02:00 | 89 commits to main since this release

    Changed

    • Merged upstream Phanpy — 2 commit(s); no Android-fork code changes (see CHANGELOG.phanpy.md for upstream detail):
      • f588710d More ignoreModifiers, ughh
      • 4179c986 [autofix.ci] apply automated fixes
    Downloads