From 9d8d0e249db1cf1cf4f3b2d700b750e7dd7663a7 Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Mon, 23 Mar 2026 09:08:28 +0200 Subject: [PATCH] fix(chess): encode required fields in Jester content JSON JesterContent fields version, fen, and history were omitted from serialized JSON because kotlinx.serialization skips default values. Jester's isStartGameEvent checks arrayEquals(json.history, []) which fails when the field is absent (undefined !== []). Added @EncodeDefault to version, fen, and history so they are always included in the JSON output regardless of value. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../quartz/nip64Chess/jester/JesterContent.kt | 16 +++++-- .../quartz/nip64Chess/ChessInteropBugTest.kt | 46 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip64Chess/jester/JesterContent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip64Chess/jester/JesterContent.kt index 0eb536cd3..ac68687b6 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip64Chess/jester/JesterContent.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip64Chess/jester/JesterContent.kt @@ -20,18 +20,26 @@ */ package com.vitorpamplona.quartz.nip64Chess.jester +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable /** - * JSON content structure for Jester events + * JSON content structure for Jester events. + * + * Fields marked @EncodeDefault are always serialized even when they match + * the default value. This is required for jester.nyo.dev compatibility — + * jester's isStartGameEvent checks arrayEquals(json.history, []) which + * fails if the field is absent. */ +@OptIn(ExperimentalSerializationApi::class) @Serializable data class JesterContent( - val version: String = "0", + @EncodeDefault val version: String = "0", val kind: Int, - val fen: String = JesterProtocol.FEN_START, + @EncodeDefault val fen: String = JesterProtocol.FEN_START, val move: String? = null, - val history: List = emptyList(), + @EncodeDefault val history: List = emptyList(), val nonce: String? = null, // Extended fields for Amethyst (backward compatible - jesterui ignores unknown fields) val playerColor: String? = null, // "white" or "black" - challenger's color choice diff --git a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/nip64Chess/ChessInteropBugTest.kt b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/nip64Chess/ChessInteropBugTest.kt index 47654d137..1c2c70d89 100644 --- a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/nip64Chess/ChessInteropBugTest.kt +++ b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/nip64Chess/ChessInteropBugTest.kt @@ -568,6 +568,52 @@ class ChessInteropBugTest { ) } + // ========================================================================== + // CATEGORY 5: JESTER CONTENT SERIALIZATION + // ========================================================================== + + @Test + fun `serialized start content includes version fen and history fields`() { + // Jester's isStartGameEvent checks arrayEquals(json.history, []) + // which fails if history field is absent from JSON + val content = + com.vitorpamplona.quartz.nip64Chess.jester.JesterContent( + kind = 0, + nonce = "test1234", + playerColor = "white", + ) + val json = + com.vitorpamplona.quartz.nip01Core.core.JsonMapper + .toJson(content) + + assertTrue(json.contains("\"version\""), "JSON must include version field: $json") + assertTrue(json.contains("\"fen\""), "JSON must include fen field: $json") + assertTrue(json.contains("\"history\""), "JSON must include history field: $json") + assertTrue(json.contains("\"history\":[]"), "history must be empty array: $json") + } + + @Test + fun `serialized move content includes version fen and history fields`() { + val content = + com.vitorpamplona.quartz.nip64Chess.jester.JesterContent( + kind = 1, + fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", + move = "e4", + history = listOf("e4"), + ) + val json = + com.vitorpamplona.quartz.nip01Core.core.JsonMapper + .toJson(content) + + assertTrue(json.contains("\"version\""), "JSON must include version field: $json") + assertTrue(json.contains("\"fen\""), "JSON must include fen field: $json") + assertTrue(json.contains("\"history\""), "JSON must include history field: $json") + } + + // ========================================================================== + // HELPERS + // ========================================================================== + private fun createMoveEvent( id: String, pubKey: String,