Agent Skill
2/7/2026

update-tutorial

ćƒćƒ„ćƒ¼ćƒˆćƒŖć‚¢ćƒ«ļ¼ˆdocs/TUTORIAL.mdļ¼‰ć‚’ęœ€ę–°ć®å®Ÿč£…ć«åˆć‚ć›ć¦ę›“ę–°ć™ć‚‹ć€‚ę–°ć—ć„ć‚¦ć‚£ć‚øć‚§ćƒƒćƒˆć€APIć€ę©Ÿčƒ½ćŒčæ½åŠ ć•ć‚ŒćŸå¾Œć«ä½æē”Øć™ć‚‹ć€‚

W
watany
9GitHub Stars
1Views
npx skills add watany-dev/kantan-ui

SKILL.md

Nameupdate-tutorial
Descriptionćƒćƒ„ćƒ¼ćƒˆćƒŖć‚¢ćƒ«ļ¼ˆdocs/TUTORIAL.mdļ¼‰ć‚’ęœ€ę–°ć®å®Ÿč£…ć«åˆć‚ć›ć¦ę›“ę–°ć™ć‚‹ć€‚ę–°ć—ć„ć‚¦ć‚£ć‚øć‚§ćƒƒćƒˆć€APIć€ę©Ÿčƒ½ćŒčæ½åŠ ć•ć‚ŒćŸå¾Œć«ä½æē”Øć™ć‚‹ć€‚

kantan-ui

A Streamlit-style UI framework that depends only on Web standards and Hono.

Features

  • Simple - Declarative API like Streamlit (kt.button(), kt.slider(), etc.)
  • Real-time - Instant UI updates via WebSocket with multi-tab sync support
  • Lightweight - Depends only on Hono, supports multiple runtimes (Bun, Node.js, Deno, AWS Lambda)
  • Session Management - Automatic state management for multiple users
  • Connection Stability - Ping/Pong, auto-reconnect, sequence-based patch recovery
  • Streaming - Progressive rendering for large UIs
  • Security - Magic byte validation, polyglot detection, and XSS protection for file uploads

Quick Start

Requirements

  • Bun v1.0+ (recommended)
  • Or Node.js v18+, Deno

Installation

bun install

Start Development Server

bun run dev

Open http://localhost:3000 in your browser to see the demo app.

Usage

Basic App (Declarative API)

import { createApp, kt, createTypedSessionState } from "kantan-ui";

// Define type-safe session state
type AppState = {
  count: number;
};

const state = createTypedSessionState<AppState>({
  count: 0,  // Default value
});

const script = () => {
  kt.title("Counter App");

  // Button returns true when clicked
  if (kt.button("+ Increment")) {
    state.count++;  // Type-safe! No type assertion needed
  }

  kt.write(`Count: ${state.count}`);

  // Return undefined when using declarative API
  return undefined;
};

// All runtimes: create app with await createApp
export default await createApp(script, { port: 3000 });

Starting the Server

Bun (Recommended)

bun run src/index.ts

Bun automatically starts a server when export default returns an object with fetch/websocket/port.

Node.js

import { createApp } from "kantan-ui";
import { serve } from "kantan-ui/serve";

const app = await createApp(script);
serve(app, { port: 3000 });

AWS Lambda

import { createApp } from "kantan-ui";
import { createLambdaHandler } from "kantan-ui/lambda";

const kantanApp = await createApp(script);
export const handler = createLambdaHandler(kantanApp);

Supports API Gateway v1 (REST API), v2 (HTTP API), ALB, and Lambda Function URLs.

For response streaming (Lambda Function URL / Lambda Web Adapter):

import { createLambdaStreamHandler } from "kantan-ui/lambda";

const kantanApp = await createApp(script);
export const handler = createLambdaStreamHandler(kantanApp);

kt API (Declarative API)

The kt object lets you build UI intuitively like Streamlit. Each function automatically outputs HTML and returns appropriate values.

Output API

import { kt } from "kantan-ui";

