Repository navigation
feat(android): Kotlin/Jetpack Compose backend - #135
Merged
Merged
Conversation
Delete platform/flutter and the Flutter gallery app, drop FlutterHost from the protocol and extension registries, strip dart codegen from the component-schema and extension-API generators, and repoint the mobile build scripts at the upcoming examples/components/android app. All flutter/flutter-profile fingerprints and docs references are removed; rg -il flutter returns zero hits.
Wire-schema codegen (renderKotlin) emits LuiWireSchema.kt; the dev.lui library carries the wire parser, retained store + validation, per-kind Compose renderers, Material-3 theme token mapping, JNI bridge (lui_jni_bridge.c + LuiBridge), and the gallery app shell at examples/components/android (appId dev.lui.components). Screenshot tests and the KotlinHost wiring still follow.
… fixes - JUnit coverage for wire parse / store semantics / extension registry - Paparazzi snapshots (PIXEL_5, light+dark) feeding patch JSON through applyBatch - compileSdk/targetSdk 35: Paparazzi 1.3.5 breaks on android-36's Build layout - settings.gradle :lui projectDir path fix; vector launcher icon - switch/checkbox inline layout; button labels ellipsize instead of wrapping - AutoMirrored icon deprecations
…/android-kotlin # Conflicts: # tooling/generate_component_schema.mjs
…backs
- host_kind gains KotlinHost (protocol, bridge code 4, 'kotlin' host name)
- extension_schemas.json: {android,kotlin} in all/splits profiles;
native-card moves to new web-and-kotlin profile
- fingerprint literals recomputed across split host sources and tests
- lui_element_combine: restore mobile-host branches keyed on KotlinHost
- LuiSplit.kt: split-view/branch/pane/tab Compose extensions with
fingerprint literals, added to split_host_sources drift check
- components_bridge.ml: register "lui_kotlin" callback prefix and the
missing extension_event entry point (shared gap; split/markdown
bridges already had it)
- gallery app registers split components + native-card + gallery-accent
Contributor
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
…kend
Node ids and wire-op id fields are Long throughout. LuiBackend now takes
(onEvent, extensions, icons) constructor args with applyJson(String?),
Content(rootId: Long), and node(id). LuiIcons becomes the LuiIconResolver
fun interface with .or() chaining and a DEFAULT instance, so app-owned
(app:-prefixed) icon names fall through to the app-level resolver.
LuiExtension gains the minimal register(id, fingerprint) { context ->
... } form plus flag/property<T>/emit(name, Map)/Children() helpers, and
extension events carry Map<String, Any?>. LocalLuiThemeDark (Boolean?)
lets hosts override dark mode; null falls back to the system setting.
This keeps the public surface identical to the dev.lui module logseq/chat
vendored in its Android app so switching to includeBuild is a pure delete.
devin-ai-integration Bot
pushed a commit
to logseq/chat
that referenced
this pull request
Oct 6, 2026
tiensonqin
added a commit
to logseq/chat
that referenced
this pull request
Oct 7, 2026
- Bump the lui pin to 264aa9f67da0d0d3e413f9dfe970785e7286f23c (logseq/lui#135; re-pin to the merge SHA once that PR lands). - Rename every FlutterHost reference in shared/ to KotlinHost and android/flutter -> android/kotlin in extension profiles, fingerprints, and all flutter_* identifiers/comments; host code 4 now maps to KotlinHost (matching lui's components_bridge.ml table: 1 Web, 2 SwiftUI, 4 Kotlin, 6 GPUI), so iOS passes host code 2 and Android 4. - shared/: bare 'reactive' calls rewritten to Signal.map — the new lui.ppx expands bare reactive to dyn in element position while ~p:(reactive ..) labeled forms are kept for prop signals. - Delete the vendored android/lui module; :lui now resolves to ../lui/platform/android/lui via module include, the same consumption model as lui's examples/components/android. - LuiDispatch handles the widened LuiEvent sealed class (pointer, context-menu, load events have no chat FFI export and are dropped, matching upstream dispatchToBridge). - Disable :lui test tasks in this build; the backend's paparazzi tests need its standalone AGP 8.6.1 build. Verification: dune build @shared/native/runtest (719 tests), ./gradlew assembleDebug assembleProfile test all green against the real backend; profile APK runs on the chat_test emulator with a fresh x86_64 liblogseq_chat_core.so and renders the auth screen.
tiensonqin
added a commit
to logseq/chat
that referenced
this pull request
Oct 7, 2026
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
Adds a Kotlin / Jetpack Compose backend that renders the LUI wire protocol on Android — a full port of the Apple backend's coverage, plus the
KotlinHostwiring needed for the OCaml side to target it.Note on diff size: this branch is built on top of
devin/remove-flutter(per coordinator sequencing); the Flutter-deletion diff shows here until #134 lands.Kotlin library (
platform/android/lui, packagedev.lui, ~4.9k lines):LuiWire— patch-batch JSON parser (kotlinx.serialization) covering all opsLuiStore/LuiRetainedTree— retained node store; atomic apply (whole batch rejected on any op failure, generation must advance by 1,rootvalidated for exactly one child post-apply)LuiNodeView/LuiRender/LuiKindRules— per-kind Compose renderers + property validation matrixLuiTheme— semantic token mapping (glass, surface-container-high, muted/secondary/accent, spacing/radii/typography) onto a Material3 colorScheme, tuned for both light and darkLuiExtension— extension registry with composable builders + event emit;LuiSplit— split-view/branch/pane/tab port (draggable ratio divider emittingratio-changed, tab bar emittingtab-selected/tab-closed/pane-focused)LuiBridge+lui_jni_bridge.c— JNI bridge under thelui_kotlin_*named-value prefix; same lock discipline as the C bridge (patch callback runs under the OCaml domain lock: copies the JSON out and dispatches UI-side)Codegen:
renderKotlinemitter intooling/generate_component_schema.mjs→LuiWireSchema.kt(kind/property enums + support matrices). Reuses theswiftname field; the one collision (name→propName,Enum.name) is handled in the generator.Host wiring:
KotlinHostinhost_kind(protocol +host_code 4),kotlinhost name,{android,kotlin}in theall/splitsextension profiles,native-cardmoved to a newweb-and-kotlinprofile, fingerprint literals recomputed across all split host sources + tests,lui_element_combinemobile branches restored keyed onKotlinHost.components_bridge.mlregisterslui_kotlinand gains the missingextension_evententry point (shared gap — the split/markdown/navigation bridges already had it).Gallery app (
examples/components/android,appId dev.lui.components, matching the Maestro flow): loadsliblui_components.so, registers split + gallery extensions, renders the tree. CMake falls back to a stub when the OCaml .so is absent.Screenshots (Paparazzi goldens, phone size)
Kind coverage
All 88 wire kinds accepted; rendered: 86. No-op (documented, mirrors other backends):
filePreview— platform-native file preview has no generic Compose equivalentbr— semantic line break, layout is handled by text kindsModal surfaces (dialog/sheet/popover) render via
LuiModalSurface; remaining kinds render through dedicated per-kind composables or the chrome-surface + children path.Verification
dune runtest -j 4: 66/66 green (incl. fingerprint drift check now coveringLuiSplit.kt)node --test tooling/test/*.mjs: 6/6gradle :lui:testDebugUnitTest: 18 JVM wire/store tests greengradle :lui:verifyPaparazziDebug: 4 goldens green (light + dark)gradle -p examples/components/android assembleDebug: gallery APK buildsStubbed / gaps
liblui_components.socan't be built on this VM (lg Android OCaml toolchain absent); CMake compileslui_jni_bridge_stub.cso the app still builds — runtooling/mobile/build_components_android.shon a machine with the toolchain for the real runtimeandroid.os.Buildreflection); bump when Paparazzi supports itFor the chat-Android session
The
dev.luipublic surface is aligned with the module vendored in logseq/chat#37 — theincludeBuild "../../lui/platform/android"swap is a pure delete:project(":lui")atplatform/android/lui(composite include viaincludeBuild)LuiBackend(onEvent: (LuiEvent) -> Unit, extensions: LuiExtensionRegistry, icons: LuiIconResolver);applyJson(raw: String?): Boolean,Content(rootId: Long = -1L)(all roots when negative),node(id: Long)LuiEventsealed class,node: Longids everywhere;LuiEvent.Extension(node, identifier, name, values: Map<String, Any?>)LuiIconResolverfun interface{ icon(name): ImageVector? }+or()+DEFAULT—app:-prefixed names fall through to the app-level resolverLocalLuiThemeDark(Boolean?— null follows the system),LuiTheme(dark, semanticColors, ...) { ... }registry.register(id, fingerprint) { context -> ... },context.string/int/double/flag/property<T>,context.emit(name, Map<String, Any?>),context.Children()LuiBridge.start(libraryName) { patchJson -> backend.applyJson(patchJson) }— optional; callingNativeCore.lui*directly works identicallyLink to Devin session: https://app.devin.ai/sessions/78ea579791324d83bc9dc95abc89fdfe
Open in Devin Desktop: https://app.devin.ai/desktop/session/78ea579791324d83bc9dc95abc89fdfe?variant=devin
Requested by: @tiensonqin