SessionTask UI System

Badges: Core · Server-Driven UI

Build reactive, AI-powered interfaces using pure Kotlin. Stream content, manage complex tasks, and create interactive human-in-the-loop workflows.

Reality Check: Kotlin to Browser

Source (analyze_task.kt):

fun analyze(task: SessionTask) {
    task.header("Analysis Results", level = 2)
    val results = task.newTask() // Sub-task
    task.add("Scanning repository...")

    // Stream findings
    findings.forEach { f ->
        results.add("<div class='finding'>${f.msg}</div>")
    }

    results.complete()
    task.add("✓ Done")
    task.complete()
}

Live Preview (rendered):

UI Component Library

API Reference

Rendering

// Basic content
task.add("Hello **World**!", markdown = true)
task.header("Title", level = 2)
task.echo("User prompt style")

// Specialized output
task.hideable("Dismissible message")
task.verbose("Debug info (hidden by default)")
task.error(exception) // Renders red box + stack trace
task.image(bufferedImage) // Auto-saves and renders <img>

// Markdown & Mermaid
val html = MarkdownUtil.renderMarkdown(raw, ui = task.ui)
task.add(html)

Interaction

// Server-side link triggers
val link = task.ui.hrefLink("Click Me") {
    task.add("Link clicked!")
}
task.add("Action: $link")

// User input
val input = task.ui.textInput { response ->
    task.add("You typed: $response")
}
task.add(input)

// Expandable sections
task.expandable("Logs", "<pre>...</pre>")

Layout & Tasks

// Sub-task management
val sub = task.newTask() // Reserves spot in stream
val detached = task.ui.newTask(false) // Manual placement
task.add("<div>${detached.placeholder}</div>")

// Tabbed Layouts
val tabs = TabbedDisplay(task)
tabs["Summary"] = "Static content"
val liveTab = tabs.newTask("Live") // Stream into tab
liveTab.add("Updating...")

// Agent Utilities
task.add(AgentPatterns.displayMapInTabs(fileMap))

Dynamic Updates

// Dynamic content updates
val statusBuffer = task.add("Starting process...")
for (i in 1..5) {
    Thread.sleep(500)
    statusBuffer?.setLength(0)
    statusBuffer?.append("Processing step $i/5...")
    task.update() // Push changes to client
}
statusBuffer?.setLength(0)
statusBuffer?.append("<strong>Done!</strong>")
task.update()
task.complete()

Best Practices

Advanced Workflow Patterns

Discussable: Human-in-the-Loop

val finalResult = Discussable(
    task = task,
    heading = "Drafting Email",
    userMessage = { "Draft an email" },
    initialResponse = { prompt ->
        MyObject(llm.generate(prompt))
    },
    outputFn = { design ->
        design.toHtml()
    },
    reviseResponse = { history ->
        llm.chat(history)
    }
).call() // Blocks until Accept

Flow: Generate → Review → Revise → Accept

Retryable: LLM Regeneration

val retryable = Retryable(task) { buffer ->
    // Runs on init and each recycle
    val result = performExpensiveOperation()
    result // Return HTML content
}
// Each retry creates a new tab
// User clicks ♻ to regenerate

Use case: LLM generations that may need multiple attempts