kt.title("Title");           // <h1>
kt.header("Header");         // <h2>
kt.subheader("Subheader");   // <h3>
kt.write("Text");            // Text output
kt.text("Text");             // Alias for write
kt.divider();                // Horizontal rule <hr>
kt.html("<div>Raw HTML</div>"); // Raw HTML output (caution: XSS risk)
kt.markdown("**Bold** text");   // Markdown rendering
kt.caption("Small annotation text");  // Small caption text (supports Markdown)
kt.code("const x = 1;", "typescript"); // Code block with syntax highlighting
kt.json({ key: "value" });   // Collapsible JSON viewer

// Link button - opens URL in new tab
kt.link_button("Visit Docs", "https://docs.example.com");
kt.link_button("Disabled", "https://example.com", { disabled: true });
kt.link_button("Full Width", "https://example.com", { use_container_width: true });

Streaming API

Display text progressively from streaming sources (ideal for LLM responses).

// AsyncGenerator (LLM-style streaming)
async function* generateResponse() {
  yield "Hello, ";
  await new Promise(r => setTimeout(r, 100));
  yield "World!";
}
const fullText = await kt.write_stream(generateResponse());

// Array (instant display)
await kt.write_stream(["Item 1, ", "Item 2, ", "Item 3"]);

// With Markdown rendering on completion
await kt.write_stream(["# Title\n", "\nThis is **bold** text."], {
  markdown: true,
});

// Custom CSS class
await kt.write_stream(chunks, { className: "my-stream" });

// ReadableStream (Web standard)
const stream = new ReadableStream<string>({
  start(controller) {
    controller.enqueue("Streaming...");
    controller.close();
  }
});
await kt.write_stream(stream);

// Response from fetch
const response = await fetch("/api/stream");
await kt.write_stream(response);

Supported Sources:

  • AsyncIterable<string> - async generators, LLM streams
  • Iterable<string> - arrays, iterators
  • ReadableStream<string> - Web standard streams
  • Response - fetch API responses
  • Factory functions returning any of the above

Options:

  • markdown: boolean - Render as Markdown when stream completes
  • className: string - Custom CSS class for styling

Features:

  • Blinking cursor during streaming
  • Automatic cursor removal on completion
  • XSS-safe text rendering
  • Multiple concurrent streams supported

Alert API

kt.success("Operation completed!");
kt.error("Something went wrong");
kt.warning("Please check your input");
kt.info("Here's some information");

Feedback API

// Progress bar (0-1 or 0-100 auto-detected)
kt.progress(0.75);
kt.progress(75, { label: "Downloading..." });

// Loading spinner
kt.spinner("Processing...");

// Toast notification
kt.toast("Saved successfully!");
kt.toast("Error occurred", { type: "error" });

Data Display API

// Table - supports various data formats
kt.table([
  { name: "Alice", age: 30 },
  { name: "Bob", age: 25 },
]);

// 2D array format
kt.table([
  ["Name", "Age"],
  ["Alice", 30],
  ["Bob", 25],
]);

// Explicit header specification
kt.table({
  columns: ["Name", "Age"],
  data: [["Alice", 30], ["Bob", 25]],
});

// Dataframe - interactive data table with sort, search, and row selection
kt.dataframe([
  { name: "Alice", age: 30, city: "Tokyo" },
  { name: "Bob", age: 25, city: "Osaka" },
]);

// With options
const selection = kt.dataframe(data, {
  height: 400,              // Container height in px
  hideIndex: true,           // Hide row index column
  columnOrder: ["city", "name"], // Reorder columns
  onSelect: "rerun",         // Enable row selection ("ignore" | "rerun")
  selectionMode: "multi-row", // "single-row" | "multi-row"
  key: "my_dataframe",
});

if (selection) {
  kt.write(`Selected rows: ${selection.rows.join(", ")}`);
}

// Metric - KPI display with optional delta
kt.metric("Revenue", "$1,234");
kt.metric("Revenue", "$1,234", { delta: "+12%" });
kt.metric("Response Time", "120ms", { delta: "+15ms", delta_color: "inverse" });

