Просмотр исходного кода

chore: storybook (#15285)

Co-authored-by: David Hill <[email protected]>
Adam 1 месяц назад
Родитель
Сommit
05d77b7d47
85 измененных файлов с 5247 добавлено и 9 удалено
  1. 195 8
      bun.lock
  2. 3 0
      packages/storybook/.gitignore
  3. 37 0
      packages/storybook/.storybook/main.ts
  4. 11 0
      packages/storybook/.storybook/manager.ts
  5. 106 0
      packages/storybook/.storybook/preview.tsx
  6. 21 0
      packages/storybook/.storybook/theme-tool.ts
  7. 307 0
      packages/storybook/debug-storybook.log
  8. 28 0
      packages/storybook/package.json
  9. 16 0
      packages/storybook/tsconfig.json
  10. 1 0
      packages/ui/package.json
  11. 7 0
      packages/ui/src/assets/icons/provider/302ai.svg
  12. 3 0
      packages/ui/src/assets/icons/provider/berget.svg
  13. 5 0
      packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg
  14. 12 0
      packages/ui/src/assets/icons/provider/firmware.svg
  15. 3 0
      packages/ui/src/assets/icons/provider/gitlab.svg
  16. 4 0
      packages/ui/src/assets/icons/provider/jiekou.svg
  17. 4 0
      packages/ui/src/assets/icons/provider/kilo.svg
  18. 3 0
      packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg
  19. 7 0
      packages/ui/src/assets/icons/provider/meganova.svg
  20. 24 0
      packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg
  21. 24 0
      packages/ui/src/assets/icons/provider/minimax-coding-plan.svg
  22. 1 0
      packages/ui/src/assets/icons/provider/moark.svg
  23. 3 0
      packages/ui/src/assets/icons/provider/nova.svg
  24. 10 0
      packages/ui/src/assets/icons/provider/novita-ai.svg
  25. 5 0
      packages/ui/src/assets/icons/provider/privatemode-ai.svg
  26. 9 0
      packages/ui/src/assets/icons/provider/qihang-ai.svg
  27. 7 0
      packages/ui/src/assets/icons/provider/qiniu-ai.svg
  28. 4 0
      packages/ui/src/assets/icons/provider/stackit.svg
  29. 24 0
      packages/ui/src/assets/icons/provider/stepfun.svg
  30. 4 0
      packages/ui/src/assets/icons/provider/vivgrid.svg
  31. 149 0
      packages/ui/src/components/accordion.stories.tsx
  32. 69 0
      packages/ui/src/components/app-icon.stories.tsx
  33. 76 0
      packages/ui/src/components/avatar.stories.tsx
  34. 133 0
      packages/ui/src/components/basic-tool.stories.tsx
  35. 108 0
      packages/ui/src/components/button.stories.tsx
  36. 90 0
      packages/ui/src/components/card.stories.tsx
  37. 71 0
      packages/ui/src/components/checkbox.stories.tsx
  38. 70 0
      packages/ui/src/components/code.stories.tsx
  39. 86 0
      packages/ui/src/components/collapsible.stories.tsx
  40. 113 0
      packages/ui/src/components/context-menu.stories.tsx
  41. 173 0
      packages/ui/src/components/dialog.stories.tsx
  42. 81 0
      packages/ui/src/components/diff-changes.stories.tsx
  43. 97 0
      packages/ui/src/components/diff-ssr.stories.tsx
  44. 96 0
      packages/ui/src/components/diff.stories.tsx
  45. 62 0
      packages/ui/src/components/dock-prompt.stories.tsx
  46. 97 0
      packages/ui/src/components/dropdown-menu.stories.tsx
  47. 49 0
      packages/ui/src/components/favicon.stories.tsx
  48. 94 0
      packages/ui/src/components/file-icon.stories.tsx
  49. 48 0
      packages/ui/src/components/font.stories.tsx
  50. 70 0
      packages/ui/src/components/hover-card.stories.tsx
  51. 74 0
      packages/ui/src/components/icon-button.stories.tsx
  52. 170 0
      packages/ui/src/components/icon.stories.tsx
  53. 59 0
      packages/ui/src/components/image-preview.stories.tsx
  54. 50 0
      packages/ui/src/components/inline-input.stories.tsx
  55. 43 0
      packages/ui/src/components/keybind.stories.tsx
  56. 115 0
      packages/ui/src/components/line-comment.stories.tsx
  57. 170 0
      packages/ui/src/components/list.stories.tsx
  58. 57 0
      packages/ui/src/components/logo.stories.tsx
  59. 53 0
      packages/ui/src/components/markdown.stories.tsx
  60. 7 0
      packages/ui/src/components/message-nav.stories.tsx
  61. 7 0
      packages/ui/src/components/message-part.stories.tsx
  62. 87 0
      packages/ui/src/components/popover.stories.tsx
  63. 59 0
      packages/ui/src/components/progress-circle.stories.tsx
  64. 67 0
      packages/ui/src/components/progress.stories.tsx
  65. 69 0
      packages/ui/src/components/provider-icon.stories.tsx
  66. 95 0
      packages/ui/src/components/provider-icons/sprite.svg
  67. 20 0
      packages/ui/src/components/provider-icons/types.ts
  68. 92 0
      packages/ui/src/components/radio-group.stories.tsx
  69. 156 0
      packages/ui/src/components/resize-handle.stories.tsx
  70. 113 0
      packages/ui/src/components/select.stories.tsx
  71. 7 0
      packages/ui/src/components/session-review.stories.tsx
  72. 7 0
      packages/ui/src/components/session-turn.stories.tsx
  73. 53 0
      packages/ui/src/components/spinner.stories.tsx
  74. 54 0
      packages/ui/src/components/sticky-accordion-header.stories.tsx
  75. 68 0
      packages/ui/src/components/switch.stories.tsx
  76. 179 0
      packages/ui/src/components/tabs.stories.tsx
  77. 58 0
      packages/ui/src/components/tag.stories.tsx
  78. 111 0
      packages/ui/src/components/text-field.stories.tsx
  79. 59 0
      packages/ui/src/components/text-shimmer.stories.tsx
  80. 138 0
      packages/ui/src/components/toast.stories.tsx
  81. 64 0
      packages/ui/src/components/tooltip.stories.tsx
  82. 51 0
      packages/ui/src/components/typewriter.stories.tsx
  83. 51 0
      packages/ui/src/storybook/fixtures.ts
  84. 62 0
      packages/ui/src/storybook/scaffold.tsx
  85. 2 1
      packages/ui/tsconfig.json

+ 195 - 8
bun.lock

@@ -418,6 +418,27 @@
         "typescript": "catalog:",
       },
     },
