summaryrefslogtreecommitdiff
path: root/modules/zero-bridge-api/src
diff options
context:
space:
mode:
authorketsuban <ketsuban@kukuri.dev>2026-09-03 10:10:18 +0000
committerketsuban <ketsuban@kukuri.dev>2026-09-03 10:10:18 +0000
commit20fbdb7c15fd74efc62833d295373b0ce601e9e5 (patch)
tree36fa734710c60f3f2a2ac3eac27daea3d604b885 /modules/zero-bridge-api/src
downloadkukuri-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')
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/BridgeException.kt3
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostCamera.kt16
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostDocumentIo.kt86
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostFileSystem.kt9
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostPeerState.kt7
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostQrScanner.kt5
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostScreenShare.kt9
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostStorageQuota.kt8
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostTransport.kt6
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/HostTransportFrame.kt8
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/MediaFrame.kt9
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/StorageEstimate.kt9
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/ZeroBridge.kt9
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostClock.kt~~1
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostCrypto.kt~~1
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostFilePicker.kt~~1
-rw-r--r--modules/zero-bridge-api/src/commonMain/kotlin/jp/orgflow/bridge/~~HostRandom.kt~~1
-rw-r--r--modules/zero-bridge-api/src/commonTest/kotlin/jp/orgflow/bridge/SlideSpecCodecTest.kt54
-rw-r--r--modules/zero-bridge-api/src/commonTest/kotlin/jp/orgflow/bridge/ZeroBridgeApiTest.kt29
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