Repository navigation
Phase 1: talk to a server and sign in - #1
Merged
Merged
Conversation
CI asked for testGenericDebugUnitTest and testGplayDebugUnitTest by name, which only the app module has, so the tests of every library and JVM module never ran there, the design system's contrast test among them; verifyRoborazziGenericDebug skipped library screenshots the same way. Each module now registers alohaUnitTests and alohaScreenshotTests over whatever its debug variants are called, and CI runs those two. Android library unit tests run on the JUnit Platform: JUnit 5 for plain tests, the vintage engine for the JUnit 4 tests Robolectric needs, with the JDK export Robolectric's file-descriptor interceptor requires. A skeleton module without tests does not fail the run. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
OkHttp 5 with MockWebServer and okhttp-tls, Room behind a convention plugin that exports schemas, DataStore, Custom Tabs, Turbine and the coroutines test library, with their checksums and signing keys. Three signing keys that no key server serves are listed as ignored, so those artifacts verify by checksum. detekt no longer counts early-return guard clauses, treats a flat table of single-expression when entries as a lookup, and does not count the members a class overrides from an interface. REUSE covers the fixture corpus and the Room schemas. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Every entity of the Apple model as an immutable, serialisable domain type: accounts, statuses and their parts, media, notifications, the instance, filters, and the Nextcloud Social extensions. Enums keep an Unknown case and never throw; an unknown visibility fails closed. Ports ContentClassifier, CharacterCount (grapheme clusters, URLs at the server's flat rate), TimelineKey and friends, ServerLimits and ServerCapabilities, which now latch hls_url, reactions and quotes on first sighting because nothing announces them. Ids compare by length, then lexically; they never pass through a number. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
|
Debug APK (generic flavor) for 9862439: https://github.com/AlohaSocial/Android/actions/runs/36604324266/artifacts/11050179993 — kept for 5 days. |
AndyScherzinger
force-pushed
the
feature/phase-1-server
branch
from
September 29, 2026 11:22
71557f2 to
a175d78
Compare
Plain OkHttp and kotlinx.serialization, no Retrofit: every account has its own API base, requests are typed ApiRequest values whose decoder maps an internal wire DTO to the domain type, and lossy decoding needs the raw row count. DTOs never leave the module. Decoding is lenient where servers are: numeric ids, "" and null URLs (Nextcloud Social sends avatar ""), string booleans, dates in four shapes, lossy arrays that record what they drop, wrong-shaped objects as absent. Errors are typed; a 401 is a state, not a failure. A Link cursor is followed only on the API base's own origin, a path segment that would resolve as . or .. is refused before anything is sent, and a read answered 429 with a short Retry-After is retried once; writes never are. A per-host token bucket is shared by all accounts. TLS trusts the system first and a certificate the person accepted only for that exact host; a client certificate comes from the KeyChain. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger
force-pushed
the
feature/phase-1-server
branch
10 times, most recently
from
September 29, 2026 16:37
44dbb5b to
632eed9
Compare
Nextcloud Social serves the Mastodon API under its app path and at the root only where the administrator installed the rewrite rules. The probe tries the RFC 8414 issuer (accepted only on the typed host), the domain root, the app path, the pretty app path and a typed path, all at once; the lowest rank that answers with a non-empty instance domain wins, and a fast worse answer never beats a slower better one. NodeInfo is read from its root directory as the cross-check of the software. An untrusted certificate is reported with its chain so the person can decide, and an unexpected 404 may trigger a new probe at most hourly. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The 153 exchanges captured from the dev instance, and mastodon.social's public discovery documents (its contact account replaced by a fictional one), served by MockWebServer as Nextcloud Social with and without the rewrite rules, with the Nextcloud connection, as stock Mastodon, as the Mastodon core only, rate-limited, and with a malformed entity. Every captured body decodes through the endpoint factory that asks for it with nothing dropped, and a new fixture fails the build until it is mapped. The server facts the client works around are pinned, so a re-capture after a server update shows which workaround to revisit. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
accounts.db holds the accounts and the per-server OAuth registrations, never a secret. Tokens, client secrets and app passwords live in one AES-GCM blob under a device-bound AndroidKeyStore key, StrongBox where available, excluded from backup. A vault that cannot be decrypted, or accounts restored without their tokens, leave the accounts needing a new sign-in; nothing is deleted. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Registers the app once per server with both redirect URIs, opens the authorisation page with PKCE S256 and a state, and exchanges the code without a scope; a spent registration is forgotten so the next attempt registers again. The sign-in in flight lives in the vault rather than in saved state, so it survives the process dying while the browser tab is open and never reaches a Bundle. Metadata endpoints on another origin are ignored, OAuth requests never follow a redirect, and a callback is matched by its state before anything else in it is read. Nextcloud Social answers 500 from verify_credentials until a new account's avatar cache job has run, so such an account is created from /oauth/userinfo with its profile pending. A 401 on an authenticated request marks the account for a new sign-in. Capability detection probes the 4.x routes and the Nextcloud Social extensions, takes Pixelfed's features document at its word, and reads the Nextcloud theme from the Nextcloud root. On every launch each account's API base is checked: one that stopped answering is probed for anew at most hourly, and capabilities are detected again when the base moved or they are older than a day. The screens see only the data layer's own results: what a lookup found, a certificate to decide on, and why a step failed. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The terms, then the server field, the probe, the server's card and the browser; failures say what was tried and offer another try, a hand-typed API address and a note for the administrator. The note links Nextcloud Social's web-server rules instead of embedding them, because those files are AGPL-3.0-or-later. An untrusted certificate is shown with its fingerprint and can be trusted for that server only, and a client certificate from the system KeyChain can be chosen for it. Status and errors are announced, the Advanced section says whether it is expanded, and right to left is recorded as it renders. The OAuth callback lands on a UI-less activity that hands it to an in-process inbox and brings the app forward, so no callback URI travels in an Intent. The verified App Link is used where this install is verified, the custom scheme otherwise. The shell shows a banner when an account needs a new sign-in; nothing is lost. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The emulator smoke had never completed. After the release builds the hosted runner had 3.6 GB of disk left, and the emulator refuses to start without 7.4 GB for its userdata partition; the job now removes the preinstalled .NET, GHC and CodeQL toolchains, which it never uses. With the emulator up, the macrobenchmark step compiled the benchmark variant with R8 while the emulator was running, and the runner stopped responding. The benchmark APKs are now built before the emulator starts, so the connected run only installs and measures. A failed flow now leaves the screen and its view hierarchy as an artifact; that is how "System UI isn't responding" over the app showed up as the cause of the flow failing on its first screen. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Every destination carries its selection in shape as well as colour, outlined at rest and filled when selected. Material's Slideshow glyph draws the same frame in both variants, so a selected Shorts tab still looked outlined. Shorts now uses AmpStories, stacked portrait cards like the Apple app's symbol, whose filled variant is a solid card. A screenshot with Shorts selected keeps the bar from regressing. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
AndyScherzinger
force-pushed
the
feature/phase-1-server
branch
from
September 29, 2026 17:21
632eed9 to
590d80b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 1: the app can talk to a server. A person types a server, the app finds its Mastodon API, whether Nextcloud Social sits at the domain root with the rewrite rules or under its app path without them, and signs in with OAuth (PKCE S256, verified App Link with a custom-scheme fallback). The account is stored with its token in a Keystore-backed vault, and the app learns what the server can do.
core:model: the domain model, withContentClassifier,CharacterCount,TimelineKeyandServerCapabilities.core:network: a plain OkHttp + kotlinx.serialization client with no Retrofit, because every account has its own base.Linkcursor is followed only on the API base's origin, and dot segments in paths are refused.Retry-Afteris retried once.stateis checked before anything else in it.verify_credentialscannot describe yet falls back to/oauth/userinfo.featuresand the Nextcloud theme.accounts.db(Room, schemas exported) plus an AES-GCM token vault with an AndroidKeyStore key, StrongBox where the device has it. A lost vault marks accountsneedsReauth, and a 401 does the same; nothing is ever deleted.core:testing:feature:signin:contrib/webserverrules instead of embedding them, because those files are AGPL-3.0-or-later.http://with a message that says why (https is required), not as an address it can't read.main; a screenshot with Shorts selected guards it.ui.yml): it had never completed, so it gets its own commit, separate from the phase's code. The job frees disk space so the emulator can start at all, builds the benchmark APKs before the emulator runs, and keeps a screenshot and view dump when a flow fails.alohaUnitTestsandalohaScreenshotTestsaggregates. Before this, CI ran only the app module's flavoured tasks, so the library tests never ran there.Exported component:
OAuthRedirectActivityis exported with anautoVerifyApp Link filter, because the browser must be able to deliver the OAuth callback. It draws nothing, hands the URI to an in-process inbox and finishes. It hastaskAffinity="", so no callback URI travels in an Intent to the main activity.New dependencies, each with SHA-256 and PGP entries in
gradle/verification-metadata.xml:No new permission.
APK size: the release APK grows from 1.11 MB to 1.99 MB. R8 and resource shrinking are on, and no keep rule is wider than the Navigation keys' serializers. The growth is real code, mostly the model, the networking and serialization, Room and DataStore; OkHttp's public-suffix list alone is 130 KB.
config/size-baseline.jsonmoves to the new size in the sign-in commit, where the growth enters the app.Docs: the "On Android" part of
docs/02-server-api.mdnow covers the client, errors, rate limiting and TLS, how the API base is found, the sign-in rules and the Android differences in capability detection. Each part is in the commit that brought in the behaviour it describes.Size: about 18.5k lines of Kotlin and XML, far above the thousand-line guideline, plus about 22k lines of fixtures. The phase is one vertical slice whose acceptance is sign-in end to end, so it is split into nine commits that each build on their own and can be reviewed one at a time.
Deferred:
OAuthClient.revokeis implemented and tested but unused until a sign-out control exists.Test plan
alohaUnitTestsis green across all modules, including:api/andwrites/fixture through its endpoint factory, with nothing dropped, and a check that fails when a new fixture is left unmapped;ServerFactsTest, which pins the server behaviours the client relies on;toString.alohaScreenshotTestsis green: every sign-in step in light, dark, tablet, 200 % font and right to left.detekt ktlintCheck lint alohaArchitectureCheckis green, andassemblepasses for both flavours plus:benchmark:assemble.http://nextcloud.local), through the opt-inLiveInstanceTest. SetALOHA_LIVE_SERVER,ALOHA_LIVE_USERandALOHA_LIVE_PASSWORD, then run./gradlew :core:testing:testDebugUnitTest --tests '*LiveInstanceTest*'./index.php/apps/social/.#00679e.invalid code_verifier.unsupported code_challenge_method.Unauthorised..localonly over multicast DNS:aloha.socialpublishes its asset links.OAuthRedirectActivity, which hands it to the app in the same task and leaves no activity behind. One token exchange runs, capabilities are detected, and the shell opens.TopTaskTrackerat debug level, outside the app's control.ui.yml) passes on this branch (run 36592272873): the Maestro flow on the release builds of both flavours, then the startup macrobenchmark. The flow used to expect the home screen at launch, which this PR moves behind sign-in. It now covers terms, then sign-in, and checks that the terms stay accepted after a restart. It waits out the "System UI isn't responding" dialog that a freshly booted CI emulator shows over the app.Checklist
detekt ktlintCheck lint alohaArchitectureCheckgreen, no baseline grownpaneTitleon new screens, 200 % font previewstrings.xmlwith translator commentsdocs/updated where behaviour changedAssisted-by:)