feat(web): add snapshot tests for all story files

Quentin Gliech and Claude Opus 4.6 (1M context) created

Every story file now has a corresponding snapshot test using the
portable stories API. The pattern auto-generates a test per exported
story β€” adding a new story automatically adds a snapshot test.

63 snapshot tests across 18 component files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Change summary

webui2/src/components/bugs/IssueRow.test.tsx                          |  13 
webui2/src/components/bugs/LabelBadge.test.tsx                        |  13 
webui2/src/components/bugs/StatusBadge.test.tsx                       |  13 
webui2/src/components/bugs/__snapshots__/IssueRow.test.tsx.snap       | 444 
webui2/src/components/bugs/__snapshots__/LabelBadge.test.tsx.snap     |  90 
webui2/src/components/bugs/__snapshots__/StatusBadge.test.tsx.snap    |  67 
webui2/src/components/code/CodeBreadcrumb.test.tsx                    |  13 
webui2/src/components/code/FileTree.test.tsx                          |  13 
webui2/src/components/code/FileViewer.test.tsx                        |  13 
webui2/src/components/code/RefSelector.test.tsx                       |  13 
webui2/src/components/code/__snapshots__/CodeBreadcrumb.test.tsx.snap | 305 
webui2/src/components/code/__snapshots__/FileTree.test.tsx.snap       | 423 
webui2/src/components/code/__snapshots__/FileViewer.test.tsx.snap     | 346 
webui2/src/components/code/__snapshots__/RefSelector.test.tsx.snap    | 187 
webui2/src/components/content/Markdown.test.tsx                       |  13 
webui2/src/components/content/__snapshots__/Markdown.test.tsx.snap    | 413 
webui2/src/components/ui/__snapshots__/avatar.test.tsx.snap           |  57 
webui2/src/components/ui/__snapshots__/badge.test.tsx.snap            |  41 
webui2/src/components/ui/__snapshots__/input.test.tsx.snap            |  39 
webui2/src/components/ui/__snapshots__/query-input.test.tsx.snap      | 266 
webui2/src/components/ui/__snapshots__/separator.test.tsx.snap        |  49 
webui2/src/components/ui/__snapshots__/skeleton.test.tsx.snap         |  39 
webui2/src/components/ui/__snapshots__/status-tabs.test.tsx.snap      |  76 
webui2/src/components/ui/__snapshots__/textarea.test.tsx.snap         |  30 
webui2/src/components/ui/__snapshots__/write-preview.test.tsx.snap    |  86 
webui2/src/components/ui/avatar.test.tsx                              |  13 
webui2/src/components/ui/badge.test.tsx                               |  13 
webui2/src/components/ui/input.test.tsx                               |  13 
webui2/src/components/ui/query-input.test.tsx                         |  13 
webui2/src/components/ui/separator.test.tsx                           |  13 
webui2/src/components/ui/skeleton.test.tsx                            |  13 
webui2/src/components/ui/status-tabs.test.tsx                         |  13 
webui2/src/components/ui/textarea.test.tsx                            |  13 
webui2/src/components/ui/write-preview.test.tsx                       |  13 
34 files changed, 3,179 insertions(+)

Detailed changes

