DraftThis article is currently in draft mode

Building a World State

A world state is a reactive data structure that manages application state. In this article, we'll explore how to build a TodoMVC application using the view model pattern with LiveStore as our world state implementation.

Interactive Demo

Architecture Overview

This example uses React with LiveStore (an event-sourced reactive database) to manage application state. The view model pattern separates business logic from UI components.

Creating the View Model

The view model (VM) defines the shape of our application's state and actions. Here's the TodoMVC VM structure:

import { computed, nanoid, queryDb } from "@livestore/livestore";
import type { Queryable, Store } from "@livestore/livestore";

import { DisposePool, dev, memoFn } from "@phosphor/utils";
import { emitDebugValue, emitDebugValueFn } from "#scripts/lib/dev/emitDebugValue.ts";
import { uiState$ } from "./livestore/queries.js";
import { events, tables } from "./livestore/schema.js";

export type TodoItemVM = {
  key: string;
  text$: Queryable<string>; 
  completed$: Queryable<boolean>; 
  toggleCompleted: () => void;
  remove: () => void;
};

export type TodoListVM = {
  header: {
    newTodoText$: Queryable<string>; 
    updateNewTodoText: (text: string) => void;
    addTodo: () => void;
  };
  itemList: {
    items$: Queryable<TodoItemVM[]>; 
  };
  footer: {
    incompleteCount$: Queryable<number>; 
    currentFilter$: Queryable<"all" | "active" | "completed">; 
    showAll: () => void;
    showActive: () => void;
    showCompleted: () => void;
    clearCompleted: () => void;
  };
};

