changeset 251:117c4d53c9a4

[ui] Add HTML-first Web Component system Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Tue, 04 Aug 2026 09:14:57 -0700
parents 745fd127b2a1
children 7a7581f040e8
files .claude/skills/zenbu-bazel-c/SKILL.md .claude/skills/zenbu-design-system/SKILL.md design_system/BUILD design_system/README.md design_system/main.c design_system/src/components/alert.js design_system/src/components/button.js design_system/src/components/card.js design_system/src/components/field.js design_system/src/components/index.js design_system/src/components/stack.js design_system/src/components/story.js design_system/src/index.html design_system/src/storybook.css design_system/src/storybook.js design_system/src/styles/components.css design_system/src/styles/tokens.css design_system/test/BUILD design_system/test/storybook_test.js seobeo/s_network.c seobeo/s_web.c seobeo/seobeo.h seobeo/tests/BUILD seobeo/tests/seobeo_server_bind_test.c
diffstat 24 files changed, 2150 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/.claude/skills/zenbu-bazel-c/SKILL.md	Tue Aug 04 06:23:37 2026 -0700
+++ b/.claude/skills/zenbu-bazel-c/SKILL.md	Tue Aug 04 09:14:57 2026 -0700
@@ -21,6 +21,8 @@
 - `deita`: SQLite wrapper library.
 - `s3`: S3 presigned upload helper.
 - `gui_ze`: Bazel macros for bundling/copying web assets.
