From 4a5e7de3d8dcdd7e0bd9fcc4ca0ff1d69ac69275 Mon Sep 17 00:00:00 2001 From: jakedahn Date: Mon, 6 Oct 2025 10:33:07 -0600 Subject: [PATCH 1/8] vendoring https://github.com/commontoolsinc/tutorials/tree/main at b8610b29613f251543ab8a37d11c7d68020f41f9 --- tutorials/.gitignore | 7 + tutorials/.nvmrc | 1 + tutorials/README.md | 14 + tutorials/code/state_02.tsx | 47 +++ tutorials/code/state_03.tsx | 65 ++++ tutorials/common_ui.md | 365 ++++++++++++++++++ tutorials/cts.md | 15 + .../images/diagrams/ct-autolayout-mid.svg | 312 +++++++++++++++ .../images/diagrams/ct-autolayout-narrow.svg | 313 +++++++++++++++ .../images/diagrams/ct-autolayout-wide.svg | 312 +++++++++++++++ tutorials/images/diagrams/ct-hstack.svg | 333 ++++++++++++++++ tutorials/images/diagrams/ct-screen.svg | 312 +++++++++++++++ tutorials/images/diagrams/ct-toolbar.svg | 330 ++++++++++++++++ tutorials/images/diagrams/ct-vstack-1.svg | 333 ++++++++++++++++ tutorials/images/llm_final.png | Bin 0 -> 17002 bytes tutorials/images/llm_handler.png | Bin 0 -> 16302 bytes tutorials/images/llmv1.png | Bin 0 -> 15769 bytes .../managing-user-interface-state~dark@2x.png | Bin 0 -> 21954 bytes tutorials/images/state_ac.png | Bin 0 -> 10553 bytes tutorials/images/state_charname.png | Bin 0 -> 9008 bytes tutorials/images/state_dex_button.png | Bin 0 -> 13332 bytes tutorials/images/state_dex_mod.png | Bin 0 -> 10083 bytes tutorials/images/state_name_change.png | Bin 0 -> 13336 bytes tutorials/index.md | 38 ++ tutorials/install-ct.md | 143 +++++++ tutorials/llm-builtin.md | 303 +++++++++++++++ tutorials/myst.yml | 26 ++ tutorials/netlify.toml | 6 + tutorials/package-lock.json | 25 ++ tutorials/package.json | 13 + tutorials/state.md | 230 +++++++++++ tutorials/state_modify.md | 205 ++++++++++ 32 files changed, 3748 insertions(+) create mode 100644 tutorials/.gitignore create mode 100644 tutorials/.nvmrc create mode 100644 tutorials/README.md create mode 100644 tutorials/code/state_02.tsx create mode 100644 tutorials/code/state_03.tsx create mode 100644 tutorials/common_ui.md create mode 100644 tutorials/cts.md create mode 100644 tutorials/images/diagrams/ct-autolayout-mid.svg create mode 100644 tutorials/images/diagrams/ct-autolayout-narrow.svg create mode 100644 tutorials/images/diagrams/ct-autolayout-wide.svg create mode 100644 tutorials/images/diagrams/ct-hstack.svg create mode 100644 tutorials/images/diagrams/ct-screen.svg create mode 100644 tutorials/images/diagrams/ct-toolbar.svg create mode 100644 tutorials/images/diagrams/ct-vstack-1.svg create mode 100755 tutorials/images/llm_final.png create mode 100755 tutorials/images/llm_handler.png create mode 100755 tutorials/images/llmv1.png create mode 100644 tutorials/images/managing-user-interface-state~dark@2x.png create mode 100755 tutorials/images/state_ac.png create mode 100755 tutorials/images/state_charname.png create mode 100755 tutorials/images/state_dex_button.png create mode 100755 tutorials/images/state_dex_mod.png create mode 100755 tutorials/images/state_name_change.png create mode 100644 tutorials/index.md create mode 100644 tutorials/install-ct.md create mode 100644 tutorials/llm-builtin.md create mode 100644 tutorials/myst.yml create mode 100644 tutorials/netlify.toml create mode 100644 tutorials/package-lock.json create mode 100644 tutorials/package.json create mode 100644 tutorials/state.md create mode 100644 tutorials/state_modify.md diff --git a/tutorials/.gitignore b/tutorials/.gitignore new file mode 100644 index 000000000..0197f8cd5 --- /dev/null +++ b/tutorials/.gitignore @@ -0,0 +1,7 @@ +# Node / tooling +node_modules/ +npm-debug.log* + +# Myst build output +_build/ +.cache/ diff --git a/tutorials/.nvmrc b/tutorials/.nvmrc new file mode 100644 index 000000000..3c032078a --- /dev/null +++ b/tutorials/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/tutorials/README.md b/tutorials/README.md new file mode 100644 index 000000000..5195fadc6 --- /dev/null +++ b/tutorials/README.md @@ -0,0 +1,14 @@ +# Tutorials + +Install MyST Markdown tooling by following https://mystmd.org/guide/installing. Once set up, run `myst` in this directory to serve the tutorial pages locally. + +## npm quickstart + +```sh +npm i +npm run dev +``` + +TODO: +* Add document on how Input and Output schemas work for Recipes +* Chapter on basic UI and existing ct components diff --git a/tutorials/code/state_02.tsx b/tutorials/code/state_02.tsx new file mode 100644 index 000000000..354a76773 --- /dev/null +++ b/tutorials/code/state_02.tsx @@ -0,0 +1,47 @@ +/// +import { + cell, + h, + recipe, + UI, + lift, + derive, + handler, + type Cell, +} from "commontools"; + +const calcAC = (dex: number) : number => + 20 + Math.floor((dex - 10) / 2); + +const updateName = handler< + { detail: { message: string } }, + { characterName: Cell } +>( + (event, { characterName }) => { + console.log("Updating character name to:", event.detail.message); + characterName.set(event.detail.message); + } +); + +export default recipe("state test", () => { + const characterName = cell(""); + characterName.set("Lady Ellyxir"); + const dex = cell(16); + const ac = lift(calcAC)(dex); + + return { + [UI]: ( +
+

Character name: {characterName}