Page Config API

// Page settings (title, layout, etc.)
kt.set_page_config({
  title: "My App",
  layout: "wide",  // "centered" | "wide"
  icon: "šŸš€",
});

// Force script re-execution
kt.rerun();

Widget API

// Button - returns true when clicked
if (kt.button("Click me", { key: "my_button" })) {
  // Handle button click
}

// Slider - returns current value
const volume = kt.slider("Volume", 0, 100, 50, { key: "volume" });

// Text input - returns current input value
const name = kt.text_input("Your name", "Default", { key: "name" });

// Selectbox - returns selected value
const color = kt.selectbox("Color", ["Red", "Green", "Blue"], "Blue", { key: "color" });

// Download button - triggers file download
kt.download_button("Download CSV", "name,age\nAlice,30", "data.csv", {
  mime: "text/csv",
});

// Checkbox - returns boolean
const agreed = kt.checkbox("I agree", false, { key: "agree" });

// Toggle - returns boolean (switch style)
const darkMode = kt.toggle("Dark mode", false, { key: "dark_mode" });

// Radio buttons - returns selected value
const size = kt.radio("Size", ["S", "M", "L"], "M", { key: "size" });

// Number input - returns number
const age = kt.number_input("Age", 0, 120, 25, { key: "age", step: 1 });

// Text area - returns multiline text
const bio = kt.text_area("Bio", "Tell us about yourself...", { key: "bio" });

// Multiselect - returns array of selected values
const tags = kt.multiselect("Tags", ["Tech", "Design", "Business"], [], { key: "tags" });

// Date input - returns "YYYY-MM-DD" string
const birthday = kt.date_input("Birthday", "2000-01-15", {
  min: "1900-01-01",
  max: "2024-12-31",
});

// Time input - returns "HH:MM" string
const alarm = kt.time_input("Alarm", "08:30", { step: 60 });

// Datetime input - returns "YYYY-MM-DDTHH:MM" string (kantan-ui unique)
const start = kt.datetime_input("Start time", "2026-01-15T09:00", {
  min: "2026-01-01T00:00",
  max: "2026-12-31T23:59",
});

// File uploader - returns UploadedFile object
const file = kt.file_uploader("Upload file", { accept: "image/*", maxSize: 5 * 1024 * 1024 });
if (file) {
  kt.write(`Uploaded: ${file.name} (${file.size} bytes)`);
  const content = file.text();  // or file.arrayBuffer()
}

// Multiple files
const files = kt.file_uploader("Upload files", { multiple: true });
for (const f of files) {
  kt.write(`${f.name}: ${f.type}`);
}

// Color picker - returns hex color string
const color = kt.color_picker("Pick a color", "#ff0000", { key: "color_pick" });
kt.write(`Selected: ${color}`);

Chart API

// Simple number array
kt.line_chart([10, 20, 15, 30, 25]);

// Object array (auto-detects series)
kt.line_chart([
  { month: "Jan", sales: 100, profit: 50 },
  { month: "Feb", sales: 120, profit: 60 },
  { month: "Mar", sales: 150, profit: 80 },
]);

// With configuration
kt.line_chart(data, {
  x: "month",
  y: ["sales", "profit"],
  x_label: "Month",
  y_label: "Amount ($)",
  color: ["#ff6384", "#36a2eb"],
  height: 300,
});
ParameterTypeDefaultDescription
dataLineChartData-Number array, object array, 2D array, or columnar format
config.xstringautoX-axis column name
config.ystring | string[]autoY-axis column name(s)
config.x_labelstring-X-axis label
config.y_labelstring-Y-axis label
config.colorstring | string[]paletteLine color(s) in HEX
config.heightnumber400Chart height in pixels
config.use_container_widthbooleantrueFit to container width
// Bar chart - simple number array
kt.bar_chart([10, 20, 15, 30, 25]);

// Key-value pairs
kt.bar_chart({ A: 10, B: 20, C: 30 });

// Multi-series with stacking (default)
kt.bar_chart([
  { month: "Jan", revenue: 100, cost: 50 },
  { month: "Feb", revenue: 120, cost: 60 },
], { x: "month" });

