feat: DevPanel render logging — unknown widgets, placeholders, and urlwidget failures

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.
This commit is contained in:
yumoqing 2026-05-21 15:51:20 +08:00
parent 5ba33b9e18
commit fe6261598b

View File

@ -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),