webui2/src/components/bugs/IssueRow.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./IssueRow.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`IssueRow/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/bugs/LabelBadge.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./LabelBadge.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`LabelBadge/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/bugs/StatusBadge.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./StatusBadge.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`StatusBadge/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/bugs/__snapshots__/IssueRow.test.tsx.snap πŸ”—

@@ -0,0 +1,444 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`IssueRow/ClosedIssue matches snapshot 1`] = `
+<div>
+  <div
+    class="border-border flex items-start gap-3 border-b px-4 py-3 last:border-0"
+  >
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-circle-check mt-0.5 size-4 shrink-0 text-purple-600 dark:text-purple-400"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <circle
+        cx="12"
+        cy="12"
+        r="10"
+      />
+      <path
+        d="m9 12 2 2 4-4"
+      />
+    </svg>
+    <div
+      class="min-w-0 flex-1"
+    >
+      <div
+        class="flex flex-wrap items-baseline gap-2"
+      >
+        <a
+          class="text-foreground hover:text-primary font-medium hover:underline"
+          href="#"
+        >
+          Add dark mode support
+        </a>
+        <span
+          class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+          style="background-color: rgb(163, 230, 53); color: rgba(0, 0, 0, 0.75);"
+        >
+          enhancement
+        </span>
+      </div>
+      <p
+        class="text-muted-foreground mt-0.5 text-xs"
+      >
+        #d4e5f6 opened 
+        about 1 hour ago
+      </p>
+    </div>
+    <div
+      class="text-muted-foreground flex shrink-0 items-center gap-1 text-xs"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-message-square size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+        />
+      </svg>
+      12
+    </div>
+  </div>
+</div>
+`;
+
+exports[`IssueRow/List matches snapshot 1`] = `
+<div>
+  <div
+    class="border-border rounded-md border"
+  >
+    <div
+      class="border-border flex items-start gap-3 border-b px-4 py-3 last:border-0 hover:bg-muted/30"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-circle-dot mt-0.5 size-4 shrink-0 text-green-600 dark:text-green-400"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <circle
+          cx="12"
+          cy="12"
+          r="10"
+        />
+        <circle
+          cx="12"
+          cy="12"
+          r="1"
+        />
+      </svg>
+      <div
+        class="min-w-0 flex-1"
+      >
+        <div
+          class="flex flex-wrap items-baseline gap-2"
+        >
+          <a
+            class="text-foreground hover:text-primary font-medium hover:underline"
+            href="#"
+          >
+            Fix login page crash on empty email
+          </a>
+          <span
+            class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+            style="background-color: rgb(252, 41, 41); color: rgba(255, 255, 255, 0.9);"
+          >
+            bug
+          </span>
+        </div>
+        <p
+          class="text-muted-foreground mt-0.5 text-xs"
+        >
+          #a1b2c3 opened 
+          about 1 hour ago
+           by Jane Doe
+        </p>
+      </div>
+      <div
+        class="text-muted-foreground flex shrink-0 items-center gap-1 text-xs"
+      >
+        <svg
+          aria-hidden="true"
+          class="lucide lucide-message-square size-3.5"
+          fill="none"
+          height="24"
+          stroke="currentColor"
+          stroke-linecap="round"
+          stroke-linejoin="round"
+          stroke-width="2"
+          viewBox="0 0 24 24"
+          width="24"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+          />
+        </svg>
+        3
+      </div>
+    </div>
+    <div
+      class="border-border flex items-start gap-3 border-b px-4 py-3 last:border-0 hover:bg-muted/30"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-circle-dot mt-0.5 size-4 shrink-0 text-green-600 dark:text-green-400"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <circle
+          cx="12"
+          cy="12"
+          r="10"
+        />
+        <circle
+          cx="12"
+          cy="12"
+          r="1"
+        />
+      </svg>
+      <div
+        class="min-w-0 flex-1"
+      >
+        <div
+          class="flex flex-wrap items-baseline gap-2"
+        >
+          <a
+            class="text-foreground hover:text-primary font-medium hover:underline"
+            href="#"
+          >
+            Add dark mode support
+          </a>
+          <span
+            class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+            style="background-color: rgb(163, 230, 53); color: rgba(0, 0, 0, 0.75);"
+          >
+            enhancement
+          </span>
+        </div>
+        <p
+          class="text-muted-foreground mt-0.5 text-xs"
+        >
+          #d4e5f6 opened 
+          about 1 hour ago
+           by Bob
+        </p>
+      </div>
+    </div>
+    <div
+      class="border-border flex items-start gap-3 border-b px-4 py-3 last:border-0 hover:bg-muted/30"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-circle-check mt-0.5 size-4 shrink-0 text-purple-600 dark:text-purple-400"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <circle
+          cx="12"
+          cy="12"
+          r="10"
+        />
+        <path
+          d="m9 12 2 2 4-4"
+        />
+      </svg>
+      <div
+        class="min-w-0 flex-1"
+      >
+        <div
+          class="flex flex-wrap items-baseline gap-2"
+        >
+          <a
+            class="text-foreground hover:text-primary font-medium hover:underline"
+            href="#"
+          >
+            Update dependencies
+          </a>
+        </div>
+        <p
+          class="text-muted-foreground mt-0.5 text-xs"
+        >
+          #g7h8i9 opened 
+          about 1 hour ago
+           by Alice
+        </p>
+      </div>
+      <div
+        class="text-muted-foreground flex shrink-0 items-center gap-1 text-xs"
+      >
+        <svg
+          aria-hidden="true"
+          class="lucide lucide-message-square size-3.5"
+          fill="none"
+          height="24"
+          stroke="currentColor"
+          stroke-linecap="round"
+          stroke-linejoin="round"
+          stroke-width="2"
+          viewBox="0 0 24 24"
+          width="24"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+          />
+        </svg>
+        7
+      </div>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`IssueRow/NoLabelsNoComments matches snapshot 1`] = `
+<div>
+  <div
+    class="border-border flex items-start gap-3 border-b px-4 py-3 last:border-0"
+  >
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-circle-dot mt-0.5 size-4 shrink-0 text-green-600 dark:text-green-400"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <circle
+        cx="12"
+        cy="12"
+        r="10"
+      />
+      <circle
+        cx="12"
+        cy="12"
+        r="1"
+      />
+    </svg>
+    <div
+      class="min-w-0 flex-1"
+    >
+      <div
+        class="flex flex-wrap items-baseline gap-2"
+      >
+        <a
+          class="text-foreground hover:text-primary font-medium hover:underline"
+          href="#"
+        >
+          Simple issue with no labels
+        </a>
+      </div>
+      <p
+        class="text-muted-foreground mt-0.5 text-xs"
+      >
+        #abc123 opened 
+        about 1 hour ago
+         by 
+        <a
+          class="hover:underline"
+          href="#"
+        >
+          Bob
+        </a>
+      </p>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`IssueRow/OpenIssue matches snapshot 1`] = `
+<div>
+  <div
+    class="border-border flex items-start gap-3 border-b px-4 py-3 last:border-0 hover:bg-muted/30"
+  >
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-circle-dot mt-0.5 size-4 shrink-0 text-green-600 dark:text-green-400"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <circle
+        cx="12"
+        cy="12"
+        r="10"
+      />
+      <circle
+        cx="12"
+        cy="12"
+        r="1"
+      />
+    </svg>
+    <div
+      class="min-w-0 flex-1"
+    >
+      <div
+        class="flex flex-wrap items-baseline gap-2"
+      >
+        <a
+          class="text-foreground hover:text-primary font-medium hover:underline"
+          href="#"
+        >
+          Fix login page crash on empty email
+        </a>
+        <span
+          class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+          style="background-color: rgb(252, 41, 41); color: rgba(255, 255, 255, 0.9);"
+        >
+          bug
+        </span>
+        <span
+          class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+          style="background-color: rgb(255, 152, 0); color: rgba(0, 0, 0, 0.75);"
+        >
+          priority
+        </span>
+      </div>
+      <p
+        class="text-muted-foreground mt-0.5 text-xs"
+      >
+        #a1b2c3 opened 
+        about 1 hour ago
+         by 
+        <a
+          class="hover:underline"
+          href="#"
+        >
+          Jane Doe
+        </a>
+      </p>
+    </div>
+    <div
+      class="text-muted-foreground flex shrink-0 items-center gap-1 text-xs"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-message-square size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+        />
+      </svg>
+      3
+    </div>
+  </div>
+</div>
+`;

webui2/src/components/bugs/__snapshots__/LabelBadge.test.tsx.snap πŸ”—

@@ -0,0 +1,90 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`LabelBadge/AllColors matches snapshot 1`] = `
+<div>
+  <div
+    class="flex flex-wrap gap-2"
+  >
+    <span
+      class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+      style="background-color: rgb(252, 41, 41); color: rgba(255, 255, 255, 0.9);"
+    >
+      bug
+    </span>
+    <span
+      class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+      style="background-color: rgb(163, 230, 53); color: rgba(0, 0, 0, 0.75);"
+    >
+      enhancement
+    </span>
+    <span
+      class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+      style="background-color: rgb(30, 80, 160); color: rgba(255, 255, 255, 0.9);"
+    >
+      documentation
+    </span>
+    <span
+      class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+      style="background-color: rgb(0, 150, 136); color: rgba(255, 255, 255, 0.9);"
+    >
+      help wanted
+    </span>
+    <span
+      class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+      style="background-color: rgb(200, 200, 200); color: rgba(0, 0, 0, 0.75);"
+    >
+      wontfix
+    </span>
+    <span
+      class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+      style="background-color: rgb(255, 152, 0); color: rgba(0, 0, 0, 0.75);"
+    >
+      priority
+    </span>
+  </div>
+</div>
+`;
+
+exports[`LabelBadge/Clickable matches snapshot 1`] = `
+<div>
+  <span
+    class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+    style="background-color: rgb(100, 200, 150); color: rgba(0, 0, 0, 0.75);"
+  >
+    feature
+  </span>
+</div>
+`;
+
+exports[`LabelBadge/DarkBackground matches snapshot 1`] = `
+<div>
+  <span
+    class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+    style="background-color: rgb(30, 80, 160); color: rgba(255, 255, 255, 0.9);"
+  >
+    documentation
+  </span>
+</div>
+`;
+
+exports[`LabelBadge/Default matches snapshot 1`] = `
+<div>
+  <span
+    class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+    style="background-color: rgb(252, 41, 41); color: rgba(255, 255, 255, 0.9);"
+  >
+    bug
+  </span>
+</div>
+`;
+
+exports[`LabelBadge/LightBackground matches snapshot 1`] = `
+<div>
+  <span
+    class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium "
+    style="background-color: rgb(163, 230, 53); color: rgba(0, 0, 0, 0.75);"
+  >
+    enhancement
+  </span>
+</div>
+`;

webui2/src/components/bugs/__snapshots__/StatusBadge.test.tsx.snap πŸ”—

@@ -0,0 +1,67 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`StatusBadge/Closed matches snapshot 1`] = `
+<div>
+  <span
+    class="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
+  >
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-circle-check size-3"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <circle
+        cx="12"
+        cy="12"
+        r="10"
+      />
+      <path
+        d="m9 12 2 2 4-4"
+      />
+    </svg>
+    Closed
+  </span>
+</div>
+`;
+
+exports[`StatusBadge/Open matches snapshot 1`] = `
+<div>
+  <span
+    class="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
+  >
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-circle-dot size-3"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <circle
+        cx="12"
+        cy="12"
+        r="10"
+      />
+      <circle
+        cx="12"
+        cy="12"
+        r="1"
+      />
+    </svg>
+    Open
+  </span>
+</div>
+`;

webui2/src/components/code/CodeBreadcrumb.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./CodeBreadcrumb.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`CodeBreadcrumb/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/code/FileTree.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./FileTree.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`FileTree/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/code/FileViewer.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./FileViewer.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`FileViewer/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/code/RefSelector.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./RefSelector.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`RefSelector/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/code/__snapshots__/CodeBreadcrumb.test.tsx.snap πŸ”—

@@ -0,0 +1,305 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`CodeBreadcrumb/DeepPath matches snapshot 1`] = `
+<div>
+  <div
+    class="flex flex-wrap items-center gap-1 font-mono text-sm"
+  >
+    <a
+      class="text-foreground font-medium hover:underline"
+      href="/git-bug/tree/feature%2Fauth"
+    >
+      git-bug
+    </a>
+    <span
+      class="flex items-center gap-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-chevron-right text-muted-foreground size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m9 18 6-6-6-6"
+        />
+      </svg>
+      <a
+        class="text-muted-foreground hover:text-foreground hover:underline"
+        href="/git-bug/tree/feature%2Fauth/src"
+      >
+        src
+      </a>
+    </span>
+    <span
+      class="flex items-center gap-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-chevron-right text-muted-foreground size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m9 18 6-6-6-6"
+        />
+      </svg>
+      <a
+        class="text-muted-foreground hover:text-foreground hover:underline"
+        href="/git-bug/tree/feature%2Fauth/src/components"
+      >
+        components
+      </a>
+    </span>
+    <span
+      class="flex items-center gap-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-chevron-right text-muted-foreground size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m9 18 6-6-6-6"
+        />
+      </svg>
+      <a
+        class="text-muted-foreground hover:text-foreground hover:underline"
+        href="/git-bug/tree/feature%2Fauth/src/components/bugs"
+      >
+        bugs
+      </a>
+    </span>
+    <span
+      class="flex items-center gap-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-chevron-right text-muted-foreground size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m9 18 6-6-6-6"
+        />
+      </svg>
+      <a
+        class="text-muted-foreground hover:text-foreground hover:underline"
+        href="/git-bug/tree/feature%2Fauth/src/components/bugs/timeline"
+      >
+        timeline
+      </a>
+    </span>
+    <span
+      class="flex items-center gap-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-chevron-right text-muted-foreground size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m9 18 6-6-6-6"
+        />
+      </svg>
+      <span
+        class="text-foreground font-medium"
+      >
+        CommentItem.tsx
+      </span>
+    </span>
+    <span
+      class="text-muted-foreground ml-2 text-xs"
+    >
+      @ 
+      feature/auth
+    </span>
+  </div>
+</div>
+`;
+
+exports[`CodeBreadcrumb/FilePath matches snapshot 1`] = `
+<div>
+  <div
+    class="flex flex-wrap items-center gap-1 font-mono text-sm"
+  >
+    <a
+      class="text-foreground font-medium hover:underline"
+      href="/git-bug/tree/main"
+    >
+      git-bug
+    </a>
+    <span
+      class="flex items-center gap-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-chevron-right text-muted-foreground size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m9 18 6-6-6-6"
+        />
+      </svg>
+      <a
+        class="text-muted-foreground hover:text-foreground hover:underline"
+        href="/git-bug/tree/main/src"
+      >
+        src
+      </a>
+    </span>
+    <span
+      class="flex items-center gap-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-chevron-right text-muted-foreground size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m9 18 6-6-6-6"
+        />
+      </svg>
+      <a
+        class="text-muted-foreground hover:text-foreground hover:underline"
+        href="/git-bug/tree/main/src/components"
+      >
+        components
+      </a>
+    </span>
+    <span
+      class="flex items-center gap-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-chevron-right text-muted-foreground size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m9 18 6-6-6-6"
+        />
+      </svg>
+      <a
+        class="text-muted-foreground hover:text-foreground hover:underline"
+        href="/git-bug/tree/main/src/components/ui"
+      >
+        ui
+      </a>
+    </span>
+    <span
+      class="flex items-center gap-1"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-chevron-right text-muted-foreground size-3.5"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m9 18 6-6-6-6"
+        />
+      </svg>
+      <span
+        class="text-foreground font-medium"
+      >
+        button.tsx
+      </span>
+    </span>
+    <span
+      class="text-muted-foreground ml-2 text-xs"
+    >
+      @ 
+      main
+    </span>
+  </div>
+</div>
+`;
+
+exports[`CodeBreadcrumb/RootPath matches snapshot 1`] = `
+<div>
+  <div
+    class="flex flex-wrap items-center gap-1 font-mono text-sm"
+  >
+    <a
+      class="text-foreground font-medium hover:underline"
+      href="/git-bug/tree/main"
+    >
+      git-bug
+    </a>
+    <span
+      class="text-muted-foreground ml-2 text-xs"
+    >
+      @ 
+      main
+    </span>
+  </div>
+</div>
+`;

webui2/src/components/code/__snapshots__/FileTree.test.tsx.snap πŸ”—

@@ -0,0 +1,423 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`FileTree/RootDirectory matches snapshot 1`] = `
+<div>
+  <div
+    class="border-border overflow-hidden rounded-md border"
+  >
+    <table
+      class="w-full text-sm"
+    >
+      <tbody
+        class="divide-border divide-y"
+      >
+        <tr
+          class="hover:bg-muted/40"
+        >
+          <td
+            class="w-6 py-2 pl-4"
+          >
+            <svg
+              aria-hidden="true"
+              class="lucide lucide-folder size-4 text-blue-500 dark:text-blue-400"
+              fill="none"
+              height="24"
+              stroke="currentColor"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+              />
+            </svg>
+          </td>
+          <td
+            class="px-3 py-2"
+          >
+            <a
+              class="font-mono font-medium hover:underline"
+              href="/my-repo/tree/main/docs"
+            >
+              docs
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden max-w-xs truncate px-3 py-2 md:table-cell"
+          >
+            <a
+              class="hover:text-foreground hover:underline"
+              href="/my-repo/commit/def2"
+            >
+              docs: update getting started guide
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden px-4 py-2 text-right text-xs whitespace-nowrap md:table-cell"
+          >
+            2 days ago
+          </td>
+        </tr>
+        <tr
+          class="hover:bg-muted/40"
+        >
+          <td
+            class="w-6 py-2 pl-4"
+          >
+            <svg
+              aria-hidden="true"
+              class="lucide lucide-folder size-4 text-blue-500 dark:text-blue-400"
+              fill="none"
+              height="24"
+              stroke="currentColor"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+              />
+            </svg>
+          </td>
+          <td
+            class="px-3 py-2"
+          >
+            <a
+              class="font-mono font-medium hover:underline"
+              href="/my-repo/tree/main/src"
+            >
+              src
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden max-w-xs truncate px-3 py-2 md:table-cell"
+          >
+            <a
+              class="hover:text-foreground hover:underline"
+              href="/my-repo/commit/def1"
+            >
+              refactor: restructure source directory
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden px-4 py-2 text-right text-xs whitespace-nowrap md:table-cell"
+          >
+            1 day ago
+          </td>
+        </tr>
+        <tr
+          class="hover:bg-muted/40"
+        >
+          <td
+            class="w-6 py-2 pl-4"
+          >
+            <svg
+              aria-hidden="true"
+              class="lucide lucide-file text-muted-foreground size-4"
+              fill="none"
+              height="24"
+              stroke="currentColor"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"
+              />
+              <path
+                d="M14 2v5a1 1 0 0 0 1 1h5"
+              />
+            </svg>
+          </td>
+          <td
+            class="px-3 py-2"
+          >
+            <a
+              class="font-mono  hover:underline"
+              href="/my-repo/blob/main/.gitignore"
+            >
+              .gitignore
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden max-w-xs truncate px-3 py-2 md:table-cell"
+          />
+          <td
+            class="text-muted-foreground hidden px-4 py-2 text-right text-xs whitespace-nowrap md:table-cell"
+          />
+        </tr>
+        <tr
+          class="hover:bg-muted/40"
+        >
+          <td
+            class="w-6 py-2 pl-4"
+          >
+            <svg
+              aria-hidden="true"
+              class="lucide lucide-file text-muted-foreground size-4"
+              fill="none"
+              height="24"
+              stroke="currentColor"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"
+              />
+              <path
+                d="M14 2v5a1 1 0 0 0 1 1h5"
+              />
+            </svg>
+          </td>
+          <td
+            class="px-3 py-2"
+          >
+            <a
+              class="font-mono  hover:underline"
+              href="/my-repo/blob/main/package.json"
+            >
+              package.json
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden max-w-xs truncate px-3 py-2 md:table-cell"
+          >
+            <a
+              class="hover:text-foreground hover:underline"
+              href="/my-repo/commit/def4"
+            >
+              chore: bump dependencies
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden px-4 py-2 text-right text-xs whitespace-nowrap md:table-cell"
+          >
+            about 2 hours ago
+          </td>
+        </tr>
+        <tr
+          class="hover:bg-muted/40"
+        >
+          <td
+            class="w-6 py-2 pl-4"
+          >
+            <svg
+              aria-hidden="true"
+              class="lucide lucide-file text-muted-foreground size-4"
+              fill="none"
+              height="24"
+              stroke="currentColor"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"
+              />
+              <path
+                d="M14 2v5a1 1 0 0 0 1 1h5"
+              />
+            </svg>
+          </td>
+          <td
+            class="px-3 py-2"
+          >
+            <a
+              class="font-mono  hover:underline"
+              href="/my-repo/blob/main/README.md"
+            >
+              README.md
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden max-w-xs truncate px-3 py-2 md:table-cell"
+          >
+            <a
+              class="hover:text-foreground hover:underline"
+              href="/my-repo/commit/def3"
+            >
+              docs: add badges to README
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden px-4 py-2 text-right text-xs whitespace-nowrap md:table-cell"
+          >
+            about 1 hour ago
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</div>
+`;
+
+exports[`FileTree/SubDirectory matches snapshot 1`] = `
+<div>
+  <div
+    class="border-border overflow-hidden rounded-md border"
+  >
+    <table
+      class="w-full text-sm"
+    >
+      <tbody
+        class="divide-border divide-y"
+      >
+        <tr
+          class="hover:bg-muted/40"
+        >
+          <td
+            colspan="4"
+          >
+            <a
+              class="flex items-center gap-3 py-2 pl-4"
+              href="/my-repo/tree/main"
+            >
+              <svg
+                aria-hidden="true"
+                class="lucide lucide-folder size-4 text-blue-500 dark:text-blue-400"
+                fill="none"
+                height="24"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                viewBox="0 0 24 24"
+                width="24"
+                xmlns="http://www.w3.org/2000/svg"
+              >
+                <path
+                  d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+                />
+              </svg>
+              <span
+                class="text-muted-foreground font-mono"
+              >
+                ..
+              </span>
+            </a>
+          </td>
+        </tr>
+        <tr
+          class="hover:bg-muted/40"
+        >
+          <td
+            class="w-6 py-2 pl-4"
+          >
+            <svg
+              aria-hidden="true"
+              class="lucide lucide-folder size-4 text-blue-500 dark:text-blue-400"
+              fill="none"
+              height="24"
+              stroke="currentColor"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+              />
+            </svg>
+          </td>
+          <td
+            class="px-3 py-2"
+          >
+            <a
+              class="font-mono font-medium hover:underline"
+              href="/my-repo/tree/main/src/components"
+            >
+              components
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden max-w-xs truncate px-3 py-2 md:table-cell"
+          >
+            <a
+              class="hover:text-foreground hover:underline"
+              href="/my-repo/commit/def5"
+            >
+              feat: add button component
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden px-4 py-2 text-right text-xs whitespace-nowrap md:table-cell"
+          >
+            about 1 hour ago
+          </td>
+        </tr>
+        <tr
+          class="hover:bg-muted/40"
+        >
+          <td
+            class="w-6 py-2 pl-4"
+          >
+            <svg
+              aria-hidden="true"
+              class="lucide lucide-file text-muted-foreground size-4"
+              fill="none"
+              height="24"
+              stroke="currentColor"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              viewBox="0 0 24 24"
+              width="24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"
+              />
+              <path
+                d="M14 2v5a1 1 0 0 0 1 1h5"
+              />
+            </svg>
+          </td>
+          <td
+            class="px-3 py-2"
+          >
+            <a
+              class="font-mono  hover:underline"
+              href="/my-repo/blob/main/src/index.ts"
+            >
+              index.ts
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden max-w-xs truncate px-3 py-2 md:table-cell"
+          >
+            <a
+              class="hover:text-foreground hover:underline"
+              href="/my-repo/commit/def6"
+            >
+              fix: correct export paths
+            </a>
+          </td>
+          <td
+            class="text-muted-foreground hidden px-4 py-2 text-right text-xs whitespace-nowrap md:table-cell"
+          >
+            about 2 hours ago
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</div>
+`;

webui2/src/components/code/__snapshots__/FileViewer.test.tsx.snap πŸ”—

@@ -0,0 +1,346 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`FileViewer/BinaryFile matches snapshot 1`] = `
+<div>
+  <div
+    class="border-border overflow-hidden rounded-md border"
+  >
+    <div
+      class="border-border bg-muted/40 text-muted-foreground flex items-center justify-between border-b px-4 py-2 text-xs"
+    >
+      <span>
+        0
+         lines Β· 
+        24.0 KB
+      </span>
+      <button
+        class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground size-7"
+        title="Copy"
+      >
+        <svg
+          aria-hidden="true"
+          class="lucide lucide-copy size-3.5"
+          fill="none"
+          height="24"
+          stroke="currentColor"
+          stroke-linecap="round"
+          stroke-linejoin="round"
+          stroke-width="2"
+          viewBox="0 0 24 24"
+          width="24"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <rect
+            height="14"
+            rx="2"
+            ry="2"
+            width="14"
+            x="8"
+            y="8"
+          />
+          <path
+            d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
+          />
+        </svg>
+      </button>
+    </div>
+    <div
+      class="text-muted-foreground px-4 py-8 text-center text-sm"
+    >
+      Binary file β€” 
+      24.0 KB
+    </div>
+  </div>
+</div>
+`;
+
+exports[`FileViewer/Loading matches snapshot 1`] = `
+<div>
+  <div
+    class="divide-border border-border divide-y rounded-md border"
+  >
+    <div
+      class="flex items-center gap-2 px-4 py-2"
+    >
+      <div
+        class="animate-pulse rounded-md bg-primary/10 h-4 w-48"
+      />
+    </div>
+    <div
+      class="p-4"
+    >
+      <div
+        class="animate-pulse rounded-md bg-primary/10 h-64 w-full"
+      />
+    </div>
+  </div>
+</div>
+`;
+
+exports[`FileViewer/TruncatedFile matches snapshot 1`] = `
+<div>
+  <div
+    class="border-border overflow-hidden rounded-md border"
+  >
+    <div
+      class="border-border bg-muted/40 text-muted-foreground flex items-center justify-between border-b px-4 py-2 text-xs"
+    >
+      <span>
+        4
+         lines Β· 
+        1.0 MB
+         Β· truncated
+      </span>
+      <button
+        class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground size-7"
+        title="Copy"
+      >
+        <svg
+          aria-hidden="true"
+          class="lucide lucide-copy size-3.5"
+          fill="none"
+          height="24"
+          stroke="currentColor"
+          stroke-linecap="round"
+          stroke-linejoin="round"
+          stroke-width="2"
+          viewBox="0 0 24 24"
+          width="24"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <rect
+            height="14"
+            rx="2"
+            ry="2"
+            width="14"
+            x="8"
+            y="8"
+          />
+          <path
+            d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
+          />
+        </svg>
+      </button>
+    </div>
+    <div
+      class="flex overflow-x-auto font-mono text-xs leading-5"
+    >
+      <div
+        aria-hidden="true"
+        class="border-border bg-muted/20 text-muted-foreground/50 border-r px-4 py-4 text-right select-none"
+      >
+        <div>
+          1
+        </div>
+        <div>
+          2
+        </div>
+        <div>
+          3
+        </div>
+        <div>
+          4
+        </div>
+      </div>
+      <pre
+        class="flex-1 overflow-visible px-4 py-4"
+      >
+        <code
+          class="hljs !bg-transparent !p-0"
+        >
+          line 
+          <span
+            class="hljs-number"
+          >
+            1
+          </span>
+          
+line 
+          <span
+            class="hljs-number"
+          >
+            2
+          </span>
+          
+line 
+          <span
+            class="hljs-number"
+          >
+            3
+          </span>
+          
+... (truncated)
+        </code>
+      </pre>
+    </div>
+  </div>
+</div>
+`;
+
+exports[`FileViewer/TypeScriptFile matches snapshot 1`] = `
+<div>
+  <div
+    class="border-border overflow-hidden rounded-md border"
+  >
+    <div
+      class="border-border bg-muted/40 border-b px-4 py-2"
+    >
+      <div
+        class="animate-pulse rounded-md bg-primary/10 h-4 w-32"
+      />
+    </div>
+    <div
+      class="flex gap-4 p-4"
+    >
+      <div
+        class="space-y-1.5"
+      >
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5 w-6"
+        />
+      </div>
+      <div
+        class="flex-1 space-y-1.5"
+      >
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 70.27272538529849%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 84.39202253245477%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 36.70967368601532%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 87.50159030488214%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 51.86083821665096%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 46.479699116022175%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 36.75406000862486%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 60.27590888571913%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 38.67713014525041%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 86.91154246154659%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 77.40682922983703%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 82.74792087067682%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 61.364498248188944%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 48.028107813055215%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 61.27378677034177%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 50.634245170950734%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 40.67974048538118%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 87.04517503265906%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 31.598566849419893%;"
+        />
+        <div
+          class="animate-pulse rounded-md bg-primary/10 h-3.5"
+          style="width: 51.4887047773234%;"
+        />
+      </div>
+    </div>
+  </div>
+</div>
+`;

webui2/src/components/code/__snapshots__/RefSelector.test.tsx.snap πŸ”—

@@ -0,0 +1,187 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`RefSelector/BranchesOnly matches snapshot 1`] = `
+<div>
+  <button
+    aria-controls="radix-_r_2_"
+    aria-expanded="false"
+    aria-haspopup="dialog"
+    class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground h-8 rounded-md px-3 gap-2 font-mono text-xs"
+    data-state="closed"
+    type="button"
+  >
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-git-branch size-3.5"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M15 6a9 9 0 0 0-9 9V3"
+      />
+      <circle
+        cx="18"
+        cy="6"
+        r="3"
+      />
+      <circle
+        cx="6"
+        cy="18"
+        r="3"
+      />
+    </svg>
+    develop
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-chevrons-up-down text-muted-foreground size-3"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="m7 15 5 5 5-5"
+      />
+      <path
+        d="m7 9 5-5 5 5"
+      />
+    </svg>
+  </button>
+</div>
+`;
+
+exports[`RefSelector/Default matches snapshot 1`] = `
+<div>
+  <button
+    aria-controls="radix-_r_0_"
+    aria-expanded="false"
+    aria-haspopup="dialog"
+    class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground h-8 rounded-md px-3 gap-2 font-mono text-xs"
+    data-state="closed"
+    type="button"
+  >
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-git-branch size-3.5"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M15 6a9 9 0 0 0-9 9V3"
+      />
+      <circle
+        cx="18"
+        cy="6"
+        r="3"
+      />
+      <circle
+        cx="6"
+        cy="18"
+        r="3"
+      />
+    </svg>
+    main
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-chevrons-up-down text-muted-foreground size-3"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="m7 15 5 5 5-5"
+      />
+      <path
+        d="m7 9 5-5 5 5"
+      />
+    </svg>
+  </button>
+</div>
+`;
+
+exports[`RefSelector/OnTag matches snapshot 1`] = `
+<div>
+  <button
+    aria-controls="radix-_r_1_"
+    aria-expanded="false"
+    aria-haspopup="dialog"
+    class="inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground h-8 rounded-md px-3 gap-2 font-mono text-xs"
+    data-state="closed"
+    type="button"
+  >
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-git-branch size-3.5"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="M15 6a9 9 0 0 0-9 9V3"
+      />
+      <circle
+        cx="18"
+        cy="6"
+        r="3"
+      />
+      <circle
+        cx="6"
+        cy="18"
+        r="3"
+      />
+    </svg>
+    v1.1.0
+    <svg
+      aria-hidden="true"
+      class="lucide lucide-chevrons-up-down text-muted-foreground size-3"
+      fill="none"
+      height="24"
+      stroke="currentColor"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+      stroke-width="2"
+      viewBox="0 0 24 24"
+      width="24"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <path
+        d="m7 15 5 5 5-5"
+      />
+      <path
+        d="m7 9 5-5 5 5"
+      />
+    </svg>
+  </button>
+</div>
+`;

webui2/src/components/content/Markdown.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./Markdown.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`Markdown/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/content/__snapshots__/Markdown.test.tsx.snap πŸ”—