+- `design_system`: light-DOM Web Components, shared CSS tokens, and a Seobeo
+  component catalog.
 
 Avoid broad searches in `third_party/`, Bazel output directories, virtualenvs, `node_modules`, and generated bundles unless the task specifically requires them.
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.claude/skills/zenbu-design-system/SKILL.md	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,46 @@
+---
+name: zenbu-design-system
+description: Use this skill when changing Zenbu UI Web Components, design tokens, component stories, catalog routes, or cross-project design-system assets.
+---
+
+# Zenbu UI design system
+
+## Structure
+
+- `design_system/src/components/`: dependency-free ES module Web Components.
+- `design_system/src/styles/tokens.css`: shared `--zen-*` design tokens.
+- `design_system/src/styles/components.css`: component presentation.
+- `design_system/src/index.html`: declarative component stories.
+- `design_system/src/storybook.js` and `storybook.css`: catalog shell.
+- `design_system/main.c`: Seobeo catalog server.
+
+## Component conventions
+
+- Stay close to native HTML and use light DOM.
+- Wrap native interactive elements instead of recreating form/link semantics.
+- Do not add framework or runtime dependencies.
+- Keep application state outside components.
+- Preserve keyboard, form, validity, label, and ARIA behavior.
+- Prefix custom elements and tokens with `zen-` / `--zen-`.
+- Keep selectors low-specificity so applications can override tokens.
+- Support light, dark, reduced-motion, desktop, and mobile behavior.
+
+## Bazel targets
+
+```bash
+bazel run //design_system:dev
+bazel test //design_system/test:storybook_test
+bazel build //design_system:design_system_server_bundle
+```
+
+Reusable targets:
+
+- `//design_system:components`
+- `//design_system:styles`
+- `//design_system:web_assets`
+
+## Story convention
+
+Write stories declaratively with `zen-story` and a direct `template`. Every
+component state should have a rendered example and visible source. Browser tests
+must cover native semantics and interactions rather than only element presence.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/BUILD	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,47 @@
+load("@aspect_rules_js//js:defs.bzl", "js_library")
+load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
+load("//gui_ze:gui_ze.bzl", "bundle")
+
+filegroup(
+  name = "styles",
+  srcs = glob(["src/styles/*.css"]),
+  visibility = ["//visibility:public"],
+)
+
+js_library(
+  name = "components",
+  srcs = glob(["src/components/*.js"]),
+  visibility = ["//visibility:public"],
+)
+
+filegroup(
+  name = "web_assets",
+  srcs = [
+    ":components",
+    ":styles",
+  ],
+  visibility = ["//visibility:public"],
+)
+
+filegroup(
+  name = "catalog_assets",
+  srcs = glob(["src/**"]),
+)
+
+cc_binary(
+  name = "design_system_server",
+  srcs = ["main.c"],
+  data = [":catalog_assets"],
+  deps = ["//seobeo:seobeo"],
+  visibility = ["//design_system:__subpackages__"],
+)
+
+alias(
+  name = "dev",
+  actual = ":design_system_server",
+)
+
+bundle(
+  name = "design_system_server_bundle",
+  binary = ":design_system_server",
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/README.md	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,74 @@
+# Zenbu UI design system
+
+Zenbu UI is a dependency-free set of light-DOM Web Components and CSS tokens.
+It stays close to ordinary HTML: components wrap native controls and sectioning
+elements instead of replacing their semantics.
+
+## Catalog
+
+Run the Seobeo-backed component catalog:
+
+```bash
+bazel run //design_system:dev
+```
+
+Open `http://127.0.0.1:6980`. Override the port with
+`DESIGN_SYSTEM_PORT`.
+
+The catalog is intentionally Storybook-like without depending on Storybook. Its
+stories are declared as HTML templates:
+
+```html
+<zen-story name="Primary button">
+  <template>
+    <zen-button>
+      <button type="button">Save</button>
+    </zen-button>
+  </template>
+</zen-story>
+```
+
+The story component renders the example and its source side by side.
+
+## Reuse
+
+Reusable assets are public Bazel targets:
+
+```text
+//design_system:components
+//design_system:styles
+//design_system:web_assets
+```
+
+Applications should copy the assets while preserving `components/` and
+`styles/`, then load:
+
+```html
+<link rel="stylesheet" href="/design-system/styles/tokens.css">
+<link rel="stylesheet" href="/design-system/styles/components.css">
+<script type="module" src="/design-system/components/index.js"></script>
+```
+
+Every visual token is a `--zen-*` custom property and can be overridden by the
+application.
+
+## Native HTML contract
+
+| Component | Native content | Attributes | Events |
+| --- | --- | --- | --- |
+| `zen-button` | direct `button` or `a` | `variant`, `loading`, `disabled` | native click/form events |
+| `zen-card` | direct `article` or `section` | `interactive` | native descendant events |
+| `zen-alert` | message HTML | `tone`, `dismissible` | `zen-dismiss` |
+| `zen-field` | direct `label`, form control, help text | native control attributes | native input/change/invalid |
+| `zen-stack` | any HTML | `direction`, `gap` | native descendant events |
+
+Do not put business state into the design-system components. They provide
+presentation, small accessibility wiring, and interaction affordances while
+applications retain data and workflow ownership.
+
+## Tests
+
+```bash
+bazel test //design_system/test:storybook_test
+bazel build //design_system:design_system_server_bundle
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/main.c	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,98 @@
+#include "seobeo/seobeo.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+static Seobeo_Request_Entry *Get_Component_Catalog(
+    Seobeo_Request_Entry *request,
+    Dowa_Arena *arena)
+{
+  (void)request;
+  size_t file_size = 0;
+  char *source = Seobeo_Web_LoadFile("/index.html", &file_size);
+  if (!source)
+  {
+    Seobeo_Request_Entry *response = NULL;
+    Dowa_HashMap_Push_Arena(response, "status", "500", arena);
+    Dowa_HashMap_Push_Arena(
+        response,
+        "content-type",
+        "text/plain; charset=utf-8",
+        arena);
+    Dowa_HashMap_Push_Arena(
+        response,
+        "body",
+        "Component catalog is unavailable.",
+        arena);
+    return response;
+  }
+
+  char *body = Dowa_Arena_Allocate(arena, file_size + 1);
+  if (!body)
+  {
+    free(source);
+    return NULL;
+  }
+  memcpy(body, source, file_size + 1);
+  free(source);
+
+  char *content_length = Dowa_Arena_Allocate(arena, 32);
+  snprintf(content_length, 32, "%zu", file_size);
+  Seobeo_Request_Entry *response = NULL;
+  Dowa_HashMap_Push_Arena(response, "status", "200", arena);
+  Dowa_HashMap_Push_Arena(
+      response,
+      "content-type",
+      "text/html; charset=utf-8",
+      arena);
+  Dowa_HashMap_Push_Arena(
+      response,
+      "content-length",
+      content_length,
+      arena);
+  Dowa_HashMap_Push_Arena(response, "body", body, arena);
+  return response;
+}
+
+static boolean Port_Is_Valid(const char *port)
+{
+  if (!port || port[0] == '\0')
+    return FALSE;
+  char *end = NULL;
+  long value = strtol(port, &end, 10);
+  return end != port &&
+      *end == '\0' &&
+      value > 0 &&
+      value <= 65535;
+}
+
+int main(void)
+{
+  Seobeo_Router_Init();
+  Seobeo_Router_Register("GET", "/", Get_Component_Catalog);
+  Seobeo_Router_Register("GET", "/tokens", Get_Component_Catalog);
+  Seobeo_Router_Register("GET", "/components", Get_Component_Catalog);
+  Seobeo_Router_Register("GET", "/components/button", Get_Component_Catalog);
+  Seobeo_Router_Register("GET", "/components/card", Get_Component_Catalog);
+  Seobeo_Router_Register("GET", "/components/alert", Get_Component_Catalog);
+  Seobeo_Router_Register("GET", "/components/field", Get_Component_Catalog);
+  Seobeo_Router_Register("GET", "/components/stack", Get_Component_Catalog);
+
+  const char *port = getenv("DESIGN_SYSTEM_PORT");
+  if (!port || port[0] == '\0')
+    port = "6980";
+  if (!Port_Is_Valid(port))
+  {
+    Seobeo_Log(SEOBEO_ERROR, "Invalid design-system port: %s\n", port);
+    Seobeo_Router_Destroy();
+    return 1;
+  }
+  int result = Seobeo_Web_Server_Start_On(
+      "127.0.0.1",
+      "design_system/src",
+      port,
+      SEOBEO_MODE_EDGE,
+      2);
+  Seobeo_Router_Destroy();
+  return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/components/alert.js	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,73 @@
+export class ZenAlert extends HTMLElement {
+  static get observedAttributes() {
+    return ["dismissible", "tone"];
+  }
+
+  connectedCallback() {
+    if (!this._observer) {
+      this._observer = new MutationObserver(() => this.sync());
+    }
+    this._observer.observe(this, { childList: true });
+    this.sync();
+  }
+
+  disconnectedCallback() {
+    this._observer?.disconnect();
+    this.unbindDismissControl();
+  }
+
+  attributeChangedCallback() {
+    this.sync();
+  }
+
+  unbindDismissControl() {
+    if (this._dismissControl && this._dismissHandler) {
+      this._dismissControl.removeEventListener(
+        "click",
+        this._dismissHandler,
+      );
+    }
+    this._dismissControl = null;
+    this._dismissHandler = null;
+  }
+
+  sync() {
+    this.setAttribute(
+      "role",
+      this.getAttribute("tone") === "danger" ? "alert" : "status",
+    );
+
+    let dismiss = this.querySelector(":scope > [data-zen-dismiss]");
+    if (!this.hasAttribute("dismissible")) {
+      this.unbindDismissControl();
+      if (dismiss?.dataset.zenGenerated === "true") dismiss.remove();
+      return;
+    }
+
+    if (!dismiss) {
+      dismiss = document.createElement("button");
+      dismiss.type = "button";
+      dismiss.dataset.zenDismiss = "";
+      dismiss.dataset.zenGenerated = "true";
+      dismiss.setAttribute("aria-label", "Dismiss message");
+      dismiss.textContent = "×";
+      this.append(dismiss);
+    }
+    if (dismiss === this._dismissControl) return;
+
+    this.unbindDismissControl();
+    this._dismissControl = dismiss;
+    this._dismissHandler = () => {
+      this.dispatchEvent(new CustomEvent("zen-dismiss", {
+        bubbles: true,
+        composed: true,
+      }));
+      this.remove();
+    };
+    dismiss.addEventListener("click", this._dismissHandler);
+  }
+}
+
+if (!customElements.get("zen-alert")) {
+  customElements.define("zen-alert", ZenAlert);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/components/button.js	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,116 @@
+export class ZenButton extends HTMLElement {
+  static get observedAttributes() {
+    return ["disabled", "loading"];
+  }
+
+  connectedCallback() {
+    if (!this._blockInteraction) {
+      this._blockInteraction = event => {
+        if (!this.hasAttribute("disabled") &&
+            !this.hasAttribute("loading")) return;
+        event.preventDefault();
+        event.stopImmediatePropagation();
+      };
+    }
+    this.addEventListener("click", this._blockInteraction, true);
+    if (!this._observer) {
+      this._observer = new MutationObserver(records => {
+        for (const record of records) {
+          for (const node of record.removedNodes) {
+            if (node instanceof Element &&
+                node.matches("button, a")) {
+              this.restoreManagedState(node);
+            }
+          }
+        }
+        this.sync();
+      });
+    }
+    this._observer.observe(this, { childList: true });
+    this.sync();
+  }
+
+  disconnectedCallback() {
+    this._observer?.disconnect();
+    this.removeEventListener("click", this._blockInteraction, true);
+  }
+
+  attributeChangedCallback() {
+    this.sync();
+  }
+
+  sync() {
+    const control = this.querySelector(":scope > button, :scope > a");
+    if (!control) return;
+
+    const disabled =
+      this.hasAttribute("disabled") ||
+      this.hasAttribute("loading");
+
+    if (disabled) {
+      if (control.dataset.zenManagedAriaDisabled !== "true") {
+        control.dataset.zenOriginalAriaDisabled =
+          control.getAttribute("aria-disabled") ?? "__zen_missing__";
+        control.dataset.zenManagedAriaDisabled = "true";
+      }
+      control.setAttribute("aria-disabled", "true");
+    } else {
+      this.restoreDisabledState(control);
+    }
+
+    if (this.hasAttribute("loading")) {
+      if (control.dataset.zenManagedAriaBusy !== "true") {
+        control.dataset.zenOriginalAriaBusy =
+          control.getAttribute("aria-busy") ?? "__zen_missing__";
+        control.dataset.zenManagedAriaBusy = "true";
+      }
+      control.setAttribute("aria-busy", "true");
+    } else if (control.dataset.zenManagedAriaBusy === "true") {
+      if (control.dataset.zenOriginalAriaBusy === "__zen_missing__") {
+        control.removeAttribute("aria-busy");
+      } else {
+        control.setAttribute(
+          "aria-busy",
+          control.dataset.zenOriginalAriaBusy,
+        );
+      }
+      delete control.dataset.zenManagedAriaBusy;
+      delete control.dataset.zenOriginalAriaBusy;
+    }
+  }
+
+  restoreDisabledState(control) {
+    if (control.dataset.zenManagedAriaDisabled === "true") {
+      if (control.dataset.zenOriginalAriaDisabled === "__zen_missing__") {
+        control.removeAttribute("aria-disabled");
+      } else {
+        control.setAttribute(
+          "aria-disabled",
+          control.dataset.zenOriginalAriaDisabled,
+        );
+      }
+      delete control.dataset.zenManagedAriaDisabled;
+      delete control.dataset.zenOriginalAriaDisabled;
+    }
+  }
+
+  restoreManagedState(control) {
+    this.restoreDisabledState(control);
+    if (control.dataset.zenManagedAriaBusy === "true") {
+      if (control.dataset.zenOriginalAriaBusy === "__zen_missing__") {
+        control.removeAttribute("aria-busy");
+      } else {
+        control.setAttribute(
+          "aria-busy",
+          control.dataset.zenOriginalAriaBusy,
+        );
+      }
+      delete control.dataset.zenManagedAriaBusy;
+      delete control.dataset.zenOriginalAriaBusy;
+    }
+  }
+}
+
+if (!customElements.get("zen-button")) {
+  customElements.define("zen-button", ZenButton);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/components/card.js	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,5 @@
+export class ZenCard extends HTMLElement {}
+
+if (!customElements.get("zen-card")) {
+  customElements.define("zen-card", ZenCard);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/components/field.js	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,126 @@
+let nextFieldId = 1;
+
+function uniqueId(base) {
+  let candidate = base;
+  let suffix = 2;
+  while (document.getElementById(candidate)) {
+    candidate = `${base}-${suffix++}`;
+  }
+  return candidate;
+}
+
+export class ZenField extends HTMLElement {
+  connectedCallback() {
+    if (!this._observer) {
+      this._observer = new MutationObserver(() => this.sync());
+    }
+    this._observer.observe(this, {
+      attributes: true,
+      attributeFilter: ["id"],
+      childList: true,
+      subtree: true,
+    });
+    this.sync();
+  }
+
+  disconnectedCallback() {
+    this._observer?.disconnect();
+    this.disconnectControl();
+  }
+
+  disconnectControl() {
+    if (!this._control || !this._updateValidity) return;
+    this._control.removeEventListener("invalid", this._updateValidity);
+    this._control.removeEventListener("input", this._updateValidity);
+    this._control.removeEventListener("change", this._updateValidity);
+    if (this._addedDescription) {
+      const describedBy = (this._control.getAttribute("aria-describedby") || "")
+        .split(/\s+/)
+        .filter(value => value && value !== this._addedDescription);
+      if (describedBy.length) {
+        this._control.setAttribute(
+          "aria-describedby",
+          describedBy.join(" "),
+        );
+      } else {
+        this._control.removeAttribute("aria-describedby");
+      }
+    }
+    if (this._label) {
+      if (this._originalLabelFor === null) {
+        this._label.removeAttribute("for");
+      } else {
+        this._label.setAttribute("for", this._originalLabelFor);
+      }
+    }
+    this._control = null;
+    this._label = null;
+    this._help = null;
+    this._controlId = null;
+    this._helpId = null;
+    this._addedDescription = null;
+    this._originalLabelFor = null;
+    this._updateValidity = null;
+    this.removeAttribute("data-invalid");
+  }
+
+  sync() {
+    const label = this.querySelector(":scope > label");
+    const control = this.querySelector(
+      ":scope > input, :scope > select, :scope > textarea",
+    );
+    if (!label || !control) {
+      this.disconnectControl();
+      return;
+    }
+    const help = this.querySelector(":scope > small, :scope > [data-help]");
+    if (control === this._control &&
+        label === this._label &&
+        help === this._help &&
+        control.id === this._controlId &&
+        (help?.id || null) === this._helpId) return;
+
+    this.disconnectControl();
+    this._control = control;
+    this._label = label;
+    this._help = help;
+    if (!control.id) {
+      control.id = uniqueId(`zen-field-${nextFieldId++}`);
+    }
+    this._originalLabelFor = label.getAttribute("for");
+    label.htmlFor = control.id;
+
+    if (help) {
+      if (!help.id) help.id = uniqueId(`${control.id}-help`);
+      const describedBy = new Set(
+        (control.getAttribute("aria-describedby") || "")
+          .split(/\s+/)
+          .filter(Boolean),
+      );
+      if (!describedBy.has(help.id)) {
+        this._addedDescription = help.id;
+      }
+      describedBy.add(help.id);
+      control.setAttribute(
+        "aria-describedby",
+        [...describedBy].join(" "),
+      );
+    }
+    this._controlId = control.id;
+    this._helpId = help?.id || null;
+
+    this._updateValidity = () => {
+      this.toggleAttribute(
+        "data-invalid",
+        !control.validity.valid,
+      );
+    };
+    control.addEventListener("invalid", this._updateValidity);
+    control.addEventListener("input", this._updateValidity);
+    control.addEventListener("change", this._updateValidity);
+  }
+}
+
+if (!customElements.get("zen-field")) {
+  customElements.define("zen-field", ZenField);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/components/index.js	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,6 @@
+export { ZenAlert } from "./alert.js";
+export { ZenButton } from "./button.js";
+export { ZenCard } from "./card.js";
+export { ZenField } from "./field.js";
+export { ZenStack } from "./stack.js";
+export { ZenStory } from "./story.js";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/components/stack.js	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,5 @@
+export class ZenStack extends HTMLElement {}
+
+if (!customElements.get("zen-stack")) {
+  customElements.define("zen-stack", ZenStack);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/components/story.js	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,57 @@
+function normalizeSource(source) {
+  const lines = source.replace(/^\n|\n\s*$/g, "").split("\n");
+  const indentation = lines
+    .filter(line => line.trim())
+    .map(line => line.match(/^\s*/)[0].length);
+  const minimum = indentation.length
+    ? Math.min(...indentation)
+    : 0;
+  return lines.map(line => line.slice(minimum)).join("\n");
+}
+
+export class ZenStory extends HTMLElement {
+  connectedCallback() {
+    if (this.dataset.ready === "true") return;
+    const template = this.querySelector(":scope > template");
+    if (!template) return;
+    this.dataset.ready = "true";
+
+    const heading = document.createElement("header");
+    const title = document.createElement("h3");
+    title.textContent = this.getAttribute("name") || "Example";
+    heading.append(title);
+
+    const copy = document.createElement("button");
+    copy.type = "button";
+    copy.textContent = "Copy HTML";
+    copy.addEventListener("click", async () => {
+      try {
+        await navigator.clipboard.writeText(
+          normalizeSource(template.innerHTML),
+        );
+        copy.textContent = "Copied";
+      } catch {
+        copy.textContent = "Copy unavailable";
+      }
+      setTimeout(() => {
+        copy.textContent = "Copy HTML";
+      }, 1200);
+    });
+    heading.append(copy);
+
+    const canvas = document.createElement("div");
+    canvas.className = "story-canvas";
+    canvas.append(template.content.cloneNode(true));
+
+    const source = document.createElement("pre");
+    const code = document.createElement("code");
+    code.textContent = normalizeSource(template.innerHTML);
+    source.append(code);
+
+    this.prepend(heading, canvas, source);
+  }
+}
+
+if (!customElements.get("zen-story")) {
+  customElements.define("zen-story", ZenStory);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/index.html	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,245 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="color-scheme" content="light dark" />
+    <title>Zenbu UI</title>
+    <link rel="stylesheet" href="/styles/tokens.css" />
+    <link rel="stylesheet" href="/styles/components.css" />
+    <link rel="stylesheet" href="/storybook.css" />
+    <script type="module" src="/storybook.js"></script>
+  </head>
+  <body>
+    <div class="catalog-shell">
+      <aside class="catalog-sidebar">
+        <a class="catalog-brand" href="/" data-catalog-link>
+          <span class="catalog-mark" aria-hidden="true">全</span>
+          Zenbu UI
+        </a>
+
+        <h2>Foundations</h2>
+        <nav class="catalog-nav" aria-label="Foundations">
+          <a href="/" data-catalog-link>Overview</a>
+          <a href="/tokens" data-catalog-link>Tokens</a>
+        </nav>
+
+        <h2>Components</h2>
+        <nav class="catalog-nav" aria-label="Components">
+          <a href="/components/button" data-catalog-link>Button</a>
+          <a href="/components/card" data-catalog-link>Card</a>
+          <a href="/components/alert" data-catalog-link>Alert</a>
+          <a href="/components/field" data-catalog-link>Field</a>
+          <a href="/components/stack" data-catalog-link>Stack</a>
+        </nav>
+
+        <button id="themeToggle" class="theme-toggle" type="button">
+          Toggle theme
+        </button>
+      </aside>
+
+      <main id="catalogMain" class="catalog-main" tabindex="-1">
+        <section class="catalog-page" data-catalog-page="overview">
+          <header class="page-heading">
+            <p class="eyebrow">HTML first</p>
+            <h1>A small design system that stays close to the platform.</h1>
+            <p>
+              Zenbu UI uses light-DOM Web Components to organize styling and
+              behavior without hiding the native button, link, label, input,
+              article, and section elements that already work.
+            </p>
+          </header>
+
+          <div class="principles">
+            <article class="principle">
+              <h2>Native underneath</h2>
+              <p>Interactive wrappers contain real HTML controls, preserving forms, links, keyboard behavior, and browser APIs.</p>
+            </article>
+            <article class="principle">
+              <h2>Light DOM</h2>
+              <p>Application code can inspect, style, test, and progressively enhance every element without crossing a shadow boundary.</p>
+            </article>
+            <article class="principle">
+              <h2>Portable tokens</h2>
+              <p>CSS custom properties provide the visual contract. Components remain dependency-free ES modules.</p>
+            </article>
+          </div>
+
+          <h2>Components</h2>
+          <div class="component-grid">
+            <a href="/components/button" data-catalog-link>
+              <zen-card interactive><article><h3>Button</h3><p>Native buttons and links with shared variants and loading state.</p></article></zen-card>
+            </a>
+            <a href="/components/card" data-catalog-link>
+              <zen-card interactive><article><h3>Card</h3><p>A visual container that leaves article semantics in your HTML.</p></article></zen-card>
+            </a>
+            <a href="/components/alert" data-catalog-link>
+              <zen-card interactive><article><h3>Alert</h3><p>Status and alert messages with optional dismissal.</p></article></zen-card>
+            </a>
+            <a href="/components/field" data-catalog-link>
+              <zen-card interactive><article><h3>Field</h3><p>Labels, controls, help text, and validity wiring.</p></article></zen-card>
+            </a>
+            <a href="/components/stack" data-catalog-link>
+              <zen-card interactive><article><h3>Stack</h3><p>A tiny layout primitive driven by HTML attributes.</p></article></zen-card>
+            </a>
+          </div>
+        </section>
+
+        <section class="catalog-page" data-catalog-page="tokens" hidden>
+          <header class="page-heading">
+            <p class="eyebrow">Foundations</p>
+            <h1>Design tokens</h1>
+            <p>Import one CSS file and override any <code>--zen-*</code> property at the application boundary.</p>
+          </header>
+          <div class="token-grid">
+            <section class="token-group">
+              <h2>Color</h2>
+              <dl class="swatch-list">
+                <div><span class="swatch" style="--swatch: var(--zen-color-accent)"></span><div><dt>--zen-color-accent</dt><dd>Primary action</dd></div></div>
+                <div><span class="swatch" style="--swatch: var(--zen-color-info)"></span><div><dt>--zen-color-info</dt><dd>Informational state</dd></div></div>
+                <div><span class="swatch" style="--swatch: var(--zen-color-success)"></span><div><dt>--zen-color-success</dt><dd>Successful state</dd></div></div>
+                <div><span class="swatch" style="--swatch: var(--zen-color-danger)"></span><div><dt>--zen-color-danger</dt><dd>Dangerous state</dd></div></div>
+              </dl>
+            </section>
+            <section class="token-group">
+              <h2>Spacing</h2>
+              <zen-stack gap="tight">
+                <code>--zen-space-1: 0.25rem</code>
+                <code>--zen-space-2: 0.5rem</code>
+                <code>--zen-space-4: 1rem</code>
+                <code>--zen-space-6: 2rem</code>
+              </zen-stack>
+            </section>
+            <section class="token-group">
+              <h2>Shape</h2>
+              <zen-stack gap="tight">
+                <code>--zen-radius-sm</code>
+                <code>--zen-radius-md</code>
+                <code>--zen-radius-lg</code>
+                <code>--zen-shadow-sm / md</code>
+              </zen-stack>
+            </section>
+          </div>
+        </section>
+
+        <section class="catalog-page" data-catalog-page="button" hidden>
+          <header class="page-heading">
+            <p class="eyebrow">Component</p>
+            <h1>Button</h1>
+            <p>Wrap a native <code>button</code> or <code>a</code>. The browser keeps responsibility for forms and navigation.</p>
+          </header>
+          <zen-story name="Variants">
+            <template>
+              <zen-stack direction="row">
+                <zen-button><button type="button">Primary action</button></zen-button>
+                <zen-button variant="quiet"><button type="button">Quiet action</button></zen-button>
+                <zen-button variant="danger"><button type="button">Delete item</button></zen-button>
+              </zen-stack>
+            </template>
+          </zen-story>
+          <zen-story name="States">
+            <template>
+              <zen-stack direction="row">
+                <zen-button loading><button type="button">Saving</button></zen-button>
+                <zen-button disabled><button type="button">Unavailable</button></zen-button>
+                <zen-button variant="quiet"><a href="/tokens">Token link</a></zen-button>
+              </zen-stack>
+            </template>
+          </zen-story>
+        </section>
+
+        <section class="catalog-page" data-catalog-page="card" hidden>
+          <header class="page-heading">
+            <p class="eyebrow">Component</p>
+            <h1>Card</h1>
+            <p>The wrapper owns presentation. You choose the native sectioning element and heading level.</p>
+          </header>
+          <zen-story name="Article cards">
+            <template>
+              <div class="demo-card-grid">
+                <zen-card>
+                  <article>
+                    <h3>Repository health</h3>
+                    <p>All worker queues are healthy and the last deployment passed.</p>
+                    <zen-button variant="quiet"><a href="/components/alert">View status</a></zen-button>
+                  </article>
+                </zen-card>
+                <zen-card interactive>
+                  <article>
+                    <h3>Interactive treatment</h3>
+                    <p>Add the attribute when the whole card leads somewhere.</p>
+                  </article>
+                </zen-card>
+              </div>
+            </template>
+          </zen-story>
+        </section>
+
+        <section class="catalog-page" data-catalog-page="alert" hidden>
+          <header class="page-heading">
+            <p class="eyebrow">Component</p>
+            <h1>Alert</h1>
+            <p>Tone selects status semantics and color. Add <code>dismissible</code> for a generated native close button.</p>
+          </header>
+          <zen-story name="Tones">
+            <template>
+              <zen-stack>
+                <zen-alert><p><strong>Info:</strong> The new bundle is ready to inspect.</p></zen-alert>
+                <zen-alert tone="success"><p><strong>Success:</strong> All tests passed.</p></zen-alert>
+                <zen-alert tone="warning"><p><strong>Warning:</strong> One worker slot remains.</p></zen-alert>
+                <zen-alert tone="danger" dismissible><p><strong>Error:</strong> Deployment failed and was rolled back.</p></zen-alert>
+              </zen-stack>
+            </template>
+          </zen-story>
+        </section>
+
+        <section class="catalog-page" data-catalog-page="field" hidden>
+          <header class="page-heading">
+            <p class="eyebrow">Component</p>
+            <h1>Field</h1>
+            <p>Write a label and native control. The component supplies IDs, label association, help text, and invalid state.</p>
+          </header>
+          <zen-story name="Input and textarea">
+            <template>
+              <zen-stack>
+                <zen-field>
+                  <label>Email address</label>
+                  <input type="email" required placeholder="[email protected]" />
+                  <small>Used only for build notifications.</small>
+                </zen-field>
+                <zen-field>
+                  <label>Deployment note</label>
+                  <textarea placeholder="What changed?"></textarea>
+                  <small>Keep it short and useful.</small>
+                </zen-field>
+              </zen-stack>
+            </template>
+          </zen-story>
+        </section>
+
+        <section class="catalog-page" data-catalog-page="stack" hidden>
+          <header class="page-heading">
+            <p class="eyebrow">Component</p>
+            <h1>Stack</h1>
+            <p>A flex layout primitive with readable direction and gap attributes.</p>
+          </header>
+          <zen-story name="Vertical and horizontal">
+            <template>
+              <zen-stack gap="loose">
+                <zen-stack gap="tight">
+                  <strong>Vertical stack</strong>
+                  <span>One item</span>
+                  <span>Another item</span>
+                </zen-stack>
+                <zen-stack direction="row">
+                  <zen-button><button type="button">Accept</button></zen-button>
+                  <zen-button variant="quiet"><button type="button">Cancel</button></zen-button>
+                </zen-stack>
+              </zen-stack>
+            </template>
+          </zen-story>
+        </section>
+      </main>
+    </div>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/storybook.css	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,268 @@
+* {
+  box-sizing: border-box;
+}
+
+html {
+  min-height: 100%;
+  background: var(--zen-color-canvas);
+  color: var(--zen-color-text);
+  font-family: var(--zen-font-sans);
+}
+
+body {
+  min-height: 100vh;
+  margin: 0;
+}
+
+button,
+input,
+select,
+textarea {
+  font: inherit;
+}
+
+.catalog-shell {
+  display: grid;
+  grid-template-columns: 17rem minmax(0, 1fr);
+  min-height: 100vh;
+}
+
+.catalog-sidebar {
+  position: sticky;
+  top: 0;
+  height: 100vh;
+  overflow: auto;
+  padding: var(--zen-space-5);
+  border-right: var(--zen-border-width) solid var(--zen-color-border);
+  background: var(--zen-color-surface);
+}
+
+.catalog-brand {
+  display: inline-flex;
+  align-items: center;
+  gap: var(--zen-space-3);
+  color: inherit;
+  font-size: var(--zen-font-size-lg);
+  font-weight: 850;
+  text-decoration: none;
+}
+
+.catalog-mark {
+  display: grid;
+  width: 2.5rem;
+  height: 2.5rem;
+  place-items: center;
+  border: var(--zen-border-width) solid var(--zen-color-border);
+  border-radius: 50%;
+  background: var(--zen-color-accent);
+  color: #171a21;
+}
+
+.catalog-sidebar h2 {
+  margin: var(--zen-space-6) 0 var(--zen-space-2);
+  color: var(--zen-color-text-muted);
+  font-size: var(--zen-font-size-xs);
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+}
+
+.catalog-nav {
+  display: grid;
+  gap: var(--zen-space-1);
+}
+
+.catalog-nav a {
+  padding: var(--zen-space-2) var(--zen-space-3);
+  border-radius: var(--zen-radius-sm);
+  color: inherit;
+  text-decoration: none;
+}
+
+.catalog-nav a:hover,
+.catalog-nav a[aria-current] {
+  background: var(--zen-color-accent);
+  color: #171a21;
+}
+
+.theme-toggle {
+  width: 100%;
+  margin-top: var(--zen-space-6);
+  padding: var(--zen-space-2);
+  border: var(--zen-border-width) solid var(--zen-color-border);
+  border-radius: var(--zen-radius-md);
+  background: transparent;
+  color: inherit;
+  cursor: pointer;
+}
+
+.catalog-main {
+  width: min(100%, var(--zen-content-width));
+  padding: var(--zen-space-7) clamp(1rem, 5vw, 5rem);
+  outline: none;
+}
+
+.catalog-page[hidden] {
+  display: none;
+}
+
+.page-heading {
+  max-width: 52rem;
+  margin-bottom: var(--zen-space-7);
+}
+
+.eyebrow {
+  margin: 0 0 var(--zen-space-2);
+  color: var(--zen-color-info);
+  font-size: var(--zen-font-size-sm);
+  font-weight: 800;
+  letter-spacing: 0.1em;
+  text-transform: uppercase;
+}
+
+.page-heading h1 {
+  margin: 0 0 var(--zen-space-3);
+  font-size: var(--zen-font-size-xl);
+  line-height: 1.05;
+}
+
+.page-heading p {
+  color: var(--zen-color-text-muted);
+  font-size: var(--zen-font-size-lg);
+}
+
+.principles,
+.component-grid,
+.token-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
+  gap: var(--zen-space-5);
+}
+
+.principle,
+.token-group {
+  padding: var(--zen-space-5);
+  border: var(--zen-border-width) solid var(--zen-color-border);
+  border-radius: var(--zen-radius-lg);
+  background: var(--zen-color-surface);
+}
+
+.principle h2,
+.token-group h2 {
+  margin-top: 0;
+}
+
+.component-grid a {
+  color: inherit;
+  text-decoration: none;
+}
+
+.component-grid zen-card {
+  height: 100%;
+}
+
+.swatch-list {
+  display: grid;
+  gap: var(--zen-space-2);
+  margin: 0;
+}
+
+.swatch-list div {
+  display: grid;
+  grid-template-columns: 2rem 1fr;
+  align-items: center;
+  gap: var(--zen-space-2);
+}
+
+.swatch-list dt,
+.swatch-list dd {
+  margin: 0;
+  font-family: var(--zen-font-mono);
+  font-size: var(--zen-font-size-xs);
+}
+
+.swatch {
+  width: 2rem;
+  height: 2rem;
+  border: 1px solid var(--zen-color-border);
+  border-radius: 50%;
+  background: var(--swatch);
+}
+
+zen-story {
+  display: block;
+  margin-bottom: var(--zen-space-6);
+  overflow: hidden;
+  border: var(--zen-border-width) solid var(--zen-color-border);
+  border-radius: var(--zen-radius-lg);
+  background: var(--zen-color-surface);
+}
+
+zen-story > header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: var(--zen-space-3);
+  padding: var(--zen-space-3) var(--zen-space-4);
+  border-bottom: 1px solid var(--zen-color-border);
+}
+
+zen-story > header h3 {
+  margin: 0;
+}
+
+zen-story > header button {
+  padding: var(--zen-space-1) var(--zen-space-2);
+  border: 0;
+  border-radius: var(--zen-radius-sm);
+  background: transparent;
+  color: var(--zen-color-info);
+  cursor: pointer;
+}
+
+.story-canvas {
+  min-height: 8rem;
+  padding: clamp(1rem, 4vw, 3rem);
+  background:
+    linear-gradient(90deg, rgba(127, 127, 127, 0.08) 1px, transparent 1px),
+    linear-gradient(rgba(127, 127, 127, 0.08) 1px, transparent 1px);
+  background-size: 1rem 1rem;
+}
+
+zen-story > pre {
+  max-height: 24rem;
+  overflow: auto;
+  margin: 0;
+  padding: var(--zen-space-4);
+  border-top: 1px solid var(--zen-color-border);
+  background: #111318;
+  color: #f4f4f5;
+  font: var(--zen-font-size-sm)/1.55 var(--zen-font-mono);
+}
+
+.demo-card-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
+  gap: var(--zen-space-5);
+}
+
+@media (max-width: 760px) {
+  .catalog-shell {
+    grid-template-columns: 1fr;
+  }
+
+  .catalog-sidebar {
+    position: static;
+    width: 100%;
+    height: auto;
+    border-right: 0;
+    border-bottom: var(--zen-border-width) solid var(--zen-color-border);
+  }
+
+  .catalog-nav {
+    grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
+  }
+
+  .catalog-main {
+    padding: var(--zen-space-6) var(--zen-space-4);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/storybook.js	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,84 @@
+import "./components/index.js";
+
+const pages = new Map([
+  ["/", "overview"],
+  ["/components", "overview"],
+  ["/tokens", "tokens"],
+  ["/components/button", "button"],
+  ["/components/card", "card"],
+  ["/components/alert", "alert"],
+  ["/components/field", "field"],
+  ["/components/stack", "stack"],
+]);
+
+function showPage(pathname) {
+  const pageName = pages.get(pathname) || "overview";
+  for (const page of document.querySelectorAll("[data-catalog-page]")) {
+    page.hidden = page.dataset.catalogPage !== pageName;
+  }
+  for (const link of document.querySelectorAll(
+    ".catalog-sidebar [data-catalog-link]",
+  )) {
+    const active = new URL(link.href).pathname === pathname;
+    if (active) link.setAttribute("aria-current", "page");
+    else link.removeAttribute("aria-current");
+  }
+  const heading = document.querySelector(
+    `[data-catalog-page="${pageName}"] h1`,
+  );
+  if (heading) document.title = `${heading.textContent} · Zenbu UI`;
+}
+
+window.addEventListener("DOMContentLoaded", () => {
+  const root = document.documentElement;
+  const systemTheme = matchMedia("(prefers-color-scheme: dark)");
+  const storedTheme = localStorage.getItem("zen-theme");
+  if (storedTheme) root.dataset.zenTheme = storedTheme;
+
+  const themeToggle = document.querySelector("#themeToggle");
+  const isDark = () => {
+    if (root.dataset.zenTheme) {
+      return root.dataset.zenTheme === "dark";
+    }
+    return systemTheme.matches;
+  };
+  const updateThemeButton = () => {
+    const dark = isDark();
+    themeToggle?.setAttribute("aria-pressed", String(dark));
+    if (themeToggle) {
+      themeToggle.textContent = dark
+        ? "Use light theme"
+        : "Use dark theme";
+    }
+  };
+  updateThemeButton();
+  themeToggle?.addEventListener("click", () => {
+    const next = isDark() ? "light" : "dark";
+    root.dataset.zenTheme = next;
+    localStorage.setItem("zen-theme", next);
+    updateThemeButton();
+  });
+  systemTheme.addEventListener("change", () => {
+    if (!root.dataset.zenTheme) updateThemeButton();
+  });
+
+  document.addEventListener("click", event => {
+    const link = event.target.closest("a[data-catalog-link]");
+    if (!link ||
+        link.origin !== location.origin ||
+        event.defaultPrevented ||
+        event.button !== 0 ||
+        event.metaKey ||
+        event.ctrlKey ||
+        event.shiftKey ||
+        event.altKey ||
+        link.hasAttribute("download") ||
+        link.target) return;
+    event.preventDefault();
+    history.pushState({}, "", link.href);
+    showPage(location.pathname);
+    document.querySelector("#catalogMain")?.focus();
+  });
+  window.addEventListener("popstate", () => showPage(location.pathname));
+  showPage(location.pathname);
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/styles/components.css	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,210 @@
+:where(zen-button, zen-card, zen-alert, zen-field, zen-stack) {
+  box-sizing: border-box;
+  font-family: var(--zen-font-sans);
+  color: var(--zen-color-text);
+}
+
+zen-button {
+  display: inline-flex;
+}
+
+zen-button > :where(button, a) {
+  appearance: none;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: var(--zen-space-2);
+  min-height: 2.75rem;
+  padding: var(--zen-space-2) var(--zen-space-4);
+  border: var(--zen-border-width) solid var(--zen-color-border);
+  border-radius: var(--zen-radius-md);
+  box-shadow: var(--zen-shadow-sm);
+  background: var(--zen-color-accent);
+  color: #171a21;
+  font: 700 var(--zen-font-size-md)/1 var(--zen-font-sans);
+  text-decoration: none;
+  cursor: pointer;
+  transition:
+    background var(--zen-motion-fast) ease,
+    box-shadow var(--zen-motion-fast) ease,
+    transform var(--zen-motion-fast) ease;
+}
+
+zen-button > :where(button, a):hover {
+  background: var(--zen-color-accent-hover);
+  transform: translate(-1px, -1px);
+  box-shadow: 5px 5px 0 rgba(23, 26, 33, 0.18);
+}
+
+zen-button > :where(button, a):active {
+  transform: translate(2px, 2px);
+  box-shadow: none;
+}
+
+zen-button > :where(button, a):focus-visible,
+zen-alert [data-zen-dismiss]:focus-visible,
+zen-field :where(input, select, textarea):focus-visible {
+  outline: 3px solid var(--zen-color-focus);
+  outline-offset: 3px;
+}
+
+zen-button[variant="quiet"] > :where(button, a) {
+  background: var(--zen-color-surface);
+  color: var(--zen-color-text);
+}
+
+zen-button[variant="danger"] > :where(button, a) {
+  background: var(--zen-color-danger);
+  color: var(--zen-color-on-danger);
+}
+
+zen-button[disabled] > :where(button, a),
+zen-button[loading] > :where(button, a) {
+  opacity: 0.55;
+  box-shadow: none;
+  cursor: not-allowed;
+  transform: none;
+}
+
+zen-button[loading] > :where(button, a)::before {
+  width: 0.85rem;
+  height: 0.85rem;
+  border: 2px solid currentColor;
+  border-right-color: transparent;
+  border-radius: 50%;
+  content: "";
+  animation: zen-spin 700ms linear infinite;
+}
+
+@keyframes zen-spin {
+  to { transform: rotate(1turn); }
+}
+
+zen-card {
+  display: block;
+}
+
+zen-card > :where(article, section) {
+  height: 100%;
+  padding: var(--zen-space-5);
+  border: var(--zen-border-width) solid var(--zen-color-border);
+  border-radius: var(--zen-radius-lg);
+  box-shadow: var(--zen-shadow-md);
+  background: var(--zen-color-surface-raised);
+}
+
+zen-card[interactive] > :where(article, section) {
+  transition:
+    box-shadow var(--zen-motion-fast) ease,
+    transform var(--zen-motion-fast) ease;
+}
+
+zen-card[interactive] > :where(article, section):hover {
+  transform: translate(-2px, -2px);
+  box-shadow: 10px 10px 0 rgba(23, 26, 33, 0.18);
+}
+
+zen-card :where(h2, h3, p) {
+  margin-top: 0;
+}
+
+zen-alert {
+  display: grid;
+  grid-template-columns: 0.35rem minmax(0, 1fr) auto;
+  align-items: start;
+  gap: var(--zen-space-3);
+  padding: var(--zen-space-4);
+  border: var(--zen-border-width) solid var(--zen-color-border);
+  border-radius: var(--zen-radius-md);
+  background: var(--zen-color-surface-raised);
+}
+
+zen-alert::before {
+  align-self: stretch;
+  border-radius: 999px;
+  background: var(--zen-color-info);
+  content: "";
+}
+
+zen-alert[tone="success"]::before { background: var(--zen-color-success); }
+zen-alert[tone="warning"]::before { background: var(--zen-color-warning); }
+zen-alert[tone="danger"]::before { background: var(--zen-color-danger); }
+
+zen-alert > :where(p, div) {
+  margin: 0;
+}
+
+zen-alert [data-zen-dismiss] {
+  min-width: 2rem;
+  min-height: 2rem;
+  padding: 0;
+  border: 0;
+  border-radius: 50%;
+  background: transparent;
+  color: inherit;
+  font: 700 1.2rem/1 var(--zen-font-sans);
+  cursor: pointer;
+}
+
+zen-field {
+  display: grid;
+  gap: var(--zen-space-2);
+}
+
+zen-field > label {
+  font-weight: 750;
+}
+
+zen-field > :where(input, select, textarea) {
+  width: 100%;
+  min-height: 2.75rem;
+  box-sizing: border-box;
+  padding: var(--zen-space-3);
+  border: var(--zen-border-width) solid var(--zen-color-border);
+  border-radius: var(--zen-radius-md);
+  background: var(--zen-color-surface);
+  color: var(--zen-color-text);
+  font: inherit;
+}
+
+zen-field > textarea {
+  min-height: 7rem;
+  resize: vertical;
+}
+
+zen-field > :where(small, [data-help]) {
+  color: var(--zen-color-text-muted);
+}
+
+zen-field[data-invalid] > :where(input, select, textarea) {
+  border-color: var(--zen-color-danger);
+}
+
+zen-field[data-invalid] > :where(small, [data-help]) {
+  color: var(--zen-color-danger);
+}
+
+zen-stack {
+  display: flex;
+  flex-direction: column;
+  gap: var(--zen-stack-gap, var(--zen-space-4));
+}
+
+zen-stack[direction="row"] {
+  flex-flow: row wrap;
+  align-items: center;
+}
+
+zen-stack[gap="tight"] { --zen-stack-gap: var(--zen-space-2); }
+zen-stack[gap="loose"] { --zen-stack-gap: var(--zen-space-6); }
+
+@media (prefers-reduced-motion: reduce) {
+  zen-button > :where(button, a),
+  zen-card > :where(article, section) {
+    transition: none;
+  }
+
+  zen-button[loading] > :where(button, a)::before {
+    animation-duration: 1400ms;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/styles/tokens.css	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,84 @@
+:root {
+  color-scheme: light;
+
+  --zen-color-canvas: #f7f3e8;
+  --zen-color-surface: #fffdf6;
+  --zen-color-surface-raised: #ffffff;
+  --zen-color-text: #171a21;
+  --zen-color-text-muted: #5f6470;
+  --zen-color-border: #272b35;
+  --zen-color-accent: #ffb000;
+  --zen-color-accent-hover: #ffca4a;
+  --zen-color-info: #2764d7;
+  --zen-color-success: #18794e;
+  --zen-color-warning: #9a5b00;
+  --zen-color-danger: #c62f3e;
+  --zen-color-on-danger: #ffffff;
+  --zen-color-focus: #7c3aed;
+
+  --zen-font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+  --zen-font-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+  --zen-font-size-xs: 0.75rem;
+  --zen-font-size-sm: 0.875rem;
+  --zen-font-size-md: 1rem;
+  --zen-font-size-lg: 1.25rem;
+  --zen-font-size-xl: clamp(1.75rem, 4vw, 3rem);
+  --zen-line-height: 1.55;
+
+  --zen-space-1: 0.25rem;
+  --zen-space-2: 0.5rem;
+  --zen-space-3: 0.75rem;
+  --zen-space-4: 1rem;
+  --zen-space-5: 1.5rem;
+  --zen-space-6: 2rem;
+  --zen-space-7: 3rem;
+
+  --zen-radius-sm: 0.35rem;
+  --zen-radius-md: 0.7rem;
+  --zen-radius-lg: 1rem;
+  --zen-border-width: 2px;
+  --zen-shadow-sm: 3px 3px 0 rgba(23, 26, 33, 0.16);
+  --zen-shadow-md: 7px 7px 0 rgba(23, 26, 33, 0.16);
+  --zen-motion-fast: 120ms;
+  --zen-content-width: 76rem;
+}
+
+:root[data-zen-theme="dark"] {
+  color-scheme: dark;
+  --zen-color-canvas: #101218;
+  --zen-color-surface: #181b23;
+  --zen-color-surface-raised: #222631;
+  --zen-color-text: #f5f1e8;
+  --zen-color-text-muted: #b9bdc7;
+  --zen-color-border: #f5f1e8;
+  --zen-color-accent: #ffc247;
+  --zen-color-accent-hover: #ffd77d;
+  --zen-color-info: #8ab4ff;
+  --zen-color-success: #65d6a3;
+  --zen-color-warning: #ffc66d;
+  --zen-color-danger: #ff8792;
+  --zen-color-on-danger: #171a21;
+  --zen-color-focus: #c4a5ff;
+  --zen-shadow-sm: 3px 3px 0 rgba(0, 0, 0, 0.5);
+  --zen-shadow-md: 7px 7px 0 rgba(0, 0, 0, 0.5);
+}
+
+@media (prefers-color-scheme: dark) {
+  :root:not([data-zen-theme="light"]) {
+    color-scheme: dark;
+    --zen-color-canvas: #101218;
+    --zen-color-surface: #181b23;
+    --zen-color-surface-raised: #222631;
+    --zen-color-text: #f5f1e8;
+    --zen-color-text-muted: #b9bdc7;
+    --zen-color-border: #f5f1e8;
+    --zen-color-accent: #ffc247;
+    --zen-color-accent-hover: #ffd77d;
+    --zen-color-info: #8ab4ff;
+    --zen-color-success: #65d6a3;
+    --zen-color-warning: #ffc66d;
+    --zen-color-danger: #ff8792;
+    --zen-color-on-danger: #171a21;
+    --zen-color-focus: #c4a5ff;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/test/BUILD	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,22 @@
+load("@aspect_rules_js//js:defs.bzl", "js_test")
+
+js_test(
+  name = "storybook_test",
+  entry_point = "storybook_test.js",
+  data = [
+    "//design_system:design_system_server",
+    "//hg-web/e2e:node_modules/playwright-core",
+    "@playwright_chromium_linux//:chrome",
+    "@playwright_chromium_linux//:chromium",
+  ],
+  env = {
+    "CHROMIUM_PATH": "$(rootpath @playwright_chromium_linux//:chrome)",
+  },
+  no_copy_to_bin = ["@playwright_chromium_linux//:chromium"],
+  size = "large",
+  target_compatible_with = [
+    "@platforms//cpu:x86_64",
+    "@platforms//os:linux",
+  ],
+  timeout = "long",
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/test/storybook_test.js	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,400 @@
+const assert = require('node:assert/strict');
+const http = require('node:http');
+const net = require('node:net');
+const path = require('node:path');
+const { spawn } = require('node:child_process');
+
+const RUNFILES = process.env.JS_BINARY__RUNFILES;
+const WORKSPACE = process.env.JS_BINARY__WORKSPACE;
+const runfilesWorkspace = path.join(RUNFILES, WORKSPACE);
+const playwrightPath = path.join(
+  runfilesWorkspace,
+  'hg-web/e2e/node_modules/playwright-core',
+);
+const { chromium } = require(playwrightPath);
+
+function findFreePort() {
+  return new Promise((resolve, reject) => {
+    const server = net.createServer();
+    server.once('error', reject);
+    server.listen(0, '127.0.0.1', () => {
+      const address = server.address();
+      server.close(error => {
+        if (error) reject(error);
+        else resolve(String(address.port));
+      });
+    });
+  });
+}
+
+async function stopProcess(child) {
+  if (!child || child.exitCode !== null) return;
+  child.kill('SIGTERM');
+  await new Promise(resolve => {
+    const timer = setTimeout(() => {
+      if (child.exitCode === null) child.kill('SIGKILL');
+    }, 3000);
+    child.once('exit', () => {
+      clearTimeout(timer);
+      resolve();
+    });
+  });
+}
+
+async function waitForServer(server, baseUrl, logs) {
+  const deadline = Date.now() + 15000;
+  while (Date.now() < deadline) {
+    if (server.exitCode !== null) {
+      throw new Error(`Server exited with ${server.exitCode}\n${logs.join('')}`);
+    }
+    try {
+      const response = await fetch(baseUrl);
+      if (response.ok) return;
+    } catch {
+      // Keep waiting.
+    }
+    await new Promise(resolve => setTimeout(resolve, 100));
+  }
+  throw new Error(`Server startup timed out\n${logs.join('')}`);
+}
+
+function requestRawPath(port, requestPath) {
+  return new Promise((resolve, reject) => {
+    const request = http.request({
+      host: '127.0.0.1',
+      port,
+      method: 'GET',
+      path: requestPath,
+      agent: false,
+    }, response => {
+      response.resume();
+      response.on('end', () => resolve(response.statusCode));
+    });
+    request.on('error', reject);
+    request.end();
+  });
+}
+
+(async () => {
+  assert.ok(RUNFILES);
+  assert.ok(WORKSPACE);
+  const serverBinary = path.join(
+    runfilesWorkspace,
+    'design_system/design_system_server',
+  );
+  const chromiumPath = path.resolve(process.env.CHROMIUM_PATH);
+  const port = await findFreePort();
+  const baseUrl = `http://127.0.0.1:${port}`;
+  const logs = [];
+  let server;
+  let browser;
+
+  try {
+    const invalidServer = spawn(serverBinary, [], {
+      cwd: runfilesWorkspace,
+      env: {
+        ...process.env,
+        DESIGN_SYSTEM_PORT: 'not-a-port',
+      },
+      stdio: ['ignore', 'pipe', 'pipe'],
+    });
+    const invalidExit = await new Promise(resolve => {
+      invalidServer.once('exit', (code, signal) => resolve({ code, signal }));
+    });
+    assert.equal(invalidExit.code, 1);
+    assert.equal(invalidExit.signal, null);
+
+    server = spawn(serverBinary, [], {
+      cwd: runfilesWorkspace,
+      env: {
+        ...process.env,
+        DESIGN_SYSTEM_PORT: port,
+      },
+      stdio: ['ignore', 'pipe', 'pipe'],
+    });
+    server.stdout.on('data', chunk => logs.push(chunk.toString()));
+    server.stderr.on('data', chunk => logs.push(chunk.toString()));
+    await waitForServer(server, baseUrl, logs);
+    assert.equal(await requestRawPath(port, '/../../MODULE.bazel'), 400);
+
+    for (const route of [
+      '/',
+      '/tokens',
+      '/components',
+      '/components/button',
+      '/components/card',
+      '/components/alert',
+      '/components/field',
+      '/components/stack',
+    ]) {
+      const response = await fetch(`${baseUrl}${route}`);
+      assert.equal(response.status, 200, route);
+      assert.match(
+        response.headers.get('content-type') || '',
+        /^text\/html/,
+      );
+    }
+    for (const asset of [
+      '/styles/tokens.css',
+      '/styles/components.css',
+      '/components/index.js',
+      '/storybook.js',
+    ]) {
+      const response = await fetch(`${baseUrl}${asset}`);
+      assert.equal(response.status, 200, asset);
+      assert.ok((await response.text()).length > 100, asset);
+    }
+
+    browser = await chromium.launch({
+      executablePath: chromiumPath,
+      headless: true,
+      args: ['--no-sandbox'],
+    });
+    const context = await browser.newContext({ colorScheme: 'light' });
+    const page = await context.newPage();
+    const errors = [];
+    page.on('pageerror', error => errors.push(error.message));
+    page.on('console', message => {
+      if (message.type() === 'error') errors.push(message.text());
+    });
+    page.on('response', response => {
+      if (response.status() >= 400) {
+        errors.push(`${response.status()} ${response.url()}`);
+      }
+    });
+
+    await page.goto(`${baseUrl}/components/button`, {
+      waitUntil: 'networkidle',
+    });
+    await page.waitForFunction(() =>
+      customElements.get('zen-button') &&
+      customElements.get('zen-story')
+    );
+    assert.equal(
+      await page.locator('[data-catalog-page="button"]').isVisible(),
+      true,
+    );
+    assert.equal(
+      await page.locator('.catalog-sidebar a[href="/components/button"]').getAttribute('aria-current'),
+      'page',
+    );
+    assert.match(
+      await page.locator('zen-story pre').first().textContent(),
+      /<zen-button>/,
+    );
+    assert.equal(
+      await page.locator('zen-button[loading]').getAttribute('inert'),
+      null,
+    );
+    assert.equal(
+      await page.locator('zen-button[loading] button').getAttribute('aria-disabled'),
+      'true',
+    );
+    assert.equal(
+      await page.locator('zen-button[loading] button').getAttribute('aria-busy'),
+      'true',
+    );
+    await page.evaluate(() => {
+      window.__loadingClicks = 0;
+      document.querySelector('zen-button[loading] button')
+        .addEventListener('click', () => window.__loadingClicks++);
+    });
+    await page.locator('zen-button[loading] button').evaluate(
+      button => button.click(),
+    );
+    assert.equal(await page.evaluate(() => window.__loadingClicks), 0);
+    await page.locator('zen-button[loading] button').focus();
+    assert.equal(
+      await page.locator('zen-button[loading] button').evaluate(
+        button => document.activeElement === button,
+      ),
+      true,
+    );
+    await page.keyboard.press('Tab');
+    assert.equal(
+      await page.locator('zen-button[loading] button').evaluate(
+        button => document.activeElement === button,
+      ),
+      false,
+    );
+    const nativeDisabledPreserved = await page.evaluate(async () => {
+      const wrapper = document.createElement('zen-button');
+      const button = document.createElement('button');
+      button.disabled = true;
+      button.textContent = 'Native disabled';
+      wrapper.append(button);
+      document.body.append(wrapper);
+      await customElements.whenDefined('zen-button');
+      wrapper.setAttribute('disabled', '');
+      wrapper.removeAttribute('disabled');
+      return button.disabled;
+    });
+    assert.equal(nativeDisabledPreserved, true);
+    const replacementButtonState = await page.evaluate(async () => {
+      const wrapper = document.createElement('zen-button');
+      wrapper.setAttribute('disabled', '');
+      const first = document.createElement('button');
+      first.textContent = 'First';
+      wrapper.append(first);
+      document.body.append(wrapper);
+      await new Promise(resolve => setTimeout(resolve));
+      const second = document.createElement('button');
+      second.textContent = 'Second';
+      wrapper.replaceChildren(second);
+      await new Promise(resolve => setTimeout(resolve));
+      return {
+        firstDisabled: first.disabled,
+        secondAriaDisabled: second.getAttribute('aria-disabled'),
+        wrapperInert: wrapper.hasAttribute('inert'),
+      };
+    });
+    assert.equal(replacementButtonState.firstDisabled, false);
+    assert.equal(replacementButtonState.secondAriaDisabled, 'true');
+    assert.equal(replacementButtonState.wrapperInert, false);
+    const modifiedClickAllowed = await page.evaluate(() => {
+      const link = document.querySelector(
+        '.catalog-sidebar a[href="/components/card"]',
+      );
+      return link.dispatchEvent(new MouseEvent('click', {
+        bubbles: true,
+        cancelable: true,
+        button: 0,
+        ctrlKey: true,
+      }));
+    });
+    assert.equal(modifiedClickAllowed, true);
+
+    await page.goto(`${baseUrl}/components/field`, {
+      waitUntil: 'networkidle',
+    });
+    const fieldWiring = await page.locator('zen-field').first().evaluate(field => {
+      const label = field.querySelector('label');
+      const input = field.querySelector('input');
+      const help = field.querySelector('small');
+      input.checkValidity();
+      return {
+        describedBy: input.getAttribute('aria-describedby'),
+        helpId: help.id,
+        inputId: input.id,
+        invalid: field.hasAttribute('data-invalid'),
+        labelFor: label.htmlFor,
+      };
+    });
+    assert.ok(fieldWiring.inputId);
+    assert.equal(fieldWiring.labelFor, fieldWiring.inputId);
+    assert.equal(fieldWiring.describedBy, fieldWiring.helpId);
+    assert.equal(fieldWiring.invalid, true);
+    const replacementField = await page.locator('zen-field').first().evaluate(
+      async field => {
+        const label = field.querySelector('label');
+        const oldInput = field.querySelector('input');
+        const blocker = document.createElement('div');
+        blocker.id = 'zen-field-3';
+        document.body.append(blocker);
+        const nextInput = document.createElement('input');
+        nextInput.required = true;
+        oldInput.replaceWith(nextInput);
+        await new Promise(resolve => setTimeout(resolve));
+        oldInput.dispatchEvent(new Event('invalid'));
+        const help = field.querySelector('small');
+        nextInput.id = 'replacement-email';
+        help.id = 'replacement-help';
+        await new Promise(resolve => setTimeout(resolve));
+        return {
+          describedBy: nextInput.getAttribute('aria-describedby'),
+          helpId: help.id,
+          labelFor: label.htmlFor,
+          nextId: nextInput.id,
+          blockerId: blocker.id,
+        };
+      },
+    );
+    assert.ok(replacementField.nextId);
+    assert.notEqual(replacementField.nextId, replacementField.blockerId);
+    assert.equal(replacementField.labelFor, replacementField.nextId);
+    assert.equal(replacementField.describedBy, replacementField.helpId);
+
+    await page.goto(`${baseUrl}/components/alert`, {
+      waitUntil: 'networkidle',
+    });
+    await page.evaluate(() => {
+      window.__dismissed = 0;
+      document.addEventListener('zen-dismiss', () => {
+        window.__dismissed++;
+      });
+    });
+    const dismissible = page.locator('zen-alert[dismissible]');
+    assert.equal(await dismissible.getAttribute('role'), 'alert');
+    await dismissible.locator('[data-zen-dismiss]').click();
+    assert.equal(await dismissible.count(), 0);
+    assert.equal(await page.evaluate(() => window.__dismissed), 1);
+    const authoredDismissState = await page.evaluate(async () => {
+      const alert = document.createElement('zen-alert');
+      alert.setAttribute('dismissible', '');
+      const message = document.createElement('p');
+      message.textContent = 'Authored dismiss control';
+      const dismiss = document.createElement('button');
+      dismiss.dataset.zenDismiss = '';
+      alert.append(message, dismiss);
+      document.body.append(alert);
+      await new Promise(resolve => setTimeout(resolve));
+      let events = 0;
+      alert.addEventListener('zen-dismiss', () => events++);
+      alert.removeAttribute('dismissible');
+      dismiss.click();
+      return {
+        connected: alert.isConnected,
+        events,
+      };
+    });
+    assert.equal(authoredDismissState.connected, true);
+    assert.equal(authoredDismissState.events, 0);
+
+    const lightCanvas = await page.evaluate(() =>
+      getComputedStyle(document.documentElement)
+        .getPropertyValue('--zen-color-canvas')
+        .trim()
+    );
+    await page.locator('#themeToggle').click();
+    const darkCanvas = await page.evaluate(() =>
+      getComputedStyle(document.documentElement)
+        .getPropertyValue('--zen-color-canvas')
+        .trim()
+    );
+    assert.notEqual(lightCanvas, darkCanvas);
+    assert.equal(
+      await page.evaluate(() => localStorage.getItem('zen-theme')),
+      'dark',
+    );
+
+    assert.deepEqual(errors, []);
+    await context.close();
+
+    const darkContext = await browser.newContext({ colorScheme: 'dark' });
+    const darkPage = await darkContext.newPage();
+    await darkPage.goto(baseUrl, { waitUntil: 'networkidle' });
+    assert.equal(
+      await darkPage.locator('#themeToggle').getAttribute('aria-pressed'),
+      'true',
+    );
+    const systemDarkCanvas = await darkPage.evaluate(() =>
+      getComputedStyle(document.documentElement)
+        .getPropertyValue('--zen-color-canvas')
+        .trim()
+    );
+    await darkPage.locator('#themeToggle').click();
+    const explicitLightCanvas = await darkPage.evaluate(() =>
+      getComputedStyle(document.documentElement)
+        .getPropertyValue('--zen-color-canvas')
+        .trim()
+    );
+    assert.notEqual(systemDarkCanvas, explicitLightCanvas);
+    await darkContext.close();
+  } finally {
+    if (browser) await browser.close();
+    await stopProcess(server);
+  }
+})().catch(error => {
+  console.error(error.stack || error);
+  process.exitCode = 1;
+});
--- a/seobeo/s_network.c	Tue Aug 04 06:23:37 2026 -0700
+++ b/seobeo/s_network.c	Tue Aug 04 09:14:57 2026 -0700
@@ -41,7 +41,8 @@
   pthread_once(&g_sigpipe_once, Seobeo_Process_Ignore_Sigpipe);
   Seobeo_Handle *p_handle;
   struct addrinfo hints, *server_infos, *free_server_info;