// Grouped bars (side by side)
kt.bar_chart(data, { x: "month", stack: false });

// Horizontal bars with sorting
kt.bar_chart(data, {
  x: "category",
  horizontal: true,
  sort: "descending",
  title: "Sales by Category",
});
ParameterTypeDefaultDescription
dataBarChartData-Number array, key-value object, object array, 2D array, or columnar format
config.xstringautoCategory column name
config.ystring | string[]autoValue column name(s)
config.x_labelstring-X-axis label
config.y_labelstring-Y-axis label
config.colorstring | string[]paletteBar color(s) - Tableau 10 palette
config.heightnumber400Chart height in pixels
config.stackbooleantrueStack multiple series (false for grouped)
config.horizontalbooleanfalseRender horizontal bars
config.sort"ascending" | "descending"-Sort bars by value
config.titlestring-Chart title
// Scatter chart - visualize relationships between two numeric variables
kt.scatter_chart([
  { height: 170, weight: 65 },
  { height: 160, weight: 55 },
  { height: 180, weight: 80 },
]);

// 2D array (each row is [x, y] pair)
kt.scatter_chart([[1, 5], [2, 8], [3, 6]]);

// Grouping by color column + bubble chart
kt.scatter_chart(data, {
  x: "gdp",
  y: "lifeExp",
  color: "region",
  size: "population",
  title: "GDP vs Life Expectancy",
});
ParameterTypeDefaultDescription
dataScatterChartData-Object array, 2D array, or columnar format
config.xstringautoX-axis column name
config.ystring | string[]autoY-axis column name(s)
config.x_labelstring-X-axis label
config.y_labelstring-Y-axis label
config.colorstring | string[]paletteGroup color column or color value(s) - Tableau 10 palette
config.sizestring | number5Size column name or fixed radius
config.heightnumber400Chart height in pixels
config.opacitynumber0.7Point opacity (0-1)
config.titlestring-Chart title
// Area chart - filled line chart for showing quantities over time
kt.area_chart([10, 20, 15, 30, 25]);

// Multi-series with overlay (default)
kt.area_chart([
  { month: "Jan", revenue: 100, cost: 50 },
  { month: "Feb", revenue: 120, cost: 60 },
], { x: "month" });

// Stacked area chart
kt.area_chart(data, { x: "month", stack: true });

// With custom colors and labels
kt.area_chart(data, {
  x: "month",
  y: ["revenue", "cost"],
  color: ["#FF000080", "#0000FF80"],
  x_label: "Month",
  y_label: "Amount ($)",
  title: "Revenue vs Cost",
});
ParameterTypeDefaultDescription
dataAreaChartData-Number array, object array, 2D array, or columnar format
config.xstringautoX-axis column name
config.ystring | string[]autoY-axis column name(s)
config.x_labelstring-X-axis label
config.y_labelstring-Y-axis label
config.colorstring | string[]paletteArea color(s) - Tableau 10 palette
config.heightnumber400Chart height in pixels
config.stackbooleanfalseStack multiple series (false for overlay)
config.use_container_widthbooleantrueFit to container width
config.titlestring-Chart title

Media API

// Image display
kt.image("https://example.com/photo.jpg");
kt.image("https://example.com/photo.jpg", { caption: "Photo caption" });

// Audio player
kt.audio("https://example.com/sound.mp3");
kt.audio(audioBytes, { mimeType: "audio/wav", loop: true });

// Video player
kt.video("https://example.com/movie.mp4");

// With poster and subtitles
kt.video("https://example.com/movie.mp4", {
  poster: "https://example.com/thumbnail.jpg",
  subtitles: { src: "/subs/ja.vtt", srclang: "ja", label: "ę—„ęœ¬čŖž" },
});

// Multiple subtitle tracks
kt.video("https://example.com/movie.mp4", {
  subtitles: [
    { src: "/subs/ja.vtt", srclang: "ja", label: "ę—„ęœ¬čŖž" },
    { src: "/subs/en.vtt", srclang: "en", label: "English" },
  ],
});