@@ -0,0 +1,413 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Markdown/BasicFormatting matches snapshot 1`] = `
+<div>
+  <div
+    class="prose prose-sm dark:prose-invert max-w-none prose-pre:bg-muted prose-pre:text-foreground prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded-sm prose-code:text-sm prose-code:before:content-none prose-code:after:content-none prose-img:inline prose-img:my-0"
+  >
+    <h1
+      id="heading-1"
+    >
+      Heading 1
+      <a
+        aria-hidden="true"
+        href="#heading-1"
+        tabindex="-1"
+      >
+        <span
+          class="icon icon-link"
+        />
+      </a>
+    </h1>
+    
+
+    <h2
+      id="heading-2"
+    >
+      Heading 2
+      <a
+        aria-hidden="true"
+        href="#heading-2"
+        tabindex="-1"
+      >
+        <span
+          class="icon icon-link"
+        />
+      </a>
+    </h2>
+    
+
+    <h3
+      id="heading-3"
+    >
+      Heading 3
+      <a
+        aria-hidden="true"
+        href="#heading-3"
+        tabindex="-1"
+      >
+        <span
+          class="icon icon-link"
+        />
+      </a>
+    </h3>
+    
+
+    <p>
+      This is a paragraph with 
+      <strong>
+        bold
+      </strong>
+      , 
+      <em>
+        italic
+      </em>
+      , and 
+      <code>
+        inline code
+      </code>
+      .
+    </p>
+    
+
+    <ul>
+      
+
+      <li>
+        Unordered list item 1
+      </li>
+      
+
+      <li>
+        Unordered list item 2
+
+        <ul>
+          
+
+          <li>
+            Nested item
+          </li>
+          
+
+        </ul>
+        
+
+      </li>
+      
+
+    </ul>
+    
+
+    <ol>
+      
+
+      <li>
+        Ordered list item 1
+      </li>
+      
+
+      <li>
+        Ordered list item 2
+      </li>
+      
+
+    </ol>
+    
+
+    <blockquote>
+      
+
+      <p>
+        This is a blockquote.
+      </p>
+      
+
+    </blockquote>
+    
+
+    <hr />
+    
+
+    <p>
+      <a
+        href="https://example.com"
+        rel="noopener noreferrer"
+        target="_blank"
+      >
+        A link
+      </a>
+    </p>
+  </div>
+</div>
+`;
+
+exports[`Markdown/CodeBlock matches snapshot 1`] = `
+<div>
+  <div
+    class="prose prose-sm dark:prose-invert max-w-none prose-pre:bg-muted prose-pre:text-foreground prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded-sm prose-code:text-sm prose-code:before:content-none prose-code:after:content-none prose-img:inline prose-img:my-0"
+  >
+    <p>
+      Here is a code block:
+    </p>
+    
+
+    <pre>
+      <code
+        class="language-typescript"
+      >
+        interface User {
+  id: string;
+  name: string;
+  email: string;
+}
+
+function greet(user: User): string {
+  return \`Hello, \${user.name}!\`;
+}
+
+      </code>
+    </pre>
+  </div>
+</div>
+`;
+
+exports[`Markdown/Comment matches snapshot 1`] = `
+<div>
+  <div
+    class="prose prose-sm dark:prose-invert max-w-none prose-pre:bg-muted prose-pre:text-foreground prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded-sm prose-code:text-sm prose-code:before:content-none prose-code:after:content-none prose-img:inline prose-img:my-0"
+  >
+    <p>
+      Fixed the issue by updating the query builder.
+    </p>
+    
+
+    <p>
+      The problem was that 
+      <code>
+        buildQuery()
+      </code>
+       wasn't escaping special characters in label names containing spaces.
+    </p>
+    
+
+    <pre>
+      <code
+        class="language-diff"
+      >
+        - const q = \`label:\${name}\`;
++ const q = \`label:"\${name}"\`;
+
+      </code>
+    </pre>
+    
+
+    <p>
+      Closes #42.
+    </p>
+  </div>
+</div>
+`;
+
+exports[`Markdown/GithubFlavored matches snapshot 1`] = `
+<div>
+  <div
+    class="prose prose-sm dark:prose-invert max-w-none prose-pre:bg-muted prose-pre:text-foreground prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded-sm prose-code:text-sm prose-code:before:content-none prose-code:after:content-none prose-img:inline prose-img:my-0"
+  >
+    <h2
+      id="task-list"
+    >
+      Task list
+      <a
+        aria-hidden="true"
+        href="#task-list"
+        tabindex="-1"
+      >
+        <span
+          class="icon icon-link"
+        />
+      </a>
+    </h2>
+    
+
+    <ul
+      class="contains-task-list"
+    >
+      
+
+      <li
+        class="task-list-item"
+      >
+        <input
+          checked=""
+          disabled=""
+          type="checkbox"
+        />
+         Completed task
+      </li>
+      
+
+      <li
+        class="task-list-item"
+      >
+        <input
+          disabled=""
+          type="checkbox"
+        />
+         Incomplete task
+      </li>
+      
+
+      <li
+        class="task-list-item"
+      >
+        <input
+          disabled=""
+          type="checkbox"
+        />
+         Another task
+      </li>
+      
+
+    </ul>
+    
+
+    <h2
+      id="table"
+    >
+      Table
+      <a
+        aria-hidden="true"
+        href="#table"
+        tabindex="-1"
+      >
+        <span
+          class="icon icon-link"
+        />
+      </a>
+    </h2>
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    <table>
+      <thead>
+        <tr>
+          <th>
+            Feature
+          </th>
+          <th>
+            Status
+          </th>
+          <th>
+            Priority
+          </th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td>
+            Auth
+          </td>
+          <td>
+            Done
+          </td>
+          <td>
+            High
+          </td>
+        </tr>
+        <tr>
+          <td>
+            Search
+          </td>
+          <td>
+            WIP
+          </td>
+          <td>
+            Medium
+          </td>
+        </tr>
+        <tr>
+          <td>
+            Export
+          </td>
+          <td>
+            Todo
+          </td>
+          <td>
+            Low
+          </td>
+        </tr>
+      </tbody>
+    </table>
+    
+
+    <h2
+      id="strikethrough"
+    >
+      Strikethrough
+      <a
+        aria-hidden="true"
+        href="#strikethrough"
+        tabindex="-1"
+      >
+        <span
+          class="icon icon-link"
+        />
+      </a>
+    </h2>
+    
+
+    <p>
+      This is 
+      <del>
+        deleted
+      </del>
+       text.
+    </p>
+    
+
+    <h2
+      id="emoji"
+    >
+      Emoji
+      <a
+        aria-hidden="true"
+        href="#emoji"
+        tabindex="-1"
+      >
+        <span
+          class="icon icon-link"
+        />
+      </a>
+    </h2>
+    
+
+    <p>
+      πŸš€ πŸ› ✨
+    </p>
+  </div>
+</div>
+`;

webui2/src/components/ui/__snapshots__/avatar.test.tsx.snap πŸ”—

@@ -0,0 +1,57 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Avatar/Large matches snapshot 1`] = `
+<div>
+  <span
+    class="relative flex shrink-0 overflow-hidden rounded-full size-16"
+  >
+    <span
+      class="flex h-full w-full items-center justify-center rounded-full bg-muted text-xs font-medium"
+    >
+      CN
+    </span>
+  </span>
+</div>
+`;
+
+exports[`Avatar/Small matches snapshot 1`] = `
+<div>
+  <span
+    class="relative flex shrink-0 overflow-hidden rounded-full size-6"
+  >
+    <span
+      class="flex h-full w-full items-center justify-center rounded-full bg-muted font-medium text-[8px]"
+    >
+      AB
+    </span>
+  </span>
+</div>
+`;
+
+exports[`Avatar/WithFallback matches snapshot 1`] = `
+<div>
+  <span
+    class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full"
+  >
+    <span
+      class="flex h-full w-full items-center justify-center rounded-full bg-muted text-xs font-medium"
+    >
+      JD
+    </span>
+  </span>
+</div>
+`;
+
+exports[`Avatar/WithImage matches snapshot 1`] = `
+<div>
+  <span
+    class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full"
+  >
+    <span
+      class="flex h-full w-full items-center justify-center rounded-full bg-muted text-xs font-medium"
+    >
+      CN
+    </span>
+  </span>
+</div>
+`;

webui2/src/components/ui/__snapshots__/badge.test.tsx.snap πŸ”—

@@ -0,0 +1,41 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Badge/Default matches snapshot 1`] = `
+<div>
+  <div
+    class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground shadow-sm hover:bg-primary/80"
+  >
+    Badge
+  </div>
+</div>
+`;
+
+exports[`Badge/Destructive matches snapshot 1`] = `
+<div>
+  <div
+    class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/80"
+  >
+    Destructive
+  </div>
+</div>
+`;
+
+exports[`Badge/Outline matches snapshot 1`] = `
+<div>
+  <div
+    class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground"
+  >
+    Outline
+  </div>
+</div>
+`;
+
+exports[`Badge/Secondary matches snapshot 1`] = `
+<div>
+  <div
+    class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80"
+  >
+    Secondary
+  </div>
+</div>
+`;