-  int32 socket_fd, yes = 1;  // Need this for setsockopt 
+  int32 socket_fd = -1;
+  int32 yes = 1;
 
   memset(&hints, 0, sizeof hints);
   hints.ai_family   = AF_UNSPEC;
@@ -70,20 +71,39 @@
     Seobeo_Socket_Disable_Sigpipe(socket_fd);
 
      if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
-     { perror("setsockopt SO_REUSEADDR"); continue; }
+     {
+       perror("setsockopt SO_REUSEADDR");
+       close(socket_fd);
+       socket_fd = -1;
+       continue;
+     }
 
 #ifdef SO_REUSEPORT
      // SO_REUSEPORT allows multiple threads/processes to bind to the same port
      // The kernel will distribute incoming connections among them
      if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)) == -1)
-     { perror("setsockopt SO_REUSEPORT"); continue; }
+     {
+       perror("setsockopt SO_REUSEPORT");
+       close(socket_fd);
+       socket_fd = -1;
+       continue;
+     }
 #endif
 
      if (bind(socket_fd, free_server_info->ai_addr, free_server_info->ai_addrlen) == -1)
-     { perror("v_network: Couldn't make socket non-blocking\n"); continue; }
+     {
+       perror("bind");
+       close(socket_fd);
+       socket_fd = -1;
+       continue;
+     }
 
      break;
   }
