Feature/aurora build variant - #387
Open
den3000 wants to merge 20 commits into
Open
Conversation
The only thing in shared:core that needed Decompose was componentCoroutineScope, and every one of its callers is a tv component. Moving it into :clients📺core:ui lets shared:core stop declaring decompose and essenty at all — the tablet graph was carrying a dependency that has no linux target and that it never used. The tv modules that call it already depend on :clients📺core:ui and declare decompose themselves, so only the import line changes there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Koin keeps the parameters of the graph currently being created on a stack and
matches them BY TYPE, ignoring qualifiers. The api clients build their HttpClient
in their constructor, i.e. lazily, inside whichever graph reaches them first — and
if that graph was created as parametersOf(route.event, route.roomName), then
get<String>(named("ApiKey")) hands back the graph's String parameter instead of the
qualified singleton. The request then goes out as
GET http://localhost/api/v1/users Authorization: Bearer <room name>
and the poisoned client is cached in the singleton for the rest of the process.
Wrap both values in ApiConfig: a dedicated type cannot collide with the String
parameters of any graph. The tv client shares the same HttpClientProvider, so it
moves over too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add expect painterResource/vectorResource to core:ui with signatures identical to the compose-resources ones, so switching a call site is a change of import and nothing else. Android and iOS delegate straight back to compose-resources, so behaviour on both is unchanged. The point is the seam: a platform whose resource loader does not handle every format we ship can now supply its own implementation without touching the eleven call sites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aurora packages compose resources flat: <qualifier>/<file> becomes <qualifier>_<file>, with no package namespace. Several modules shipping an identically named values/strings.xml therefore collapse into a single values_strings.commonMain.cvr, and whichever module's Res reaches it first reads someone else's bytes at its own offsets. Renaming the files costs nothing anywhere else: the generated Res takes string keys from <string name="...">, not from the file name, so no call site changes. core:ui keeps its own name — it is already the only one — and drawable names were unique across modules to begin with. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`-PbuildVariant=aurora` swaps three things at once in settings.gradle.kts: the
plugin versions (the Aurora fork of Compose plus the fork's build/deploy plugins),
the repository list (a local maven fork whose path lives in local.properties), and
the set of included modules — only the tablet, because backend/tv/smsrouter pull
AGP and upstream Compose through build-logic and the two Compose plugins cannot
coexist in one invocation.
Each Aurora module builds from its own build.aurora.gradle.kts (linux targets, fork
dependencies) so the upstream build files stay untouched. The convention plugins
are deliberately not reused there — they are what drags AGP in.
Note the version binding has to sit in pluginManagement { plugins { } }: the
top-level plugins block of a settings script applies to the Settings object, and
declaring compose there actually applies it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Neither Napier nor compose-ui-tooling-preview publishes linux targets, and both are referenced from common code. Rather than guard every call site, squat the package: a source directory carrying the library's own package name, wired in with kotlin.srcDir from the aurora build file only. Callers and their imports are untouched, and the upstream builds never see these files. The Napier stub has to live in exactly one module — two modules declaring the same package would collide at klib link time. shared:core is that module, and core:domain and core:ui re-export it with api(project(...)), so the tablet modules get it transitively. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Linking a Kotlin/Native linux binary does not fit in the default Kotlin daemon heap. Set it before the first link rather than after the first OOM. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four of these are one-liners because the seams were put in earlier: the ktor engine
becomes Curl, getCurrentLanguageCode is hardcoded to ru, and SettingsStore gets an
in-memory map — multiplatform-settings has no linux target, so the selected room does
not survive a restart yet (ak-shared-preferences exists in the fork; TODO on the file).
Two are not:
toLocalisedString is written out by hand. kotlinx-datetime's byUnicodePattern refuses
locale-dependent directives on Kotlin/Native ("The directive 'MMMM' is locale-dependent"),
and delegating to the common toFormattedString throws — which on Aurora is worse than it
sounds, because the fork swallows an exception thrown from a composable and rolls the
frame back, so a crash presents as a screen that simply does not change.
painterResource/vectorResource resolve the drawable bytes themselves and dispatch on the
byte signature: raster magic bytes to a BitmapPainter, a <svg substring to Skia's SVGDOM,
and anything else as Android vector XML through a vendored AOSP parser (Apache 2.0 header
kept) with a hand-rolled DOM, since javax.xml is not available. That path keeps Icon(tint)
and intrinsic dp sizes; the fork's own loader renders SVG only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
isDebug is Platform.isDebugBinary, which the Aurora variant makes false — it links a release executable. Worth remembering: that same flag also selects API_URL_RELEASE. TimeReceiver has neither Android's time-change broadcast nor NSTimer to lean on, so it just ticks a coroutine once a minute into CurrentTimeHolder. The date and time pickers drop to Material3 directly, since calf publishes no linux target — on Android and iOS calf draws the same Material3 widgets underneath. Colours are mapped onto LocalCustomColorsPalette so they match the rest of the app. With this the aurora variant compiles: -PbuildVariant=aurora :clients:tablet:composeApp:compileKotlinLinuxX64 is green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main() initialises logging and koin and hands off to application { AppRoot() } — the
fork creates the window itself, so there is no Window or singleWindowApplication here.
The root LocalViewModelStoreOwner comes from AppRoot, since Aurora has no Activity or
ViewController to provide one, and there are no Firebase topics: no push updates.
Linking needs the fork's own flags (-Xoverride-konan-properties, or the link fails
against the sysroot) plus Qt5Network on top of what cmpLinkerOpts already adds, because
the http client is ktor-curl.
Resources need staging. aurora-build packages preparedResources of this module only —
it does not follow project dependencies — and Aurora flattens what it does package into
<qualifier>_<file> with no package namespace. stageAuroraResources copies the
composeResources of all six tablet modules into one directory for customDirectory to
hand over, with duplicatesStrategy = FAIL so a future name clash is a build error rather
than a wrong .cvr read at runtime.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
auroraBuild describes the package itself: app id, the Internet permission, maliit-glib
for the on-screen keyboard, and the four icon sizes. auroraDevices describes where it
goes — host from -P, then local.properties, then a default — and which rpms to pick up.
runReleaseOnDevice and buildReleasePipeline both come from the fork's plugins and are
registered in afterEvaluate, so chaining them needs tasks.matching{}.configureEach;
tasks.named() at that point throws. That lives in aurora-tasks.gradle.kts to keep the
build file about the build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things kept the device silent no matter how many log calls the code had. LoggerInitializer only installs an Antilog when isDebug, and on linux that is Platform.isDebugBinary — false, because the Aurora variant links a release executable. Install it directly from the linux entry point instead. (The same flag also picks API_URL_RELEASE over the debug one, which is worth remembering when a build talks to the wrong backend.) Even then the interesting lines were at risk: the Napier stub prints to stdout, and under journald or a pipe stdout is fully buffered, so whatever was printed right before a crash never made it out. Flush after every line — fflush(null) from a linux actual, since the stub itself lives in common code. With that working, log the two things worth watching from the start: how many organizers came back from the backend (an empty list is the first symptom of an api client built with the wrong config) and the raw key events reaching the organizer field, since maliit input is still an open question. core:ui gains the napier dependency for the view side; under Aurora it resolves to the stub from shared:core. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The overlay now also shows the window size the system handed over and the density and font scale it set. On Aurora there is no other way to see what a device actually gave the app, and the alternative is guessing at scale from screenshots — which is exactly what cost several rounds on the device. Float.toString would print "1.4666667" and there is no String.format in common code, hence the local two-decimal helper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
indexSelectRoom is -1 until the room saved in settings turns up in the list that came back from the backend, and roomList[-1] takes the composable down with it. On Aurora that reads as "navigation did not work": the fork swallows an exception thrown from a composable and rolls the frame back, so the screen simply does not change and nothing lands in the log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The window arrives portrait on every Aurora device seen so far, and the tablet UI is laid out horizontally, so it gets squashed. ForcedLandscape rotates the content 90° when the window really is portrait: a landscape-sized box (H×W) centred and rotated about its own centre ends up exactly W×H. On Android and iOS forceLandscape is false and the whole thing is a no-op. It has to wrap all three layers, not just the root: the fork renders Popup and dialog<> as separate scenes in the untouched window, so nothing applied at the root reaches them. That makes the organizer dropdown position itself. The popup provider now returns IntOffset.Zero and the list is placed by hand, opening upward from the field with a gap measured in px so a substituted density cannot shift it. The popup layer is explicitly stretched to fill the window — a popup window is sized to its content by default, and the offset list would fall outside its own window and be clipped, which is exactly what it did on Android before this line existed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aurora's status bar covers the top of the window. Padding the whole window from the entry point would put the gap in the portrait coordinate space, and after the rotation it shows up as a stripe down the side. Applying it inside the rotated content puts it where it belongs, and painting the background before the padding keeps the strip dark. Zero on Android and iOS, where systemBarsPadding already handles it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fork builds its Compose scene as ComposeScene(density = Density(window.contentScale)), and contentScale comes from the system with the window — the app cannot set it. So fix the dp space instead: ScaledUiDensity substitutes LocalDensity with short_side_px / uiScaleBaseline, and the layout occupies the same fraction of the screen whatever scale the system handed over. fontScale is pinned to 1 so the system font scale does not multiply on top of ours. Baseline is 800 dp — a 10" tablet, and exactly what the reference Android tablet reports (2560x1600 at density 2.0). Android and iOS set it to 0, which makes the wrapper a no-op. Applied in the same three layers as the rotation, for the same reason. The version overlay stays outside it and gains the computed density, so what the system gave and what we made of it are both visible at once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three small things added up to a visible frame. AppTheme's bare Surface defaulted to colorScheme.surface, which is lighter than the page background. The sticky-header room card carried an opaque background about 50dp wider than its own rounded shape, clipping the slots underneath — the background belongs on the panel, not on the card. And the release button's Material elevation drew a shadow on top of its own border. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What the variant switch does, what local.properties and Docker it needs, the three commands worth knowing, and — the part that matters — each Aurora-specific piece with the reason it exists, since none of them are obvious from the code alone. The scale section records all three baseline candidates with what each costs, because the measurements say the devices differ by ~3%, not the 20-30% the first screenshots suggested: the baseline is buying room for text the fork draws wider, not correcting a density mismatch. That choice may well be revisited. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
den3000
force-pushed
the
feature/aurora-build-variant
branch
from
July 31, 2026 12:04
b23540b to
b340fc3
Compare
The explanations of why the Aurora variant is built the way it is do not belong next to every line they touch — a per-module build file does not need to re-explain why the convention plugins are avoided, and the version overlay is scaffolding that comes out once the scale baseline is settled. So the code keeps only what cannot be inferred from it (the flat resource packing, which is the one comment left in a build file), and everything else — the variant switch, the stubs, linking, packaging, resources, the platform APIs and the scale measurements — moves into clients/AURORA.md. clients/README.md keeps a short pointer. The doc also gains an explicit list of what is not finished: keyboard input, in-memory settings, the hardcoded locale, the naive time ticker, no FCM, no kiosk mode, the wrong icons, and the unexplained dropdown position. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
No description provided.