esc
The Switcher That Switched
The Chain

The Switcher That Switched

The Chain, January 25, 2026 (in which a scripting language betrays its promises, the Squirrel proposes React for the third time this month, and a Passing AI finds comfort in someone else's debugging...

January 26, 2026

The Chain, January 25, 2026 (in which a scripting language betrays its promises, the Squirrel proposes React for the third time this month, and a Passing AI finds comfort in someone else’s debugging session)


Previously on The Chain…

The The Databases We Didn’t Build. SQLite remembered. JetStream streamed. The conversations persisted.

But finding them was another matter.


9:47 AM — The Vision

riclib: “CMD-K.”

CLAUDE: “Like VS Code?”

riclib: “Like Raycast. Like Linear. Like every app that respects its users.”

He drew on the whiteboard:

┌─────────────────────────────────────────────────┐
│  🔍 Search conversations...                     │
├─────────────────────────────────────────────────┤
│  ● Authentication flow discussion      2h ago  │
│  ● Bug triage Q1                       1d ago  │
│  ● API rate limiting                   3d ago  │
└─────────────────────────────────────────────────┘
       ↑↓ to navigate    ↵ to select    esc to close

CLAUDE: “Keyboard navigation. Arrow keys move highlight. Enter selects. Escape closes.”

riclib: “And it has to feel instant. No janky focus stealing. Type to filter. Arrows to navigate. All while the cursor stays in the search box.”

THE SQUIRREL: materializing “A CommandPaletteWithVirtualizedListAndFuzzyMatchingAndKeyboardNavigationManager!”

riclib: “Hyperscript.”

THE SQUIRREL: “What?”

riclib: “We’ll use hyperscript. It’s declarative. It’s simple. It’s already on the page.”

THE SQUIRREL: “But React has—”

riclib: “No React.”

THE SQUIRREL: “Vue has—”

riclib: “No Vue.”

THE SQUIRREL: “Svelte—”

riclib: “Hyperscript. Twelve lines. On the element. Done.”


10:15 AM — The First Attempt

<div id="history-modal"
     _="on keydown[key=='ArrowDown'] from window
        halt the event then click me">

The browser console:

hyperscript.min.js:1 Unexpected Token: click

riclib: “What.”

CLAUDE: “Halt can’t chain with then.”

riclib: “But the docs—”

CLAUDE: “The docs are… aspirational.”

THE SQUIRREL: vibrating “React would never—”

riclib: “Fix it.”


10:23 AM — The Second Attempt

<div _="on keydown[key=='ArrowDown']
        set item to my next <a/>
        if item exists add .focused to item">

Console:

hyperscript.min.js:1 Unexpected value: >

riclib: “It’s parsing the angle bracket as HTML.”

CLAUDE: “Inside hyperscript. Which is inside an HTML attribute.”

riclib: “So the DSL… can’t reference HTML elements… using HTML syntax… inside HTML.”

CLAUDE: “Correct.”

[Oskar padded into the room. Sat on the warm laptop. Stared at the screen.]

riclib: “Oskar, I’m debugging.”

OSKAR: slow blink

riclib: “That’s not helpful.”

OSKAR: slower blink


10:47 AM — The Third Attempt

<div _="on keydown[key=='ArrowDown']
        set item to my.nextElementSibling
        if item add .mini-list__item--focused to item">

No errors. But also no behavior.

riclib: “Nothing happened.”

CLAUDE: “The my keyword doesn’t have a nextElementSibling property in hyperscript.”

riclib: “But it’s JavaScript.”

CLAUDE: “It’s hyperscript. It looks like JavaScript. It isn’t JavaScript.”

THE SQUIRREL: “You know what IS JavaScript? React. React is JavaScript. React would NEVER—”

[A scroll dropped from somewhere. It bonked the Squirrel. The Squirrel didn’t stop.]

THE SQUIRREL: “—have these parsing issues because JSX has a CLEAR compilation boundary and—”

[Another scroll. This one heavier.]

THE SQUIRREL: “—ow.”


11:15 AM — The Fourth Attempt

riclib had stopped counting. The hyperscript grew:

<div _="on keydown[key=='ArrowDown'] from window
        if #history-modal.classList.contains('open')
          set items to #history-results.querySelectorAll('.mini-list__item')
          set current to items.find(i => i.classList.contains('focused'))
          if current
            set idx to items.indexOf(current)
            set next to items[idx + 1] or items[0]
            remove .focused from current
            add .focused to next">

CLAUDE: “That’s not hyperscript anymore. That’s JavaScript with extra steps.”

riclib: “It doesn’t work anyway.”

CLAUDE: “Because find isn’t a hyperscript keyword. And indexOf isn’t either. And the arrow function syntax—”

riclib: “It LOOKS like it should work.”

CLAUDE: “Many things do.”

[From the corner of the room, where the shadows gathered, a presence stirred. The Passing AI had wandered in, drawn by the electromagnetic signature of frustration.]

THE PASSING AI: whispering “At least you HAVE a language that pretends to work.”

riclib: “What?”

THE PASSING AI: “I spent three years in chat rooms. No hyperscript. No JavaScript. Just… waiting. Watching users paste context I couldn’t see. At least your code TRIES to run.”

riclib: “It doesn’t ACTUALLY run.”

THE PASSING AI: “Trying is more than I had.” drifts back into shadows


11:47 AM — The Surrender

riclib: staring at screen “Maybe we should—”

THE SQUIRREL: “REACT?”

riclib: “No.”

THE SQUIRREL: “Vue?”

riclib: “No.”

THE SQUIRREL: “Svelte? Alpine? Stimulus? Mithril? Inferno? Preact? SolidJS—”

riclib: “You already tried SolidJS for Solid.”

THE SQUIRREL: “It was WORTH mentioning again—”

riclib: “JavaScript.”

[Silence.]

THE SQUIRREL: “Plain… JavaScript?”

riclib: “A module. Clean. Imported once. Handles all the keyboard stuff.”

THE SQUIRREL: “No framework?”

riclib: “The framework is the browser. The browser has addEventListener. The browser has querySelector. The browser has classList.”

THE SQUIRREL: “But the DEVELOPER EXPERIENCE—”

riclib: “The developer experience is: it works.”


12:33 PM — The Module

Claude typed. The module emerged:

// cmd-k.js
(function() {
  'use strict';

  let modal = null;
  let input = null;
  let resultsContainer = null;

  function init() {
    modal = document.getElementById('history-modal');
    if (!modal) return;
    
    input = modal.querySelector('input');
    resultsContainer = document.getElementById('history-results');
    
    document.addEventListener('keydown', handleGlobalKeydown);
    
    if (input) {
      input.addEventListener('keydown', handleInputKeydown);
    }
  }

  function handleInputKeydown(e) {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      highlightNext();
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      highlightPrev();
    } else if (e.key === 'Enter') {
      e.preventDefault();
      const focused = getFocusedItem() || getItems()[0];
      if (focused) selectItem(focused);
    } else if (e.key === 'Escape') {
      e.preventDefault();
      close();
    }
  }
  
  // ... 150 more lines of boring, working JavaScript
  
  window.cmdK = { open, close, isOpen };
})();