webui2/src/components/ui/__snapshots__/input.test.tsx.snap πŸ”—

@@ -0,0 +1,39 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Input/Default matches snapshot 1`] = `
+<div>
+  <input
+    class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
+    placeholder="Type something…"
+  />
+</div>
+`;
+
+exports[`Input/Disabled matches snapshot 1`] = `
+<div>
+  <input
+    class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
+    disabled=""
+    placeholder="Disabled"
+  />
+</div>
+`;
+
+exports[`Input/Password matches snapshot 1`] = `
+<div>
+  <input
+    class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
+    placeholder="Enter password"
+    type="password"
+  />
+</div>
+`;
+
+exports[`Input/WithValue matches snapshot 1`] = `
+<div>
+  <input
+    class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
+    value="Hello world"
+  />
+</div>
+`;

webui2/src/components/ui/__snapshots__/query-input.test.tsx.snap πŸ”—

@@ -0,0 +1,266 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`QueryInput/AsyncCompletions matches snapshot 1`] = `
+<div>
+  <div
+    class="relative flex flex-1 items-center rounded-md border border-input bg-background ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"
+  >
+    <div
+      class="text-muted-foreground pointer-events-none absolute left-3 size-4 shrink-0"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-search"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m21 21-4.34-4.34"
+        />
+        <circle
+          cx="11"
+          cy="11"
+          r="8"
+        />
+      </svg>
+    </div>
+    <div
+      aria-hidden="true"
+      class="text-foreground pointer-events-none absolute inset-0 flex items-center overflow-hidden pr-3 pl-9 font-mono text-sm whitespace-pre"
+    />
+    <input
+      autocomplete="off"
+      class="caret-foreground placeholder:text-muted-foreground relative w-full bg-transparent py-2 pr-3 pl-9 font-mono text-sm text-transparent outline-hidden placeholder:font-sans"
+      placeholder="Type label: to see async loading…"
+      spellcheck="false"
+      type="text"
+      value=""
+    />
+  </div>
+</div>
+`;
+
+exports[`QueryInput/Default matches snapshot 1`] = `
+<div>
+  <div
+    class="relative flex flex-1 items-center rounded-md border border-input bg-background ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"
+  >
+    <div
+      class="text-muted-foreground pointer-events-none absolute left-3 size-4 shrink-0"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-search"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m21 21-4.34-4.34"
+        />
+        <circle
+          cx="11"
+          cy="11"
+          r="8"
+        />
+      </svg>
+    </div>
+    <div
+      aria-hidden="true"
+      class="text-foreground pointer-events-none absolute inset-0 flex items-center overflow-hidden pr-3 pl-9 font-mono text-sm whitespace-pre"
+    >
+      <span>
+        <span
+          class="text-green-600 dark:text-green-400"
+        >
+          status:
+        </span>
+        <span>
+          open
+        </span>
+      </span>
+    </div>
+    <input
+      autocomplete="off"
+      class="caret-foreground placeholder:text-muted-foreground relative w-full bg-transparent py-2 pr-3 pl-9 font-mono text-sm text-transparent outline-hidden placeholder:font-sans"
+      placeholder="status:open author:… label:…"
+      spellcheck="false"
+      type="text"
+      value="status:open"
+    />
+  </div>
+</div>
+`;
+
+exports[`QueryInput/SyntaxOnly matches snapshot 1`] = `
+<div>
+  <div
+    class="relative flex flex-1 items-center rounded-md border border-input bg-background ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"
+  >
+    <div
+      class="text-muted-foreground pointer-events-none absolute left-3 size-4 shrink-0"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-search"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m21 21-4.34-4.34"
+        />
+        <circle
+          cx="11"
+          cy="11"
+          r="8"
+        />
+      </svg>
+    </div>
+    <div
+      aria-hidden="true"
+      class="text-foreground pointer-events-none absolute inset-0 flex items-center overflow-hidden pr-3 pl-9 font-mono text-sm whitespace-pre"
+    >
+      <span>
+        <span
+          class="text-green-600 dark:text-green-400"
+        >
+          status:
+        </span>
+        <span>
+          open
+        </span>
+      </span>
+      <span>
+         
+      </span>
+      <span>
+        label:bug
+      </span>
+    </div>
+    <input
+      autocomplete="off"
+      class="caret-foreground placeholder:text-muted-foreground relative w-full bg-transparent py-2 pr-3 pl-9 font-mono text-sm text-transparent outline-hidden placeholder:font-sans"
+      placeholder="Search…"
+      spellcheck="false"
+      type="text"
+      value="status:open label:bug"
+    />
+  </div>
+</div>
+`;
+
+exports[`QueryInput/WithFilters matches snapshot 1`] = `
+<div>
+  <div
+    class="relative flex flex-1 items-center rounded-md border border-input bg-background ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"
+  >
+    <div
+      class="text-muted-foreground pointer-events-none absolute left-3 size-4 shrink-0"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-search"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <path
+          d="m21 21-4.34-4.34"
+        />
+        <circle
+          cx="11"
+          cy="11"
+          r="8"
+        />
+      </svg>
+    </div>
+    <div
+      aria-hidden="true"
+      class="text-foreground pointer-events-none absolute inset-0 flex items-center overflow-hidden pr-3 pl-9 font-mono text-sm whitespace-pre"
+    >
+      <span>
+        <span
+          class="text-green-600 dark:text-green-400"
+        >
+          status:
+        </span>
+        <span>
+          open
+        </span>
+      </span>
+      <span>
+         
+      </span>
+      <span>
+        <span
+          class="text-yellow-600 dark:text-yellow-500"
+        >
+          label:
+        </span>
+        <span>
+          bug
+        </span>
+      </span>
+      <span>
+         
+      </span>
+      <span>
+        <span
+          class="text-blue-600 dark:text-blue-400"
+        >
+          author:
+        </span>
+        <span>
+          janedoe
+        </span>
+      </span>
+      <span>
+         
+      </span>
+      <span>
+        fix
+      </span>
+      <span>
+         
+      </span>
+      <span>
+        login
+      </span>
+    </div>
+    <input
+      autocomplete="off"
+      class="caret-foreground placeholder:text-muted-foreground relative w-full bg-transparent py-2 pr-3 pl-9 font-mono text-sm text-transparent outline-hidden placeholder:font-sans"
+      placeholder="status:open author:… label:…"
+      spellcheck="false"
+      type="text"
+      value="status:open label:bug author:janedoe fix login"
+    />
+  </div>
+</div>
+`;

webui2/src/components/ui/__snapshots__/separator.test.tsx.snap πŸ”—

@@ -0,0 +1,49 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Separator/Horizontal matches snapshot 1`] = `
+<div>
+  <div
+    class="w-64 space-y-2"
+  >
+    <p
+      class="text-sm"
+    >
+      Above
+    </p>
+    <div
+      class="shrink-0 bg-border h-[1px] w-full"
+      data-orientation="horizontal"
+      role="none"
+    />
+    <p
+      class="text-sm"
+    >
+      Below
+    </p>
+  </div>
+</div>
+`;
+
+exports[`Separator/Vertical matches snapshot 1`] = `
+<div>
+  <div
+    class="flex h-8 items-center gap-2"
+  >
+    <span
+      class="text-sm"
+    >
+      Left
+    </span>
+    <div
+      class="shrink-0 bg-border h-full w-[1px]"
+      data-orientation="vertical"
+      role="none"
+    />
+    <span
+      class="text-sm"
+    >
+      Right
+    </span>
+  </div>
+</div>
+`;

