generated from JetBrains/intellij-platform-plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 471
Closed
Description
Problem
Currently, NanoAction definitions in NanoIR are not functional. Actions like on_click, AddTask, DeleteTask exist in the DSL but cannot be executed by renderers across platforms.
Current State
// NanoAction.kt - Well-defined action types
sealed class NanoAction {
data class StateMutation(val path: String, val operation: MutationOp, val value: String) : NanoAction()
data class Navigate(val to: String) : NanoAction()
data class Fetch(val url: String, val method: HttpMethod, ...) : NanoAction()
data class ShowToast(val message: String) : NanoAction()
data class Sequence(val actions: List<NanoAction>) : NanoAction()
}But renderers only store actions as data attributes without execution:
// HtmlRenderer.kt
if (onSubmit != null) append(" data-action=\"$onSubmit\"")Use Case
component TaskList(tasks: List[Task]):
state:
new_task: str = ""
Card:
HStack(spacing="sm"):
Input(value := state.new_task, placeholder="Add new task...")
Button("+", intent="primary"):
on_click:
AddTask(title=state.new_task) # Custom action
state.new_task = "" # State mutation
for task in tasks:
HStack:
Checkbox(checked := task.done)
Text(task.title)
Button("Delete", intent="danger"):
on_click: DeleteTask(id=task.id)
Proposed Solution
1. Action Handler Interface
Create a platform-agnostic action handler interface:
// NanoActionHandler.kt
interface NanoActionHandler {
fun handleAction(action: NanoAction, context: NanoContext): ActionResult
// Built-in action handlers
fun handleStateMutation(mutation: StateMutation, context: NanoContext): ActionResult
fun handleNavigate(navigate: Navigate, context: NanoContext): ActionResult
fun handleFetch(fetch: Fetch, context: NanoContext): ActionResult
fun handleShowToast(toast: ShowToast, context: NanoContext): ActionResult
fun handleSequence(sequence: Sequence, context: NanoContext): ActionResult
// Custom action hook - for user-defined actions like AddTask, DeleteTask
fun handleCustomAction(name: String, payload: Map<String, Any>, context: NanoContext): ActionResult
}
sealed class ActionResult {
object Success : ActionResult()
data class Error(val message: String) : ActionResult()
data class Async(val promise: Deferred<ActionResult>) : ActionResult()
}2. State Management
Add reactive state container:
// NanoState.kt
class NanoState(initialState: Map<String, Any>) {
private val _state = MutableStateFlow(initialState)
val state: StateFlow<Map<String, Any>> = _state.asStateFlow()
fun mutate(path: String, op: MutationOp, value: Any) {
// Apply mutation and emit new state
}
fun get(path: String): Any?
fun set(path: String, value: Any)
}3. Platform Implementations
Compose (Kotlin)
class ComposeActionHandler : NanoActionHandler {
private val navController: NavController? = null
private val snackbarHost: SnackbarHostState? = null
override fun handleNavigate(navigate: Navigate, context: NanoContext): ActionResult {
navController?.navigate(navigate.to)
return ActionResult.Success
}
override fun handleShowToast(toast: ShowToast, context: NanoContext): ActionResult {
scope.launch { snackbarHost?.showSnackbar(toast.message) }
return ActionResult.Success
}
override fun handleCustomAction(name: String, payload: Map<String, Any>, context: NanoContext): ActionResult {
// Dispatch to user-provided custom action registry
return customActionRegistry[name]?.invoke(payload, context) ?: ActionResult.Error("Unknown action: $name")
}
}React (TypeScript)
interface NanoActionHandler {
handleAction(action: NanoAction, context: NanoContext): Promise<ActionResult>;
onCustomAction?: (name: string, payload: Record<string, any>) => Promise<ActionResult>;
}
const ReactActionHandler: NanoActionHandler = {
async handleAction(action, context) {
switch (action.type) {
case "StateMutation":
context.setState(prev => applyMutation(prev, action));
return { success: true };
case "Navigate":
window.location.href = action.to;
return { success: true };
case "Fetch":
return await fetch(action.url, { method: action.method, body: action.body });
case "ShowToast":
toast(action.message);
return { success: true };
default:
return this.onCustomAction?.(action.type, action.payload) ?? { error: "Unknown action" };
}
}
};4. Renderer Integration
Update NanoRenderer to accept action handler:
interface NanoRenderer<T> {
// Existing methods...
// New: Action binding
fun bindActionHandler(handler: NanoActionHandler)
// Or pass in context
fun render(ir: NanoIR, context: NanoRenderContext): T
}
data class NanoRenderContext(
val state: NanoState,
val actionHandler: NanoActionHandler,
val theme: NanoTheme
)5. Custom Action Registry
For user-defined actions like AddTask, DeleteTask:
// User code
val customActions = mapOf(
"AddTask" to { payload, context ->
val title = payload["title"] as String
taskRepository.add(Task(title))
ActionResult.Success
},
"DeleteTask" to { payload, context ->
val id = payload["id"] as String
taskRepository.delete(id)
ActionResult.Success
}
)
// Pass to renderer
val actionHandler = ComposeActionHandler(customActions)
NanoRenderer(ir, NanoRenderContext(state, actionHandler, theme))Implementation Plan
- Create
NanoActionHandlerinterface inxuiper-ui - Create
NanoStatereactive state container - Create
NanoRenderContextto bundle state + actions + theme - Implement
ComposeActionHandlerfor Compose - Implement
ReactActionHandlerfor VSCode/React - Update
HtmlRendererto generate JavaScript action bindings - Add custom action registry mechanism
- Write integration tests
References
- DivKit Action Protocol: https://divkit.tech/en/doc/overview/concepts/actions/
- Flutter BLoC Pattern
- Redux Action/Reducer pattern
Metadata
Metadata
Assignees
Labels
No labels