<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/android/app/src, branch master</title>
<subtitle>coinos v3 — bitcoin, Ark, Lightning and nostr wallet (web + Android)</subtitle>
<id>https://git.coinos.io/coinosv3.git/atom?h=master</id>
<link rel='self' href='https://git.coinos.io/coinosv3.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/'/>
<updated>2026-10-13T06:51:13Z</updated>
<entry>
<title>Android 1.14 (code 15): UnifiedPush only where web push can't reach the app. isolabellart asked why Coinos opens a prompt inside ntfy: every launch of the TWA asked whatever UnifiedPush distributor was installed for an endpoint (and asked for notification permission for it), Play Services or not. UnifiedPush is there for phones without Google Play Services (GrapheneOS and the like); with Play Services, web push reaches Chrome — and so the TWA — through Google already, and asking ntfy too only put up its prompt (and would bring every notification twice). The TWA now asks a distributor only when Play Services isn't installed and enabled (Push.playServices; its package named in the manifest's queries for Android 11+), or when it already holds an endpoint — whoever has one has been through the prompt and may be counting on it. The WebView builds (webview, fdroid) are unchanged: web push never reaches a WebView, so UnifiedPush is their only way in either way.</title>
<updated>2026-10-13T06:51:13Z</updated>
<author>
<name>Adam Soltys</name>
<email>asoltys@gmail.com</email>
</author>
<published>2026-10-13T06:51:13Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=90bc4d3d54b91cb9845cda4edc53ed148ae00251'/>
<id>urn:sha1:90bc4d3d54b91cb9845cda4edc53ed148ae00251</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt
</content>
</entry>
<entry>
<title>Android 1.13 (code 14): the app runs immersive. Adam doesn't want Chrome's "v3.coinos.io – To exit full screen, drag from the top and touch the back button", which Chrome shows whenever a page enters its full screen and a page can't turn off. The TWA now launches in sticky-immersive display mode (android-browser-helper's DISPLAY_MODE): the status and navigation bars are hidden, and a swipe in from an edge shows them for a moment. The page — in display-mode fullscreen, which installedApp() counts — fills the screen with the pager without ever asking for the browser's full screen. A Custom Tab's window is the browser's, so the page can't switch this on and off: it's the whole app. Thread clips and YouTube still ask for the browser's full screen, and so still get the notice. 1.12 (code 13) was published without its APK committed here; this sits above it.</title>
<updated>2026-10-13T06:28:40Z</updated>
<author>
<name>Adam Soltys</name>
<email>asoltys@gmail.com</email>
</author>
<published>2026-10-13T06:28:40Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=bcf5f54c928dcc8e1893fe1227b9d8930f2744df'/>
<id>urn:sha1:bcf5f54c928dcc8e1893fe1227b9d8930f2744df</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt
</content>
</entry>
<entry>
<title>Dark mode: your own chat bubbles are a gray (#343a44, light text) instead of the white accent, which glared among the dark bubbles. Android 1.12 (code 13): the TWA no longer names a navigation-bar colour — the helper library always passed one (white when the manifest named none), and Chrome 154's Custom Tab window paints that bar white anyway while the buttons follow the colour asked for, so the colour extras are stripped from the launch intent (both schemes) and the bar is left to the browser and the system. Untested on a device yet; not published.</title>
<updated>2026-10-09T16:46:29Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-09T16:46:29Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=479959cdd77fdd18ca03f63af9b94eae291d0e2a'/>
<id>urn:sha1:479959cdd77fdd18ca03f63af9b94eae291d0e2a</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
</content>
</entry>
<entry>
<title>README rewritten for today's coinos v3; build switches are COINOS_* (HAL_* still accepted); silent-payment leftovers removed</title>
<updated>2026-10-06T05:35:56Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-06T05:35:56Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=f571e46937d423d9ba44154a92aeb4136466daaa'/>
<id>urn:sha1:f571e46937d423d9ba44154a92aeb4136466daaa</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01YRC6aKhwAud231ot9FPmhi
</content>
</entry>
<entry>
<title>Android: TWA activities only in the TWA build, push wakes run the bundled app, the origin fails closed; 1.9 (code 10).</title>
<updated>2026-10-05T06:02:15Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-05T06:02:15Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=e1bc34923bed8fd00c5a3cdacdfb7f102c87d7a3'/>
<id>urn:sha1:e1bc34923bed8fd00c5a3cdacdfb7f102c87d7a3</id>
<content type='text'>
F-Droid review of 1.8: the shared manifest declared the TWA's
LauncherActivity/IntentActivity in the webview and fdroid builds, which
don't compile them (a second, crashing launcher icon). They move to
src/twa/AndroidManifest.xml. WakeJob's off-screen WebView now answers the
origin from assets like MainActivity's, and Assets returns 404 for any
v3.coinos.io path it doesn't carry outside the live API list instead of
fetching it. POST_NOTIFICATIONS is asked only when a UnifiedPush
distributor is installed. The description names the third-party hosts
(DoH, post media).

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>The Android app's system bars follow the system theme, and the page's theme colour follows the app's.</title>
<updated>2026-09-28T06:58:44Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-28T06:58:44Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=318fef4880373fe5c12c366cd8b1c322771714e6'/>
<id>urn:sha1:318fef4880373fe5c12c366cd8b1c322771714e6</id>
<content type='text'>
One dark colour for both bars gave white navigation icons over a light page
under gesture navigation. The wrapper (1.9, versionCode 10) now uses the
page background in light mode and the dark theme colour in dark mode, for
the status bar and the navigation bar alike. The page updates its
theme-color meta when its own theme is applied, so a PWA's status bar and
the wrapper's follow the same switch.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
</content>
</entry>
<entry>
<title>Android: a reproducible F-Droid flavor with the web app bundled inside.</title>
<updated>2026-09-17T15:19:41Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-17T15:19:41Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=1bd3e2613641a8d2bc63c262a58cad126632459f'/>
<id>urn:sha1:1bd3e2613641a8d2bc63c262a58cad126632459f</id>
<content type='text'>
The Android builds so far were shells around the live site: a verified
APK proved nothing about the code that ran. The new `fdroid` flavor
(io.coinos.fdroid) shares the WebView shell and carries the built web
app in its assets. The page still lives at the v3.coinos.io origin — same
storage, links and gift URLs — but every file under it is answered from
the APK (Assets.java); only the site's live API paths reach the network,
and the bundled page registers no service worker (HAL_NO_SW=1).

Two clean unsigned builds give the same APK. COINOS_UNSIGNED=1 builds
without the keystore; Bun is pinned via packageManager; the Gradle wrapper
properties pin 8.12. fdroid/io.coinos.fdroid.yml is the fdroiddata recipe
(checksum-pinned Bun in sudo, prebuild web build, scandelete, our signing
key allowed for the Binaries match) and docs/REPRODUCIBLE.md the how-to.
Verified on a phone: the bundled build cold-starts in airplane mode.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01JuwQqNWtDJcxPWtEm9134z
</content>
</entry>
<entry>
<title>End to end over the real network, and the advice now fits the app it's in.</title>
<updated>2026-09-13T00:06:14Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-13T00:06:14Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=ce61a0f20a2c535e0c69f59836a17f31a3d88468'/>
<id>urn:sha1:ce61a0f20a2c535e0c69f59836a17f31a3d88468</id>
<content type='text'>
A push sent to ntfy.sh reached a killed app on a real phone:

  17:02:21.259  ntfy.sh → the phone's ntfy → broadcast to io.coinos.webview
  17:02:21.308  app process started (it had been killed)
  17:02:21.458  wake: job started
  17:02:22.926  wake: finishing (page said so)   — 1.7s, and "You were paid"

One thing that took two goes: `adb shell am force-stop` marks a package
STOPPED, and Android delivers no broadcasts to a stopped package unless the
sender sets FLAG_INCLUDE_STOPPED_PACKAGES — which `am broadcast` does and a
real distributor rightly doesn't. The first attempt looked like a failure and
was a test artefact; `am kill`, which is what Android itself does to a
backgrounded app, delivers fine.

And the WebView build can now say something useful about push. It was
inheriting the browser's line — "your browser has no push service, try
Firefox" — which is nonsense inside a WebView that IS the app. It asks the
host what's actually there and says: install a push app like ntfy (nothing
installed), or wait a moment and reopen (a distributor that hasn't answered
yet). Payments and messages still arrive whenever coinos is open, which is
the part that matters most to someone reading an apology.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
</content>
</entry>
<entry>
<title>Don't trust the first distributor on the phone — three were installed.</title>
<updated>2026-09-12T23:59:10Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-12T23:59:10Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=8fe3d0d28d5f15643c1737809c5f1773cb8f9f94'/>
<id>urn:sha1:8fe3d0d28d5f15643c1737809c5f1773cb8f9f94</id>
<content type='text'>
ntfy went onto a real phone and the handshake ran: our app broadcast REGISTER,
ntfy answered with an endpoint (https://ntfy.sh/up…), the app stored it, and
the web app took it off the launch URL and kept it. That was the link I'd said
was unproven.

It also turned up something the sandbox never would. The phone had THREE apps
answering the distributor action — Element's and Yakihonne's internal ones
alongside ntfy — and both of those exist to serve their own app, not anyone
else's. Picking the first would have registered with a distributor that will
never hand us an endpoint, and there'd be nothing to show for it.

The spec's answer is to let the user choose, and this app has no settings
screen to choose in, so it works through them instead: ask one, and if no
endpoint has arrived by the next launch, ask the next. A refusal moves on
immediately. Whoever answers is remembered for good.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
</content>
</entry>
<entry>
<title>Proven on a phone: woken from a cold stop in four seconds.</title>
<updated>2026-09-12T22:52:54Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-12T22:52:54Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=7fcad2ddfbaae862d150430b27674606ebca299e'/>
<id>urn:sha1:7fcad2ddfbaae862d150430b27674606ebca299e</id>
<content type='text'>
The whole chain, on real hardware, with the shipping APK installed:

  am broadcast → PushReceiver → expedited job → off-screen WebView →
  v3.coinos.io/?wake= → the wallet restored from its vault → CoinosHost.done()

  15:49:52.341  schedule -&gt; 1
  15:49:52.390  job started          (49ms later)
  15:49:56.602  finishing (page said so)

Four and a bit seconds from a force-stopped app to the page reporting it had
dealt with the payload — not the timeout, the page. A notify payload raised
the right notification too ("You were paid", "New message"), and the wallet
it woke was one imported into the WebView build minutes earlier, restored
from the vault in a process that had been killed.

The payload no longer goes to logcat. It carries the user's push, and
anything with ADB can read that.

Unproven, and the one link I couldn't close here: the distributor handshake.
There's no UnifiedPush distributor on this phone, so every push above was a
broadcast I sent by hand — which is exactly what a distributor sends, but
it isn't the same as watching ntfy do it.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
</content>
</entry>
</feed>