webui2/src/components/ui/__snapshots__/skeleton.test.tsx.snap πŸ”—

@@ -0,0 +1,39 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Skeleton/Card matches snapshot 1`] = `
+<div>
+  <div
+    class="flex items-center gap-4"
+  >
+    <div
+      class="animate-pulse bg-primary/10 size-12 rounded-full"
+    />
+    <div
+      class="space-y-2"
+    >
+      <div
+        class="animate-pulse rounded-md bg-primary/10 h-4 w-48"
+      />
+      <div
+        class="animate-pulse rounded-md bg-primary/10 h-4 w-32"
+      />
+    </div>
+  </div>
+</div>
+`;
+
+exports[`Skeleton/Circle matches snapshot 1`] = `
+<div>
+  <div
+    class="animate-pulse bg-primary/10 size-10 rounded-full"
+  />
+</div>
+`;
+
+exports[`Skeleton/Default matches snapshot 1`] = `
+<div>
+  <div
+    class="animate-pulse rounded-md bg-primary/10 h-4 w-48"
+  />
+</div>
+`;

webui2/src/components/ui/__snapshots__/status-tabs.test.tsx.snap πŸ”—

@@ -0,0 +1,76 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`StatusTabs/Default matches snapshot 1`] = `
+<div>
+  <div
+    class="flex items-center gap-1"
+  >
+    <button
+      class="bg-accent text-accent-foreground flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-circle-dot size-4 group-[.active]:text-green-600 dark:group-[.active]:text-green-400"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <circle
+          cx="12"
+          cy="12"
+          r="10"
+        />
+        <circle
+          cx="12"
+          cy="12"
+          r="1"
+        />
+      </svg>
+      Open
+      <span
+        class="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none"
+      >
+        12
+      </span>
+    </button>
+    <button
+      class="text-muted-foreground hover:bg-accent/50 hover:text-foreground flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium"
+    >
+      <svg
+        aria-hidden="true"
+        class="lucide lucide-circle-check size-4 group-[.active]:text-purple-600 dark:group-[.active]:text-purple-400"
+        fill="none"
+        height="24"
+        stroke="currentColor"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-width="2"
+        viewBox="0 0 24 24"
+        width="24"
+        xmlns="http://www.w3.org/2000/svg"
+      >
+        <circle
+          cx="12"
+          cy="12"
+          r="10"
+        />
+        <path
+          d="m9 12 2 2 4-4"
+        />
+      </svg>
+      Closed
+      <span
+        class="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none"
+      >
+        5
+      </span>
+    </button>
+  </div>
+</div>
+`;

webui2/src/components/ui/__snapshots__/textarea.test.tsx.snap πŸ”—

@@ -0,0 +1,30 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Textarea/Default matches snapshot 1`] = `
+<div>
+  <textarea
+    class="flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
+    placeholder="Write a comment…"
+  />
+</div>
+`;
+
+exports[`Textarea/Disabled matches snapshot 1`] = `
+<div>
+  <textarea
+    class="flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
+    disabled=""
+    placeholder="Disabled"
+  />
+</div>
+`;
+
+exports[`Textarea/WithValue matches snapshot 1`] = `
+<div>
+  <textarea
+    class="flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
+  >
+    This is some content in the textarea.
+  </textarea>
+</div>
+`;

