Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion compose/api/compose.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ final class app.tinyui.schema/ComponentSchema { // app.tinyui.schema/ComponentSc
}

final class app.tinyui/BuildManifest { // app.tinyui/BuildManifest|null[0]
constructor <init>(kotlin.collections/List<kotlin/String>, kotlin.collections/List<kotlin/String>, kotlin.collections/Map<kotlin/String, kotlin/String>, kotlin.collections/Map<kotlin/String, kotlin/String>, kotlin/String, kotlin/String, kotlin/String, kotlin/String, kotlin/String, kotlin/Int, kotlin.collections/Map<kotlin/String, kotlin/String>, kotlin/String? = ...) // app.tinyui/BuildManifest.<init>|<init>(kotlin.collections.List<kotlin.String>;kotlin.collections.List<kotlin.String>;kotlin.collections.Map<kotlin.String,kotlin.String>;kotlin.collections.Map<kotlin.String,kotlin.String>;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.Int;kotlin.collections.Map<kotlin.String,kotlin.String>;kotlin.String?){}[0]
constructor <init>(kotlin.collections/List<kotlin/String>, kotlin.collections/List<kotlin/String>, kotlin.collections/Map<kotlin/String, kotlin/String>, kotlin.collections/Map<kotlin/String, kotlin/String>, kotlin/String, kotlin/String, kotlin/String, kotlin/String, kotlin/String, kotlin/Int, kotlin.collections/Map<kotlin/String, kotlin/String>, kotlin/String = ..., kotlin.collections/Map<kotlin/String, app.tinyui/PageRequires> = ..., kotlin/String? = ...) // app.tinyui/BuildManifest.<init>|<init>(kotlin.collections.List<kotlin.String>;kotlin.collections.List<kotlin.String>;kotlin.collections.Map<kotlin.String,kotlin.String>;kotlin.collections.Map<kotlin.String,kotlin.String>;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.Int;kotlin.collections.Map<kotlin.String,kotlin.String>;kotlin.String;kotlin.collections.Map<kotlin.String,app.tinyui.PageRequires>;kotlin.String?){}[0]

final val buildIds // app.tinyui/BuildManifest.buildIds|{}buildIds[0]
final fun <get-buildIds>(): kotlin.collections/Map<kotlin/String, kotlin/String> // app.tinyui/BuildManifest.buildIds.<get-buildIds>|<get-buildIds>(){}[0]
Expand All @@ -270,8 +270,12 @@ final class app.tinyui/BuildManifest { // app.tinyui/BuildManifest|null[0]
final fun <get-protocol>(): kotlin/Int // app.tinyui/BuildManifest.protocol.<get-protocol>|<get-protocol>(){}[0]
final val publicKey // app.tinyui/BuildManifest.publicKey|{}publicKey[0]
final fun <get-publicKey>(): kotlin/String // app.tinyui/BuildManifest.publicKey.<get-publicKey>|<get-publicKey>(){}[0]
final val requires // app.tinyui/BuildManifest.requires|{}requires[0]
final fun <get-requires>(): kotlin.collections/Map<kotlin/String, app.tinyui/PageRequires> // app.tinyui/BuildManifest.requires.<get-requires>|<get-requires>(){}[0]
final val runtime // app.tinyui/BuildManifest.runtime|{}runtime[0]
final fun <get-runtime>(): kotlin.collections/List<kotlin/String> // app.tinyui/BuildManifest.runtime.<get-runtime>|<get-runtime>(){}[0]
final val tinyui // app.tinyui/BuildManifest.tinyui|{}tinyui[0]
final fun <get-tinyui>(): kotlin/String // app.tinyui/BuildManifest.tinyui.<get-tinyui>|<get-tinyui>(){}[0]
final val version // app.tinyui/BuildManifest.version|{}version[0]
final fun <get-version>(): kotlin/String // app.tinyui/BuildManifest.version.<get-version>|<get-version>(){}[0]

Expand Down Expand Up @@ -471,6 +475,15 @@ final class app.tinyui/PageModule { // app.tinyui/PageModule|null[0]
final fun <get-name>(): kotlin/String // app.tinyui/PageModule.name.<get-name>|<get-name>(){}[0]
}

final class app.tinyui/PageRequires { // app.tinyui/PageRequires|null[0]
constructor <init>(kotlin.collections/List<kotlin/String>, kotlin.collections/List<kotlin/String>) // app.tinyui/PageRequires.<init>|<init>(kotlin.collections.List<kotlin.String>;kotlin.collections.List<kotlin.String>){}[0]

final val capabilities // app.tinyui/PageRequires.capabilities|{}capabilities[0]
final fun <get-capabilities>(): kotlin.collections/List<kotlin/String> // app.tinyui/PageRequires.capabilities.<get-capabilities>|<get-capabilities>(){}[0]
final val components // app.tinyui/PageRequires.components|{}components[0]
final fun <get-components>(): kotlin.collections/List<kotlin/String> // app.tinyui/PageRequires.components.<get-components>|<get-components>(){}[0]
}

final class app.tinyui/RuntimeBundle { // app.tinyui/RuntimeBundle|null[0]
constructor <init>(kotlin/ByteArray, kotlin/ByteArray) // app.tinyui/RuntimeBundle.<init>|<init>(kotlin.ByteArray;kotlin.ByteArray){}[0]

Expand Down Expand Up @@ -660,6 +673,10 @@ final object app.tinyui/HostSnapshot { // app.tinyui/HostSnapshot|null[0]
final fun render(app.tinyui/TinyUIHost, kotlin/String): kotlin/String // app.tinyui/HostSnapshot.render|render(app.tinyui.TinyUIHost;kotlin.String){}[0]
}

final object app.tinyui/PackageCheck { // app.tinyui/PackageCheck|null[0]
final fun problems(app.tinyui/BuildManifest, app.tinyui/TinyUIHost): kotlin.collections/List<kotlin/String> // app.tinyui/PackageCheck.problems|problems(app.tinyui.BuildManifest;app.tinyui.TinyUIHost){}[0]
}

final object app.tinyui/TinyUI { // app.tinyui/TinyUI|null[0]
final val engineVersion // app.tinyui/TinyUI.engineVersion|{}engineVersion[0]
final fun <get-engineVersion>(): kotlin/String // app.tinyui/TinyUI.engineVersion.<get-engineVersion>|<get-engineVersion>(){}[0]
Expand Down Expand Up @@ -710,13 +727,15 @@ final val app.tinyui/app_tinyui_HttpResponse$stableprop // app.tinyui/app_tinyui
final val app.tinyui/app_tinyui_InMemoryStore$stableprop // app.tinyui/app_tinyui_InMemoryStore$stableprop|#static{}app_tinyui_InMemoryStore$stableprop[0]
final val app.tinyui/app_tinyui_LoadedPage$stableprop // app.tinyui/app_tinyui_LoadedPage$stableprop|#static{}app_tinyui_LoadedPage$stableprop[0]
final val app.tinyui/app_tinyui_Navigator_None$stableprop // app.tinyui/app_tinyui_Navigator_None$stableprop|#static{}app_tinyui_Navigator_None$stableprop[0]
final val app.tinyui/app_tinyui_PackageCheck$stableprop // app.tinyui/app_tinyui_PackageCheck$stableprop|#static{}app_tinyui_PackageCheck$stableprop[0]
final val app.tinyui/app_tinyui_PageContext$stableprop // app.tinyui/app_tinyui_PageContext$stableprop|#static{}app_tinyui_PageContext$stableprop[0]
final val app.tinyui/app_tinyui_PageError$stableprop // app.tinyui/app_tinyui_PageError$stableprop|#static{}app_tinyui_PageError$stableprop[0]
final val app.tinyui/app_tinyui_PageFailure$stableprop // app.tinyui/app_tinyui_PageFailure$stableprop|#static{}app_tinyui_PageFailure$stableprop[0]
final val app.tinyui/app_tinyui_PageHost$stableprop // app.tinyui/app_tinyui_PageHost$stableprop|#static{}app_tinyui_PageHost$stableprop[0]
final val app.tinyui/app_tinyui_PageLocal$stableprop // app.tinyui/app_tinyui_PageLocal$stableprop|#static{}app_tinyui_PageLocal$stableprop[0]
final val app.tinyui/app_tinyui_PageLocalValue$stableprop // app.tinyui/app_tinyui_PageLocalValue$stableprop|#static{}app_tinyui_PageLocalValue$stableprop[0]
final val app.tinyui/app_tinyui_PageModule$stableprop // app.tinyui/app_tinyui_PageModule$stableprop|#static{}app_tinyui_PageModule$stableprop[0]
final val app.tinyui/app_tinyui_PageRequires$stableprop // app.tinyui/app_tinyui_PageRequires$stableprop|#static{}app_tinyui_PageRequires$stableprop[0]
final val app.tinyui/app_tinyui_RuntimeBundle$stableprop // app.tinyui/app_tinyui_RuntimeBundle$stableprop|#static{}app_tinyui_RuntimeBundle$stableprop[0]
final val app.tinyui/app_tinyui_SourceMap_Position$stableprop // app.tinyui/app_tinyui_SourceMap_Position$stableprop|#static{}app_tinyui_SourceMap_Position$stableprop[0]
final val app.tinyui/app_tinyui_SourceMaps$stableprop // app.tinyui/app_tinyui_SourceMaps$stableprop|#static{}app_tinyui_SourceMaps$stableprop[0]
Expand Down Expand Up @@ -765,13 +784,15 @@ final fun app.tinyui/app_tinyui_HttpResponse$stableprop_getter(): kotlin/Int //
final fun app.tinyui/app_tinyui_InMemoryStore$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_InMemoryStore$stableprop_getter|app_tinyui_InMemoryStore$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_LoadedPage$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_LoadedPage$stableprop_getter|app_tinyui_LoadedPage$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_Navigator_None$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_Navigator_None$stableprop_getter|app_tinyui_Navigator_None$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_PackageCheck$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_PackageCheck$stableprop_getter|app_tinyui_PackageCheck$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_PageContext$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_PageContext$stableprop_getter|app_tinyui_PageContext$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_PageError$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_PageError$stableprop_getter|app_tinyui_PageError$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_PageFailure$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_PageFailure$stableprop_getter|app_tinyui_PageFailure$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_PageHost$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_PageHost$stableprop_getter|app_tinyui_PageHost$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_PageLocal$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_PageLocal$stableprop_getter|app_tinyui_PageLocal$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_PageLocalValue$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_PageLocalValue$stableprop_getter|app_tinyui_PageLocalValue$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_PageModule$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_PageModule$stableprop_getter|app_tinyui_PageModule$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_PageRequires$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_PageRequires$stableprop_getter|app_tinyui_PageRequires$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_RuntimeBundle$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_RuntimeBundle$stableprop_getter|app_tinyui_RuntimeBundle$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_SourceMap_Position$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_SourceMap_Position$stableprop_getter|app_tinyui_SourceMap_Position$stableprop_getter(){}[0]
final fun app.tinyui/app_tinyui_SourceMaps$stableprop_getter(): kotlin/Int // app.tinyui/app_tinyui_SourceMaps$stableprop_getter|app_tinyui_SourceMaps$stableprop_getter(){}[0]
Expand Down
15 changes: 15 additions & 0 deletions compose/src/commonMain/kotlin/app/tinyui/BuildManifest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class BuildManifest(
val engine: String,
val protocol: Int,
val hashes: Map<String, String>,
/** The tinyui-core version the runtime modules came from. */
val tinyui: String = "",
/** Page module name → the host things it uses (docs/updates.md §1.1). */
val requires: Map<String, PageRequires> = emptyMap(),
/** Present only in a manifest `tinyui bundle` wrote: the embedded package has none. */
val hostVersion: String? = null,
) {
Expand Down Expand Up @@ -51,8 +55,19 @@ class BuildManifest(
engine = string("engine"),
protocol = root["protocol"]?.jsonPrimitive?.intOrNull ?: 0,
hashes = strings("hashes"),
tinyui = string("tinyui"),
requires = root["requires"]?.jsonObject?.mapValues { (_, v) ->
val page = v.jsonObject
PageRequires(
components = page["components"]?.jsonArray?.map { it.jsonPrimitive.content } ?: emptyList(),
capabilities = page["capabilities"]?.jsonArray?.map { it.jsonPrimitive.content } ?: emptyList(),
)
} ?: emptyMap(),
hostVersion = root["hostVersion"]?.jsonPrimitive?.contentOrNull,
)
}
}
}

/** Dotted host components and `host.call` capability names one page uses. */
class PageRequires(val components: List<String>, val capabilities: List<String>)
18 changes: 18 additions & 0 deletions compose/src/commonMain/kotlin/app/tinyui/PackageCheck.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package app.tinyui

import wang.harlon.quickjs.QuickJs

/** Whether a package can run on a host (docs/updates.md §4.1): for a host's test over its embedded packages. */
object PackageCheck {
/** Every reason [manifest]'s package cannot run on [host]; empty when it can. */
fun problems(manifest: BuildManifest, host: TinyUIHost): List<String> = buildList {
if (manifest.engine != QuickJs.upstreamCommit) add("built for engine ${manifest.engine}, the host embeds ${QuickJs.upstreamCommit}")
if (manifest.protocol != PageHost.PROTOCOL) add("built for protocol ${manifest.protocol}, the host implements ${PageHost.PROTOCOL}")
if (manifest.tinyui != TinyUI.version) add("built with tinyui ${manifest.tinyui.ifEmpty { "(unknown)" }}, the host has ${TinyUI.version}")
val capabilities = FrameworkCapabilities.toSet() + host.capabilities.names
for ((page, uses) in manifest.requires.entries.sortedBy { it.key }) {
uses.components.filter { host.components.schema(it) == null }.forEach { add("$page uses component $it, which the host does not register") }
uses.capabilities.filter { it !in capabilities }.forEach { add("$page calls $it, which the host does not provide") }
}
}
}
72 changes: 72 additions & 0 deletions compose/src/commonTest/kotlin/app/tinyui/PackageCheckTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package app.tinyui

import app.tinyui.components.registerBuiltins
import app.tinyui.schema.ComponentRegistry
import app.tinyui.schema.ComponentSchema
import wang.harlon.quickjs.QuickJs
import kotlin.test.Test
import kotlin.test.assertEquals

class PackageCheckTest {
private val sink = object : PageSink {
override fun error(error: PageError) {}
override fun log(line: String) {}
}

private val host = TinyUIHost(
ComponentRegistry().registerBuiltins().apply {
register(ComponentSchema("ta.Icon", emptyMap(), emptyMap(), emptyMap(), children = false, layout = true)) {}
},
sink,
CapabilityRegistry().apply { register("checkout.start") { _, _ -> null } },
)

private fun manifest(
engine: String = QuickJs.upstreamCommit,
protocol: Int = PageHost.PROTOCOL,
tinyui: String = TinyUI.version,
requires: Map<String, PageRequires> = mapOf("shop/home" to PageRequires(listOf("ta.Icon"), listOf("checkout.start", "store.get"))),
) = BuildManifest(
runtime = emptyList(), pages = listOf("shop/home"), files = emptyMap(), buildIds = emptyMap(),
name = "shop", publicKey = "k", version = "v1", createdAt = "2026-09-24T00:00:00Z",
engine = engine, protocol = protocol, hashes = emptyMap(), tinyui = tinyui, requires = requires,
)

@Test
fun aPackageBuiltForThisHostHasNoProblems() {
assertEquals(emptyList(), PackageCheck.problems(manifest(), host))
}

@Test
fun namesEveryMismatch() {
val problems = PackageCheck.problems(
manifest(
engine = "0".repeat(40),
protocol = 99,
tinyui = "0.0.1",
requires = mapOf("shop/home" to PageRequires(listOf("ta.Rating"), listOf("coupon.apply"))),
),
host,
)
assertEquals(
listOf(
"built for engine ${"0".repeat(40)}, the host embeds ${QuickJs.upstreamCommit}",
"built for protocol 99, the host implements ${PageHost.PROTOCOL}",
"built with tinyui 0.0.1, the host has ${TinyUI.version}",
"shop/home uses component ta.Rating, which the host does not register",
"shop/home calls coupon.apply, which the host does not provide",
),
problems,
)
}

@Test
fun readsTinyuiAndRequiresFromTheManifest() {
val parsed = BuildManifest.parse(
"""{"name":"shop","publicKey":"k","tinyui":"0.5.0","requires":{"shop/home":{"components":["ta.Icon"],"capabilities":["checkout.start"]}}}""",
)
assertEquals("0.5.0", parsed.tinyui)
assertEquals(listOf("ta.Icon"), parsed.requires.getValue("shop/home").components)
assertEquals(listOf("checkout.start"), parsed.requires.getValue("shop/home").capabilities)
}
}
Loading
Loading