OXYGENHUB
# af9f2e514c72 oxygenhub-android / CHANGELOG.md
9.2 KiB · Markdown · last changed Review a proposal from the phone: approve, request changes or comment from its Reviews section (refs #4) 8 days ago renderedsource blame history raw

Changelog

Unreleased

  • Review a proposal from the phone (#4): an open proposal's Reviews section takes a review: Approve, Request changes or Comment, with what you think (needed to ask for changes or to comment), for a phone signed in to change things. An approval from the phone is not sealed, as it holds no key of yours: where a repository needs sealed approvals it does not count yet.
  • A proposal's files and their diffs (#4): a proposal's Changes section opens Files changed: each file it changes, folding open to its diff (old and new line numbers, added and removed lines tinted), as its page on the hub shows them, from the hub's GET …/proposals/N/diff (OxygenHub 0.9.10). The change screen and this one share the same diff list.
  • Clone a repository from the phone (#3): the repository screen's Clone action shows its oxy clone command, selectable, with a button that copies it.

0.1.0 (2026-10-27)

  • Alerts from the hub (#16): what your inbox on the hub is told (builds broken and fixed, reviews, mentions…) also reaches the phone through Firebase Cloud Messaging, sealed to an X25519 key the phone made (kept wrapped by the Android Keystore), so Google carries only ciphertext; the app opens it with the Rust core (flow_core::alert, as the hub seals it) and shows it, and a tap opens its link. Each signed-in session registers the phone (PUT /user/push); signing out ends it. Firebase starts by hand from google-services.json (the release job's firebase-config secret), with no Google Gradle plugin and only firebase-messaging; a build without it takes no alerts. The hub sends once it runs on GCP.

  • The app says when it is out of date (#7): Account shows the app's version and, signed in to oxygenhub.io, whether a later full release of andymccall/oxygenhub-android is out, with its APK to download; opening the file updates the app, since every release is signed with the same key. Pre-releases are not offered.

  • Releases built by OxygenFlow (#7): the app's version lives in gradle.properties (oxygenhub.version; the version code follows from it), and when it changes the pipeline's release job builds a release APK signed with the app's own key (a PKCS12 keystore and its password, the repository's android-keystore and android-keystore-password secrets, mounted as files), checks it with apksigner and keeps it with its SHA256SUMS for a release on oxygenhub.io. scripts/release.sh does the same locally with the key's files. The key's SHA-256 certificate fingerprint is B0:99:57:36:44:42:AF:B2:27:28:B7:EF:93:F8:23:2D:F1:41:17:DA:50:28:6D:45:93:04:0F:9C:9F:A2:2A:48; a release-signed app replaces a debug build only after uninstalling it.

  • The phone runner on OxygenFlow 0.5.0 (#22): the app pins OxygenFlow 0.5.0 and oxy 0.8.0, so the phone's runner takes the idle claim that fits its memory (oxygenflow#92), stops a WASM job promptly (#93), offers the images it holds (#94) and reports where a job's time went (#35). Reinstall the app to move the runner from 0.3.1.

  • CI on OxygenFlow (#19): every push builds the app and runs its unit tests in OxygenFlow's Android image, with no network, and keeps a JUnit report (junit.xml, the hub's Tests tab) and JaCoCo coverage (coverage.xml, the Coverage tab); scripts/ci.sh is the job. Debug builds record unit-test coverage (./gradlew :app:createDebugUnitTestCoverageReport). The screenshot tests stay local: their references change with the clock, so are not kept.

  • Account and organisation pages: an owner's page shows who they are (avatar, name, when they joined; an organisation's description and members, each opening their own page), their open project boards and the repositories of theirs you can read, newest first. Opened from a repository's top bar, from a member, and from hub links to /{owner} or /orgs/{name}. From GET /users/{name}, else GET /orgs/{name}.

  • Off charge, the runner gives a minute's notice (#8, oxygenflow#76/#77): taken off charge, the phone claims nothing more, finishes a job the hub expects to end within a minute and hands back any other to another runner, its checkpoint saved so the next attempt resumes where it stopped. Leaving Wi-Fi still hands back at once. Pins OxygenFlow's main (checkpoints, leave_within).

  • Diffs on a change (#21): each file a change touched shows its diff in the app: context, removed and added lines with their old and new numbers, tinted, long lines wrapped, gaps between hunks marked. A small change (up to five files, 400 lines) opens every diff; a larger one starts folded and a file's row opens it. The icon on the row still opens the file at the change. From the hub's change API (hub #320); an older hub sends no lines, and the row says so.

  • The phone as an OxygenFlow runner (#8, first part): Account → OxygenFlow runner → Offer this phone as a runner asks the hub for a one-time token (POST /api/v1/user/runner-token) and registers the phone as a runner for your own repositories, labelled android, offering the WASM tier. The runner is OxygenFlow's own, built into the app through rust-core (OxygenFlow is a subrepo, oxygenflow/, and rust-core's oxy-core is the one it pins), running WASM modules in a thread with wasmtime. It runs in a foreground service with a notification, only while the phone is charging on Wi-Fi (an unmetered network); coming off charge or off Wi-Fi drains it (it takes nothing more and finishes its job). Start and Stop on the card. The app is larger: the runner and wasmtime are about 29 MB per architecture.

  • Proposals on the dashboard (#18): Waiting for your review (open proposals by others in your repositories you have not reviewed) and Your proposals (your open ones and drafts), as the hub's dashboard lists them, each opening the proposal screen where it can be landed. From the hub's GET /api/v1/user/proposals; a hub without it shows neither.

  • Land proposals from the phone (#17): a repository has Issues and Proposals tabs; proposals open (with drafts), landed, closed or all, each saying whether it is ready to land, what stands in the way, or that it will land when its checks pass. A proposal shows the landing card (Land into the target, after a confirmation; land when its checks pass, on or off; the hub's blockers in its own words, and its reason when it refuses), then its checks (failures first, each opening its run), sealed approvals and asked reviewers, its changes and description. Landing needs repo:write, which sign-in now asks for: a phone signed in before this reads only, and says so with a button to sign in again rather than failing.

  • A proper launcher icon (#15): OxygenHub's mark, the hub's two joined rings in its cyan-to-blue gradient, on the app's night navy with a soft glow, as an adaptive icon (any launcher's mask, inside the safe zone) with a monochrome layer for themed icons; the default robot's bitmaps are gone. A rendered preview is in docs/screenshots/launcher-icon.png.

  • Your avatar (#13): the dashboard's greeting and the Account screen show the signed-in account's avatar from the hub's /avatar/{name}: an uploaded picture as it is, or the hub's identicon, read from its SVG (hue and cells) and drawn natively, so no SVG library; the initial while there is neither, or the hub cannot be reached. Fetched without the token (it is public), kept for the run, and refreshed with the dashboard.

  • The keyboard no longer comes up on its own on Repositories (#12): opening Repositories gave the filter focus and brought the keyboard up on every visit (tapping the navigation bar gives the app's view focus, which Compose hands to the screen's first focusable). The filter now takes focus only when it is touched, or reached from a keyboard. Checked on a Pixel 10 Pro Fold.

  • Sign in, the hub's API, navigation and foldable layouts (#2): sign in with the hub's device flow, as oh auth login does (a code to approve on the hub's /device page, with the password and second factor there, never in the app), against oxygenhub.io or any hub; the token sealed with a Keystore key, kept out of backups, and revoked on the hub when signing out. A hand-written Kotlin client for the hub's API: the user, repositories, a repository's issues, the inbox and marking it read. A dashboard (greeting, recent repositories, unread count), repositories with their issues side by side from a medium width up (a foldable open, a tablet), and the inbox with a badge; a navigation bar on a phone and a rail when wider; light and dark, in dynamic colour or the hub's own palette; loading, empty and error states with pull to refresh. The placeholder login (username and password) and tabs are gone. Unit tests for the client (against fixtures from hub-api's types) and the view models; README screenshots rendered from Compose previews without an emulator.

  • The project set up properly (#1): the application id and package are io.oxygenhub.android (permanent), minSdk 31 (Android 12), the Gradle and build caches no longer tracked, rust-core/ part of the repository (it was a nested, empty Git repository, so it had never been committed), and the Rust core built for arm64-v8a and x86_64 and its Kotlin bindings generated by the build itself (buildRustCore, generateRustBindings) instead of being committed. The pipeline has no jobs until OxygenFlow has an Android image (oxygenflow#19).