webui2/src/components/ui/__snapshots__/write-preview.test.tsx.snap πŸ”—

@@ -0,0 +1,86 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`WritePreview/Controlled matches snapshot 1`] = `
+<div>
+  <div>
+    <div
+      class="flex gap-2 mb-2"
+    >
+      <button
+        class="rounded-sm px-2 py-0.5 text-sm transition-colors bg-muted font-medium"
+        type="button"
+      >
+        Write
+      </button>
+      <button
+        class="rounded-sm px-2 py-0.5 text-sm font-medium transition-colors disabled:opacity-40 text-muted-foreground hover:text-foreground"
+        disabled=""
+        type="button"
+      >
+        Preview
+      </button>
+    </div>
+    <textarea
+      class="flex w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm min-h-[120px]"
+      placeholder="Leave a comment…"
+    />
+  </div>
+</div>
+`;
+
+exports[`WritePreview/Empty matches snapshot 1`] = `
+<div>
+  <div>
+    <div
+      class="flex gap-2 mb-2"
+    >
+      <button
+        class="rounded-sm px-2 py-0.5 text-sm transition-colors bg-muted font-medium"
+        type="button"
+      >
+        Write
+      </button>
+      <button
+        class="rounded-sm px-2 py-0.5 text-sm font-medium transition-colors disabled:opacity-40 text-muted-foreground hover:text-foreground"
+        disabled=""
+        type="button"
+      >
+        Preview
+      </button>
+    </div>
+    <textarea
+      class="flex w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm min-h-[120px]"
+      placeholder="Preview is disabled until you type something…"
+    />
+  </div>
+</div>
+`;
+
+exports[`WritePreview/Uncontrolled matches snapshot 1`] = `
+<div>
+  <div>
+    <div
+      class="flex gap-2 mb-2"
+    >
+      <button
+        class="rounded-sm px-2 py-0.5 text-sm transition-colors bg-muted font-medium"
+        type="button"
+      >
+        Write
+      </button>
+      <button
+        class="rounded-sm px-2 py-0.5 text-sm font-medium transition-colors disabled:opacity-40 text-muted-foreground hover:text-foreground"
+        type="button"
+      >
+        Preview
+      </button>
+    </div>
+    <textarea
+      class="flex w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm min-h-[200px]"
+      placeholder="Describe the issue…"
+    >
+      Hello **world**!
+    </textarea>
+  </div>
+</div>
+`;

webui2/src/components/ui/avatar.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./avatar.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`Avatar/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/ui/badge.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./badge.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`Badge/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/ui/input.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./input.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`Input/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/ui/query-input.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./query-input.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`QueryInput/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/ui/separator.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./separator.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`Separator/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/ui/skeleton.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./skeleton.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`Skeleton/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/ui/status-tabs.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./status-tabs.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`StatusTabs/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/ui/textarea.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./textarea.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`Textarea/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}

webui2/src/components/ui/write-preview.test.tsx πŸ”—

@@ -0,0 +1,13 @@
+import { composeStories } from "@storybook/react-vite";
+import { expect, test } from "vitest";
+
+import * as stories from "./write-preview.stories";
+
+const composed = composeStories(stories);
+
+for (const [name, Story] of Object.entries(composed)) {
+  test(`WritePreview/${name} matches snapshot`, async () => {
+    await Story.run();
+    expect(document.body.firstChild).toMatchSnapshot();
+  });
+}