// Playback options
kt.video("https://example.com/demo.mp4", {
  autoplay: true,
  muted: true,
  loop: true,
});

// Time range (Media Fragment URI)
kt.video("https://example.com/long-video.mp4", {
  startTime: 30,
  endTime: 120,
});

// Binary data
kt.video(videoBytes, { mimeType: "video/mp4" });

Layout API

// Tabs - organize content in multiple tabs
const [tab1, tab2, tab3] = kt.tabs(["Overview", "Data", "Settings"]);

tab1(() => {
  kt.header("Overview");
  kt.write("This is the overview tab.");
});

tab2(() => {
  kt.header("Data");
  kt.table(data);
});

tab3(() => {
  kt.header("Settings");
  kt.write("Configure your preferences here.");
});

// Columns - create multi-column layout
kt.columns([
  () => kt.write("Left"),
  () => kt.write("Right"),
]);

// With ratios (1:2:1 = 25%:50%:25%)
kt.columns(
  [
    () => kt.write("Sidebar"),
    () => kt.write("Main content"),
    () => kt.write("Sidebar"),
  ],
  { ratios: [1, 2, 1] }
);

// Container - group content
kt.container(() => {
  kt.write("Grouped content");
  kt.button("Action");
}, { border: true });

// Expander - collapsible section
kt.expander("See details", () => {
  kt.write("Hidden content");
});

// Expanded by default
kt.expander("Important notice", () => {
  kt.write("Please read this!");
}, { expanded: true });

// Status container - collapsible with state indicators
kt.status("Processing data...", (s) => {
  kt.write("Connecting to database...");
  kt.write("Fetching records...");
  s.update({ state: "running", expanded: true });
}, { key: "my_status" });

// Auto-completes to "complete" when update() is not called
kt.status("Quick task", () => {
  kt.write("Done instantly");
});

// Manual state control
kt.status("Upload files", (s) => {
  kt.write("Uploading...");
  s.update({ state: "complete", label: "Upload finished!", expanded: false });
}, { key: "upload_status" });

// Error state
kt.status("Validation", (s) => {
  kt.write("Checking data...");
  s.update({ state: "error", label: "Validation failed" });
}, { key: "validation_status" });
ParameterTypeDescription
labelstringStatus container label
content(controller: StatusController) => voidCallback with content and controller
config.keystringWidget key for state persistence
config.state"running" | "complete" | "error"Initial state (default: "running")
config.expandedbooleanInitial expanded state (default: true when running)

StatusController methods:

  • update({ state?, label?, expanded? }) - Update status state, label, or expanded state

Behavior:

  • If update() is never called, auto-completes to "complete" with expanded: false
  • State persists across reruns via widget registry
  • Invalid states fall back to "running"

Sidebar API

// Callback notation
kt.sidebar(() => {
  kt.title("Settings");
  kt.button("Reset");
});

// Object notation
kt.sidebar.title("Settings");
kt.sidebar.button("Reset");

// Custom width
kt.sidebar(() => {
  kt.title("Wide Sidebar");
}, { width: "350px" });

Form API

// Form with submit button
kt.form("login_form", () => {
  const username = kt.text_input("Username");
  const password = kt.text_input("Password", "", { type: "password" });
  if (kt.form_submit_button("Login")) {
    // Handle form submission
  }
});

// Validation errors
kt.form("contact", () => {
  const email = kt.text_input("Email");
  if (kt.form_submit_button("Send")) {
    if (!email.includes("@")) {
      kt.validation_error("Please enter a valid email address");
      return;
    }
    // Process valid form data
  }
});

// Multiple validation errors
kt.form("signup", () => {
  const name = kt.text_input("Name");
  const email = kt.text_input("Email");
  if (kt.form_submit_button("Sign Up")) {
    const errors = [];
    if (!name) errors.push("Name is required");
    if (!email) errors.push("Email is required");
    if (errors.length > 0) {
      kt.validation_errors(errors);
      return;
    }
  }
});

