/// // Teaching example: CTS (CommonTools TypeScript) generates JSON Schemas from the // TypeScript types below. Recipes use typed inputs/outputs, and handlers take // explicit `Cell` parameters; the transform emits the correct asCell schema // automatically where mutation is required. import { Cell, cell, Default, derive, handler, NAME, navigateTo, recipe, str, UI, } from "commontools"; // User object for better data modeling interface User { name: string; } // Data types for the chat messages. CTS will derive the matching schema. interface ChatMessage { author: User; message: string; timestamp: number; } // Recipe input (typed). The UI receives a reactive reference that supports // mapping (`messages.map(...)`). Writes should be performed in handlers. type MainRecipeInput = { messages: Default; }; type UserSessionInput = { messages: Default; user: User; }; // Session recipe result (typed): return User object alongside [NAME] and [UI]. // CTS will carry these in the recipe result schema. type UserSessionResult = { messages: Default; user: User; }; // Event payload type for ct-message-input's ct-send event type InputEventType = { detail: { message: string; }; }; // Simple messenger-style time format (e.g., 3:05 PM). No full date per message. function formatTime(ts: number): string { const d = new Date(ts); return d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit", }); } // Handler to send a new chat message. const sendMessage = handler< InputEventType, { messages: Cell; user: Cell; } >((event, { messages, user }) => { const text = event.detail?.message?.trim(); if (!text) return; messages.push({ author: user.get(), message: text, timestamp: Date.now(), }); }); // Handler to set/update the username (local-only field) const setUsername = handler< InputEventType, { user: Cell; } >((event, { user }) => { const name = (event.detail?.message ?? "").trim(); // Update only the "name" field to avoid clearing other properties user.key("name").set(name); }); // User Session Recipe - Individual instance with local state export const UserSession = recipe< UserSessionInput, UserSessionResult >( "User Chat Session", ({ messages, user }) => { return { [NAME]: str`Chat Session` as any, [UI]: (

Your Chat Session

Set your display name


Chat Messages

{messages.map((m) => (
{m.author.name} ยท {derive(m.timestamp, formatTime)}
{m.message}
))}
), messages, user, }; }, ); // Handler to create a new user session. Receives typed parameters so the handler // can pass the reactive references directly into the child recipe and // keep all sessions linked to the same underlying state. const createUserSession = handler< never, { messages: Default; } >((_, { messages }) => { const sessionCharm = UserSession({ messages: messages as any, user: cell({ name: "" }), }); return navigateTo(sessionCharm); }); // Main chat recipe: a state container with a button to spawn per-user sessions. // All sessions get the same `messages` reference so changes are shared. export default recipe( "Main Chat State Container", ({ messages }) => { return { [NAME]: "Chat State Container", [UI]: (

Chat State Container

This charm stores the shared chat state.

Messages: {messages.length}

Click below to create your personal chat session:

Generate User Session
), messages, }; }, );