
Server-driven UI for Compose Multiplatform — a maintained, production-hardened fork of Cash App Redwood. Render Kotlin Compose UI on Android/iOS from JS bundles.
Server-driven UI for Compose Multiplatform. Render Kotlin Compose UI on Android and iOS from JavaScript bundles delivered over the network — ship UI updates without an app-store release.
▶ Try it in your browser, zero install: the Keliver Portal playground — the visual editor with a live canvas, 60+ widgets, data bindings with mock previews, and one-click Export Kotlin (the output is real code).
Status: public on Maven Central —
0.3.0. AddmavenCentral()and depend ondev.keliver:keliver-host:0.3.0(host) /dev.keliver:keliver-guest:0.3.0(guest) — no GitHub PAT, no extra repo.keliver-materialis a Compose/Material3-parity widget library (76 widgets — seedocs/WIDGET_PARITY.md) so you build server-driven screens without authoring a schema.0.3.0adds Icon + ListItem, DropdownMenu/SegmentedButtonRow in the portal, and publishesdev.keliver:portal-sql(the SDUI data layer). Start a new app in seconds with keliver-init (below). Pre-1.0, API may still evolve; the wire format is stable within a line.
Keliver is a Cash App Redwood fork.
Upstream Redwood is great but is no longer being actively developed by
Cash App. Keliver picks up the foundation — redwood-runtime,
redwood-compose, redwood-treehouse, redwood-treehouse-host,
redwood-treehouse-host-composeui — and ships it under the dev.keliver
namespace with:
Already running on upstream Redwood? See
docs/MIGRATION_FROM_REDWOOD.md— the migration is mostly a scripted rename.
docs/KNOWN_BUGS.md
for the full list — 11 of 12 documented gotchas now have a Keliver-side
mitigation as of 1.0.0-caliclan.3.You write Kotlin Compose UI in a separate guest module. That code
compiles to JavaScript via Kotlin/JS and gets bundled into a
.zipline file. Your host app downloads the bundle at runtime,
executes it in Zipline (a
QuickJS wrapper), and Keliver's protocol translates the guest's
widget tree into real Compose widgets on the host platform —
Android (Jetpack Compose), iOS (Compose Multiplatform), or anywhere
CMP runs.
The result: change a screen, push a new bundle, every user has it in seconds without an app-store cycle.
A typical guest screen looks like normal Compose:
class QuotesScreen : Screen {
@Composable override fun Content(navigator: Navigator) {
val provider = HostQuotesProviderBridge.instance
var quotes by remember { mutableStateOf<List<Quote>?>(null) }
LaunchedEffect(provider) {
quotes = provider?.getQuotes(filter = null) ?: emptyList()
}
Column(modifier = Modifier.fillMaxSize().padding(16, 16, 16, 16)) {
Text("Select Quote", style = SchemaTextStyle.TitleLarge)
LazyColumn {
quotes.orEmpty().forEach { q ->
LazyItem { QuoteCard(q) }
}
}
}
}
}The host wires services that the guest can call via Zipline RPC:
class QuotesAppSpec : TreehouseApp.Spec<SduiAppService>() {
override val name = "quotes"
override val manifestUrl = quotesManifestUrlFlow.asStateFlow()
override val serializersModule = SduiSerializersModule
private lateinit var quotesProvider: RealHostQuotesProvider
private lateinit var quoteNavigator: RealHostQuoteNavigator
override suspend fun bindServices(treehouseApp, zipline) {
// bindWithTimeout turns the silent bind-hang failure shape into
// an actionable timeout — see KNOWN_BUGS.md U1.
bindWithTimeout {
zipline.bind<HostConsole>("console", retain(StatusCraftHostConsole()))
}
requireSerializerOf<Quote>() // bind-time pre-flight — KNOWN_BUGS U3.
quotesProvider = RealHostQuotesProvider(
source = quotesSource,
scope = appScope,
ziplineDispatcher = treehouseApp.dispatchers.zipline,
)
bindWithTimeout {
zipline.bind<HostQuotesProvider>("quotes", quotesProvider)
}
quoteNavigator = RealHostQuoteNavigator(
scope = appScope,
uiDispatcher = treehouseApp.dispatchers.ui, // KNOWN_BUGS U8.
callback = onQuoteSelected,
)
zipline.bind<HostQuoteNavigator>("quote-nav", quoteNavigator)
}
override fun create(zipline: Zipline) = zipline.take<SduiAppService>("app")
}Beyond the library, this repo ships the Keliver Portal: a visual editor,
any code editor, and AI agents all edit the same screen document, with the
.kt file in git as the source of truth.
EDIT (any of) SHIP
Portal UI ─┐ Publish → compile the canonical
IDE / vim ─┼─► one UiDocument ──► Kotlin → Ed25519-sign → versioned,
AI (MCP) ─┘ (.kt in git = truth) capability-gated bundle → devices
│ verify the signature
┌────────────┼───────────────┐
web preview dev devices surgical .kt write-back
(mock rows, (live overlay, (comments survive edits)
fidelity) ~1s mirror)
screens/*.kt; edits
in your IDE appear live in the browser and on devices — no restart.scripts/keliver-dev.sh.See docs/PORTAL_USAGE.md (workflow) and
docs/PORTAL_V2_COMPLETE.md (architecture).
The playground is this editor running
serverlessly in wasm.
Good fit:
Less good fit:
A typical app uses Keliver for a slice of its surface area, not the whole app. The DevoStatus integration uses it for two screens out of ~10.
New project in 60 seconds — grab keliver-portal-tools from the
latest release, then:
export PATH="$PWD/keliver-portal-tools/bin:$PATH"
keliver-init Acme && cd acme
./gradlew compileKotlinJs # your screens, type-checked against keliver 0.3.0
keliver-portal . # visual editor + live preview → http://localhost:8096keliver-init scaffolds a standalone project whose screens are real Kotlin
Compose against the published dev.keliver:* artifacts — no keliver checkout.
Or just poke the hosted playground: http://keliver.me/keliver/.
The sample/ directory is a complete, runnable starter — a
Workouts screen (an API call + a host database + live search) built in both
architecture styles, with a green unit test. Copy it and rename, or run it
first to see the loop.
When the public OSS launch lands (see PUBLIC_LAUNCH_ROADMAP.md) a proper docs site will replace these.
docs/KNOWN_BUGS.md is the punch list. As of
1.0.0-caliclan.3:
1.0.0-caliclan.4 via keliver-image).@MainThread codegen) — only entry without a
Keliver-side fix; root cause is in Zipline's compiler plugin, tracked
at cashapp/zipline#1825.
Workaround pattern is well-established.Keliver is pre-1.0 — pin an exact dev.keliver:* version and expect
breaking API changes across 0.x. The public surface of every published module
is tracked by the binary-compatibility-validator on each build, so no API change
ships by accident. See docs/API_STABILITY.md.
ROADMAP.md — adopter-facing forward-looking plan.
Distinct from PUBLIC_LAUNCH_ROADMAP.md
which tracks the one-time work to take this repo public.
Apache 2.0 — same as upstream Cash App Redwood. See LICENSE.txt.
Keliver is built on Cash App Redwood and Zipline. The original Redwood team did the hard architectural work; this fork picks up maintenance and adds the production-hardening that surfaces in real-world integrations.
Server-driven UI for Compose Multiplatform. Render Kotlin Compose UI on Android and iOS from JavaScript bundles delivered over the network — ship UI updates without an app-store release.
▶ Try it in your browser, zero install: the Keliver Portal playground — the visual editor with a live canvas, 60+ widgets, data bindings with mock previews, and one-click Export Kotlin (the output is real code).
Status: public on Maven Central —
0.3.0. AddmavenCentral()and depend ondev.keliver:keliver-host:0.3.0(host) /dev.keliver:keliver-guest:0.3.0(guest) — no GitHub PAT, no extra repo.keliver-materialis a Compose/Material3-parity widget library (76 widgets — seedocs/WIDGET_PARITY.md) so you build server-driven screens without authoring a schema.0.3.0adds Icon + ListItem, DropdownMenu/SegmentedButtonRow in the portal, and publishesdev.keliver:portal-sql(the SDUI data layer). Start a new app in seconds with keliver-init (below). Pre-1.0, API may still evolve; the wire format is stable within a line.
Keliver is a Cash App Redwood fork.
Upstream Redwood is great but is no longer being actively developed by
Cash App. Keliver picks up the foundation — redwood-runtime,
redwood-compose, redwood-treehouse, redwood-treehouse-host,
redwood-treehouse-host-composeui — and ships it under the dev.keliver
namespace with:
Already running on upstream Redwood? See
docs/MIGRATION_FROM_REDWOOD.md— the migration is mostly a scripted rename.
docs/KNOWN_BUGS.md
for the full list — 11 of 12 documented gotchas now have a Keliver-side
mitigation as of 1.0.0-caliclan.3.You write Kotlin Compose UI in a separate guest module. That code
compiles to JavaScript via Kotlin/JS and gets bundled into a
.zipline file. Your host app downloads the bundle at runtime,
executes it in Zipline (a
QuickJS wrapper), and Keliver's protocol translates the guest's
widget tree into real Compose widgets on the host platform —
Android (Jetpack Compose), iOS (Compose Multiplatform), or anywhere
CMP runs.
The result: change a screen, push a new bundle, every user has it in seconds without an app-store cycle.
A typical guest screen looks like normal Compose:
class QuotesScreen : Screen {
@Composable override fun Content(navigator: Navigator) {
val provider = HostQuotesProviderBridge.instance
var quotes by remember { mutableStateOf<List<Quote>?>(null) }
LaunchedEffect(provider) {
quotes = provider?.getQuotes(filter = null) ?: emptyList()
}
Column(modifier = Modifier.fillMaxSize().padding(16, 16, 16, 16)) {
Text("Select Quote", style = SchemaTextStyle.TitleLarge)
LazyColumn {
quotes.orEmpty().forEach { q ->
LazyItem { QuoteCard(q) }
}
}
}
}
}The host wires services that the guest can call via Zipline RPC:
class QuotesAppSpec : TreehouseApp.Spec<SduiAppService>() {
override val name = "quotes"
override val manifestUrl = quotesManifestUrlFlow.asStateFlow()
override val serializersModule = SduiSerializersModule
private lateinit var quotesProvider: RealHostQuotesProvider
private lateinit var quoteNavigator: RealHostQuoteNavigator
override suspend fun bindServices(treehouseApp, zipline) {
// bindWithTimeout turns the silent bind-hang failure shape into
// an actionable timeout — see KNOWN_BUGS.md U1.
bindWithTimeout {
zipline.bind<HostConsole>("console", retain(StatusCraftHostConsole()))
}
requireSerializerOf<Quote>() // bind-time pre-flight — KNOWN_BUGS U3.
quotesProvider = RealHostQuotesProvider(
source = quotesSource,
scope = appScope,
ziplineDispatcher = treehouseApp.dispatchers.zipline,
)
bindWithTimeout {
zipline.bind<HostQuotesProvider>("quotes", quotesProvider)
}
quoteNavigator = RealHostQuoteNavigator(
scope = appScope,
uiDispatcher = treehouseApp.dispatchers.ui, // KNOWN_BUGS U8.
callback = onQuoteSelected,
)
zipline.bind<HostQuoteNavigator>("quote-nav", quoteNavigator)
}
override fun create(zipline: Zipline) = zipline.take<SduiAppService>("app")
}Beyond the library, this repo ships the Keliver Portal: a visual editor,
any code editor, and AI agents all edit the same screen document, with the
.kt file in git as the source of truth.
EDIT (any of) SHIP
Portal UI ─┐ Publish → compile the canonical
IDE / vim ─┼─► one UiDocument ──► Kotlin → Ed25519-sign → versioned,
AI (MCP) ─┘ (.kt in git = truth) capability-gated bundle → devices
│ verify the signature
┌────────────┼───────────────┐
web preview dev devices surgical .kt write-back
(mock rows, (live overlay, (comments survive edits)
fidelity) ~1s mirror)
screens/*.kt; edits
in your IDE appear live in the browser and on devices — no restart.scripts/keliver-dev.sh.See docs/PORTAL_USAGE.md (workflow) and
docs/PORTAL_V2_COMPLETE.md (architecture).
The playground is this editor running
serverlessly in wasm.
Good fit:
Less good fit:
A typical app uses Keliver for a slice of its surface area, not the whole app. The DevoStatus integration uses it for two screens out of ~10.
New project in 60 seconds — grab keliver-portal-tools from the
latest release, then:
export PATH="$PWD/keliver-portal-tools/bin:$PATH"
keliver-init Acme && cd acme
./gradlew compileKotlinJs # your screens, type-checked against keliver 0.3.0
keliver-portal . # visual editor + live preview → http://localhost:8096keliver-init scaffolds a standalone project whose screens are real Kotlin
Compose against the published dev.keliver:* artifacts — no keliver checkout.
Or just poke the hosted playground: http://keliver.me/keliver/.
The sample/ directory is a complete, runnable starter — a
Workouts screen (an API call + a host database + live search) built in both
architecture styles, with a green unit test. Copy it and rename, or run it
first to see the loop.
When the public OSS launch lands (see PUBLIC_LAUNCH_ROADMAP.md) a proper docs site will replace these.
docs/KNOWN_BUGS.md is the punch list. As of
1.0.0-caliclan.3:
1.0.0-caliclan.4 via keliver-image).@MainThread codegen) — only entry without a
Keliver-side fix; root cause is in Zipline's compiler plugin, tracked
at cashapp/zipline#1825.
Workaround pattern is well-established.Keliver is pre-1.0 — pin an exact dev.keliver:* version and expect
breaking API changes across 0.x. The public surface of every published module
is tracked by the binary-compatibility-validator on each build, so no API change
ships by accident. See docs/API_STABILITY.md.
ROADMAP.md — adopter-facing forward-looking plan.
Distinct from PUBLIC_LAUNCH_ROADMAP.md
which tracks the one-time work to take this repo public.
Apache 2.0 — same as upstream Cash App Redwood. See LICENSE.txt.
Keliver is built on Cash App Redwood and Zipline. The original Redwood team did the hard architectural work; this fork picks up maintenance and adds the production-hardening that surfaces in real-world integrations.