const createID = (name: string) => `${name}_${nanoid(12)}`; 
export function createTodoListScope(store: Store): TodoListVM {
  const currentFilter$ = computed((get) => get(uiState$).filter, { label: "filter" });
  const newTodoText$ = computed((get) => get(uiState$).newTodoText, { label: "newTodoText" });

  const createTodoItemVM = memoFn((id: string): TodoItemVM => {
const completed$ = queryDb(
tables.todos.select("completed").where({ id }).first({ behaviour: "error" }), { label: "todoItem.completed", deps: [id] },
);
const text$ = queryDb(tables.todos.select("text").where({ id }).first({ behaviour: "error" }), {
label: "todoItem.text", deps: [id],
}); return { key: id, completed$, text$, toggleCompleted: () => store.commit(store.query(completed$) ? events.todoUncompleted({ id }) : events.todoCompleted({ id })), remove: () => store.commit(events.todoDeleted({ id, deletedAt: new Date() })), }; }); const visibleTodosQuery = (filter: "all" | "active" | "completed") => queryDb( () => tables.todos.where({ completed: filter === "all" ? undefined : filter === "completed", deletedAt: { op: "=", value: null }, }), { label: "visibleTodos", map: (rows) => rows.map((row) => createTodoItemVM(row.id)), deps: [filter], }, );
const visibleTodos$ = computed(
(get) => { const filter = get(currentFilter$); return get(visibleTodosQuery(filter)); }, { label: "visibleTodos" },
); const incompleteCount$ = queryDb(tables.todos.count().where({ completed: false, deletedAt: null }), { label: "incompleteCount", }); return { header: { newTodoText$, updateNewTodoText: (text: string) => store.commit(events.uiStateSet({ newTodoText: text })), addTodo: () => { const newTodoText = store.query(newTodoText$).trim(); if (newTodoText) { store.commit( events.todoCreated({ id: createID("todo"), text: newTodoText }), events.uiStateSet({ newTodoText: "" }), // update text ); } }, }, itemList: { items$: visibleTodos$, }, footer: { incompleteCount$, currentFilter$, showAll: () => store.commit(events.uiStateSet({ filter: "all" })), showActive: () => store.commit(events.uiStateSet({ filter: "active" })), showCompleted: () => store.commit(events.uiStateSet({ filter: "completed" })), clearCompleted: () => store.commit(events.todoClearedCompleted({ deletedAt: new Date() })), }, }; }

Wiring the View Model to React

The Root component sets up the LiveStore provider and creates the view model:

import { LiveStoreProvider, useStore } from "@livestore/react";
import { createContext, useContext, useMemo } from "react";

const TodoVMContext = createContext<TodoListVM | null>(null);

export const useTodoVM = () => {
  const vm = useContext(TodoVMContext);
  if (!vm) throw new Error("useTodoVM must be used within TodoVMProvider");
  return vm;
};

const TodoVMProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const { store } = useStore();
  const vm = useMemo(() => createTodoListScope(store), [store]);
  return <TodoVMContext.Provider value={vm}>{children}</TodoVMContext.Provider>;
};

export const App: React.FC = () => (
  <LiveStoreProvider
    schema={schema}
    adapter={adapter}
    renderLoading={(state) => <div>Loading LiveStore ({state.stage})...</div>}
  >
    <TodoVMProvider>
      <section className="todos">
        <Header />
        <MainSection />
        <Footer />
      </section>
    </TodoVMProvider>
  </LiveStoreProvider>
);

The key concept is that all components share the same view model instance through React Context. The view model is created once when the store is initialized, and components subscribe to reactive queries that automatically re-render when data changes.

Key patterns:

  • Single source of truth: The LiveStore holds all application state
  • Reactive queries: Components use Queryable<T> to subscribe to data
  • Action methods: View model exposes methods that commit events to the store
  • Separation of concerns: Business logic lives in the view model, not in components
Tera Template Context (Zola data-loc)
	

Tera Documentation

{
  "config": {
    "base_url": "/",
    "mode": "build",
    "title": "Phosphor",
    "description": "Things we learned while building Phosphor",
    "languages": {},
    "default_language": "en",
    "generate_feed": true,
    "generate_feeds": true,
    "feed_filenames": [
      "atom.xml"
    ],
    "taxonomies": [],
    "author": null,
    "build_search_index": true,
    "extra": {
      "logo_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"108\" height=\"26\" fill=\"none\" viewBox=\"0 0 174 42\">\n<g fill=\"currentColor\">\n<path d=\"M.4 32.6c1.9 0 3-.9 3.8-3.9l4.6-22c.7-3-.7-4-2.5-4v-.5H19c9.2 0 11 5.5 10 10.1-1 4.8-4.8 9.5-16.5 9l.1-.5c7.3.4 10.5-3.9 11.3-8.4.9-4.3-.3-9.4-6.2-9.4h-3L9.2 28.7c-.7 3 1.2 3.9 3.7 3.9v.6H.5v-.6Z\"/><path d=\"M34.5 30.5c0-1.3.9-2.7 2.5-2.7 1.5 0 2.5 1.2 2.3 3-.2 1.2.3 1.6 1 1.5 1.7-.2 4.6-4.2 4.7-10.7 0-6.4-2-8.6-3.6-8.6-2.9 0-7.8 8-9.8 17.9l-.5 2.3h-4.7l5.7-27c.6-2.6 0-3.4-2-3.4v-.6c2.6-.2 5-.5 7.7-2.2l.3.3-.8 4-4 18.1C36 16 40 10 43.9 10c3.4 0 5.4 3.5 5.1 8.4-.3 6.2-4.8 15.2-10.8 15.2-2.4 0-3.6-1.6-3.6-3Zm15.9-9.2c1.6-7 7.4-11 12.6-11 5.2 0 9 5 7.3 12-1.6 7.2-7.4 11.3-12.5 11.3-5.5 0-9-5.2-7.4-12.3Zm7.9 11.5c2.7 0 5.6-4.1 7-11.1 1.4-7 .3-10.8-2.7-10.7-3 0-6 4.3-7.3 11.1-1.4 7 0 10.8 3 10.8Zm11.4-3.3c-.4-1.6.2-3 1.4-3.4a2.2 2.2 0 0 1 3 1.3c.5 1.4-.7 2.4-.6 3.8.2 1 1.2 1.9 3 1.8 1.8 0 3.5-1 4-3 .4-2-1-3.8-3.7-6.5-3-3-4.4-5.2-3.9-8.1.7-3 3.5-5.1 7.3-5.2 3-.1 5.9 1.2 6.5 3.6.5 1.5 0 3-1.4 3.4a2.1 2.1 0 0 1-2.8-1.3c-.5-1.6.8-2.5.6-3.5-.2-.9-1.1-1.7-2.6-1.6a3.8 3.8 0 0 0-3.7 3.2c-.2 1.7.5 3 3.1 5.5 2.7 2.6 5.2 5 4.5 8.5-.7 3.8-4 5.6-7.7 5.6-2.8 0-6.2-1.3-7-4.2Zm17.4 12c1.7 0 3-1 3-4v-22c0-2-.8-2.8-2.9-2.8V12c2.4-.2 5-.7 7.3-1.7l.4.2v4.6a7.7 7.7 0 0 1 7.1-4.8c5.3 0 9 5 9 11.4s-4.5 11.8-10.3 11.8A7.4 7.4 0 0 1 95 31v6.5c0 3 1.6 4 3.7 4v.5H87.1v-.6Zm13.3-8.7c3.3 0 5.7-3.7 5.7-9.4 0-6.9-3-10.5-6.5-10.5-2.4 0-4 1.6-4.7 3.7v7.6c0 4.8 2 8.6 5.5 8.6Z\"/><path d=\"M125 32.6c2.2 0 3.8-.9 3.4-3.9l-1.4-10c-.5-3.6-1.9-5.7-4.6-5.7-2.3 0-4 2.2-4.3 4.8v10.9c0 3 1.3 3.9 3 3.9v.6h-10.9v-.6c1.8 0 3.1-.8 3.1-3.9v-23c0-2.2-.9-3-2.9-3v-.5c2.3-.2 5-.6 7.3-1.7l.4.2V16c.8-3 3.3-5.6 6.7-5.6 3.7 0 6.2 2.5 7 8.2l1.5 10.2c.4 3 1.7 3.9 3.5 3.9v.5h-11.7v-.5ZM134.4 21.8a11 11 0 0 1 11.1-11.5c6 0 11.2 4.9 11.2 11.6 0 6.8-5 11.7-11 11.7-6.2 0-11.3-5.1-11.3-11.8Zm12.6 11c3.7-.6 5.3-5.7 4.5-12-.8-6-3.8-10.1-7.4-9.6-3.7.5-5.3 5.6-4.5 11.6.8 6.2 3.7 10.4 7.4 10Z\"/><path d=\"M155.9 32.6c1.8 0 3-.9 3-4v-13c0-2.2-.8-2.9-2.8-3v-.5a21.2 21.2 0 0 0 7.3-1.7l.4.2v4.8c.7-2.5 2.6-5 5.4-5 2.1 0 3.5 1.5 3.5 3.1 0 1.4-1 2.7-2.6 2.7s-2-1.4-2.4-2.3c-.3-.7-.5-1-1-1-1.4 0-2.7 2-3 4.2v11.6c0 3 1.8 3.9 4 3.9v.6H156v-.6Z\"/>\n</g>\n</svg>\n",
      "drafts": true,
      "sections": [
        "Getting Started",
        "Managing Complexity",
        "Moving Quickly",
        "Understanding Quickly",
        "Architecture Patterns",
        "Interactive Demos",
        "Developer Tools",
        "Advanced Topics"
      ],
      "author": "Phosphor"
    },
    "markdown": {
      "highlight_code": true,
      "error_on_missing_highlight": false,
      "highlight_theme": "base16-ocean-dark",
      "highlight_themes_css": [],
      "render_emoji": false,
      "external_links_class": null,
      "external_links_target_blank": false,
      "external_links_no_follow": false,
      "external_links_no_referrer": false,
      "smart_punctuation": false,
      "definition_list": false,
      "bottom_footnotes": false,
      "extra_syntaxes_and_themes": [],
      "lazy_async_image": false,
      "insert_anchor_links": "none",
      "github_alerts": false
    },
    "search": {
      "index_format": "elasticlunr_javascript"
    },
    "generate_sitemap": true,
    "generate_robots_txt": true,
    "exclude_paginated_pages_in_sitemap": "none"
  },
  "current_path": "/world-state-example/",
  "current_url": "/world-state-example/",
  "lang": "en",
  "page": {
    "relative_path": "world-state-example.md",
    "colocated_path": null,
    "content": "<h1 id=\"building-a-world-state\">Building a World State <div data-loc=\"content/world-state-example.md:10\" class=\"heading-src\">⋅</div></h1>\n<p>A world state is a reactive data structure that manages application state. In this article, we'll explore how to build a TodoMVC application using the view model pattern with LiveStore as our world state implementation.</p>\n<h2 id=\"interactive-demo\">Interactive Demo <div data-loc=\"content/world-state-example.md:14\" class=\"heading-src\">⋅</div></h2>\n<link rel=\"stylesheet\" href=\"/js/todo-vm.entrypoint.css\">\n<div data-loc=\"content/world-state-example.md:17\" id=\"todo-vm-demo\"></div>\n<script src=\"/js/todo-vm.entrypoint.js\" type=\"module\"></script>\n<h2 id=\"architecture-overview\">Architecture Overview <div data-loc=\"content/world-state-example.md:20\" class=\"heading-src\">⋅</div></h2>\n<p>This example uses React with LiveStore (an event-sourced reactive database) to manage application state. The view model pattern separates business logic from UI components.</p>\n<h2 id=\"creating-the-view-model\">Creating the View Model <div data-loc=\"content/world-state-example.md:24\" class=\"heading-src\">⋅</div></h2>\n<p>The view model (VM) defines the shape of our application's state and actions. Here's the TodoMVC VM structure:</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;world-state-example.md:28\">\n  \n  <div class=\"codeblock-content\" data-id=\"todo-vm&#x2F;scope.ts\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"content/_sources/view-model-interfaces/todo-vm/scope.ts:1\"><code><span class=\"line\"><span style=\"color:#31748F\">import </span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> computed</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> nanoid</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> queryDb</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> from </span><span style=\"color:#F6C177\">\"@livestore/livestore\"</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">import type </span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> Queryable</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> Store</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> from </span><span style=\"color:#F6C177\">\"@livestore/livestore\"</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#31748F\">import </span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> DisposePool</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> dev</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> memoFn</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> from </span><span style=\"color:#F6C177\">\"@phosphor/utils\"</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">import </span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> emitDebugValue</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> emitDebugValueFn</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> from </span><span style=\"color:#F6C177\">\"#scripts/lib/dev/emitDebugValue.ts\"</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">import </span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> uiState$</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> from </span><span style=\"color:#F6C177\">\"./livestore/queries.js\"</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">import </span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> events</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> tables</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> from </span><span style=\"color:#F6C177\">\"./livestore/schema.js\"</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#31748F\">export</span><span style=\"color:#31748F\"> type</span><span style=\"color:#9CCFD8\"> TodoItemVM</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  key</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> string</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  text$</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Queryable</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">string</span><span style=\"color:#908CAA\">>;</span><span style=\"color:#E0DEF4\"> </span><span data-debug=\"!todo.text\"></span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  completed$</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Queryable</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">boolean</span><span style=\"color:#908CAA\">>;</span><span style=\"color:#E0DEF4\"> </span><span data-debug=\"!todo.completed\"></span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">  toggleCompleted</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#9CCFD8\"> void</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">  remove</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#9CCFD8\"> void</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#31748F\">export</span><span style=\"color:#31748F\"> type</span><span style=\"color:#9CCFD8\"> TodoListVM</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  header</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">    newTodoText$</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Queryable</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">string</span><span style=\"color:#908CAA\">>;</span><span style=\"color:#E0DEF4\"> </span><span data-debug=\"!newTodoText\"></span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">    updateNewTodoText</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">text</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> string</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#9CCFD8\"> void</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">    addTodo</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#9CCFD8\"> void</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  };</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  itemList</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">    items$</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Queryable</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">TodoItemVM</span><span style=\"color:#E0DEF4\">[]</span><span style=\"color:#908CAA\">>;</span><span style=\"color:#E0DEF4\"> </span><span data-debug=\"!visibleTodos\"></span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  };</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  footer</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">    incompleteCount$</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Queryable</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">number</span><span style=\"color:#908CAA\">>;</span><span style=\"color:#E0DEF4\"> </span><span data-debug=\"!incompleteCount\"></span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">    currentFilter$</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Queryable</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#F6C177\">\"all\"</span><span style=\"color:#31748F\"> |</span><span style=\"color:#F6C177\"> \"active\"</span><span style=\"color:#31748F\"> |</span><span style=\"color:#F6C177\"> \"completed\"</span><span style=\"color:#908CAA\">>;</span><span style=\"color:#E0DEF4\"> </span><span data-debug=\"!currentFilter\"></span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">    showAll</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#9CCFD8\"> void</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">    showActive</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#9CCFD8\"> void</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">    showCompleted</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#9CCFD8\"> void</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">    clearCompleted</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#9CCFD8\"> void</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  };</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line muted\"><span style=\"color:#31748F\">const</span><span style=\"color:#EBBCBA;font-style:italic\"> createID</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">name</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> string</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#F6C177\"> `</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">name</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">_</span><span style=\"color:#908CAA\">${</span><span style=\"color:#EBBCBA\">nanoid</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#EBBCBA\">12</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">`</span><span style=\"color:#908CAA\">;</span><span style=\"color:#E0DEF4\"> </span></span>\n<span class=\"line\"><span style=\"color:#31748F\">export</span><span style=\"color:#31748F\"> function</span><span style=\"color:#EBBCBA\"> createTodoListScope</span><span style=\"color:#908CAA\">(</span><span style=\"color:#C4A7E7;font-style:italic\">store</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Store</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> TodoListVM</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  const</span><span style=\"color:#E0DEF4;font-style:italic\"> currentFilter$</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> computed</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">(</span><span style=\"color:#C4A7E7;font-style:italic\">get</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#EBBCBA\"> get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">uiState$</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">filter</span><span style=\"color:#908CAA\">,</span><span style=\"color:#908CAA\"> {</span><span style=\"color:#E0DEF4\"> label</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"filter\"</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  const</span><span style=\"color:#E0DEF4;font-style:italic\"> newTodoText$</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> computed</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">(</span><span style=\"color:#C4A7E7;font-style:italic\">get</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#EBBCBA\"> get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">uiState$</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">newTodoText</span><span style=\"color:#908CAA\">,</span><span style=\"color:#908CAA\"> {</span><span style=\"color:#E0DEF4\"> label</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"newTodoText\"</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#31748F\">  const</span><span style=\"color:#E0DEF4;font-style:italic\"> createTodoItemVM</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> memoFn</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">(</span><span style=\"color:#C4A7E7;font-style:italic\">id</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> string</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> TodoItemVM</span><span style=\"color:#31748F\"> =></span><span style=\"color:#908CAA\"> {</span></span>\n<details class=\"code-fold\" data-fold-kind=\"indent\"><summary class=\"code-fold-summary\"><span class=\"line\"><span style=\"color:#31748F\">    const</span><span style=\"color:#E0DEF4;font-style:italic\"> completed$</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> queryDb</span><span style=\"color:#E0DEF4\">(</span></span><span class=\"code-fold-ellipsis\" aria-hidden=\"true\">[…]</span></summary><div class=\"code-fold-content\"><span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      tables</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">todos</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">select</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\"completed\"</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">where</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> id</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">first</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> behaviour</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"error\"</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">      {</span><span style=\"color:#E0DEF4\"> label</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"todoItem.completed\"</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4\"> deps</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4\"> [</span><span style=\"color:#E0DEF4;font-style:italic\">id</span><span style=\"color:#E0DEF4\">] </span><span style=\"color:#908CAA\">},</span></span></div></details><span class=\"line\"><span style=\"color:#E0DEF4\">    )</span><span style=\"color:#908CAA\">;</span></span>\n<details class=\"code-fold\" data-fold-kind=\"indent\"><summary class=\"code-fold-summary\"><span class=\"line\"><span style=\"color:#31748F\">    const</span><span style=\"color:#E0DEF4;font-style:italic\"> text$</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> queryDb</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">tables</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">todos</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">select</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\"text\"</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">where</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> id</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">first</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> behaviour</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"error\"</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">,</span><span style=\"color:#908CAA\"> {</span></span><span class=\"code-fold-ellipsis\" aria-hidden=\"true\">[…]</span></summary><div class=\"code-fold-content\"><span class=\"line\"><span style=\"color:#E0DEF4\">      label</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"todoItem.text\"</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">      deps</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4\"> [</span><span style=\"color:#E0DEF4;font-style:italic\">id</span><span style=\"color:#E0DEF4\">]</span><span style=\"color:#908CAA\">,</span></span></div></details><span class=\"line\"><span style=\"color:#908CAA\">    }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    return</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">      key</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4;font-style:italic\"> id</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      completed$</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      text$</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      toggleCompleted</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">        store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">commit</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">query</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">completed$</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">?</span><span style=\"color:#E0DEF4;font-style:italic\"> events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">todoUncompleted</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> id</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">:</span><span style=\"color:#E0DEF4;font-style:italic\"> events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">todoCompleted</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> id</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      remove</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">commit</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">todoDeleted</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> id</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4\"> deletedAt</span><span style=\"color:#908CAA\">:</span><span style=\"color:#31748F\"> new</span><span style=\"color:#EBBCBA\"> Date</span><span style=\"color:#E0DEF4\">() </span><span style=\"color:#908CAA\">}</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">    };</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#31748F\">  const</span><span style=\"color:#EBBCBA;font-style:italic\"> visibleTodosQuery</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">filter</span><span style=\"color:#31748F\">:</span><span style=\"color:#F6C177\"> \"all\"</span><span style=\"color:#31748F\"> |</span><span style=\"color:#F6C177\"> \"active\"</span><span style=\"color:#31748F\"> |</span><span style=\"color:#F6C177\"> \"completed\"</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">    queryDb</span><span style=\"color:#E0DEF4\">(</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">      ()</span><span style=\"color:#31748F\"> =></span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">        tables</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">todos</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">where</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">          completed</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4;font-style:italic\"> filter</span><span style=\"color:#31748F\"> ===</span><span style=\"color:#F6C177\"> \"all\"</span><span style=\"color:#31748F\"> ?</span><span style=\"color:#EBBCBA\"> undefined</span><span style=\"color:#31748F\"> :</span><span style=\"color:#E0DEF4;font-style:italic\"> filter</span><span style=\"color:#31748F\"> ===</span><span style=\"color:#F6C177\"> \"completed\"</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">          deletedAt</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> {</span><span style=\"color:#E0DEF4\"> op</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"=\"</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4\"> value</span><span style=\"color:#908CAA\">:</span><span style=\"color:#EBBCBA\"> null</span><span style=\"color:#908CAA\"> },</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">        }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">      {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">        label</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"visibleTodos\"</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">        map</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">rows</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#E0DEF4;font-style:italic\"> rows</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">map</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">(</span><span style=\"color:#C4A7E7;font-style:italic\">row</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#EBBCBA\"> createTodoItemVM</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">row</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">id</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">        deps</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4\"> [</span><span style=\"color:#E0DEF4;font-style:italic\">filter</span><span style=\"color:#E0DEF4\">]</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">      },</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">    )</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"></span>\n<details class=\"code-fold\" data-fold-kind=\"indent\"><summary class=\"code-fold-summary\"><span class=\"line\"><span style=\"color:#31748F\">  const</span><span style=\"color:#E0DEF4;font-style:italic\"> visibleTodos$</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> computed</span><span style=\"color:#E0DEF4\">(</span></span><span class=\"code-fold-ellipsis\" aria-hidden=\"true\">[…]</span></summary><div class=\"code-fold-content\"><span class=\"line\"><span style=\"color:#908CAA\">    (</span><span style=\"color:#C4A7E7;font-style:italic\">get</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">      const</span><span style=\"color:#E0DEF4;font-style:italic\"> filter</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">currentFilter$</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">      return</span><span style=\"color:#EBBCBA\"> get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#EBBCBA\">visibleTodosQuery</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">filter</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">    },</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">    {</span><span style=\"color:#E0DEF4\"> label</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"visibleTodos\"</span><span style=\"color:#908CAA\"> },</span></span></div></details><span class=\"line\"><span style=\"color:#E0DEF4\">  )</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#31748F\">  const</span><span style=\"color:#E0DEF4;font-style:italic\"> incompleteCount$</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> queryDb</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">tables</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">todos</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">count</span><span style=\"color:#E0DEF4\">()</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">where</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> completed</span><span style=\"color:#908CAA\">:</span><span style=\"color:#EBBCBA\"> false</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4\"> deletedAt</span><span style=\"color:#908CAA\">:</span><span style=\"color:#EBBCBA\"> null</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">,</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">    label</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"incompleteCount\"</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#31748F\">  return</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">    header</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      newTodoText$</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      updateNewTodoText</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">text</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> string</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">commit</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">uiStateSet</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> newTodoText</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4;font-style:italic\"> text</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      addTodo</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        const</span><span style=\"color:#E0DEF4;font-style:italic\"> newTodoText</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">query</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">newTodoText$</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">trim</span><span style=\"color:#E0DEF4\">()</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        if</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">newTodoText</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">          store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">commit</span><span style=\"color:#E0DEF4\">(</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">            events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">todoCreated</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> id</span><span style=\"color:#908CAA\">:</span><span style=\"color:#EBBCBA\"> createID</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\"todo\"</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4\"> text</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4;font-style:italic\"> newTodoText</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">            events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">uiStateSet</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> newTodoText</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"\"</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">,</span><span style=\"color:#908CAA;font-style:italic\"> //</span><span style=\"color:#6E6A86;font-style:italic\"> update text</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">          )</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">        }</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">      },</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">    },</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">    itemList</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">      items$</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4;font-style:italic\"> visibleTodos$</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">    },</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">    footer</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      incompleteCount$</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      currentFilter$</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      showAll</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">commit</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">uiStateSet</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> filter</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"all\"</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      showActive</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">commit</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">uiStateSet</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> filter</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"active\"</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      showCompleted</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">commit</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">uiStateSet</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> filter</span><span style=\"color:#908CAA\">:</span><span style=\"color:#F6C177\"> \"completed\"</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      clearCompleted</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> ()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">commit</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">events</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">todoClearedCompleted</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4\"> deletedAt</span><span style=\"color:#908CAA\">:</span><span style=\"color:#31748F\"> new</span><span style=\"color:#EBBCBA\"> Date</span><span style=\"color:#E0DEF4\">() </span><span style=\"color:#908CAA\">}</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">    },</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  };</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">}</span></span>\n<span class=\"line\"></span></code></pre>\n  </div>\n  \n</div>\n<h2 id=\"wiring-the-view-model-to-react\">Wiring the View Model to React <div data-loc=\"content/world-state-example.md:30\" class=\"heading-src\">⋅</div></h2>\n<p>The Root component sets up the LiveStore provider and creates the view model:</p>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span style=\"color:#b48ead;\">import </span><span>{ </span><span style=\"color:#bf616a;\">LiveStoreProvider</span><span>, </span><span style=\"color:#bf616a;\">useStore </span><span>} </span><span style=\"color:#b48ead;\">from </span><span>&quot;</span><span style=\"color:#a3be8c;\">@livestore/react</span><span>&quot;;\n</span><span style=\"color:#b48ead;\">import </span><span>{ </span><span style=\"color:#bf616a;\">createContext</span><span>, </span><span style=\"color:#bf616a;\">useContext</span><span>, </span><span style=\"color:#bf616a;\">useMemo </span><span>} </span><span style=\"color:#b48ead;\">from </span><span>&quot;</span><span style=\"color:#a3be8c;\">react</span><span>&quot;;\n</span><span>\n</span><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">TodoVMContext </span><span>= </span><span style=\"color:#8fa1b3;\">createContext</span><span>&lt;TodoListVM | null&gt;(</span><span style=\"color:#d08770;\">null</span><span>);\n</span><span>\n</span><span style=\"color:#b48ead;\">export const </span><span style=\"color:#8fa1b3;\">useTodoVM </span><span>= () </span><span style=\"color:#b48ead;\">=&gt; </span><span>{\n</span><span>  </span><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">vm </span><span>= </span><span style=\"color:#8fa1b3;\">useContext</span><span>(</span><span style=\"color:#bf616a;\">TodoVMContext</span><span>);\n</span><span>  </span><span style=\"color:#b48ead;\">if </span><span>(!</span><span style=\"color:#bf616a;\">vm</span><span>) </span><span style=\"color:#b48ead;\">throw </span><span>new Error(&quot;</span><span style=\"color:#a3be8c;\">useTodoVM must be used within TodoVMProvider</span><span>&quot;);\n</span><span>  </span><span style=\"color:#b48ead;\">return </span><span style=\"color:#bf616a;\">vm</span><span>;\n</span><span>};\n</span><span>\n</span><span style=\"color:#b48ead;\">const </span><span style=\"color:#8fa1b3;\">TodoVMProvider</span><span>: React.FC&lt;{ </span><span style=\"color:#bf616a;\">children</span><span>: React.ReactNode }&gt; = ({ </span><span style=\"color:#bf616a;\">children </span><span>}) </span><span style=\"color:#b48ead;\">=&gt; </span><span>{\n</span><span>  </span><span style=\"color:#b48ead;\">const </span><span>{ </span><span style=\"color:#bf616a;\">store </span><span>} = </span><span style=\"color:#8fa1b3;\">useStore</span><span>();\n</span><span>  </span><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">vm </span><span>= </span><span style=\"color:#8fa1b3;\">useMemo</span><span>(() </span><span style=\"color:#b48ead;\">=&gt; </span><span style=\"color:#8fa1b3;\">createTodoListScope</span><span>(</span><span style=\"color:#bf616a;\">store</span><span>), [</span><span style=\"color:#bf616a;\">store</span><span>]);\n</span><span>  </span><span style=\"color:#b48ead;\">return </span><span>&lt;</span><span style=\"color:#ebcb8b;\">TodoVMContext.Provider </span><span style=\"color:#d08770;\">value</span><span>=</span><span style=\"color:#ab7967;\">{</span><span style=\"color:#bf616a;\">vm</span><span style=\"color:#ab7967;\">}</span><span>&gt;</span><span style=\"color:#ab7967;\">{</span><span style=\"color:#bf616a;\">children</span><span style=\"color:#ab7967;\">}</span><span>&lt;/</span><span style=\"color:#ebcb8b;\">TodoVMContext.Provider</span><span>&gt;;\n</span><span>};\n</span><span>\n</span><span style=\"color:#b48ead;\">export const </span><span style=\"color:#8fa1b3;\">App</span><span>: React.FC = () </span><span style=\"color:#b48ead;\">=&gt; </span><span>(\n</span><span>  &lt;</span><span style=\"color:#ebcb8b;\">LiveStoreProvider\n</span><span>    </span><span style=\"color:#d08770;\">schema</span><span>=</span><span style=\"color:#ab7967;\">{</span><span style=\"color:#bf616a;\">schema</span><span style=\"color:#ab7967;\">}\n</span><span>    </span><span style=\"color:#d08770;\">adapter</span><span>=</span><span style=\"color:#ab7967;\">{</span><span style=\"color:#bf616a;\">adapter</span><span style=\"color:#ab7967;\">}\n</span><span>    </span><span style=\"color:#d08770;\">renderLoading</span><span>=</span><span style=\"color:#ab7967;\">{</span><span>(</span><span style=\"color:#bf616a;\">state</span><span>) </span><span style=\"color:#b48ead;\">=&gt; </span><span>&lt;</span><span style=\"color:#bf616a;\">div</span><span>&gt;Loading LiveStore (</span><span style=\"color:#ab7967;\">{</span><span style=\"color:#bf616a;\">state</span><span>.</span><span style=\"color:#bf616a;\">stage</span><span style=\"color:#ab7967;\">}</span><span>)...&lt;/</span><span style=\"color:#bf616a;\">div</span><span>&gt;</span><span style=\"color:#ab7967;\">}\n</span><span>  &gt;\n</span><span>    &lt;</span><span style=\"color:#ebcb8b;\">TodoVMProvider</span><span>&gt;\n</span><span>      &lt;</span><span style=\"color:#bf616a;\">section </span><span style=\"color:#d08770;\">className</span><span>=&quot;</span><span style=\"color:#a3be8c;\">todos</span><span>&quot;&gt;\n</span><span>        &lt;</span><span style=\"color:#ebcb8b;\">Header </span><span>/&gt;\n</span><span>        &lt;</span><span style=\"color:#ebcb8b;\">MainSection </span><span>/&gt;\n</span><span>        &lt;</span><span style=\"color:#ebcb8b;\">Footer </span><span>/&gt;\n</span><span>      &lt;/</span><span style=\"color:#bf616a;\">section</span><span>&gt;\n</span><span>    &lt;/</span><span style=\"color:#ebcb8b;\">TodoVMProvider</span><span>&gt;\n</span><span>  &lt;/</span><span style=\"color:#ebcb8b;\">LiveStoreProvider</span><span>&gt;\n</span><span>);\n</span></code></pre>\n<p>The key concept is that all components share the same view model instance through React Context. The view model is created once when the store is initialized, and components subscribe to reactive queries that automatically re-render when data changes.</p>\n<p><strong>Key patterns:</strong></p>\n<ul>\n<li><strong>Single source of truth</strong>: The LiveStore holds all application state</li>\n<li><strong>Reactive queries</strong>: Components use <code>Queryable&lt;T&gt;</code> to subscribe to data</li>\n<li><strong>Action methods</strong>: View model exposes methods that commit events to the store</li>\n<li><strong>Separation of concerns</strong>: Business logic lives in the view model, not in components</li>\n</ul>\n",
    "permalink": "/world-state-example/",
    "slug": "world-state-example",
    "ancestors": [
      "_index.md"
    ],
    "title": "Building a World State",
    "description": null,
    "updated": null,
    "date": "2025-10-15",
    "year": 2025,
    "month": 10,
    "day": 15,
    "taxonomies": {},
    "authors": [],
    "extra": {
      "nav_section": "Architecture Patterns",
      "nav_order": 2
    },
    "path": "/world-state-example/",
    "components": [
      "world-state-example"
    ],
    "summary": null,
    "toc": [
      {
        "level": 1,
        "id": "building-a-world-state",
        "permalink": "/world-state-example/#building-a-world-state",
        "title": "Building a World State ⋅",
        "children": [
          {
            "level": 2,
            "id": "interactive-demo",
            "permalink": "/world-state-example/#interactive-demo",
            "title": "Interactive Demo ⋅",
            "children": []
          },
          {
            "level": 2,
            "id": "architecture-overview",
            "permalink": "/world-state-example/#architecture-overview",
            "title": "Architecture Overview ⋅",
            "children": []
          },
          {
            "level": 2,
            "id": "creating-the-view-model",
            "permalink": "/world-state-example/#creating-the-view-model",
            "title": "Creating the View Model ⋅",
            "children": []
          },
          {
            "level": 2,
            "id": "wiring-the-view-model-to-react",
            "permalink": "/world-state-example/#wiring-the-view-model-to-react",
            "title": "Wiring the View Model to React ⋅",
            "children": []
          }
        ]
      }
    ],
    "word_count": 295,
    "reading_time": 2,
    "assets": [],
    "draft": true,
    "lang": "en",
    "lower": {
      "relative_path": "der-draft.md",
      "colocated_path": null,
      "content": "<h1 id=\"design-engineering-rectangles\">Design Engineering Rectangles <div data-loc=\"content/der-draft.md:7\" class=\"heading-src\">⋅</div></h1>\n<h2 id=\"s1-hook-rectangle-hydra-problem\">[S1] Hook: Rectangle hydra problem <div data-loc=\"content/der-draft.md:9\" class=\"heading-src\">⋅</div></h2>\n<p>Hi, my name is Cole. I work at Phosphor where we are tackling extremely dense UI problems. At previous companies, I had experienced very similar things. But the scale out was always a challenge I had gone from in my career I've gone from a basic view-model-controller patterns to a form of MVVM (model-view-view-model) and then at my previous start-up, I ventured into an architecture that was in Rust. While writing in Rust, we found that the ease due to how the borrow checker was working made it the easiest way to do this is to write it in Rust.</p>\n<p>The most easy way to do things like reactivity was through a pattern called entity-component-systems, which allow you to create detached components from entities where entities are themselves just IDs that you use to look up components in a sparse set or... a type map.</p>\n<p>What this yielded was that in files that needed to do some kind of complex logic, instead of importing an entire AST structure, you had a parsed version and type checking and errors all as separate components, so one system could handle the raw structure too. Parsed form. Then another system could take parsed form too. The two errors and suggestions and a separate system could handle the parsed form to suggestions. Yet another system says, \"Okay, now let's map those suggestions to UI notifications\", and each one of these systems was able to sort of build on each other in an extremely scoped way where they own each system individually only cared about the components between them.</p>\n<p>I had previously used this pattern not just because I was in Rust previously, but I grew to love the pattern for the properties that gave me for testing and reasoning. I had the suspicion early on here at Phosphor that we could leverage this pattern to improve our UI building because every rectangle has a lot of complexity behind it and you have to figure out how you're going to scale out that complexity.</p>\n<hr />\n<p>Let me take that step back and tell you kind of like, well, see if I can paint the picture of the problem that you know we're experiencing at Phosphor. If we start with one input box, you're going to imagine there's a little there's some hooks, there's some state management, there might be some persistence involved, and you kind of start. Then we add a little bit more. We add validation, and validation will report if there's an error or something. This is where you're thinking now I need system-wide undo and redo, which is going to touch a few files and need to link things together. Then you say, \"Okay, well, I have a shit. I need to also support collaborative editing so multiple people can see what each other are doing and maintain kind of a collaborative feature.\" This is going to touch a bunch of files because now we're talking about the way you present things and it's the talking about your sync layer. Your front-end needs to maybe be reworked so that the sync layer is actually the hook that you're using and not some other persistence thing. You refactor that. \"Okay, cool, normal things.\" But we start to notice that unless you adopt a full sync engine tool, say like Yjs or Loro + Loro-Mirror, the problem becomes very big already.</p>\n<p>[hole] - example of components</p>\n<p>But things don't stop there. All we've categorized so far is very basic, like features for any authorable environment. These are table stakes basics. Maybe some authorable environments don't need validation pieces, but let's continue because now we have to talk about the things that are expensive at Phosphor. We go from what we just described to having a number of additional features like comparisons, so you are comparing a single value in that input to another value, and where does that other value come from? For us, it comes from a version control state. We maintain a version-controlled graph of all the changes and groups of changes with proposals and where every change came from, and we present information about every value's origin in the UI. Right here, you start to wonder, \"Okay, well, if I'm going to present all this information in the UI, how am I going to manage that complexity? I have undo/redo, I didn't even mention key bindings yet, which is another thing you need to consider. Then we're talking about diff views that we're seeing like before and afters, and that has its own set of layers that we don't have to get into, but it's a lot. I'm not even sure I could continue. We could talk about keyboard maps and customizable key bindings, and we can talk about selection state, where you have multiple things that are selected, and then you have copy/paste. You have that, the yada yada, a lot that ends up going into this, and that's how we found ourselves here.</p>\n<p>And here forward, I'm thinking to myself, \"Well, I know how we scaled the complexity at Story.ai, and that was through this ECS pattern. How will that adapt to my favorite approach to UI state management today, which is the view model pattern?\" I started thinking about that because I practically maintained our company's ECS crate that we were specifically using and we had a lot of opinions about how to build up that crate and make it have better errors and make it more usable and so forth. I thought, \"Okay, well, maybe we could adapt a lot of that idea into a library that we can use here at Phosphor,\" and that is where I started with <code>WorldState</code>. <code>WorldState</code> is a set of tools that allows you to build up a UI state without any reliance on React and with this sort of decoupled approach that I liked from structuring things as view models and vs. views and things that I like from ECS. Right now, our <code>WorldState</code> is backed by Jotai Atoms, which is just a reactive container of some kind and reactive values, right? Signals and so forth. We're considering adapting <code>WorldState</code> to use Livestore as our reactivity graph next.</p>\n<h2 id=\"s2-ecs-solution-translated-to-ui\">[S2] ECS solution translated to UI <div data-loc=\"content/der-draft.md:29\" class=\"heading-src\">⋅</div></h2>\n<p>So I was thinking to myself, \"What are the characteristics going to be like? Because unlike Rust, we have these reactive containers and we don't have the frame-by-frame computation thing that most games have. We just have components that contain atoms, and when we want to create the equivalent of a system, we are going to create a plugin. A plugin will base on the fact that if there's an entity created with these components, then I can provide these components and there's never a need to add or remove components from it. You just simply have this sort of plugin architecture.\"</p>\n<blockquote>\n<p>Game developers faced this exact problem a decade ago and found an elegant solution: entities, components, and systems. The pattern translates directly to UI engineering.</p>\n</blockquote>\n<p>[hole](\"Example of a plugin\")</p>\n<aside alt=\"Here's the gist\">\n<p><strong>Entities</strong> are things in your UI world—a text input, a button, a data table. Not React components, but conceptual objects that can have multiple aspects.</p>\n<p><strong>Components</strong> are pure data attached to entities. A Position component stores coordinates. A Label component holds display text. A Selection component tracks what's highlighted. Crucially, components contain no behavior—they're just structured data.</p>\n<p><strong>Plugins</strong> provide behavior that reacts to component combinations. A ValidationPlugin watches entities with Value and ValidationRules components. A CursorPlugin manages entities that have both Position and Presence components. A FocusPlugin handles entities with Selection and Keyboard components.</p>\n</aside>\n<p>The key insight: add features by adding plugins and components, not by editing old code. Your existing validation logic never changes when you add collaborative cursors. Your undo system remains untouched when you introduce AI suggestions.</p>\n<p><code>WorldState</code> isn't a game engine—it's an ECS-inspired approach for expressing UI concerns as composable data and behavior.</p>\n<h2 id=\"s3-interactive-proof-scrolly-feature-accumulation\">[S3] Interactive proof: Scrolly feature accumulation <div data-loc=\"content/der-draft.md:51\" class=\"heading-src\">⋅</div></h2>\n<p>[Interactive scrollytelling demo showing rectangle gaining features step by step]</p>\n<p>Watch a simple rectangle evolve: Base → validation → undo → diffs → presence → focus → commands → AI → inline visualization.</p>\n<p>Each step demonstrates additive architecture. The validation plugin doesn't know about undo. The undo plugin doesn't know about presence. The presence plugin doesn't know about AI suggestions. Yet they all compose cleanly because they operate on shared entity state through well-defined component interfaces.</p>\n<p>Toggle the layer chips at the bottom to see how features compose. Turn validation off and on—notice how it doesn't break undo or cursors. Enable all layers simultaneously—no conflicts, no special coordination code needed.</p>\n<p>Reality check: if your app is a simple contact form, you don't need this. But if your rectangles feel like living systems with multiple interacting concerns, this architecture prevents the coupling nightmare.</p>\n<h2 id=\"s4-interactive-proof-keyboard-focus-composition\">[S4] Interactive proof: Keyboard/focus composition <div data-loc=\"content/der-draft.md:63\" class=\"heading-src\">⋅</div></h2>\n<p>[Working demo: grid navigation with context-sensitive actions palette]</p>\n<p>This demo shows three complex UI concerns working in harmony: selection, focus, and context-sensitive commands.</p>\n<p>Selection (what's highlighted) and focus (where keyboard input goes) are different concepts that can diverge then sync. In this grid, you can select multiple cells with Shift+click while focus remains on a single cell for typing. The selection and focus plugins coordinate through shared component state, not direct coupling.</p>\n<p>Navigate with arrow keys—the focus moves, and the actions palette updates to show contextually relevant commands. Hit Spacebar to select the focused cell. Use Shift+arrows to extend selection. Each behavior is handled by a separate plugin responding to component state changes.</p>\n<p>The power emerges from composition: KeyboardPlugin + SelectionPlugin + ActionsPlugin working together without knowing about each other's implementation details.</p>\n<h2 id=\"s5-when-to-use-vs-avoid\">[S5] When to use vs avoid <div data-loc=\"content/der-draft.md:75\" class=\"heading-src\">⋅</div></h2>\n<p>This architecture shines for certain types of applications and can be overkill for others.</p>\n<p><strong>Great fit if you're building:</strong></p>\n<ul>\n<li>Collaborative editors where multiple people edit the same document simultaneously</li>\n<li>Dashboards with cross-panel effects where selecting data in one chart filters others</li>\n<li>Complex modeling tools with multiple overlapping interaction modes</li>\n<li>Professional workflows requiring deep keyboard navigation and shortcuts</li>\n</ul>\n<p><strong>Probably overkill if:</strong></p>\n<ul>\n<li>You're building a basic CRUD application with standard forms</li>\n<li>Your UI is mostly static content with minimal interactivity</li>\n<li>You have a small team and simple requirements</li>\n</ul>\n<p><strong>Design engineers welcome</strong>: The more interaction layers your application has, the more this pattern will help. If you find yourself building \"features that affect other features,\" you're in the sweet spot.</p>\n<p>The architecture pays for its complexity by preventing coupling debt. Early investment in entity-component structure pays dividends when you need to add the fifth, sixth, and seventh layers of interaction.</p>\n<h2 id=\"s6-community-engagement-conclusion\">[S6] Community engagement conclusion <div data-loc=\"content/der-draft.md:96\" class=\"heading-src\">⋅</div></h2>\n<p>Your rectangle doesn't have to be a hydra.</p>\n<p>We're collecting UI engineering war stories and solutions like this one. What's your rectangle horror story? What tools have you discovered that tame coupling nightmares in complex interfaces?</p>\n<p>Share your dev tooling discoveries with us—we'll feature the best ones and build a knowledge base of battle-tested patterns for UI engineers.</p>\n<p>Send us a DM, or check out our careers page if you're the kind of engineer who sees these patterns and wants to build tools that make them easier to implement.</p>\n<p>The future of UI engineering is compositional. Let's build it together.</p>\n",
      "permalink": "/der-draft/",
      "slug": "der-draft",
      "ancestors": [
        "_index.md"
      ],
      "title": "Design Engineering Rectangles",
      "description": null,
      "updated": null,
      "date": "2025-10-15",
      "year": 2025,
      "month": 10,
      "day": 15,
      "taxonomies": {},
      "authors": [],
      "extra": {},
      "path": "/der-draft/",
      "components": [
        "der-draft"
      ],
      "summary": null,
      "toc": [
        {
          "level": 1,
          "id": "design-engineering-rectangles",
          "permalink": "/der-draft/#design-engineering-rectangles",
          "title": "Design Engineering Rectangles ⋅",
          "children": [
            {
              "level": 2,
              "id": "s1-hook-rectangle-hydra-problem",
              "permalink": "/der-draft/#s1-hook-rectangle-hydra-problem",
              "title": "[S1] Hook: Rectangle hydra problem ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "s2-ecs-solution-translated-to-ui",
              "permalink": "/der-draft/#s2-ecs-solution-translated-to-ui",
              "title": "[S2] ECS solution translated to UI ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "s3-interactive-proof-scrolly-feature-accumulation",
              "permalink": "/der-draft/#s3-interactive-proof-scrolly-feature-accumulation",
              "title": "[S3] Interactive proof: Scrolly feature accumulation ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "s4-interactive-proof-keyboard-focus-composition",
              "permalink": "/der-draft/#s4-interactive-proof-keyboard-focus-composition",
              "title": "[S4] Interactive proof: Keyboard/focus composition ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "s5-when-to-use-vs-avoid",
              "permalink": "/der-draft/#s5-when-to-use-vs-avoid",
              "title": "[S5] When to use vs avoid ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "s6-community-engagement-conclusion",
              "permalink": "/der-draft/#s6-community-engagement-conclusion",
              "title": "[S6] Community engagement conclusion ⋅",
              "children": []
            }
          ]
        }
      ],
      "word_count": 1975,
      "reading_time": 10,
      "assets": [],
      "draft": true,
      "lang": "en",
      "lower": null,
      "higher": null,
      "translations": [],
      "backlinks": []
    },
    "higher": {
      "relative_path": "der-demo.md",
      "colocated_path": null,
      "content": "<h1 id=\"design-engineering-rectangles\">design engineering rectangles <div data-loc=\"content/der-demo.md:12\" class=\"heading-src\">⋅</div></h1>\n<details class=\"article-spoiler\">\n<summary>\n<span class=\"article-spoiler-title\">About the author & background</span>\n</summary>\n<div data-loc=\"content/der-demo.md:18\" class=\"article-spoiler-content\">\n<p>Hi, I'm Cole. At Phosphor, we're tackling extremely dense UI problems—the kind where every rectangle becomes a hydra.</p>\n<p>I've gone from basic MVC to MVVM, then at my previous startup, we ventured into Rust. The borrow checker led us to entity-component-systems (ECS), a pattern from game development where entities are just IDs, components are pure data in sparse sets, and systems provide behavior.</p>\n<p>What this yielded: files that needed complex logic could import just the components they cared about. One system handled raw structure to parsed form. Another took parsed form to errors. A third mapped errors to UI notifications. Each system was extremely scoped, only caring about the components between them.</p>\n<p>I loved this pattern not just for Rust reasons, but for the properties it gave me for testing and reasoning. Early on at Phosphor, I suspected we could leverage this to improve our UI architecture.</p>\n</div>\n</details>\n<h2 id=\"the-rectangle-hydra-problem\">The Rectangle Hydra Problem <div data-loc=\"content/der-demo.md:31\" class=\"heading-src\">⋅</div></h2>\n<p>Let me paint the picture of what we're experiencing.</p>\n<p>Start with one input box: some hooks, state management, maybe persistence. Add validation that reports errors. Now you need system-wide undo/redo, which touches multiple files. Then collaborative editing where multiple people see each other's changes in real-time—this touches your sync layer, your frontend hooks, everything.</p>\n<p>And these are just <strong>table stakes</strong>. Basic features for any authoring environment.</p>\n<p>But things don't stop there. At Phosphor, we add comparisons (single value vs. version-controlled state), origin tracking (where every value came from), diff views, key bindings, selection state, copy/paste... the list continues.</p>\n<p>Each feature touches the same rectangles. Without the right architecture, you're rewriting yesterday's code to add tomorrow's feature.</p>\n<p>Modern UI isn't a form with buttons. It's a world of interacting concerns—presence, diffs, commands, AI, validation, keyboard, and more.</p>\n<p>Here's the problem: how do you add these features <strong>without rewriting yesterday's code</strong>?</p>\n<h2 id=\"the-rectangle-that-grew-up\">The Rectangle That Grew Up <div data-loc=\"content/der-demo.md:47\" class=\"heading-src\">⋅</div></h2>\n<p>Watch a simple rectangle evolve. <strong>This is a live WorldState demo</strong>—each scroll step toggles feature flags that plugins react to.</p>\n<div data-loc=\"content/der-demo.md:51\" class=\"scrolly-container\" data-scrolly data-presence-anchor=\"__page__\">\n  <div data-loc=\"content/der-demo.md:52\" class=\"grid grid-cols-2 lg:grid-cols-3 gap-8\">\n    <div data-loc=\"content/der-demo.md:53\" class=\"scrolly-figure sticky top-4 flex flex-col h-fit col-span-2 lg:col-span-1\" data-scrolly-figure>\n      <!-- WorldState demo mounted here with CFeatureFlags component -->\n      <div data-loc=\"content/der-demo.md:55\" id=\"scrolly-world-demo\" data-world=\"rectangle-evolution\" data-presence-anchor=\"demo-canvas\"></div>\n    </div>\n    <div data-loc=\"content/der-demo.md:57\" class=\"scrolly-steps col-span-2 lg:col-span-2\" data-scrolly-steps>\n<div data-loc=\"content/der-demo.md:58\" class=\"scrolly-step\" data-step=\"base\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base\" data-presence-anchor=\"step-base\">\n<p><strong>Base</strong>: A rectangle with a value.</p>\n<p>Open devtools (Cmd/Ctrl + Click the rectangle) to see the entity with <code>CValue</code> component.</p>\n</div>\n<div data-loc=\"content/der-demo.md:66\" class=\"scrolly-step\" data-step=\"validation\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base,validation\" data-presence-anchor=\"step-validation\">\n<p><strong>+ Validation</strong>: Add inline rules and formatting.</p>\n<p>The <code>ValidationPlugin</code> activates, adding <code>CValidationRules</code> and <code>CValidationState</code> components.</p>\n</div>\n<div data-loc=\"content/der-demo.md:74\" class=\"scrolly-step\" data-step=\"undo\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base,validation,undo\" data-presence-anchor=\"step-undo\">\n<p><strong>+ Undo/Redo</strong>: Users expect time travel.</p>\n<p>The <code>UndoPlugin</code> wraps value changes, adding <code>CUndoStack</code> component.</p>\n</div>\n<div data-loc=\"content/der-demo.md:82\" class=\"scrolly-step\" data-step=\"diffs\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base,validation,undo,diffs\">\n<p><strong>+ Diffs</strong>: Show what changed and why. <em>\"Changed from 42 by Alice, 2m ago\"</em></p>\n<p>The <code>DiffPlugin</code> adds <code>CVersionComparison</code> and <code>CChangeOrigin</code> components.</p>\n</div>\n<div data-loc=\"content/der-demo.md:90\" class=\"scrolly-step\" data-step=\"presence\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base,validation,undo,diffs,presence\">\n<p><strong>+ Presence</strong>: Two more cursors just entered the room.</p>\n<p>The <code>PresencePlugin</code> adds <code>CCollaborators</code> component tracking cursor positions.</p>\n</div>\n<div data-loc=\"content/der-demo.md:98\" class=\"scrolly-step\" data-step=\"focus\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base,validation,undo,diffs,presence,focus\">\n<p><strong>+ Selection &amp; Focus</strong>: They're not the same thing.</p>\n<blockquote>\n<p><strong>Note</strong>: Selection (what's highlighted) ≠ Focus (where keyboard input goes). They can diverge, then sync.</p>\n</blockquote>\n<p>The <code>FocusPlugin</code> adds <code>CSelection</code> and <code>CFocus</code> as separate components.</p>\n</div>\n<div data-loc=\"content/der-demo.md:108\" class=\"scrolly-step\" data-step=\"shortcuts\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base,validation,undo,diffs,presence,focus,shortcuts\">\n<p><strong>+ Commands</strong>: Actions follow context, not hard-coded switches.</p>\n<p>The <code>CommandsPlugin</code> adds <code>CActions</code> component with context-aware command palette.</p>\n</div>\n<div data-loc=\"content/der-demo.md:116\" class=\"scrolly-step\" data-step=\"ai\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base,validation,undo,diffs,presence,focus,shortcuts,ai\">\n<p><strong>+ AI Suggestions</strong>: Assistive proposals without breaking your model.</p>\n<p>The <code>AIPlugin</code> adds <code>CSuggestions</code> component without touching validation or undo logic.</p>\n</div>\n<div data-loc=\"content/der-demo.md:124\" class=\"scrolly-step\" data-step=\"viz\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base,validation,undo,diffs,presence,focus,shortcuts,ai,viz\">\n<p><strong>+ Inline Viz</strong>: Tiny chart, big meaning.</p>\n<p>The <code>VisualizationPlugin</code> adds <code>CInlineChart</code> component reading from <code>CValue</code>.</p>\n</div>\n<div data-loc=\"content/der-demo.md:132\" class=\"scrolly-step\" data-step=\"reality\" data-target-uid=\"_|_|scrolly-rectangle\" data-set-flags=\"base,validation,undo,diffs,presence,focus,shortcuts,ai,viz\">\n<p><strong>Reality check</strong>: If your app is a simple form—great. You don't need this.</p>\n<p>But if your rectangles feel like <strong>living systems</strong>, keep reading.</p>\n<p><strong>Notice</strong>: All 9 plugins active. No coupling. Each feature is just a flag in <code>CFeatureFlags</code>.</p>\n</div>\n  </div>\n</div>\n<h2 id=\"borrowed-from-games\">Borrowed From Games <div data-loc=\"content/der-demo.md:145\" class=\"heading-src\">⋅</div></h2>\n<p>Game developers faced this exact problem a decade ago and found an elegant solution: entities, components, and systems. The pattern translates directly to UI engineering.</p>\n<p><strong>Entities</strong> are things in your UI world—a text input, a button, a data table. Not React components, but conceptual objects that can have multiple aspects.</p>\n<p><strong>Components</strong> are pure data attached to entities. A Position component stores coordinates. A Label component holds display text. A Selection component tracks what's highlighted. Crucially, components contain no behavior—they're just structured data.</p>\n<p><strong>Plugins</strong> provide behavior that reacts to component combinations. A ValidationPlugin watches entities with Value and ValidationRules components. A CursorPlugin manages entities that have both Position and Presence components. A FocusPlugin handles entities with Selection and Keyboard components.</p>\n<p><strong>The key insight</strong>: add features by adding plugins and components, not by editing old code. Your existing validation logic never changes when you add collaborative cursors. Your undo system remains untouched when you introduce AI suggestions.</p>\n<h3 id=\"translating-ecs-to-ui\">Translating ECS to UI <div data-loc=\"content/der-demo.md:157\" class=\"heading-src\">⋅</div></h3>\n<p>Unlike Rust, we have reactive containers—signals, atoms—and we don't have frame-by-frame computation like games. Instead, we have components that contain atoms. When we create a plugin, it operates on the fact that if there's an entity with certain components, it can provide additional components. There's never a need to add or remove components dynamically. You simply have this plugin architecture.</p>\n<p>At Phosphor, <strong>WorldState</strong> is backed by Jotai Atoms—reactive containers for values. We're considering adapting it to use Livestore as our reactivity graph next.</p>\n<blockquote>\n<p><strong>WorldState ≠ game engine</strong>. It's an ECS-inspired way to express UI concerns as composable data + behavior.</p>\n</blockquote>\n<h2 id=\"two-proof-paths\">Two Proof Paths <div data-loc=\"content/der-demo.md:165\" class=\"heading-src\">⋅</div></h2>\n<h3 id=\"1-interactive-worldstate-demo-see-the-devtools-in-action\">1. Interactive WorldState Demo <em>(see the devtools in action)</em> <div data-loc=\"content/der-demo.md:167\" class=\"heading-src\">⋅</div></h3>\n<div data-loc=\"content/der-demo.md:169\" id=\"scrolly-world-demo\" data-presence-anchor=\"standalone-demo\"></div>\n<script src=\"/js/world-state-demo.entrypoint.js\" type=\"module\"></script>\n<p>Try this:</p>\n<ul>\n<li><strong>Cmd/Ctrl + Click</strong> any entity card to inspect it in the devtools panel</li>\n<li><strong>Alt + Click</strong> for detailed component view</li>\n<li>Watch how components (Position, Label) are isolated but composable</li>\n<li><strong>Nothing coupled</strong>—each feature is a plugin layer</li>\n</ul>\n<blockquote>\n<p><strong>What it proves</strong>: Entities with components. Dev panel inspection. Additive architecture in practice.</p>\n</blockquote>\n<h3 id=\"2-keyboard-focus-production-pain-solved\">2. Keyboard/Focus <em>(production pain, solved)</em> <div data-loc=\"content/der-demo.md:181\" class=\"heading-src\">⋅</div></h3>\n<div data-loc=\"content/der-demo.md:183\" id=\"demo-keyboard-focus\" data-demo=\"keyboard-focus\"></div>\n<ul>\n<li>Context-sensitive shortcuts without coupling</li>\n<li>Focus, selection, actions—cleanly composed</li>\n<li>Grid navigation with live actions palette</li>\n</ul>\n<blockquote>\n<p><strong>What it proves</strong>: Selection ↔ Focus ↔ Actions interplay managed by components and parent-walking.</p>\n</blockquote>\n<h2 id=\"the-promise\">The Promise <div data-loc=\"content/der-demo.md:191\" class=\"heading-src\">⋅</div></h2>\n<p>You don't scale complex UI by burrowing deeper into components.</p>\n<p>You scale it by <strong>spreading features horizontally</strong>:</p>\n<ul>\n<li>Attach data</li>\n<li>Register behavior</li>\n<li>Let events find their handlers</li>\n</ul>\n<p>WorldState helps you ship the \"next layer\" without touching yesterday's files.</p>\n<h2 id=\"is-this-for-you\">Is This For You? <div data-loc=\"content/der-demo.md:203\" class=\"heading-src\">⋅</div></h2>\n<p><strong>Great fit if building</strong>:</p>\n<ul>\n<li>Collaborative editors</li>\n<li>Dashboards with cross-panel effects</li>\n<li>Complex modeling tools</li>\n<li>Pro workflows with deep keyboarding</li>\n</ul>\n<p><strong>Probably overkill if</strong>:</p>\n<ul>\n<li>A basic CRUD app meets your needs</li>\n</ul>\n<p><strong>Design engineers welcome</strong>: The more interaction layers, the more it shines.</p>\n<p>The architecture pays for its complexity by preventing coupling debt. Early investment in entity-component structure pays dividends when you need to add the fifth, sixth, and seventh layers of interaction.</p>\n<hr />\n<h2 id=\"your-rectangle-doesn-t-have-to-be-a-hydra\">Your Rectangle Doesn't Have to Be a Hydra <div data-loc=\"content/der-demo.md:222\" class=\"heading-src\">⋅</div></h2>\n<p>We're collecting UI engineering war stories and solutions like this one. What's your rectangle horror story? What tools have you discovered that tame coupling nightmares in complex interfaces?</p>\n<p>Share your dev tooling discoveries with us—we'll feature the best ones and build a knowledge base of battle-tested patterns for UI engineers.</p>\n<p>The future of UI engineering is compositional. Let's build it together.</p>\n<p>Check out <a href=\"https://phosphor.co/\">our job listings</a> if you're the kind of engineer who sees these patterns and wants to build tools that make them easier to implement.</p>\n<hr />\n<h2 id=\"implementation-notes\">Implementation Notes <div data-loc=\"content/der-demo.md:234\" class=\"heading-src\">⋅</div></h2>\n<details>\n<summary><strong>Scrollytelling pattern</strong></summary>\n<ul>\n<li>Sticky canvas on left; right-rail copy advances steps</li>\n<li>Each step toggles a single \"layer\" (avoid visual clutter)</li>\n<li>Layer chips bar at end: toggle all on/off for composition understanding</li>\n</ul>\n</details>\n<details>\n<summary><strong>Motion language</strong></summary>\n<ul>\n<li>Gentle, purposeful motion (250–400ms)</li>\n<li>Single pulse/ripple motif for \"cross-panel effects\"</li>\n</ul>\n</details>\n<details>\n<summary><strong>Accessibility</strong></summary>\n<ul>\n<li>Each step reachable with ←/→ keys</li>\n<li>ARIA live regions narrate visual changes</li>\n<li>Respect <code>prefers-reduced-motion</code></li>\n</ul>\n</details>\n<details>\n<summary><strong>Performance</strong></summary>\n<ul>\n<li>Lazy-load heavy assets</li>\n<li>Canvas demos render only when in view</li>\n<li>Avoid blurs/alpha stacking on large regions</li>\n</ul>\n</details>\n<hr />\n<h2 id=\"next-steps\">Next Steps <div data-loc=\"content/der-demo.md:273\" class=\"heading-src\">⋅</div></h2>\n<ul>\n<li><a href=\"/#the-problem\">See the Problem →</a></li>\n<li><a href=\"/#the-solution\">Learn WorldState →</a></li>\n<li><a href=\"/demos\">Play with Demos →</a></li>\n<li><a href=\"/reference\">Reference Implementation →</a></li>\n</ul>\n",
      "permalink": "/der-demo/",
      "slug": "der-demo",
      "ancestors": [
        "_index.md"
      ],
      "title": "Design Engineering Rectangles (scrolly demo)",
      "description": "From one input to a living system—adding features horizontally without rewriting yesterday's code",
      "updated": null,
      "date": "2025-10-02",
      "year": 2025,
      "month": 10,
      "day": 2,
      "taxonomies": {},
      "authors": [],
      "extra": {
        "category": "engineering",
        "nav_section": "Interactive Demos",
        "nav_order": 2
      },
      "path": "/der-demo/",
      "components": [
        "der-demo"
      ],
      "summary": null,
      "toc": [
        {
          "level": 1,
          "id": "design-engineering-rectangles",
          "permalink": "/der-demo/#design-engineering-rectangles",
          "title": "design engineering rectangles ⋅",
          "children": [
            {
              "level": 2,
              "id": "the-rectangle-hydra-problem",
              "permalink": "/der-demo/#the-rectangle-hydra-problem",
              "title": "The Rectangle Hydra Problem ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "the-rectangle-that-grew-up",
              "permalink": "/der-demo/#the-rectangle-that-grew-up",
              "title": "The Rectangle That Grew Up ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "borrowed-from-games",
              "permalink": "/der-demo/#borrowed-from-games",
              "title": "Borrowed From Games ⋅",
              "children": [
                {
                  "level": 3,
                  "id": "translating-ecs-to-ui",
                  "permalink": "/der-demo/#translating-ecs-to-ui",
                  "title": "Translating ECS to UI ⋅",
                  "children": []
                }
              ]
            },
            {
              "level": 2,
              "id": "two-proof-paths",
              "permalink": "/der-demo/#two-proof-paths",
              "title": "Two Proof Paths ⋅",
              "children": [
                {
                  "level": 3,
                  "id": "1-interactive-worldstate-demo-see-the-devtools-in-action",
                  "permalink": "/der-demo/#1-interactive-worldstate-demo-see-the-devtools-in-action",
                  "title": "1. Interactive WorldState Demo (see the devtools in action) ⋅",
                  "children": []
                },
                {
                  "level": 3,
                  "id": "2-keyboard-focus-production-pain-solved",
                  "permalink": "/der-demo/#2-keyboard-focus-production-pain-solved",
                  "title": "2. Keyboard/Focus (production pain, solved) ⋅",
                  "children": []
                }
              ]
            },
            {
              "level": 2,
              "id": "the-promise",
              "permalink": "/der-demo/#the-promise",
              "title": "The Promise ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "is-this-for-you",
              "permalink": "/der-demo/#is-this-for-you",
              "title": "Is This For You? ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "your-rectangle-doesn-t-have-to-be-a-hydra",
              "permalink": "/der-demo/#your-rectangle-doesn-t-have-to-be-a-hydra",
              "title": "Your Rectangle Doesn't Have to Be a Hydra ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "implementation-notes",
              "permalink": "/der-demo/#implementation-notes",
              "title": "Implementation Notes ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "next-steps",
              "permalink": "/der-demo/#next-steps",
              "title": "Next Steps ⋅",
              "children": []
            }
          ]
        }
      ],
      "word_count": 1861,
      "reading_time": 10,
      "assets": [],
      "draft": false,
      "lang": "en",
      "lower": null,
      "higher": null,
      "translations": [],
      "backlinks": []
    },
    "translations": [],
    "backlinks": []
  },
  "zola_version": "0.21.0"
}