-
Notifications
You must be signed in to change notification settings - Fork 471
refactor(nano): add component-specific render methods to NanoRenderer #488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- 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
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughRefactors 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (4)
Comment |
There was a problem hiding this 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
RenderBadgefunction uses hardcoded hex colors (Color(0xFF4CAF50),Color(0xFFF44336), etc.) which violates the coding guidelines. As per coding guidelines, useAutoDevColorsorMaterialTheme.colorSchemeinstead 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("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'") + } + 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
StateMutationaction accepts anypath: Stringandvalue: 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-yellowclass uses a hardcoded#FFEB3Bwithout 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: AlignNanoIR.typewith a string‑literal union for known component typesYou 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: stringloses 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: NarrowNanoBindingIR.modeto the documented modesThe docstring says
modeis"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
NanoRenderActionis modeled and gives better autocomplete + validation in the webview code.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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: Useexpect/actualfor 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.ktxuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
**/*Renderer.kt
📄 CodeRabbit inference engine (AGENTS.md)
When modifying
CodingAgentRendererin Kotlin, update ALL implementations:DefaultCodingAgentRenderer,ComposeRenderer,JewelRenderer,ServerSideRenderer,JsRendererAdapter
Files:
xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/NanoRenderer.ktxuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/nano/ComposeNanoRenderer.kt
**/*Compose*.kt
📄 CodeRabbit inference engine (AGENTS.md)
For Compose UI design, use
AutoDevColorsorMaterialTheme.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.tsxmpp-vscode/webview/src/components/nano/index.tsxuiper-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.tsxmpp-vscode/webview/src/types/nano.tsmpp-vscode/webview/src/components/nano/index.tsmpp-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.tsmpp-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.ktxuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.ktmpp-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.ktxuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.ktmpp-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
ComposeNanoRendererobject follows a clean component-based dispatch pattern withRenderNoderouting 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 inNanoRenderer.kt.xuiper-ui/src/main/kotlin/cc/unitmesh/xuiper/render/HtmlRenderer.kt (1)
32-52: LGTM - Comprehensive renderNode dispatcher.The
renderNodeimplementation correctly dispatches all component types defined in theNanoRendererinterface, with proper fallback torenderUnknownfor 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 inHtmlRendererand other renderers, surfacing missing implementations at compile time rather than silently falling through toelsebranches.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
NanoRenderercomponent 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
handleClickhandler 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, andDEFAULT_THEMEare 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.
There was a problem hiding this 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
renderComponentmethod with dedicated methods for each component type (e.g.,renderVStack,renderCard,renderButton) - Removed the
supports()method in favor of an explicitrenderUnknown()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.
| 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) | ||
| } | ||
| } |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| 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} | ||
| /> | ||
| ); |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| 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) | ||
| } | ||
| } |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| height: 18px; | ||
| cursor: pointer; | ||
| } | ||
|
|
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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;
}| .nano-checkbox-wrapper { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| cursor: pointer; | |
| } |
| const label = ir.props.label; | ||
| return ( | ||
| <label className="nano-checkbox-wrapper"> | ||
| <input type="checkbox" className="nano-checkbox" /> | ||
| {label && <span>{label}</span>} |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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:
- Adding label support to HtmlRenderer and ComposeNanoRenderer, or
- Documenting this as a platform-specific feature, or
- 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.
| 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" /> |
|
|
||
| return <img src={src} className={`nano-image radius-${radius}`} alt="" />; |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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:
- Using a prop from
ir.props.altto allow meaningful alt text, or - Using a default descriptive alt text if no alt prop is provided, or
- 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.
| 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} />; |
| <select className="nano-select"> | ||
| {placeholder && ( | ||
| <option value="" disabled selected>{placeholder}</option> |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| <select className="nano-select"> | |
| {placeholder && ( | |
| <option value="" disabled selected>{placeholder}</option> | |
| <select className="nano-select" {...(placeholder ? { defaultValue: "" } : {})}> | |
| {placeholder && ( | |
| <option value="" disabled>{placeholder}</option> |
| flex-direction: column; | ||
| gap: 16px; | ||
| } | ||
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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.
| .nano-checkbox { | |
| width: 16px; | |
| height: 16px; | |
| accent-color: #1976D2; | |
| margin-right: 8px; | |
| vertical-align: middle; | |
| } |
| 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" |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
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:
- Extracting an
altprop fromir.propsto allow meaningful alt text, or - Using a default descriptive alt text, or
- If images are truly decorative, document this decision
This same issue exists in the TypeScript renderer (NanoRenderer.tsx line 170).
| 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" |
- 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
Summary
Refactor NanoRenderer to use component-specific methods instead of
whenstatement string matching.Problem
The current
NanoRendereruseswhen (ir.type)string matching to dispatch rendering, which:elsebranchSolution
Following the pattern from
CodingAgentRenderer, each component type now has a dedicated render method:Changes
NanoRendererinterface: Added component-specific methodsHtmlRenderer: Implements all new methods, added missing components (Form, TextArea, Select, Divider)ComposeNanoRenderer: Refactored to follow same pattern withRender*functionsNanoRenderer.tsximplementation with types and CSSBenefits
Closes #487
Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.