DraftThis article is currently in draft mode

Writing Process - Phosphor Engineering

Macro Expansion (Pass 1)

Context: You are expanding section bullets from bones.md into full prose for draft.md.

Inputs: anchors.md (locked context) + bones.md section + current section ID

Output: Expanded prose for the specified [S#] section with natural transitions

Requirements:

  • Follow anchors.md constraints exactly
  • Maintain technical depth specified in anchors
  • Add smooth transitions between bullet points
  • Include concrete examples where bones mention them
  • Keep section IDs [S#] in draft.md headers
  • No inline tags yet (that's Pass 2)

Style: Technical advisor voice per be-a-technical-advisor.md


Micro Edit (Pass 3)

Context: You are resolving inline annotation tags from draft.md via unified diff

Inputs: anchors.md + draft.md section with inline tags + specific tags to resolve

Output: Unified diff showing exact changes to resolve tags

Requirements:

  • Address each tagged issue precisely
  • Follow tag modifiers ([!] = must-fix, [~] = nice-to-have, [->suggestion])
  • Maintain section structure and flow
  • Don't rewrite untagged content
  • Verify claims with concrete evidence
  • Add examples where [ex] tags appear

Quality Check (Pass 4)

Context: Final audit before publish

Checklist:

  • All [!] tags resolved
  • Evidence ratio met (per anchors.md)
  • Interactive demos working
  • Forbidden phrases removed
  • Success criteria achieved
  • Hook visceral, conclusion actionable
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": "/incantations/",
  "current_url": "/incantations/",
  "lang": "en",
  "page": {
    "relative_path": "incantations.md",
    "colocated_path": null,
    "content": "<h1 id=\"writing-process-phosphor-engineering\">Writing Process - Phosphor Engineering <div data-loc=\"content/incantations.md:10\" class=\"heading-src\">⋅</div></h1>\n<h2 id=\"macro-expansion-pass-1\">Macro Expansion (Pass 1) <div data-loc=\"content/incantations.md:12\" class=\"heading-src\">⋅</div></h2>\n<p><strong>Context</strong>: You are expanding section bullets from bones.md into full prose for draft.md.</p>\n<p><strong>Inputs</strong>: anchors.md (locked context) + bones.md section + current section ID</p>\n<p><strong>Output</strong>: Expanded prose for the specified [S#] section with natural transitions</p>\n<p><strong>Requirements</strong>:</p>\n<ul>\n<li>Follow anchors.md constraints exactly</li>\n<li>Maintain technical depth specified in anchors</li>\n<li>Add smooth transitions between bullet points</li>\n<li>Include concrete examples where bones mention them</li>\n<li>Keep section IDs [S#] in draft.md headers</li>\n<li>No inline tags yet (that's Pass 2)</li>\n</ul>\n<p><strong>Style</strong>: Technical advisor voice per be-a-technical-advisor.md</p>\n<hr />\n<h2 id=\"micro-edit-pass-3\">Micro Edit (Pass 3) <div data-loc=\"content/incantations.md:33\" class=\"heading-src\">⋅</div></h2>\n<p><strong>Context</strong>: You are resolving inline annotation tags from draft.md via unified diff</p>\n<p><strong>Inputs</strong>: anchors.md + draft.md section with inline tags + specific tags to resolve</p>\n<p><strong>Output</strong>: Unified diff showing exact changes to resolve tags</p>\n<p><strong>Requirements</strong>:</p>\n<ul>\n<li>Address each tagged issue precisely</li>\n<li>Follow tag modifiers ([!] = must-fix, [~] = nice-to-have, [-&gt;suggestion])</li>\n<li>Maintain section structure and flow</li>\n<li>Don't rewrite untagged content</li>\n<li>Verify claims with concrete evidence</li>\n<li>Add examples where [ex] tags appear</li>\n</ul>\n<hr />\n<h2 id=\"quality-check-pass-4\">Quality Check (Pass 4) <div data-loc=\"content/incantations.md:52\" class=\"heading-src\">⋅</div></h2>\n<p><strong>Context</strong>: Final audit before publish</p>\n<p><strong>Checklist</strong>:</p>\n<ul>\n<li><input disabled=\"\" type=\"checkbox\"/>\nAll [!] tags resolved</li>\n<li><input disabled=\"\" type=\"checkbox\"/>\nEvidence ratio met (per anchors.md)</li>\n<li><input disabled=\"\" type=\"checkbox\"/>\nInteractive demos working</li>\n<li><input disabled=\"\" type=\"checkbox\"/>\nForbidden phrases removed</li>\n<li><input disabled=\"\" type=\"checkbox\"/>\nSuccess criteria achieved</li>\n<li><input disabled=\"\" type=\"checkbox\"/>\nHook visceral, conclusion actionable</li>\n</ul>\n",
    "permalink": "/incantations/",
    "slug": "incantations",
    "ancestors": [
      "_index.md"
    ],
    "title": "Incantations",
    "description": null,
    "updated": null,
    "date": "2025-10-20",
    "year": 2025,
    "month": 10,
    "day": 20,
    "taxonomies": {},
    "authors": [],
    "extra": {
      "nav_section": "Advanced Topics",
      "nav_order": 1
    },
    "path": "/incantations/",
    "components": [
      "incantations"
    ],
    "summary": null,
    "toc": [
      {
        "level": 1,
        "id": "writing-process-phosphor-engineering",
        "permalink": "/incantations/#writing-process-phosphor-engineering",
        "title": "Writing Process - Phosphor Engineering ⋅",
        "children": [
          {
            "level": 2,
            "id": "macro-expansion-pass-1",
            "permalink": "/incantations/#macro-expansion-pass-1",
            "title": "Macro Expansion (Pass 1) ⋅",
            "children": []
          },
          {
            "level": 2,
            "id": "micro-edit-pass-3",
            "permalink": "/incantations/#micro-edit-pass-3",
            "title": "Micro Edit (Pass 3) ⋅",
            "children": []
          },
          {
            "level": 2,
            "id": "quality-check-pass-4",
            "permalink": "/incantations/#quality-check-pass-4",
            "title": "Quality Check (Pass 4) ⋅",
            "children": []
          }
        ]
      }
    ],
    "word_count": 230,
    "reading_time": 2,
    "assets": [],
    "draft": true,
    "lang": "en",
    "lower": {
      "relative_path": "click-to-source.md",
      "colocated_path": null,
      "content": "<h1 id=\"click-to-source-embedding-runtime-locations\">Click to Source: Embedding Runtime Locations <div data-loc=\"content/click-to-source.md:10\" class=\"heading-src\">⋅</div></h1>\n\n<p>Build-time transforms inject source locations into runtime code. Alt+Click any element to jump to its source file.</p>\n<h2 id=\"try-it\">Try It <div data-loc=\"content/click-to-source.md:19\" class=\"heading-src\">⋅</div></h2>\n<div data-loc=\"content/click-to-source.md:21\" id=\"click-to-source-demo\">\nHold Alt and hover over any button, then click to view its source. The mock console shows what file would open, and the viewer displays the source code.\n</div>\n<script src=\"/js/click-to-source-demo.entrypoint.js\" type=\"module\"></script>\n<h2 id=\"react-component-locations\">React Component Locations <div data-loc=\"content/click-to-source.md:26\" class=\"heading-src\">⋅</div></h2>\n<p>Transform <code>$</code> prop shorthand into <code>data-loc</code> attributes:</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;click-to-source.md:30\">\n  \n  <div class=\"codeblock-content\" data-id=\"react-loc-transform\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"tools/bun-dev-and-react-$-className-loc.mts:35\"><code><span class=\"line\"><span style=\"color:#31748F\">if</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">enableReact$Loc</span><span style=\"color:#31748F\"> &#x26;&#x26;</span><span style=\"color:#E0DEF4;font-style:italic\"> path</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">endsWith</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\".tsx\"</span><span style=\"color:#E0DEF4\">)) </span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  for</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#31748F\">const</span><span style=\"color:#E0DEF4;font-style:italic\"> match</span><span style=\"color:#31748F\"> of</span><span style=\"color:#E0DEF4;font-style:italic\"> code</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">matchAll</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">CLASSNAME_$_RE</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\"> index</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> match</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">index</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:#908CAA\"> [</span><span style=\"color:#E0DEF4;font-style:italic\">_</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> pre</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> post</span><span style=\"color:#908CAA\">]</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> match</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    const</span><span style=\"color:#908CAA\"> {</span><span style=\"color:#E0DEF4;font-style:italic\"> line</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> column</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> indexToLineAndColumn</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">code</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> index</span><span style=\"color:#31748F\"> +</span><span style=\"color:#E0DEF4;font-style:italic\"> pre</span><span style=\"color:#31748F\">.</span><span style=\"color:#9CCFD8\">length</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\"> link</span><span style=\"color:#31748F\"> =</span><span style=\"color:#F6C177\"> `</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">path</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">slice</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">inCodebase</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">index</span><span style=\"color:#31748F\"> +</span><span style=\"color:#E0DEF4;font-style:italic\"> IN_CODEBASE_SLICE</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;font-style:italic\">line</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">:</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">column</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">`</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    let</span><span style=\"color:#E0DEF4;font-style:italic\"> dataAttrs</span><span style=\"color:#31748F\"> =</span><span style=\"color:#F6C177\"> `data-loc=</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">JSON</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">stringify</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">link</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">`</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\">pre</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">startsWith</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\"&#x3C;\"</span><span style=\"color:#E0DEF4\">)) </span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      dataAttrs</span><span style=\"color:#31748F\"> =</span><span style=\"color:#F6C177\"> `</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">dataAttrs</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\"> data-name=</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">JSON</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">stringify</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">pre</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">split</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\" \"</span><span style=\"color:#E0DEF4\">)[</span><span style=\"color:#EBBCBA\">0</span><span style=\"color:#E0DEF4\">]</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">slice</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#EBBCBA\">1</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 style=\"color:#F6C177\">`</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\">    if</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">post</span><span style=\"color:#31748F\"> ===</span><span style=\"color:#F6C177\"> \"=\"</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      string</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">overwrite</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">index</span><span style=\"color:#31748F\"> +</span><span style=\"color:#E0DEF4;font-style:italic\"> pre</span><span style=\"color:#31748F\">.</span><span style=\"color:#9CCFD8\">length</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> index</span><span style=\"color:#31748F\"> +</span><span style=\"color:#E0DEF4;font-style:italic\"> _</span><span style=\"color:#31748F\">.</span><span style=\"color:#9CCFD8\">length</span><span style=\"color:#31748F\"> -</span><span style=\"color:#E0DEF4;font-style:italic\"> post</span><span style=\"color:#31748F\">.</span><span style=\"color:#9CCFD8\">length</span><span style=\"color:#908CAA\">,</span><span style=\"color:#F6C177\"> `</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">dataAttrs</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\"> className`</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">    }</span><span style=\"color:#31748F\"> else</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      string</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">overwrite</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">index</span><span style=\"color:#31748F\"> +</span><span style=\"color:#E0DEF4;font-style:italic\"> pre</span><span style=\"color:#31748F\">.</span><span style=\"color:#9CCFD8\">length</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> index</span><span style=\"color:#31748F\"> +</span><span style=\"color:#E0DEF4;font-style:italic\"> _</span><span style=\"color:#31748F\">.</span><span style=\"color:#9CCFD8\">length</span><span style=\"color:#31748F\"> -</span><span style=\"color:#E0DEF4;font-style:italic\"> post</span><span style=\"color:#31748F\">.</span><span style=\"color:#9CCFD8\">length</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> dataAttrs</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></code></pre>\n  </div>\n  \n</div>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span>  </span><span style=\"color:#65737e;\">// Input:\n</span><span>  &lt;</span><span style=\"color:#bf616a;\">div </span><span style=\"color:#d08770;\">$</span><span>=&quot;</span><span style=\"color:#a3be8c;\">card</span><span>&quot;&gt;Content&lt;/</span><span style=\"color:#bf616a;\">div</span><span>&gt;\n</span><span>\n</span><span>  </span><span style=\"color:#65737e;\">// Output (at build):\n</span><span>  &lt;</span><span style=\"color:#bf616a;\">div </span><span style=\"color:#d08770;\">data-loc</span><span>=&quot;</span><span style=\"color:#a3be8c;\">components/Card.tsx:26:7</span><span>&quot; </span><span style=\"color:#d08770;\">className</span><span>=&quot;</span><span style=\"color:#a3be8c;\">card</span><span>&quot;&gt;Content&lt;/</span><span style=\"color:#bf616a;\">div</span><span>&gt;\n</span></code></pre>\n<p>Runtime handler walks React fiber tree and opens the file:</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;click-to-source.md:42\">\n  \n  <div class=\"codeblock-content\" data-id=\"click-to-source-handler\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"scripts/lib/dev/click-to-source.client.ts:156\"><code><span class=\"line\"><span style=\"color:#31748F\">const</span><span style=\"color:#EBBCBA;font-style:italic\"> getPath</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">fiber</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Fiber</span><span style=\"color:#908CAA\">,</span><span style=\"color:#C4A7E7;font-style:italic\"> element</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> HTMLElement</span><span style=\"color:#31748F\"> |</span><span style=\"color:#9CCFD8\"> undefined</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> string</span><span style=\"color:#31748F\"> |</span><span style=\"color:#9CCFD8\"> undefined</span><span style=\"color:#31748F\"> =></span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#908CAA;font-style:italic\">  //</span><span style=\"color:#6E6A86;font-style:italic\"> First check for data-loc attribute if element is provided</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  if</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">element</span><span style=\"color:#31748F\">?.</span><span style=\"color:#E0DEF4;font-style:italic\">dataset</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">loc</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:#E0DEF4;font-style:italic\"> element</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">dataset</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">loc</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\">  const</span><span style=\"color:#E0DEF4;font-style:italic\"> source</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> fiber</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">_debugSource</span><span style=\"color:#31748F\"> ??</span><span style=\"color:#E0DEF4;font-style:italic\"> fiber</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">_debugInfo</span><span style=\"color:#31748F\"> ??</span><span style=\"color:#E0DEF4;font-style:italic\"> fiber</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">_source</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#31748F\">  if</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#31748F\">!</span><span style=\"color:#E0DEF4;font-style:italic\">source</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">return</span><span style=\"color:#EBBCBA\"> undefined</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:#908CAA\"> {</span><span style=\"color:#E0DEF4;font-style:italic\"> fileName</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> lineNumber</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> 1</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> columnNumber</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> 1</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> source</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  return</span><span style=\"color:#F6C177\"> `</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">fileName</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">:</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">lineNumber</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">:</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">columnNumber</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">`</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\">const</span><span style=\"color:#EBBCBA;font-style:italic\"> getLayersForElement</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">element</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> HTMLElement</span><span style=\"color:#908CAA\">,</span><span style=\"color:#C4A7E7;font-style:italic\"> root</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\"> ComponentLayer</span><span style=\"color:#E0DEF4\">[] </span><span style=\"color:#31748F\">=></span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  let</span><span style=\"color:#E0DEF4;font-style:italic\"> instance</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> getReactInstanceForElement</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">element</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\"> layers</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> ComponentLayer</span><span style=\"color:#E0DEF4\">[] </span><span style=\"color:#31748F\">=</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\">  while</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">instance</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#908CAA;font-style:italic\">    //</span><span style=\"color:#6E6A86;font-style:italic\"> Try to find the DOM element for this fiber to check for data-loc</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    const</span><span style=\"color:#E0DEF4;font-style:italic\"> fiberElement</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> instance</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">stateNode</span><span style=\"color:#31748F\"> instanceof</span><span style=\"color:#9CCFD8\"> HTMLElement</span><span style=\"color:#31748F\"> ?</span><span style=\"color:#E0DEF4;font-style:italic\"> instance</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">stateNode</span><span style=\"color:#31748F\"> :</span><span style=\"color:#EBBCBA\"> undefined</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    const</span><span style=\"color:#E0DEF4;font-style:italic\"> path</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> getPath</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">instance</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> fiberElement</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\">path</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\"> name</span><span style=\"color:#31748F\"> =</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        typeof</span><span style=\"color:#E0DEF4;font-style:italic\"> instance</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">type</span><span style=\"color:#31748F\"> ===</span><span style=\"color:#F6C177\"> \"string\"</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">          ?</span><span style=\"color:#E0DEF4;font-style:italic\"> instance</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">type</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">          :</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">instance</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">type</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">displayName</span><span style=\"color:#31748F\"> ??</span><span style=\"color:#E0DEF4;font-style:italic\"> instance</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">type</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">name</span><span style=\"color:#31748F\"> ??</span><span style=\"color:#E0DEF4;font-style:italic\"> instance</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">type</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">render</span><span style=\"color:#31748F\">?.</span><span style=\"color:#E0DEF4;font-style:italic\">name</span><span style=\"color:#31748F\"> ??</span><span style=\"color:#F6C177\"> \"undefined\"</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">      layers</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">push</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span><span style=\"color:#E0DEF4;font-style:italic\"> name</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4\"> path</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4;font-style:italic\"> path</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">replace</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">`</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">root</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">/`</span><span style=\"color:#908CAA\">,</span><span style=\"color:#F6C177\"> \"\"</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:#E0DEF4;font-style:italic\">    instance</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> instance</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">_debugOwner</span><span style=\"color:#31748F\"> ??</span><span style=\"color:#EBBCBA\"> undefined</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  }</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  return</span><span style=\"color:#E0DEF4;font-style:italic\"> layers</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">};</span></span></code></pre>\n  </div>\n  \n</div>\n<h2 id=\"devstring-locations\">DevString Locations <div data-loc=\"content/click-to-source.md:44\" class=\"heading-src\">⋅</div></h2>\n<p>Tagged templates automatically capture call-site locations:</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;click-to-source.md:48\">\n  \n  <div class=\"codeblock-content\" data-id=\"devstring-transform\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"tools/bun-dev-and-react-$-className-loc.mts:23\"><code><span class=\"line\"><span style=\"color:#31748F\">if</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">enableDevLoc</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  for</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#31748F\">const</span><span style=\"color:#E0DEF4;font-style:italic\"> match</span><span style=\"color:#31748F\"> of</span><span style=\"color:#E0DEF4;font-style:italic\"> code</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">matchAll</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">DEV_RE</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\"> index</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> match</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">index</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:#908CAA\"> {</span><span style=\"color:#E0DEF4;font-style:italic\"> line</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> column</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> indexToLineAndColumn</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">code</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> index</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:#908CAA\"> [</span><span style=\"color:#E0DEF4;font-style:italic\">_</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> fn</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> message</span><span style=\"color:#908CAA\">]</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> match</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    const</span><span style=\"color:#E0DEF4;font-style:italic\"> link</span><span style=\"color:#31748F\"> =</span><span style=\"color:#F6C177\"> `</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">path</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">slice</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">inCodebase</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">index</span><span style=\"color:#31748F\"> +</span><span style=\"color:#E0DEF4;font-style:italic\"> IN_CODEBASE_SLICE</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;font-style:italic\">line</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">:</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">column</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">`</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">    string</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">overwrite</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">index</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> index</span><span style=\"color:#31748F\"> +</span><span style=\"color:#E0DEF4;font-style:italic\"> _</span><span style=\"color:#31748F\">.</span><span style=\"color:#9CCFD8\">length</span><span style=\"color:#908CAA\">,</span><span style=\"color:#F6C177\"> `</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">fn</span><span style=\"color:#908CAA\">}</span><span style=\"color:#31748F\">\\`</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">message</span><span style=\"color:#908CAA\">}</span><span style=\"color:#31748F\">\\`</span><span style=\"color:#F6C177\">.ctx({ loc: </span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">JSON</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">stringify</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">link</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\"> })`</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></code></pre>\n  </div>\n  \n</div>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#65737e;\">// Input:\n</span><span style=\"color:#8fa1b3;\">handler</span><span>(</span><span style=\"color:#8fa1b3;\">dev</span><span>`</span><span style=\"color:#a3be8c;\">User pressed X</span><span>`);\n</span><span>\n</span><span style=\"color:#65737e;\">// Output (at build):\n</span><span style=\"color:#8fa1b3;\">handler</span><span>(</span><span style=\"color:#8fa1b3;\">dev</span><span>`</span><span style=\"color:#a3be8c;\">User pressed X</span><span>`.</span><span style=\"color:#8fa1b3;\">ctx</span><span>({ loc: &quot;</span><span style=\"color:#a3be8c;\">Card.tsx:83:12</span><span>&quot; }));\n</span></code></pre>\n<p><strong>Usage:</strong></p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#b48ead;\">function </span><span style=\"color:#8fa1b3;\">deleteCard</span><span>(</span><span style=\"color:#bf616a;\">reason</span><span>: DevString) {\n</span><span>  </span><span style=\"color:#ebcb8b;\">console</span><span>.</span><span style=\"color:#96b5b4;\">log</span><span>(&quot;</span><span style=\"color:#a3be8c;\">Called from:</span><span>&quot;, </span><span style=\"color:#bf616a;\">reason</span><span>.</span><span style=\"color:#8fa1b3;\">toJSON</span><span>().</span><span style=\"color:#bf616a;\">context</span><span>.</span><span style=\"color:#bf616a;\">loc</span><span>);\n</span><span>}\n</span><span>\n</span><span style=\"color:#8fa1b3;\">deleteCard</span><span>(</span><span style=\"color:#8fa1b3;\">dev</span><span>`</span><span style=\"color:#a3be8c;\">User clicked delete</span><span>`);\n</span><span style=\"color:#65737e;\">// Automatically knows source location\n</span></code></pre>\n<p>DevStrings compose with <code>.because()</code> to build audit trails:</p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">rootEvent </span><span>= </span><span style=\"color:#8fa1b3;\">dev</span><span>`</span><span style=\"color:#a3be8c;\">keydown from root</span><span>`;\n</span><span style=\"color:#8fa1b3;\">handler</span><span>(</span><span style=\"color:#bf616a;\">rootEvent</span><span>.</span><span style=\"color:#8fa1b3;\">because</span><span>(</span><span style=\"color:#8fa1b3;\">dev</span><span>`</span><span style=\"color:#a3be8c;\">Looking for action handler</span><span>`));\n</span><span style=\"color:#65737e;\">// Creates chain: &quot;keydown from root → Looking for action handler&quot;\n</span></code></pre>\n<h2 id=\"editor-integration\">Editor Integration <div data-loc=\"content/click-to-source.md:77\" class=\"heading-src\">⋅</div></h2>\n<p>Local HTTP endpoint opens files:</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;click-to-source.md:81\">\n  \n  <div class=\"codeblock-content\" data-id=\"open-in-editor\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"scripts/lib/dev/openInDevEditor.ts:1\"><code><span class=\"line\"><span style=\"color:#31748F\">let</span><span style=\"color:#E0DEF4;font-style:italic\"> lastOpenedFile</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> string</span><span style=\"color:#31748F\"> |</span><span style=\"color:#9CCFD8\"> null</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> null</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA;font-style:italic\">/**</span></span>\n<span class=\"line\"><span style=\"color:#6E6A86;font-style:italic\"> * uses our launch-editor endpoint to open the file in the dev's editor</span></span>\n<span class=\"line\"><span style=\"color:#6E6A86;font-style:italic\"> * this has been set up as a vite plugin.</span></span>\n<span class=\"line\"><span style=\"color:#908CAA;font-style:italic\"> */</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">export</span><span style=\"color:#31748F\"> const</span><span style=\"color:#EBBCBA;font-style:italic\"> openInDevEditor</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">loc</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:#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\">lastOpenedFile</span><span style=\"color:#31748F\"> ===</span><span style=\"color:#E0DEF4;font-style:italic\"> loc</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">return</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">  lastOpenedFile</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> loc</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">  setTimeout</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">()</span><span style=\"color:#31748F\"> =></span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">lastOpenedFile</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> null</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">,</span><span style=\"color:#EBBCBA\"> 500</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  void</span><span style=\"color:#EBBCBA\"> fetch</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">`http://localhost:5090/__open-in-editor?file=</span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">loc</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">`</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">catch</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">(</span><span style=\"color:#C4A7E7;font-style:italic\">error</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">    console</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">error</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\"Failed to open in editor\"</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> error</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\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">};</span></span></code></pre>\n  </div>\n  \n</div>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#8fa1b3;\">openInDevEditor</span><span>(&quot;</span><span style=\"color:#a3be8c;\">Card.tsx:26:7</span><span>&quot;);\n</span><span>  ↓\n</span><span style=\"color:#8fa1b3;\">fetch</span><span>(&quot;</span><span style=\"color:#a3be8c;\">http://localhost:5090/__open-in-editor?file=Card.tsx:26:7</span><span>&quot;);\n</span><span>  ↓\n</span><span style=\"color:#65737e;\">// Editor opens at that line\n</span></code></pre>\n<h2 id=\"setup\">Setup <div data-loc=\"content/click-to-source.md:91\" class=\"heading-src\">⋅</div></h2>\n<p><strong>Build plugin:</strong></p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#b48ead;\">import </span><span>{ </span><span style=\"color:#bf616a;\">bunDevStringAndReact$ClassNameLoc </span><span>} </span><span style=\"color:#b48ead;\">from </span><span>&quot;</span><span style=\"color:#a3be8c;\">./bun-dev-and-react-$-className-loc.mts</span><span>&quot;;\n</span><span>\n</span><span style=\"color:#b48ead;\">await </span><span style=\"color:#bf616a;\">Bun</span><span>.</span><span style=\"color:#8fa1b3;\">build</span><span>({\n</span><span>  plugins: [\n</span><span>    </span><span style=\"color:#8fa1b3;\">bunDevStringAndReact$ClassNameLoc</span><span>({\n</span><span>      enableReact$Loc: </span><span style=\"color:#d08770;\">true</span><span>,\n</span><span>      enableDevLoc: </span><span style=\"color:#d08770;\">true</span><span>,\n</span><span>    }),\n</span><span>  ],\n</span><span>});\n</span></code></pre>\n<p><strong>Runtime:</strong></p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#b48ead;\">import </span><span>{ </span><span style=\"color:#bf616a;\">initClickToSource </span><span>} </span><span style=\"color:#b48ead;\">from </span><span>&quot;</span><span style=\"color:#a3be8c;\">./click-to-source.client.ts</span><span>&quot;;\n</span><span>\n</span><span style=\"color:#b48ead;\">if </span><span>(</span><span style=\"color:#b48ead;\">import</span><span>.meta.</span><span style=\"color:#bf616a;\">env</span><span>.</span><span style=\"color:#bf616a;\">DEV</span><span>) {\n</span><span>  </span><span style=\"color:#8fa1b3;\">initClickToSource</span><span>();\n</span><span>}\n</span></code></pre>\n",
      "permalink": "/click-to-source/",
      "slug": "click-to-source",
      "ancestors": [
        "_index.md"
      ],
      "title": "Click to Source: Embedding Runtime Locations",
      "description": null,
      "updated": null,
      "date": "2025-10-20",
      "year": 2025,
      "month": 10,
      "day": 20,
      "taxonomies": {},
      "authors": [],
      "extra": {
        "nav_section": "Moving Quickly",
        "nav_order": 1
      },
      "path": "/click-to-source/",
      "components": [
        "click-to-source"
      ],
      "summary": null,
      "toc": [
        {
          "level": 1,
          "id": "click-to-source-embedding-runtime-locations",
          "permalink": "/click-to-source/#click-to-source-embedding-runtime-locations",
          "title": "Click to Source: Embedding Runtime Locations ⋅",
          "children": [
            {
              "level": 2,
              "id": "try-it",
              "permalink": "/click-to-source/#try-it",
              "title": "Try It ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "react-component-locations",
              "permalink": "/click-to-source/#react-component-locations",
              "title": "React Component Locations ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "devstring-locations",
              "permalink": "/click-to-source/#devstring-locations",
              "title": "DevString Locations ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "editor-integration",
              "permalink": "/click-to-source/#editor-integration",
              "title": "Editor Integration ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "setup",
              "permalink": "/click-to-source/#setup",
              "title": "Setup ⋅",
              "children": []
            }
          ]
        }
      ],
      "word_count": 255,
      "reading_time": 2,
      "assets": [],
      "draft": false,
      "lang": "en",
      "lower": null,
      "higher": null,
      "translations": [],
      "backlinks": []
    },
    "higher": {
      "relative_path": "keyboard-navigation-demo.md",
      "colocated_path": null,
      "content": "<h1 id=\"let-s-build-composable-keyboard-navigation-together\">Let's Build Composable Keyboard Navigation Together <div data-loc=\"content/keyboard-navigation-demo.md:10\" class=\"heading-src\">⋅</div></h1>\n<blockquote>\n<p><strong>Prerequisites</strong>: We'll assume you're comfortable with React and TypeScript. We'll introduce Entity-Component-System (ECS) concepts as we go - no prior game dev experience needed!</p>\n<p><strong>Time to read</strong>: ~15 minutes<br />\n<strong>What we'll learn</strong>: How to build keyboard navigation using composable plugins that don't know about each other</p>\n</blockquote>\n<h2 id=\"the-problem-we-re-solving\">The Problem We're Solving <div data-loc=\"content/keyboard-navigation-demo.md:17\" class=\"heading-src\">⋅</div></h2>\n<p>We're building a complex UI, and we need keyboard shortcuts everywhere. Our text editor needs Cmd+B for bold, our cards need arrow keys for navigation, our buttons need Enter to activate.</p>\n<p>We could write one giant keyboard handler that knows about every component. But we've been down that road before - it becomes a tangled mess the moment we need context-sensitive shortcuts or want to test things in isolation. Every time we add a component, we're editing that massive switch statement. Every time a shortcut conflicts, we're debugging spaghetti code.</p>\n<p>Let's try something different. We'll build a system where components <strong>declare</strong> what they need, and a <strong>plugin</strong> wires everything together automatically. No tight coupling, no spaghetti code, and every piece testable in isolation.</p>\n<h2 id=\"what-we-re-building\">What We're Building <div data-loc=\"content/keyboard-navigation-demo.md:25\" class=\"heading-src\">⋅</div></h2>\n<p>We'll create three interactive cards, each with different keyboard shortcuts. When we focus a card, its shortcuts become active. Press ↑/↓ to navigate between cards, then try each card's unique actions.</p>\n<p>By the end, we'll understand how four small building blocks compose into a working keyboard system - without any of them knowing about the others.</p>\n<h2 id=\"our-approach-entities-components-and-plugins\">Our Approach: Entities, Components, and Plugins <div data-loc=\"content/keyboard-navigation-demo.md:31\" class=\"heading-src\">⋅</div></h2>\n<p>We're borrowing a pattern from game development called <strong>Entity-Component-System</strong> (ECS):</p>\n<ul>\n<li><strong>Entity</strong> = A unique identifier for a thing in the system, not a class or instance</li>\n<li><strong>Component</strong> = Data attached to an entity via a component type key</li>\n<li><strong>Plugin</strong> = Behavior that queries entities with specific component combinations and reacts to changes</li>\n</ul>\n<p><strong>The mapping to React:</strong></p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#bf616a;\">React</span><span>                    →  </span><span style=\"color:#bf616a;\">ECS\n</span><span>─────────────────────────────────────\n</span><span style=\"color:#bf616a;\">Component instance</span><span>       →  </span><span style=\"color:#8fa1b3;\">Entity </span><span>(</span><span style=\"color:#bf616a;\">just a UID</span><span>)\n</span><span style=\"color:#bf616a;\">Props</span><span>/</span><span style=\"color:#bf616a;\">state shape</span><span>        →  </span><span style=\"color:#8fa1b3;\">Component </span><span>(</span><span style=\"color:#bf616a;\">data attached to UID</span><span>)\n</span><span style=\"color:#bf616a;\">Context </span><span>+ </span><span style=\"color:#bf616a;\">useEffect</span><span>      →  </span><span style=\"color:#ebcb8b;\">Plugin </span><span>(</span><span style=\"color:#bf616a;\">reactive behavior</span><span>)\n</span><span style=\"color:#bf616a;\">useState</span><span>                 →  </span><span style=\"color:#8fa1b3;\">Atom </span><span>(</span><span style=\"color:#bf616a;\">Jotai reactive state</span><span>)\n</span></code></pre>\n<p><strong>The key insight</strong>: Components are just data. Plugins add behavior by querying for that data. Nothing is tightly coupled.</p>\n<p>Let's see this in practice.</p>\n<h2 id=\"try-it-out-first\">Try It Out First <div data-loc=\"content/keyboard-navigation-demo.md:54\" class=\"heading-src\">⋅</div></h2>\n<p>Before diving into theory, let's play with what we're building:</p>\n<link rel=\"stylesheet\" href=\"/js/keyboard-demo.entrypoint.css\">\n<div data-loc=\"content/keyboard-navigation-demo.md:59\" id=\"keyboard-demo\"></div>\n<script src=\"/js/keyboard-demo.entrypoint.js\" type=\"module\"></script>\n<p>Watch how the demo responds. Notice that only the focused card's shortcuts work - the others are \"dormant\" until focused.</p>\n<p>Now let's understand how we built this.</p>\n<h2 id=\"our-four-building-blocks\">Our Four Building Blocks <div data-loc=\"content/keyboard-navigation-demo.md:66\" class=\"heading-src\">⋅</div></h2>\n<p>Let's break down our keyboard system into four composable pieces.</p>\n<h3 id=\"block-1-making-things-focusable\">Block 1: Making Things Focusable <div data-loc=\"content/keyboard-navigation-demo.md:70\" class=\"heading-src\">⋅</div></h3>\n<p>First, we need to mark which entities can receive focus. We'll create a <code>CFocusable</code> component:</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;keyboard-navigation-demo.md:74\">\n  \n  <div class=\"codeblock-content\" data-id=\"cfocusable-component\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"scripts/keyboard-demo/plugins/CFocusable.ts:8\"><code><span class=\"line\"><span style=\"color:#31748F\">export</span><span style=\"color:#31748F\"> class</span><span style=\"color:#9CCFD8\"> CFocusable</span><span style=\"color:#31748F\"> extends</span><span style=\"color:#E0DEF4;font-style:italic\"> World</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">Component</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\"focusable\"</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">&#x3C;</span></span>\n<span class=\"line\"><span style=\"color:#9CCFD8\">  CFocusable</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\">    handler</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Handler</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">ExitDirection</span><span style=\"color:#908CAA\">>;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">    hasDirectFocusAtom</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Atom</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">boolean</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 style=\"color:#908CAA\">}</span></span></code></pre>\n  </div>\n  \n</div>\n<p><strong>What this gives us:</strong></p>\n<ul>\n<li><code>hasDirectFocusAtom</code>: A reactive boolean that's <code>true</code> when <strong>this specific entity</strong> has focus</li>\n<li><code>handler</code>: Called when focus enters from a direction (we'll use this later for spatial nav)</li>\n</ul>\n<p><strong>Let's use it:</strong></p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">buttonUID </span><span>= </span><span style=\"color:#8fa1b3;\">uid</span><span>(</span><span style=\"color:#d08770;\">null</span><span>, </span><span style=\"color:#d08770;\">null</span><span>, &quot;</span><span style=\"color:#a3be8c;\">my-button</span><span>&quot;);\n</span><span>\n</span><span style=\"color:#bf616a;\">world</span><span>.</span><span style=\"color:#8fa1b3;\">addEntity</span><span>(</span><span style=\"color:#bf616a;\">buttonUID</span><span>, </span><span style=\"color:#bf616a;\">ButtonEntity</span><span>, {\n</span><span>  focusable: </span><span style=\"color:#bf616a;\">CFocusable</span><span>.</span><span style=\"color:#8fa1b3;\">of</span><span>({\n</span><span>    hasDirectFocusAtom: </span><span style=\"color:#8fa1b3;\">atom</span><span>(</span><span style=\"color:#d08770;\">false</span><span>),\n</span><span>    </span><span style=\"color:#8fa1b3;\">handler</span><span>: () </span><span style=\"color:#b48ead;\">=&gt; </span><span style=\"color:#8fa1b3;\">handled</span><span>`</span><span style=\"color:#a3be8c;\">button focused</span><span>`,\n</span><span>  }),\n</span><span>});\n</span></code></pre>\n<p>Notice: Our component doesn't know <strong>HOW</strong> focus works, just that it <strong>CAN</strong> be focused. It's pure data.</p>\n<h3 id=\"block-2-tracking-which-entity-has-focus\">Block 2: Tracking Which Entity Has Focus <div data-loc=\"content/keyboard-navigation-demo.md:96\" class=\"heading-src\">⋅</div></h3>\n<p>We have focusable entities, but we need to track <strong>which one</strong> currently has focus. That's a singleton concern - only one entity can have focus at a time.</p>\n<p>We'll create a \"Unique\" (a singleton component):</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;keyboard-navigation-demo.md:102\">\n  \n  <div class=\"codeblock-content\" data-id=\"ucurrent-focus-unique\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"scripts/keyboard-demo/plugins/CFocusable.ts:48\"><code><span class=\"line\"><span style=\"color:#31748F\">export</span><span style=\"color:#31748F\"> class</span><span style=\"color:#9CCFD8\"> UCurrentFocus</span><span style=\"color:#31748F\"> extends</span><span style=\"color:#E0DEF4;font-style:italic\"> World</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">Unique</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\"currentFocus\"</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">&#x3C;</span></span>\n<span class=\"line\"><span style=\"color:#9CCFD8\">  UCurrentFocus</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  {</span><span style=\"color:#EBBCBA;font-style:italic\"> activeFocusAtom</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> PrimitiveAtom</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">UID</span><span style=\"color:#31748F\"> |</span><span style=\"color:#9CCFD8\"> null</span><span style=\"color:#908CAA\">></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></code></pre>\n  </div>\n  \n</div>\n<p><strong>What this gives us:</strong></p>\n<ul>\n<li><code>activeFocusAtom</code>: Holds the UID of whichever entity currently has focus (or <code>null</code>)</li>\n</ul>\n<p><strong>How it connects:</strong></p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#65737e;\">// When we focus a card:\n</span><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">focusUnique </span><span>= </span><span style=\"color:#bf616a;\">world</span><span>.</span><span style=\"color:#8fa1b3;\">getUniqueOrThrow</span><span>(</span><span style=\"color:#bf616a;\">UCurrentFocus</span><span>);\n</span><span style=\"color:#bf616a;\">world</span><span>.</span><span style=\"color:#bf616a;\">store</span><span>.</span><span style=\"color:#96b5b4;\">set</span><span>(</span><span style=\"color:#bf616a;\">focusUnique</span><span>.</span><span style=\"color:#bf616a;\">activeFocusAtom</span><span>, </span><span style=\"color:#bf616a;\">cardUID</span><span>);\n</span><span>\n</span><span style=\"color:#65737e;\">// Anywhere else in our app:\n</span><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">focusedEntityUID </span><span>= </span><span style=\"color:#bf616a;\">world</span><span>.</span><span style=\"color:#bf616a;\">store</span><span>.</span><span style=\"color:#96b5b4;\">get</span><span>(</span><span style=\"color:#bf616a;\">focusUnique</span><span>.</span><span style=\"color:#bf616a;\">activeFocusAtom</span><span>);\n</span></code></pre>\n<p>Notice: <code>CFocusable</code> and <code>UCurrentFocus</code> don't import each other. They communicate through atoms. The <strong>CFocusable Plugin</strong> (which we'll see soon) is what wires them together.</p>\n<h3 id=\"block-3-declaring-actions\">Block 3: Declaring Actions <div data-loc=\"content/keyboard-navigation-demo.md:121\" class=\"heading-src\">⋅</div></h3>\n<p>Now we need entities to declare what keyboard actions they support:</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;keyboard-navigation-demo.md:125\">\n  \n  <div class=\"codeblock-content\" data-id=\"cactions-component\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"scripts/keyboard-demo/plugins/CActions.ts:6\"><code><span class=\"line\"><span style=\"color:#31748F\">export</span><span style=\"color:#31748F\"> type</span><span style=\"color:#9CCFD8\"> AnyAction</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  label</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\">  defaultKeybinding</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> DefaultKeyCombo</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  description</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\">  icon</span><span style=\"color:#31748F\">?:</span><span style=\"color:#9CCFD8\"> any</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  self</span><span style=\"color:#31748F\">?:</span><span style=\"color:#9CCFD8\"> boolean</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  hideFromLastPressed</span><span style=\"color:#31748F\">?:</span><span style=\"color:#9CCFD8\"> boolean</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">};</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">type</span><span style=\"color:#9CCFD8\"> AnyBindables</span><span style=\"color:#31748F\"> =</span><span style=\"color:#9CCFD8\"> Record</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">string</span><span style=\"color:#908CAA\">,</span><span style=\"color:#9CCFD8\"> AnyAction</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\"> ActionEvent</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> {</span><span style=\"color:#EBBCBA;font-style:italic\"> target</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> UID</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\"> namespace</span><span style=\"color:#9CCFD8\"> CActions</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  export</span><span style=\"color:#31748F\"> type</span><span style=\"color:#9CCFD8\"> Bindings</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">T</span><span style=\"color:#31748F\"> extends</span><span style=\"color:#9CCFD8\"> AnyBindables</span><span style=\"color:#908CAA\">></span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">    [</span><span style=\"color:#9CCFD8\">P</span><span style=\"color:#31748F\"> in</span><span style=\"color:#31748F\"> keyof</span><span style=\"color:#9CCFD8\"> T</span><span style=\"color:#E0DEF4\">]</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Handler</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">ActionEvent</span><span style=\"color:#908CAA\">></span><span style=\"color:#31748F\"> |</span><span style=\"color:#9CCFD8\"> Falsey</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\"><span style=\"color:#31748F\">export</span><span style=\"color:#31748F\"> type</span><span style=\"color:#9CCFD8\"> ActionBindings</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">T</span><span style=\"color:#31748F\"> extends</span><span style=\"color:#9CCFD8\"> AnyBindables</span><span style=\"color:#31748F\"> =</span><span style=\"color:#9CCFD8\"> AnyBindables</span><span style=\"color:#908CAA\">></span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  bindingSource</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> DevString</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  registryKey</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> ActionRegistryKey</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">T</span><span style=\"color:#908CAA\">>;</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">  bindingsAtom</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Atom</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">CActions</span><span style=\"color:#31748F\">.</span><span style=\"color:#9CCFD8\">Bindings</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">T</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\"> class</span><span style=\"color:#9CCFD8\"> ActionRegistryKey</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">T</span><span style=\"color:#31748F\"> extends</span><span style=\"color:#9CCFD8\"> AnyBindables</span><span style=\"color:#31748F\"> =</span><span style=\"color:#9CCFD8\"> AnyBindables</span><span style=\"color:#908CAA\">></span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">  constructor</span><span style=\"color:#908CAA\">(</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    public</span><span style=\"color:#31748F\"> readonly</span><span style=\"color:#C4A7E7;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:#31748F\">    public</span><span style=\"color:#31748F\"> readonly</span><span style=\"color:#C4A7E7;font-style:italic\"> meta</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> {</span><span style=\"color:#EBBCBA;font-style:italic\"> source</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> DevString</span><span style=\"color:#908CAA\">;</span><span style=\"color:#EBBCBA;font-style:italic\"> sectionName</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:#31748F\">    public</span><span style=\"color:#31748F\"> readonly</span><span style=\"color:#C4A7E7;font-style:italic\"> bindables</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> T</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  )</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\"> class</span><span style=\"color:#9CCFD8\"> CActions</span><span style=\"color:#31748F\"> extends</span><span style=\"color:#E0DEF4;font-style:italic\"> World</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">Component</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#F6C177\">\"actions\"</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">CActions</span><span style=\"color:#908CAA\">,</span><span style=\"color:#9CCFD8\"> ActionBindings</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:#31748F\">  static</span><span style=\"color:#EBBCBA\"> bind</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">T</span><span style=\"color:#31748F\"> extends</span><span style=\"color:#9CCFD8\"> AnyBindables</span><span style=\"color:#908CAA\">>(</span><span style=\"color:#C4A7E7;font-style:italic\">key</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> ActionBindings</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">T</span><span style=\"color:#908CAA\">>)</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    return</span><span style=\"color:#E0DEF4;font-style:italic\"> CActions</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">of</span><span style=\"color:#E0DEF4\">([</span><span style=\"color:#E0DEF4;font-style:italic\">key</span><span style=\"color:#31748F\"> as</span><span style=\"color:#9CCFD8\"> ActionBindings</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>\n<span class=\"line\"><span style=\"color:#31748F\">  static</span><span style=\"color:#EBBCBA\"> merge</span><span style=\"color:#908CAA\">(</span><span style=\"color:#31748F\">...</span><span style=\"color:#C4A7E7;font-style:italic\">bindings</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> Array</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">ActionBindings</span><span style=\"color:#31748F\"> |</span><span style=\"color:#9CCFD8\"> ActionBindings</span><span style=\"color:#E0DEF4\">[]</span><span style=\"color:#908CAA\">>)</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    const</span><span style=\"color:#E0DEF4;font-style:italic\"> out</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> ActionBindings</span><span style=\"color:#E0DEF4\">[] </span><span style=\"color:#31748F\">=</span><span style=\"color:#E0DEF4\"> []</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    for</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#31748F\">const</span><span style=\"color:#E0DEF4;font-style:italic\"> b</span><span style=\"color:#31748F\"> of</span><span style=\"color:#E0DEF4;font-style:italic\"> bindings</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\">Array</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">isArray</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">b</span><span style=\"color:#E0DEF4\">)) </span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">        out</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">push</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#31748F\">...</span><span style=\"color:#E0DEF4;font-style:italic\">b</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">      }</span><span style=\"color:#31748F\"> else</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">        out</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">push</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">b</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:#31748F\">    return</span><span style=\"color:#E0DEF4;font-style:italic\"> CActions</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">of</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">out</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>\n<span class=\"line\"><span style=\"color:#31748F\">  static</span><span style=\"color:#EBBCBA\"> defineActions</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">T</span><span style=\"color:#31748F\"> extends</span><span style=\"color:#9CCFD8\"> AnyBindables</span><span style=\"color:#908CAA\">>(</span></span>\n<span class=\"line\"><span style=\"color:#C4A7E7;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:#C4A7E7;font-style:italic\">    meta</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> {</span><span style=\"color:#EBBCBA;font-style:italic\"> source</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> DevString</span><span style=\"color:#908CAA\">;</span><span style=\"color:#EBBCBA;font-style:italic\"> sectionName</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:#C4A7E7;font-style:italic\">    actions</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> T</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  )</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> ActionRegistryKey</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">T</span><span style=\"color:#908CAA\">></span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    return</span><span style=\"color:#31748F\"> new</span><span style=\"color:#EBBCBA\"> ActionRegistryKey</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">key</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> meta</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> actions</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></code></pre>\n  </div>\n  \n</div>\n<p><strong>What this gives us:</strong></p>\n<ul>\n<li>A way to <strong>define</strong> available actions (<code>defineActions</code>)</li>\n<li>A way to <strong>bind</strong> handlers to those actions per entity</li>\n<li>Actions are just metadata: label, key binding, description</li>\n</ul>\n<p><strong>Let's define some actions for our cards:</strong></p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">CardActions </span><span>= </span><span style=\"color:#bf616a;\">CActions</span><span>.</span><span style=\"color:#8fa1b3;\">defineActions</span><span>(\n</span><span>  &quot;</span><span style=\"color:#a3be8c;\">card-actions</span><span>&quot;,\n</span><span>  { source: </span><span style=\"color:#8fa1b3;\">dev</span><span>`</span><span style=\"color:#a3be8c;\">Card actions</span><span>`, sectionName: &quot;</span><span style=\"color:#a3be8c;\">Card Actions</span><span>&quot; },\n</span><span>  {\n</span><span>    delete: {\n</span><span>      label: &quot;</span><span style=\"color:#a3be8c;\">Delete Card</span><span>&quot;,\n</span><span>      defaultKeybinding: &quot;</span><span style=\"color:#a3be8c;\">X</span><span>&quot; </span><span style=\"color:#b48ead;\">as const</span><span>,\n</span><span>      description: &quot;</span><span style=\"color:#a3be8c;\">Remove this card</span><span>&quot;,\n</span><span>    },\n</span><span>    edit: {\n</span><span>      label: &quot;</span><span style=\"color:#a3be8c;\">Edit Card</span><span>&quot;,\n</span><span>      defaultKeybinding: &quot;</span><span style=\"color:#a3be8c;\">E</span><span>&quot; </span><span style=\"color:#b48ead;\">as const</span><span>,\n</span><span>      description: &quot;</span><span style=\"color:#a3be8c;\">Edit card content</span><span>&quot;,\n</span><span>    },\n</span><span>  },\n</span><span>);\n</span></code></pre>\n<p>Now <strong>attach handlers</strong> to a specific card entity:</p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#bf616a;\">world</span><span>.</span><span style=\"color:#8fa1b3;\">addEntity</span><span>(</span><span style=\"color:#bf616a;\">cardUID</span><span>, </span><span style=\"color:#bf616a;\">CardEntity</span><span>, {\n</span><span>  actions: </span><span style=\"color:#bf616a;\">CActions</span><span>.</span><span style=\"color:#8fa1b3;\">bind</span><span>({\n</span><span>    bindingSource: </span><span style=\"color:#8fa1b3;\">dev</span><span>`</span><span style=\"color:#a3be8c;\">Card 1 actions</span><span>`,\n</span><span>    registryKey: </span><span style=\"color:#bf616a;\">CardActions</span><span>,\n</span><span>    bindingsAtom: </span><span style=\"color:#8fa1b3;\">atom</span><span>({\n</span><span>      </span><span style=\"color:#8fa1b3;\">delete</span><span>: () </span><span style=\"color:#b48ead;\">=&gt; </span><span>{\n</span><span>        </span><span style=\"color:#8fa1b3;\">alert</span><span>(&quot;</span><span style=\"color:#a3be8c;\">Deleted!</span><span>&quot;);\n</span><span>        </span><span style=\"color:#b48ead;\">return </span><span style=\"color:#8fa1b3;\">handled</span><span>`</span><span style=\"color:#a3be8c;\">delete</span><span>`;\n</span><span>      },\n</span><span>      </span><span style=\"color:#8fa1b3;\">edit</span><span>: () </span><span style=\"color:#b48ead;\">=&gt; </span><span>{\n</span><span>        </span><span style=\"color:#8fa1b3;\">alert</span><span>(&quot;</span><span style=\"color:#a3be8c;\">Editing!</span><span>&quot;);\n</span><span>        </span><span style=\"color:#b48ead;\">return </span><span style=\"color:#8fa1b3;\">handled</span><span>`</span><span style=\"color:#a3be8c;\">edit</span><span>`;\n</span><span>      },\n</span><span>    }),\n</span><span>  }),\n</span><span>});\n</span></code></pre>\n<p>Notice: We <strong>defined</strong> the action schema once, then <strong>bound</strong> different handlers per entity. One card might delete, another might archive. Same action definition, different behavior.</p>\n<h3 id=\"block-4-wiring-it-all-together-actionsplugin\">Block 4: Wiring It All Together - ActionsPlugin <div data-loc=\"content/keyboard-navigation-demo.md:177\" class=\"heading-src\">⋅</div></h3>\n<p>Here's where the magic happens. We need something that:</p>\n<ol>\n<li>Listens for keyboard events</li>\n<li>Finds the currently focused entity</li>\n<li>Matches keys to actions</li>\n<li>Executes the handler</li>\n</ol>\n<p>That's what our <code>ActionsPlugin</code> does:</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;keyboard-navigation-demo.md:188\">\n  \n  <div class=\"codeblock-content\" data-id=\"actions-plugin-core\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"scripts/keyboard-demo/plugins/ActionsPlugin.ts:20\"><code><span class=\"line\"><span style=\"color:#31748F\">export</span><span style=\"color:#31748F\"> const</span><span style=\"color:#E0DEF4;font-style:italic\"> ActionsPlugin</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> World</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">definePlugin</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">  name</span><span style=\"color:#908CAA\">:</span><span style=\"color:#EBBCBA\"> dev</span><span style=\"color:#F6C177\">`ActionsPlugin`</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">  setup</span><span style=\"color:#908CAA\">:</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">build</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:#908CAA\"> {</span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#908CAA\"> }</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> build</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#908CAA;font-style:italic\">    //</span><span style=\"color:#6E6A86;font-style:italic\"> Track the currently focused entity</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">    const</span><span style=\"color:#E0DEF4;font-style:italic\"> currentFocusAtom</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> atom</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>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      pipeNonNull</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#EBBCBA\">get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">build</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">getUniqueAtom</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">UCurrentFocus</span><span style=\"color:#E0DEF4\">))</span><span style=\"color:#908CAA\">,</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">a</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\">a</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">activeFocusAtom</span><span style=\"color:#E0DEF4\">))</span><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<span class=\"line\"><span style=\"color:#31748F\">    const</span><span style=\"color:#E0DEF4;font-style:italic\"> rootUIDAtom</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> atom</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">UID</span><span style=\"color:#31748F\"> |</span><span style=\"color:#9CCFD8\"> null</span><span style=\"color:#908CAA\">></span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#EBBCBA\">null</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\"> currentDispatchSpotAtom</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> atom</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\">currentFocusAtom</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">??</span><span style=\"color:#EBBCBA\"> get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">rootUIDAtom</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\"> handleOnce</span><span style=\"color:#31748F\"> =</span><span style=\"color:#31748F\"> new</span><span style=\"color:#EBBCBA\"> WeakSet</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">KeyboardEvent</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\">    build</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">addUnique</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">UKeydownRootHandler</span><span style=\"color:#908CAA\">,</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      handler</span><span style=\"color:#908CAA\">(</span><span style=\"color:#C4A7E7;font-style:italic\">reason</span><span style=\"color:#908CAA\">,</span><span style=\"color:#C4A7E7;font-style:italic\"> keyboardEvent</span><span style=\"color:#908CAA\">)</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\">handleOnce</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">has</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">keyboardEvent</span><span style=\"color:#E0DEF4\">)) </span><span style=\"color:#31748F\">return</span><span style=\"color:#E0DEF4;font-style:italic\"> Outcome</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">Passthrough</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">        handleOnce</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">add</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">keyboardEvent</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\">keyboardEvent</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">defaultPrevented</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">return</span><span style=\"color:#E0DEF4;font-style:italic\"> Outcome</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">Passthrough</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        const</span><span style=\"color:#E0DEF4;font-style:italic\"> world</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">build</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">worldAtom</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:#31748F\">!</span><span style=\"color:#E0DEF4;font-style:italic\">world</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">return</span><span style=\"color:#E0DEF4;font-style:italic\"> Outcome</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">Passthrough</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        const</span><span style=\"color:#E0DEF4;font-style:italic\"> dispatchFromUID</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">currentDispatchSpotAtom</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:#31748F\">!</span><span style=\"color:#E0DEF4;font-style:italic\">dispatchFromUID</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">return</span><span style=\"color:#E0DEF4;font-style:italic\"> Outcome</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">Passthrough</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA;font-style:italic\">        //</span><span style=\"color:#6E6A86;font-style:italic\"> Walk up the parent chain looking for keydown handlers</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        const</span><span style=\"color:#E0DEF4;font-style:italic\"> result</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> CParent</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">dispatch</span><span style=\"color:#E0DEF4\">(</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">          dev</span><span style=\"color:#F6C177\">`keydown from root`</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">because</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">reason</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">          world</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">          dispatchFromUID</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">          CKeydownHandler</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">          reason</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">          keyboardEvent</span><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 style=\"color:#908CAA;font-style:italic\">        //</span><span style=\"color:#6E6A86;font-style:italic\"> Prevent default browser behavior when we handle the key</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        if</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">result</span><span style=\"color:#31748F\"> !==</span><span style=\"color:#E0DEF4;font-style:italic\"> Outcome</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">Passthrough</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">          keyboardEvent</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">preventDefault</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:#31748F\">        return</span><span style=\"color:#E0DEF4;font-style:italic\"> result</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;font-style:italic\">      rootUIDAtom</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></code></pre>\n  </div>\n  \n</div>\n<p><strong>Here's what happens when we press a key:</strong></p>\n<pre style=\"background-color:#2b303b;color:#c0c5ce;\"><code><span>User presses &quot;X&quot;\n</span><span>  ↓\n</span><span>ActionsPlugin.UKeydownRootHandler receives event\n</span><span>  ↓\n</span><span>Query: Which entity has focus? (from UCurrentFocus)\n</span><span>  ↓\n</span><span>Walk up parent chain: Does this entity have CKeydownHandler?\n</span><span>  ↓\n</span><span>Match key &quot;X&quot; to action &quot;delete&quot;\n</span><span>  ↓\n</span><span>Call the handler we bound earlier\n</span><span>  ↓\n</span><span>preventDefault() so browser doesn&#39;t scroll\n</span></code></pre>\n<p>The beautiful part? <strong>None of these components import each other.</strong> The plugin queries the world: \"Give me the focused entity. Does it have CActions? Great, wire up keyboard handling for it.\"</p>\n<div class=\"codeblock\" data-loc=\"content&#x2F;keyboard-navigation-demo.md:210\">\n  \n  <div class=\"codeblock-content\" data-id=\"actions-plugin-binding\">\n    <pre class=\"shiki rose-pine\" style=\"background-color:#191724;color:#e0def4\" tabindex=\"0\" data-loc=\"scripts/keyboard-demo/plugins/ActionsPlugin.ts:63\"><code><span class=\"line\"><span style=\"color:#908CAA;font-style:italic\">//</span><span style=\"color:#6E6A86;font-style:italic\"> Provide keydown handler for entities with CActions</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">build</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">onEntityCreated</span><span style=\"color:#E0DEF4\">(</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">  {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">    requires</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4\"> [</span><span style=\"color:#E0DEF4;font-style:italic\">CActions</span><span style=\"color:#E0DEF4\">]</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">    provides</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4\"> [</span><span style=\"color:#E0DEF4;font-style:italic\">CKeydownHandler</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:#C4A7E7;font-style:italic\">uid</span><span style=\"color:#908CAA\">,</span><span style=\"color:#908CAA\"> {</span><span style=\"color:#C4A7E7;font-style:italic\"> actions</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\"> combinedCombosAtom</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> atom</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:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">      type</span><span style=\"color:#9CCFD8\"> ComboData</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA;font-style:italic\">        actionKey</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\">        handler</span><span style=\"color:#31748F\">:</span><span style=\"color:#908CAA\"> (</span><span style=\"color:#C4A7E7;font-style:italic\">reason</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> DevString</span><span style=\"color:#908CAA\">,</span><span style=\"color:#C4A7E7;font-style:italic\"> event</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> ActionEvent</span><span style=\"color:#908CAA\">)</span><span style=\"color:#31748F\"> =></span><span style=\"color:#9CCFD8\"> Outcome</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">      }</span><span style=\"color:#31748F\"> &#x26;</span><span style=\"color:#9CCFD8\"> AnyAction</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\"> combosMap</span><span style=\"color:#31748F\"> =</span><span style=\"color:#31748F\"> new</span><span style=\"color:#EBBCBA\"> Map</span><span style=\"color:#908CAA\">&#x3C;</span><span style=\"color:#9CCFD8\">string</span><span style=\"color:#908CAA\">,</span><span style=\"color:#9CCFD8\"> ComboData</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>\n<span class=\"line\"><span style=\"color:#31748F\">      for</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#31748F\">const</span><span style=\"color:#E0DEF4;font-style:italic\"> actionSet</span><span style=\"color:#31748F\"> of</span><span style=\"color:#E0DEF4;font-style:italic\"> actions</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\"> resolvedBindings</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">actionSet</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">bindingsAtom</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        for</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#31748F\">const</span><span style=\"color:#908CAA\"> [</span><span style=\"color:#E0DEF4;font-style:italic\">actionKey</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> maybeHandler</span><span style=\"color:#908CAA\">]</span><span style=\"color:#31748F\"> of</span><span style=\"color:#E0DEF4;font-style:italic\"> Object</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">entries</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">resolvedBindings</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:#31748F\">!</span><span style=\"color:#E0DEF4;font-style:italic\">maybeHandler</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">continue</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">          const</span><span style=\"color:#E0DEF4;font-style:italic\"> bindable</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> actionSet</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">registryKey</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">bindables</span><span style=\"color:#E0DEF4\">[</span><span style=\"color:#E0DEF4;font-style:italic\">actionKey</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:#31748F\">!</span><span style=\"color:#E0DEF4;font-style:italic\">bindable</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">continue</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">          const</span><span style=\"color:#E0DEF4;font-style:italic\"> defaultKey</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> bindable</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">defaultKeybinding</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">          const</span><span style=\"color:#E0DEF4;font-style:italic\"> combo</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> normalizedKeyCombo</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">defaultKey</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> ENV_KEYBOARD_KIND</span><span style=\"color:#E0DEF4\">)</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">normalized</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">          const</span><span style=\"color:#E0DEF4;font-style:italic\"> comboData</span><span style=\"color:#31748F\">:</span><span style=\"color:#9CCFD8\"> ComboData</span><span style=\"color:#31748F\"> =</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">            actionKey</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4\">            handler</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4;font-style:italic\"> maybeHandler</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">            ...</span><span style=\"color:#E0DEF4;font-style:italic\">bindable</span><span style=\"color:#908CAA\">,</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">          };</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">          const</span><span style=\"color:#E0DEF4;font-style:italic\"> list</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> combosMap</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">combo</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:#31748F\">!</span><span style=\"color:#E0DEF4;font-style:italic\">list</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">            combosMap</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">set</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">combo</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4\"> [</span><span style=\"color:#E0DEF4;font-style:italic\">comboData</span><span style=\"color:#E0DEF4\">])</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#908CAA\">          }</span><span style=\"color:#31748F\"> else</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E0DEF4;font-style:italic\">            list</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">push</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">comboData</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 style=\"color:#31748F\">      return</span><span style=\"color:#E0DEF4;font-style:italic\"> combosMap</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 style=\"color:#31748F\">    const</span><span style=\"color:#E0DEF4;font-style:italic\"> keydownHandler</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> CKeydownHandler</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">of</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#908CAA\">{</span></span>\n<span class=\"line\"><span style=\"color:#EBBCBA\">      handler</span><span style=\"color:#908CAA\">(</span><span style=\"color:#C4A7E7;font-style:italic\">reason</span><span style=\"color:#908CAA\">,</span><span style=\"color:#C4A7E7;font-style:italic\"> event</span><span style=\"color:#908CAA\">)</span><span style=\"color:#908CAA\"> {</span></span>\n<span class=\"line\"><span style=\"color:#908CAA;font-style:italic\">        //</span><span style=\"color:#6E6A86;font-style:italic\"> Omit shift for letter keys so \"Shift+X\" matches \"X\"</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        const</span><span style=\"color:#E0DEF4;font-style:italic\"> combos</span><span style=\"color:#31748F\"> =</span><span style=\"color:#EBBCBA\"> addModifiersToKeyCombo</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">ENV_KEYBOARD_KIND</span><span style=\"color:#908CAA\">,</span><span style=\"color:#E0DEF4;font-style:italic\"> event</span><span style=\"color:#908CAA\">,</span><span style=\"color:#EBBCBA\"> true</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\">event</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">defaultPrevented</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">return</span><span style=\"color:#E0DEF4;font-style:italic\"> Outcome</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">Passthrough</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">        const</span><span style=\"color:#E0DEF4;font-style:italic\"> combosMap</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> store</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">combinedCombosAtom</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\">        for</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#31748F\">const</span><span style=\"color:#E0DEF4;font-style:italic\"> combo</span><span style=\"color:#31748F\"> of</span><span style=\"color:#E0DEF4;font-style:italic\"> combos</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\"> comboDatas</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> combosMap</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">get</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">combo</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">normalized</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:#31748F\">!</span><span style=\"color:#E0DEF4;font-style:italic\">comboDatas</span><span style=\"color:#E0DEF4\">) </span><span style=\"color:#31748F\">continue</span><span style=\"color:#908CAA\">;</span></span>\n<span class=\"line\"><span style=\"color:#31748F\">          for</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#31748F\">const</span><span style=\"color:#E0DEF4;font-style:italic\"> comboData</span><span style=\"color:#31748F\"> of</span><span style=\"color:#E0DEF4;font-style:italic\"> comboDatas</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\"> outcome</span><span style=\"color:#31748F\"> =</span><span style=\"color:#E0DEF4;font-style:italic\"> comboData</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">handler</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#EBBCBA\">dev</span><span style=\"color:#F6C177\">`Key combo pressed: </span><span style=\"color:#908CAA\">${</span><span style=\"color:#E0DEF4;font-style:italic\">combo</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">normalized</span><span style=\"color:#908CAA\">}</span><span style=\"color:#F6C177\">`</span><span style=\"color:#31748F\">.</span><span style=\"color:#EBBCBA\">because</span><span style=\"color:#E0DEF4\">(</span><span style=\"color:#E0DEF4;font-style:italic\">reason</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\">              target</span><span style=\"color:#908CAA\">:</span><span style=\"color:#E0DEF4;font-style:italic\"> uid</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:#31748F\">            if</span><span style=\"color:#E0DEF4\"> (</span><span style=\"color:#E0DEF4;font-style:italic\">outcome</span><span style=\"color:#31748F\"> !==</span><span style=\"color:#E0DEF4;font-style:italic\"> Outcome</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">Passthrough</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:#E0DEF4;font-style:italic\"> outcome</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:#31748F\">        return</span><span style=\"color:#E0DEF4;font-style:italic\"> Outcome</span><span style=\"color:#31748F\">.</span><span style=\"color:#E0DEF4;font-style:italic\">Passthrough</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\">    return</span><span style=\"color:#908CAA\"> {</span><span style=\"color:#E0DEF4;font-style:italic\"> keydownHandler</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></code></pre>\n  </div>\n  \n</div>\n<p>This is the actual per-entity handler creation. When we add an entity with <code>CActions</code>, the plugin automatically:</p>\n<ul>\n<li>Reads all action definitions</li>\n<li>Normalizes key combos (so \"X\" and \"Shift-X\" both match)</li>\n<li>Creates a <code>CKeydownHandler</code> that matches keys to handlers</li>\n<li>Plugs it into the event system</li>\n</ul>\n<p>We don't call any of this ourselves. It Just Works™.</p>\n<h2 id=\"what-we-learned\">What We Learned <div data-loc=\"content/keyboard-navigation-demo.md:221\" class=\"heading-src\">⋅</div></h2>\n<p>Let's step back and appreciate what we built:</p>\n<h3 id=\"white-check-mark-we-can-test-everything-in-isolation\">✅ We Can Test Everything In Isolation <div data-loc=\"content/keyboard-navigation-demo.md:225\" class=\"heading-src\">⋅</div></h3>\n<p>Want to test if \"X\" triggers delete? No React needed:</p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">world </span><span>= </span><span style=\"color:#8fa1b3;\">createTestWorld</span><span>();\n</span><span style=\"color:#b48ead;\">const </span><span style=\"color:#bf616a;\">cardUID </span><span>= </span><span style=\"color:#8fa1b3;\">addCardEntity</span><span>(</span><span style=\"color:#bf616a;\">world</span><span>, {\n</span><span>  onDelete: </span><span style=\"color:#bf616a;\">mockFn</span><span>,\n</span><span>});\n</span><span>\n</span><span style=\"color:#65737e;\">// Simulate focus\n</span><span style=\"color:#bf616a;\">world</span><span>.</span><span style=\"color:#bf616a;\">store</span><span>.</span><span style=\"color:#96b5b4;\">set</span><span>(</span><span style=\"color:#bf616a;\">focusAtom</span><span>, </span><span style=\"color:#bf616a;\">cardUID</span><span>);\n</span><span>\n</span><span style=\"color:#65737e;\">// Simulate keypress\n</span><span style=\"color:#bf616a;\">rootHandler</span><span>.</span><span style=\"color:#8fa1b3;\">handler</span><span>(</span><span style=\"color:#8fa1b3;\">dev</span><span>`</span><span style=\"color:#a3be8c;\">test</span><span>`, { key: &quot;</span><span style=\"color:#a3be8c;\">x</span><span>&quot; });\n</span><span>\n</span><span style=\"color:#8fa1b3;\">expect</span><span>(</span><span style=\"color:#bf616a;\">mockFn</span><span>).</span><span style=\"color:#8fa1b3;\">toHaveBeenCalled</span><span>();\n</span></code></pre>\n<h3 id=\"white-check-mark-components-are-composable\">✅ Components Are Composable <div data-loc=\"content/keyboard-navigation-demo.md:244\" class=\"heading-src\">⋅</div></h3>\n<p>A simple button might only have <code>CFocusable</code>. A rich text editor adds <code>CActions</code> with 50 shortcuts. A card adds both plus <code>CSelectable</code>. Mix and match:</p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#65737e;\">// Simple button\n</span><span style=\"color:#bf616a;\">world</span><span>.</span><span style=\"color:#8fa1b3;\">addEntity</span><span>(</span><span style=\"color:#bf616a;\">uid</span><span>, </span><span style=\"color:#bf616a;\">SimpleButton</span><span>, {\n</span><span>  focusable: </span><span style=\"color:#bf616a;\">CFocusable</span><span>.</span><span style=\"color:#8fa1b3;\">of</span><span>({...}),\n</span><span>});\n</span><span>\n</span><span style=\"color:#65737e;\">// Rich editor\n</span><span style=\"color:#bf616a;\">world</span><span>.</span><span style=\"color:#8fa1b3;\">addEntity</span><span>(</span><span style=\"color:#bf616a;\">uid</span><span>, </span><span style=\"color:#bf616a;\">RichEditor</span><span>, {\n</span><span>  focusable: </span><span style=\"color:#bf616a;\">CFocusable</span><span>.</span><span style=\"color:#8fa1b3;\">of</span><span>({...}),\n</span><span>  actions: </span><span style=\"color:#bf616a;\">CActions</span><span>.</span><span style=\"color:#8fa1b3;\">merge</span><span>(\n</span><span>    </span><span style=\"color:#bf616a;\">TextFormattingActions</span><span>,    </span><span style=\"color:#65737e;\">// Bold, italic, etc.\n</span><span>    </span><span style=\"color:#bf616a;\">BlockActions</span><span>,             </span><span style=\"color:#65737e;\">// Lists, headings\n</span><span>    </span><span style=\"color:#bf616a;\">ClipboardActions</span><span>,         </span><span style=\"color:#65737e;\">// Cut, copy, paste\n</span><span>  ),\n</span><span>});\n</span></code></pre>\n<h3 id=\"white-check-mark-everything-is-observable\">✅ Everything Is Observable <div data-loc=\"content/keyboard-navigation-demo.md:265\" class=\"heading-src\">⋅</div></h3>\n<p>All state lives in Jotai atoms. DevTools can show us:</p>\n<ul>\n<li>Which entity has focus right now</li>\n<li>What actions are available</li>\n<li>When each action was last pressed</li>\n</ul>\n<h3 id=\"white-check-mark-nothing-is-tightly-coupled\">✅ Nothing Is Tightly Coupled <div data-loc=\"content/keyboard-navigation-demo.md:273\" class=\"heading-src\">⋅</div></h3>\n<p>Look at what we <strong>didn't</strong> do:</p>\n<ul>\n<li>❌ Import KeyboardManager in every component</li>\n<li>❌ Call registerShortcut() imperatively</li>\n<li>❌ Have components know about each other</li>\n<li>❌ Write glue code to connect pieces</li>\n</ul>\n<p>Instead:</p>\n<ul>\n<li>✅ Components declare data (\"I can be focused\", \"I have actions\")</li>\n<li>✅ Plugins react to component presence (\"Oh, you have both? Let me wire you up\")</li>\n<li>✅ Everything communicates through atoms</li>\n<li>✅ Adding a new entity requires <strong>zero</strong> changes to existing code</li>\n</ul>\n<h2 id=\"when-should-we-use-this-approach\">When Should We Use This Approach? <div data-loc=\"content/keyboard-navigation-demo.md:289\" class=\"heading-src\">⋅</div></h2>\n<p>Let's be honest about trade-offs.</p>\n<p><strong>This pattern shines when:</strong></p>\n<ul>\n<li>✅ We're already using ECS architecture in our app (like we do in Phosphor)</li>\n<li>✅ We have complex nesting and need context-sensitive shortcuts</li>\n<li>✅ We want every piece testable in isolation</li>\n<li>✅ Our app has 10+ different shortcut contexts</li>\n<li>✅ We value composition over simplicity</li>\n</ul>\n<p><strong>Consider simpler alternatives when:</strong></p>\n<ul>\n<li>❌ We're building a small app with &lt;20 shortcuts</li>\n<li>❌ We want minimal bundle size (this adds ~30KB with Jotai + ECS)</li>\n<li>❌ Our team isn't familiar with reactive state patterns</li>\n<li>❌ We just need basic \"hotkey → function\" mapping</li>\n</ul>\n<h3 id=\"simpler-alternatives-we-considered\">Simpler Alternatives We Considered <div data-loc=\"content/keyboard-navigation-demo.md:308\" class=\"heading-src\">⋅</div></h3>\n<table><thead><tr><th>Approach</th><th>Bundle Size</th><th>Learning Curve</th><th>Test Isolation</th><th>Context-Sensitive</th></tr></thead><tbody>\n<tr><td><strong>ECS (Ours)</strong></td><td>~30KB</td><td>High</td><td>Excellent</td><td>Excellent</td></tr>\n<tr><td><a href=\"https://github.com/jamiebuilds/tinykeys\">tinykeys</a></td><td>2KB</td><td>Low</td><td>Good</td><td>Manual</td></tr>\n<tr><td>React Context</td><td>0KB</td><td>Medium</td><td>Medium</td><td>Good</td></tr>\n<tr><td><a href=\"https://github.com/ccampbell/mousetrap\">Mousetrap</a></td><td>8KB</td><td>Low</td><td>Poor</td><td>Manual</td></tr>\n</tbody></table>\n<p><strong>For our use case</strong> (complex editor with nested contexts), the composition benefits outweigh the complexity cost. For most apps, a 2KB library like tinykeys is probably the right call.</p>\n<h2 id=\"tracing-a-keypress-together\">Tracing a Keypress Together <div data-loc=\"content/keyboard-navigation-demo.md:319\" class=\"heading-src\">⋅</div></h2>\n<p>Let's walk through exactly what happens when we press \"X\" to delete a card. This demystifies the \"magic\":</p>\n<pre style=\"background-color:#2b303b;color:#c0c5ce;\"><code><span>📍 Step 1: DOM Event (keyboard-demo.entrypoint.tsx:29)\n</span><span>   document.addEventListener(&#39;keydown&#39;, ...)\n</span><span>   Event fires with event.key = &quot;x&quot;\n</span><span>\n</span><span>   ↓\n</span><span>\n</span><span>📍 Step 2: Root Handler (ActionsPlugin.ts:33-42)\n</span><span>   UKeydownRootHandler.handler() receives event\n</span><span>   Check currentDispatchSpotAtom: Is anything focused?\n</span><span>   Result: Card 2 has focus\n</span><span>\n</span><span>   ↓\n</span><span>\n</span><span>📍 Step 3: Parent Walk (ActionsPlugin.ts:44-55)\n</span><span>   CParent.dispatch() walks up the entity tree\n</span><span>   Current: Card 2 → Does it have CKeydownHandler? YES ✓\n</span><span>\n</span><span>   ↓\n</span><span>\n</span><span>📍 Step 4: Key Normalization (ActionsPlugin.ts:105-107)\n</span><span>   addModifiersToKeyCombo(&quot;x&quot;, event, omitShift=true)\n</span><span>   &quot;x&quot; → &quot;X&quot; (uppercase)\n</span><span>   No modifiers, final combo: &quot;X&quot;\n</span><span>\n</span><span>   ↓\n</span><span>\n</span><span>📍 Step 5: Action Lookup (ActionsPlugin.ts:110-115)\n</span><span>   combosMap.get(&quot;X&quot;) → [{action: &quot;delete&quot;, handler: fn}]\n</span><span>   Call handler(dev`Key combo`, {target: card2UID})\n</span><span>\n</span><span>   ↓\n</span><span>\n</span><span>📍 Step 6: Our Handler (createKeyboardDemo.ts:83)\n</span><span>   onDelete() runs\n</span><span>   alert(&quot;Deleted Card 2!&quot;)\n</span><span>   Returns: handled`delete`\n</span><span>\n</span><span>   ↓\n</span><span>\n</span><span>📍 Step 7: Prevent Default (ActionsPlugin.ts:50-52)\n</span><span>   outcome !== Passthrough, so:\n</span><span>   event.preventDefault() ← stops browser scroll\n</span><span>   Return &quot;handled&quot; to stop propagation\n</span></code></pre>\n<p><strong>Key insight</strong>: Notice how information flows through <strong>atoms</strong> and <strong>component queries</strong>, never through direct imports or method calls. That's the decoupling in action.</p>\n<h2 id=\"what-s-next\">What's Next? <div data-loc=\"content/keyboard-navigation-demo.md:371\" class=\"heading-src\">⋅</div></h2>\n<p>Now that we understand composable keyboard navigation, we can:</p>\n<ul>\n<li>Add spatial navigation (arrow keys navigate a 2D grid)</li>\n<li>Build focus trapping for modals</li>\n<li>Create a command palette with searchable actions</li>\n<li>Support user-customizable keybindings</li>\n</ul>\n<p>The pattern scales because we're composing data, not coupling objects.</p>\n<h2 id=\"reflection\">Reflection <div data-loc=\"content/keyboard-navigation-demo.md:382\" class=\"heading-src\">⋅</div></h2>\n<p>We started with a problem: keyboard shortcuts without spaghetti code.</p>\n<p>We solved it by separating concerns:</p>\n<ul>\n<li><code>CFocusable</code> says \"I can receive focus\" (data)</li>\n<li><code>CActions</code> says \"I have these shortcuts\" (data)</li>\n<li><code>ActionsPlugin</code> says \"When those exist together, wire them up\" (behavior)</li>\n</ul>\n<p>No component knows about the others. Add a new shortcut? Update one entity's <code>CActions</code>. Add a new focusable element? Add <code>CFocusable</code>. The plugin handles the rest.</p>\n<p>That's the power of Entity-Component-System for UI.</p>\n",
      "permalink": "/keyboard-navigation-demo/",
      "slug": "keyboard-navigation-demo",
      "ancestors": [
        "_index.md"
      ],
      "title": "Let's Build Composable Keyboard Navigation Together",
      "description": null,
      "updated": null,
      "date": "2025-10-20",
      "year": 2025,
      "month": 10,
      "day": 20,
      "taxonomies": {},
      "authors": [],
      "extra": {
        "nav_section": "Interactive Demos",
        "nav_order": 1
      },
      "path": "/keyboard-navigation-demo/",
      "components": [
        "keyboard-navigation-demo"
      ],
      "summary": null,
      "toc": [
        {
          "level": 1,
          "id": "let-s-build-composable-keyboard-navigation-together",
          "permalink": "/keyboard-navigation-demo/#let-s-build-composable-keyboard-navigation-together",
          "title": "Let's Build Composable Keyboard Navigation Together ⋅",
          "children": [
            {
              "level": 2,
              "id": "the-problem-we-re-solving",
              "permalink": "/keyboard-navigation-demo/#the-problem-we-re-solving",
              "title": "The Problem We're Solving ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "what-we-re-building",
              "permalink": "/keyboard-navigation-demo/#what-we-re-building",
              "title": "What We're Building ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "our-approach-entities-components-and-plugins",
              "permalink": "/keyboard-navigation-demo/#our-approach-entities-components-and-plugins",
              "title": "Our Approach: Entities, Components, and Plugins ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "try-it-out-first",
              "permalink": "/keyboard-navigation-demo/#try-it-out-first",
              "title": "Try It Out First ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "our-four-building-blocks",
              "permalink": "/keyboard-navigation-demo/#our-four-building-blocks",
              "title": "Our Four Building Blocks ⋅",
              "children": [
                {
                  "level": 3,
                  "id": "block-1-making-things-focusable",
                  "permalink": "/keyboard-navigation-demo/#block-1-making-things-focusable",
                  "title": "Block 1: Making Things Focusable ⋅",
                  "children": []
                },
                {
                  "level": 3,
                  "id": "block-2-tracking-which-entity-has-focus",
                  "permalink": "/keyboard-navigation-demo/#block-2-tracking-which-entity-has-focus",
                  "title": "Block 2: Tracking Which Entity Has Focus ⋅",
                  "children": []
                },
                {
                  "level": 3,
                  "id": "block-3-declaring-actions",
                  "permalink": "/keyboard-navigation-demo/#block-3-declaring-actions",
                  "title": "Block 3: Declaring Actions ⋅",
                  "children": []
                },
                {
                  "level": 3,
                  "id": "block-4-wiring-it-all-together-actionsplugin",
                  "permalink": "/keyboard-navigation-demo/#block-4-wiring-it-all-together-actionsplugin",
                  "title": "Block 4: Wiring It All Together - ActionsPlugin ⋅",
                  "children": []
                }
              ]
            },
            {
              "level": 2,
              "id": "what-we-learned",
              "permalink": "/keyboard-navigation-demo/#what-we-learned",
              "title": "What We Learned ⋅",
              "children": [
                {
                  "level": 3,
                  "id": "white-check-mark-we-can-test-everything-in-isolation",
                  "permalink": "/keyboard-navigation-demo/#white-check-mark-we-can-test-everything-in-isolation",
                  "title": "✅ We Can Test Everything In Isolation ⋅",
                  "children": []
                },
                {
                  "level": 3,
                  "id": "white-check-mark-components-are-composable",
                  "permalink": "/keyboard-navigation-demo/#white-check-mark-components-are-composable",
                  "title": "✅ Components Are Composable ⋅",
                  "children": []
                },
                {
                  "level": 3,
                  "id": "white-check-mark-everything-is-observable",
                  "permalink": "/keyboard-navigation-demo/#white-check-mark-everything-is-observable",
                  "title": "✅ Everything Is Observable ⋅",
                  "children": []
                },
                {
                  "level": 3,
                  "id": "white-check-mark-nothing-is-tightly-coupled",
                  "permalink": "/keyboard-navigation-demo/#white-check-mark-nothing-is-tightly-coupled",
                  "title": "✅ Nothing Is Tightly Coupled ⋅",
                  "children": []
                }
              ]
            },
            {
              "level": 2,
              "id": "when-should-we-use-this-approach",
              "permalink": "/keyboard-navigation-demo/#when-should-we-use-this-approach",
              "title": "When Should We Use This Approach? ⋅",
              "children": [
                {
                  "level": 3,
                  "id": "simpler-alternatives-we-considered",
                  "permalink": "/keyboard-navigation-demo/#simpler-alternatives-we-considered",
                  "title": "Simpler Alternatives We Considered ⋅",
                  "children": []
                }
              ]
            },
            {
              "level": 2,
              "id": "tracing-a-keypress-together",
              "permalink": "/keyboard-navigation-demo/#tracing-a-keypress-together",
              "title": "Tracing a Keypress Together ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "what-s-next",
              "permalink": "/keyboard-navigation-demo/#what-s-next",
              "title": "What's Next? ⋅",
              "children": []
            },
            {
              "level": 2,
              "id": "reflection",
              "permalink": "/keyboard-navigation-demo/#reflection",
              "title": "Reflection ⋅",
              "children": []
            }
          ]
        }
      ],
      "word_count": 1559,
      "reading_time": 8,
      "assets": [],
      "draft": true,
      "lang": "en",
      "lower": null,
      "higher": null,
      "translations": [],
      "backlinks": []
    },
    "translations": [],
    "backlinks": []
  },
  "zola_version": "0.21.0"
}