Skip to content

Conversation

@phodal
Copy link
Owner

@phodal phodal commented Dec 6, 2025

Summary

Refactor NanoRenderer to use component-specific methods instead of when statement string matching.

Problem

The current NanoRenderer uses when (ir.type) string matching to dispatch rendering, which:

  • Silently ignores unknown components in the else branch
  • Makes it difficult to discover missing implementations when adding new components
  • Provides no compile-time safety across platforms

Solution

Following the pattern from CodingAgentRenderer, each component type now has a dedicated render method:

interface NanoRenderer<T> {
    // Layout
    fun renderVStack(ir: NanoIR): T
    fun renderHStack(ir: NanoIR): T
    // Container
    fun renderCard(ir: NanoIR): T
    fun renderForm(ir: NanoIR): T
    // Content
    fun renderText(ir: NanoIR): T
    fun renderImage(ir: NanoIR): T
    fun renderBadge(ir: NanoIR): T
    fun renderDivider(ir: NanoIR): T
    // Input
    fun renderButton(ir: NanoIR): T
    fun renderInput(ir: NanoIR): T
    fun renderCheckbox(ir: NanoIR): T
    fun renderTextArea(ir: NanoIR): T
    fun renderSelect(ir: NanoIR): T
    // Control Flow
    fun renderConditional(ir: NanoIR): T
    fun renderForLoop(ir: NanoIR): T
    // Meta
    fun renderComponent(ir: NanoIR): T
    fun renderUnknown(ir: NanoIR): T
}

Changes

  • NanoRenderer interface: Added component-specific methods
  • HtmlRenderer: Implements all new methods, added missing components (Form, TextArea, Select, Divider)
  • ComposeNanoRenderer: Refactored to follow same pattern with Render* functions
  • VSCode TypeScript: New NanoRenderer.tsx implementation with types and CSS

Benefits

  • Compile-time safety: New components require implementation in all renderers
  • Discoverability: IDE shows all required methods at a glance
  • Consistency: All platforms follow the same pattern

Closes #487

Summary by CodeRabbit

  • New Features

    • Extended UI component library: Forms, TextArea, Select, Divider, Badge, Checkbox, Button and richer layout containers.
    • Modular renderer exposing a unified node-based dispatch and platform-agnostic NanoUI rendering for the VSCode webview.
    • Theming added: spacing scale and color scheme with a default theme.
    • Control flow support: conditionals and loops.
  • Tests

    • Updated tests to use the new node-based rendering entry point and adjusted assertions.

✏️ Tip: You can customize this high-level summary in your review settings.

- Refactor NanoRenderer interface to add dedicated methods for each component type
- Update HtmlRenderer with all new interface methods (Form, TextArea, Select, Divider)
- Refactor ComposeNanoRenderer to follow the same component-specific pattern
- Add VSCode TypeScript NanoRenderer implementation with types and CSS

This change ensures compile-time safety when adding new components - the compiler
will enforce that all renderer implementations add corresponding methods, solving
the issue where the 'else' branch would silently ignore unimplemented components.

Closes #487
Copilot AI review requested due to automatic review settings December 6, 2025 14:15
@coderabbitai
Copy link

coderabbitai bot commented Dec 6, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Refactors Nano rendering into a component-specific dispatcher (renderNode) and adds explicit per-component render methods, TypeScript NanoIR/types, a React NanoRenderer, and CSS, updating JVM/HTML/VSCode renderers to route child rendering through the new dispatch.

Changes