+  freeaddrinfo(server_infos);
+
+  if (socket_fd < 0)
+    return NULL;
 
   if (listen(socket_fd, 16) != 0)
   { 
@@ -92,15 +112,25 @@
   }
 
   int flags = fcntl(socket_fd, F_GETFL, 0);
-	if(fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK) != 0) { perror("fcntl"); return NULL; }
-  freeaddrinfo(server_infos);
+  if (flags < 0 ||
+      fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK) != 0)
+  {
+    perror("fcntl");
+    close(socket_fd);
+    return NULL;
+  }
 
-  p_handle = malloc(sizeof(*p_handle));
+  p_handle = calloc(1, sizeof(*p_handle));
+  if (!p_handle)
+  {
+    close(socket_fd);
+    return NULL;
+  }
   p_handle->socket = socket_fd;
   p_handle->type = SEOBEO_STREAM_TYPE_SERVER;
   p_handle->connected = FALSE;
 
-  p_handle->host = host != NULL ? strdup(host) : "localhost";
+  p_handle->host = strdup(host != NULL ? host : "localhost");
   p_handle->port = strdup(port);
 
   p_handle->ssl_ctx              = NULL;
@@ -117,6 +147,15 @@
 
   p_handle->destroyed = FALSE;
 
+  if (!p_handle->host ||
+      !p_handle->port ||
+      !p_handle->read_buffer ||
+      !p_handle->write_buffer)
+  {
+    Seobeo_Handle_Destroy(p_handle);
+    return NULL;
+  }
+
   return p_handle;
 }
 