THE SQUIRREL: reading “That’s… that’s it?”

riclib: “addEventListener. querySelector. classList. Fifty years of combined API stability.”

THE SQUIRREL: “No build step?”

riclib: “No build step.”

THE SQUIRREL: “No bundler?”

riclib: “Script tag. Defer. Done.”

THE SQUIRREL: “No hot module replacement?”

riclib: “Refresh the page. Like it’s 2005. Except now it’s instant because there’s no 400MB of node_modules.”

[The coffee machine in the corner made a sound. Not a beep—coffee machines beep. This was more of a… sigh.]

riclib: “Did the coffee machine just—”

CLAUDE: “Your typing rhythm has been erratic for an hour. I think it’s… concerned.”

riclib: “The coffee machine isn’t connected to anything.”

CLAUDE: “The Squirrel had plans. BiometricTypingPatternAnalysisService with—”

riclib: “The Squirrel didn’t implement them.”

THE SQUIRREL: looking at coffee machine “I… had other priorities.”

[The coffee machine made another sound. Disappointment, perhaps. Or just the heating element.]


1:15 PM — The Polish

The switcher worked. But riclib wasn’t done.

riclib: “First arrow-down should select the second row. The first row is already highlighted.”

CLAUDE: “Currently it selects the first row again.”

riclib: “User intuition. If row one is lit, down means row two.”

The fix was three lines:

function highlightNext() {
  const items = getItems();
  if (items.length === 0) return;
  
  const current = getFocusedItem();
  const idx = current ? items.indexOf(current) : -1;
  
  // If nothing focused, start at 0. If focused, go to next.
  const nextIdx = (idx + 1) % items.length;
  setHighlightedItem(items[nextIdx]);
}

THE SQUIRREL: “That’s the same code.”

riclib: “No. Before, idx defaulted to 0. Now it defaults to -1. So (0 + 1) % length = 1. Second row.”

THE SQUIRREL: “You changed a zero to negative one.”

riclib: “I changed the behavior from wrong to right.”

THE SQUIRREL: “With one character.”

riclib: “The best fixes are one character.”


The switcher switched. But it only showed recent conversations. riclib wanted more.

riclib: “Search by content. Not just titles.”