Cohort / File(s) Summary
Core interface & models
xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt
Replaced string-based dispatch with renderNode dispatcher and added per-component methods (renderVStack, renderHStack, renderCard, renderForm, renderText, renderImage, renderBadge, renderDivider, renderButton, renderInput, renderCheckbox, renderTextArea, renderSelect, renderConditional, renderForLoop, renderComponent, renderUnknown). Removed supports(). Added RenderContext, NanoRenderAction (StateMutation, Navigate, Fetch, ShowToast), NanoTheme, SpacingScale, ColorScheme.
Compose renderer (JVM)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
Introduced public RenderNode dispatcher and public per-component RenderXxx functions; reorganized rendering into Layout/Container/Content/Input/ControlFlow/Meta sections; updated child traversal to use RenderNode(child); added new UI element renderers (Form, Divider, TextArea, Select, Button, Checkbox, Image, etc.).
HTML renderer
xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt, xuiper-ui/src/test/kotlin/cc/unitmesh/xuiper/render/HtmlRendererTest.kt
Replaced renderComponent with renderNode dispatcher; exposed many previously-private render helpers as public overrides (Form, Divider, TextArea, Select, etc.); tests updated to call renderNode.
Webview types
mpp-vscode/webview/src/types/nano.ts
Added comprehensive TypeScript typings for NanoIR, state, actions, bindings, loops, rendering context, render actions, theme/spacing/colors, and exported DEFAULT_THEME.
Webview renderer & assets
mpp-vscode/webview/src/components/nano/NanoRenderer.tsx, mpp-vscode/webview/src/components/nano/NanoRenderer.css, mpp-vscode/webview/src/components/nano/index.ts
New React NanoRenderer component with RenderNode dispatcher and per-component renderers; form submit wiring to Fetch actions; default render context merging; CSS file with layout primitives, components, theming via VSCode tokens; barrel index.ts re-exports NanoRenderer, types, and DEFAULT_THEME.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • Consistency of method signatures across Kotlin interface and all implementations (Compose, HTML).
    • Correct propagation of render context/state/dispatch and action serialization (Navigate/Fetch/ShowToast).
    • Webview React wiring for Form submit and action dispatch (ensure types align with NanoActionIR).
    • CSS class naming vs. JSX structure to avoid visual/behavior mismatches.
    • Tests updated to renderNode coverage for new component types.

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Copilot

Poem

🐰 I hopped through code with nimble paws,

RenderNode now leads the cause.
VStack, Card, and Form in line,
Types and CSS look sleek and fine.
A tiny carrot toast — render! 🍃

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring work: converting NanoRenderer to use component-specific render methods instead of string-based dispatch.
Linked Issues check ✅ Passed The PR fully implements all core requirements from issue #487: added per-component render methods to NanoRenderer interface, updated HtmlRenderer and ComposeNanoRenderer implementations, and added all required component types.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the refactoring objectives. The VSCode TypeScript additions (NanoRenderer.tsx, NanoRenderer.css, types/nano.ts) implement the interface on a new platform as intended by the pattern.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e88c2e0 and 3d25ab8.