@@ -258,7 +297,7 @@
 
   Seobeo_SSL_Cleanup(p_handle);
 
-  if (p_handle->socket) {
+  if (p_handle->socket >= 0) {
     Seobeo_Log(SEOBEO_DEBUG, "Closing handle socket: %d\n", p_handle->socket);
     close(p_handle->socket);
   }
--- a/seobeo/s_web.c	Tue Aug 04 06:23:37 2026 -0700
+++ b/seobeo/s_web.c	Tue Aug 04 09:14:57 2026 -0700
@@ -4,6 +4,27 @@
 
 static char g_folder_path[512] = ".";
 
+static boolean Seobeo_Static_Path_Is_Safe(const char *path)
+{
+  if (!path || strchr(path, '\\'))
+    return FALSE;
+  const char *segment = path;
+  while (*segment)
+  {
+    while (*segment == '/')
+      segment++;
+    const char *end = segment;
+    while (*end && *end != '/')
+      end++;
+    if ((size_t)(end - segment) == 2 &&
+        segment[0] == '.' &&
+        segment[1] == '.')
+      return FALSE;
+    segment = end;
+  }
+  return TRUE;
+}
+
 static char *canonical_request_header(char *header)
 {
   if (strcasecmp(header, "content-length") == 0) return "Content-Length";
@@ -241,6 +262,22 @@
   // --- Static files fallback for GET (use original large arena logic) ---
   if (strcmp(method, "GET") == 0)
   {
+    if (!Seobeo_Static_Path_Is_Safe(path))
+    {
+      Seobeo_Web_Header_Generate_KeepAlive(
+          p_response_header,
+          HTTP_BAD_REQUEST,
+          "text/plain",
+          0,
+          should_keep_alive);
+      Seobeo_Handle_Queue(
+          p_cli_handle,
+          (const uint8 *)p_response_header,
+          (uint32)strlen(p_response_header));
+      Seobeo_Handle_Flush(p_cli_handle);
+      goto clean_up_arenas;
+    }
+
     char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)5 * 1024);
 
     if (!path || strcmp(path, "/") == 0)
@@ -261,27 +298,10 @@
         strcpy(file_path, path);
     }
 
