SessionTask UI System

Server-driven reactive UI framework for building AI-powered interfaces. Write Kotlin, stream HTML, create interactive workflows.

Core Framework WebSocket Streaming Real-time Updates

Architecture Overview

The SessionTask UI system is fundamentally server-driven. Instead of writing JavaScript to manipulate the DOM, you write Kotlin code that generates HTML fragments. These fragments are pushed to the browser via WebSockets, where they're rendered in real-time.

Kotlin Server
SessionTask generates HTML
โ†’
SocketManager
WebSocket connection
โ†’
Browser
DOM updates via messageID
Key Insight: Each SessionTask owns a unique messageID that maps to a <div> in the DOM. When you call task.add(), content is appended to that div. When you call task.update(), the entire div is replaced with the current buffer state.

Reality Check: Code โ†’ Output

๐Ÿ“ Kotlin Code
fun analyzeCode(task: SessionTask) {
    task.header("Code Analysis", level = 2)
    
    // Create a sub-task for streaming results
    val resultsTask = task.newTask()
    
    task.add("Scanning repository...")
    
    // Stream findings as they're discovered
    for (finding in analyzer.scan()) {
        resultsTask.add("""
            <div class="finding">
                <strong>${finding.file}</strong>
                <p>${finding.message}</p>
            </div>
        """)
    }
    
    resultsTask.complete()
    task.add("โœ“ Analysis complete")
    task.complete()
}
๐Ÿ–ฅ๏ธ Browser Output

Code Analysis

src/main/App.kt

Unused import detected

src/utils/Helper.kt

Consider extracting method

Scanning repository...

โœ“ Analysis complete

Core Components

๐Ÿ“‹
SessionTask
The primary canvas for UI output. Manages an HTML buffer, supports streaming, and handles lifecycle (spinner โ†’ content โ†’ complete).
๐Ÿ”Œ
SocketManager
Handles WebSocket connections, routes events, manages file paths, and provides the hrefLink and textInput APIs.
๐Ÿ“‘
TabbedDisplay
Dynamic tabbed interface. Add, update, or remove tabs programmatically. Supports embedding live streaming tasks inside tabs.
๐Ÿ”„
Retryable
Extends TabbedDisplay for operations that might fail. Adds a "Recycle" button to re-run the operation in a new tab.
๐Ÿ’ฌ
Discussable
Human-in-the-loop component. Generate โ†’ Review โ†’ Revise workflow with chat-based feedback and explicit acceptance.
๐Ÿ“
File Management
Save files to session directories, generate download links, create live-updating log streams.

Rendering Methods

kotlin
// Simple text (wrapped in div)
task.add("Hello, World!")

// Headers (H1-H6)
task.header("Analysis Results", level = 2)

// HTML with custom CSS classes
task.add("<b>Important</b>", additionalClasses = "alert alert-warning")

// User-style message (right-aligned)
task.echo("User prompt appears here")

// Dismissible message (has close button)
task.hideable("<b>Note:</b> Click X to dismiss")

// Verbose/debug output (hidden by default)
task.verbose("Detailed debug information...")

// Error display with expandable stack trace
try {
    riskyOperation()
} catch (e: Exception) {
    task.error(e)
}

// Mark task as complete (removes spinner)
task.complete()

Human-in-the-Loop: Discussable

The Discussable component implements a powerful Generate โ†’ Review โ†’ Revise workflow. It blocks execution until the user explicitly accepts the result.

1

Initial Generation

The initialResponse function generates content based on the user's prompt.

2

Review Interface

Content is displayed with a chat box for feedback and an "Accept" button.

3

Revision Loop

If the user provides feedback, reviseResponse generates a new version in a new tab.

4

Acceptance

When "Accept" is clicked, the function returns the final approved object.

kotlin
val finalResult = Discussable(
    task = task,
    heading = "Drafting Email",
    userMessage = { "Draft an email to the team about the release" },
    initialResponse = { prompt ->
        EmailDraft(llm.generate(prompt))
    },
    outputFn = { draft ->
        // Render the draft as HTML for review
        draft.toHtml()
    },
    reviseResponse = { history ->
        // history is List<Pair<String, Role>>
        // Contains user feedback + assistant responses
        llm.chat(history)
    }
).call() // Blocks until user clicks "Accept"

task.add("Final approved email: ${finalResult.subject}")

Best Practices

โœ…
Always Complete Tasks
Call task.complete() when work is done. Otherwise, the spinner spins forever, making the UI look unresponsive.
๐Ÿงต
Thread Safety
SessionTask methods are safe to call from background threads. Use task.ui.pool for heavy computations.
๐Ÿ”’
Security
SocketManager checks AuthorizationManager before allowing reads/writes. Configure your auth interface properly.
โฑ๏ธ
Blocking Awareness
Discussable is blocking. Retryable uses a thread pool. Use task.ui.scheduledThreadPoolExecutor for delayed execution.

Quick API Reference

kotlin
// SessionTask Methods
task.add(html: String, additionalClasses: String = ""): StringBuilder?
task.header(text: String, level: Int = 1)
task.echo(message: String)
task.hideable(html: String): StringBuilder?
task.verbose(message: String)
task.error(e: Throwable)
task.expandable(title: String, content: String)
task.expanded(title: String, content: String)
task.image(image: BufferedImage)
task.append(html: String, showSpinner: Boolean = true)
task.update()
task.complete()
task.newTask(): SessionTask
task.saveFile(path: String, data: ByteArray): String
task.newLogStream(name: String): OutputStream
task.linkedTask(label: String): SessionTask

// SocketManager Methods (via task.ui)
task.ui.hrefLink(text: String, handler: () -> Unit): String
task.ui.textInput(handler: (String) -> Unit): String
task.ui.newTask(root: Boolean = true, cancelable: Boolean = false): SessionTask
task.ui.linkToSession(text: String): String
task.ui.pool: ExecutorService
task.ui.scheduledThreadPoolExecutor: ScheduledExecutorService

// TabbedDisplay
val tabs = TabbedDisplay(task, closable: Boolean = true)
tabs[label: String] = content: String
tabs.newTask(label: String): SessionTask
tabs.delete(label: String)
tabs.clear()

// Retryable
Retryable.retryable(ui: SocketManager) { subTask -> ... }

// Discussable
Discussable(task, heading, userMessage, initialResponse, outputFn, reviseResponse).call()

Start Building

The SessionTask UI system powers all of Cognotik's interactive features. Explore the source code or dive into the documentation.