+    "packages/storybook": {
+      "name": "@opencode-ai/storybook",
+      "devDependencies": {
+        "@opencode-ai/ui": "workspace:*",
+        "@solidjs/meta": "catalog:",
+        "@storybook/addon-a11y": "^10.2.10",
+        "@storybook/addon-docs": "^10.2.10",
+        "@storybook/addon-links": "^10.2.10",
+        "@storybook/addon-onboarding": "^10.2.10",
+        "@storybook/addon-vitest": "^10.2.10",
+        "@tsconfig/node22": "catalog:",
+        "@types/node": "catalog:",
+        "@types/react": "18.0.25",
+        "react": "18.2.0",
+        "solid-js": "catalog:",
+        "storybook": "^10.2.10",
+        "storybook-solidjs-vite": "^10.0.9",
+        "typescript": "catalog:",
+        "vite": "catalog:",
+      },
+    },
     "packages/ui": {
       "name": "@opencode-ai/ui",
       "version": "1.2.15",
@@ -1136,6 +1157,8 @@
 
     "@jimp/utils": ["@jimp/[email protected]", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
 
+    "@joshwooding/vite-plugin-react-docgen-typescript": ["@joshwooding/[email protected]", "", { "dependencies": { "glob": "^13.0.1", "react-docgen-typescript": "^2.2.2" }, "peerDependencies": { "typescript": ">= 4.3.x", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-6PyZBYKnnVNqOSB0YFly+62R7dmov8segT27A+RVTBVd4iAE6kbW9QBJGlyR2yG4D4ohzhZSTIu7BK1UTtmFFA=="],
+
     "@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
 
     "@jridgewell/remapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
@@ -1208,6 +1231,8 @@
 
     "@mdx-js/mdx": ["@mdx-js/[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
 
+    "@mdx-js/react": ["@mdx-js/[email protected]", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="],
+
     "@mixmark-io/domino": ["@mixmark-io/[email protected]", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
 
     "@modelcontextprotocol/sdk": ["@modelcontextprotocol/[email protected]", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww=="],
@@ -1302,6 +1327,8 @@
 
     "@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"],
 
+    "@opencode-ai/storybook": ["@opencode-ai/storybook@workspace:packages/storybook"],
+
     "@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"],
 
     "@opencode-ai/util": ["@opencode-ai/util@workspace:packages/util"],
@@ -1774,6 +1801,26 @@
 
     "@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
 
+    "@storybook/addon-a11y": ["@storybook/[email protected]", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-1S9pDXgvbHhBStGarCvfJ3/rfcaiAcQHRhuM3Nk4WGSIYtC1LCSRuzYdDYU0aNRpdCbCrUA7kUCbqvIE3tH+3Q=="],
+
+    "@storybook/addon-docs": ["@storybook/[email protected]", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.10", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.10", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-2wIYtdvZIzPbQ5194M5Igpy8faNbQ135nuO5ZaZ2VuttqGr+IJcGnDP42zYwbAsGs28G8ohpkbSgIzVyJWUhPQ=="],
+
+    "@storybook/addon-links": ["@storybook/[email protected]", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" }, "optionalPeers": ["react"] }, "sha512-oo9Xx4/2OVJtptXKpqH4ySri7ZuBdiSOXlZVGejEfLa0Jeajlh/KIlREpGvzPPOqUVT7dSddWzBjJmJUyQC3ew=="],
+
+    "@storybook/addon-onboarding": ["@storybook/[email protected]", "", { "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-DkzZQTXHp99SpHMIQ5plbbHcs4EWVzWhLXlW+icA8sBlKo5Bwj540YcOApKbqB0m/OzWprsznwN7Kv4vfvHu4w=="],
+
+    "@storybook/addon-vitest": ["@storybook/[email protected]", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.2.10", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-U2oHw+Ar+Xd06wDTB74VlujhIIW89OHThpJjwgqgM6NWrOC/XLllJ53ILFDyREBkMwpBD7gJQIoQpLEcKBIEhw=="],
+
+    "@storybook/builder-vite": ["@storybook/[email protected]", "", { "dependencies": { "@storybook/csf-plugin": "10.2.10", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-Wd6CYL7LvRRNiXMz977x9u/qMm7nmMw/7Dow2BybQo+Xbfy1KhVjIoZ/gOiG515zpojSozctNrJUbM0+jH1jwg=="],
+
+    "@storybook/csf-plugin": ["@storybook/[email protected]", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.10", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g=="],
+
+    "@storybook/global": ["@storybook/[email protected]", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="],
+
+    "@storybook/icons": ["@storybook/[email protected]", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg=="],
+
+    "@storybook/react-dom-shim": ["@storybook/[email protected]", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" } }, "sha512-TmBrhyLHn8B8rvDHKk5uW5BqzO1M1T+fqFNWg88NIAJOoyX4Uc90FIJjDuN1OJmWKGwB5vLmPwaKBYsTe1yS+w=="],
+
     "@stripe/stripe-js": ["@stripe/[email protected]", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="],
 
     "@swc/helpers": ["@swc/[email protected]", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ=="],
@@ -1866,6 +1913,12 @@
 
     "@tediousjs/connection-string": ["@tediousjs/[email protected]", "", {}, "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ=="],
 
+    "@testing-library/dom": ["@testing-library/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
+
+    "@testing-library/jest-dom": ["@testing-library/[email protected]", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="],
+
+    "@testing-library/user-event": ["@testing-library/[email protected]", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="],
+
     "@thisbeyond/solid-dnd": ["@thisbeyond/[email protected]", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="],
 
     "@tokenizer/token": ["@tokenizer/[email protected]", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
@@ -1876,6 +1929,8 @@
 
     "@tybys/wasm-util": ["@tybys/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
 
+    "@types/aria-query": ["@types/[email protected]", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
+
     "@types/babel__core": ["@types/[email protected]", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
 
     "@types/babel__generator": ["@types/[email protected]", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
@@ -2010,7 +2065,7 @@
 
     "@vitejs/plugin-react": ["@vitejs/[email protected]", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
 
-    "@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="],
+    "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
 
     "@vitest/mocker": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ=="],
 
@@ -2020,7 +2075,7 @@
 
     "@vitest/snapshot": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA=="],
 
-    "@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="],
+    "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
 
     "@vitest/utils": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" } }, "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA=="],
 
@@ -2116,6 +2171,8 @@
 
     "assertion-error": ["[email protected]", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
 
+    "ast-types": ["[email protected]", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
+
     "astring": ["[email protected]", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
 
     "astro": ["[email protected]", "", { "dependencies": { "@astrojs/compiler": "^2.11.0", "@astrojs/internal-helpers": "0.6.1", "@astrojs/markdown-remark": "6.3.1", "@astrojs/telemetry": "3.2.1", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w=="],
@@ -2144,6 +2201,8 @@
 
     "aws4fetch": ["[email protected]", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
 
+    "axe-core": ["[email protected]", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="],
+
     "axios": ["[email protected]", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
 
     "axobject-query": ["[email protected]", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
@@ -2258,7 +2317,7 @@
 
     "ccount": ["[email protected]", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
 
-    "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
+    "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
 
     "chainsaw": ["[email protected]", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
 
@@ -2274,6 +2333,8 @@
 
     "chart.js": ["[email protected]", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
 
+    "check-error": ["[email protected]", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
+
     "cheerio": ["[email protected]", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "htmlparser2": "^8.0.1", "parse5": "^7.0.0", "parse5-htmlparser2-tree-adapter": "^7.0.0" } }, "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q=="],
 
     "cheerio-select": ["[email protected]", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
@@ -2368,6 +2429,8 @@
 
     "css-what": ["[email protected]", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
 
+    "css.escape": ["[email protected]", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
+
     "cssesc": ["[email protected]", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
 
     "csstype": ["[email protected]", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
@@ -2388,6 +2451,8 @@
 
     "decode-named-character-reference": ["[email protected]", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
 
+    "deep-eql": ["[email protected]", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
+
     "deepmerge": ["[email protected]", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
 
     "default-browser": ["[email protected]", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
@@ -2440,6 +2505,8 @@
 
     "dns-packet": ["[email protected]", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="],
 
+    "dom-accessibility-api": ["[email protected]", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
+
     "dom-serializer": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
 
     "domelementtype": ["[email protected]", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
@@ -2834,6 +2901,8 @@
 
     "import-meta-resolve": ["[email protected]", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
 
+    "indent-string": ["[email protected]", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
+
     "inherits": ["[email protected]", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
 
     "ini": ["[email protected]", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
@@ -3074,6 +3143,8 @@
 
     "loose-envify": ["[email protected]", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
 
+    "loupe": ["[email protected]", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
+
     "lower-case": ["[email protected]", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="],
 
     "lru-cache": ["[email protected]", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
@@ -3084,6 +3155,8 @@
 
     "luxon": ["[email protected]", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="],
 
+    "lz-string": ["[email protected]", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
+
     "magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
 
     "magicast": ["[email protected]", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="],
@@ -3234,6 +3307,8 @@
 
     "mimic-fn": ["[email protected]", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
 
+    "min-indent": ["[email protected]", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
+
     "miniflare": ["[email protected]", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251118.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ=="],
 
     "minimatch": ["[email protected]", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
@@ -3426,6 +3501,8 @@
 
     "pathe": ["[email protected]", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
 
+    "pathval": ["[email protected]", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
+
     "peberminta": ["[email protected]", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
 
     "peek-readable": ["[email protected]", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
@@ -3492,6 +3569,8 @@
 
     "pretty": ["[email protected]", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="],
 
+    "pretty-format": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
+
     "prismjs": ["[email protected]", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
 
     "process": ["[email protected]", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
@@ -3534,8 +3613,12 @@
 
     "react": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="],
 
+    "react-docgen-typescript": ["[email protected]", "", { "peerDependencies": { "typescript": ">= 4.3.x" } }, "sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg=="],
+
     "react-dom": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
 
+    "react-is": ["[email protected]", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
+
     "react-refresh": ["[email protected]", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
 
     "react-remove-scroll": ["[email protected]", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.3", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw=="],
@@ -3560,6 +3643,8 @@
 
     "real-require": ["[email protected]", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
 
+    "recast": ["[email protected]", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
+
     "recma-build-jsx": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="],
 
     "recma-jsx": ["[email protected]", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="],
@@ -3568,6 +3653,8 @@
 
     "recma-stringify": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g=="],
 
+    "redent": ["[email protected]", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
+
     "reflect.getprototypeof": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
 
     "regex": ["[email protected]", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
@@ -3760,7 +3847,7 @@
 
     "sonic-boom": ["[email protected]", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
 
-    "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+    "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
 
     "source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
 
@@ -3808,6 +3895,10 @@
 
     "stoppable": ["[email protected]", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
 
+    "storybook": ["[email protected]", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-N4U42qKgzMHS7DjqLz5bY4P7rnvJtYkWFCyKspZr3FhPUuy6CWOae3aYC2BjXkHrdug0Jyta6VxFTuB1tYUKhg=="],
+
+    "storybook-solidjs-vite": ["[email protected]", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.1", "@storybook/builder-vite": "^10.0.0", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": ">= 4.9.x", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n6MwWCL9mK/qIaUutE9vhGB0X1I1hVnKin2NL+iVC5oXfAiuaABVZlr/1oEeEypsgCdyDOcbEbhJmDWmaqGpPw=="],
+
     "stream-replace-string": ["[email protected]", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="],
 
     "streamx": ["[email protected]", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
@@ -3834,6 +3925,8 @@
 
     "strip-final-newline": ["[email protected]", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
 
+    "strip-indent": ["[email protected]", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
+
     "stripe": ["[email protected]", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="],
 
     "strnum": ["[email protected]", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
@@ -3896,6 +3989,8 @@
 
     "tinyrainbow": ["[email protected]", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
 
+    "tinyspy": ["[email protected]", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
+
     "titleize": ["[email protected]", "", {}, "sha512-ZgUJ1K83rhdu7uh7EHAC2BgY5DzoX8V5rTvoWI4vFysggi6YjLe5gUXABPWAU7VkvGP7P/0YiWq+dcPeYDsf1g=="],
 
     "to-regex-range": ["[email protected]", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
@@ -3920,6 +4015,8 @@
 
     "ts-algebra": ["[email protected]", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
 
+    "ts-dedent": ["[email protected]", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
+
     "ts-interface-checker": ["[email protected]", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
 
     "tsconfck": ["[email protected]", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
@@ -4020,6 +4117,8 @@
 
     "unpipe": ["[email protected]", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
 
+    "unplugin": ["[email protected]", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
+
     "unstorage": ["[email protected]", "", { "peerDependencies": { "@azure/app-configuration": "^1.9.0", "@azure/cosmos": "^4.7.0", "@azure/data-tables": "^13.3.1", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.29.1", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.12.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.35.6", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.8.2", "lru-cache": "^11.2.2", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g=="],
 
     "unzip-stream": ["[email protected]", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="],
@@ -4032,6 +4131,8 @@
 
     "use-sidecar": ["[email protected]", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
 
+    "use-sync-external-store": ["[email protected]", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
+
     "utif2": ["[email protected]", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
 
     "util": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
@@ -4106,6 +4207,8 @@
 
     "webidl-conversions": ["[email protected]", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
 
+    "webpack-virtual-modules": ["[email protected]", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
+
     "whatwg-mimetype": ["[email protected]", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
 
     "whatwg-url": ["[email protected]", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
@@ -4260,6 +4363,8 @@
 
     "@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/[email protected]", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.19.0", "smol-toml": "^1.5.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A=="],
 
+    "@astrojs/mdx/source-map": ["[email protected]", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+
     "@astrojs/sitemap/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
 
     "@astrojs/solid-js/vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
@@ -4488,6 +4593,8 @@
 
     "@jsx-email/doiuse-email/htmlparser2": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
 
+    "@mdx-js/mdx/source-map": ["[email protected]", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+
     "@modelcontextprotocol/sdk/express": ["[email protected]", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
 
     "@modelcontextprotocol/sdk/jose": ["[email protected]", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
@@ -4632,8 +4739,18 @@
 
     "@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
 
+    "@testing-library/dom/aria-query": ["[email protected]", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
+
+    "@testing-library/dom/dom-accessibility-api": ["[email protected]", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
+
     "@types/serve-static/@types/send": ["@types/[email protected]", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
 
+    "@vitest/expect/@vitest/utils": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
+
+    "@vitest/expect/tinyrainbow": ["[email protected]", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
+
+    "@vitest/mocker/@vitest/spy": ["@vitest/[email protected]", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="],
+
     "@vscode/emmet-helper/jsonc-parser": ["[email protected]", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
 
     "accepts/mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
@@ -4692,8 +4809,6 @@
 
     "c12/chokidar": ["[email protected]", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
 
-    "clean-css/source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
-
     "compress-commons/is-stream": ["[email protected]", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
 
     "condense-newlines/kind-of": ["[email protected]", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
@@ -4714,6 +4829,8 @@
 
     "esbuild-plugin-copy/chokidar": ["[email protected]", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
 
+    "estree-util-to-js/source-map": ["[email protected]", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+
     "execa/is-stream": ["[email protected]", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
 
     "express/cookie": ["[email protected]", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
@@ -4814,6 +4931,10 @@
 
     "postcss-load-config/lilconfig": ["[email protected]", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
 
+    "pretty-format/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+    "pretty-format/ansi-styles": ["[email protected]", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
+
     "prompts/kleur": ["[email protected]", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
 
     "raw-body/iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
@@ -4842,12 +4963,16 @@
 
     "sitemap/sax": ["[email protected]", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
 
-    "source-map-support/source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
-
     "sst/aws4fetch": ["[email protected]", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="],
 
     "sst/jose": ["[email protected]", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
 
+    "storybook/esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
+
+    "storybook/open": ["[email protected]", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
+
+    "storybook/ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
+
     "string-width-cjs/emoji-regex": ["[email protected]", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
 
     "string-width-cjs/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -4880,6 +5005,10 @@
 
     "vite-plugin-icons-spritesheet/glob": ["[email protected]", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
 
+    "vitest/@vitest/expect": ["@vitest/[email protected]", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="],
+
+    "vitest/@vitest/spy": ["@vitest/[email protected]", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="],
+
     "vitest/tinyexec": ["[email protected]", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
 
     "vitest/vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="],
@@ -5210,6 +5339,8 @@
 
     "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
 
+    "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/[email protected]", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
+
     "accepts/mime-types/mime-db": ["[email protected]", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
 
     "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-I3RhaOEMnWlWnrvjNBOYvUb19Dwf2nw01IruZrVJRDi688886e11wnd5DxrBZLd2V29Gizo3vpOPnnExsA+wTA=="],
@@ -5304,6 +5435,60 @@
 
     "send/debug/ms": ["[email protected]", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
 
+    "storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
+
+    "storybook/esbuild/@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
+
+    "storybook/esbuild/@esbuild/android-arm64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
+
+    "storybook/esbuild/@esbuild/android-x64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
+
+    "storybook/esbuild/@esbuild/darwin-arm64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
+
+    "storybook/esbuild/@esbuild/darwin-x64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
+
+    "storybook/esbuild/@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
+
+    "storybook/esbuild/@esbuild/freebsd-x64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
+
+    "storybook/esbuild/@esbuild/linux-arm": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
+
+    "storybook/esbuild/@esbuild/linux-arm64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
+
+    "storybook/esbuild/@esbuild/linux-ia32": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
+
+    "storybook/esbuild/@esbuild/linux-loong64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
+
+    "storybook/esbuild/@esbuild/linux-mips64el": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
+
+    "storybook/esbuild/@esbuild/linux-ppc64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
+
+    "storybook/esbuild/@esbuild/linux-riscv64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
+
+    "storybook/esbuild/@esbuild/linux-s390x": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
+
+    "storybook/esbuild/@esbuild/linux-x64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
+
+    "storybook/esbuild/@esbuild/netbsd-arm64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
+
+    "storybook/esbuild/@esbuild/netbsd-x64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
+
+    "storybook/esbuild/@esbuild/openbsd-arm64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
+
+    "storybook/esbuild/@esbuild/openbsd-x64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
+
+    "storybook/esbuild/@esbuild/openharmony-arm64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
+
+    "storybook/esbuild/@esbuild/sunos-x64": ["@esbuild/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
+
+    "storybook/esbuild/@esbuild/win32-arm64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
+
+    "storybook/esbuild/@esbuild/win32-ia32": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
+
+    "storybook/esbuild/@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
+
+    "storybook/open/wsl-utils": ["[email protected]", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
+
     "string-width-cjs/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
 
     "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
@@ -5372,6 +5557,8 @@
 
     "vite-plugin-icons-spritesheet/glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="],
 
+    "vitest/@vitest/expect/chai": ["[email protected]", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
+
     "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
 
     "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],

+ 3 - 0
packages/storybook/.gitignore

@@ -0,0 +1,3 @@
+node_modules/
+storybook-static/
+.storybook-cache/

+ 37 - 0
packages/storybook/.storybook/main.ts

@@ -0,0 +1,37 @@
+import { defineMain } from "storybook-solidjs-vite"
+import path from "node:path"
+import { fileURLToPath } from "node:url"
+
+const here = path.dirname(fileURLToPath(import.meta.url))
+const ui = path.resolve(here, "../../ui")
+
+export default defineMain({
+  framework: {
+    name: "storybook-solidjs-vite",
+    options: {},
+  },
+  addons: [
+    "@storybook/addon-onboarding",
+    "@storybook/addon-docs",
+    "@storybook/addon-links",
+    "@storybook/addon-a11y",
+    "@storybook/addon-vitest",
+  ],
+  stories: ["../../ui/src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
+  async viteFinal(config) {
+    const { mergeConfig, searchForWorkspaceRoot } = await import("vite")
+    return mergeConfig(config, {
+      resolve: {
+        dedupe: ["solid-js", "solid-js/web", "@solidjs/meta"],
+      },
+      worker: {
+        format: "es",
+      },
+      server: {
+        fs: {
+          allow: [searchForWorkspaceRoot(process.cwd()), ui],
+        },
+      },
+    })
+  },
+})

+ 11 - 0
packages/storybook/.storybook/manager.ts

@@ -0,0 +1,11 @@
+import { addons, types } from "storybook/manager-api"
+import { ThemeTool } from "./theme-tool"
+
+addons.register("opencode/theme-toggle", () => {
+  addons.add("opencode/theme-toggle/tool", {
+    type: types.TOOL,
+    title: "Theme",
+    match: ({ viewMode }) => viewMode === "story" || viewMode === "docs",
+    render: ThemeTool,
+  })
+})

+ 106 - 0
packages/storybook/.storybook/preview.tsx

@@ -0,0 +1,106 @@
+import "@opencode-ai/ui/styles"
+
+import { createEffect, onCleanup, onMount } from "solid-js"
+import addonA11y from "@storybook/addon-a11y"
+import addonDocs from "@storybook/addon-docs"
+import { MetaProvider } from "@solidjs/meta"
+import { addons } from "storybook/preview-api"
+import { GLOBALS_UPDATED } from "storybook/internal/core-events"
+import { createJSXDecorator, definePreview } from "storybook-solidjs-vite"
+import { Code } from "@opencode-ai/ui/code"
+import { CodeComponentProvider } from "@opencode-ai/ui/context/code"
+import { DialogProvider } from "@opencode-ai/ui/context/dialog"
+import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
+import { MarkedProvider } from "@opencode-ai/ui/context/marked"
+import { Diff } from "@opencode-ai/ui/diff"
+import { ThemeProvider, useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
+import { Font } from "@opencode-ai/ui/font"
+
+function resolveScheme(value: unknown): ColorScheme {
+  if (value === "light" || value === "dark" || value === "system") return value
+  return "system"
+}
+
+const channel = addons.getChannel()
+
+const Scheme = (props: { value?: unknown }) => {
+  const theme = useTheme()
+  const apply = (value?: unknown) => {
+    theme.setColorScheme(resolveScheme(value))
+  }
+  createEffect(() => {
+    apply(props.value)
+  })
+  createEffect(() => {
+    const root = document.documentElement
+    root.classList.remove("light", "dark")
+    root.classList.add(theme.mode())
+  })
+  onMount(() => {
+    const handler = (event: { globals?: Record<string, unknown> }) => {
+      apply(event.globals?.theme)
+    }
+    channel.on(GLOBALS_UPDATED, handler)
+    onCleanup(() => channel.off(GLOBALS_UPDATED, handler))
+  })
+  return null
+}
+
+const frame = createJSXDecorator((Story, context) => {
+  const override = context.parameters?.themes?.themeOverride
+  const selected = context.globals?.theme
+  const pick = override === "light" || override === "dark" ? override : selected
+  const scheme = resolveScheme(pick)
+  return (
+    <MetaProvider>
+      <Font />
+      <ThemeProvider>
+        <Scheme value={scheme} />
+        <DialogProvider>
+          <MarkedProvider>
+            <DiffComponentProvider component={Diff}>
+              <CodeComponentProvider component={Code}>
+                <div
+                  style={{
+                    "min-height": "100vh",
+                    padding: "24px",
+                    "background-color": "var(--background-base)",
+                    color: "var(--text-base)",
+                  }}
+                >
+                  <Story />
+                </div>
+              </CodeComponentProvider>
+            </DiffComponentProvider>
+          </MarkedProvider>
+        </DialogProvider>
+      </ThemeProvider>
+    </MetaProvider>
+  )
+})
+
+export default definePreview({
+  addons: [addonDocs(), addonA11y()],
+  decorators: [frame],
+  globalTypes: {
+    theme: {
+      name: "Theme",
+      description: "Global theme",
+      defaultValue: "light",
+    },
+  },
+  parameters: {
+    actions: {
+      argTypesRegex: "^on.*",
+    },
+    controls: {
+      matchers: {
+        color: /(background|color)$/i,
+        date: /Date$/i,
+      },
+    },
+    a11y: {
+      test: "todo",
+    },
+  },
+})

+ 21 - 0
packages/storybook/.storybook/theme-tool.ts

@@ -0,0 +1,21 @@
+import { createElement } from "react"
+import { useGlobals } from "storybook/manager-api"
+import { ToggleButton } from "storybook/internal/components"
+
+export function ThemeTool() {
+  const [globals, updateGlobals] = useGlobals()
+  const mode = globals.theme === "dark" ? "dark" : "light"
+  const toggle = () => {
+    const next = mode === "dark" ? "light" : "dark"
+    updateGlobals({ theme: next })
+  }
+  return createElement(
+    ToggleButton,
+    {
+      title: "Toggle theme",
+      active: mode === "dark",
+      onClick: toggle,
+    },
+    mode === "dark" ? "Dark" : "Light",
+  )
+}

+ 307 - 0
packages/storybook/debug-storybook.log

@@ -0,0 +1,307 @@
+[14:25:48.462] [INFO] storybook v10.2.10
+[14:25:48.749] [DEBUG] Getting package.json info for /Users/davidhill/Documents/Local/opencode/packages/storybook/package.json...
+[14:25:48.997] [INFO] Starting...
+[14:25:49.095] [DEBUG] Starting preview..
+[14:25:49.098] [WARN] 🚨 Unable to index files:
+- ./../ui/src/components/accordion.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/accordion.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/app-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/app-icon.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/avatar.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/avatar.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/basic-tool.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/basic-tool.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/checkbox.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/checkbox.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/code.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/code.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/collapsible.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/collapsible.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/context-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/context-menu.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/dialog.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dialog.stories.tsx (line 10, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/diff-changes.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-changes.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/diff-ssr.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-ssr.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/diff.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/dock-prompt.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dock-prompt.stories.tsx (line 15, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/dropdown-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dropdown-menu.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/favicon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/favicon.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/file-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/file-icon.stories.tsx (line 13, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/font.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/font.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/hover-card.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/hover-card.stories.tsx (line 13, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/icon-button.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon-button.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/image-preview.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/image-preview.stories.tsx (line 13, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/inline-input.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/inline-input.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/keybind.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/keybind.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/line-comment.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/line-comment.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/list.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/list.stories.tsx (line 15, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/logo.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/logo.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/markdown.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/markdown.stories.tsx (line 12, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/message-nav.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-nav.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/message-part.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-part.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/popover.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/popover.stories.tsx (line 16, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/progress-circle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress-circle.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/progress.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress.stories.tsx (line 15, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/provider-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/provider-icon.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/radio-group.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/radio-group.stories.tsx (line 13, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/resize-handle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/resize-handle.stories.tsx (line 17, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/select.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/select.stories.tsx (line 16, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/session-review.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-review.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/session-turn.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-turn.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/spinner.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/spinner.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/sticky-accordion-header.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/sticky-accordion-header.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/switch.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/switch.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/tabs.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tabs.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/tag.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tag.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/text-field.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-field.stories.tsx (line 14, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/text-shimmer.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-shimmer.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/toast.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/toast.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/tooltip.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tooltip.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/typewriter.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/typewriter.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+[14:25:49.109] [ERROR] Failed to build the preview
+[14:25:49.110] [ERROR] Error: Unable to index files:
+- ./../ui/src/components/accordion.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/accordion.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/app-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/app-icon.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/avatar.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/avatar.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/basic-tool.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/basic-tool.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/checkbox.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/checkbox.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/code.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/code.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/collapsible.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/collapsible.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/context-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/context-menu.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/dialog.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dialog.stories.tsx (line 10, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/diff-changes.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-changes.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/diff-ssr.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff-ssr.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/diff.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/diff.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/dock-prompt.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dock-prompt.stories.tsx (line 15, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/dropdown-menu.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/dropdown-menu.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/favicon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/favicon.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/file-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/file-icon.stories.tsx (line 13, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/font.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/font.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/hover-card.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/hover-card.stories.tsx (line 13, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/icon-button.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon-button.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/icon.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/image-preview.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/image-preview.stories.tsx (line 13, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/inline-input.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/inline-input.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/keybind.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/keybind.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/line-comment.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/line-comment.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/list.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/list.stories.tsx (line 15, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/logo.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/logo.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/markdown.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/markdown.stories.tsx (line 12, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/message-nav.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-nav.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/message-part.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/message-part.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/popover.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/popover.stories.tsx (line 16, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/progress-circle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress-circle.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/progress.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/progress.stories.tsx (line 15, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/provider-icon.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/provider-icon.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/radio-group.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/radio-group.stories.tsx (line 13, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/resize-handle.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/resize-handle.stories.tsx (line 17, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/select.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/select.stories.tsx (line 16, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/session-review.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-review.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/session-turn.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-turn.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/spinner.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/spinner.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/sticky-accordion-header.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/sticky-accordion-header.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/switch.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/switch.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/tabs.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tabs.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/tag.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tag.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/text-field.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-field.stories.tsx (line 14, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/text-shimmer.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/text-shimmer.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/toast.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/toast.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/tooltip.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/tooltip.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+- ./../ui/src/components/typewriter.stories.tsx: CSF: default export must be an object /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/typewriter.stories.tsx (line 6, col 0)
+
+More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
+    at _StoryIndexGenerator.getIndexAndStats (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/[email protected]+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:6085:15)
+    at async _StoryIndexGenerator.getIndex (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/[email protected]+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:6074:13)
+    at async getOptimizeDeps (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/@[email protected]+a2a25316dbcddd7f/node_modules/@storybook/builder-vite/dist/index.js:1862:15)
+    at async createViteServer (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/@[email protected]+a2a25316dbcddd7f/node_modules/@storybook/builder-vite/dist/index.js:1888:19)
+    at async Module.start (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/@[email protected]+a2a25316dbcddd7f/node_modules/@storybook/builder-vite/dist/index.js:1923:17)
+    at async storybookDevServer (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/[email protected]+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:7241:83)
+    at async buildOrThrow (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/[email protected]+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:4504:12)
+    at async buildDevStandalone (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/[email protected]+4edd68b244e756bb/node_modules/storybook/dist/core-server/index.js:7611:66)
+    at async withTelemetry (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/[email protected]+4edd68b244e756bb/node_modules/storybook/dist/_node-chunks/chunk-S3MWHNYJ.js:218:12)
+    at async dev (file:///Users/davidhill/Documents/Local/opencode/node_modules/.bun/[email protected]+4edd68b244e756bb/node_modules/storybook/dist/bin/core.js:2734:3)
+[14:25:49.118] [WARN] Broken build, fix the error above.
+You may need to refresh the browser.

+ 28 - 0
packages/storybook/package.json

@@ -0,0 +1,28 @@
+{
+  "$schema": "https://json.schemastore.org/package.json",
+  "name": "@opencode-ai/storybook",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "storybook": "storybook dev -p 6006",
+    "build": "storybook build"
+  },
+  "devDependencies": {
+    "@opencode-ai/ui": "workspace:*",
+    "@solidjs/meta": "catalog:",
+    "@storybook/addon-a11y": "^10.2.10",
+    "@storybook/addon-docs": "^10.2.10",
+    "@storybook/addon-links": "^10.2.10",
+    "@storybook/addon-onboarding": "^10.2.10",
+    "@storybook/addon-vitest": "^10.2.10",
+    "@tsconfig/node22": "catalog:",
+    "@types/node": "catalog:",
+    "@types/react": "18.0.25",
+    "react": "18.2.0",
+    "solid-js": "catalog:",
+    "storybook": "^10.2.10",
+    "storybook-solidjs-vite": "^10.0.9",
+    "typescript": "catalog:",
+    "vite": "catalog:"
+  }
+}

+ 16 - 0
packages/storybook/tsconfig.json

@@ -0,0 +1,16 @@
+{
+  "$schema": "https://json.schemastore.org/tsconfig",
+  "extends": "@tsconfig/node22/tsconfig.json",
+  "compilerOptions": {
+    "jsx": "preserve",
+    "jsxImportSource": "solid-js",
+    "target": "ESNext",
+    "lib": ["es2023", "dom", "dom.iterable"],
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "noEmit": true,
+    "strict": true,
+    "types": ["vite/client", "node"]
+  },
+  "include": [".storybook/**/*.ts", ".storybook/**/*.tsx"]
+}

+ 1 - 0
packages/ui/package.json

@@ -4,6 +4,7 @@
   "type": "module",
   "license": "MIT",
   "exports": {
+    "./package.json": "./package.json",
     "./*": "./src/components/*.tsx",
     "./i18n/*": "./src/i18n/*.ts",
     "./pierre": "./src/pierre/index.ts",

+ 7 - 0
packages/ui/src/assets/icons/provider/302ai.svg

@@ -0,0 +1,7 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display: block;" viewBox="0 0 2048 2048" width="1046" height="1046" preserveAspectRatio="none">
+<path transform="translate(0,0)" fill="rgb(156,155,155)" d="M 388.193 1682.23 C 380.878 1674.23 349.014 1650.79 338.487 1642.13 C 148.161 1485.91 28.1107 1260.14 5.01063 1015 C -18.4493 771.014 55.9168 527.694 211.767 338.509 C 367.861 148.455 593.42 28.6444 838.288 5.7197 C 1092.75 -18.7181 1345.95 63.524 1537.48 232.828 C 1567.76 259.726 1596.32 288.496 1623 318.968 C 1631.78 329.058 1640.32 339.36 1648.6 349.865 C 1654.15 356.824 1662.64 368.871 1669.4 374.06 C 1866.4 518.124 1998.49 734.204 2036.88 975.222 C 2041.98 1007.66 2045.11 1039.38 2046.62 1072.12 C 2046.85 1077.09 2047.16 1082.22 2048 1087.12 L 2048 1155.79 L 2047.85 1156.71 C 2045.71 1170.93 2045.27 1196.57 2043.86 1212.22 C 2040.62 1246.59 2035.38 1280.75 2028.17 1314.51 C 1981.27 1534.16 1856.1 1729.25 1675.99 1863.43 C 1479.61 2010.02 1232.99 2072.49 990.498 2037.07 C 801.767 2009.86 626.135 1924.74 487.842 1793.46 C 455.956 1763.37 418.158 1722.72 392.022 1687.5 C 390.729 1685.76 389.452 1684 388.193 1682.23 z"/>
+<path transform="translate(0,0)" fill="rgb(117,116,116)" d="M 1669.4 374.06 C 1866.4 518.124 1998.49 734.204 2036.88 975.222 C 2041.98 1007.66 2045.11 1039.38 2046.62 1072.12 C 2046.85 1077.09 2047.16 1082.22 2048 1087.12 L 2048 1155.79 L 2047.85 1156.71 C 2045.71 1170.93 2045.27 1196.57 2043.86 1212.22 C 2040.62 1246.59 2035.38 1280.75 2028.17 1314.51 C 1981.27 1534.16 1856.1 1729.25 1675.99 1863.43 C 1479.61 2010.02 1232.99 2072.49 990.498 2037.07 C 801.767 2009.86 626.135 1924.74 487.842 1793.46 C 455.956 1763.37 418.158 1722.72 392.022 1687.5 C 390.729 1685.76 389.452 1684 388.193 1682.23 C 394.373 1684.07 421.092 1702.62 428.047 1707.15 C 439.611 1714.6 451.324 1721.83 463.177 1728.82 C 490.564 1744.99 528.003 1763.59 557.361 1776.32 C 782.899 1873.92 1037.96 1878.01 1266.51 1787.69 C 1495.36 1696.62 1678.57 1518.24 1775.72 1291.9 C 1877.79 1053.06 1875.1 782.375 1768.31 545.611 C 1753.03 511.993 1733.8 474.565 1714.15 443.177 C 1706.81 431.355 1699.2 419.704 1691.32 408.231 C 1685.67 400.055 1672.56 382.889 1669.4 374.06 z"/>
+<path transform="translate(0,0)" fill="rgb(254,254,254)" d="M 907.581 300.401 C 923.66 299.084 947.483 300.532 962.897 302.556 C 1041.39 313.102 1112.41 354.577 1160.17 417.755 C 1202.42 473.452 1228.57 555 1218.78 624.854 C 1256.03 619.819 1285.79 618.643 1323.38 625.442 C 1401.3 639.582 1470.3 684.357 1514.95 749.754 C 1560.04 815.479 1576.85 896.56 1561.6 974.792 C 1546.59 1052.29 1501.28 1120.59 1435.71 1164.55 C 1366.19 1211.67 1287.89 1223.85 1206.7 1207.99 L 1208.16 1225.92 C 1213.72 1304.44 1187.72 1381.94 1135.93 1441.23 C 1080.14 1505.71 1008.69 1536.8 924.661 1542.84 C 910.37 1543.02 898.883 1543.12 884.551 1541.84 C 806.009 1534.5 733.606 1496.24 683.294 1435.48 C 630.495 1371.76 609.152 1293.49 617.004 1211.82 C 577.182 1218.28 543.704 1219.15 503.598 1211.31 C 425.862 1195.87 357.514 1150.02 313.752 1083.94 C 269.997 1017.95 254.398 937.224 270.419 859.682 C 286.452 782.387 332.522 714.62 398.502 671.28 C 468.674 625.191 546.96 613.555 628.149 630.32 C 625.459 583.013 627.036 545.389 643.173 499.662 C 684.204 383.394 785.737 308.984 907.581 300.401 z"/>
+<path transform="translate(0,0)" fill="rgb(156,155,155)" d="M 907.659 407.406 C 928.022 404.422 959.425 408.947 978.901 414.989 C 1028.11 429.972 1069.15 464.261 1092.65 510.025 C 1116.27 555.792 1120.24 609.2 1103.65 657.957 C 1098.74 672.384 1090.33 686.336 1086.3 699.186 C 1082.14 712.53 1083.57 726.994 1090.26 739.265 C 1100.24 757.657 1118.84 768.259 1139.57 767.119 C 1155.78 766.227 1164.63 759.273 1178.02 751.678 C 1221.56 726.903 1273.23 720.668 1321.41 734.376 C 1370.6 748.209 1412.18 781.215 1436.82 825.985 C 1461.35 870.569 1467.02 923.112 1452.58 971.905 C 1438.06 1020.82 1404.54 1061.88 1359.51 1085.9 C 1280.31 1128.12 1181.39 1108.75 1124.87 1039.74 C 1113.7 1026.12 1105.83 1013.42 1087.4 1008.03 C 1041 993.798 1000.36 1044.75 1026.36 1086.49 C 1041.95 1111.53 1062.76 1127.17 1078.18 1153.03 C 1090.26 1175.57 1097.84 1197.66 1100.84 1223.2 C 1107.04 1273.73 1092.65 1324.64 1060.9 1364.44 C 1029.42 1404.28 983.238 1429.79 932.752 1435.23 C 882.189 1440.86 831.491 1425.87 792.121 1393.65 C 752.588 1361.54 727.605 1314.91 722.781 1264.21 C 718.894 1225.82 727.653 1167.83 758.478 1141.35 C 771.141 1130.47 781.921 1118.81 792.404 1105.84 C 869.373 1010.12 879.456 876.886 817.771 770.669 C 809.316 756.055 799.574 742.224 788.661 729.342 C 782.235 721.815 773.191 712.791 767.461 705.187 C 749.008 680.699 737.483 646.859 734.444 616.659 C 729.188 565.849 744.584 515.06 777.168 475.721 C 810.289 435.451 856.055 412.394 907.659 407.406 z"/>
+<path transform="translate(0,0)" fill="rgb(156,155,155)" d="M 554.228 729.711 C 659.104 725.931 747.227 807.802 751.164 912.672 C 755.1 1017.54 673.362 1105.79 568.498 1109.88 C 463.411 1113.98 374.937 1032.03 370.992 926.942 C 367.047 821.849 449.129 733.498 554.228 729.711 z"/>
+</svg>

+ 3 - 0
packages/ui/src/assets/icons/provider/berget.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 463 419" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M208.739 17L255.261 17L446 403L398 403L313.5 255L261.5 176L233.163 96.1677L237.815 98.6522H226.185L230.837 96.1677L113 331L64.5 403L18 403L208.739 17Z" fill="currentColor"/>
+</svg>

+ 5 - 0
packages/ui/src/assets/icons/provider/cloudferro-sherlock.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
+<path d="M12,24l-8.996,-8.996l8.996,-8.997l2.996,2.997l-5.999,6l2.996,2.997l6,-5.999l2.997,2.996l-8.99,8.996Z" opacity="0.01"/>
+<path d="M9,15l6,-6l-3,-3l-9,9l-3,-3l12,-12l12,12l-3,3l-3,-3l-6,6l-3,-3Z"/>
+<path d="M0,12L12,0L24,12L12,24L0,12Z" fill="none" stroke="currentColor" stroke-width="0.2"/>
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 12 - 0
packages/ui/src/assets/icons/provider/firmware.svg


+ 3 - 0
packages/ui/src/assets/icons/provider/gitlab.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 380 380" xmlns="http://www.w3.org/2000/svg">
+<path d="M265.26416,174.37243l-.2134-.55822-21.19899-55.30908c-.4236-1.08359-1.18542-1.99642-2.17699-2.62689-.98837-.63373-2.14749-.93253-3.32305-.87014-1.1689.06239-2.29195.48925-3.20809,1.21821-.90957.73554-1.56629,1.73047-1.87493,2.85346l-14.31327,43.80662h-57.90965l-14.31327-43.80662c-.30864-1.12299-.96536-2.11791-1.87493-2.85346-.91614-.72895-2.03911-1.15582-3.20809-1.21821-1.17548-.06239-2.33468.23641-3.32297.87014-.99166.63047-1.75348,1.5433-2.17707,2.62689l-21.19891,55.31237-.21348.55493c-6.28158,16.38521-.92929,34.90803,13.05891,45.48782.02621.01641.04922.03611.07552.05582l.18719.14119,32.29094,24.17392,15.97151,12.09024,9.71951,7.34871c2.34117,1.77316,5.57877,1.77316,7.92002,0l9.71943-7.34871,15.96822-12.09024,32.48142-24.31511c.02958-.02299.05588-.04269.08538-.06568,13.97834-10.57977,19.32735-29.09604,13.04905-45.47796Z" fill="currentColor"/>
+</svg>

+ 4 - 0
packages/ui/src/assets/icons/provider/jiekou.svg

@@ -0,0 +1,4 @@
+<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M16 4H4V20H16C18.2091 20 20 18.2091 20 16V8H24V16C24 20.4183 20.4183 24 16 24H4C1.79086 24 1.61064e-08 22.2091 0 20V0H16V4Z" fill="currentColor"/>
+<path d="M20 4H24V0H20V4Z" fill="currentColor"/>
+</svg>

+ 4 - 0
packages/ui/src/assets/icons/provider/kilo.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
+  <path d="M5 3v18" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/>
+  <path d="M17.5 4.5 9.5 12l8 7.5" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 3 - 0
packages/ui/src/assets/icons/provider/kuae-cloud-coding-plan.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
+<path d="M20.1312 7.5L17.4088 11.1912H5.81625L8.5375 7.5H20.1325H20.1312ZM34.0675 28.81L31.3475 32.5H19.795L22.5125 28.81H34.0675ZM35 7.5L16.58 32.5H5L23.42 7.5H35Z" fill="currentColor"/>
+</svg>

+ 7 - 0
packages/ui/src/assets/icons/provider/meganova.svg

@@ -0,0 +1,7 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12 4C8.5 4 5.5 6 4 8.5C3.2 9.9 3 11 3 12C3 13 3.2 14.1 4 15.5C5.5 18 8.5 20 12 20C15.5 20 18.5 18 20 15.5C20.8 14.1 21 13 21 12C21 11 20.8 9.9 20 8.5C18.5 6 15.5 4 12 4Z" fill="currentColor" fill-opacity="0"/>
+<path d="M12 5C7.5 5 4.5 8.5 3.5 11C3.2 11.6 3 12 3 12.5C3.5 12 5 10.5 7 9.5C9 8.5 10.5 8 12 8C13.5 8 15 8.5 17 9.5C19 10.5 20.5 12 21 12.5C21 12 20.8 11.6 20.5 11C19.5 8.5 16.5 5 12 5Z" fill="currentColor"/>
+<path d="M5.5 14C6.5 15.5 8 16.5 9.5 17L8 19.5C6 18.5 4.5 16.5 3.5 14.5L5.5 14Z" fill="currentColor"/>
+<path d="M18.5 14C17.5 15.5 16 16.5 14.5 17L16 19.5C18 18.5 19.5 16.5 20.5 14.5L18.5 14Z" fill="currentColor"/>
+<path d="M12 8C10.5 8 9 8.5 7 9.5C5 10.5 3.5 12 3 12.5C3.5 13 5 14.5 7 15.5C9 16.5 10.5 17 12 17C13.5 17 15 16.5 17 15.5C19 14.5 20.5 13 21 12.5C20.5 12 19 10.5 17 9.5C15 8.5 13.5 8 12 8ZM12 14.5C10.6 14.5 9.5 13.4 9.5 12C9.5 10.6 10.6 9.5 12 9.5C13.4 9.5 14.5 10.6 14.5 12C14.5 13.4 13.4 14.5 12 14.5Z" fill="currentColor"/>
+</svg>

+ 24 - 0
packages/ui/src/assets/icons/provider/minimax-cn-coding-plan.svg

@@ -0,0 +1,24 @@
+<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    shape-rendering="geometricPrecision"
+    d="M9.8132 15.9038L9 18.75L8.1868 15.9038C7.75968 14.4089 6.59112 13.2403 5.09619 12.8132L2.25 12L5.09619 11.1868C6.59113 10.7597 7.75968 9.59112 8.1868 8.09619L9 5.25L9.8132 8.09619C10.2403 9.59113 11.4089 10.7597 12.9038 11.1868L15.75 12L12.9038 12.8132C11.4089 13.2403 10.2403 14.4089 9.8132 15.9038Z"
+    stroke="currentColor"
+    stroke-width="1.5"
+    stroke-linecap="round"
+    stroke-linejoin="round"
+  />
+  <path
+    d="M18.2589 8.71454L18 9.75L17.7411 8.71454C17.4388 7.50533 16.4947 6.56117 15.2855 6.25887L14.25 6L15.2855 5.74113C16.4947 5.43883 17.4388 4.49467 17.7411 3.28546L18 2.25L18.2589 3.28546C18.5612 4.49467 19.5053 5.43883 20.7145 5.74113L21.75 6L20.7145 6.25887C19.5053 6.56117 18.5612 7.50533 18.2589 8.71454Z"
+    stroke="currentColor"
+    stroke-width="1.5"
+    stroke-linecap="round"
+    stroke-linejoin="round"
+  />
+  <path
+    d="M16.8942 20.5673L16.5 21.75L16.1058 20.5673C15.8818 19.8954 15.3546 19.3682 14.6827 19.1442L13.5 18.75L14.6827 18.3558C15.3546 18.1318 15.8818 17.6046 16.1058 16.9327L16.5 15.75L16.8942 16.9327C17.1182 17.6046 17.6454 18.1318 18.3173 18.3558L19.5 18.75L18.3173 19.1442C17.6454 19.3682 17.1182 19.8954 16.8942 20.5673Z"
+    stroke="currentColor"
+    stroke-width="1.5"
+    stroke-linecap="round"
+    stroke-linejoin="round"
+  />
+</svg>

+ 24 - 0
packages/ui/src/assets/icons/provider/minimax-coding-plan.svg

@@ -0,0 +1,24 @@
+<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    shape-rendering="geometricPrecision"
+    d="M9.8132 15.9038L9 18.75L8.1868 15.9038C7.75968 14.4089 6.59112 13.2403 5.09619 12.8132L2.25 12L5.09619 11.1868C6.59113 10.7597 7.75968 9.59112 8.1868 8.09619L9 5.25L9.8132 8.09619C10.2403 9.59113 11.4089 10.7597 12.9038 11.1868L15.75 12L12.9038 12.8132C11.4089 13.2403 10.2403 14.4089 9.8132 15.9038Z"
+    stroke="currentColor"
+    stroke-width="1.5"
+    stroke-linecap="round"
+    stroke-linejoin="round"
+  />
+  <path
+    d="M18.2589 8.71454L18 9.75L17.7411 8.71454C17.4388 7.50533 16.4947 6.56117 15.2855 6.25887L14.25 6L15.2855 5.74113C16.4947 5.43883 17.4388 4.49467 17.7411 3.28546L18 2.25L18.2589 3.28546C18.5612 4.49467 19.5053 5.43883 20.7145 5.74113L21.75 6L20.7145 6.25887C19.5053 6.56117 18.5612 7.50533 18.2589 8.71454Z"
+    stroke="currentColor"
+    stroke-width="1.5"
+    stroke-linecap="round"
+    stroke-linejoin="round"
+  />
+  <path
+    d="M16.8942 20.5673L16.5 21.75L16.1058 20.5673C15.8818 19.8954 15.3546 19.3682 14.6827 19.1442L13.5 18.75L14.6827 18.3558C15.3546 18.1318 15.8818 17.6046 16.1058 16.9327L16.5 15.75L16.8942 16.9327C17.1182 17.6046 17.6454 18.1318 18.3173 18.3558L19.5 18.75L18.3173 19.1442C17.6454 19.3682 17.1182 19.8954 16.8942 20.5673Z"
+    stroke="currentColor"
+    stroke-width="1.5"
+    stroke-linecap="round"
+    stroke-linejoin="round"
+  />
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
packages/ui/src/assets/icons/provider/moark.svg


+ 3 - 0
packages/ui/src/assets/icons/provider/nova.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
+  <path d="M13.48 17.46L14.64 18.62C14.69 18.67 14.71 18.75 14.68 18.82L12.87 23.41C12.73 23.76 12.38 24 12 24C11.62 24 11.27 23.76 11.13 23.41L8.52 16.80L4.31 21.02C4.24 21.09 4.12 21.09 4.05 21.02L2.98 19.95C2.91 19.88 2.91 19.76 2.98 19.69L8.18 14.49C8.35 14.32 8.58 14.21 8.81 14.19C9.23 14.17 9.60 14.42 9.74 14.78L12.00 20.51L13.18 17.52C13.23 17.40 13.39 17.37 13.49 17.46H13.48ZM19.69 2.98L15.48 7.20L12.87 0.59C12.71 0.17 12.26 -0.08 11.79 0.02C11.48 0.09 11.23 0.33 11.12 0.63L9.32 5.18C9.29 5.25 9.31 5.33 9.36 5.38L10.52 6.54C10.61 6.63 10.77 6.60 10.82 6.47L12 3.49L14.26 9.21C14.37 9.51 14.63 9.72 14.94 9.79C15.00 9.80 15.07 9.81 15.13 9.81C15.38 9.81 15.62 9.71 15.79 9.53L21.02 4.31C21.09 4.24 21.09 4.12 21.02 4.04L19.96 2.98C19.88 2.91 19.76 2.91 19.69 2.98L19.69 2.98ZM6.47 13.17L3.49 12.00L9.21 9.74C9.58 9.59 9.83 9.23 9.81 8.81C9.79 8.57 9.68 8.35 9.51 8.18L4.31 2.98C4.24 2.91 4.12 2.91 4.05 2.98L2.98 4.05C2.91 4.12 2.91 4.24 2.98 4.31L7.20 8.52L0.59 11.13C0.24 11.27 0 11.62 0 12.00C0 12.38 0.24 12.73 0.59 12.87L5.18 14.68C5.25 14.71 5.33 14.69 5.38 14.64L6.54 13.48C6.64 13.39 6.60 13.23 6.48 13.17H6.47ZM23.41 11.13L18.82 9.32C18.75 9.29 18.67 9.31 18.62 9.36L17.46 10.52C17.36 10.61 17.40 10.77 17.52 10.82L20.51 12.00L14.78 14.26C14.42 14.40 14.17 14.77 14.19 15.19C14.21 15.42 14.32 15.65 14.49 15.82L19.69 21.02C19.76 21.09 19.88 21.09 19.95 21.02L21.02 19.95C21.09 19.88 21.09 19.76 21.02 19.69L16.80 15.48L23.41 12.87C23.76 12.73 24 12.38 24 12.00C24 11.62 23.76 11.27 23.41 11.13V11.13Z"/>
+</svg>

+ 10 - 0
packages/ui/src/assets/icons/provider/novita-ai.svg

@@ -0,0 +1,10 @@
+<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3135_1230)">
+<path d="M15.5564 8.26172V16.5239L2.1875 29.8928H15.5564V21.6302L23.8194 29.8928H37.1875L15.5564 8.26172Z" fill="black"/>
+</g>
+<defs>
+<clipPath id="clip0_3135_1230">
+<rect width="35" height="21.6311" fill="white" transform="translate(2.1875 8.26172)"/>
+</clipPath>
+</defs>
+</svg>

+ 5 - 0
packages/ui/src/assets/icons/provider/privatemode-ai.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="61" height="63" viewBox="0 0 61 63" fill="none">
+<path d="M13.2167 6.71884C13.2167 10.4296 10.258 13.4377 6.60833 13.4377C2.95865 13.4377 0 10.4296 0 6.71884C0 3.00813 2.95865 0 6.60833 0C10.258 0 13.2167 3.00813 13.2167 6.71884Z" fill="currentColor"/>
+<path d="M16.2667 22.7407H28.4667V62.0201H16.2667V22.7407Z" fill="currentColor"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M38.6333 33.0774C44.2482 33.0774 48.8 28.4495 48.8 22.7407C48.8 17.0319 44.2482 12.404 38.6333 12.404C33.0184 12.404 28.4667 17.0319 28.4667 22.7407C28.4667 28.4495 33.0184 33.0774 38.6333 33.0774ZM38.6333 45.4814C50.9861 45.4814 61 35.3 61 22.7407C61 10.1814 50.9861 0 38.6333 0C26.2806 0 16.2667 10.1814 16.2667 22.7407C16.2667 35.3 26.2806 45.4814 38.6333 45.4814Z" fill="currentColor"/>
+</svg>

+ 9 - 0
packages/ui/src/assets/icons/provider/qihang-ai.svg

@@ -0,0 +1,9 @@
+<svg width="24" height="24" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path 
+    d="M20 2L36.4 11V29L20 38L3.6 29V11L20 2Z M20 20V2 M20 20L36.4 11 M20 20L36.4 29 M20 20V38 M20 20L3.6 29 M20 20L3.6 11" 
+    stroke="currentColor" 
+    stroke-width="3" 
+    stroke-linecap="round" 
+    stroke-linejoin="round"
+  />
+</svg>

+ 7 - 0
packages/ui/src/assets/icons/provider/qiniu-ai.svg

@@ -0,0 +1,7 @@
+<svg viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g fill="currentColor" fill-rule="nonzero">
+            <path d="M23.568,4.37284513 C23.180619,4.24052218 22.8592977,4.40224993 22.6756964,4.54008596 C20.1236928,7.55961616 16.310265,9.47645197 12.0489207,9.47645197 C10.5856349,9.47645197 9.17375432,9.25040654 7.84814258,8.82954254 C7.5029753,7.57615888 7.25878488,6.68481809 7.25878488,6.68481809 C7.25878488,6.68481809 6.97237307,5.81185554 6.00112785,5.96071698 L6.28019714,8.22306831 C4.39829587,7.36296976 2.74038351,6.09854994 1.42029592,4.53641031 C1.23669698,4.39857428 0.915396329,4.23684654 0.528,4.36916956 C1.62959981,7.32069904 3.8328071,9.73559053 6.63638202,11.1121076 L7.40015707,17.309184 C7.40015707,17.309184 7.74165693,19.68 9.96508874,19.68 L14.7184659,19.68 C16.9418594,19.68 17.2833592,17.309184 17.2833592,17.309184 L17.8212733,12.8690689 C16.3782427,12.7514113 15.4693667,13.7585665 15.1829548,14.7509761 C14.7001288,16.4233728 14.7001288,16.5318144 14.6046071,16.8221952 C14.4100343,17.4194688 13.7692329,17.4912 13.7692329,17.4912 L10.9179278,17.4912 C10.9179278,17.4912 10.2772032,17.4194688 10.0825536,16.8221952 C9.95772321,16.4381184 9.32981155,14.1868033 8.69821712,11.9023719 C9.76307366,12.2037505 10.8885424,12.3654913 12.0507621,12.3654913 C17.3237162,12.3710209 21.8219851,9.04456718 23.568,4.37284513 Z"></path>
+        </g>
+    </g>
+</svg>

+ 4 - 0
packages/ui/src/assets/icons/provider/stackit.svg

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="STACKIT" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 41.536063 41.536063">
+  <path d="M13.9510149,10.4421038h20.8374634l-1.1608887,5.4412842h-14.5545654l-1.3522339,6.3352661h-6.2828369l2.5130615-11.7765503ZM22.5114397,25.652614H7.9081072l-1.1605225,5.4413452h20.8855591l2.5210571-11.8130493h-6.2828979l-1.3598633,6.3717041Z" fill="currentColor"/>
+</svg>

+ 24 - 0
packages/ui/src/assets/icons/provider/stepfun.svg

@@ -0,0 +1,24 @@
+<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path
+    shape-rendering="geometricPrecision"
+    d="M9.8132 15.9038L9 18.75L8.1868 15.9038C7.75968 14.4089 6.59112 13.2403 5.09619 12.8132L2.25 12L5.09619 11.1868C6.59113 10.7597 7.75968 9.59112 8.1868 8.09619L9 5.25L9.8132 8.09619C10.2403 9.59113 11.4089 10.7597 12.9038 11.1868L15.75 12L12.9038 12.8132C11.4089 13.2403 10.2403 14.4089 9.8132 15.9038Z"
+    stroke="currentColor"
+    stroke-width="1.5"
+    stroke-linecap="round"
+    stroke-linejoin="round"
+  />
+  <path
+    d="M18.2589 8.71454L18 9.75L17.7411 8.71454C17.4388 7.50533 16.4947 6.56117 15.2855 6.25887L14.25 6L15.2855 5.74113C16.4947 5.43883 17.4388 4.49467 17.7411 3.28546L18 2.25L18.2589 3.28546C18.5612 4.49467 19.5053 5.43883 20.7145 5.74113L21.75 6L20.7145 6.25887C19.5053 6.56117 18.5612 7.50533 18.2589 8.71454Z"
+    stroke="currentColor"
+    stroke-width="1.5"
+    stroke-linecap="round"
+    stroke-linejoin="round"
+  />
+  <path
+    d="M16.8942 20.5673L16.5 21.75L16.1058 20.5673C15.8818 19.8954 15.3546 19.3682 14.6827 19.1442L13.5 18.75L14.6827 18.3558C15.3546 18.1318 15.8818 17.6046 16.1058 16.9327L16.5 15.75L16.8942 16.9327C17.1182 17.6046 17.6454 18.1318 18.3173 18.3558L19.5 18.75L18.3173 19.1442C17.6454 19.3682 17.1182 19.8954 16.8942 20.5673Z"
+    stroke="currentColor"
+    stroke-width="1.5"
+    stroke-linecap="round"
+    stroke-linejoin="round"
+  />
+</svg>

+ 4 - 0
packages/ui/src/assets/icons/provider/vivgrid.svg

@@ -0,0 +1,4 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M 10.921 1.266 C 11.477 0.952 12.161 0.952 12.717 1.266 L 14.079 2.037 C 12.108 3.127 9.934 4.437 8.274 5.788 C 7.425 6.479 6.706 7.185 6.216 7.879 C 5.727 8.574 5.458 9.268 5.53 9.935 C 5.625 10.839 6.133 11.432 6.825 11.858 C 7.512 12.283 8.388 12.55 9.232 12.809 C 9.968 13.033 10.681 13.253 11.245 13.56 C 11.808 13.868 12.214 14.257 12.353 14.819 C 12.492 15.384 12.369 16.145 11.825 17.206 C 11.286 18.255 10.342 19.586 8.861 21.293 L 2.91 17.924 C 2.356 17.608 2.013 17.028 2.013 16.398 L 2.013 7.327 C 2.013 6.697 2.356 6.116 2.91 5.802 L 10.921 1.266 Z" fill="currentColor" stroke="currentColor"/>
+<path d="M 21.122 6.009 C 21.677 6.324 22.019 6.904 22.019 7.534 L 22.019 16.606 C 22.019 17.235 21.677 17.816 21.122 18.131 L 13.11 22.667 C 12.555 22.981 11.872 22.981 11.314 22.667 L 10.388 22.142 C 10.772 21.78 11.159 21.413 11.55 21.047 C 12.722 19.945 13.901 18.834 14.928 17.765 C 15.953 16.698 16.825 15.667 17.382 14.721 C 17.936 13.778 18.187 12.902 17.93 12.154 C 17.671 11.401 16.913 10.813 15.54 10.415 C 13.986 9.966 12.92 9.457 12.272 8.908 C 11.628 8.362 11.403 7.78 11.507 7.17 C 11.617 6.55 12.069 5.883 12.824 5.18 C 13.572 4.488 14.606 3.77 15.874 3.039 L 21.122 6.009 Z" fill="currentColor" stroke="currentColor"/>
+</svg>

+ 149 - 0
packages/ui/src/components/accordion.stories.tsx

@@ -0,0 +1,149 @@
+// @ts-nocheck
+import { createEffect, createSignal } from "solid-js"
+import * as mod from "./accordion"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Accordion for collapsible content sections with optional multi-open behavior.
+
+Use one trigger per item; keep content concise.
+
+### API
+- Root supports Kobalte Accordion props: \`value\`, \`multiple\`, \`collapsible\`, \`onChange\`.
+- Compose with \`Accordion.Item\`, \`Header\`, \`Trigger\`, \`Content\`.
+
+### Variants and states
+- Single or multiple open items.
+- Collapsible or fixed-open behavior.
+
+### Behavior
+- Controlled via \`value\`/\`onChange\` when provided.
+
+### Accessibility
+- TODO: confirm keyboard navigation from Kobalte Accordion.
+
+### Theming/tokens
+- Uses \`data-component="accordion"\` and slot data attributes.
+
+`
+
+const story = create({ title: "UI/Accordion", mod })
+export default {
+  title: "UI/Accordion",
+  id: "components-accordion",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+export const Basic = {
+  args: {
+    collapsible: true,
+    multiple: false,
+    value: "first",
+  },
+  argTypes: {
+    collapsible: { control: "boolean" },
+    multiple: { control: "boolean" },
+    value: {
+      control: "select",
+      options: ["first", "second", "none"],
+      mapping: {
+        none: undefined,
+      },
+    },
+  },
+  render: (props) => {
+    const [value, setValue] = createSignal(props.value)
+    createEffect(() => {
+      setValue(props.value)
+    })
+
+    const current = () => {
+      if (props.multiple) {
+        if (Array.isArray(value())) return value()
+        if (value()) return [value()]
+        return []
+      }
+
+      if (Array.isArray(value())) return value()[0]
+      return value()
+    }
+
+    return (
+      <div style={{ display: "grid", gap: "8px", width: "420px" }}>
+        <mod.Accordion collapsible={props.collapsible} multiple={props.multiple} value={current()} onChange={setValue}>
+          <mod.Accordion.Item value="first">
+            <mod.Accordion.Header>
+              <mod.Accordion.Trigger>First</mod.Accordion.Trigger>
+            </mod.Accordion.Header>
+            <mod.Accordion.Content>
+              <div style={{ color: "var(--text-weak)", padding: "8px 0" }}>Accordion content.</div>
+            </mod.Accordion.Content>
+          </mod.Accordion.Item>
+          <mod.Accordion.Item value="second">
+            <mod.Accordion.Header>
+              <mod.Accordion.Trigger>Second</mod.Accordion.Trigger>
+            </mod.Accordion.Header>
+            <mod.Accordion.Content>
+              <div style={{ color: "var(--text-weak)", padding: "8px 0" }}>More content.</div>
+            </mod.Accordion.Content>
+          </mod.Accordion.Item>
+        </mod.Accordion>
+      </div>
+    )
+  },
+}
+
+export const Multiple = {
+  args: {
+    collapsible: true,
+    multiple: true,
+    value: ["first", "second"],
+  },
+  render: (props) => (
+    <mod.Accordion collapsible={props.collapsible} multiple={props.multiple} value={props.value}>
+      <mod.Accordion.Item value="first">
+        <mod.Accordion.Header>
+          <mod.Accordion.Trigger>First</mod.Accordion.Trigger>
+        </mod.Accordion.Header>
+        <mod.Accordion.Content>
+          <div style={{ color: "var(--text-weak)", padding: "8px 0" }}>Accordion content.</div>
+        </mod.Accordion.Content>
+      </mod.Accordion.Item>
+      <mod.Accordion.Item value="second">
+        <mod.Accordion.Header>
+          <mod.Accordion.Trigger>Second</mod.Accordion.Trigger>
+        </mod.Accordion.Header>
+        <mod.Accordion.Content>
+          <div style={{ color: "var(--text-weak)", padding: "8px 0" }}>More content.</div>
+        </mod.Accordion.Content>
+      </mod.Accordion.Item>
+    </mod.Accordion>
+  ),
+}
+
+export const NonCollapsible = {
+  args: {
+    collapsible: false,
+    multiple: false,
+    value: "first",
+  },
+  render: (props) => (
+    <mod.Accordion collapsible={props.collapsible} multiple={props.multiple} value={props.value}>
+      <mod.Accordion.Item value="first">
+        <mod.Accordion.Header>
+          <mod.Accordion.Trigger>First</mod.Accordion.Trigger>
+        </mod.Accordion.Header>
+        <mod.Accordion.Content>
+          <div style={{ color: "var(--text-weak)", padding: "8px 0" }}>Accordion content.</div>
+        </mod.Accordion.Content>
+      </mod.Accordion.Item>
+    </mod.Accordion>
+  ),
+}

+ 69 - 0
packages/ui/src/components/app-icon.stories.tsx

@@ -0,0 +1,69 @@
+// @ts-nocheck
+import { iconNames } from "./app-icons/types"
+import * as mod from "./app-icon"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Application icon renderer for known editor/terminal apps.
+
+Use in provider or app selection lists.
+
+### API
+- Required: \`id\` (app icon name).
+- Accepts standard img props except \`src\`.
+
+### Variants and states
+- Auto-switches themed icons when available.
+
+### Behavior
+- Watches color scheme changes to swap themed assets.
+
+### Accessibility
+- Provide \`alt\` text when the icon conveys meaning.
+
+### Theming/tokens
+- Uses \`data-component="app-icon"\`.
+
+`
+
+const story = create({ title: "UI/AppIcon", mod, args: { id: "vscode" } })
+export default {
+  title: "UI/AppIcon",
+  id: "components-app-icon",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    id: {
+      control: "select",
+      options: iconNames,
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const AllIcons = {
+  render: () => (
+    <div
+      style={{
+        display: "grid",
+        gap: "12px",
+        "grid-template-columns": "repeat(auto-fill, minmax(72px, 1fr))",
+      }}
+    >
+      {iconNames.map((id) => (
+        <div style={{ display: "grid", gap: "6px", "justify-items": "center" }}>
+          <mod.AppIcon id={id} alt={id} />
+          <div style={{ "font-size": "10px", color: "var(--text-weak)", "text-align": "center" }}>{id}</div>
+        </div>
+      ))}
+    </div>
+  ),
+}

+ 76 - 0
packages/ui/src/components/avatar.stories.tsx

@@ -0,0 +1,76 @@
+// @ts-nocheck
+import * as mod from "./avatar"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+User avatar with image fallback to initials.
+
+Use in user lists and headers.
+
+### API
+- Required: \`fallback\` string.
+- Optional: \`src\`, \`background\`, \`foreground\`, \`size\`.
+
+### Variants and states
+- Sizes: small, normal, large.
+- Image vs fallback state.
+
+### Behavior
+- Uses grapheme-aware fallback rendering.
+
+### Accessibility
+- TODO: provide alt text when using images; currently image is decorative.
+
+### Theming/tokens
+- Uses \`data-component="avatar"\` with size and image state attributes.
+
+`
+
+const story = create({ title: "UI/Avatar", mod, args: { fallback: "A" } })
+
+export default {
+  title: "UI/Avatar",
+  id: "components-avatar",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    size: {
+      control: "select",
+      options: ["small", "normal", "large"],
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const WithImage = {
+  args: {
+    src: "https://placehold.co/80x80/png",
+    fallback: "J",
+  },
+}
+
+export const Sizes = {
+  render: () => (
+    <div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
+      <mod.Avatar size="small" fallback="S" />
+      <mod.Avatar size="normal" fallback="N" />
+      <mod.Avatar size="large" fallback="L" />
+    </div>
+  ),
+}
+
+export const CustomColors = {
+  args: {
+    fallback: "C",
+    background: "#1f2a44",
+    foreground: "#f2f5ff",
+  },
+}

+ 133 - 0
packages/ui/src/components/basic-tool.stories.tsx

@@ -0,0 +1,133 @@
+// @ts-nocheck
+import { createSignal } from "solid-js"
+import * as mod from "./basic-tool"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Expandable tool panel with a structured trigger and optional details.
+
+Use structured triggers for consistent layout; custom triggers allowed.
+
+### API
+- Required: \`icon\` and \`trigger\` (structured or custom JSX).
+- Optional: \`status\`, \`defaultOpen\`, \`forceOpen\`, \`defer\`, \`locked\`.
+
+### Variants and states
+- Pending/running status animates the title via TextShimmer.
+
+### Behavior
+- Uses Collapsible; can defer content rendering until open.
+- Locked state prevents closing.
+
+### Accessibility
+- TODO: confirm trigger semantics and aria labeling.
+
+### Theming/tokens
+- Uses \`data-component="tool-trigger"\` and related slots.
+
+`
+
+const story = create({
+  title: "UI/Basic Tool",
+  mod,
+  args: {
+    icon: "mcp",
+    defaultOpen: true,
+    trigger: {
+      title: "Basic Tool",
+      subtitle: "Example subtitle",
+      args: ["--flag", "value"],
+    },
+    children: "Details content",
+  },
+})
+
+export default {
+  title: "UI/Basic Tool",
+  id: "components-basic-tool",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Pending = {
+  args: {
+    status: "pending",
+    trigger: {
+      title: "Running tool",
+      subtitle: "Working...",
+    },
+    children: "Progress details",
+  },
+}
+
+export const Locked = {
+  args: {
+    locked: true,
+    trigger: {
+      title: "Locked tool",
+      subtitle: "Cannot close",
+    },
+    children: "Locked details",
+  },
+}
+
+export const Deferred = {
+  args: {
+    defer: true,
+    defaultOpen: false,
+    trigger: {
+      title: "Deferred tool",
+      subtitle: "Content mounts on open",
+    },
+    children: "Deferred content",
+  },
+}
+
+export const ForceOpen = {
+  args: {
+    forceOpen: true,
+    trigger: {
+      title: "Forced open",
+      subtitle: "Cannot close",
+    },
+    children: "Forced content",
+  },
+}
+
+export const HideDetails = {
+  args: {
+    hideDetails: true,
+    trigger: {
+      title: "Summary only",
+      subtitle: "Details hidden",
+    },
+    children: "Hidden content",
+  },
+}
+
+export const SubtitleAction = {
+  render: () => {
+    const [message, setMessage] = createSignal("Subtitle not clicked")
+    return (
+      <div style={{ display: "grid", gap: "8px" }}>
+        <div style={{ "font-size": "12px", color: "var(--text-weak)" }}>{message()}</div>
+        <mod.BasicTool
+          icon="mcp"
+          trigger={{ title: "Clickable subtitle", subtitle: "Click me" }}
+          onSubtitleClick={() => setMessage("Subtitle clicked")}
+        >
+          Subtitle action details
+        </mod.BasicTool>
+      </div>
+    )
+  },
+}

+ 108 - 0
packages/ui/src/components/button.stories.tsx

@@ -0,0 +1,108 @@
+// @ts-nocheck
+import { Button } from "./button"
+
+const docs = `### Overview
+Primary action button with size, variant, and optional icon support.
+
+Use \`IconButton\` for icon-only actions.
+
+### API
+- \`variant\`: "primary" | "secondary" | "ghost".
+- \`size\`: "small" | "normal" | "large".
+- \`icon\`: Icon name for a leading icon.
+- Inherits Kobalte Button props and native button attributes.
+
+### Variants and states
+- Variants: primary, secondary, ghost.
+- States: disabled.
+
+### Behavior
+- Renders an Icon when \`icon\` is set.
+
+### Accessibility
+- Provide clear label text; use \`aria-label\` for icon-only buttons.
+
+### Theming/tokens
+- Uses \`data-component="button"\` with size/variant data attributes.
+
+`
+
+export default {
+  title: "UI/Button",
+  id: "components-button",
+  component: Button,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  args: {
+    children: "Button",
+    variant: "secondary",
+    size: "normal",
+  },
+  argTypes: {
+    variant: {
+      control: "select",
+      options: ["primary", "secondary", "ghost"],
+    },
+    size: {
+      control: "select",
+      options: ["small", "normal", "large"],
+    },
+    icon: {
+      control: "select",
+      options: ["none", "check", "plus", "arrow-right"],
+      mapping: {
+        none: undefined,
+      },
+    },
+  },
+}
+
+export const Primary = {
+  args: {
+    variant: "primary",
+  },
+}
+
+export const Secondary = {}
+
+export const Ghost = {
+  args: {
+    variant: "ghost",
+  },
+}
+
+export const WithIcon = {
+  args: {
+    children: "Continue",
+    icon: "arrow-right",
+  },
+}
+
+export const Disabled = {
+  args: {
+    variant: "primary",
+    disabled: true,
+  },
+}
+
+export const Sizes = {
+  render: () => (
+    <div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
+      <Button size="small" variant="secondary">
+        Small
+      </Button>
+      <Button size="normal" variant="secondary">
+        Normal
+      </Button>
+      <Button size="large" variant="secondary">
+        Large
+      </Button>
+    </div>
+  ),
+}

+ 90 - 0
packages/ui/src/components/card.stories.tsx

@@ -0,0 +1,90 @@
+// @ts-nocheck
+import { Card } from "./card"
+import { Button } from "./button"
+
+const docs = `### Overview
+Surface container for grouping related content and actions.
+
+Pair with \`Button\` or \`Tag\` for quick actions.
+
+### API
+- Optional: \`variant\` (normal, error, warning, success, info).
+- Accepts standard div props.
+
+### Variants and states
+- Semantic variants for status-driven messaging.
+
+### Behavior
+- Pure presentational container.
+
+### Accessibility
+- Provide headings or aria labels when used in isolation.
+
+### Theming/tokens
+- Uses \`data-component="card"\` with variant data attributes.
+
+`
+
+export default {
+  title: "UI/Card",
+  id: "components-card",
+  component: Card,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  args: {
+    variant: "normal",
+  },
+  argTypes: {
+    variant: {
+      control: "select",
+      options: ["normal", "error", "warning", "success", "info"],
+    },
+  },
+  render: (props: { variant?: "normal" | "error" | "warning" | "success" | "info" }) => {
+    return (
+      <Card variant={props.variant}>
+        <div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
+          <div style={{ flex: 1 }}>
+            <div style={{ fontWeight: 500 }}>Card title</div>
+            <div style={{ color: "var(--text-weak)", fontSize: "13px" }}>Small supporting text.</div>
+          </div>
+          <Button size="small" variant="ghost">
+            Action
+          </Button>
+        </div>
+      </Card>
+    )
+  },
+}
+
+export const Normal = {}
+
+export const Error = {
+  args: {
+    variant: "error",
+  },
+}
+
+export const Warning = {
+  args: {
+    variant: "warning",
+  },
+}
+
+export const Success = {
+  args: {
+    variant: "success",
+  },
+}
+
+export const Info = {
+  args: {
+    variant: "info",
+  },
+}

+ 71 - 0
packages/ui/src/components/checkbox.stories.tsx

@@ -0,0 +1,71 @@
+// @ts-nocheck
+import { Icon } from "./icon"
+import * as mod from "./checkbox"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Checkbox control for multi-select or agreement inputs.
+
+Use in forms and multi-select lists.
+
+### API
+- Uses Kobalte Checkbox props (\`checked\`, \`defaultChecked\`, \`onChange\`).
+- Optional: \`hideLabel\`, \`description\`, \`icon\`.
+- Children render as the label.
+
+### Variants and states
+- Checked/unchecked, indeterminate, disabled (via Kobalte).
+
+### Behavior
+- Controlled or uncontrolled usage.
+
+### Accessibility
+- TODO: confirm aria attributes from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="checkbox"\` and related slots.
+
+`
+
+const story = create({ title: "UI/Checkbox", mod, args: { children: "Checkbox", defaultChecked: true } })
+export default {
+  title: "UI/Checkbox",
+  id: "components-checkbox",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const States = {
+  render: () => (
+    <div style={{ display: "grid", gap: "12px" }}>
+      <mod.Checkbox defaultChecked>Checked</mod.Checkbox>
+      <mod.Checkbox>Unchecked</mod.Checkbox>
+      <mod.Checkbox disabled>Disabled</mod.Checkbox>
+      <mod.Checkbox description="Helper text">With description</mod.Checkbox>
+    </div>
+  ),
+}
+
+export const CustomIcon = {
+  render: () => (
+    <mod.Checkbox icon={<Icon name="check" size="small" />} defaultChecked>
+      Custom icon
+    </mod.Checkbox>
+  ),
+}
+
+export const HiddenLabel = {
+  args: {
+    children: "Hidden label",
+    hideLabel: true,
+  },
+}

+ 70 - 0
packages/ui/src/components/code.stories.tsx

@@ -0,0 +1,70 @@
+// @ts-nocheck
+import * as mod from "./code"
+import { create } from "../storybook/scaffold"
+import { code } from "../storybook/fixtures"
+
+const docs = `### Overview
+Syntax-highlighted code viewer with selection support and large-file virtualization.
+
+Use alongside \`LineComment\` and \`Diff\` in review workflows.
+
+### API
+- Required: \`file\` with file name + contents.
+- Optional: \`language\`, \`annotations\`, \`selectedLines\`, \`commentedLines\`.
+- Optional callbacks: \`onRendered\`, \`onLineSelectionEnd\`.
+
+### Variants and states
+- Supports large-file virtualization automatically.
+
+### Behavior
+- Re-renders when \`file\` or rendering options change.
+- Optional line selection integrates with selection callbacks.
+
+### Accessibility
+- TODO: confirm keyboard find and selection behavior.
+
+### Theming/tokens
+- Uses \`data-component="code"\` and Pierre CSS variables from \`styleVariables\`.
+
+`
+
+const story = create({
+  title: "UI/Code",
+  mod,
+  args: {
+    file: code,
+    language: "ts",
+  },
+})
+
+export default {
+  title: "UI/Code",
+  id: "components-code",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const SelectedLines = {
+  args: {
+    enableLineSelection: true,
+    selectedLines: { start: 2, end: 4 },
+  },
+}
+
+export const CommentedLines = {
+  args: {
+    commentedLines: [
+      { start: 1, end: 1 },
+      { start: 5, end: 6 },
+    ],
+  },
+}

+ 86 - 0
packages/ui/src/components/collapsible.stories.tsx

@@ -0,0 +1,86 @@
+// @ts-nocheck
+import * as mod from "./collapsible"
+
+const docs = `### Overview
+Toggleable content region with optional arrow indicator.
+
+Compose \`Collapsible.Trigger\`, \`Collapsible.Content\`, and \`Collapsible.Arrow\`.
+
+### API
+- Root accepts Kobalte Collapsible props (\`open\`, \`defaultOpen\`, \`onOpenChange\`).
+- \`variant\` controls styling ("normal" | "ghost").
+
+### Variants and states
+- Normal and ghost variants.
+- Open/closed states.
+
+### Behavior
+- Trigger toggles the content visibility.
+
+### Accessibility
+- TODO: confirm ARIA attributes provided by Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="collapsible"\` and slots for trigger/content/arrow.
+
+`
+
+export default {
+  title: "UI/Collapsible",
+  id: "components-collapsible",
+  component: mod.Collapsible,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    variant: {
+      control: "select",
+      options: ["normal", "ghost"],
+    },
+  },
+}
+
+export const Basic = {
+  args: {
+    variant: "normal",
+    defaultOpen: true,
+  },
+  render: (props) => (
+    <mod.Collapsible {...props}>
+      <mod.Collapsible.Trigger data-slot="collapsible-trigger">
+        <div style={{ display: "flex", "align-items": "center", gap: "8px" }}>
+          <span>Details</span>
+          <mod.Collapsible.Arrow />
+        </div>
+      </mod.Collapsible.Trigger>
+      <mod.Collapsible.Content data-slot="collapsible-content">
+        <div style={{ color: "var(--text-weak)", "padding-top": "8px" }}>Optional details sit here.</div>
+      </mod.Collapsible.Content>
+    </mod.Collapsible>
+  ),
+}
+
+export const Ghost = {
+  args: {
+    variant: "ghost",
+    defaultOpen: false,
+  },
+  render: (props) => (
+    <mod.Collapsible {...props}>
+      <mod.Collapsible.Trigger data-slot="collapsible-trigger">
+        <div style={{ display: "flex", "align-items": "center", gap: "8px" }}>
+          <span>Ghost trigger</span>
+          <mod.Collapsible.Arrow />
+        </div>
+      </mod.Collapsible.Trigger>
+      <mod.Collapsible.Content data-slot="collapsible-content">
+        <div style={{ color: "var(--text-weak)", "padding-top": "8px" }}>Ghost content.</div>
+      </mod.Collapsible.Content>
+    </mod.Collapsible>
+  ),
+}

+ 113 - 0
packages/ui/src/components/context-menu.stories.tsx

@@ -0,0 +1,113 @@
+// @ts-nocheck
+import * as mod from "./context-menu"
+
+const docs = `### Overview
+Context menu for right-click interactions with composable items and submenus.
+
+Use \`ItemLabel\` and \`ItemDescription\` for rich items.
+
+### API
+- Root accepts Kobalte ContextMenu props (\`open\`, \`defaultOpen\`, \`onOpenChange\`).
+- Compose \`Trigger\`, \`Content\`, \`Item\`, \`Separator\`, and optional \`Sub\` sections.
+
+### Variants and states
+- Supports grouped sections and nested submenus.
+
+### Behavior
+- Opens on context menu gesture over the trigger element.
+
+### Accessibility
+- TODO: confirm keyboard and focus behavior from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="context-menu"\` and slot attributes for styling.
+
+`
+
+export default {
+  title: "UI/ContextMenu",
+  id: "components-context-menu",
+  component: mod.ContextMenu,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => (
+    <mod.ContextMenu defaultOpen>
+      <mod.ContextMenu.Trigger>
+        <div
+          style={{
+            padding: "20px",
+            border: "1px dashed var(--border-weak)",
+            "border-radius": "8px",
+            color: "var(--text-weak)",
+          }}
+        >
+          Right click (or open) here
+        </div>
+      </mod.ContextMenu.Trigger>
+      <mod.ContextMenu.Portal>
+        <mod.ContextMenu.Content>
+          <mod.ContextMenu.Group>
+            <mod.ContextMenu.GroupLabel>Actions</mod.ContextMenu.GroupLabel>
+            <mod.ContextMenu.Item>
+              <mod.ContextMenu.ItemLabel>Copy</mod.ContextMenu.ItemLabel>
+            </mod.ContextMenu.Item>
+            <mod.ContextMenu.Item>
+              <mod.ContextMenu.ItemLabel>Paste</mod.ContextMenu.ItemLabel>
+            </mod.ContextMenu.Item>
+          </mod.ContextMenu.Group>
+          <mod.ContextMenu.Separator />
+          <mod.ContextMenu.Sub>
+            <mod.ContextMenu.SubTrigger>More</mod.ContextMenu.SubTrigger>
+            <mod.ContextMenu.SubContent>
+              <mod.ContextMenu.Item>
+                <mod.ContextMenu.ItemLabel>Duplicate</mod.ContextMenu.ItemLabel>
+              </mod.ContextMenu.Item>
+              <mod.ContextMenu.Item>
+                <mod.ContextMenu.ItemLabel>Move</mod.ContextMenu.ItemLabel>
+              </mod.ContextMenu.Item>
+            </mod.ContextMenu.SubContent>
+          </mod.ContextMenu.Sub>
+        </mod.ContextMenu.Content>
+      </mod.ContextMenu.Portal>
+    </mod.ContextMenu>
+  ),
+}
+
+export const CheckboxRadio = {
+  render: () => (
+    <mod.ContextMenu defaultOpen>
+      <mod.ContextMenu.Trigger>
+        <div
+          style={{
+            padding: "20px",
+            border: "1px dashed var(--border-weak)",
+            "border-radius": "8px",
+            color: "var(--text-weak)",
+          }}
+        >
+          Right click (or open) here
+        </div>
+      </mod.ContextMenu.Trigger>
+      <mod.ContextMenu.Portal>
+        <mod.ContextMenu.Content>
+          <mod.ContextMenu.CheckboxItem checked>Show line numbers</mod.ContextMenu.CheckboxItem>
+          <mod.ContextMenu.CheckboxItem>Wrap lines</mod.ContextMenu.CheckboxItem>
+          <mod.ContextMenu.Separator />
+          <mod.ContextMenu.RadioGroup value="compact">
+            <mod.ContextMenu.RadioItem value="compact">Compact</mod.ContextMenu.RadioItem>
+            <mod.ContextMenu.RadioItem value="comfortable">Comfortable</mod.ContextMenu.RadioItem>
+          </mod.ContextMenu.RadioGroup>
+        </mod.ContextMenu.Content>
+      </mod.ContextMenu.Portal>
+    </mod.ContextMenu>
+  ),
+}

+ 173 - 0
packages/ui/src/components/dialog.stories.tsx

@@ -0,0 +1,173 @@
+// @ts-nocheck
+import { onMount } from "solid-js"
+import * as mod from "./dialog"
+import { Button } from "./button"
+import { useDialog } from "../context/dialog"
+
+const docs = `### Overview
+Dialog content wrapper used with the DialogProvider for modal flows.
+
+Provide concise title/description and keep body focused.
+
+### API
+- Optional: \`title\`, \`description\`, \`action\`.
+- \`size\`: normal | large | x-large.
+- \`fit\` and \`transition\` control layout and animation.
+
+### Variants and states
+- Sizes and optional header/action controls.
+
+### Behavior
+- Intended to be rendered via \`useDialog().show\`.
+
+### Accessibility
+- TODO: confirm focus trapping and aria attributes from Kobalte Dialog.
+
+### Theming/tokens
+- Uses \`data-component="dialog"\` and slot attributes.
+
+`
+
+export default {
+  title: "UI/Dialog",
+  id: "components-dialog",
+  component: mod.Dialog,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => {
+    const dialog = useDialog()
+    const open = () =>
+      dialog.show(() => (
+        <mod.Dialog title="Dialog" description="Description">
+          Dialog body content.
+        </mod.Dialog>
+      ))
+
+    onMount(open)
+
+    return (
+      <Button variant="secondary" onClick={open}>
+        Open dialog
+      </Button>
+    )
+  },
+}
+
+export const Sizes = {
+  render: () => {
+    const dialog = useDialog()
+    return (
+      <div style={{ display: "flex", gap: "12px" }}>
+        <Button
+          variant="secondary"
+          onClick={() =>
+            dialog.show(() => (
+              <mod.Dialog title="Normal" description="Normal size">
+                Normal dialog content.
+              </mod.Dialog>
+            ))
+          }
+        >
+          Normal
+        </Button>
+        <Button
+          variant="secondary"
+          onClick={() =>
+            dialog.show(() => (
+              <mod.Dialog size="large" title="Large" description="Large size">
+                Large dialog content.
+              </mod.Dialog>
+            ))
+          }
+        >
+          Large
+        </Button>
+        <Button
+          variant="secondary"
+          onClick={() =>
+            dialog.show(() => (
+              <mod.Dialog size="x-large" title="Extra large" description="X-large size">
+                X-large dialog content.
+              </mod.Dialog>
+            ))
+          }
+        >
+          X-Large
+        </Button>
+      </div>
+    )
+  },
+}
+
+export const Transition = {
+  render: () => {
+    const dialog = useDialog()
+    return (
+      <Button
+        variant="secondary"
+        onClick={() =>
+          dialog.show(() => (
+            <mod.Dialog title="Transition" description="Animated" transition>
+              Transition enabled.
+            </mod.Dialog>
+          ))
+        }
+      >
+        Open transition dialog
+      </Button>
+    )
+  },
+}
+
+export const CustomAction = {
+  render: () => {
+    const dialog = useDialog()
+    return (
+      <Button
+        variant="secondary"
+        onClick={() =>
+          dialog.show(() => (
+            <mod.Dialog
+              title="Custom action"
+              description="Dialog with a custom header action"
+              action={<Button variant="ghost">Help</Button>}
+            >
+              Dialog body content.
+            </mod.Dialog>
+          ))
+        }
+      >
+        Open action dialog
+      </Button>
+    )
+  },
+}
+
+export const Fit = {
+  render: () => {
+    const dialog = useDialog()
+    return (
+      <Button
+        variant="secondary"
+        onClick={() =>
+          dialog.show(() => (
+            <mod.Dialog title="Fit content" fit>
+              Dialog fits its content.
+            </mod.Dialog>
+          ))
+        }
+      >
+        Open fit dialog
+      </Button>
+    )
+  },
+}

+ 81 - 0
packages/ui/src/components/diff-changes.stories.tsx

@@ -0,0 +1,81 @@
+// @ts-nocheck
+import * as mod from "./diff-changes"
+import { create } from "../storybook/scaffold"
+import { changes } from "../storybook/fixtures"
+
+const docs = `### Overview
+Summarize additions/deletions as text or compact bars.
+
+Pair with \`Diff\`/\`DiffSSR\` to contextualize a change set.
+
+### API
+- Required: \`changes\` as { additions, deletions } or an array of those objects.
+- Optional: \`variant\` ("default" | "bars").
+
+### Variants and states
+- Default text summary or bar visualization.
+- Handles zero-change state (renders nothing in default variant).
+
+### Behavior
+- Aggregates arrays into total additions/deletions.
+
+### Accessibility
+- Ensure surrounding context conveys meaning of the counts/bars.
+
+### Theming/tokens
+- Uses \`data-component="diff-changes"\` and diff color tokens.
+
+`
+
+const story = create({
+  title: "UI/DiffChanges",
+  mod,
+  args: {
+    changes,
+    variant: "default",
+  },
+})
+
+export default {
+  title: "UI/DiffChanges",
+  id: "components-diff-changes",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    variant: {
+      control: "select",
+      options: ["default", "bars"],
+    },
+  },
+}
+
+export const Default = story.Basic
+
+export const Bars = {
+  args: {
+    variant: "bars",
+  },
+}
+
+export const MultipleFiles = {
+  args: {
+    changes: [
+      { additions: 4, deletions: 1 },
+      { additions: 8, deletions: 3 },
+      { additions: 2, deletions: 0 },
+    ],
+  },
+}
+
+export const Zero = {
+  args: {
+    changes: { additions: 0, deletions: 0 },
+  },
+}

+ 97 - 0
packages/ui/src/components/diff-ssr.stories.tsx

@@ -0,0 +1,97 @@
+// @ts-nocheck
+import { preloadMultiFileDiff } from "@pierre/diffs/ssr"
+import { createResource, Show } from "solid-js"
+import * as mod from "./diff-ssr"
+import { createDefaultOptions } from "../pierre"
+import { WorkerPoolProvider } from "../context/worker-pool"
+import { getWorkerPools } from "../pierre/worker"
+import { diff } from "../storybook/fixtures"
+
+const docs = `### Overview
+Server-rendered diff hydration component for preloaded Pierre diff output.
+
+Use alongside server routes that preload diffs.
+Pair with \`DiffChanges\` for summaries.
+
+### API
+- Required: \`before\`, \`after\`, and \`preloadedDiff\` from \`preloadMultiFileDiff\`.
+- Optional: \`diffStyle\`, \`annotations\`, \`selectedLines\`, \`commentedLines\`.
+
+### Variants and states
+- Unified/split styles (preloaded must match the style used during preload).
+
+### Behavior
+- Hydrates pre-rendered diff HTML into a live diff instance.
+- Requires a worker pool provider for syntax highlighting.
+
+### Accessibility
+- TODO: confirm keyboard behavior from the Pierre diff engine.
+
+### Theming/tokens
+- Uses \`data-component="diff"\` with Pierre CSS variables and theme tokens.
+
+`
+
+const load = async () => {
+  return preloadMultiFileDiff({
+    oldFile: diff.before,
+    newFile: diff.after,
+    options: createDefaultOptions("unified"),
+  })
+}
+
+const loadSplit = async () => {
+  return preloadMultiFileDiff({
+    oldFile: diff.before,
+    newFile: diff.after,
+    options: createDefaultOptions("split"),
+  })
+}
+
+export default {
+  title: "UI/DiffSSR",
+  id: "components-diff-ssr",
+  component: mod.Diff,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => {
+    const [data] = createResource(load)
+    return (
+      <WorkerPoolProvider pools={getWorkerPools()}>
+        <Show when={data()} fallback={<div>Loading pre-rendered diff...</div>}>
+          {(preloaded) => (
+            <div style={{ "max-width": "960px" }}>
+              <mod.Diff before={diff.before} after={diff.after} diffStyle="unified" preloadedDiff={preloaded()} />
+            </div>
+          )}
+        </Show>
+      </WorkerPoolProvider>
+    )
+  },
+}
+
+export const Split = {
+  render: () => {
+    const [data] = createResource(loadSplit)
+    return (
+      <WorkerPoolProvider pools={getWorkerPools()}>
+        <Show when={data()} fallback={<div>Loading pre-rendered diff...</div>}>
+          {(preloaded) => (
+            <div style={{ "max-width": "960px" }}>
+              <mod.Diff before={diff.before} after={diff.after} diffStyle="split" preloadedDiff={preloaded()} />
+            </div>
+          )}
+        </Show>
+      </WorkerPoolProvider>
+    )
+  },
+}

+ 96 - 0
packages/ui/src/components/diff.stories.tsx

@@ -0,0 +1,96 @@
+// @ts-nocheck
+import * as mod from "./diff"
+import { create } from "../storybook/scaffold"
+import { diff } from "../storybook/fixtures"
+
+const docs = `### Overview
+Render a code diff with OpenCode styling using the Pierre diff engine.
+
+Pair with \`DiffChanges\` for summary counts.
+Use \`LineComment\` or external UI for annotation workflows.
+
+### API
+- Required: \`before\` and \`after\` file contents (name + contents).
+- Optional: \`diffStyle\` ("unified" | "split"), \`annotations\`, \`selectedLines\`, \`commentedLines\`.
+- Optional interaction: \`enableLineSelection\`, \`onLineSelectionEnd\`.
+- Passes through Pierre FileDiff options (see component source).
+
+### Variants and states
+- Unified and split diff styles.
+- Optional line selection + commented line highlighting.
+
+### Behavior
+- Re-renders when \`before\`/\`after\` or diff options change.
+- Line selection uses mouse drag/selection when enabled.
+
+### Accessibility
+- TODO: confirm keyboard behavior from the Pierre diff engine.
+- Provide surrounding labels or headings when used as a standalone view.
+
+### Theming/tokens
+- Uses \`data-component="diff"\` and Pierre CSS variables from \`styleVariables\`.
+- Colors derive from theme tokens (diff add/delete, background, text).
+
+`
+
+const story = create({
+  title: "UI/Diff",
+  mod,
+  args: {
+    before: diff.before,
+    after: diff.after,
+    diffStyle: "unified",
+  },
+})
+
+export default {
+  title: "UI/Diff",
+  id: "components-diff",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    diffStyle: {
+      control: "select",
+      options: ["unified", "split"],
+    },
+    enableLineSelection: {
+      control: "boolean",
+    },
+  },
+}
+
+export const Unified = story.Basic
+
+export const Split = {
+  args: {
+    diffStyle: "split",
+  },
+}
+
+export const Selectable = {
+  args: {
+    enableLineSelection: true,
+  },
+}
+
+export const SelectedLines = {
+  args: {
+    selectedLines: { start: 2, end: 4 },
+  },
+}
+
+export const CommentedLines = {
+  args: {
+    commentedLines: [
+      { start: 1, end: 1 },
+      { start: 4, end: 4 },
+    ],
+  },
+}

+ 62 - 0
packages/ui/src/components/dock-prompt.stories.tsx

@@ -0,0 +1,62 @@
+// @ts-nocheck
+import * as mod from "./dock-prompt"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Docked prompt layout for questions and permission requests.
+
+Use with form controls or confirmation buttons in the footer.
+
+### API
+- Required: \`kind\` (question | permission), \`header\`, \`children\`, \`footer\`.
+- Optional: \`ref\` for measuring or focus management.
+
+### Variants and states
+- Question and permission layouts (data attributes).
+
+### Behavior
+- Pure layout component; behavior handled by parent.
+
+### Accessibility
+- Ensure header and footer content provide clear context and actions.
+
+### Theming/tokens
+- Uses \`data-component="dock-prompt"\` with kind data attribute.
+
+`
+
+const story = create({
+  title: "UI/DockPrompt",
+  mod,
+  args: {
+    kind: "question",
+    header: "Header",
+    children: "Prompt content",
+    footer: "Footer",
+  },
+})
+
+export default {
+  title: "UI/DockPrompt",
+  id: "components-dock-prompt",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Permission = {
+  args: {
+    kind: "permission",
+    header: "Allow access?",
+    children: "This action needs permission to proceed.",
+    footer: "Approve or deny",
+  },
+}

+ 97 - 0
packages/ui/src/components/dropdown-menu.stories.tsx

@@ -0,0 +1,97 @@
+// @ts-nocheck
+import * as mod from "./dropdown-menu"
+import { Button } from "./button"
+
+const docs = `### Overview
+Dropdown menu built on Kobalte with composable items, groups, and submenus.
+
+Use \`DropdownMenu.ItemLabel\`/\`ItemDescription\` for richer rows.
+
+### API
+- Root accepts Kobalte DropdownMenu props (\`open\`, \`defaultOpen\`, \`onOpenChange\`).
+- Compose with \`Trigger\`, \`Content\`, \`Item\`, \`Separator\`, and optional \`Sub\` sections.
+
+### Variants and states
+- Supports item groups, separators, and nested submenus.
+
+### Behavior
+- Menu opens from trigger and renders in a portal by default.
+
+### Accessibility
+- TODO: confirm keyboard navigation from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="dropdown-menu"\` and slot attributes for styling.
+
+`
+
+export default {
+  title: "UI/DropdownMenu",
+  id: "components-dropdown-menu",
+  component: mod.DropdownMenu,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => (
+    <mod.DropdownMenu defaultOpen>
+      <mod.DropdownMenu.Trigger as={Button} variant="secondary" size="small">
+        Open menu
+      </mod.DropdownMenu.Trigger>
+      <mod.DropdownMenu.Portal>
+        <mod.DropdownMenu.Content>
+          <mod.DropdownMenu.Group>
+            <mod.DropdownMenu.GroupLabel>Actions</mod.DropdownMenu.GroupLabel>
+            <mod.DropdownMenu.Item>
+              <mod.DropdownMenu.ItemLabel>New file</mod.DropdownMenu.ItemLabel>
+            </mod.DropdownMenu.Item>
+            <mod.DropdownMenu.Item>
+              <mod.DropdownMenu.ItemLabel>Rename</mod.DropdownMenu.ItemLabel>
+              <mod.DropdownMenu.ItemDescription>Shift+R</mod.DropdownMenu.ItemDescription>
+            </mod.DropdownMenu.Item>
+          </mod.DropdownMenu.Group>
+          <mod.DropdownMenu.Separator />
+          <mod.DropdownMenu.Sub>
+            <mod.DropdownMenu.SubTrigger>More options</mod.DropdownMenu.SubTrigger>
+            <mod.DropdownMenu.SubContent>
+              <mod.DropdownMenu.Item>
+                <mod.DropdownMenu.ItemLabel>Duplicate</mod.DropdownMenu.ItemLabel>
+              </mod.DropdownMenu.Item>
+              <mod.DropdownMenu.Item>
+                <mod.DropdownMenu.ItemLabel>Move</mod.DropdownMenu.ItemLabel>
+              </mod.DropdownMenu.Item>
+            </mod.DropdownMenu.SubContent>
+          </mod.DropdownMenu.Sub>
+        </mod.DropdownMenu.Content>
+      </mod.DropdownMenu.Portal>
+    </mod.DropdownMenu>
+  ),
+}
+
+export const CheckboxRadio = {
+  render: () => (
+    <mod.DropdownMenu defaultOpen>
+      <mod.DropdownMenu.Trigger as={Button} variant="secondary" size="small">
+        Open menu
+      </mod.DropdownMenu.Trigger>
+      <mod.DropdownMenu.Portal>
+        <mod.DropdownMenu.Content>
+          <mod.DropdownMenu.CheckboxItem checked>Show line numbers</mod.DropdownMenu.CheckboxItem>
+          <mod.DropdownMenu.CheckboxItem>Wrap lines</mod.DropdownMenu.CheckboxItem>
+          <mod.DropdownMenu.Separator />
+          <mod.DropdownMenu.RadioGroup value="compact">
+            <mod.DropdownMenu.RadioItem value="compact">Compact</mod.DropdownMenu.RadioItem>
+            <mod.DropdownMenu.RadioItem value="comfortable">Comfortable</mod.DropdownMenu.RadioItem>
+          </mod.DropdownMenu.RadioGroup>
+        </mod.DropdownMenu.Content>
+      </mod.DropdownMenu.Portal>
+    </mod.DropdownMenu>
+  ),
+}

+ 49 - 0
packages/ui/src/components/favicon.stories.tsx

@@ -0,0 +1,49 @@
+// @ts-nocheck
+import * as mod from "./favicon"
+
+const docs = `### Overview
+Injects favicon and app icon meta tags for the document head.
+
+Render once near the app root (head management).
+
+### API
+- No props.
+
+### Variants and states
+- Single configuration.
+
+### Behavior
+- Registers link and meta tags via Solid Meta components.
+
+### Accessibility
+- Not applicable.
+
+### Theming/tokens
+- Not applicable.
+
+`
+
+export default {
+  title: "UI/Favicon",
+  id: "components-favicon",
+  component: mod.Favicon,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => (
+    <div style={{ display: "grid", gap: "8px" }}>
+      <mod.Favicon />
+      <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>
+        Head tags are injected for favicon and app icons.
+      </div>
+    </div>
+  ),
+}

+ 94 - 0
packages/ui/src/components/file-icon.stories.tsx

@@ -0,0 +1,94 @@
+// @ts-nocheck
+import * as mod from "./file-icon"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+File and folder icon renderer based on file name and extension.
+
+Use in file trees and lists.
+
+### API
+- Required: \`node\` with \`path\` and \`type\`.
+- Optional: \`expanded\` (for folders), \`mono\` for monochrome rendering.
+
+### Variants and states
+- Folder vs file icons; expanded folder variant.
+
+### Behavior
+- Maps file names and extensions to sprite icons.
+
+### Accessibility
+- Provide adjacent text labels for filenames; icons are decorative.
+
+### Theming/tokens
+- Uses \`data-component="file-icon"\` and sprite-based styling.
+
+`
+
+const story = create({
+  title: "UI/FileIcon",
+  mod,
+  args: {
+    node: { path: "package.json", type: "file" },
+    mono: true,
+  },
+})
+
+export default {
+  title: "UI/FileIcon",
+  id: "components-file-icon",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Folder = {
+  args: {
+    node: { path: "src", type: "directory" },
+    expanded: true,
+    mono: false,
+  },
+}
+
+export const Samples = {
+  render: () => {
+    const items = [
+      { path: "README.md", type: "file" },
+      { path: "package.json", type: "file" },
+      { path: "tsconfig.json", type: "file" },
+      { path: "index.ts", type: "file" },
+      { path: "styles.css", type: "file" },
+      { path: "logo.svg", type: "file" },
+      { path: "photo.png", type: "file" },
+      { path: "Dockerfile", type: "file" },
+      { path: ".env", type: "file" },
+      { path: "src", type: "directory" },
+      { path: "public", type: "directory" },
+    ] as const
+
+    return (
+      <div
+        style={{
+          display: "grid",
+          gap: "12px",
+          "grid-template-columns": "repeat(auto-fill, minmax(120px, 1fr))",
+        }}
+      >
+        {items.map((node) => (
+          <div style={{ display: "flex", gap: "8px", "align-items": "center" }}>
+            <mod.FileIcon node={{ path: node.path, type: node.type }} mono={false} />
+            <div style={{ "font-size": "12px", color: "var(--text-weak)" }}>{node.path}</div>
+          </div>
+        ))}
+      </div>
+    )
+  },
+}

+ 48 - 0
packages/ui/src/components/font.stories.tsx

@@ -0,0 +1,48 @@
+// @ts-nocheck
+import * as mod from "./font"
+
+const docs = `### Overview
+Loads OpenCode typography assets and mono nerd fonts.
+
+Render once at the app root or Storybook preview.
+
+### API
+- No props.
+
+### Variants and states
+- Fonts include sans and multiple mono families.
+
+### Behavior
+- Injects @font-face rules and preload links into the document head.
+
+### Accessibility
+- Not applicable.
+
+### Theming/tokens
+- Provides font families used by theme tokens.
+
+`
+
+export default {
+  title: "UI/Font",
+  id: "components-font",
+  component: mod.Font,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => (
+    <div style={{ display: "grid", gap: "8px" }}>
+      <mod.Font />
+      <div style={{ "font-family": "var(--font-family-sans)" }}>OpenCode Sans Sample</div>
+      <div style={{ "font-family": "var(--font-family-mono)" }}>OpenCode Mono Sample</div>
+    </div>
+  ),
+}

+ 70 - 0
packages/ui/src/components/hover-card.stories.tsx

@@ -0,0 +1,70 @@
+// @ts-nocheck
+import { createSignal } from "solid-js"
+import * as mod from "./hover-card"
+
+const docs = `### Overview
+Hover-triggered card for lightweight previews and metadata.
+
+Use for short summaries; avoid dense interactive controls.
+
+### API
+- Required: \`trigger\` element.
+- Children render inside the hover card body.
+
+### Variants and states
+- None; content and trigger are fully composable.
+
+### Behavior
+- Opens on hover/focus over the trigger.
+
+### Accessibility
+- TODO: confirm focus and hover intent behavior from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="hover-card-content"\` and slots for styling.
+
+`
+
+export default {
+  title: "UI/HoverCard",
+  id: "components-hover-card",
+  component: mod.HoverCard,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => (
+    <mod.HoverCard trigger={<span style={{ "text-decoration": "underline", cursor: "default" }}>Hover me</span>}>
+      <div style={{ display: "grid", gap: "6px" }}>
+        <div style={{ "font-weight": 600 }}>Preview</div>
+        <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Short supporting text.</div>
+      </div>
+    </mod.HoverCard>
+  ),
+}
+
+export const InlineMount = {
+  render: () => {
+    const [mount, setMount] = createSignal<HTMLDivElement | undefined>(undefined)
+    return (
+      <div ref={setMount} style={{ padding: "16px", border: "1px dashed var(--border-weak)" }}>
+        <mod.HoverCard
+          mount={mount()}
+          trigger={<span style={{ "text-decoration": "underline", cursor: "default" }}>Hover me</span>}
+        >
+          <div style={{ display: "grid", gap: "6px" }}>
+            <div style={{ "font-weight": 600 }}>Mounted inside</div>
+            <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Uses custom mount node.</div>
+          </div>
+        </mod.HoverCard>
+      </div>
+    )
+  },
+}

+ 74 - 0
packages/ui/src/components/icon-button.stories.tsx

@@ -0,0 +1,74 @@
+// @ts-nocheck
+import * as mod from "./icon-button"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Compact icon-only button with size and variant control.
+
+Use \`Button\` for text labels and primary actions.
+
+### API
+- Required: \`icon\` icon name.
+- Optional: \`size\`, \`iconSize\`, \`variant\`.
+- Inherits Kobalte Button props and native button attributes.
+
+### Variants and states
+- Variants: primary, secondary, ghost.
+- Sizes: small, normal, large.
+
+### Behavior
+- Icon size adapts to button size unless overridden.
+
+### Accessibility
+- Provide \`aria-label\` when there is no visible text.
+
+### Theming/tokens
+- Uses \`data-component="icon-button"\` and size/variant data attributes.
+
+`
+
+const story = create({ title: "UI/IconButton", mod, args: { icon: "check", "aria-label": "Icon" } })
+export default {
+  title: "UI/IconButton",
+  id: "components-icon-button",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+  render: () => (
+    <div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
+      <mod.IconButton icon="check" size="small" aria-label="Small" />
+      <mod.IconButton icon="check" size="normal" aria-label="Normal" />
+      <mod.IconButton icon="check" size="large" aria-label="Large" />
+    </div>
+  ),
+}
+
+export const Variants = {
+  render: () => (
+    <div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
+      <mod.IconButton icon="check" variant="primary" aria-label="Primary" />
+      <mod.IconButton icon="check" variant="secondary" aria-label="Secondary" />
+      <mod.IconButton icon="check" variant="ghost" aria-label="Ghost" />
+    </div>
+  ),
+}
+
+export const IconSizeOverride = {
+  render: () => (
+    <div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
+      <mod.IconButton icon="check" size="small" iconSize="large" aria-label="Small with large icon" />
+      <mod.IconButton icon="check" size="large" iconSize="small" aria-label="Large with small icon" />
+    </div>
+  ),
+}

+ 170 - 0
packages/ui/src/components/icon.stories.tsx

@@ -0,0 +1,170 @@
+// @ts-nocheck
+import * as mod from "./icon"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Inline icon renderer using the built-in OpenCode icon set.
+
+Use with \`Button\`, \`IconButton\`, and menu items.
+
+### API
+- Required: \`name\` (icon key).
+- Optional: \`size\` (small | normal | medium | large).
+- Accepts standard SVG props.
+
+### Variants and states
+- Size variants only.
+
+### Behavior
+- Uses an internal SVG path map.
+
+### Accessibility
+- Icons are aria-hidden by default; wrap with accessible text when needed.
+
+### Theming/tokens
+- Uses \`data-component="icon"\` with size data attributes.
+
+`
+
+const names = [
+  "align-right",
+  "arrow-up",
+  "arrow-left",
+  "arrow-right",
+  "archive",
+  "bubble-5",
+  "prompt",
+  "brain",
+  "bullet-list",
+  "check-small",
+  "chevron-down",
+  "chevron-left",
+  "chevron-right",
+  "chevron-grabber-vertical",
+  "chevron-double-right",
+  "circle-x",
+  "close",
+  "close-small",
+  "checklist",
+  "console",
+  "expand",
+  "collapse",
+  "code",
+  "code-lines",
+  "circle-ban-sign",
+  "edit-small-2",
+  "eye",
+  "enter",
+  "folder",
+  "file-tree",
+  "file-tree-active",
+  "magnifying-glass",
+  "plus-small",
+  "plus",
+  "new-session",
+  "pencil-line",
+  "mcp",
+  "glasses",
+  "magnifying-glass-menu",
+  "window-cursor",
+  "task",
+  "stop",
+  "layout-left",
+  "layout-left-partial",
+  "layout-left-full",
+  "layout-right",
+  "layout-right-partial",
+  "layout-right-full",
+  "square-arrow-top-right",
+  "open-file",
+  "speech-bubble",
+  "comment",
+  "folder-add-left",
+  "github",
+  "discord",
+  "layout-bottom",
+  "layout-bottom-partial",
+  "layout-bottom-full",
+  "dot-grid",
+  "circle-check",
+  "copy",
+  "check",
+  "photo",
+  "share",
+  "download",
+  "menu",
+  "server",
+  "branch",
+  "edit",
+  "help",
+  "settings-gear",
+  "dash",
+  "cloud-upload",
+  "trash",
+  "sliders",
+  "keyboard",
+  "selector",
+  "arrow-down-to-line",
+  "warning",
+  "link",
+  "providers",
+  "models",
+]
+
+const story = create({ title: "UI/Icon", mod, args: { name: "check" } })
+
+export default {
+  title: "UI/Icon",
+  id: "components-icon",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    name: {
+      control: "select",
+      options: names,
+    },
+    size: {
+      control: "select",
+      options: ["small", "normal", "medium", "large"],
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+  render: () => (
+    <div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
+      <mod.Icon name="check" size="small" />
+      <mod.Icon name="check" size="normal" />
+      <mod.Icon name="check" size="medium" />
+      <mod.Icon name="check" size="large" />
+    </div>
+  ),
+}
+
+export const Gallery = {
+  render: () => (
+    <div
+      style={{
+        display: "grid",
+        gap: "12px",
+        "grid-template-columns": "repeat(auto-fill, minmax(88px, 1fr))",
+      }}
+    >
+      {names.map((name) => (
+        <div style={{ display: "grid", gap: "6px", "justify-items": "center" }}>
+          <mod.Icon name={name} />
+          <div style={{ "font-size": "10px", color: "var(--text-weak)", "text-align": "center" }}>{name}</div>
+        </div>
+      ))}
+    </div>
+  ),
+}

+ 59 - 0
packages/ui/src/components/image-preview.stories.tsx

@@ -0,0 +1,59 @@
+// @ts-nocheck
+import { onMount } from "solid-js"
+import * as mod from "./image-preview"
+import { Button } from "./button"
+import { useDialog } from "../context/dialog"
+
+const docs = `### Overview
+Image preview content intended to render inside the dialog stack.
+
+Use for full-size image inspection; keep images optimized.
+
+### API
+- Required: \`src\`.
+- Optional: \`alt\` text.
+
+### Variants and states
+- Single layout with close action.
+
+### Behavior
+- Intended to be used via \`useDialog().show\`.
+
+### Accessibility
+- Uses localized aria-label for close button.
+
+### Theming/tokens
+- Uses \`data-component="image-preview"\` and slot attributes.
+
+`
+
+export default {
+  title: "UI/ImagePreview",
+  id: "components-image-preview",
+  component: mod.ImagePreview,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => {
+    const dialog = useDialog()
+    const src = "https://placehold.co/640x360/png"
+
+    const open = () => dialog.show(() => <mod.ImagePreview src={src} alt="Preview" />)
+
+    onMount(open)
+
+    return (
+      <Button variant="secondary" onClick={open}>
+        Open image preview
+      </Button>
+    )
+  },
+}

+ 50 - 0
packages/ui/src/components/inline-input.stories.tsx

@@ -0,0 +1,50 @@
+// @ts-nocheck
+import * as mod from "./inline-input"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Compact inline input for short values.
+
+Use inside text or table rows for quick edits.
+
+### API
+- Optional: \`width\` to set a fixed width.
+- Accepts standard input props.
+
+### Variants and states
+- No built-in variants; style via class or width.
+
+### Behavior
+- Uses inline width when provided.
+
+### Accessibility
+- Provide a label or aria-label when used standalone.
+
+### Theming/tokens
+- Uses \`data-component="inline-input"\`.
+
+`
+
+const story = create({ title: "UI/InlineInput", mod, args: { placeholder: "Type...", value: "Inline" } })
+export default {
+  title: "UI/InlineInput",
+  id: "components-inline-input",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const FixedWidth = {
+  args: {
+    value: "80px",
+    width: "80px",
+  },
+}

+ 43 - 0
packages/ui/src/components/keybind.stories.tsx

@@ -0,0 +1,43 @@
+// @ts-nocheck
+import * as mod from "./keybind"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Keyboard shortcut pill for displaying keybindings.
+
+Pair with menu items or command palettes.
+
+### API
+- Children render the key sequence text.
+- Accepts standard span props.
+
+### Variants and states
+- Single visual style.
+
+### Behavior
+- Presentational only.
+
+### Accessibility
+- Ensure text conveys the shortcut (e.g., "Cmd+K").
+
+### Theming/tokens
+- Uses \`data-component="keybind"\`.
+
+`
+
+const story = create({ title: "UI/Keybind", mod, args: { children: "Cmd+K" } })
+export default {
+  title: "UI/Keybind",
+  id: "components-keybind",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic

+ 115 - 0
packages/ui/src/components/line-comment.stories.tsx

@@ -0,0 +1,115 @@
+// @ts-nocheck
+import { createSignal } from "solid-js"
+import * as mod from "./line-comment"
+
+const docs = `### Overview
+Inline comment anchor and editor for code review or annotation flows.
+
+Pair with \`Diff\` or \`Code\` to align comments to lines.
+
+### API
+- \`LineCommentAnchor\`: position with \`top\`, control \`open\`, render custom children.
+- \`LineComment\`: convenience wrapper for displaying comment + selection label.
+- \`LineCommentEditor\`: controlled textarea with submit/cancel handlers.
+
+### Variants and states
+- Default display and editor display variants.
+
+### Behavior
+- Anchor positions relative to a containing element.
+- Editor submits on Enter (Shift+Enter for newline).
+
+### Accessibility
+- TODO: confirm ARIA labeling for comment button and editor textarea.
+
+### Theming/tokens
+- Uses \`data-component="line-comment"\` and related slots.
+
+`
+
+export default {
+  title: "UI/LineComment",
+  id: "components-line-comment",
+  component: mod.LineComment,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Default = {
+  render: () => (
+    <div
+      style={{
+        position: "relative",
+        height: "160px",
+        padding: "16px 16px 16px 40px",
+        border: "1px solid var(--border-weak)",
+        "border-radius": "8px",
+        "font-family": "var(--font-family-mono)",
+        "font-size": "12px",
+        color: "var(--text-weak)",
+      }}
+    >
+      <div>12 | const total = sum(values)</div>
+      <div>13 | return total / values.length</div>
+      <mod.LineComment open top={18} comment="Consider guarding against empty arrays." selection="L12-L13" />
+    </div>
+  ),
+}
+
+export const Editor = {
+  render: () => {
+    const [value, setValue] = createSignal("Add context for this change.")
+    return (
+      <div
+        style={{
+          position: "relative",
+          height: "220px",
+          padding: "16px 16px 16px 40px",
+          border: "1px solid var(--border-weak)",
+          "border-radius": "8px",
+          "font-family": "var(--font-family-mono)",
+          "font-size": "12px",
+          color: "var(--text-weak)",
+        }}
+      >
+        <div>40 | if (values.length === 0) return 0</div>
+        <mod.LineCommentEditor
+          top={24}
+          value={value()}
+          selection="L40"
+          onInput={setValue}
+          onCancel={() => setValue("")}
+          onSubmit={(next) => setValue(next)}
+        />
+      </div>
+    )
+  },
+}
+
+export const AnchorOnly = {
+  render: () => (
+    <div
+      style={{
+        position: "relative",
+        height: "120px",
+        padding: "16px 16px 16px 40px",
+        border: "1px solid var(--border-weak)",
+        "border-radius": "8px",
+        "font-family": "var(--font-family-mono)",
+        "font-size": "12px",
+        color: "var(--text-weak)",
+      }}
+    >
+      <div>20 | const ready = true</div>
+      <mod.LineCommentAnchor top={18} open={false}>
+        <div data-slot="line-comment-content">Anchor content</div>
+      </mod.LineCommentAnchor>
+    </div>
+  ),
+}

+ 170 - 0
packages/ui/src/components/list.stories.tsx

@@ -0,0 +1,170 @@
+// @ts-nocheck
+import * as mod from "./list"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Filterable list with keyboard navigation and optional search input.
+
+Use within panels or popovers where keyboard navigation is expected.
+
+### API
+- Required: \`items\` and \`key\`.
+- Required: \`children\` render function for items.
+- Optional: \`search\`, \`filterKeys\`, \`groupBy\`, \`onSelect\`, \`onKeyEvent\`.
+
+### Variants and states
+- Optional search bar and group headers.
+
+### Behavior
+- Uses fuzzy search when \`search\` is enabled.
+- Keyboard navigation via arrow keys; Enter selects.
+
+### Accessibility
+- TODO: confirm ARIA roles for list items and search input.
+
+### Theming/tokens
+- Uses \`data-component="list"\` and data slots for structure.
+
+`
+
+const story = create({
+  title: "UI/List",
+  mod,
+  args: {
+    items: ["One", "Two", "Three", "Four"],
+    key: (x: string) => x,
+    children: (x: string) => x,
+    search: true,
+  },
+})
+
+export default {
+  title: "UI/List",
+  id: "components-list",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Grouped = {
+  render: () => {
+    const items = [
+      { id: "a1", title: "Alpha", group: "Group A" },
+      { id: "a2", title: "Bravo", group: "Group A" },
+      { id: "b1", title: "Delta", group: "Group B" },
+    ]
+    return (
+      <mod.List items={items} key={(item) => item.id} groupBy={(item) => item.group} search={true}>
+        {(item) => item.title}
+      </mod.List>
+    )
+  },
+}
+
+export const Empty = {
+  render: () => (
+    <mod.List items={[]} key={(item) => item} search={true}>
+      {(item) => item}
+    </mod.List>
+  ),
+}
+
+export const WithAdd = {
+  render: () => (
+    <mod.List
+      items={["One", "Two"]}
+      key={(item) => item}
+      search={true}
+      add={{
+        render: () => (
+          <button type="button" data-slot="list-item">
+            Add item
+          </button>
+        ),
+      }}
+    >
+      {(item) => item}
+    </mod.List>
+  ),
+}
+
+export const Divider = {
+  render: () => (
+    <mod.List items={["One", "Two", "Three"]} key={(item) => item} divider={true}>
+      {(item) => item}
+    </mod.List>
+  ),
+}
+
+export const ActiveIcon = {
+  render: () => (
+    <mod.List items={["Alpha", "Beta", "Gamma"]} key={(item) => item} activeIcon="chevron-right">
+      {(item) => item}
+    </mod.List>
+  ),
+}
+
+export const NoSearch = {
+  render: () => (
+    <mod.List items={["One", "Two", "Three"]} key={(item) => item} search={false}>
+      {(item) => item}
+    </mod.List>
+  ),
+}
+
+export const SearchOptions = {
+  render: () => (
+    <mod.List
+      items={["Apple", "Banana", "Cherry"]}
+      key={(item) => item}
+      search={{
+        placeholder: "Filter...",
+        hideIcon: true,
+        action: <button type="button">Action</button>,
+      }}
+    >
+      {(item) => item}
+    </mod.List>
+  ),
+}
+
+export const ItemWrapper = {
+  render: () => (
+    <mod.List
+      items={["One", "Two", "Three"]}
+      key={(item) => item}
+      itemWrapper={(item, node) => (
+        <div style={{ border: "1px solid var(--border-weak)", "border-radius": "6px", margin: "4px 0" }}>{node}</div>
+      )}
+    >
+      {(item) => item}
+    </mod.List>
+  ),
+}
+
+export const GroupHeader = {
+  render: () => {
+    const items = [
+      { id: "a1", title: "Alpha", group: "Group A" },
+      { id: "b1", title: "Beta", group: "Group B" },
+    ]
+    return (
+      <mod.List
+        items={items}
+        key={(item) => item.id}
+        groupBy={(item) => item.group}
+        groupHeader={(group) => <strong>{group.category}</strong>}
+      >
+        {(item) => item.title}
+      </mod.List>
+    )
+  },
+}

+ 57 - 0
packages/ui/src/components/logo.stories.tsx

@@ -0,0 +1,57 @@
+// @ts-nocheck
+import * as mod from "./logo"
+
+const docs = `### Overview
+OpenCode logo assets: mark, splash, and wordmark.
+
+Use Mark for compact spaces, Logo for headers, Splash for hero sections.
+
+### API
+- \`Mark\`, \`Splash\`, and \`Logo\` components accept standard SVG props.
+
+### Variants and states
+- Multiple logo variants for different contexts.
+
+### Behavior
+- Pure SVG rendering.
+
+### Accessibility
+- Provide title/aria-label when logos convey meaning.
+
+### Theming/tokens
+- Uses theme color tokens via CSS variables.
+
+`
+
+export default {
+  title: "UI/Logo",
+  id: "components-logo",
+  component: mod.Logo,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => (
+    <div style={{ display: "grid", gap: "16px", "align-items": "start" }}>
+      <div>
+        <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Mark</div>
+        <mod.Mark />
+      </div>
+      <div>
+        <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Splash</div>
+        <mod.Splash style={{ width: "80px", height: "100px" }} />
+      </div>
+      <div>
+        <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Logo</div>
+        <mod.Logo style={{ width: "200px" }} />
+      </div>
+    </div>
+  ),
+}

+ 53 - 0
packages/ui/src/components/markdown.stories.tsx

@@ -0,0 +1,53 @@
+// @ts-nocheck
+import * as mod from "./markdown"
+import { create } from "../storybook/scaffold"
+import { markdown } from "../storybook/fixtures"
+
+const docs = `### Overview
+Render sanitized Markdown with code blocks, inline code, and safe links.
+
+Pair with \`Code\` for standalone code views.
+
+### API
+- Required: \`text\` Markdown string.
+- Uses the Marked context provider for parsing and sanitization.
+
+### Variants and states
+- Code blocks include copy buttons when rendered.
+
+### Behavior
+- Sanitizes HTML and auto-converts inline URL code to links.
+- Adds copy buttons to code blocks.
+
+### Accessibility
+- Copy buttons include aria-labels from i18n.
+- TODO: confirm link target behavior in sanitized output.
+
+### Theming/tokens
+- Uses \`data-component="markdown"\` and related slots for styling.
+
+`
+
+const story = create({
+  title: "UI/Markdown",
+  mod,
+  args: {
+    text: markdown,
+  },
+})
+
+export default {
+  title: "UI/Markdown",
+  id: "components-markdown",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic

+ 7 - 0
packages/ui/src/components/message-nav.stories.tsx

@@ -0,0 +1,7 @@
+// @ts-nocheck
+import * as mod from "./message-nav"
+import { create } from "../storybook/scaffold"
+
+const story = create({ title: "UI/MessageNav", mod })
+export default { title: "UI/MessageNav", id: "components-message-nav", component: story.meta.component }
+export const Basic = story.Basic

+ 7 - 0
packages/ui/src/components/message-part.stories.tsx

@@ -0,0 +1,7 @@
+// @ts-nocheck
+import * as mod from "./message-part"
+import { create } from "../storybook/scaffold"
+
+const story = create({ title: "UI/MessagePart", mod })
+export default { title: "UI/MessagePart", id: "components-message-part", component: story.meta.component }
+export const Basic = story.Basic

+ 87 - 0
packages/ui/src/components/popover.stories.tsx

@@ -0,0 +1,87 @@
+// @ts-nocheck
+import { createSignal } from "solid-js"
+import * as mod from "./popover"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Composable popover with optional title, description, and close button.
+
+Use for small contextual details; avoid long forms.
+
+### API
+- \`trigger\` and \`children\` define the anchor and content.
+- Optional: \`title\`, \`description\`, \`portal\`, \`open\`, \`defaultOpen\`.
+
+### Variants and states
+- Supports controlled and uncontrolled open state.
+
+### Behavior
+- Closes on outside click or Escape by default.
+
+### Accessibility
+- TODO: confirm focus management from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="popover-content"\` and related slots.
+
+`
+
+const story = create({
+  title: "UI/Popover",
+  mod,
+  args: {
+    trigger: "Open popover",
+    title: "Popover",
+    description: "Optional description",
+    defaultOpen: true,
+    children: "Popover content",
+  },
+})
+
+export default {
+  title: "UI/Popover",
+  id: "components-popover",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const NoHeader = {
+  args: {
+    title: undefined,
+    description: undefined,
+    children: "Popover body only",
+  },
+}
+
+export const Inline = {
+  args: {
+    portal: false,
+    defaultOpen: true,
+  },
+}
+
+export const Controlled = {
+  render: () => {
+    const [open, setOpen] = createSignal(true)
+    return (
+      <mod.Popover
+        open={open()}
+        onOpenChange={setOpen}
+        trigger="Toggle popover"
+        title="Controlled"
+        description="Open state is controlled"
+      >
+        Controlled content
+      </mod.Popover>
+    )
+  },
+}

+ 59 - 0
packages/ui/src/components/progress-circle.stories.tsx

@@ -0,0 +1,59 @@
+// @ts-nocheck
+import * as mod from "./progress-circle"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Circular progress indicator for compact loading states.
+
+Pair with labels for clarity in dashboards.
+
+### API
+- Required: \`percentage\` (0-100).
+- Optional: \`size\`, \`strokeWidth\`.
+
+### Variants and states
+- Single visual style; size and stroke width adjust appearance.
+
+### Behavior
+- Percentage is clamped between 0 and 100.
+
+### Accessibility
+- Use alongside text or aria-live messaging for progress context.
+
+### Theming/tokens
+- Uses \`data-component="progress-circle"\` with background/progress slots.
+
+`
+
+const story = create({ title: "UI/ProgressCircle", mod, args: { percentage: 65, size: 48 } })
+
+export default {
+  title: "UI/ProgressCircle",
+  id: "components-progress-circle",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    percentage: {
+      control: { type: "range", min: 0, max: 100, step: 1 },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const States = {
+  render: () => (
+    <div style={{ display: "flex", gap: "16px", "align-items": "center" }}>
+      <mod.ProgressCircle percentage={0} size={32} />
+      <mod.ProgressCircle percentage={50} size={32} />
+      <mod.ProgressCircle percentage={100} size={32} />
+    </div>
+  ),
+}

+ 67 - 0
packages/ui/src/components/progress.stories.tsx

@@ -0,0 +1,67 @@
+// @ts-nocheck
+import * as mod from "./progress"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Linear progress indicator with optional label and value display.
+
+Use in forms, uploads, or background tasks.
+
+### API
+- \`value\` and \`maxValue\` control progress.
+- Optional: \`showValueLabel\`, \`hideLabel\`.
+- Children provide the label text.
+
+### Variants and states
+- Supports indeterminate state via Kobalte props (if provided).
+
+### Behavior
+- Uses Kobalte Progress for value calculation.
+
+### Accessibility
+- TODO: confirm ARIA attributes from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="progress"\` with track/fill slots.
+
+`
+
+const story = create({
+  title: "UI/Progress",
+  mod,
+  args: {
+    value: 60,
+    maxValue: 100,
+    children: "Progress",
+    showValueLabel: true,
+  },
+})
+
+export default {
+  title: "UI/Progress",
+  id: "components-progress",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const NoLabel = {
+  args: {
+    children: "",
+    hideLabel: true,
+    showValueLabel: false,
+    value: 30,
+  },
+}
+
+export const Indeterminate = {
+  render: () => <mod.Progress>Loading</mod.Progress>,
+}

+ 69 - 0
packages/ui/src/components/provider-icon.stories.tsx

@@ -0,0 +1,69 @@
+// @ts-nocheck
+import { iconNames } from "./provider-icons/types"
+import * as mod from "./provider-icon"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Provider icon sprite renderer for model/provider badges.
+
+Use in model pickers or provider lists.
+
+### API
+- Required: \`id\` (provider icon name).
+- Accepts standard SVG props.
+
+### Variants and states
+- Single visual style; size via CSS.
+
+### Behavior
+- Renders from the provider SVG sprite sheet.
+
+### Accessibility
+- Provide accessible text nearby when the icon conveys meaning.
+
+### Theming/tokens
+- Uses \`data-component="provider-icon"\`.
+
+`
+
+const story = create({ title: "UI/ProviderIcon", mod, args: { id: "openai" } })
+export default {
+  title: "UI/ProviderIcon",
+  id: "components-provider-icon",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    id: {
+      control: "select",
+      options: iconNames,
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const AllIcons = {
+  render: () => (
+    <div
+      style={{
+        display: "grid",
+        gap: "12px",
+        "grid-template-columns": "repeat(auto-fill, minmax(80px, 1fr))",
+      }}
+    >
+      {iconNames.map((id) => (
+        <div style={{ display: "grid", gap: "6px", "justify-items": "center" }}>
+          <mod.ProviderIcon id={id} width="28" height="28" aria-label={id} />
+          <div style={{ "font-size": "10px", color: "var(--text-weak)", "text-align": "center" }}>{id}</div>
+        </div>
+      ))}
+    </div>
+  ),
+}

Разница между файлами не показана из-за своего большого размера
+ 95 - 0
packages/ui/src/components/provider-icons/sprite.svg


+ 20 - 0
packages/ui/src/components/provider-icons/types.ts

@@ -10,6 +10,7 @@ export const iconNames = [
   "xai",
   "wandb",
   "vultr",
+  "vivgrid",
   "vercel",
   "venice",
   "v0",
@@ -17,11 +18,16 @@ export const iconNames = [
   "togetherai",
   "synthetic",
   "submodel",
+  "stepfun",
+  "stackit",
   "siliconflow",
   "siliconflow-cn",
   "scaleway",
   "sap-ai-core",
   "requesty",
+  "qiniu-ai",
+  "qihang-ai",
+  "privatemode-ai",
   "poe",
   "perplexity",
   "ovhcloud",
@@ -30,19 +36,28 @@ export const iconNames = [
   "openai",
   "ollama-cloud",
   "nvidia",
+  "novita-ai",
+  "nova",
   "nebius",
   "nano-gpt",
   "morph",
   "moonshotai",
   "moonshotai-cn",
   "modelscope",
+  "moark",
   "mistral",
   "minimax",
+  "minimax-coding-plan",
   "minimax-cn",
+  "minimax-cn-coding-plan",
+  "meganova",
   "lucidquery",
   "lmstudio",
   "llama",
+  "kuae-cloud-coding-plan",
   "kimi-for-coding",
+  "kilo",
+  "jiekou",
   "io-net",
   "inference",
   "inception",
@@ -53,9 +68,11 @@ export const iconNames = [
   "google",
   "google-vertex",
   "google-vertex-anthropic",
+  "gitlab",
   "github-models",
   "github-copilot",
   "friendli",
+  "firmware",
   "fireworks-ai",
   "fastrouter",
   "deepseek",
@@ -64,8 +81,10 @@ export const iconNames = [
   "cohere",
   "cloudflare-workers-ai",
   "cloudflare-ai-gateway",
+  "cloudferro-sherlock",
   "chutes",
   "cerebras",
+  "berget",
   "baseten",
   "bailing",
   "azure",
@@ -76,6 +95,7 @@ export const iconNames = [
   "alibaba-cn",
   "aihubmix",
   "abacus",
+  "302ai",
 ] as const
 
 export type IconName = (typeof iconNames)[number]

+ 92 - 0
packages/ui/src/components/radio-group.stories.tsx

@@ -0,0 +1,92 @@
+// @ts-nocheck
+import * as mod from "./radio-group"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Segmented radio group for choosing a single option.
+
+Use for view toggles or mode selection.
+
+### API
+- Required: \`options\`.
+- Optional: \`current\`, \`defaultValue\`, \`value\`, \`label\`, \`onSelect\`.
+- Optional layout: \`size\`, \`fill\`, \`pad\`.
+
+### Variants and states
+- Size variants: small, medium.
+- Optional fill and padding behavior.
+
+### Behavior
+- Maps options to segmented items and manages selection.
+
+### Accessibility
+- TODO: confirm role/aria attributes from Kobalte SegmentedControl.
+
+### Theming/tokens
+- Uses \`data-component="radio-group"\` with size/pad data attributes.
+
+`
+
+const story = create({
+  title: "UI/RadioGroup",
+  mod,
+  args: {
+    options: ["One", "Two", "Three"],
+    defaultValue: "One",
+  },
+})
+
+export default {
+  title: "UI/RadioGroup",
+  id: "components-radio-group",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    size: {
+      control: "select",
+      options: ["small", "medium"],
+    },
+    pad: {
+      control: "select",
+      options: ["none", "normal"],
+    },
+    fill: {
+      control: "boolean",
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+  render: () => (
+    <div style={{ display: "grid", gap: "12px" }}>
+      <mod.RadioGroup options={["One", "Two"]} defaultValue="One" size="small" />
+      <mod.RadioGroup options={["One", "Two"]} defaultValue="One" size="medium" />
+    </div>
+  ),
+}
+
+export const Filled = {
+  args: {
+    fill: true,
+    pad: "none",
+  },
+}
+
+export const CustomLabels = {
+  render: () => (
+    <mod.RadioGroup
+      options={["list", "grid"]}
+      defaultValue="list"
+      label={(value) => (value === "list" ? "List view" : "Grid view")}
+    />
+  ),
+}

+ 156 - 0
packages/ui/src/components/resize-handle.stories.tsx

@@ -0,0 +1,156 @@
+// @ts-nocheck
+import { createSignal } from "solid-js"
+import * as mod from "./resize-handle"
+
+const docs = `### Overview
+Drag handle for resizing panels or split views.
+
+Use alongside resizable panels and split layouts.
+
+### API
+- Required: \`direction\`, \`size\`, \`min\`, \`max\`, \`onResize\`.
+- Optional: \`edge\`, \`onCollapse\`, \`collapseThreshold\`.
+
+### Variants and states
+- Horizontal and vertical directions.
+
+### Behavior
+- Drag updates size and calls \`onResize\` with clamped values.
+
+### Accessibility
+- TODO: provide keyboard resizing guidance if needed.
+
+### Theming/tokens
+- Uses \`data-component="resize-handle"\` with direction/edge data attributes.
+
+`
+
+export default {
+  title: "UI/ResizeHandle",
+  id: "components-resize-handle",
+  component: mod.ResizeHandle,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => {
+    const [size, setSize] = createSignal(240)
+    return (
+      <div style={{ display: "grid", gap: "8px" }}>
+        <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Size: {size()}px</div>
+        <div
+          style={{
+            width: `${size()}px`,
+            height: "48px",
+            "background-color": "var(--background-stronger)",
+            "border-radius": "6px",
+          }}
+        />
+        <mod.ResizeHandle
+          direction="horizontal"
+          size={size()}
+          min={120}
+          max={480}
+          onResize={setSize}
+          style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
+        />
+      </div>
+    )
+  },
+}
+
+export const Vertical = {
+  render: () => {
+    const [size, setSize] = createSignal(180)
+    return (
+      <div style={{ display: "grid", gap: "8px", width: "220px" }}>
+        <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Size: {size()}px</div>
+        <div
+          style={{
+            height: `${size()}px`,
+            "background-color": "var(--background-stronger)",
+            "border-radius": "6px",
+          }}
+        />
+        <mod.ResizeHandle
+          direction="vertical"
+          size={size()}
+          min={120}
+          max={320}
+          onResize={setSize}
+          style="width:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
+        />
+      </div>
+    )
+  },
+}
+
+export const Collapse = {
+  render: () => {
+    const [size, setSize] = createSignal(200)
+    const [collapsed, setCollapsed] = createSignal(false)
+    return (
+      <div style={{ display: "grid", gap: "8px" }}>
+        <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>
+          {collapsed() ? "Collapsed" : `Size: ${size()}px`}
+        </div>
+        <div
+          style={{
+            width: `${collapsed() ? 0 : size()}px`,
+            height: "48px",
+            "background-color": "var(--background-stronger)",
+            "border-radius": "6px",
+          }}
+        />
+        <mod.ResizeHandle
+          direction="horizontal"
+          size={size()}
+          min={80}
+          max={360}
+          collapseThreshold={100}
+          onResize={(next) => {
+            setCollapsed(false)
+            setSize(next)
+          }}
+          onCollapse={() => setCollapsed(true)}
+          style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
+        />
+      </div>
+    )
+  },
+}
+
+export const EdgeStart = {
+  render: () => {
+    const [size, setSize] = createSignal(240)
+    return (
+      <div style={{ display: "grid", gap: "8px" }}>
+        <div style={{ color: "var(--text-weak)", "font-size": "12px" }}>Size: {size()}px</div>
+        <div
+          style={{
+            width: `${size()}px`,
+            height: "48px",
+            "background-color": "var(--background-stronger)",
+            "border-radius": "6px",
+          }}
+        />
+        <mod.ResizeHandle
+          direction="horizontal"
+          edge="start"
+          size={size()}
+          min={120}
+          max={480}
+          onResize={setSize}
+          style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
+        />
+      </div>
+    )
+  },
+}

+ 113 - 0
packages/ui/src/components/select.stories.tsx

@@ -0,0 +1,113 @@
+// @ts-nocheck
+import * as mod from "./select"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Select menu for choosing a single option with optional grouping.
+
+Use \`children\` to customize option rendering.
+
+### API
+- Required: \`options\`.
+- Optional: \`current\`, \`placeholder\`, \`value\`, \`label\`, \`groupBy\`.
+- Accepts Button props for the trigger (\`variant\`, \`size\`).
+
+### Variants and states
+- Trigger supports "settings" style via \`triggerVariant\`.
+
+### Behavior
+- Uses Kobalte Select with optional item highlight callbacks.
+
+### Accessibility
+- TODO: confirm keyboard navigation and aria attributes from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="select"\` with slot attributes.
+
+`
+
+const story = create({
+  title: "UI/Select",
+  mod,
+  args: {
+    options: ["One", "Two", "Three"],
+    current: "One",
+    placeholder: "Choose...",
+    variant: "secondary",
+    size: "normal",
+  },
+})
+
+export default {
+  title: "UI/Select",
+  id: "components-select",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    triggerVariant: {
+      control: "select",
+      options: ["settings", undefined],
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Grouped = {
+  render: () => {
+    const options = [
+      { id: "alpha", label: "Alpha", group: "Group A" },
+      { id: "bravo", label: "Bravo", group: "Group A" },
+      { id: "delta", label: "Delta", group: "Group B" },
+    ]
+    return (
+      <mod.Select
+        options={options}
+        current={options[0]}
+        value={(item) => item.id}
+        label={(item) => item.label}
+        groupBy={(item) => item.group}
+        placeholder="Choose..."
+        variant="secondary"
+      />
+    )
+  },
+}
+
+export const SettingsTrigger = {
+  args: {
+    triggerVariant: "settings",
+  },
+}
+
+export const CustomRender = {
+  render: () => (
+    <mod.Select
+      options={["Primary", "Secondary", "Ghost"]}
+      current="Primary"
+      placeholder="Choose..."
+      variant="secondary"
+    >
+      {(item) => <span style={{ "text-transform": "uppercase" }}>{item}</span>}
+    </mod.Select>
+  ),
+}
+
+export const CustomTriggerStyle = {
+  args: {
+    triggerStyle: { "min-width": "180px", "justify-content": "space-between" },
+  },
+}
+
+export const Disabled = {
+  args: {
+    disabled: true,
+  },
+}

+ 7 - 0
packages/ui/src/components/session-review.stories.tsx

@@ -0,0 +1,7 @@
+// @ts-nocheck
+import * as mod from "./session-review"
+import { create } from "../storybook/scaffold"
+
+const story = create({ title: "UI/SessionReview", mod })
+export default { title: "UI/SessionReview", id: "components-session-review", component: story.meta.component }
+export const Basic = story.Basic

+ 7 - 0
packages/ui/src/components/session-turn.stories.tsx

@@ -0,0 +1,7 @@
+// @ts-nocheck
+import * as mod from "./session-turn"
+import { create } from "../storybook/scaffold"
+
+const story = create({ title: "UI/SessionTurn", mod })
+export default { title: "UI/SessionTurn", id: "components-session-turn", component: story.meta.component }
+export const Basic = story.Basic

+ 53 - 0
packages/ui/src/components/spinner.stories.tsx

@@ -0,0 +1,53 @@
+// @ts-nocheck
+import * as mod from "./spinner"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Animated loading indicator for inline or page-level loading states.
+
+Use with \`Button\` or in empty states.
+
+### API
+- Accepts standard SVG props (class, style).
+
+### Variants and states
+- Single default animation style.
+
+### Behavior
+- Animation is CSS-driven via data attributes.
+
+### Accessibility
+- Use alongside text or aria-live regions to convey loading state.
+
+### Theming/tokens
+- Uses \`data-component="spinner"\` for styling hooks.
+
+`
+
+const story = create({ title: "UI/Spinner", mod })
+
+export default {
+  title: "UI/Spinner",
+  id: "components-spinner",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+  render: () => (
+    <div style={{ display: "flex", gap: "16px", "align-items": "center" }}>
+      <mod.Spinner style={{ width: "12px", height: "12px" }} />
+      <mod.Spinner style={{ width: "20px", height: "20px" }} />
+      <mod.Spinner style={{ width: "28px", height: "28px" }} />
+    </div>
+  ),
+}

+ 54 - 0
packages/ui/src/components/sticky-accordion-header.stories.tsx

@@ -0,0 +1,54 @@
+// @ts-nocheck
+import { Accordion } from "./accordion"
+import * as mod from "./sticky-accordion-header"
+
+const docs = `### Overview
+Sticky accordion header wrapper for persistent section labels.
+
+Use only inside \`Accordion.Item\` with \`Accordion.Trigger\`.
+
+### API
+- Accepts standard header props and children.
+
+### Variants and states
+- Inherits accordion states.
+
+### Behavior
+- Renders inside an Accordion item header.
+
+### Accessibility
+- TODO: confirm semantics from Accordion.Header usage.
+
+### Theming/tokens
+- Uses \`data-component="sticky-accordion-header"\`.
+
+`
+
+export default {
+  title: "UI/StickyAccordionHeader",
+  id: "components-sticky-accordion-header",
+  component: mod.StickyAccordionHeader,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => (
+    <Accordion value="first">
+      <Accordion.Item value="first">
+        <mod.StickyAccordionHeader>
+          <Accordion.Trigger>Sticky header</Accordion.Trigger>
+        </mod.StickyAccordionHeader>
+        <Accordion.Content>
+          <div style={{ color: "var(--text-weak)", padding: "8px 0" }}>Accordion content.</div>
+        </Accordion.Content>
+      </Accordion.Item>
+    </Accordion>
+  ),
+}

+ 68 - 0
packages/ui/src/components/switch.stories.tsx

@@ -0,0 +1,68 @@
+// @ts-nocheck
+import * as mod from "./switch"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Toggle control for binary settings.
+
+Use in settings panels or forms.
+
+### API
+- Uses Kobalte Switch props (\`checked\`, \`defaultChecked\`, \`onChange\`).
+- Optional: \`hideLabel\`, \`description\`.
+- Children render as the label.
+
+### Variants and states
+- Checked/unchecked, disabled states.
+
+### Behavior
+- Controlled or uncontrolled usage via Kobalte props.
+
+### Accessibility
+- TODO: confirm aria attributes from Kobalte.
+
+### Theming/tokens
+- Uses \`data-component="switch"\` and slot attributes.
+
+`
+
+const story = create({
+  title: "UI/Switch",
+  mod,
+  args: { defaultChecked: true, children: "Enable notifications" },
+})
+
+export default {
+  title: "UI/Switch",
+  id: "components-switch",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const States = {
+  render: () => (
+    <div style={{ display: "grid", gap: "12px" }}>
+      <mod.Switch defaultChecked>Enabled</mod.Switch>
+      <mod.Switch>Disabled</mod.Switch>
+      <mod.Switch disabled>Disabled switch</mod.Switch>
+      <mod.Switch description="Optional description">With description</mod.Switch>
+    </div>
+  ),
+}
+
+export const HiddenLabel = {
+  args: {
+    children: "Hidden label",
+    hideLabel: true,
+    defaultChecked: true,
+  },
+}

+ 179 - 0
packages/ui/src/components/tabs.stories.tsx

@@ -0,0 +1,179 @@
+// @ts-nocheck
+import { IconButton } from "./icon-button"
+import { createSignal } from "solid-js"
+import * as mod from "./tabs"
+
+const docs = `### Overview
+Tabbed navigation for switching between related panels.
+
+Compose \`Tabs.List\` + \`Tabs.Trigger\` + \`Tabs.Content\`.
+
+### API
+- Root accepts Kobalte Tabs props (\`value\`, \`defaultValue\`, \`onChange\`).
+- \`variant\` sets visual style: normal, alt, pill, settings.
+- \`orientation\` supports horizontal or vertical layouts.
+- Trigger supports \`closeButton\`, \`hideCloseButton\`, and \`onMiddleClick\`.
+
+### Variants and states
+- Normal, alt, pill, settings variants.
+- Horizontal and vertical orientations.
+
+### Behavior
+- Uses Kobalte Tabs for roving focus and selection management.
+
+### Accessibility
+- TODO: confirm keyboard interactions from Kobalte Tabs.
+
+### Theming/tokens
+- Uses \`data-component="tabs"\` with variant/orientation data attributes.
+
+`
+
+export default {
+  title: "UI/Tabs",
+  id: "components-tabs",
+  component: mod.Tabs,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    variant: {
+      control: "select",
+      options: ["normal", "alt", "pill", "settings"],
+    },
+    orientation: {
+      control: "select",
+      options: ["horizontal", "vertical"],
+    },
+  },
+}
+
+export const Basic = {
+  args: {
+    variant: "normal",
+    orientation: "horizontal",
+    defaultValue: "overview",
+  },
+  render: (props) => (
+    <mod.Tabs {...props}>
+      <mod.Tabs.List>
+        <mod.Tabs.Trigger value="overview">Overview</mod.Tabs.Trigger>
+        <mod.Tabs.Trigger value="details">Details</mod.Tabs.Trigger>
+        <mod.Tabs.Trigger value="activity">Activity</mod.Tabs.Trigger>
+      </mod.Tabs.List>
+      <mod.Tabs.Content value="overview">Overview content</mod.Tabs.Content>
+      <mod.Tabs.Content value="details">Details content</mod.Tabs.Content>
+      <mod.Tabs.Content value="activity">Activity content</mod.Tabs.Content>
+    </mod.Tabs>
+  ),
+}
+
+export const Settings = {
+  args: {
+    variant: "settings",
+    orientation: "horizontal",
+    defaultValue: "general",
+  },
+  render: (props) => (
+    <mod.Tabs {...props}>
+      <mod.Tabs.List>
+        <mod.Tabs.Trigger value="general">General</mod.Tabs.Trigger>
+        <mod.Tabs.Trigger value="appearance">Appearance</mod.Tabs.Trigger>
+      </mod.Tabs.List>
+      <mod.Tabs.Content value="general">General settings</mod.Tabs.Content>
+      <mod.Tabs.Content value="appearance">Appearance settings</mod.Tabs.Content>
+    </mod.Tabs>
+  ),
+}
+
+export const Alt = {
+  args: {
+    variant: "alt",
+    orientation: "horizontal",
+    defaultValue: "first",
+  },
+  render: (props) => (
+    <mod.Tabs {...props}>
+      <mod.Tabs.List>
+        <mod.Tabs.Trigger value="first">First</mod.Tabs.Trigger>
+        <mod.Tabs.Trigger value="second">Second</mod.Tabs.Trigger>
+      </mod.Tabs.List>
+      <mod.Tabs.Content value="first">Alt content</mod.Tabs.Content>
+      <mod.Tabs.Content value="second">Alt content 2</mod.Tabs.Content>
+    </mod.Tabs>
+  ),
+}
+
+export const Vertical = {
+  args: {
+    variant: "pill",
+    orientation: "vertical",
+    defaultValue: "alpha",
+  },
+  render: (props) => (
+    <mod.Tabs {...props}>
+      <mod.Tabs.List>
+        <mod.Tabs.Trigger value="alpha">Alpha</mod.Tabs.Trigger>
+        <mod.Tabs.Trigger value="beta">Beta</mod.Tabs.Trigger>
+      </mod.Tabs.List>
+      <mod.Tabs.Content value="alpha">Alpha content</mod.Tabs.Content>
+      <mod.Tabs.Content value="beta">Beta content</mod.Tabs.Content>
+    </mod.Tabs>
+  ),
+}
+
+export const Closable = {
+  args: {
+    variant: "normal",
+    orientation: "horizontal",
+    defaultValue: "tab-1",
+  },
+  render: (props) => (
+    <mod.Tabs {...props}>
+      <mod.Tabs.List>
+        <mod.Tabs.Trigger
+          value="tab-1"
+          closeButton={<IconButton icon="close" size="small" variant="ghost" aria-label="Close tab" />}
+        >
+          Tab 1
+        </mod.Tabs.Trigger>
+        <mod.Tabs.Trigger value="tab-2">Tab 2</mod.Tabs.Trigger>
+      </mod.Tabs.List>
+      <mod.Tabs.Content value="tab-1">Closable content</mod.Tabs.Content>
+      <mod.Tabs.Content value="tab-2">Standard content</mod.Tabs.Content>
+    </mod.Tabs>
+  ),
+}
+
+export const MiddleClick = {
+  args: {
+    variant: "normal",
+    orientation: "horizontal",
+    defaultValue: "tab-1",
+  },
+  render: (props) => {
+    const [message, setMessage] = createSignal("Middle click a tab")
+    return (
+      <div style={{ display: "grid", gap: "8px" }}>
+        <div style={{ "font-size": "12px", color: "var(--text-weak)" }}>{message()}</div>
+        <mod.Tabs {...props}>
+          <mod.Tabs.List>
+            <mod.Tabs.Trigger value="tab-1" onMiddleClick={() => setMessage("Middle clicked tab-1")}>
+              Tab 1
+            </mod.Tabs.Trigger>
+            <mod.Tabs.Trigger value="tab-2" onMiddleClick={() => setMessage("Middle clicked tab-2")}>
+              Tab 2
+            </mod.Tabs.Trigger>
+          </mod.Tabs.List>
+          <mod.Tabs.Content value="tab-1">Tab 1 content</mod.Tabs.Content>
+          <mod.Tabs.Content value="tab-2">Tab 2 content</mod.Tabs.Content>
+        </mod.Tabs>
+      </div>
+    )
+  },
+}

+ 58 - 0
packages/ui/src/components/tag.stories.tsx

@@ -0,0 +1,58 @@
+// @ts-nocheck
+import * as mod from "./tag"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Small label tag for metadata and status chips.
+
+Use alongside headings or lists for quick metadata.
+
+### API
+- Optional: \`size\` (normal | large).
+- Accepts standard span props.
+
+### Variants and states
+- Size variants only.
+
+### Behavior
+- Inline element; size controls padding and font size via CSS.
+
+### Accessibility
+- Ensure text conveys meaning; avoid color-only distinction.
+
+### Theming/tokens
+- Uses \`data-component="tag"\` with size data attributes.
+
+`
+
+const story = create({ title: "UI/Tag", mod, args: { children: "Tag" } })
+export default {
+  title: "UI/Tag",
+  id: "components-tag",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+  argTypes: {
+    size: {
+      control: "select",
+      options: ["normal", "large"],
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Sizes = {
+  render: () => (
+    <div style={{ display: "flex", gap: "8px", "align-items": "center" }}>
+      <mod.Tag size="normal">Normal</mod.Tag>
+      <mod.Tag size="large">Large</mod.Tag>
+    </div>
+  ),
+}

+ 111 - 0
packages/ui/src/components/text-field.stories.tsx

@@ -0,0 +1,111 @@
+// @ts-nocheck
+import * as mod from "./text-field"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Text input with label, description, and optional copy-to-clipboard action.
+
+Pair with \`Tooltip\` and \`IconButton\` for copy affordance (built in).
+
+### API
+- Supports Kobalte TextField props: \`value\`, \`defaultValue\`, \`onChange\`, \`disabled\`, \`readOnly\`.
+- Optional: \`label\`, \`description\`, \`error\`, \`variant\`, \`copyable\`, \`multiline\`.
+
+### Variants and states
+- Normal and ghost variants.
+- Supports multiline textarea.
+
+### Behavior
+- When \`copyable\` is true, clicking copies the current value.
+
+### Accessibility
+- Label is hidden when \`hideLabel\` is true (sr-only).
+
+### Theming/tokens
+- Uses \`data-component="input"\` with slot attributes for styling.
+
+`
+
+const story = create({
+  title: "UI/TextField",
+  mod,
+  args: {
+    label: "Label",
+    placeholder: "Type here...",
+    defaultValue: "Hello",
+  },
+})
+
+export default {
+  title: "UI/TextField",
+  id: "components-text-field",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Variants = {
+  render: () => (
+    <div style={{ display: "grid", gap: "12px", width: "320px" }}>
+      <mod.TextField label="Normal" placeholder="Type here..." defaultValue="Value" />
+      <mod.TextField label="Ghost" variant="ghost" placeholder="Type here..." defaultValue="Value" />
+    </div>
+  ),
+}
+
+export const Multiline = {
+  args: {
+    label: "Description",
+    multiline: true,
+    defaultValue: "Line one\nLine two",
+  },
+}
+
+export const Copyable = {
+  args: {
+    label: "Invite link",
+    defaultValue: "https://example.com/invite/abc",
+    copyable: true,
+    copyKind: "link",
+  },
+}
+
+export const Error = {
+  args: {
+    label: "Email",
+    defaultValue: "invalid@",
+    error: "Enter a valid email address",
+  },
+}
+
+export const Disabled = {
+  args: {
+    label: "Disabled",
+    defaultValue: "Readonly",
+    disabled: true,
+  },
+}
+
+export const ReadOnly = {
+  args: {
+    label: "Read only",
+    defaultValue: "Read only value",
+    readOnly: true,
+  },
+}
+
+export const HiddenLabel = {
+  args: {
+    label: "Hidden label",
+    hideLabel: true,
+    placeholder: "Hidden label",
+  },
+}

+ 59 - 0
packages/ui/src/components/text-shimmer.stories.tsx

@@ -0,0 +1,59 @@
+// @ts-nocheck
+import * as mod from "./text-shimmer"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Animated shimmer effect for loading text placeholders.
+
+Use for pending states inside buttons or list rows.
+
+### API
+- Required: \`text\` string.
+- Optional: \`as\`, \`active\`, \`stepMs\`, \`durationMs\`.
+
+### Variants and states
+- Active/inactive state via \`active\`.
+
+### Behavior
+- Characters animate with staggered delays.
+
+### Accessibility
+- Uses \`aria-label\` with the full text.
+
+### Theming/tokens
+- Uses \`data-component="text-shimmer"\` and CSS custom properties for timing.
+
+`
+
+const story = create({ title: "UI/TextShimmer", mod, args: { text: "Loading..." } })
+
+export default {
+  title: "UI/TextShimmer",
+  id: "components-text-shimmer",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Inactive = {
+  args: {
+    text: "Static text",
+    active: false,
+  },
+}
+
+export const CustomTiming = {
+  args: {
+    text: "Custom timing",
+    stepMs: 80,
+    durationMs: 1800,
+  },
+}

+ 138 - 0
packages/ui/src/components/toast.stories.tsx

@@ -0,0 +1,138 @@
+// @ts-nocheck
+import * as mod from "./toast"
+import { Button } from "./button"
+
+const docs = `### Overview
+Toast notifications with optional icons, actions, and progress.
+
+Use brief titles/descriptions; limit actions to 1-2.
+
+### API
+- Use \`showToast\` or \`showPromiseToast\` to trigger toasts.
+- Render \`Toast.Region\` once per page.
+- \`Toast\` subcomponents compose the structure.
+
+### Variants and states
+- Variants: default, success, error, loading.
+- Optional actions and persistent toasts.
+
+### Behavior
+- Toasts render in a portal and auto-dismiss unless persistent.
+
+### Accessibility
+- TODO: confirm aria-live behavior from Kobalte Toast.
+
+### Theming/tokens
+- Uses \`data-component="toast"\` and slot data attributes.
+
+`
+
+export default {
+  title: "UI/Toast",
+  id: "components-toast",
+  component: mod.Toast,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = {
+  render: () => (
+    <div style={{ display: "grid", gap: "12px" }}>
+      <mod.Toast.Region />
+      <Button
+        variant="primary"
+        onClick={() =>
+          mod.showToast({
+            title: "Saved",
+            description: "Your changes are stored.",
+            variant: "success",
+            icon: "check",
+          })
+        }
+      >
+        Show success toast
+      </Button>
+      <Button
+        variant="secondary"
+        onClick={() =>
+          mod.showToast({
+            description: "This action needs attention.",
+            variant: "error",
+            icon: "warning",
+          })
+        }
+      >
+        Show error toast
+      </Button>
+    </div>
+  ),
+}
+
+export const Actions = {
+  render: () => (
+    <div style={{ display: "grid", gap: "12px" }}>
+      <mod.Toast.Region />
+      <Button
+        variant="secondary"
+        onClick={() =>
+          mod.showToast({
+            title: "Update available",
+            description: "Restart to apply the update.",
+            actions: [
+              { label: "Restart", onClick: "dismiss" },
+              { label: "Later", onClick: "dismiss" },
+            ],
+          })
+        }
+      >
+        Show action toast
+      </Button>
+    </div>
+  ),
+}
+
+export const Promise = {
+  render: () => (
+    <div style={{ display: "grid", gap: "12px" }}>
+      <mod.Toast.Region />
+      <Button
+        variant="secondary"
+        onClick={() =>
+          mod.showPromiseToast(() => new Promise((resolve) => setTimeout(() => resolve(true), 800)), {
+            loading: "Saving...",
+            success: () => "Saved",
+            error: () => "Failed",
+          })
+        }
+      >
+        Show promise toast
+      </Button>
+    </div>
+  ),
+}
+
+export const Loading = {
+  render: () => (
+    <div style={{ display: "grid", gap: "12px" }}>
+      <mod.Toast.Region />
+      <Button
+        variant="secondary"
+        onClick={() =>
+          mod.showToast({
+            description: "Syncing...",
+            variant: "loading",
+            persistent: true,
+          })
+        }
+      >
+        Show loading toast
+      </Button>
+    </div>
+  ),
+}

+ 64 - 0
packages/ui/src/components/tooltip.stories.tsx

@@ -0,0 +1,64 @@
+// @ts-nocheck
+import * as mod from "./tooltip"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Tooltip for contextual hints and keybind callouts.
+
+Use for short hints; avoid long descriptions.
+
+### API
+- Required: \`value\` (tooltip content).
+- Optional: \`inactive\`, \`forceOpen\`, placement props from Kobalte.
+
+### Variants and states
+- Supports keybind-style tooltip via \`TooltipKeybind\`.
+
+### Behavior
+- Opens on hover/focus; can be forced open.
+
+### Accessibility
+- TODO: confirm trigger semantics and focus behavior.
+
+### Theming/tokens
+- Uses \`data-component="tooltip"\` and related slots.
+
+`
+
+const story = create({ title: "UI/Tooltip", mod, args: { value: "Tooltip", children: "Hover me" } })
+
+export default {
+  title: "UI/Tooltip",
+  id: "components-tooltip",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Keybind = {
+  render: () => (
+    <mod.TooltipKeybind title="Search" keybind="Cmd+K">
+      <span style={{ "text-decoration": "underline" }}>Hover for keybind</span>
+    </mod.TooltipKeybind>
+  ),
+}
+
+export const ForcedOpen = {
+  args: {
+    forceOpen: true,
+  },
+}
+
+export const Inactive = {
+  args: {
+    inactive: true,
+  },
+}

+ 51 - 0
packages/ui/src/components/typewriter.stories.tsx

@@ -0,0 +1,51 @@
+// @ts-nocheck
+import * as mod from "./typewriter"
+import { create } from "../storybook/scaffold"
+
+const docs = `### Overview
+Animated typewriter text effect for short inline messages.
+
+Use for short status lines; avoid long paragraphs.
+
+### API
+- Optional: \`text\` string; if absent, nothing is rendered.
+- Optional: \`as\` to change the rendered element.
+
+### Variants and states
+- Single animation style with cursor blink.
+
+### Behavior
+- Types one character at a time with randomized delays.
+
+### Accessibility
+- TODO: confirm if cursor should be aria-hidden in all contexts.
+
+### Theming/tokens
+- Uses \`blinking-cursor\` class for cursor styling.
+
+`
+
+const story = create({ title: "UI/Typewriter", mod, args: { text: "Typewriter text" } })
+
+export default {
+  title: "UI/Typewriter",
+  id: "components-typewriter",
+  component: story.meta.component,
+  tags: ["autodocs"],
+  parameters: {
+    docs: {
+      description: {
+        component: docs,
+      },
+    },
+  },
+}
+
+export const Basic = story.Basic
+
+export const Inline = {
+  args: {
+    text: "Inline typewriter",
+    as: "span",
+  },
+}

+ 51 - 0
packages/ui/src/storybook/fixtures.ts

@@ -0,0 +1,51 @@
+export const diff = {
+  before: {
+    name: "src/greet.ts",
+    contents: `export function greet(name: string) {
+  return \`Hello, \${name}!\`
+}
+`,
+  },
+  after: {
+    name: "src/greet.ts",
+    contents: `export function greet(name: string, excited = false) {
+  const message = \`Hello, \${name}!\`
+  return excited ? \`\${message}!!\` : message
+}
+`,
+  },
+}
+
+export const code = {
+  name: "src/calc.ts",
+  contents: `export function sum(values: number[]) {
+  return values.reduce((total, value) => total + value, 0)
+}
+
+export function average(values: number[]) {
+  if (values.length === 0) return 0
+  return sum(values) / values.length
+}
+`,
+}
+
+export const markdown = [
+  "# Markdown",
+  "",
+  "Use **Markdown** for rich text.",
+  "",
+  "## Highlights",
+  "- Headings, lists, and code blocks",
+  "- Inline `code` and links",
+  "",
+  "```ts",
+  "export const value = 42",
+  "```",
+  "",
+  "More at https://example.com/docs",
+].join("\n")
+
+export const changes = {
+  additions: 18,
+  deletions: 6,
+}

+ 62 - 0
packages/ui/src/storybook/scaffold.tsx

@@ -0,0 +1,62 @@
+import { ErrorBoundary, type ValidComponent } from "solid-js"
+import { Dynamic } from "solid-js/web"
+
+function fn(value: unknown): value is (...args: never[]) => unknown {
+  return typeof value === "function"
+}
+
+function pick(mod: Record<string, unknown>, name?: string) {
+  if (name && fn(mod[name])) return mod[name]
+  if (fn(mod.default)) return mod.default
+
+  const preferred = Object.keys(mod)
+    .filter((k) => k[0] && k[0] === k[0].toUpperCase())
+    .find((k) => fn(mod[k]))
+  if (preferred) return mod[preferred]
+
+  const first = Object.keys(mod).find((k) => fn(mod[k]))
+  if (first) return mod[first]
+
+  return () => {
+    return (
+      <div data-component="storybook-missing">
+        <div>Missing component export.</div>
+        <div style="opacity:0.7;font-size:12px">Exports: {Object.keys(mod).join(", ") || "(none)"}</div>
+      </div>
+    )
+  }
+}
+
+export function create(input: {
+  title: string
+  mod: Record<string, unknown>
+  name?: string
+  args?: Record<string, unknown>
+}) {
+  const component = pick(input.mod, input.name) as unknown as ValidComponent
+
+  return {
+    meta: {
+      title: input.title,
+      component,
+    },
+    Basic: {
+      args: input.args ?? {},
+      render: (args: Record<string, unknown>) => {
+        return (
+          <ErrorBoundary
+            fallback={(err) => {
+              return (
+                <pre data-component="storybook-error" style="white-space:pre-wrap">
+                  {String(err)}
+                </pre>
+              )
+            }}
+          >
+            <Dynamic component={component} {...args} />
+          </ErrorBoundary>
+        )
+      },
+    },
+  }
+}

+ 2 - 1
packages/ui/tsconfig.json

@@ -17,5 +17,6 @@
     // Type Checking & Safety
     "strict": true,
     "types": ["vite/client", "bun"]
-  }
+  },
+  "exclude": ["**/*.stories.*", "**/*.mdx"]
 }

Некоторые файлы не были показаны из-за большого количества измененных файлов