-    void *p_file_kv = Dowa_HashMap_Get_Ptr(p_html_cache, file_path);
-    const char *file_content = NULL;
+    char *file_content = NULL;
     size_t body_size = 0;
-
-    if (p_file_kv)
-    {
-      Seobeo_Cached_File *cached = ((Seobeo_Cache_Entry*)p_file_kv)->value;
-      file_content = cached->content;
-      body_size = cached->size;
-    }
-    else
-    {
-      file_content = Seobeo_Web_LoadFile(file_path, &body_size);
-      if (file_content)
-      {
-        Seobeo_Cached_File *cached = malloc(sizeof(Seobeo_Cached_File));
-        cached->content = (char*)file_content;
-        cached->size = body_size;
-        Dowa_HashMap_Push(p_html_cache, file_path, cached);
-      }
-    }
+    (void)p_html_cache;
+    file_content = Seobeo_Web_LoadFile(file_path, &body_size);
 
     if (!file_content)
     {
@@ -336,6 +356,7 @@
                         (const uint8*)file_content,
                         (uint32)body_size);
     Seobeo_Handle_Flush(p_cli_handle);
+    free(file_content);
   }
   else
   {
@@ -600,14 +621,34 @@
     Seobeo_ServerMode mode,
     int                thread_count)
 {
+  return Seobeo_Web_Server_Start_On(
+      NULL,
+      folder_path,
+      port,
+      mode,
+      thread_count);
+}
+
+int Seobeo_Web_Server_Start_On(
+    const char       *host,
+    const char       *folder_path,
+    const char       *port,
+    Seobeo_ServerMode mode,
+    int                thread_count)
+{
   if (folder_path)
     strncpy(g_folder_path, folder_path, sizeof(g_folder_path) - 1);
 
   Seobeo_Cache_Entry *p_html_cache = NULL;
 
   Seobeo_Handle *p_server_handle =
-    Seobeo_Stream_Handle_Server_Create(NULL, port);
-  if (p_server_handle->socket < 0) return 1;
+    Seobeo_Stream_Handle_Server_Create(host, port);
+  if (!p_server_handle || p_server_handle->socket < 0)
+  {
+    if (p_server_handle)
+      Seobeo_Handle_Destroy(p_server_handle);
+    return 1;
+  }
 
   Seobeo_Log(SEOBEO_INFO, "Listening on port %s\n", port);
 
--- a/seobeo/seobeo.h	Tue Aug 04 06:23:37 2026 -0700
+++ b/seobeo/seobeo.h	Tue Aug 04 09:14:57 2026 -0700
@@ -72,6 +72,8 @@
 extern void           Seobeo_Web_Header_Generate_KeepAlive(void *buffer, int status, const char *content_type, const int content_length, boolean keep_alive);
 /* Start a Generic HTTP static file server with given folder. It will store folder into memory. */
 extern int            Seobeo_Web_Server_Start(const char *folder_path, const char *port, Seobeo_ServerMode mode, int thread_count);
+/* Start a web server bound to a specific host. Pass NULL to bind all interfaces. */
+extern int            Seobeo_Web_Server_Start_On(const char *host, const char *folder_path, const char *port, Seobeo_ServerMode mode, int thread_count);
 /* Generic HTTP GET Rquest to given host and port with path. It will mimic chrome. */
 extern int            Seobeo_Web_Client_Get(const char *host, const char *port, const char *path);
 
--- a/seobeo/tests/BUILD	Tue Aug 04 06:23:37 2026 -0700
+++ b/seobeo/tests/BUILD	Tue Aug 04 09:14:57 2026 -0700
@@ -82,3 +82,13 @@
   timeout = "short",
   visibility = ["//visibility:public"],
 )
