diff options
| author | ketsuban <ketsuban@kukuri.dev> | 2026-09-03 10:10:18 +0000 |
|---|---|---|
| committer | ketsuban <ketsuban@kukuri.dev> | 2026-09-03 10:10:18 +0000 |
| commit | 20fbdb7c15fd74efc62833d295373b0ce601e9e5 (patch) | |
| tree | 36fa734710c60f3f2a2ac3eac27daea3d604b885 /modules/zero-bridge-api/src | |
| download | kukuri-20fbdb7c15fd74efc62833d295373b0ce601e9e5.tar.gz kukuri-20fbdb7c15fd74efc62833d295373b0ce601e9e5.tar.bz2 kukuri-20fbdb7c15fd74efc62833d295373b0ce601e9e5.zip | |
kukuri reset: welcome-only app shell + bridge/vendor modules + CI infra
Full pre-reset history preserved on branch backup/full-pre-reset (2ccab79).
Diffstat (limited to 'modules/zero-bridge-api/src')
19 files changed, 271 insertions, 0 deletions
diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/BridgeException.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/BridgeException.kt new file mode 100644 index 0000000..1c25e82 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/BridgeException.kt @@ -0,0 +1,3 @@ +package jp.orgflow.bridge + +class BridgeException(message: String, cause: Throwable? = null) : Exception(message, cause)
\ No newline at end of file diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostCamera.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostCamera.kt new file mode 100644 index 0000000..2e11db0 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostCamera.kt @@ -0,0 +1,16 @@ +package jp.orgflow.bridge + +import kotlinx.coroutines.flow.Flow + +// Frame-emitting media source. Frames are encoded byte arrays (JPEG/PNG) small enough +// for Compose rendering via Image.makeFromEncoded (raw frame transport, not RTC tracks). + +interface HostCamera { + suspend fun start(): CameraStream? + + fun stop() +} + +interface CameraStream { + val frames: Flow<MediaFrame> +} diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostDocumentIo.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostDocumentIo.kt new file mode 100644 index 0000000..b2bc60d --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostDocumentIo.kt @@ -0,0 +1,86 @@ +package jp.orgflow.bridge + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.Json + +interface HostDocumentIo { + suspend fun importSlides(bytes: ByteArray): List<SlideSpecData> + suspend fun renderSlidePng(bytes: ByteArray, index: Int): ByteArray? + suspend fun exportPptx(slides: List<SlideSpecData>): ByteArray + suspend fun importXlsxGrid(bytes: ByteArray, sheet: Int): List<List<String>> + suspend fun exportXlsxGrid(rows: List<List<String>>, formulas: Map<String, String>): ByteArray +} + +@Serializable +data class SlideSpecData( + val id: String, + val title: String = "", + val backgroundArgb: Long? = null, + val boxes: List<SlideSpecBox> = emptyList(), + val images: Map<String, ByteArray> = emptyMap(), + val notes: String = "", +) { + override fun equals(other: Any?): Boolean = + other is SlideSpecData && + id == other.id && + title == other.title && + backgroundArgb == other.backgroundArgb && + boxes == other.boxes && + images.size == other.images.size && + images.all { (key, value) -> other.images[key]?.contentEquals(value) == true } && + notes == other.notes + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + title.hashCode() + result = 31 * result + (backgroundArgb?.hashCode() ?: 0) + result = 31 * result + boxes.hashCode() + result = 31 * result + images.entries.fold(0) { acc, entry -> 31 * acc + entry.key.hashCode() * 31 + entry.value.contentHashCode() } + result = 31 * result + notes.hashCode() + return result + } + + companion object { + const val WIDTH_PT = 720.0 + const val HEIGHT_PT = 405.0 + } +} + +@Serializable +enum class SlideSpecBoxKind { + TEXT, + IMAGE, + TABLE, +} + +@Serializable +data class SlideSpecBox( + val kind: SlideSpecBoxKind, + val xPt: Double, + val yPt: Double, + val widthPt: Double, + val heightPt: Double, + val textRuns: List<SlideSpecTextRun> = emptyList(), + val imageRef: String? = null, + val tableCells: List<List<String>> = emptyList(), +) + +@Serializable +data class SlideSpecTextRun( + val text: String, + val bold: Boolean = false, + val italic: Boolean = false, + val sizePt: Double? = null, + val colorArgb: Long? = null, +) + +object SlideSpecCodec { + private val json = Json { ignoreUnknownKeys = true } + + fun encode(slides: List<SlideSpecData>): String = + json.encodeToString(ListSerializer(SlideSpecData.serializer()), slides) + + fun decode(text: String): List<SlideSpecData> = + json.decodeFromString(ListSerializer(SlideSpecData.serializer()), text) +} diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostFileSystem.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostFileSystem.kt new file mode 100644 index 0000000..8d2c21c --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostFileSystem.kt @@ -0,0 +1,9 @@ +package jp.orgflow.bridge + +interface HostFileSystem { + fun readBytes(path: String): ByteArray + fun writeBytes(path: String, data: ByteArray) + fun delete(path: String) + fun exists(path: String): Boolean + fun list(directory: String): List<String> +}
\ No newline at end of file diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostPeerState.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostPeerState.kt new file mode 100644 index 0000000..1b23ea8 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostPeerState.kt @@ -0,0 +1,7 @@ +package jp.orgflow.bridge + +interface HostPeerState { + fun peerCount(): Int + fun connectedPeers(): List<String> + fun isConnected(peerId: String): Boolean +}
\ No newline at end of file diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostQrScanner.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostQrScanner.kt new file mode 100644 index 0000000..bb2214f --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostQrScanner.kt @@ -0,0 +1,5 @@ +package jp.orgflow.bridge + +interface HostQrScanner { + suspend fun scan(): String? +}
\ No newline at end of file diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostScreenShare.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostScreenShare.kt new file mode 100644 index 0000000..9caa230 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostScreenShare.kt @@ -0,0 +1,9 @@ +package jp.orgflow.bridge + +// display selects the screen device index (JVM GraphicsEnvironment order); ignored on wasm. + +interface HostScreenShare { + suspend fun start(display: Int = 0): CameraStream? + + fun stop() +} diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostStorageQuota.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostStorageQuota.kt new file mode 100644 index 0000000..987c083 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostStorageQuota.kt @@ -0,0 +1,8 @@ +package jp.orgflow.bridge + +interface HostStorageQuota { + fun usedBytes(): Long + fun totalBytes(): Long + fun availableBytes(): Long + fun estimate(): StorageEstimate +}
\ No newline at end of file diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostTransport.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostTransport.kt new file mode 100644 index 0000000..eff8d6a --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostTransport.kt @@ -0,0 +1,6 @@ +package jp.orgflow.bridge + +interface HostTransport { + suspend fun send(peerId: String, frame: HostTransportFrame) + suspend fun receive(): HostTransportFrame? +}
\ No newline at end of file diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostTransportFrame.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostTransportFrame.kt new file mode 100644 index 0000000..2095843 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostTransportFrame.kt @@ -0,0 +1,8 @@ +package jp.orgflow.bridge + +data class HostTransportFrame( + val from: String, + val to: String, + val payload: ByteArray, + val type: String, +)
\ No newline at end of file diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/MediaFrame.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/MediaFrame.kt new file mode 100644 index 0000000..e70ee06 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/MediaFrame.kt @@ -0,0 +1,9 @@ +package jp.orgflow.bridge + +data class MediaFrame( + val bytes: ByteArray, + val width: Int, + val height: Int, + val format: String, + val capturedAtMs: Long = 0L, +) diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/StorageEstimate.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/StorageEstimate.kt new file mode 100644 index 0000000..bcc3c39 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/StorageEstimate.kt @@ -0,0 +1,9 @@ +package jp.orgflow.bridge + +data class StorageEstimate( + val usedBytes: Long, + val totalBytes: Long, + val quotaPercentage: Double, +) { + fun availableBytes(): Long = totalBytes - usedBytes +}
\ No newline at end of file diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/ZeroBridge.kt b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/ZeroBridge.kt new file mode 100644 index 0000000..c899d53 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/ZeroBridge.kt @@ -0,0 +1,9 @@ +package jp.orgflow.bridge + +interface ZeroBridge { + val fileSystem: HostFileSystem + val storageQuota: HostStorageQuota + val transport: HostTransport? + val peerState: HostPeerState? + val qrScanner: HostQrScanner? +}
\ No newline at end of file diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostClock.kt~~ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostClock.kt~~ new file mode 100644 index 0000000..8eeb2ba --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostClock.kt~~ @@ -0,0 +1 @@ +TODO: per docs/spec.md (modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostClock.kt~~) diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostCrypto.kt~~ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostCrypto.kt~~ new file mode 100644 index 0000000..bba743f --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostCrypto.kt~~ @@ -0,0 +1 @@ +TODO: per docs/spec.md (modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostCrypto.kt~~) diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostFilePicker.kt~~ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostFilePicker.kt~~ new file mode 100644 index 0000000..5286555 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostFilePicker.kt~~ @@ -0,0 +1 @@ +TODO: per docs/spec.md (modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostFilePicker.kt~~) diff --git a/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostRandom.kt~~ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostRandom.kt~~ new file mode 100644 index 0000000..42d12e8 --- /dev/null +++ b/modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostRandom.kt~~ @@ -0,0 +1 @@ +TODO: per docs/spec.md (modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostRandom.kt~~) diff --git a/modules/zero-bridge-api/src/commonTest/kotlin/jp/orgflow/bridge/SlideSpecCodecTest.kt b/modules/zero-bridge-api/src/commonTest/kotlin/jp/orgflow/bridge/SlideSpecCodecTest.kt new file mode 100644 index 0000000..34f99f9 --- /dev/null +++ b/modules/zero-bridge-api/src/commonTest/kotlin/jp/orgflow/bridge/SlideSpecCodecTest.kt @@ -0,0 +1,54 @@ +package jp.orgflow.bridge + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SlideSpecCodecTest { + + @Test + fun codecRoundtripsSlidesWithImages() { + val slides = listOf( + SlideSpecData( + id = "s1", + title = "Codec", + backgroundArgb = 0xFFFFF8E1, + boxes = listOf( + SlideSpecBox( + kind = SlideSpecBoxKind.TEXT, + xPt = 54.0, + yPt = 40.0, + widthPt = 400.0, + heightPt = 60.0, + textRuns = listOf(SlideSpecTextRun("hi", bold = true, italic = true, sizePt = 24.0, colorArgb = 0xFF0000FF)), + ), + SlideSpecBox(kind = SlideSpecBoxKind.IMAGE, xPt = 5.0, yPt = 6.0, widthPt = 7.0, heightPt = 8.0, imageRef = "img1"), + SlideSpecBox( + kind = SlideSpecBoxKind.TABLE, + xPt = 9.0, + yPt = 10.0, + widthPt = 11.0, + heightPt = 12.0, + tableCells = listOf(listOf("a", "b"), listOf("1", "2")), + ), + ), + images = mapOf("img1" to byteArrayOf(1, 2, 3, -1)), + notes = "note", + ), + ) + assertEquals(slides, SlideSpecCodec.decode(SlideSpecCodec.encode(slides))) + } + + @Test + fun codecHandlesEmptyDeckAndDefaults() { + assertEquals(emptyList(), SlideSpecCodec.decode(SlideSpecCodec.encode(emptyList()))) + val minimal = SlideSpecData(id = "s1") + assertEquals(minimal, SlideSpecCodec.decode(SlideSpecCodec.encode(listOf(minimal))).single()) + } + + @Test + fun codecIgnoresUnknownKeys() { + val encoded = SlideSpecCodec.encode(listOf(SlideSpecData(id = "s1", title = "t"))) + assertTrue(encoded.contains("\"s1\"")) + } +} diff --git a/modules/zero-bridge-api/src/commonTest/kotlin/jp/orgflow/bridge/ZeroBridgeApiTest.kt b/modules/zero-bridge-api/src/commonTest/kotlin/jp/orgflow/bridge/ZeroBridgeApiTest.kt new file mode 100644 index 0000000..1a392c9 --- /dev/null +++ b/modules/zero-bridge-api/src/commonTest/kotlin/jp/orgflow/bridge/ZeroBridgeApiTest.kt @@ -0,0 +1,29 @@ +package jp.orgflow.bridge + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class ZeroBridgeApiTest { + @Test + fun storageEstimate() { + val est = StorageEstimate(100L, 1000L, 10.0) + assertEquals(900L, est.availableBytes()) + } + + @Test + fun transportFrame() { + val frame = HostTransportFrame("alice", "bob", byteArrayOf(1, 2, 3), "data") + assertEquals("alice", frame.from) + assertEquals("bob", frame.to) + assertTrue(frame.payload.contentEquals(byteArrayOf(1, 2, 3))) + } + + @Test + fun bridgeException() { + val e = BridgeException("test", RuntimeException("cause")) + assertEquals("test", e.message) + assertNotNull(e.cause) + } +}
\ No newline at end of file |