Chat API

import { kt, createTypedSessionState } from "kantan-ui";

// Message history type definition
type ChatState = {
  messages: Array<{ role: "user" | "assistant"; content: string }>;
};

const state = createTypedSessionState<ChatState>({
  messages: [],
});

// Chat container (with auto-scroll)
kt.chat_container(() => {
  for (const msg of state.messages) {
    kt.chat_message(msg.role, msg.content);
  }
}, { height: "400px" });

// Individual chat messages
kt.chat_message("user", "Hello!");
kt.chat_message("assistant", "Hi! How can I help you?");

// Custom avatar and name
kt.chat_message("user", "What is **TypeScript**?", {
  name: "Alice",
  avatar: "šŸ§‘ā€šŸ’»",
});

// System message
kt.chat_message("system", "Session started at 10:00 AM");

// Chat input - returns submitted text (null when no submission)
const userInput = kt.chat_input("Type a message...", { key: "chat" });
if (userInput) {
  state.messages.push({ role: "user", content: userInput });
}

Features:

  • Role-based styling (user / assistant / system)
  • Markdown content support
  • Customizable avatar and display name
  • Auto-scroll (pauses when user scrolls up)
  • Chat input with pinned-to-bottom positioning

Empty Placeholder API

Create dynamically updatable placeholders. Similar to Streamlit's st.empty().

// Create placeholder
const status = kt.empty({ key: "status" });

// Dynamically change content on button click
if (kt.button("Start Process")) {
  status.spinner("Processing...");
}

if (kt.button("Complete")) {
  status.success("Done!");
}

if (kt.button("Show Error")) {
  status.error("Something went wrong!");
}

if (kt.button("Clear")) {
  status.empty();  // Clear content
}

// Show progress bar
const progress = kt.empty({ key: "progress" });
if (kt.button("Show Progress")) {
  progress.progress(0.75, { text: "75% complete" });
}

Placeholder Object Methods:

  • write(content) - Display text/number/boolean
  • text(content) - Display plain text
  • markdown(content) - Display Markdown
  • html(content) - Display raw HTML
  • json(data) - Display formatted JSON
  • code(content, language?) - Display code block
  • success(message) - Success alert
  • error(message) - Error alert
  • warning(message) - Warning alert
  • info(message) - Info alert
  • progress(value, config?) - Progress bar (0.0-1.0)
  • spinner(text?) - Loading spinner
  • empty() - Clear content

Session State Management

Manage per-user session state. Similar to Streamlit's st.session_state.

createTypedSessionState (Recommended)

Create type-safe session state. Access safely without type assertions, with IDE completion support.

import { createTypedSessionState } from "kantan-ui";

// Define type and specify defaults
type AppState = {
  counter: number;
  name: string;
  items: string[];
};

const state = createTypedSessionState<AppState>({
  counter: 0,
  name: "World",
  items: [],
});

// Type-safe access
state.counter++;           // OK - number type
state.name = "Hello";      // OK - string type
state.items.push("item");  // OK - string[] type
// state.unknown = 1;      // Compile error!

session_state (Legacy)

For dynamic keys, the traditional session_state is also available.

import { session_state } from "kantan-ui";

// Initialize
if (session_state.myValue === undefined) {
  session_state.myValue = "initial";
}

// Read (type assertion required)
const value = session_state.myValue as string;

// Write
session_state.myValue = "new value";

Cache API

Cache expensive function results to improve performance. Similar to Streamlit's @st.cache_data and @st.cache_resource.

cache_data

For serializable data (API results, computed values):

import { kt } from "kantan-ui";

// Basic usage
const fetchUsers = kt.cache_data(async (limit: number) => {
  const res = await fetch(`/api/users?limit=${limit}`);
  return res.json();
});

const users = await fetchUsers(10);  // Cached on subsequent calls

// With TTL (expires in 1 hour)
const fetchWeather = kt.cache_data(async (city: string) => {
  return await weatherApi.get(city);
}, { ttl: 3600 });