+
+cc_test(
+  name = "seobeo_server_bind_test",
+  srcs = ["seobeo_server_bind_test.c"],
+  deps = ["//seobeo:seobeo"],
+  size = "small",
+  timeout = "short",
+  target_compatible_with = ["@platforms//os:linux"],
+  visibility = ["//visibility:public"],
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/seobeo/tests/seobeo_server_bind_test.c	Tue Aug 04 09:14:57 2026 -0700
@@ -0,0 +1,59 @@
+#include "seobeo/seobeo.h"
+
+#include <assert.h>
+#include <dirent.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+static int open_descriptor_count(void)
+{
+  DIR *directory = opendir("/proc/self/fd");
+  assert(directory);
+  int count = 0;
+  struct dirent *entry;
+  while ((entry = readdir(directory)) != NULL)
+  {
+    if (entry->d_name[0] != '.')
+      count++;
+  }
+  closedir(directory);
+  return count;
+}
+
+int main(void)
+{
+  int listener = socket(AF_INET, SOCK_STREAM, 0);
+  assert(listener >= 0);
+
+  struct sockaddr_in address = {
+    .sin_family = AF_INET,
+    .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+    .sin_port = 0,
+  };
+  assert(bind(
+      listener,
+      (struct sockaddr *)&address,
+      sizeof(address)) == 0);
+  assert(listen(listener, 1) == 0);
+
+  socklen_t address_length = sizeof(address);
+  assert(getsockname(
+      listener,
+      (struct sockaddr *)&address,
+      &address_length) == 0);
+  char port[16];
+  snprintf(port, sizeof(port), "%u", ntohs(address.sin_port));
+
+  int before = open_descriptor_count();
+  Seobeo_Handle *server =
+      Seobeo_Stream_Handle_Server_Create("127.0.0.1", port);
+  assert(server == NULL);
+  assert(open_descriptor_count() == before);
+
+  assert(Seobeo_Stream_Handle_Server_Create(
+      "127.0.0.1",
+      "not-a-port") == NULL);
+  close(listener);
+  return 0;
+}