From fe6261598b0576742c6ed48de068293a1fe3b4ad Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 21 May 2026 15:51:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20DevPanel=20render=20logging=20=E2=80=94?= =?UTF-8?q?=20unknown=20widgets,=20placeholders,=20and=20urlwidget=20failu?= =?UTF-8?q?res?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive logging to BricksRenderer for DevPanel visibility: 1. Unknown widgettypes: WARN log with full JSON dump in DevPanel Logs tab. Also shows [? WidgetType] orange placeholder on screen when no subwidgets. 2. Placeholder widgets (Html, MarkdownViewer, etc.): INFO log with widget summary. 3. urlwidget load failures: ERROR log with full widget JSON and error message. 4. widgetSummary(): compact one-line summary (type, id, options keys, counts). 5. widgetJsonDump(): pretty-printed JSON for deep inspection in DevPanel. Previously: unknown widgets silently fell through to subwidget rendering. Now: every unrecognized widget is visible in DevPanel with full context. --- .../com/bricks/mp/core/BricksRenderer.kt | 79 ++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/shared/src/commonMain/kotlin/com/bricks/mp/core/BricksRenderer.kt b/shared/src/commonMain/kotlin/com/bricks/mp/core/BricksRenderer.kt index 2283ae8..31448e4 100644 --- a/shared/src/commonMain/kotlin/com/bricks/mp/core/BricksRenderer.kt +++ b/shared/src/commonMain/kotlin/com/bricks/mp/core/BricksRenderer.kt @@ -27,12 +27,51 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.bricks.mp.actions.ActionDispatcher +import com.bricks.mp.dev.DevLogLevel +import com.bricks.mp.dev.DevLogSource +import com.bricks.mp.dev.DevLogStore import com.bricks.mp.widgets.* +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonElement +/** + * Serialize a widget to a compact, readable string for dev logging. + * Shows widgettype, id, options keys, subwidget count, and bind count. + */ +private fun widgetSummary(w: BricksWidget): String { + val optKeys = w.options.keys.joinToString(", ") + val subCount = w.subwidgets.size + val bindCount = w.binds.size + return buildString { + append("widgettype=${w.widgettype}") + if (w.id.isNotEmpty()) append(", id=${w.id}") + if (optKeys.isNotEmpty()) append(", options=[$optKeys]") + if (subCount > 0) append(", subwidgets=$subCount") + if (bindCount > 0) append(", binds=$bindCount") + } +} + +/** + * Serialize the full widget JSON for detailed error inspection in DevPanel. + */ +private fun widgetJsonDump(w: BricksWidget): String { + return try { + val json = Json { encodeDefaults = true; prettyPrint = true } + json.encodeToString(BricksWidget.serializer(), w) + } catch (e: Exception) { + "Failed to serialize: ${e.message}" + } +} + /** * 递归渲染引擎 - 将 BricksWidget 树渲染为 Compose UI + * + * Error handling strategy: + * - Unknown widgettypes → WARN log with full widget data in DevPanel + * - Placeholder widgets → INFO log with widget summary + * - urlwidget load failures → ERROR log with widget JSON dump + * - RenderPlaceholder and individual render functions handle their own errors */ @Composable fun RenderWidget( @@ -41,7 +80,15 @@ fun RenderWidget( modifier: Modifier = Modifier ) { val resolvedWidget = resolveTemplates(widget) + dispatchRender(resolvedWidget, actionDispatcher, modifier) +} +@Composable +private fun dispatchRender( + resolvedWidget: BricksWidget, + actionDispatcher: ActionDispatcher?, + modifier: Modifier +) { when (resolvedWidget.widgettype) { // 文本 "Text" -> RenderTextWidget(resolvedWidget) @@ -94,14 +141,30 @@ fun RenderWidget( "Message" -> RenderMessageWidget(resolvedWidget) "urlwidget" -> RenderUrlWidget(resolvedWidget, actionDispatcher) - // 默认: 渲染子组件 + // 默认: 未知 widgettype — 记录日志并尝试渲染子组件 else -> { + // Log unknown widgettype with full widget data for DevPanel inspection + DevLogStore.log( + level = DevLogLevel.WARN, + message = "Unknown widgettype: ${resolvedWidget.widgettype}", + details = "Widget data:\n${widgetJsonDump(resolvedWidget)}", + source = DevLogSource.RENDER + ) if (resolvedWidget.subwidgets.isNotEmpty()) { Column(modifier = modifier) { resolvedWidget.subwidgets.forEach { child -> RenderWidget(child, actionDispatcher) } } + } else { + // Show placeholder so the developer can see what was skipped + Text( + text = "[? ${resolvedWidget.widgettype}]", + color = Color(0xFFFFA000), + fontSize = 10.sp, + fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace, + modifier = Modifier.padding(4.dp) + ) } } } @@ -532,6 +595,13 @@ private fun RenderUrlWidget(widget: BricksWidget, actionDispatcher: ActionDispat } if (error != null) { + // Log the failure to DevPanel with full widget context + DevLogStore.log( + level = DevLogLevel.ERROR, + message = "urlwidget load failed: $url", + details = "Widget data:\n${widgetJsonDump(widget)}\n\nError: $error", + source = DevLogSource.RENDER + ) Column( modifier = Modifier.fillMaxWidth().padding(16.dp), verticalArrangement = Arrangement.Center, @@ -696,6 +766,13 @@ private fun RenderMessageWidget(widget: BricksWidget) { @Composable private fun RenderPlaceholder(widget: BricksWidget, name: String) { + // Log placeholder rendering so developers know which widgets are not yet implemented + DevLogStore.log( + level = DevLogLevel.INFO, + message = "Placeholder rendered: ${widget.widgettype} ($name)", + details = "Widget data:\n${widgetSummary(widget)}", + source = DevLogSource.RENDER + ) Text( text = "[${widget.widgettype}: $name - TODO]", modifier = Modifier.padding(8.dp),