📒 Files selected for processing (4)
  • mpp-vscode/webview/src/components/nano/NanoRenderer.css (1 hunks)
  • mpp-vscode/webview/src/components/nano/NanoRenderer.tsx (1 hunks)
  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt (5 hunks)
  • xuiper-ui/src/test/kotlin/cc/unitmesh/xuiper/render/HtmlRendererTest.kt (7 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt (1)

190-202: Replace hardcoded colors with design tokens.

The RenderBadge function uses hardcoded hex colors (Color(0xFF4CAF50), Color(0xFFF44336), etc.) which violates the coding guidelines. As per coding guidelines, use AutoDevColors or MaterialTheme.colorScheme instead of hardcoded colors.

Consider refactoring to use Material theme colors:

-        val bgColor = when (colorName) {
-            "green" -> Color(0xFF4CAF50)
-            "red" -> Color(0xFFF44336)
-            "blue" -> Color(0xFF2196F3)
-            "yellow" -> Color(0xFFFFEB3B)
-            "orange" -> Color(0xFFFF9800)
-            else -> MaterialTheme.colorScheme.primaryContainer
-        }
-
-        val textColor = when (colorName) {
-            "yellow" -> Color.Black
-            else -> Color.White
-        }
+        val (bgColor, textColor) = when (colorName) {
+            "green" -> MaterialTheme.colorScheme.tertiary to MaterialTheme.colorScheme.onTertiary
+            "red" -> MaterialTheme.colorScheme.error to MaterialTheme.colorScheme.onError
+            "blue" -> MaterialTheme.colorScheme.primary to MaterialTheme.colorScheme.onPrimary
+            "yellow" -> MaterialTheme.colorScheme.secondaryContainer to MaterialTheme.colorScheme.onSecondaryContainer
+            "orange" -> MaterialTheme.colorScheme.tertiaryContainer to MaterialTheme.colorScheme.onTertiaryContainer
+            else -> MaterialTheme.colorScheme.primaryContainer to MaterialTheme.colorScheme.onPrimaryContainer
+        }
xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt (1)

108-120: Potential XSS vulnerability: HTML content not escaped.

User-provided content from ir.props["content"] is directly interpolated into HTML without escaping. If the NanoIR tree is ever generated from untrusted input, this could lead to XSS vulnerabilities.

Consider escaping HTML entities:

+    private fun String.escapeHtml(): String {
+        return this
+            .replace("&", "&amp;")
+            .replace("<", "&lt;")
+            .replace(">", "&gt;")
+            .replace("\"", "&quot;")
+            .replace("'", "&#39;")
+    }
+
     override fun renderText(ir: NanoIR): String {
-        val content = ir.props["content"]?.jsonPrimitive?.content ?: ""
+        val content = (ir.props["content"]?.jsonPrimitive?.content ?: "").escapeHtml()
         val style = ir.props["style"]?.jsonPrimitive?.content ?: "body"
         // ... rest unchanged
     }

Apply similar escaping to renderBadge, renderButton, and other methods that render user content.

🧹 Nitpick comments (5)
xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt (1)

183-188: Consider adding StateMutation path validation.

The StateMutation action accepts any path: String and value: Any. For robustness, consider documenting the expected path format (e.g., dot-notation like "user.name") or validating the path in the dispatch handler.

mpp-vscode/webview/src/components/nano/NanoRenderer.css (1)

127-130: Use VSCode theme variable for yellow badge color.

The .color-yellow class uses a hardcoded #FFEB3B without a VSCode theme variable fallback. As per coding guidelines, avoid hardcoded colors in favor of design tokens.

 .color-yellow { 
-  background: #FFEB3B; 
+  background: var(--vscode-editorWarning-foreground, #FFEB3B); 
   color: black;
 }
mpp-vscode/webview/src/components/nano/NanoRenderer.tsx (1)

88-90: Consider using stable keys instead of array index.

Using array index as the React key (key={i}) can cause issues if children are reordered, filtered, or dynamically modified. If NanoIR nodes have unique identifiers, prefer using those.

If NanoIR nodes don't have IDs, consider generating stable keys:

-      {ir.children?.map((child, i) => (
-        <RenderNode key={i} ir={child} context={context} />
+      {ir.children?.map((child, i) => (
+        <RenderNode key={child.props.id ?? `${ir.type}-${i}`} ir={child} context={context} />
       ))}
mpp-vscode/webview/src/types/nano.ts (2)

56-80: Align NanoIR.type with a string‑literal union for known component types

You already have a closed set of component types in NanoSpecV1 (VStack, HStack, Card, Form, Text, Image, Badge, Divider, Button, Input, Checkbox, TextArea, Select, Conditional, ForLoop, Component). Keeping type: string loses compile‑time checking for those values in the webview.

Consider introducing a dedicated union type and using it here:

+export type NanoComponentType =
+  | 'VStack'
+  | 'HStack'
+  | 'Card'
+  | 'Form'
+  | 'Text'
+  | 'Image'
+  | 'Badge'
+  | 'Divider'
+  | 'Button'
+  | 'Input'
+  | 'Checkbox'
+  | 'TextArea'
+  | 'Select'
+  | 'Conditional'
+  | 'ForLoop'
+  | 'Component';
+
 export interface NanoIR {
   /** Component type (VStack, Card, Text, Button, etc.) */
-  type: string;
+  type: NanoComponentType;

This keeps the IR in sync with the explicit renderer methods and makes it harder to introduce typos or unsupported components on the TypeScript side.


36-40: Narrow NanoBindingIR.mode to the documented modes

The docstring says mode is "subscribe" for << or "twoWay" for :=, but the type is a free "string". You can get a nice discriminated union here:

 export interface NanoBindingIR {
   /** "subscribe" for << or "twoWay" for := */
-  mode: string;
+  mode: 'subscribe' | 'twoWay';
   expression: string;
 }

This aligns with how NanoRenderAction is modeled and gives better autocomplete + validation in the webview code.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbb930c and e88c2e0.

📒 Files selected for processing (7)
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt (8 hunks)
  • mpp-vscode/webview/src/components/nano/NanoRenderer.css (1 hunks)
  • mpp-vscode/webview/src/components/nano/NanoRenderer.tsx (1 hunks)
  • mpp-vscode/webview/src/components/nano/index.ts (1 hunks)
  • mpp-vscode/webview/src/types/nano.ts (1 hunks)
  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt (4 hunks)
  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use expect/actual for platform-specific code in Kotlin Multiplatform projects
For @JsExport in Kotlin, use concrete classes (not interfaces) and Promise (not Flow) for JavaScript interoperability
Avoid emoji and UTF-8 characters in WASM code

Files:

  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt
  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
**/*Renderer.kt

📄 CodeRabbit inference engine (AGENTS.md)

When modifying CodingAgentRenderer in Kotlin, update ALL implementations: DefaultCodingAgentRenderer, ComposeRenderer, JewelRenderer, ServerSideRenderer, JsRendererAdapter

Files:

  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt
  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
**/*Compose*.kt

📄 CodeRabbit inference engine (AGENTS.md)

For Compose UI design, use AutoDevColors or MaterialTheme.colorScheme

Files:

  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
{**/Compose*.kt,mpp-ui/src/jsMain/typescript/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Never use hardcoded colors - always use design tokens from the design system

Files:

  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*Renderer.kt : When modifying `CodingAgentRenderer` in Kotlin, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, `JsRendererAdapter`
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*Renderer.ts : When modifying `CodingAgentRenderer` in TypeScript, update ALL implementations: `BaseRenderer.ts`, `CliRenderer.ts`, `ServerRenderer.ts`, `TuiRenderer.ts`
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*Renderer.ts : When modifying `CodingAgentRenderer` in TypeScript, update ALL implementations: `BaseRenderer.ts`, `CliRenderer.ts`, `ServerRenderer.ts`, `TuiRenderer.ts`

Applied to files:

  • mpp-vscode/webview/src/components/nano/NanoRenderer.tsx
  • mpp-vscode/webview/src/components/nano/index.ts
  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to mpp-vscode/src/{bridge,providers}/**/*.ts : When modifying `CodingAgentRenderer` in VSCode, update implementations in `mpp-vscode/src/bridge/mpp-core.ts` and `mpp-vscode/src/providers/chat-view.ts`

Applied to files:

  • mpp-vscode/webview/src/components/nano/NanoRenderer.tsx
  • mpp-vscode/webview/src/types/nano.ts
  • mpp-vscode/webview/src/components/nano/index.ts
  • mpp-vscode/webview/src/components/nano/NanoRenderer.css
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to mpp-ui/src/jsMain/typescript/**/*.ts : For CLI/TUI design, use `semanticInk`/`semanticChalk` from `mpp-ui/src/jsMain/typescript/design-system/`

Applied to files:

  • mpp-vscode/webview/src/types/nano.ts
  • mpp-vscode/webview/src/components/nano/index.ts
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*Renderer.kt : When modifying `CodingAgentRenderer` in Kotlin, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, `JsRendererAdapter`

Applied to files:

  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt
  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*{CliRenderer,ConsoleRenderer}.kt : When modifying `CodingAgentRenderer` for JVM CLI, update implementations in `CodingCliRenderer` and `ConsoleRenderer`

Applied to files:

  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt
  • xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*Compose*.kt : For Compose UI design, use `AutoDevColors` or `MaterialTheme.colorScheme`

Applied to files:

  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
🧬 Code graph analysis (1)
mpp-vscode/webview/src/components/nano/NanoRenderer.tsx (2)
mpp-vscode/webview/src/components/nano/index.ts (4)
  • NanoIR (11-11)
  • NanoRenderContext (12-12)
  • DEFAULT_THEME (18-18)
  • NanoRenderer (9-9)
mpp-vscode/webview/src/types/nano.ts (3)
  • NanoIR (56-80)
  • NanoRenderContext (85-94)
  • DEFAULT_THEME (143-163)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Agent
  • GitHub Check: Test mpp-core (KMP)
  • GitHub Check: Build
🔇 Additional comments (8)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt (1)

31-77: LGTM - well-structured component dispatch pattern.

The ComposeNanoRenderer object follows a clean component-based dispatch pattern with RenderNode routing to specific render methods. The categorization into Layout, Container, Content, Input, Control Flow, and Meta components is well-organized and consistent with the interface defined in NanoRenderer.kt.

xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt (1)

32-52: LGTM - Comprehensive renderNode dispatcher.

The renderNode implementation correctly dispatches all component types defined in the NanoRenderer interface, with proper fallback to renderUnknown for unrecognized types. The implementation is complete and type-safe.

xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt (1)

23-164: LGTM - Well-designed interface with compile-time safety.

The NanoRenderer<T> interface provides a comprehensive contract with component-specific render methods. This achieves the PR objective of compile-time enforcement - any new component added to this interface will require implementations in HtmlRenderer and other renderers, surfacing missing implementations at compile time rather than silently falling through to else branches.

mpp-vscode/webview/src/components/nano/NanoRenderer.css (1)

1-251: LGTM - Comprehensive VSCode-themed CSS.

The stylesheet provides thorough coverage of all NanoUI components with proper VSCode theme variable integration. The use of CSS custom properties ensures the UI adapts to the user's VSCode theme.

mpp-vscode/webview/src/components/nano/index.ts (1)

1-19: LGTM - Clean barrel export.

The barrel export provides a convenient single entry point for the NanoRenderer component and all related types. The documentation cross-references the Kotlin interface, which helps maintain consistency across platforms.

mpp-vscode/webview/src/components/nano/NanoRenderer.tsx (2)

33-45: LGTM - Clean main component with context merging.

The NanoRenderer component properly merges the provided partial context with defaults, ensuring all required context fields are available during rendering. The structure follows the component-specific pattern outlined in the PR objectives.


188-212: LGTM - Robust action dispatch handling.

The handleClick handler properly validates action types and required payload fields before dispatching. This defensive approach prevents runtime errors from malformed action definitions.

mpp-vscode/webview/src/types/nano.ts (1)

82-163: Types and default theme look consistent and ready to use

NanoRenderContext, NanoRenderAction, NanoTheme/NanoSpacingScale/NanoColorScheme, and DEFAULT_THEME are well‑structured and self‑contained. They give the renderer a clear contract for state, actions, and theming without over‑constraining usage.

No changes required here from my side.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the NanoRenderer architecture from string-based type matching with when statements to component-specific render methods, improving compile-time safety and discoverability of missing implementations across platforms.

Key Changes:

  • Replaced the single renderComponent method with dedicated methods for each component type (e.g., renderVStack, renderCard, renderButton)
  • Removed the supports() method in favor of an explicit renderUnknown() handler
  • Added missing component implementations: Form, TextArea, Select, and Divider across all platforms
  • Created new TypeScript/React renderer for VSCode webview with matching CSS styling

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.kt Refactored interface from single renderComponent method to 17 component-specific methods with comprehensive documentation
xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt Implements new interface with public override methods; added Form, TextArea, Select, Divider components; removed supports() method
mpp-vscode/webview/src/types/nano.ts New TypeScript type definitions matching Kotlin NanoIR data classes with theme and context types
mpp-vscode/webview/src/components/nano/index.ts Export file for NanoUI renderer components and types
mpp-vscode/webview/src/components/nano/NanoRenderer.tsx React implementation with component-specific render functions following the Kotlin pattern
mpp-vscode/webview/src/components/nano/NanoRenderer.css VSCode-integrated CSS using custom properties for theming
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt Refactored from private to public methods; added Form, TextArea, Select components; follows same dispatch pattern

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to 52
override fun renderNode(ir: NanoIR): String {
return when (ir.type) {
"Component" -> renderComponentNode(ir)
"Component" -> renderComponent(ir)
"VStack" -> renderVStack(ir)
"HStack" -> renderHStack(ir)
"Card" -> renderCard(ir)
"Form" -> renderForm(ir)
"Text" -> renderText(ir)
"Button" -> renderButton(ir)
"Image" -> renderImage(ir)
"Badge" -> renderBadge(ir)
"Divider" -> renderDivider(ir)
"Button" -> renderButton(ir)
"Input" -> renderInput(ir)
"Checkbox" -> renderCheckbox(ir)
"Divider" -> "<hr class=\"nano-divider\">"
"TextArea" -> renderTextArea(ir)
"Select" -> renderSelect(ir)
"Conditional" -> renderConditional(ir)
"ForLoop" -> renderForLoop(ir)
else -> "<!-- Unknown component: ${ir.type} -->"
else -> renderUnknown(ir)
}
}
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test file HtmlRendererTest.kt uses the old API methods renderComponent(ir) and renderer.supports(type) which have been removed/renamed in this refactoring. The method renderComponent is now specific to the "Component" type, and supports has been completely removed. These tests need to be updated to use renderNode(ir) instead of renderComponent(ir), and the supports test should be removed or replaced with a different approach.

Copilot uses AI. Check for mistakes.
Comment on lines 147 to 224
const RenderText: React.FC<{ ir: NanoIR; context: NanoRenderContext }> = ({ ir }) => {
const content = ir.props.content || '';
const style = ir.props.style || 'body';

const Tag = getTextTag(style);
return <Tag className={`nano-text style-${style}`}>{content}</Tag>;
};

function getTextTag(style: string): keyof JSX.IntrinsicElements {
switch (style) {
case 'h1': return 'h1';
case 'h2': return 'h2';
case 'h3': return 'h3';
case 'h4': return 'h4';
case 'caption': return 'small';
default: return 'p';
}
}

const RenderImage: React.FC<{ ir: NanoIR; context: NanoRenderContext }> = ({ ir }) => {
const src = ir.props.src || '';
const radius = ir.props.radius || 'none';

return <img src={src} className={`nano-image radius-${radius}`} alt="" />;
};

const RenderBadge: React.FC<{ ir: NanoIR; context: NanoRenderContext }> = ({ ir }) => {
const text = ir.props.text || '';
const color = ir.props.color || 'default';

return <span className={`nano-badge color-${color}`}>{text}</span>;
};

const RenderDivider: React.FC = () => {
return <hr className="nano-divider" />;
};

// ============================================================================
// Input Components
// ============================================================================

const RenderButton: React.FC<{ ir: NanoIR; context: NanoRenderContext }> = ({ ir, context }) => {
const label = ir.props.label || 'Button';
const intent = ir.props.intent || 'default';
const icon = ir.props.icon;

const handleClick = () => {
if (ir.actions?.onClick && context.dispatch) {
const action = ir.actions.onClick;
if (action.type === 'Navigate' && action.payload?.to) {
context.dispatch({ type: 'Navigate', to: action.payload.to });
} else if (action.type === 'Fetch' && action.payload?.url) {
context.dispatch({ type: 'Fetch', url: action.payload.url, method: action.payload.method });
} else if (action.type === 'ShowToast' && action.payload?.message) {
context.dispatch({ type: 'ShowToast', message: action.payload.message });
}
}
};

return (
<button className={`nano-button intent-${intent}`} onClick={handleClick}>
{icon && <span className="icon">{icon}</span>}
{label}
</button>
);
};

const RenderInput: React.FC<{ ir: NanoIR; context: NanoRenderContext }> = ({ ir }) => {
const placeholder = ir.props.placeholder || '';
const type = ir.props.type || 'text';

return (
<input
type={type}
className="nano-input"
placeholder={placeholder}
/>
);
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context parameter in RenderText, RenderImage, RenderBadge, and RenderInput is unused. While it's part of the function signature for consistency, TypeScript will report this as an unused parameter. Consider using the _context naming convention or adding // eslint-disable-next-line @typescript-eslint/no-unused-vars if this is intentional for API consistency.

Copilot uses AI. Check for mistakes.
Comment on lines 31 to 77
object ComposeNanoRenderer {

// ============================================================================
// Main Entry Point
// ============================================================================

/**
* Render a NanoIR node to Compose UI.
* Dispatches to component-specific render methods.
*/
@Composable
fun Render(ir: NanoIR, modifier: Modifier = Modifier) {
RenderNode(ir, modifier)
}

/**
* Dispatch rendering based on component type.
* Routes to the appropriate component-specific render method.
*/
@Composable
fun RenderNode(ir: NanoIR, modifier: Modifier = Modifier) {
when (ir.type) {
"Component" -> RenderComponent(ir, modifier)
// Layout
"VStack" -> RenderVStack(ir, modifier)
"HStack" -> RenderHStack(ir, modifier)
// Container
"Card" -> RenderCard(ir, modifier)
"Form" -> RenderForm(ir, modifier)
// Content
"Text" -> RenderText(ir, modifier)
"Button" -> RenderButton(ir, modifier)
"Image" -> RenderImage(ir, modifier)
"Badge" -> RenderBadge(ir, modifier)
"Divider" -> RenderDivider(ir, modifier)
// Input
"Button" -> RenderButton(ir, modifier)
"Input" -> RenderInput(ir, modifier)
"Checkbox" -> RenderCheckbox(ir, modifier)
"Divider" -> HorizontalDivider(modifier.padding(vertical = 8.dp))
"TextArea" -> RenderTextArea(ir, modifier)
"Select" -> RenderSelect(ir, modifier)
// Control Flow
"Conditional" -> RenderConditional(ir, modifier)
"ForLoop" -> RenderForLoop(ir, modifier)
// Meta
"Component" -> RenderComponent(ir, modifier)
else -> RenderUnknown(ir, modifier)
}
}
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The ComposeNanoRenderer object doesn't implement the NanoRenderer<Unit> interface, even though the PR description mentions all platforms should follow the same pattern. While Compose's @Composable functions make direct interface implementation challenging, consider whether this should be documented more clearly or if a wrapper class implementing the interface should be provided for consistency with other renderers.

Copilot uses AI. Check for mistakes.
height: 18px;
cursor: pointer;
}

Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSS file is missing styles for .nano-checkbox-wrapper which is used in the React component (line 230 of NanoRenderer.tsx). The wrapper is used to associate the label with the checkbox but has no styling defined. Consider adding appropriate styles for the wrapper, such as:

.nano-checkbox-wrapper {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}
Suggested change
.nano-checkbox-wrapper {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}

Copilot uses AI. Check for mistakes.
Comment on lines +228 to +232
const label = ir.props.label;
return (
<label className="nano-checkbox-wrapper">
<input type="checkbox" className="nano-checkbox" />
{label && <span>{label}</span>}
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TypeScript implementation supports a label prop for checkboxes (line 228), but the HtmlRenderer (line 162-163) and ComposeNanoRenderer (line 258-265) do not. This creates an inconsistency where checkbox labels work in the VSCode webview but not in HTML or Compose renderers. Consider either:

  1. Adding label support to HtmlRenderer and ComposeNanoRenderer, or
  2. Documenting this as a platform-specific feature, or
  3. Removing label support from the TypeScript implementation

The NanoRenderer interface documentation (line 116-118) doesn't mention a label prop, suggesting it may not be part of the official API.

Suggested change
const label = ir.props.label;
return (
<label className="nano-checkbox-wrapper">
<input type="checkbox" className="nano-checkbox" />
{label && <span>{label}</span>}
return (
<label className="nano-checkbox-wrapper">
<input type="checkbox" className="nano-checkbox" />

Copilot uses AI. Check for mistakes.
Comment on lines 169 to 170

return <img src={src} className={`nano-image radius-${radius}`} alt="" />;
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <img> element has an empty alt="" attribute. While this is valid HTML, it signals to screen readers that the image is decorative. Consider either:

  1. Using a prop from ir.props.alt to allow meaningful alt text, or
  2. Using a default descriptive alt text if no alt prop is provided, or
  3. If images are truly decorative, consider documenting this decision

The NanoRenderer interface documentation (line 83-85) doesn't specify an alt prop for images, but it would improve accessibility.

Suggested change
return <img src={src} className={`nano-image radius-${radius}`} alt="" />;
const alt = ir.props.alt ?? 'Image';
return <img src={src} className={`nano-image radius-${radius}`} alt={alt} />;

Copilot uses AI. Check for mistakes.
Comment on lines 254 to 256
<select className="nano-select">
{placeholder && (
<option value="" disabled selected>{placeholder}</option>
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecated selected attribute should be replaced with defaultSelected in React. Using selected on <option> elements is a common anti-pattern in React as it tries to make forms controlled. The correct approach is to use defaultValue on the <select> element or defaultSelected on the <option> element for uncontrolled components.

Suggested change
<select className="nano-select">
{placeholder && (
<option value="" disabled selected>{placeholder}</option>
<select className="nano-select" {...(placeholder ? { defaultValue: "" } : {})}>
{placeholder && (
<option value="" disabled>{placeholder}</option>

Copilot uses AI. Check for mistakes.
flex-direction: column;
gap: 16px;
}
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HtmlRenderer is missing CSS styling for .nano-checkbox. While the HTML is generated on line 163, the corresponding CSS class is not defined in the generateCss() method. This creates an inconsistency where the class is applied but has no styling. Consider adding basic checkbox styling in the CSS generation.

Suggested change
.nano-checkbox {
width: 16px;
height: 16px;
accent-color: #1976D2;
margin-right: 8px;
vertical-align: middle;
}

Copilot uses AI. Check for mistakes.
val aspect = ir.props["aspect"]?.jsonPrimitive?.content
val radius = ir.props["radius"]?.jsonPrimitive?.content ?: "none"
val aspectClass = aspect?.replace("/", "-") ?: "auto"
return "<img src=\"$src\" class=\"nano-image aspect-$aspectClass radius-$radius\" alt=\"\">\n"
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <img> element has an empty alt="" attribute. While this is valid HTML, it signals to screen readers that the image is decorative. Consider either:

  1. Extracting an alt prop from ir.props to allow meaningful alt text, or
  2. Using a default descriptive alt text, or
  3. If images are truly decorative, document this decision

This same issue exists in the TypeScript renderer (NanoRenderer.tsx line 170).

Suggested change
return "<img src=\"$src\" class=\"nano-image aspect-$aspectClass radius-$radius\" alt=\"\">\n"
val alt = ir.props["alt"]?.jsonPrimitive?.content ?: "Image"
return "<img src=\"$src\" class=\"nano-image aspect-$aspectClass radius-$radius\" alt=\"$alt\">\n"

Copilot uses AI. Check for mistakes.
- Add missing .nano-checkbox-wrapper CSS in NanoRenderer.css
- Use defaultValue on <select> instead of selected attribute in NanoRenderer.tsx
- Add alt prop support to RenderImage in TypeScript and Kotlin renderers
- Add label support to renderCheckbox in HtmlRenderer.kt
- Add .nano-checkbox CSS styling to HtmlRenderer.kt
- Update HtmlRendererTest.kt to use renderNode instead of deprecated renderComponent
@phodal phodal merged commit 5c42f21 into master Dec 6, 2025
1 check was pending
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor NanoRenderer to use component-specific render methods for better cross-platform support

2 participants