// With max entries (LRU eviction)
const searchProducts = kt.cache_data(async (query: string) => {
  return await productApi.search(query);
}, { max_entries: 50 });

// Clear cache
fetchUsers.clear();

cache_resource

For non-serializable resources (database connections, ML models):

// Returns the same instance on every call
const getDb = kt.cache_resource(() => {
  return new DatabaseConnection(process.env.DB_URL);
});

const db1 = getDb();
const db2 = getDb();
console.log(db1 === db2); // true

Global cache clear

kt.cache_data.clear();      // Clear all cache_data caches
kt.cache_resource.clear();  // Clear all cache_resource caches
kt.clear_all_caches();      // Clear all caches

Imperative API (Low-level API)

For finer control, imperative APIs are also available.

import { button, renderButton, slider, renderSlider } from "kantan-ui";

// Functional API (returns true when pressed)
const pressed = button("Click me", { key: "my_button" });

// For rendering (returns HTML)
const html = renderButton("Click me", { key: "my_button" });

Project Structure

src/
ā”œā”€ā”€ index.ts          # Entry point (exports)
ā”œā”€ā”€ app.ts            # createApp function
ā”œā”€ā”€ lambda.ts         # AWS Lambda handler helpers
ā”œā”€ā”€ server.ts         # Demo server
ā”œā”€ā”€ client/           # Client script generation
│   ā”œā”€ā”€ script.ts     # WebSocket/event handling script
│   ā”œā”€ā”€ dataframe-script.ts # Dataframe client-side interactions
│   ā”œā”€ā”€ types.ts      # Client config types
│   └── index.ts
ā”œā”€ā”€ config/           # Configuration management
│   ā”œā”€ā”€ defaults.ts   # Default settings
│   ā”œā”€ā”€ types.ts      # Config types
│   └── index.ts
ā”œā”€ā”€ kt/               # Declarative API (Streamlit-style)
│   ā”œā”€ā”€ context.ts    # Render context
│   ā”œā”€ā”€ config.ts     # Page config (set_page_config)
│   ā”œā”€ā”€ control.ts    # Control API (rerun)
│   ā”œā”€ā”€ charts.ts     # Chart API (line_chart, bar_chart, area_chart, scatter_chart)
│   ā”œā”€ā”€ chart/        # Chart modules
│   │   ā”œā”€ā”€ types.ts      # Type definitions (BaseChartConfig, NormalizeConfig, etc.)
│   │   ā”œā”€ā”€ colors.ts     # Color palette & validation
│   │   ā”œā”€ā”€ normalize.ts  # Data normalization
│   │   ā”œā”€ā”€ scale.ts      # Axis scale calculation (niceScale, calculateAxisScale)
│   │   ā”œā”€ā”€ shared.ts     # Common chart pipeline (prepareChartData, sanitizeConfig)
│   │   ā”œā”€ā”€ render-utils.ts # Shared rendering utilities (grid, axes, legend)
│   │   ā”œā”€ā”€ bar-chart.ts  # Bar chart SVG rendering
│   │   ā”œā”€ā”€ area-chart.ts # Area chart SVG rendering
│   │   └── scatter-chart.ts # Scatter chart SVG rendering
│   ā”œā”€ā”€ chat.ts       # Chat API (chat_message, chat_container)
│   ā”œā”€ā”€ data.ts       # Data display (table, dataframe)
│   ā”œā”€ā”€ empty.ts      # Empty placeholder
│   ā”œā”€ā”€ feedback.ts   # Feedback API (progress, spinner, toast)
│   ā”œā”€ā”€ form.ts       # Form API
│   ā”œā”€ā”€ layout.ts     # Layout (tabs, columns, container, expander)
│   ā”œā”€ā”€ media.ts      # Media API (image, audio, video)
│   ā”œā”€ā”€ sidebar.ts    # Sidebar API
│   ā”œā”€ā”€ status.ts     # Status container API
│   ā”œā”€ā”€ output.ts     # Output API (title, write, header, etc.)
│   ā”œā”€ā”€ stream.ts     # Streaming API (write_stream)
│   ā”œā”€ā”€ stream-utils.ts   # Stream normalization utilities
│   ā”œā”€ā”€ stream-registry.ts # Pending stream management
│   ā”œā”€ā”€ widgets.ts    # Widget API (button, slider, etc.)
│   └── index.ts
ā”œā”€ā”€ runtime/          # Runtime context management
│   ā”œā”€ā”€ context.ts    # getContext/setContext
│   ā”œā”€ā”€ rerun.ts      # Script re-execution logic
│   ā”œā”€ā”€ stream-processor.ts # Stream processing engine
│   └── index.ts
ā”œā”€ā”€ session/          # Session management
│   ā”œā”€ā”€ manager.ts    # SessionManager (multi-tab support)
│   ā”œā”€ā”€ state.ts      # session_state (Proxy implementation)
│   ā”œā”€ā”€ types.ts      # Type definitions
│   └── index.ts
ā”œā”€ā”€ utils/            # Utilities
│   ā”œā”€ā”€ html.ts       # HTML escaping, etc.
│   ā”œā”€ā”€ sanitize.ts   # Filename sanitization
│   ā”œā”€ā”€ magic-bytes.ts # Magic byte validation
│   ā”œā”€ā”€ polyglot-detection.ts # Polyglot detection
│   ā”œā”€ā”€ file-validation.ts # File validation integration
│   └── type-guards.ts
ā”œā”€ā”€ websocket/        # WebSocket handling
│   ā”œā”€ā”€ handler.ts    # WebSocket handler
│   ā”œā”€ā”€ types.ts      # Message type definitions
│   └── index.ts
└── widgets/          # UI widgets (imperative API)
    ā”œā”€ā”€ dataframe.ts
    ā”œā”€ā”€ button.ts
    ā”œā”€ā”€ slider.ts
    ā”œā”€ā”€ text-input.ts
    ā”œā”€ā”€ text-area.ts
    ā”œā”€ā”€ selectbox.ts
    ā”œā”€ā”€ download-button.ts
    ā”œā”€ā”€ checkbox.ts
    ā”œā”€ā”€ toggle.ts
    ā”œā”€ā”€ radio.ts
    ā”œā”€ā”€ number-input.ts
    ā”œā”€ā”€ multiselect.ts
    ā”œā”€ā”€ date-input.ts
    ā”œā”€ā”€ time-input.ts
    ā”œā”€ā”€ file-uploader.ts
    ā”œā”€ā”€ uploaded-file.ts
    ā”œā”€ā”€ line-chart.ts
    ā”œā”€ā”€ image.ts
    ā”œā”€ā”€ audio.ts
    ā”œā”€ā”€ video.ts
    ā”œā”€ā”€ placeholder.ts
    ā”œā”€ā”€ core.ts
    ā”œā”€ā”€ registry.ts
    ā”œā”€ā”€ types.ts
    └── index.ts

NPM Scripts

CommandDescription
bun run devStart development server (hot reload)
bun run buildProduction build
bun run testRun unit tests (Vitest)
bun run test:watchUnit tests (watch mode)
bun run test:coverageTests with coverage
bun run test:e2eE2E tests (Playwright)
bun run lintLint check with Biome
bun run lint:fixAuto-fix lint issues
bun run ciCI pipeline (lint + build + test:coverage)

How It Works

  1. Client loads the page and establishes WebSocket connection
  2. Server creates a session and sends initial HTML
  3. When user interacts with widgets, sendEvent notifies the server (with debounce)
  4. Server updates session_state and re-executes the script (rerun)
  5. New HTML is sent to client via WebSocket (streaming supported)
  6. Client updates DOM (preserving focus state)

Connection Management

  • Ping/Pong: Server periodically sends ping to monitor connection state
  • Auto-reconnect: Exponential backoff retry on disconnect
  • Sequence Numbers: Recover missed patches on reconnect
  • Multi-tab: Broadcast state changes to all tabs of the same session

License

MIT

Skills Info
Original Name:update-tutorialAuthor:watany