+ +
  • DEX: {dex}
  • +
  • DEX Modifier: {Math.floor((dex - 10) / 2)}
  • +
  • AC: {ac}
  • +
    + ), + }; +}); diff --git a/tutorials/code/state_03.tsx b/tutorials/code/state_03.tsx new file mode 100644 index 000000000..da8cc1568 --- /dev/null +++ b/tutorials/code/state_03.tsx @@ -0,0 +1,65 @@ +/// +import { + cell, + h, + recipe, + UI, + lift, + derive, + handler, + type Cell, +} from "commontools"; + +const calcAC = (dex: number) : number => + 20 + Math.floor((dex - 10) / 2); + +const updateName = handler< + { detail: { message: string } }, + { characterName: Cell } +>( + (event, { characterName }) => { + characterName.set(event.detail.message); + } +); + +const rollD6 = () => Math.floor(Math.random() * 6) + 1; + +const rollDex = handler< + unknown, + Cell +>( + (_, dex) => { + // Roll 3d6 for new DEX value + const roll = rollD6() + rollD6() + rollD6(); + dex.set(roll); + } +); + +export default recipe("state test", () => { + const characterName = cell(""); + characterName.set("Lady Ellyxir"); + const dex = cell(16); + const ac = lift(calcAC)(dex); + + return { + [UI]: ( +
    +

    Character name: {characterName}

    + +
  • + DEX: {dex} + {" "} + + Roll + +
  • +
  • DEX Modifier: {Math.floor((dex - 10) / 2)}
  • +
  • AC: {ac}
  • +
    + ), + }; +}); diff --git a/tutorials/common_ui.md b/tutorials/common_ui.md new file mode 100644 index 000000000..4710bf5bd --- /dev/null +++ b/tutorials/common_ui.md @@ -0,0 +1,365 @@ +--- +title: Common UI +short_title: Common UI +description: Introduction to Common UI +subject: Tutorial +authors: + - name: Ben Follington + email: ben@common.tools +keywords: commontools, UI +abstract: | + Common UI is a collection of web components (prefixed with ct-) exposed for building patterns. +--- +# Common UI + +The philosophy of Common UI is inspired by Swift UI, the 'default' configuration should 'just work' if you use the correct building blocks together. + +:::{note} +Swift UI operates on a [reactive binding model](https://developer.apple.com/tutorials/swiftui-concepts/driving-changes-in-your-ui-with-state-and-bindings) with [FRP elements](https://developer.apple.com/documentation/combine), making it a short-leap from our needs (as compared with general Web UI). + +![](./images/managing-user-interface-state~dark@2x.png) + +Swift developers are encouraged to use the defaults as much as possible. By doing less specification you [maintain the dynamic ability to adapt to the user's preferences and environment]( https://developer.apple.com/tutorials/swiftui-concepts/maintaining-the-adaptable-sizes-of-built-in-views). This means you 'know less' about what you'll be drawing than you may have come to expect from an abstraction like Tailwind. The **composition** of components is emphasised over granular control. +::: + + +Our `ui` package is a web component library implemented in `lit` that interoperates with the Common Tools runtime to produce a Swift UI-like abstraction, this means our components are divided into layers: + + +# System Components + +## ct-theme + +Applies a set of theme variables to the entire subtree. Not all components respect the theme yet, but many do. See `packages/ui/src/v2/components/theme-context.ts` + +```{code-block} typescript +const localTheme = { + accentColor: cell("#3b82f6"), + fontFace: cell("system-ui, -apple-system, sans-serif"), + borderRadius: cell("0.5rem"), +}; + +// later... + +return { + [NAME]: "Themed Charm", + [UI]: ( + + {/* all components in subtree are themed */} + + ) +} +``` + +Can be nested and overriden further down the subtree. + +## ct-render + +Used to render a `Cell` that has a `[UI]` property into the DOM. Usually not required inside a pattern, used in the app shell itself. + +```{code-block} html + +``` + +## ct-keybind (beta) + +Register keyboard shortcuts with a handler. These registrations are mediated by `packages/shell/src/lib/keyboard-router.ts` in the shell to prevent conflicts with system shortcuts. + +```{code-block} html + +``` + +# Layout Components + +Layout components do not provide any content themselves, they are used to arrange other components. We draw quite directly from the [Swift UI Layout Fundamentals](https://developer.apple.com/documentation/swiftui/layout-fundamentals). + +## ct-screen + +Designed to represent content that could fill the entire screen or a panel / content area. This will expand to fill the available space. It offers two optional slots: `header` and `footer`. + +When to use: your `
    ` or `
    ` is not growing to full the available space. Typically appears _once_ at the root of a pattern's `[UI]` tree: + +```{figure} ./images/diagrams/ct-screen.svg +:name: layout-example +``` + +```{code-block} html + + + Hello + + +
    ...
    +
    ...
    + +
    + World +
    +
    +``` + +Inspired by this [Swift UI convention](https://scottsmithdev.com/screen-vs-view-in-swiftui). A `Screen` is just a `View` but it represents the kind of view that MIGHT fill a screen on some device. + +## ct-toolbar + +Stack several actions into a horizontal bar, typically at the top of ``. + +```{figure} ./images/diagrams/ct-toolbar.svg +:name: layout-example +``` + +```{code-block} html + + + A + B + + +``` + +## Stacks are all you need + +... almost. Just the [horizontal and vertical stacks](https://developer.apple.com/tutorials/swiftui-concepts/organizing-and-aligning-content-with-stacks) if you control the [spacing and alignment](https://developer.apple.com/tutorials/swiftui-concepts/adjusting-the-space-between-views). + +## ct-vstack + +Stack items vertically, this is a layer over the [CSS flexbox API](https://flexbox.malven.co/). You can permuate `gap`, `align`, `justify` and `reverse` attributes to control the behavior. + +When to use: any time you need to stack items vertically. + +```{figure} ./images/diagrams/ct-vstack-1.svg +:name: layout-example +``` + +```{code-block} html + +
    A
    +
    B
    +
    C
    +
    +``` + +## ct-hstack + +Stack items horizontally, this is a layer over the [CSS flexbox API](https://flexbox.malven.co/). You can permuate `gap`, `align`, `justify` and `reverse` attributes to control the behavior. + +When to use: toolbars, column layouts, grouping icons and buttons and text together. + +```{figure} ./images/diagrams/ct-hstack.svg +:name: layout-example +``` + +```{code-block} html + +
    A
    +
    B
    +
    C
    +
    +``` + +## ct-zstack + +Currently missing, would allow similar control for layering items on top of one another. [Swift UI ZStack](https://developer.apple.com/documentation/swiftui/zstack). + +## ct-vscroll + +Wrap tall vertical content in a scrollable container with control over autoscroll and scrollbar appearance. Inspired by [SwiftUI ScrollView](https://developer.apple.com/documentation/swiftui/scrollview). + +```{code-block} html + + +

    Long content...

    +
    +
    +``` + +In practice we often use a specific set of properties if dealing with a "chat view" that scrolls: + +```{code-block} html + +``` + +Here `flex` will force the `vscroll` to expand without a fixed height. `snapToBottom` will automatically scroll to the bottom when new content is added. + +## ct-autolayout + +Will attempt to lay out the children provided as best it can. Provides two slots for `left` and `right` sidebars (that can be toggled open/shut). On a wide view, items stack horizontally, on a medium view thet stack vertically and on mobile it converts to a tabbed view. + +```{figure} ./images/diagrams/ct-autolayout-wide.svg +:name: layout-example +``` + +```{figure} ./images/diagrams/ct-autolayout-mid.svg +:name: layout-example +``` + +```{figure} ./images/diagrams/ct-autolayout-narrow.svg +:name: layout-example +``` + +```{code-block} html + + +
    +

    Header Section

    +
    + + + + + + + + + + +
    +

    Main Content Area

    +

    This is the main content with sidebars

    + Main Button +
    + +
    +

    Second Content Area

    +

    This is the second content with sidebars

    + Second Button +
    + + + +
    + + +
    +

    Footer Section

    +
    +
    +``` + +## ct-grid (stale) + +`ct-grid` has not been used in production and likely doesn't work, but the intention is to wrap the [CSS Grid API](https://grid.malven.co/) and blend in ideas from [Swift UI Grid](https://developer.apple.com/documentation/swiftui/grid). + +## ct-spacer (missing) + +[Swift UI Spacer](https://developer.apple.com/documentation/swiftui/spacer) + +## Composed Layouts + +You can mix-and-match the above components to achieve practically any (standard) layout. + +```{code-block} html + + + hello + + + + + question + + + + + + + question + + + + + + + + ... + + + + + +
      +
    • Imagine this was long
    • +
    +
    +
    +
    +``` + +# Visual Components + +- typesetting: `ct-label`, `ct-heading` + - gap: `ct-text` for themed paragraph usecase (`p` works) + -

    , + +- gap: `ct-icon` (and `ct-label` has an optional in-built icon) + - gap: icon set? + +- visual: `ct-kdb`, `ct-separator`, `ct-table`, `ct-tool-call` + - gap: `ct-img` or `ct-media` + +# Input Components + +- input: `ct-button`, `ct-select`, `ct-input`, `ct-textarea`, `ct-checkbox`, `ct-tags` + - gap: `ct-search` which has an autocomplete menu + - gap: `ct-file-picker` + - redundant: common-send-message, ct-message-input (?) + - this is JUST a button and an input + - the "right" way is: + - ```{code-block} html + + + Submit + + ``` + + - ```{code-block} typescript + const EnterToSubmit = recipe(({ myHandler }) => { + return { + [UI]: + + Submit + + } + }) + + + ``` + +# Interactive / Complex Components + +- interactive: `ct-collapsible`, `ct-list-item`, `ct-tab-list`, `ct-canvas` + - `type ListItem = { title: string }` + - `const items: ListItem[]` + - Consider using `[NAME]`? + - gap: re-orderable list + - ```{code-block} html + + {items.map((i: Opaque<{ name: string }>) => {i.name})} + + ``` + - `editable` only applies to the `title` property of each list item + +- complex/integrated (cell interop): `ct-code-editor`, `ct-outliner`, `ct-list` + - gap: editable table rows + +## Chat Components + +- chat: `ct-chat`, `ct-prompt-input`, `ct-chat-message`, `ct-tool-call`, `ct-tools-chip` + +# Unused/Unproven Components + +- stale: `ct-aspect-ratio`, `ct-draggable`, `ct-form`, `ct-grid`, `ct-hgroup`, `ct-input-otp`, `ct-message-input`, `ct-progress`, `ct-radio`, `ct-radio-group`, `ct-slider`, `ct-switch`, `ct-tile`, `ct-toggle`, `ct-toggle-group`, `ct-vgroup` +- superfluous: `ct-resizeable-handle`, `ct-resizable-panel`, `ct-resizeable-panel-group`, `ct-scroll-area`, `ct-tabs`/`ct-tab-list`/`ct-tab-panel` diff --git a/tutorials/cts.md b/tutorials/cts.md new file mode 100644 index 000000000..c16108f6e --- /dev/null +++ b/tutorials/cts.md @@ -0,0 +1,15 @@ +--- +title: Common Type System +short_title: Common Type System +description: How to use the Common Type System to simplify types +subject: Tutorial +keywords: commontools, recipes, cts, types +abstract: | + The CTS (Common Type System) helps automatically convert types and common + coding patterns for you. This leads to more readable and succint code. + In this section, we will go over the basic features offered by CTS. +--- +# Common Type System + +Needs to be written + diff --git a/tutorials/images/diagrams/ct-autolayout-mid.svg b/tutorials/images/diagrams/ct-autolayout-mid.svg new file mode 100644 index 000000000..2d69c0c29 --- /dev/null +++ b/tutorials/images/diagrams/ct-autolayout-mid.svg @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tutorials/images/diagrams/ct-autolayout-narrow.svg b/tutorials/images/diagrams/ct-autolayout-narrow.svg new file mode 100644 index 000000000..0714b6356 --- /dev/null +++ b/tutorials/images/diagrams/ct-autolayout-narrow.svg @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tutorials/images/diagrams/ct-autolayout-wide.svg b/tutorials/images/diagrams/ct-autolayout-wide.svg new file mode 100644 index 000000000..335868e8a --- /dev/null +++ b/tutorials/images/diagrams/ct-autolayout-wide.svg @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tutorials/images/diagrams/ct-hstack.svg b/tutorials/images/diagrams/ct-hstack.svg new file mode 100644 index 000000000..1ca6f6df6 --- /dev/null +++ b/tutorials/images/diagrams/ct-hstack.svg @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tutorials/images/diagrams/ct-screen.svg b/tutorials/images/diagrams/ct-screen.svg new file mode 100644 index 000000000..5f2715116 --- /dev/null +++ b/tutorials/images/diagrams/ct-screen.svg @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tutorials/images/diagrams/ct-toolbar.svg b/tutorials/images/diagrams/ct-toolbar.svg new file mode 100644 index 000000000..6410e137d --- /dev/null +++ b/tutorials/images/diagrams/ct-toolbar.svg @@ -0,0 +1,330 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tutorials/images/diagrams/ct-vstack-1.svg b/tutorials/images/diagrams/ct-vstack-1.svg new file mode 100644 index 000000000..297b4507b --- /dev/null +++ b/tutorials/images/diagrams/ct-vstack-1.svg @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tutorials/images/llm_final.png b/tutorials/images/llm_final.png new file mode 100755 index 0000000000000000000000000000000000000000..d7a2b66d82077effd8bc1ca5b0be2eded7ab2c0a GIT binary patch literal 17002 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 literal 0 HcmV?d00001 diff --git a/tutorials/images/llm_handler.png b/tutorials/images/llm_handler.png new file mode 100755 index 0000000000000000000000000000000000000000..06679aa7caa4f095966495a40eab87e88b12541a GIT binary patch literal 16302 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 R0LF8h$rTI3a{W7x{s+o_c;^5B literal 0 HcmV?d00001 diff --git a/tutorials/images/llmv1.png b/tutorials/images/llmv1.png new file mode 100755 index 0000000000000000000000000000000000000000..3c4899bb4c3a14697f5984d8f7b95cc4d7d613dc GIT binary patch literal 15769 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 TFR6B5>|+X#RUTm<8o&Kt&=pwN literal 0 HcmV?d00001 diff --git a/tutorials/images/managing-user-interface-state~dark@2x.png b/tutorials/images/managing-user-interface-state~dark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..702f0ba8e7e2aedfb1059c757ea5c5fd10f52e16 GIT binary patch literal 21954 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 URu%x?W<`;eR+1``c=6`{05JZUQ2+n{ literal 0 HcmV?d00001 diff --git a/tutorials/images/state_ac.png b/tutorials/images/state_ac.png new file mode 100755 index 0000000000000000000000000000000000000000..2ba42194ce34bd2771ae085e55d3cebca45dd02e GIT binary patch literal 10553 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 literal 0 HcmV?d00001 diff --git a/tutorials/images/state_dex_button.png b/tutorials/images/state_dex_button.png new file mode 100755 index 0000000000000000000000000000000000000000..4d398838a1b674707d66e306c527580a217348b1 GIT binary patch literal 13332 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 literal 0 HcmV?d00001 diff --git a/tutorials/images/state_name_change.png b/tutorials/images/state_name_change.png new file mode 100755 index 0000000000000000000000000000000000000000..caa108a5283bacefd506b8e210b00025c2f49eb1 GIT binary patch literal 13336 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 KE7QI8 +``` + +## Run the servers +You'll need to run two servers (at the same time). The first one is the backend server, Toolshed. The following command will run Toolshed on its default port 8000. Note: the previous exported environment variables are important only for Toolshed. So make sure they are set in this shell instance. +``` +$ cd ./packages/toolshed +$ deno task dev +``` + +Next, is the front-end server, Shell. The following command will run Shell on its default port 5173. +``` +$ cd ./packages/shell +$ deno task dev-local +``` + +Now the servers should be running and you can navigate to [http://localhost:5173/](http://localhost:5173/) to see a not-too-exciting-yet charm. + +(deploy_charms)= +## How to deploy charms +To deploy your first charm, you will run the `ct` CLI tool. +You'll need to create an identity for yourself. Run the following command at the project root: +``` +$ deno task ct id new > my.key +``` +This will create a key for you. We'll refer to this in the next command which actually deploys a charm. We'll deploy the `counter.tsx` charm. You can explore other charms in the same directory. + +``` +$ deno task ct charm new --identity my.key --url http://localhost:8000/test_space ./packages/patterns/counter.tsx +Task ct ROOT=$(pwd) && cd $INIT_CWD && deno run --allow-net --allow-ffi --allow-read --allow-write --allow-env "$ROOT/packages/cli/mod.ts" "charm" "new" "--identity" "my.key" "--url" "http://localhost:8000/test_space" "./packages/patterns/counter.tsx" +Warning experimentalDecorators compiler option is deprecated and may be removed at any time +baedreihr5yyujte22cd7oogtqldt4miifj356zj7ivgk4eom264ldsu5pm +``` + +Notice the last line from the deploy output. This is the charm ID that you just deployed. We will use it to navigate to this charm. +Here is the URL; replace with + the value from your command output: `http://localhost:5173/test_space/` + +Notice the format. Port 5173 is the port number that the Shell process is listening on. +`test_space` is the SPACE that you are deploying to. You can think of it as a namespace for permissions. +Any SPACE that doesn't exist already is dynamically created when you visit it. +Lastly, we see the charm ID that was created when you deployed the charm. + +:::{admonition} Don't forget! +You will need to run the `deno task ct charm new` command each time you want to deploy a new charm. + +You will also need to keep the Toolshed and Shell servers running in order to run the deploy command and also to visit the charm on your browser. +::: + diff --git a/tutorials/llm-builtin.md b/tutorials/llm-builtin.md new file mode 100644 index 000000000..e6976cb3a --- /dev/null +++ b/tutorials/llm-builtin.md @@ -0,0 +1,303 @@ +--- +title: Using the LLM built-in within a Recipe +short_title: LLM built-in +description: A tutorial on llm built-in +subject: Tutorial +subtitle: A gentle tutorial into using the llm() function in a recipe +authors: + - name: Ellyse Cedeno + email: ellyse@common.tools +keywords: commontools, recipes, llm, builtins +abstract: | + In this section, we will create recipes to make LLM calls. We'll iterate on them to make new features, making sure you understand the changes each step of the way. +--- +(skeleton_recipe)= +## Skeleton Recipe +Let's first create a skeleton recipe. We'll need the basic imports. These are so common that you should generally just copy and paste them. + +```{code-block} typescript +:label: imports +:linenos: true +:emphasize-lines: 1 +:caption: Imports for Recipe +/// +import { + BuiltInLLMContent, + Cell, + cell, + Default, + derive, + h, + handler, + ifElse, + llm, + NAME, + recipe, + UI, +} from "commontools"; +``` +Notice line 1 begins with `/// { + return { + [NAME]: "MyLLM Test", + [UI]: ( +

    + ), + }; +}); +``` + +Note that we are using `export` on line 1, this is because a single recipe source file may have multiple recipes in it. When the system is building a charm from the source file, it needs the main entry point, which will be the recipe that you export. + +The [NAME] symbol is used as a property in the created charm. It is used for displaying the charm and also often for debugging. + +The [UI] property has all the information to render the charm in the browser. It contains a mix of JSX and interpolated function calls using `{some_function()}` syntax. The results of these functions are inserted into display for rendering. We'll learn more about what works in the UI property in future sections. + +When you deploy and browse to your charm, you should see something that looks like this: + +![](./images/llmv1.png) +**Figure**: Placeholder Recipe + +## Add User Input +Now that we have a working deployed charm, let's continue to iterate and add a user input form. +This will eventually serve as the user's input to the LLM call. + +We'll update our `recipe` function to create a `Cell` to hold the value of the user input, add the JSX component that gets user text input, and also display the user input. +A `Cell` is like a variable in programming languages. Cells can store values and can be displayed in a recipe's [UI]. They are also persistent and automatically saved to the datastore. + +Here's our updated Recipe: +```{code-block} typescript +:label: llm_code_03 +:linenos: true +:emphasize-lines: 2, 11, 15 +:caption: Adding the Input JSX Component +export default recipe("LLM Test", () => { + const userMessage = cell(undefined); + + return { + [NAME]: "MyLLM Test", + [UI]: ( +
    +

    My LLM Test

    +
    User Message: {userMessage}
    +
    + +
    +
    + ), + userMessage, + }; +}); +``` +On line 2, we have a call to cell(). This will create a Cell with the default value passed in. +We'll use this cell to store the text the user types in our user input component. + +On line 11, we've added the `` component. Note that regular HTML forms are not allowed in the recipe UI. These restrictions are there for data privacy and security reasons. +The `placeholder` property (line 13) shows faded text in the input form as its default value. The `onmessagesend` property (line 15) is called when the user submits their message (presses enter or clicks on the submit button). The value for `onmessagesend` is the function that gets executed to handle the event. We'll define that next. The parameters you send must be wrapped in an object. Example: `{ userMessage }`. Additional parameters would be comma separated. + + +Before this code will actually work, we'll need to define the textInputHandler function. This should be at the same level as the `recipe` function. + +```{code-block} typescript +:label: llm_code_04 +:linenos: true +:emphasize-lines: +:caption: Adding the Event Handler + +const textInputHandler = handler< + { detail: { message: string } }, + { userMessage: Cell } +>(({ detail: { message } }, { userMessage }) => { + userMessage.set(message); +}); +``` + +We first imported the function `handler` from {ref}`imports`. It takes in type parameters for the Event it will handle and the Args it will be passed (`handler`). The shape of `Event` is based on the component you use. In our case, we used the component `common-send-message`. It takes in an event with the shape `{detail: {message: string}}`, you see this reflected on line 2. The shape of `Args` depends on what we are passing into the handler. We've set this in {ref}`llm_code_03` line 15, where we pass in `userMessage`, which is a Cell. We therefore set the Args type as `{ userMessage: Cell }` (line 3). + +Now that we've told `handler` what to expect as types, we can send the actual parameters on line 4. Here we pretty much repeat the type information with the actual variables. + +Our function body is just a single line (line 5). It simply sets the value of the `userMessage` cell to the user input. Cells have set() and get() functions. There are other functions which we'll discover in other chapters. + +After deploying your charm, you should see something like this: + +![](./images/llm_handler.png) +**Figure**: Recipe with Handler + +(calling_llm)= +## Calling the LLM! + +The next step is the real pay-off. We'll finally call the LLM. We'll add the built-in call right after the userMessage definition in the recipe. The actual location doesn't matter as long as it's in the `recipe`'s body. +```{code-block} typescript +:label: llm_builtin +:linenos: true +:emphasize-lines: +:caption: Adding the LLM built-in call +export default recipe("LLM Test", () => { + const userMessage = cell(undefined); + + const llmResponse = llm({ + system: + "You are a helpful assistant. Answer questions clearly and concisely.", + messages: derive(userMessage, (msg) => + msg ? [{ role: "user", content: msg, }] : []), }); +``` + +Lines 4-8 are new. We imported the `llm` function on line 10 in our {ref}`imports`. +We define a variable `llmResponse` to hold the return value of the llm call. +The parameter to `llm` is an object of the shape `{system: string, messages: BuiltInLLMMessage[]}`. The `BuiltInLLMParams` type looks like this, but really you only need to worry about `system` and `messages` for now: +``` +export interface BuiltInLLMParams { + messages?: BuiltInLLMMessage[]; + model?: string; + system?: string; + stop?: string; + maxTokens?: number; + mode?: "json"; + tools?: Record; +} +``` +You can see on line 5-8 that our shape is +`{system: string, messages: Array<{role: "user", content: msg}>}`. So what's the `derive` all about here? +This gets a bit technical. The parameters to a built-in like `llm()` are +passed as an `Opaque` object. Opaque objects mirror the expected type, and +any property you pass in as an `OpaqueRef` (such as `Cell`, `derive`) +stays reactive. Plain values still stay static. We want the +`messages` property to be reactive so it updates whenever `userMessage` +changes. The easiest way to do that here is to wrap the transformation in +`derive`, which returns an `OpaqueRef`. + +We are calling `derive(userMessage, (msg) => function_body())` on line 7. This means that the reactive node depends on `userMessage`; it will be called each time userMessage is updated. `userMessage` is passed in as the first arg to the anonymous function here, as `msg`. + +Looking at the function body on line 8, we see it first checks if `msg` is defined. This is important because `derive` will be called initially with an `undefined` value. We do not want to pass that along, so we do an explicit check here. If it is defined, we then return a well-formed message array, with just one message. This looks like `[ {role: "user", content: msg} ]`. Otherwise, we return `[]`, the empty list. + +Notice that we do not have to explicitly call the llm() function each time we get an input. +This is handled for us by the reactive system we just talked about. Specifically, the `llm()` built-in will re-execute every time `userMessages` is updated. We can then use the variable `llmResponse` to view the response, which we'll do right now. + +We'll add a new section to the recipe [UI] to display the current value of the `llmResponse`. Luckily, this is very straightforward: +```{code-block} html +:label: llm_response +:linenos: true +:emphasize-lines: +:caption: Display llmResponse +
    + llmResponse: + {llmResponse.result ? JSON.stringify(llmResponse.result) : ""} +
    +``` +`llmResponse` is typed as `Opaque`. The important thing to know about this is that it has a `result?` property (the ? means it is optional). This is where the result of the llm call is stored. +Because `result` is optional, we guard with a ternary before stringifying so that we don't render `undefined` while the LLM is still working. +The result can be either a direct string or an array of parts. We call JSON.stringify() because +this just makes it easier to do a bunch of work to display it. It won't be pretty, but you'll get +content you're looking for. + +:::{dropdown} Detailed explanation +:animate: fade-in + +The AST Transformer (enabled via `/// `) rewrites that ternary expression into `{ifElse(llmResponse.result, derive(llmResponse.result, _v1 => JSON.stringify(_v1)), "")}`. You'll still need to import `ifElse` (even though you never call it yourself) alongside the existing `derive` import for the generated code to type-check. +::: + +If you deploy and run it, you should be able to enter a message into the input form, then wait a few seconds and see a response from our friendly LLM. Here is what it looks like for me: + +![](./images/llm_final.png) +**Figure**: Final Recipe + +## The Final Flow +Et voilà ! Hopefully that worked for you. The full source code is listed at the end of this section. + +Here is the flow control at a high level. +When the system first loads, it executes the body of the recipe() function, which creates the `userMessage` cell, and `llmResponse` which holds the result of calling `llm()`. + +Technically, the `llm()` built-in is called once with the undefined userMessage upon its initialization. + +The charm then renders the code in the [UI] section and the system sets the reactive node to display the llmResponse with the conditional expression we wrote (`{llmResponse.result ? ... : ""}`) and the user's message with `{userMessage}`. +These initially don't show anything since the values are undefined. + +The user types a prompt into the `` component which triggers the `textInputHandler()`. The handler gets passed in the event, which contains the user's message (as a normal js object), and also the `userMessage` which is a Cell. The handler sets the cell's value with the event message. + +The `userMessage` has been updated now and therefore kicks off the reactive system. +We re-render the portion of the UI that contains `User Message: {userMessage}` since the cell contained within the braces has changed. +The `llm()` built-in notices that its object (the messages property) has been updated and runs again. +In a few seconds, it gets a response back from the LLM. +This sets `llmResponse.result`, which triggers the generated `ifElse(derive(...))` wrapper behind that conditional expression. +And finally we see the `llmResponse: ...` in the [UI]. + +There's a lot more to discover with the llm() function call (such as sending a list of user and agent messages for history or even tool use) and even more to learn about the Common Tools runtime system. + + +```{code-block} typescript +:label: llm_full_code +:linenos: true +:emphasize-lines: +:caption: Full Code +/// +import { + BuiltInLLMContent, + Cell, + cell, + Default, + derive, + h, + handler, + ifElse, + llm, + NAME, + recipe, + UI, +} from "commontools"; + +const textInputHandler = handler< + { detail: { message: string } }, + { userMessage: Cell } +>(({ detail: { message } }, { userMessage }) => { + userMessage.set(message); +}); + +export default recipe("LLM Test", () => { + const userMessage = cell(undefined); + + const llmResponse = llm({ + system: + "You are a helpful assistant. Answer questions clearly and concisely.", + messages: derive(userMessage, (msg) => + msg ? [{ role: "user", content: msg, }] : []), }); + + return { + [NAME]: "MyLLM Test", + [UI]: ( +
    +

    MyLLM Test

    +
    User Message: {userMessage}
    +
    + llmResponse: + {llmResponse.result ? JSON.stringify(llmResponse.result) : ""} +
    +
    + +
    +
    + ), + }; +}); +``` diff --git a/tutorials/myst.yml b/tutorials/myst.yml new file mode 100644 index 000000000..45f177bee --- /dev/null +++ b/tutorials/myst.yml @@ -0,0 +1,26 @@ +# See docs at: https://mystmd.org/guide/frontmatter +version: 1 +project: + id: 9ec3a2ef-fa55-4e77-8593-8dc1fc1e51ed + title: Guide to the Common Tools Runtime + # description: + # keywords: [] + # github: + # To autogenerate a Table of Contents, run "myst init --write-toc" + toc: + # Auto-generated by `myst init --write-toc` + - file: index.md + - file: install-ct.md + - file: llm-builtin.md + - file: state.md + - file: state_modify.md + - file: common_ui.md + - file: cts.md + +site: + template: book-theme + parts: + title: Ellyxir's Guide to Common Tools + # options: + # favicon: favicon.ico + # logo: site_logo.png diff --git a/tutorials/netlify.toml b/tutorials/netlify.toml new file mode 100644 index 000000000..65181ee79 --- /dev/null +++ b/tutorials/netlify.toml @@ -0,0 +1,6 @@ +[build] + command = "npm ci && npm run build" + publish = "_build/html" + +[build.environment] + NODE_VERSION = "18" diff --git a/tutorials/package-lock.json b/tutorials/package-lock.json new file mode 100644 index 000000000..79312b238 --- /dev/null +++ b/tutorials/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "common-tools-guide", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "common-tools-guide", + "version": "0.0.1", + "devDependencies": { + "mystmd": "^1.0.0" + } + }, + "node_modules/mystmd": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mystmd/-/mystmd-1.6.1.tgz", + "integrity": "sha512-jrqXTYtKAfy3wtnXwoPSEFO9POHTe2QYI16lZAhNw1FvfWkdTmpDys8vNfXDsTWFRmRdKeDg30MdebDcnhSKxQ==", + "dev": true, + "license": "MIT", + "bin": { + "myst": "dist/myst.cjs" + } + } + } +} diff --git a/tutorials/package.json b/tutorials/package.json new file mode 100644 index 000000000..7bda2ce67 --- /dev/null +++ b/tutorials/package.json @@ -0,0 +1,13 @@ +{ + "name": "common-tools-guide", + "private": true, + "version": "0.0.1", + "scripts": { + "build": "myst build --html", + "clean": "myst clean --cache || true", + "dev": "myst start" + }, + "devDependencies": { + "mystmd": "^1.0.0" + } +} diff --git a/tutorials/state.md b/tutorials/state.md new file mode 100644 index 000000000..bbbaf09d2 --- /dev/null +++ b/tutorials/state.md @@ -0,0 +1,230 @@ +--- +title: Working with State +short_title: State +description: How state is handled in the Common Tools runtime +subject: Tutorial +authors: + - name: Ellyse Cedeno + email: ellyse@common.tools +keywords: commontools, state, Cell, database +abstract: | + In this section, we discover how state is handled in the runtime, how persistence is related, and discuss common patterns to use. +--- +## State and Cells + +We'll be learning how to handle state within the Common Tools runtime. +The most direct way to store state is via `Cells`. +Cells store and access data. We can set data in a cell via the set() function. We can also retrieve data via the get() function, we'll demonstrate that in a later section. +There are many ways to create cells and we'll get to all of them, but for now, we'll start with the `cell()` function available in Recipes. +We've already used this in {ref}`calling_llm` + +Creating a cell is quite easy! Let's create the beginnings of a +character sheet, one we might use playing a table top role playing game. Don't worry if you don't get the reference, it should be easy to follow. + +```{code-block} typescript +export default recipe("state test", () => { + const characterName = cell(""); +} +``` +Here, we have created a cell with a type argument of `string`, +and its initial value is the empty string. + +Let's now set `characterName` to something a bit more interesting. + +```{code-block} typescript +export default recipe("state test", () => { + const characterName = cell(""); + characterName.set("Lady Ellyxir"); +} +``` +We can now display the cell within the `[UI]` section of the recipe: +```{code-block} typescript +/// +import { + cell, + h, + recipe, + UI, +} from "commontools"; + +export default recipe("state test", () => { + const characterName = cell(""); + characterName.set("Lady Ellyxir"); + return { + [UI]: ( +
    +

    Character name: {characterName}

    +
    + ), + }; +}); +``` +The `{characterName}` snippet creates a reactive node behind the scenes. This means the rendered character name is updated whenever the cell changes. + +We can now deploy the code. See the section {ref}`deploy_charms` for how to do this. + +It'll look a bit like this: +![](./images/state_charname.png) +**Figure**: Character Name set + +## Deriving from Cells + +We often have computed states which are derived from existing states. + +A concrete example of derived state is AC (Armor Class). +Its value is affected by another state value, Dexterity. +We'll build out this example. + +First, let's create the Dexterity attribute. Not surprisingly, we'll use a `Cell` to store this data. We'll also display it in the `[UI]` section. + +```{code-block} typescript +:label: state_display_dex +:linenos: true +:emphasize-lines: 2,3,8 +:caption: Displaying DEX + const characterName = cell(""); + const dex = cell(16); + ... + [UI]: ( +
    +

    Character name: {characterName}

    +
  • DEX: {dex}
  • +
    + ... +``` +The highlighted lines are the ones we added. It should be pretty self-explanatory. We create the dex cell to store Dexterity and we display on line 8. + +If all we want to do is display the derived calculation, we can simply put it between the `{}` symbols. Let's display our dexterity modifier. We'll use this later to calculate our Armor Class. + +```{code-block} typescript +:label: state_display_dex_modifier +:linenos: true +:emphasize-lines: 5 +:caption: Displaying DEX Modifier + [UI]: ( +
    +

    Character name: {characterName}

    +
  • DEX: {dex}
  • +
  • DEX Modifier: {Math.floor((dex - 10) / 2)}
  • +
    + ... +``` + +It should look a bit like this: +![](./images/state_dex_mod.png) + +Let's now calculate Armor Class. +It's defined as `10 + Dexterity modifier`. We *could* do that same thing +and just display it inline, however, this gets complicated to read +after a while. Instead we'll introduce `lift()` which lets you +create a reactive state based on inputs such as cells. + +## Lift + +A lift takes a regular function and allows it to be used with +reactive nodes. For example, here's a regular TypeScript function to +calculate armor class: +```{code-block} typescript +:label: state_ac +:linenos: false +:emphasize-lines: + const calcAC = (dex: number) : number => + 10 + Math.floor((dex - 10) / 2); +``` + +We can't just pass our `dex` variable into this function since `dex` isn't a regular `number` (it's a `Cell`). This is the magic of `lift`. It takes in the regular function and returns a new function that can take in matching reactive components as parameters. + +We create the lifted function with the following code in the recipe body: +```{code-block} typescript +:label: state_ac_lift +:linenos: false +:emphasize-lines: + const ac = lift(calcAC)(dex); +``` +We can now use the reference to the lift `ac` just like we'd use any cell reference. + +:::{dropdown} Detailed explanation +:animate: fade-in + +`lift` returns a function that matches the passed in function's +argument list but accepts reactive component versions of each parameter instead. + +In our code above, we immediately call this function with the parameter `dex`. +The return value is a reactive component this will be updated anytime +the input is updated (in our case, the `dex Cell`). + +We can instead defer calling this `lift`'d function or even call it repeatedly. Each new call will result in a new reactive component that is tracked independently of the others. +::: + +Here, we add it to the `[UI]` on line 5: +```{code-block} typescript +:label: state_ac_lift_display +:linenos: true +:emphasize-lines: 5 +
    +

    Character name: {characterName}

    +
  • DEX: {dex}
  • +
  • DEX Modifier: {Math.floor((dex - 10) / 2)}
  • +
  • AC: {ac}
  • +
    +``` + +Note: we must import `lift` and `derive`. We'll need `derive` because of some behind-the-scenes code transformation, but we will not be using it directly in this section. (See {doc}`cts` for more information). + +Here's what the full Recipe looks like: +```{code-block} typescript +:label: state_code_full +:linenos: true +:emphasize-lines: 7,8,14,15,16,17,23,24 +:caption: Full State Code +/// +import { + cell, + h, + recipe, + UI, + lift, + derive, +} from "commontools"; + +export default recipe("state test", () => { + const characterName = cell(""); + characterName.set("Lady Ellyxir"); + const dex = cell(16); + const calcAC = (dex: number) : number => + 10 + Math.floor((dex - 10) / 2); + const ac = lift(calcAC)(dex); + return { + [UI]: ( +
    +

    Character name: {characterName}

    +
  • DEX: {dex}
  • +
  • DEX Modifier: {Math.floor((dex - 10) / 2)}
  • +
  • AC: {ac}
  • +
    + ), + }; +}); +``` + +It should render something similar to this: +![](./images/state_ac.png) +**Figure:** Display derived `ac` for Armor Class + +We've demonstrated the following state-related concepts: +* How to create a simple `Cell` +* Set its value +* Display the cell in `[UI]` +* Create UI that calculates a derived value (DEX Modifier) +* Create a lifted function from regular TypeScript +* Use the lifted function as a reactive component in `[UI]` + +In the next section, we will learn how user input can +modify state. + +### Credits +We used the Open Source SRD 5.1 for character sheet information. +See [SRD 5.1](https://www.dndbeyond.com/srd). +It is licensed under +Creative Commons Attribution 4.0 International (“CC-BY-4.0”) + diff --git a/tutorials/state_modify.md b/tutorials/state_modify.md new file mode 100644 index 000000000..8c5ec15ee --- /dev/null +++ b/tutorials/state_modify.md @@ -0,0 +1,205 @@ +--- +title: Modifying State +short_title: Modifying State +description: Using user input to modify state +subject: Tutorial +authors: + - name: Ellyse Cedeno + email: ellyse@common.tools +keywords: commontools, state, Cell, database +abstract: | + In this section, we will add on to the simple state we created in the last + section. We will use user input to modify the existing state. +--- +# Modifying State + +## Introduction + +In the last section, we learned how to create state via `Cell`s and also +how to create derived states. +We used building a fantasy game character sheet as an example. +We'll continue with that to learn how to modify state. + +## Handling User Input + +Let's start with changing our character's name. +We'll need to add a text input field in the `[UI]` section. +We can't just use regular HTML components. +The Common Tools runtime has its own JSX components to +make sure data is protected and not accessed by +other scripts. + +```{code-block} typescript +:label: state_send_message_placeholder +:linenos: false +:emphasize-lines: + +``` + +If you deploy this update, you'll see an input field, but nothing happens +when you enter data. As the comments indicate, we +need to fill out code for the onmessagesend JSX event listener. + +This is when we learn about `handler`. +A `handler` is a Common Tools runtime component that, like its name +suggests, handles events. +The JSX event listener (such as `onmessagesend` in our code) will call +our handler to handle the event emitted by the JSX component. + +## Understanding Handlers + +Handlers in Common Tools have a specific signature: + +```{code-block} typescript +handler(handlerFunction) +``` + +The `handler` function takes: +- Two **type** parameters: + - `EventType`: defines the event data structure + - `ArgsType`: defines the arguments/context that you want to pass to the handler +- One **argument**: we pass in a function which receives: + - `event` (matches EventType) as its first parameter + - `args` (matches ArgsType) as its second parameter + +:::{dropdown} Detailed explanation +:animate: fade-in + +The `handler` function returns a factory that you call with your actual arguments to create the event handler. This factory pattern allows the handler to bind specific values from your recipe while still receiving events from the UI components. +::: + +We'll start by writing our handler which takes the event emitted by the +`` component. This component emits a CustomEvent with the structure `{detail: {message: string}}`, +where `message` contains the text the user entered. +The handler will also take in the +`characterName` cell. It will simply set the cell with the new name +from the event. + +### Creating the Handler + +```{code-block} typescript +:label: state_handler_updatename +:linenos: false +:emphasize-lines: +const updateName = handler< + { detail: { message: string } }, + { characterName: Cell } +>( + (event, { characterName }) => { + console.log("Updating character name to:", event.detail.message); + characterName.set(event.detail.message); + } +); +``` +Note that `characterName` was passed in as a `Cell`. We created it via the +`cell()` function, which returns us a `Cell`. It's important to +mark reactive components as `Cell` so that we can call methods such +as `set()` on them. + +Now we can attach this handler to our input component: + +```{code-block} typescript +:label: state_handler_attach +:linenos: false +:emphasize-lines: + +``` + +If you deploy this code, you should see something like: +![](./images/state_name_change.png) +**Figure:** Updating your character's name + +:::{dropdown} View complete code +:animate: fade-in + +```{literalinclude} ./code/state_02.tsx +:language: typescript +``` +::: + +Et voilà ! We've fully implemented modifying state through user input. + +:::{admonition} Important! +Notice that if you reload the page, or even load the same URL on a different +browser, you'll see the saved data for your character's name. +This is because cells are persistent by default. +::: + +:::{dropdown} Detailed explanation +:animate: fade-in + +Each cell is created with a `cause` that uniquely identifies it. +We carefully construct the `cause` so that it remains the same +each time a recipe is run, but also unique from other cells created. +This leads to automatic persistence when using the Common Tools +runtime. +::: + +## Adding Buttons + +We'll create a button to roll "dice" for the character's Dexterity +value. This will update the existing value. + +First let's create the handler for the click event. We +don't need details on the event itself, so we mark it as `unknown`. + +```{code-block} typescript +:label: state_rollDex_handler +:linenos: false +:emphasize-lines: +const rollD6 = () => Math.floor(Math.random() * 6) + 1; + +const rollDex = handler< + unknown, + Cell +>( + (_, dex) => { + // Roll 3d6 for new DEX value + const roll = rollD6() + rollD6() + rollD6(); + dex.set(roll); + } +); +``` + +This handler simulates rolling three six-sided dice (3d6) and sets the DEX value to the result. + +Next, we'll add a button beside DEX in the UI and attach our handler: + +```{code-block} typescript +:label: state_button_with_handler +:linenos: false +:emphasize-lines: +
  • + DEX: {dex} + {" "} + + Roll + +
  • +``` +Note the `{" "}` between the DEX value and button - this adds just a little padding before the button. + +When we click on the button, the elements that depend on the value of that cell are also updated. This means the DEX, DEX Modifier, and AC values are all updated. + +You should see something like the following once you click on the Roll button: +![](./images/state_dex_button.png) + +:::{dropdown} View complete code +:animate: fade-in + +```{literalinclude} ./code/state_03.tsx +:language: typescript +``` +::: + +So far, we've been using `Cell` to store primitive data types. +In the next section, we'll move on to objects and arrays. From 0418f5c0acebba98a5e66d5a6afdadcea2ae0d17 Mon Sep 17 00:00:00 2001 From: jakedahn Date: Mon, 6 Oct 2025 10:42:33 -0600 Subject: [PATCH 2/8] feat: Add GitHub Actions workflow for MyST documentation deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create deploy-docs.yml workflow to build and deploy MyST docs - Trigger on main branch changes to tutorials/ directory - Configure MyST for docs.commontools.dev custom domain - Add CNAME file for GitHub Pages custom domain - Document build process in README_BUILD.md - Update MyST config with project metadata and base URL The workflow uses Node.js for MyST builds (separate from Deno codebase) and deploys static documentation to GitHub Pages automatically. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/deploy-docs.yml | 63 +++++++++++++++++++++++++++++++ tutorials/CNAME | 1 + tutorials/README_BUILD.md | 53 ++++++++++++++++++++++++++ tutorials/myst.yml | 16 ++++---- 4 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/deploy-docs.yml create mode 100644 tutorials/CNAME create mode 100644 tutorials/README_BUILD.md diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 000000000..7e452a638 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,63 @@ +name: Deploy MyST Documentation + +on: + push: + branches: [main] + paths: + - 'tutorials/**' + - '.github/workflows/deploy-docs.yml' + workflow_dispatch: # Allow manual trigger + +env: + # For custom domain docs.commontools.dev, we don't need a subfolder + BASE_URL: '' + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: 'pages' + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: tutorials/package-lock.json + + - name: Install dependencies + working-directory: tutorials + run: npm ci + + - name: Build MyST site + working-directory: tutorials + run: npm run build + + - name: Copy CNAME to build directory + run: cp tutorials/CNAME tutorials/_build/html/CNAME + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: 'tutorials/_build/html' + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/tutorials/CNAME b/tutorials/CNAME new file mode 100644 index 000000000..f89f15423 --- /dev/null +++ b/tutorials/CNAME @@ -0,0 +1 @@ +docs.commontools.dev \ No newline at end of file diff --git a/tutorials/README_BUILD.md b/tutorials/README_BUILD.md new file mode 100644 index 000000000..a7545b4df --- /dev/null +++ b/tutorials/README_BUILD.md @@ -0,0 +1,53 @@ +# Documentation Build Process + +This directory contains the MyST documentation that gets built and deployed to https://docs.commontools.dev + +## Overview + +While the main codebase uses Deno, the documentation is built using MyST (mystmd), which is a Node.js-based static site generator for scientific and technical documentation. + +## GitHub Actions Deployment + +The documentation is automatically built and deployed via GitHub Actions when: +- Changes are pushed to the `main` branch +- The changes affect files in the `tutorials/` directory +- Or when manually triggered via workflow dispatch + +The workflow is defined in `.github/workflows/deploy-docs.yml` + +## Local Development + +To work on the documentation locally, you'll need Node.js (not Deno) for MyST: + +```bash +# Navigate to tutorials directory +cd tutorials + +# Install dependencies (requires Node.js) +npm install + +# Start development server +npm run dev + +# Build static site +npm run build +``` + +## Configuration + +- **MyST Config**: `myst.yml` - Defines the site structure and settings +- **Custom Domain**: The CNAME file configures the custom domain (docs.commontools.dev) +- **GitHub Pages**: The site is deployed to GitHub Pages with the custom domain + +## Important Notes + +1. The MyST build process is completely separate from the Deno-based application code +2. The GitHub Action uses Node.js to build the documentation +3. The built HTML is deployed to GitHub Pages +4. Make sure to configure DNS records to point `docs.commontools.dev` to GitHub Pages + +## DNS Configuration Required + +For the custom domain to work, you need to configure DNS: +- Add a CNAME record pointing `docs.commontools.dev` to `.github.io` +- Or configure it according to GitHub's custom domain documentation \ No newline at end of file diff --git a/tutorials/myst.yml b/tutorials/myst.yml index 45f177bee..1ded3d75b 100644 --- a/tutorials/myst.yml +++ b/tutorials/myst.yml @@ -3,9 +3,9 @@ version: 1 project: id: 9ec3a2ef-fa55-4e77-8593-8dc1fc1e51ed title: Guide to the Common Tools Runtime - # description: - # keywords: [] - # github: + description: Documentation and tutorials for the Common Tools runtime and framework + keywords: [commontools, runtime, tutorials, documentation] + github: https://github.com/jakedahn/common/tree/main/labs # To autogenerate a Table of Contents, run "myst init --write-toc" toc: # Auto-generated by `myst init --write-toc` @@ -19,8 +19,10 @@ project: site: template: book-theme + title: Common Tools Documentation parts: - title: Ellyxir's Guide to Common Tools - # options: - # favicon: favicon.ico - # logo: site_logo.png + title: Common Tools Documentation + options: + # favicon: favicon.ico + # logo: site_logo.png + base_url: https://docs.commontools.dev From 13cffcfe3d2878f2b65ba51f3f162f57f2c4f82c Mon Sep 17 00:00:00 2001 From: jakedahn Date: Mon, 6 Oct 2025 10:44:00 -0600 Subject: [PATCH 3/8] chore: Enable GitHub Actions for migrate-tutorials branch and PRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add migrate-tutorials branch to workflow triggers - Enable workflow for pull requests (build-only) - Restrict deployment to push events on main and migrate-tutorials - PRs will build but not deploy (validation only) This allows testing the deployment from the migration branch before merging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/deploy-docs.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 7e452a638..d51844ef5 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -2,6 +2,11 @@ name: Deploy MyST Documentation on: push: + branches: [main, migrate-tutorials] + paths: + - 'tutorials/**' + - '.github/workflows/deploy-docs.yml' + pull_request: branches: [main] paths: - 'tutorials/**' @@ -52,6 +57,8 @@ jobs: path: 'tutorials/_build/html' deploy: + # Only deploy from main and migrate-tutorials branches, not from PRs + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/migrate-tutorials') environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} From 62dc383cfa572fd7d95906e3b30ffdf640f6c99f Mon Sep 17 00:00:00 2001 From: Jake Dahn Date: Mon, 6 Oct 2025 10:48:45 -0600 Subject: [PATCH 4/8] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..f89f15423 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +docs.commontools.dev \ No newline at end of file From 92c04ff26c22d9d590b306f59cd06576f2e98ea2 Mon Sep 17 00:00:00 2001 From: jakedahn Date: Mon, 6 Oct 2025 11:03:48 -0600 Subject: [PATCH 5/8] ignore tutorials --- .github/workflows/deno.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml index 413a59253..e46554c2c 100644 --- a/.github/workflows/deno.yml +++ b/.github/workflows/deno.yml @@ -35,7 +35,7 @@ jobs: run: deno task check - name: 🔎 Check codebase formatting - run: deno fmt --check + run: deno fmt --check --ignore=tutorials - name: 🔎 Check derived artifacts working-directory: packages/static From 1d3b58135a42df271f508a0b27e9cafca8b65166 Mon Sep 17 00:00:00 2001 From: jakedahn Date: Mon, 6 Oct 2025 11:09:34 -0600 Subject: [PATCH 6/8] fix: Exclude tutorials directory from Deno formatting checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add tutorials/ to fmt.exclude in deno.json - Format deploy-docs.yml to match Deno style guidelines - Ensures tutorials with different formatting rules don't break CI 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/deno.yml | 2 +- .github/workflows/deploy-docs.yml | 20 ++++++++++---------- deno.json | 3 ++- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml index e46554c2c..413a59253 100644 --- a/.github/workflows/deno.yml +++ b/.github/workflows/deno.yml @@ -35,7 +35,7 @@ jobs: run: deno task check - name: 🔎 Check codebase formatting - run: deno fmt --check --ignore=tutorials + run: deno fmt --check - name: 🔎 Check derived artifacts working-directory: packages/static diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index d51844ef5..39b736ced 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -4,18 +4,18 @@ on: push: branches: [main, migrate-tutorials] paths: - - 'tutorials/**' - - '.github/workflows/deploy-docs.yml' + - "tutorials/**" + - ".github/workflows/deploy-docs.yml" pull_request: branches: [main] paths: - - 'tutorials/**' - - '.github/workflows/deploy-docs.yml' + - "tutorials/**" + - ".github/workflows/deploy-docs.yml" workflow_dispatch: # Allow manual trigger env: # For custom domain docs.commontools.dev, we don't need a subfolder - BASE_URL: '' + BASE_URL: "" permissions: contents: read @@ -23,7 +23,7 @@ permissions: id-token: write concurrency: - group: 'pages' + group: "pages" cancel-in-progress: false jobs: @@ -36,8 +36,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" cache-dependency-path: tutorials/package-lock.json - name: Install dependencies @@ -54,7 +54,7 @@ jobs: - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - path: 'tutorials/_build/html' + path: "tutorials/_build/html" deploy: # Only deploy from main and migrate-tutorials branches, not from PRs @@ -67,4 +67,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v4 diff --git a/deno.json b/deno.json index 83e8d4875..b4dd2f3c1 100644 --- a/deno.json +++ b/deno.json @@ -91,7 +91,8 @@ "packages/static/assets/", "packages/ts-transformers/test/fixtures", "packages/schema-generator/test/fixtures", - "packages/vendor-astral" + "packages/vendor-astral", + "tutorials/" ] }, "imports": { From 3d6abf02d9e3ca4fc24ca020d0c1ee5bb7391e09 Mon Sep 17 00:00:00 2001 From: jakedahn Date: Mon, 6 Oct 2025 11:20:44 -0600 Subject: [PATCH 7/8] chore: Lock down docs deployment to main branch only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove migrate-tutorials branch from deployment triggers - Remove PR builds entirely - Only build and deploy when tutorials/ directory changes on main - Manual workflow dispatch still available for emergency deployments 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/deploy-docs.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 39b736ced..15546f4bd 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -2,11 +2,6 @@ name: Deploy MyST Documentation on: push: - branches: [main, migrate-tutorials] - paths: - - "tutorials/**" - - ".github/workflows/deploy-docs.yml" - pull_request: branches: [main] paths: - "tutorials/**" @@ -57,8 +52,8 @@ jobs: path: "tutorials/_build/html" deploy: - # Only deploy from main and migrate-tutorials branches, not from PRs - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/migrate-tutorials') + # Only deploy from main branch + if: github.event_name == 'push' && github.ref == 'refs/heads/main' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} From 736791c6a8cd8c0ac5e4dc78795d62d383dc304e Mon Sep 17 00:00:00 2001 From: jakedahn Date: Mon, 6 Oct 2025 11:29:30 -0600 Subject: [PATCH 8/8] fixing github url --- tutorials/myst.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/myst.yml b/tutorials/myst.yml index 1ded3d75b..1fdf8f3bc 100644 --- a/tutorials/myst.yml +++ b/tutorials/myst.yml @@ -5,7 +5,7 @@ project: title: Guide to the Common Tools Runtime description: Documentation and tutorials for the Common Tools runtime and framework keywords: [commontools, runtime, tutorials, documentation] - github: https://github.com/jakedahn/common/tree/main/labs + github: https://github.com/commontoolsinc/labs/ # To autogenerate a Table of Contents, run "myst init --write-toc" toc: # Auto-generated by `myst init --write-toc` @@ -15,7 +15,7 @@ project: - file: state.md - file: state_modify.md - file: common_ui.md - - file: cts.md + - file: cts.md site: template: book-theme