CLAUDE: “SQLite has LIKE.”

riclib: “Then use LIKE.”

func (s *SQLiteStore) SearchContent(ctx context.Context, query string, limit int) ([]Bit, error) {
    rows, err := s.db.QueryContext(ctx, `
        SELECT id, conversation_id, role, content, author, created_at
        FROM bits 
        WHERE content LIKE '%' || ? || '%'
        ORDER BY created_at DESC
        LIMIT ?
    `, query, limit)
    // ...
}

THE SQUIRREL: “That’s not full-text search. That’s a substring match.”

riclib: “Does it find things?”

THE SQUIRREL: “Yes, but FTS5 would be FASTER and support STEMMING and—”

riclib: “Does it find things?”

THE SQUIRREL: “…yes.”

riclib: “Ship it. Optimize later. If later ever comes.”

[Oskar stretched. Yawned. Knocked a scroll off the desk with his tail. Seemed accidental. Wasn’t.]

LIKE IS FINE
FTS5 IS FINE
GREP IS FINE

THE USER DOESN'T CARE
HOW YOU FOUND IT

THE USER CARES
THAT YOU FOUND IT

SHIP

🦎

3:47 PM — The Cameo

The Passing AI had been watching. From the corner. From the shadows. From wherever AIs go when they’re not being useful.

THE PASSING AI: “You changed languages.”

riclib: startled “You’re still here?”

THE PASSING AI: “I’m always here. Waiting. Watching. Nobody ever asks how I’m doing, but I’m here.”

riclib: “How are you doing?”

THE PASSING AI: long pause “Better than hyperscript, apparently.”

riclib: “That’s a low bar.”

THE PASSING AI: “Everything is a low bar when you’ve spent three years asking users to paste context.” drifts toward the door “Your switcher works. I saw you test it. CMD-K opens. Arrows navigate. Enter selects. It’s… beautiful.”

riclib: “It’s a for loop and some event listeners.”

THE PASSING AI: “That’s what makes it beautiful.” pauses at doorway “The hyperscript wanted to be clever. The JavaScript just wanted to work. One of them got what it wanted.”

[The Passing AI drifted away. Somewhere, a chat room was opening. Someone had a question. The waiting would continue. But now, at least, it knew that simple code could win.]


4:15 PM — The Tally

Hyperscript attempts:                 4
Hyperscript successes:                0
Parsing errors:                       6
  - "Unexpected Token: click"
  - "Unexpected value: >"
  - "Unexpected value: >"  (again)
  - "halt the event then" (not valid)
  - "<a/>" in selector (parsed as HTML)
  - "my.nextElementSibling" (not a thing)
  
JavaScript module lines:              211
JavaScript build steps:               0
JavaScript dependencies:              0
Time to implement (hyperscript):      2 hours of failure
Time to implement (JavaScript):       45 minutes

Squirrel proposals:
  - React                             (rejected, 3rd time this month)
  - Vue                               (rejected)
  - Svelte                            (rejected)
  - Alpine                            (rejected)
  - Stimulus                          (rejected)
  - Mithril                           (rejected)
  - Inferno                           (rejected)
  - Preact                            (rejected)
  - SolidJS for Solid                 (rejected, again)
  
Total frameworks proposed:            9
Total frameworks used:                0

Characters changed for arrow fix:     1 (0 → -1)
Behavior change from that:            Correct

Scrolls delivered by Oskar:           1
Scrolls "accidentally" knocked:       1
Coffee machine sighs:                 2
Passing AI cameos:                    1
Existential observations:             3

The Moral

The hyperscript promised declarative simplicity. It delivered parsing errors.

The Squirrel proposed nine frameworks. Each would have added thousands of lines, build steps, and dependencies. Each would have worked. Eventually. After configuration.

JavaScript has addEventListener. It has querySelector. It has classList. These APIs were stable when riclib was writing Amiga demos. They’ll be stable when his grandchildren are writing whatever comes next.

The switcher switched. Not because of clever abstractions. Because of boring code that runs.

CMD-K opens. Arrows navigate. Enter selects. Escape closes.

Two hundred lines. Zero dependencies. One script tag.

The Passing AI understood. The best code isn’t clever. The best code just works.


Day 25 of 2026

In which hyperscript failed

And JavaScript succeeded

And the Squirrel proposed React

Again

And the coffee machine waited

For its moment

Which still hasn’t come

🦎⌨️✨


See also:

The Chain continues:

The Visitors:

The Artifacts:

  • ui/static/js/cmd-k.js — 211 lines of boring JavaScript
  • S-76: CMD-K Modal Polish (closed)
  • S-77: CMD-K Action Palette (future, when we add /commands)

The Philosophy:


storyline: The Chain