Kaynağa Gözat

wip: css/ui and desktop work

Adam 4 ay önce
ebeveyn
işleme
47d9e01765
52 değiştirilmiş dosya ile 532 ekleme ve 1588 silme
  1. 2 1
      package.json
  2. 3 2
      packages/css/package.json
  3. BIN
      packages/css/src/assets/fonts/geist-italic.ttf
  4. BIN
      packages/css/src/assets/fonts/geist-italic.woff2
  5. BIN
      packages/css/src/assets/fonts/geist-mono-italic.ttf
  6. BIN
      packages/css/src/assets/fonts/geist-mono-italic.woff2
  7. BIN
      packages/css/src/assets/fonts/geist-mono.ttf
  8. BIN
      packages/css/src/assets/fonts/geist-mono.woff2
  9. BIN
      packages/css/src/assets/fonts/geist.ttf
  10. BIN
      packages/css/src/assets/fonts/geist.woff2
  11. 6 25
      packages/css/src/base.css
  12. 36 35
      packages/css/src/components/button.css
  13. 52 50
      packages/css/src/components/select.css
  14. 25 28
      packages/css/src/components/tabs.css
  15. 53 0
      packages/css/src/components/tooltip.css
  16. 1 0
      packages/css/src/index.css
  17. 3 2
      packages/css/src/tailwind.css
  18. 19 9
      packages/css/src/theme.css
  19. 2 3
      packages/desktop/index.html
  20. 2 1
      packages/desktop/package.json
  21. 0 163
      packages/desktop/scripts/vite-theme-plugin.ts
  22. 115 122
      packages/desktop/src/components/editor-pane.tsx
  23. 2 1
      packages/desktop/src/components/file-tree.tsx
  24. 2 2
      packages/desktop/src/components/prompt-form.tsx
  25. 0 217
      packages/desktop/src/components/resizeable-pane.tsx
  26. 2 1
      packages/desktop/src/components/select-dialog.tsx
  27. 0 108
      packages/desktop/src/components/select.tsx
  28. 1 1
      packages/desktop/src/components/session-list.tsx
  29. 2 1
      packages/desktop/src/components/session-timeline.tsx
  30. 0 48
      packages/desktop/src/components/sidebar-nav.tsx
  31. 0 1
      packages/desktop/src/context/index.ts
  32. 25 25
      packages/desktop/src/context/local.tsx
  33. 0 92
      packages/desktop/src/context/theme.tsx
  34. 1 168
      packages/desktop/src/index.css
  35. 22 31
      packages/desktop/src/index.tsx
  36. 56 54
      packages/desktop/src/pages/index.tsx
  37. 0 5
      packages/desktop/src/pages/layout.tsx
  38. 0 36
      packages/desktop/src/ui/button.tsx
  39. 1 1
      packages/desktop/src/ui/collapsible.tsx
  40. 0 109
      packages/desktop/src/ui/icon.tsx
  41. 0 5
      packages/desktop/src/ui/index.ts
  42. 0 13
      packages/desktop/src/ui/link.tsx
  43. 0 125
      packages/desktop/src/ui/logo.tsx
  44. 0 71
      packages/desktop/src/ui/tabs.tsx
  45. 0 2
      packages/desktop/vite.config.ts
  46. 2 1
      packages/ui/package.json
  47. 32 6
      packages/ui/src/app.tsx
  48. 44 0
      packages/ui/src/components/fonts.tsx
  49. 2 0
      packages/ui/src/components/index.ts
  50. 4 18
      packages/ui/src/components/tooltip.tsx
  51. 6 4
      packages/ui/src/index.css
  52. 9 1
      packages/ui/src/index.tsx

+ 2 - 1
package.json

@@ -25,6 +25,8 @@
       "@tsconfig/bun": "1.0.9",
       "@tsconfig/bun": "1.0.9",
       "@cloudflare/workers-types": "4.20251008.0",
       "@cloudflare/workers-types": "4.20251008.0",
       "@openauthjs/openauth": "0.0.0-20250322224806",
       "@openauthjs/openauth": "0.0.0-20250322224806",
+      "@solidjs/meta": "0.29.4",
+      "@tailwindcss/vite": "4.1.11",
       "diff": "8.0.2",
       "diff": "8.0.2",
       "ai": "5.0.8",
       "ai": "5.0.8",
       "hono": "4.7.10",
       "hono": "4.7.10",
@@ -36,7 +38,6 @@
       "remeda": "2.26.0",
       "remeda": "2.26.0",
       "solid-js": "1.9.9",
       "solid-js": "1.9.9",
       "tailwindcss": "4.1.11",
       "tailwindcss": "4.1.11",
-      "@tailwindcss/vite": "4.1.11",
       "vite": "7.1.4",
       "vite": "7.1.4",
       "vite-plugin-solid": "2.11.8"
       "vite-plugin-solid": "2.11.8"
     }
     }

+ 3 - 2
packages/css/package.json

@@ -1,10 +1,11 @@
 {
 {
   "name": "@opencode-ai/css",
   "name": "@opencode-ai/css",
-  "version": "0.15.5",
+  "version": "0.15.4",
   "type": "module",
   "type": "module",
   "exports": {
   "exports": {
     ".": "./src/index.css",
     ".": "./src/index.css",
-    "./*": "./src/*"
+    "./*": "./src/*",
+    "./fonts/*": "./src/assets/fonts/*"
   },
   },
   "scripts": {
   "scripts": {
     "dev": "bun run dev.ts",
     "dev": "bun run dev.ts",

BIN
packages/css/src/assets/fonts/geist-italic.ttf


BIN
packages/css/src/assets/fonts/geist-italic.woff2


BIN
packages/css/src/assets/fonts/geist-mono-italic.ttf


BIN
packages/css/src/assets/fonts/geist-mono-italic.woff2


BIN
packages/css/src/assets/fonts/geist-mono.ttf


BIN
packages/css/src/assets/fonts/geist-mono.woff2


BIN
packages/css/src/assets/fonts/geist.ttf


BIN
packages/css/src/assets/fonts/geist.woff2


+ 6 - 25
packages/css/src/base.css

@@ -30,18 +30,9 @@ html,
   line-height: 1.5; /* 1 */
   line-height: 1.5; /* 1 */
   -webkit-text-size-adjust: 100%; /* 2 */
   -webkit-text-size-adjust: 100%; /* 2 */
   tab-size: 4; /* 3 */
   tab-size: 4; /* 3 */
-  font-family: --theme(
-    --default-font-family,
-    ui-sans-serif,
-    system-ui,
-    sans-serif,
-    "Apple Color Emoji",
-    "Segoe UI Emoji",
-    "Segoe UI Symbol",
-    "Noto Color Emoji"
-  ); /* 4 */
-  font-feature-settings: --theme(--default-font-feature-settings, normal); /* 5 */
-  font-variation-settings: --theme(--default-font-variation-settings, normal); /* 6 */
+  font-family: var(--font-sans); /* 4 */
+  font-feature-settings: var(--font-sans--font-feature-settings, normal); /* 5 */
+  font-variation-settings: var(--default-font-variation-settings, normal); /* 6 */
   -webkit-tap-highlight-color: transparent; /* 7 */
   -webkit-tap-highlight-color: transparent; /* 7 */
 }
 }
 
 
@@ -110,19 +101,9 @@ code,
 kbd,
 kbd,
 samp,
 samp,
 pre {
 pre {
-  font-family: --theme(
-    --default-mono-font-family,
-    ui-monospace,
-    SFMono-Regular,
-    Menlo,
-    Monaco,
-    Consolas,
-    "Liberation Mono",
-    "Courier New",
-    monospace
-  ); /* 1 */
-  font-feature-settings: --theme(--default-mono-font-feature-settings, normal); /* 2 */
-  font-variation-settings: --theme(--default-mono-font-variation-settings, normal); /* 3 */
+  font-family: var(--font-mono); /* 1 */
+  font-feature-settings: var(--font-mono--font-feature-settings, normal); /* 2 */
+  font-variation-settings: var(--default-mono-font-variation-settings, normal); /* 3 */
   font-size: 1em; /* 4 */
   font-size: 1em; /* 4 */
 }
 }
 
 

+ 36 - 35
packages/css/src/components/button.css

@@ -6,76 +6,66 @@
   border-style: solid;
   border-style: solid;
   border-width: 1px;
   border-width: 1px;
   border-radius: var(--radius-md);
   border-radius: var(--radius-md);
-  font-family: var(--font-sans);
   font-size: var(--text-base);
   font-size: var(--text-base);
   line-height: var(--text-base--line-height);
   line-height: var(--text-base--line-height);
   font-weight: var(--font-weight-normal);
   font-weight: var(--font-weight-normal);
   text-decoration: none;
   text-decoration: none;
   user-select: none;
   user-select: none;
-  gap: calc(var(--spacing) * 2);
-
-  &:disabled {
-    opacity: 0.5;
-    cursor: not-allowed;
-  }
-
-  &:focus {
-    outline: none;
-  }
+  gap: calc(var(--spacing) * 0.5);
 
 
   &[data-variant="primary"] {
   &[data-variant="primary"] {
-    border-color: var(--border-default-border);
-    background-color: var(--surface-brand-surface-brand);
-    color: var(--text-on-brand-text-strong-on-brand);
+    border-color: var(--border-base);
+    background-color: var(--surface-brand-base);
+    color: var(--text-on-brand-strong);
 
 
     &:hover:not(:disabled) {
     &:hover:not(:disabled) {
-      border-color: var(--border-default-border-hover);
-      background-color: var(--surface-brand-surface-brand-hover);
+      border-color: var(--border-hover);
+      background-color: var(--surface-brand-hover);
     }
     }
     &:active:not(:disabled) {
     &:active:not(:disabled) {
-      border-color: var(--border-default-border-active);
-      background-color: var(--surface-brand-surface-brand-active);
+      border-color: var(--border-active);
+      background-color: var(--surface-brand-active);
     }
     }
     &:focus:not(:disabled) {
     &:focus:not(:disabled) {
-      border-color: var(--border-default-border-focus);
-      background-color: var(--surface-brand-surface-brand-focus);
+      border-color: var(--border-focus);
+      background-color: var(--surface-brand-focus);
     }
     }
   }
   }
 
 
   &[data-variant="secondary"] {
   &[data-variant="secondary"] {
-    border-color: var(--border-default-border);
-    background-color: var(--surface-default-surface);
-    color: var(--text-default-text);
+    border-color: var(--border-base);
+    background-color: var(--surface-base);
+    color: var(--text-strong);
 
 
     &:hover:not(:disabled) {
     &:hover:not(:disabled) {
-      border-color: var(--border-default-border-hover);
-      background-color: var(--surface-default-surface-hover);
+      border-color: var(--border-hover);
+      background-color: var(--surface-hover);
     }
     }
     &:active:not(:disabled) {
     &:active:not(:disabled) {
-      border-color: var(--border-default-border-active);
-      background-color: var(--surface-default-surface-active);
+      border-color: var(--border-active);
+      background-color: var(--surface-active);
     }
     }
     &:focus:not(:disabled) {
     &:focus:not(:disabled) {
-      border-color: var(--border-default-border-focus);
-      background-color: var(--surface-default-surface-focus);
+      border-color: var(--border-focus);
+      background-color: var(--surface-focus);
     }
     }
   }
   }
 
 
   &[data-variant="ghost"] {
   &[data-variant="ghost"] {
     border-color: transparent;
     border-color: transparent;
     background-color: transparent;
     background-color: transparent;
-    color: var(--text-default-text);
+    color: var(--text-strong);
 
 
     &:hover:not(:disabled) {
     &:hover:not(:disabled) {
-      background-color: var(--surface-default-surface-hover);
+      background-color: var(--surface-hover);
     }
     }
     &:active:not(:disabled) {
     &:active:not(:disabled) {
-      border-color: var(--border-default-border-active);
-      background-color: var(--surface-default-surface-active);
+      border-color: var(--border-active);
+      background-color: var(--surface-active);
     }
     }
     &:focus:not(:disabled) {
     &:focus:not(:disabled) {
-      border-color: var(--border-default-border-focus);
-      background-color: var(--surface-default-surface-focus);
+      border-color: var(--border-focus);
+      background-color: var(--surface-focus);
     }
     }
   }
   }
 
 
@@ -90,4 +80,15 @@
     font-size: var(--text-sm);
     font-size: var(--text-sm);
     line-height: var(--text-sm--line-height);
     line-height: var(--text-sm--line-height);
   }
   }
+
+  &:disabled {
+    border-color: var(--border-disabled);
+    background-color: var(--surface-disabled);
+    color: var(--text-weak);
+    cursor: not-allowed;
+  }
+
+  &:focus {
+    outline: none;
+  }
 }
 }

+ 52 - 50
packages/css/src/components/select.css

@@ -1,12 +1,49 @@
 [data-component="select"] {
 [data-component="select"] {
-  &:disabled {
-    opacity: 0.5;
-    cursor: not-allowed;
+  [data-slot="trigger"] {
+    [data-slot="value"] {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    [data-slot="icon"] {
+      width: fit-content;
+      height: fit-content;
+      flex-shrink: 0;
+      color: var(--text-weak);
+      transition: transform 0.1s ease-in-out;
+    }
   }
   }
+}
 
 
-  &:focus {
-    outline: none;
-    box-shadow: 0 0 0 2px var(--color-primary);
+[data-component="select-content"] {
+  min-width: 8rem;
+  overflow: hidden;
+  border-radius: var(--radius-md);
+  border-width: 1px;
+  border-style: solid;
+  border-color: var(--border-weak-base);
+  background-color: var(--surface-raised-base);
+  padding: calc(var(--spacing) * 1);
+  box-shadow: var(--shadow-md);
+  z-index: 50;
+
+  &[data-closed] {
+    animation: select-close 0.15s ease-out;
+  }
+
+  &[data-expanded] {
+    animation: select-open 0.15s ease-out;
+  }
+
+  [data-slot="list"] {
+    overflow-y: auto;
+    max-height: 12rem;
+    white-space: nowrap;
+    overflow-x: hidden;
+
+    &:focus {
+      outline: none;
+    }
   }
   }
 
 
   [data-slot="section"] {
   [data-slot="section"] {
@@ -14,7 +51,7 @@
     line-height: var(--text-xs--line-height);
     line-height: var(--text-xs--line-height);
     font-weight: var(--font-weight-light);
     font-weight: var(--font-weight-light);
     text-transform: uppercase;
     text-transform: uppercase;
-    color: var(--text-default-text-weak);
+    color: var(--text-weak);
     opacity: 0.6;
     opacity: 0.6;
     margin-top: calc(var(--spacing) * 3);
     margin-top: calc(var(--spacing) * 3);
     margin-left: calc(var(--spacing) * 2);
     margin-left: calc(var(--spacing) * 2);
@@ -31,7 +68,7 @@
     border-radius: var(--radius-sm);
     border-radius: var(--radius-sm);
     font-size: var(--text-xs);
     font-size: var(--text-xs);
     line-height: var(--text-xs--line-height);
     line-height: var(--text-xs--line-height);
-    color: var(--text-default-text);
+    color: var(--text-base);
     cursor: pointer;
     cursor: pointer;
     transition:
     transition:
       background-color 0.2s ease-in-out,
       background-color 0.2s ease-in-out,
@@ -40,60 +77,25 @@
     user-select: none;
     user-select: none;
 
 
     &[data-highlighted] {
     &[data-highlighted] {
-      background-color: var(--surface-default-surface);
+      background-color: var(--surface-base);
     }
     }
 
 
     &[data-disabled] {
     &[data-disabled] {
+      background-color: var(--surface-disabled);
       pointer-events: none;
       pointer-events: none;
-      opacity: 0.5;
     }
     }
 
 
     [data-slot="item-indicator"] {
     [data-slot="item-indicator"] {
       margin-left: auto;
       margin-left: auto;
     }
     }
-  }
 
 
-  [data-slot="trigger"] {
-    [data-slot="value"] {
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
+    &:focus {
+      outline: none;
     }
     }
-    [data-slot="icon"] {
-      width: fit-content;
-      height: fit-content;
-      flex-shrink: 0;
-      color: var(--text-default-text-weak);
-      transition: transform 0.1s ease-in-out;
-    }
-  }
-}
-
-[data-component="select-content"] {
-  min-width: 8rem;
-  overflow: hidden;
-  border-radius: var(--radius-md);
-  border-width: 1px;
-  border-style: solid;
-  border-color: var(--border-default-border-weak);
-  background-color: var(--surface-raised-surface-raised);
-  padding: calc(var(--spacing) * 1);
-  box-shadow: var(--shadow-md);
-  z-index: 50;
 
 
-  &[data-closed] {
-    animation: select-close 0.15s ease-out;
-  }
-
-  &[data-expanded] {
-    animation: select-open 0.15s ease-out;
-  }
-
-  [data-slot="list"] {
-    overflow-y: auto;
-    max-height: 12rem;
-    white-space: nowrap;
-    overflow-x: hidden;
+    &:hover {
+      background-color: var(--surface-hover);
+    }
   }
   }
 }
 }
 
 

+ 25 - 28
packages/css/src/components/tabs.css

@@ -1,98 +1,95 @@
 [data-component="tabs"] {
 [data-component="tabs"] {
+  width: 100%;
+  height: 100%;
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
-  height: 100%;
+  border-width: 1px;
+  border-style: solid;
+  border-radius: var(--radius-sm);
+  border-color: var(--border-weak-base);
+  background-color: var(--background-weaker);
+  overflow: clip;
 
 
   & [data-slot="list"] {
   & [data-slot="list"] {
+    width: 100%;
     position: relative;
     position: relative;
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
-    background-color: var(--surface-default-surface);
     overflow-x: auto;
     overflow-x: auto;
 
 
     /* Hide scrollbar */
     /* Hide scrollbar */
     scrollbar-width: none;
     scrollbar-width: none;
     -ms-overflow-style: none;
     -ms-overflow-style: none;
-
     &::-webkit-scrollbar {
     &::-webkit-scrollbar {
       display: none;
       display: none;
     }
     }
 
 
-    /* Divider between tabs */
-    & > [data-slot="trigger"]:not(:first-child) {
-      border-left: 1px solid var(--border-default-border-weak);
-    }
-
     /* After element to fill remaining space */
     /* After element to fill remaining space */
     &::after {
     &::after {
       content: "";
       content: "";
       display: block;
       display: block;
       flex-grow: 1;
       flex-grow: 1;
-      height: calc(var(--spacing) * 8);
-      border-left: 1px solid var(--border-default-border-weak);
-      border-bottom: 1px solid var(--border-default-border-weak);
+      height: 100%;
+      border-bottom: 1px solid var(--border-weak-base);
+      background-color: var(--background-weak);
+      border-top-right-radius: var(--radius-sm);
     }
     }
 
 
     &:empty::after {
     &:empty::after {
-      border-left: none;
+      display: none;
     }
     }
   }
   }
 
 
   & [data-slot="trigger"] {
   & [data-slot="trigger"] {
     position: relative;
     position: relative;
-    padding: 0 calc(var(--spacing) * 3);
-    height: calc(var(--spacing) * 8);
+    height: 36px;
+    padding: 8px 12px;
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
     font-size: var(--text-sm);
     font-size: var(--text-sm);
     font-weight: var(--font-weight-medium);
     font-weight: var(--font-weight-medium);
-    color: var(--text-default-text-weak);
+    color: var(--text-weak);
     cursor: pointer;
     cursor: pointer;
     white-space: nowrap;
     white-space: nowrap;
     flex-shrink: 0;
     flex-shrink: 0;
-    border-bottom: 1px solid var(--border-default-border-weak);
-    background-color: transparent;
+    border-bottom: 1px solid var(--border-weak-base);
+    border-right: 1px solid var(--border-weak-base);
+    background-color: var(--background-weak);
     transition:
     transition:
       background-color 0.15s ease,
       background-color 0.15s ease,
       color 0.15s ease;
       color 0.15s ease;
 
 
     &:disabled {
     &:disabled {
       pointer-events: none;
       pointer-events: none;
-      opacity: 0.5;
+      color: var(--text-weaker);
     }
     }
-
     &:focus-visible {
     &:focus-visible {
       outline: none;
       outline: none;
-      box-shadow: 0 0 0 2px var(--border-default-border-focus);
+      box-shadow: 0 0 0 2px var(--border-focus);
     }
     }
-
     &[data-selected] {
     &[data-selected] {
-      color: var(--text-default-text);
-      background-color: var(--surface-panel-surface);
+      color: var(--text-base);
+      background-color: transparent;
       border-bottom-color: transparent;
       border-bottom-color: transparent;
     }
     }
-
     &:hover:not(:disabled):not([data-selected]) {
     &:hover:not(:disabled):not([data-selected]) {
-      color: var(--text-default-text);
+      color: var(--text-strong);
     }
     }
   }
   }
 
 
   & [data-slot="content"] {
   & [data-slot="content"] {
-    background-color: var(--surface-panel-surface);
     overflow-y: auto;
     overflow-y: auto;
     flex: 1;
     flex: 1;
 
 
     /* Hide scrollbar */
     /* Hide scrollbar */
     scrollbar-width: none;
     scrollbar-width: none;
     -ms-overflow-style: none;
     -ms-overflow-style: none;
-
     &::-webkit-scrollbar {
     &::-webkit-scrollbar {
       display: none;
       display: none;
     }
     }
 
 
     &:focus-visible {
     &:focus-visible {
       outline: none;
       outline: none;
-      box-shadow: 0 0 0 2px var(--border-default-border-focus);
     }
     }
   }
   }
 }
 }

+ 53 - 0
packages/css/src/components/tooltip.css

@@ -0,0 +1,53 @@
+/* [data-component="tooltip-trigger"] { */
+/*   display: flex; */
+/*   align-items: center; */
+/* } */
+
+[data-component="tooltip"] {
+  z-index: 1000;
+  max-width: 320px;
+  border-radius: var(--radius-md);
+  background-color: var(--surface-base);
+  padding: calc(var(--spacing) * 0.5) calc(var(--spacing) * 1);
+  font-size: var(--text-xs);
+  font-weight: var(--font-weight-medium);
+  color: var(--text-base);
+  box-shadow: var(--shadow-md);
+  pointer-events: none !important;
+  transition: all 150ms ease-out;
+  transform: translate3d(0, 0, 0);
+  transform-origin: var(--kb-tooltip-content-transform-origin);
+
+  &[data-expanded] {
+    opacity: 1;
+    transform: translate3d(0, 0, 0);
+  }
+
+  &[data-closed] {
+    opacity: 0;
+  }
+
+  &[data-placement="top"] {
+    &[data-closed] {
+      transform: translate3d(0, 4px, 0);
+    }
+  }
+
+  &[data-placement="bottom"] {
+    &[data-closed] {
+      transform: translate3d(0, -4px, 0);
+    }
+  }
+
+  &[data-placement="left"] {
+    &[data-closed] {
+      transform: translate3d(4px, 0, 0);
+    }
+  }
+
+  &[data-placement="right"] {
+    &[data-closed] {
+      transform: translate3d(-4px, 0, 0);
+    }
+  }
+}

+ 1 - 0
packages/css/src/index.css

@@ -9,5 +9,6 @@
 @import "./components/icon.css" layer(components);
 @import "./components/icon.css" layer(components);
 @import "./components/select.css" layer(components);
 @import "./components/select.css" layer(components);
 @import "./components/tabs.css" layer(components);
 @import "./components/tabs.css" layer(components);
+@import "./components/tooltip.css" layer(components);
 
 
 @import "./utilities.css" layer(utilities);
 @import "./utilities.css" layer(utilities);

+ 3 - 2
packages/css/src/tailwind.css

@@ -1,8 +1,9 @@
-@import "./index.css";
-
+@layer theme, base, components, utilities;
 @import "tailwindcss/theme.css" layer(theme);
 @import "tailwindcss/theme.css" layer(theme);
 @import "tailwindcss/utilities.css" layer(utilities);
 @import "tailwindcss/utilities.css" layer(utilities);
 
 
+@import "./index.css";
+
 @theme {
 @theme {
   --shadow-*: initial;
   --shadow-*: initial;
   --shadow-xs-border-selected: var(--shadow-xs-border-selected);
   --shadow-xs-border-selected: var(--shadow-xs-border-selected);

+ 19 - 9
packages/css/src/theme.css

@@ -1,8 +1,13 @@
 :root {
 :root {
   --font-sans:
   --font-sans:
-    ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+    geist, geist-fallback, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
+    "Segoe UI Symbol", "Noto Color Emoji";
+  --font-sans--font-feature-settings: "ss02" 1;
   --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
   --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
-  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  --font-mono:
+    geist-mono, geist-mono-fallback, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
+    "Courier New", monospace;
+  --font-mono--font-feature-settings: "ss02" 1;
 
 
   --size-12: 12;
   --size-12: 12;
   --size-14: 14;
   --size-14: 14;
@@ -99,13 +104,18 @@
   --radius-3xl: 1.5rem;
   --radius-3xl: 1.5rem;
   --radius-4xl: 2rem;
   --radius-4xl: 2rem;
 
 
-  --shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
-  --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
-  --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
-  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
-  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
-  --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
-  --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
+  --shadow-xs-border-selected:
+    0 0 0 3px var(--border-weak-selected, rgba(1, 103, 255, 0.29)),
+    0 0 0 1px var(--border-selected, rgba(0, 74, 255, 0.99)), 0 1px 2px -1px rgba(19, 16, 16, 0.25),
+    0 1px 2px 0 rgba(19, 16, 16, 0.08), 0 1px 3px 0 rgba(19, 16, 16, 0.12);
+
+  /* --shadow-2xs: 0 1px rgb(0 0 0 / 0.05); */
+  /* --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); */
+  /* --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); */
+  /* --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); */
+  /* --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); */
+  /* --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); */
+  /* --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); */
 
 
   /* --inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05); */
   /* --inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05); */
   /* --inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05); */
   /* --inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05); */

+ 2 - 3
packages/desktop/index.html

@@ -1,12 +1,11 @@
 <!doctype html>
 <!doctype html>
-<html lang="en" class="h-full bg-background">
+<html lang="en" class="h-full bg-background-weak">
   <head>
   <head>
     <meta charset="utf-8" />
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <meta name="theme-color" content="#000000" />
     <meta name="theme-color" content="#000000" />
     <link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.svg" />
     <link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.svg" />
-    <link rel="stylesheet" href="/src/assets/theme.css" />
-    <title>opencode</title>
+    <title>OpenCode</title>
   </head>
   </head>
   <body class="h-full overscroll-none select-none">
   <body class="h-full overscroll-none select-none">
     <script>
     <script>

+ 2 - 1
packages/desktop/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@opencode-ai/desktop",
   "name": "@opencode-ai/desktop",
-  "version": "0.15.5",
+  "version": "0.15.4",
   "description": "",
   "description": "",
   "type": "module",
   "type": "module",
   "scripts": {
   "scripts": {
@@ -29,6 +29,7 @@
     "@solid-primitives/event-bus": "1.1.2",
     "@solid-primitives/event-bus": "1.1.2",
     "@solid-primitives/resize-observer": "2.1.3",
     "@solid-primitives/resize-observer": "2.1.3",
     "@solid-primitives/scroll": "2.1.3",
     "@solid-primitives/scroll": "2.1.3",
+    "@solidjs/meta": "catalog:",
     "@solidjs/router": "0.15.3",
     "@solidjs/router": "0.15.3",
     "@thisbeyond/solid-dnd": "0.7.5",
     "@thisbeyond/solid-dnd": "0.7.5",
     "diff": "catalog:",
     "diff": "catalog:",

+ 0 - 163
packages/desktop/scripts/vite-theme-plugin.ts

@@ -1,163 +0,0 @@
-import type { Plugin } from "vite"
-import { readdir, readFile, writeFile } from "fs/promises"
-import { join, resolve } from "path"
-
-interface ThemeDefinition {
-  $schema?: string
-  defs?: Record<string, string>
-  theme: Record<string, any>
-}
-
-interface ResolvedThemeColor {
-  dark: string
-  light: string
-}
-
-class ColorResolver {
-  private colors: Map<string, any> = new Map()
-  private visited: Set<string> = new Set()
-
-  constructor(defs: Record<string, string> = {}, theme: Record<string, any> = {}) {
-    Object.entries(defs).forEach(([key, value]) => {
-      this.colors.set(key, value)
-    })
-    Object.entries(theme).forEach(([key, value]) => {
-      this.colors.set(key, value)
-    })
-  }
-
-  resolveColor(key: string, value: any): ResolvedThemeColor {
-    if (this.visited.has(key)) {
-      throw new Error(`Circular reference detected for color ${key}`)
-    }
-
-    this.visited.add(key)
-
-    try {
-      if (typeof value === "string") {
-        if (value === "none") return { dark: value, light: value }
-        if (value.startsWith("#")) {
-          return { dark: value.toLowerCase(), light: value.toLowerCase() }
-        }
-        const resolved = this.resolveReference(value)
-        return { dark: resolved, light: resolved }
-      }
-      if (typeof value === "object" && value !== null) {
-        const dark = this.resolveColorValue(value.dark || value.light || "#000000")
-        const light = this.resolveColorValue(value.light || value.dark || "#FFFFFF")
-        return { dark, light }
-      }
-      return { dark: "#000000", light: "#FFFFFF" }
-    } finally {
-      this.visited.delete(key)
-    }
-  }
-
-  private resolveColorValue(value: any): string {
-    if (typeof value === "string") {
-      if (value === "none") return value
-      if (value.startsWith("#")) {
-        return value.toLowerCase()
-      }
-      return this.resolveReference(value)
-    }
-    return value
-  }
-
-  private resolveReference(ref: string): string {
-    const colorValue = this.colors.get(ref)
-    if (colorValue === undefined) {
-      throw new Error(`Color reference '${ref}' not found`)
-    }
-    if (typeof colorValue === "string") {
-      if (colorValue === "none") return colorValue
-      if (colorValue.startsWith("#")) {
-        return colorValue.toLowerCase()
-      }
-      return this.resolveReference(colorValue)
-    }
-    return colorValue
-  }
-}
-
-function kebabCase(str: string): string {
-  return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
-}
-
-function parseTheme(themeData: ThemeDefinition): Record<string, ResolvedThemeColor> {
-  const resolver = new ColorResolver(themeData.defs, themeData.theme)
-  const colors: Record<string, ResolvedThemeColor> = {}
-  Object.entries(themeData.theme).forEach(([key, value]) => {
-    colors[key] = resolver.resolveColor(key, value)
-  })
-  return colors
-}
-
-async function loadThemes(): Promise<Record<string, Record<string, ResolvedThemeColor>>> {
-  const themesDir = resolve(__dirname, "../../tui/internal/theme/themes")
-  const files = await readdir(themesDir)
-  const themes: Record<string, Record<string, ResolvedThemeColor>> = {}
-
-  for (const file of files) {
-    if (!file.endsWith(".json")) continue
-
-    const themeName = file.replace(".json", "")
-    const themeData: ThemeDefinition = JSON.parse(await readFile(join(themesDir, file), "utf-8"))
-
-    themes[themeName] = parseTheme(themeData)
-  }
-
-  return themes
-}
-
-function generateCSS(themes: Record<string, Record<string, ResolvedThemeColor>>): string {
-  let css = `/* Auto-generated theme CSS - Do not edit manually */\n:root {\n`
-
-  const defaultTheme = themes["opencode"] || Object.values(themes)[0]
-  if (defaultTheme) {
-    Object.entries(defaultTheme).forEach(([key, color]) => {
-      const cssVar = `--theme-${kebabCase(key)}`
-      css += `  ${cssVar}: ${color.light};\n`
-    })
-  }
-  css += `}\n\n`
-
-  Object.entries(themes).forEach(([themeName, colors]) => {
-    css += `[data-theme="${themeName}"][data-dark="false"] {\n`
-    Object.entries(colors).forEach(([key, color]) => {
-      const cssVar = `--theme-${kebabCase(key)}`
-      css += `  ${cssVar}: ${color.light};\n`
-    })
-    css += `}\n\n`
-
-    css += `[data-theme="${themeName}"][data-dark="true"] {\n`
-    Object.entries(colors).forEach(([key, color]) => {
-      const cssVar = `--theme-${kebabCase(key)}`
-      css += `  ${cssVar}: ${color.dark};\n`
-    })
-    css += `}\n\n`
-  })
-
-  return css
-}
-
-export function generateThemeCSS(): Plugin {
-  return {
-    name: "generate-theme-css",
-    async buildStart() {
-      try {
-        console.log("Generating theme CSS...")
-        const themes = await loadThemes()
-        const css = generateCSS(themes)
-
-        const outputPath = resolve(__dirname, "../src/assets/theme.css")
-        await writeFile(outputPath, css)
-
-        console.log(`✅ Generated theme CSS with ${Object.keys(themes).length} themes`)
-        console.log(`   Output: ${outputPath}`)
-      } catch (error) {
-        throw new Error(`Theme CSS generation failed: ${error}`)
-      }
-    },
-  }
-}

+ 115 - 122
packages/desktop/src/components/editor-pane.tsx

@@ -1,6 +1,7 @@
 import { For, Match, Show, Switch, createSignal, splitProps } from "solid-js"
 import { For, Match, Show, Switch, createSignal, splitProps } from "solid-js"
-import { Tabs } from "@/ui/tabs"
-import { FileIcon, Icon, IconButton, Logo, Tooltip } from "@/ui"
+import { Tabs, Tooltip } from "@opencode-ai/ui"
+import { Icon } from "@opencode-ai/ui"
+import { FileIcon, IconButton } from "@/ui"
 import {
 import {
   DragDropProvider,
   DragDropProvider,
   DragDropSensors,
   DragDropSensors,
@@ -64,127 +65,119 @@ export default function EditorPane(props: EditorPaneProps): JSX.Element {
   }
   }
 
 
   return (
   return (
-    <div class="relative flex h-full flex-col">
-      <DragDropProvider
-        onDragStart={handleDragStart}
-        onDragEnd={handleDragEnd}
-        onDragOver={handleDragOver}
-        collisionDetector={closestCenter}
-      >
-        <DragDropSensors />
-        <ConstrainDragYAxis />
-        <Tabs
-          class="relative grow w-full flex flex-col h-full"
-          value={local.file.active()?.path}
-          onChange={handleTabChange}
-        >
-          <div class="sticky top-0 shrink-0 flex">
-            <Tabs.List class="grow">
-              <SortableProvider ids={local.file.opened().map((file) => file.path)}>
-                <For each={local.file.opened()}>
-                  {(file) => (
-                    <SortableTab file={file} onTabClick={localProps.onFileClick} onTabClose={handleTabClose} />
-                  )}
-                </For>
-              </SortableProvider>
-            </Tabs.List>
-            <div class="shrink-0 h-full flex items-center gap-1 px-2 border-b border-border-subtle/40">
-              <Show when={local.file.active() && local.file.active()!.content?.diff}>
-                {(() => {
-                  const activeFile = local.file.active()!
-                  const view = local.file.view(activeFile.path)
-                  return (
-                    <div class="flex items-center gap-1">
-                      <Show when={view !== "raw"}>
-                        <div class="mr-1 flex items-center gap-1">
-                          <Tooltip value="Previous change" placement="bottom">
-                            <IconButton size="xs" variant="ghost" onClick={() => navigateChange(-1)}>
-                              <Icon name="arrow-up" size={14} />
-                            </IconButton>
-                          </Tooltip>
-                          <Tooltip value="Next change" placement="bottom">
-                            <IconButton size="xs" variant="ghost" onClick={() => navigateChange(1)}>
-                              <Icon name="arrow-down" size={14} />
-                            </IconButton>
-                          </Tooltip>
-                        </div>
-                      </Show>
-                      <Tooltip value="Raw" placement="bottom">
-                        <IconButton
-                          size="xs"
-                          variant="ghost"
-                          classList={{
-                            "text-text": view === "raw",
-                            "text-text-muted/70": view !== "raw",
-                            "bg-background-element": view === "raw",
-                          }}
-                          onClick={() => local.file.setView(activeFile.path, "raw")}
-                        >
-                          <Icon name="file-text" size={14} />
-                        </IconButton>
-                      </Tooltip>
-                      <Tooltip value="Unified diff" placement="bottom">
-                        <IconButton
-                          size="xs"
-                          variant="ghost"
-                          classList={{
-                            "text-text": view === "diff-unified",
-                            "text-text-muted/70": view !== "diff-unified",
-                            "bg-background-element": view === "diff-unified",
-                          }}
-                          onClick={() => local.file.setView(activeFile.path, "diff-unified")}
-                        >
-                          <Icon name="checklist" size={14} />
-                        </IconButton>
-                      </Tooltip>
-                      <Tooltip value="Split diff" placement="bottom">
-                        <IconButton
-                          size="xs"
-                          variant="ghost"
-                          classList={{
-                            "text-text": view === "diff-split",
-                            "text-text-muted/70": view !== "diff-split",
-                            "bg-background-element": view === "diff-split",
-                          }}
-                          onClick={() => local.file.setView(activeFile.path, "diff-split")}
-                        >
-                          <Icon name="columns" size={14} />
-                        </IconButton>
-                      </Tooltip>
-                    </div>
-                  )
-                })()}
-              </Show>
-            </div>
+    <DragDropProvider
+      onDragStart={handleDragStart}
+      onDragEnd={handleDragEnd}
+      onDragOver={handleDragOver}
+      collisionDetector={closestCenter}
+    >
+      <DragDropSensors />
+      <ConstrainDragYAxis />
+      <Tabs value={local.file.active()?.path} onChange={handleTabChange}>
+        <div class="sticky top-0 shrink-0 flex">
+          <Tabs.List>
+            <SortableProvider ids={local.file.opened().map((file) => file.path)}>
+              <For each={local.file.opened()}>
+                {(file) => <SortableTab file={file} onTabClick={localProps.onFileClick} onTabClose={handleTabClose} />}
+              </For>
+            </SortableProvider>
+          </Tabs.List>
+          <div class="hidden shrink-0 h-full _flex items-center gap-1 px-2 border-b border-border-subtle/40">
+            <Show when={local.file.active() && local.file.active()!.content?.diff}>
+              {(() => {
+                const activeFile = local.file.active()!
+                const view = local.file.view(activeFile.path)
+                return (
+                  <div class="flex items-center gap-1">
+                    <Show when={view !== "raw"}>
+                      <div class="mr-1 flex items-center gap-1">
+                        <Tooltip value="Previous change" placement="bottom">
+                          <IconButton size="xs" variant="ghost" onClick={() => navigateChange(-1)}>
+                            <Icon name="arrow-up" size={14} />
+                          </IconButton>
+                        </Tooltip>
+                        <Tooltip value="Next change" placement="bottom">
+                          <IconButton size="xs" variant="ghost" onClick={() => navigateChange(1)}>
+                            <Icon name="arrow-down" size={14} />
+                          </IconButton>
+                        </Tooltip>
+                      </div>
+                    </Show>
+                    <Tooltip value="Raw" placement="bottom">
+                      <IconButton
+                        size="xs"
+                        variant="ghost"
+                        classList={{
+                          "text-text": view === "raw",
+                          "text-text-muted/70": view !== "raw",
+                          "bg-background-element": view === "raw",
+                        }}
+                        onClick={() => local.file.setView(activeFile.path, "raw")}
+                      >
+                        <Icon name="file-text" size={14} />
+                      </IconButton>
+                    </Tooltip>
+                    <Tooltip value="Unified diff" placement="bottom">
+                      <IconButton
+                        size="xs"
+                        variant="ghost"
+                        classList={{
+                          "text-text": view === "diff-unified",
+                          "text-text-muted/70": view !== "diff-unified",
+                          "bg-background-element": view === "diff-unified",
+                        }}
+                        onClick={() => local.file.setView(activeFile.path, "diff-unified")}
+                      >
+                        <Icon name="checklist" size={14} />
+                      </IconButton>
+                    </Tooltip>
+                    <Tooltip value="Split diff" placement="bottom">
+                      <IconButton
+                        size="xs"
+                        variant="ghost"
+                        classList={{
+                          "text-text": view === "diff-split",
+                          "text-text-muted/70": view !== "diff-split",
+                          "bg-background-element": view === "diff-split",
+                        }}
+                        onClick={() => local.file.setView(activeFile.path, "diff-split")}
+                      >
+                        <Icon name="columns" size={14} />
+                      </IconButton>
+                    </Tooltip>
+                  </div>
+                )
+              })()}
+            </Show>
           </div>
           </div>
-          <For each={local.file.opened()}>
-            {(file) => (
-              <Tabs.Content value={file.path} class="grow h-full pt-1 select-text">
-                {(() => {
-                  const view = local.file.view(file.path)
-                  const showRaw = view === "raw" || !file.content?.diff
-                  const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "")
-                  return <Code path={file.path} code={code} class="[&_code]:pb-60" />
-                })()}
-              </Tabs.Content>
-            )}
-          </For>
-        </Tabs>
-        <DragOverlay>
-          {(() => {
-            const id = activeItem()
-            if (!id) return null
-            const draggedFile = local.file.node(id)
-            if (!draggedFile) return null
-            return (
-              <div class="relative px-3 h-8 flex items-center text-sm font-medium text-text whitespace-nowrap shrink-0 bg-background-panel border-x border-border-subtle/40 border-b border-b-transparent">
-                <TabVisual file={draggedFile} />
-              </div>
-            )
-          })()}
-        </DragOverlay>
-      </DragDropProvider>
-    </div>
+        </div>
+        <For each={local.file.opened()}>
+          {(file) => (
+            <Tabs.Content value={file.path} class="select-text">
+              {(() => {
+                const view = local.file.view(file.path)
+                const showRaw = view === "raw" || !file.content?.diff
+                const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "")
+                return <Code path={file.path} code={code} class="[&_code]:pb-60" />
+              })()}
+            </Tabs.Content>
+          )}
+        </For>
+      </Tabs>
+      <DragOverlay>
+        {(() => {
+          const id = activeItem()
+          if (!id) return null
+          const draggedFile = local.file.node(id)
+          if (!draggedFile) return null
+          return (
+            <div class="relative px-3 h-8 flex items-center text-sm font-medium text-text whitespace-nowrap shrink-0 bg-background-panel border-x border-border-subtle/40 border-b border-b-transparent">
+              <TabVisual file={draggedFile} />
+            </div>
+          )
+        })()}
+      </DragOverlay>
+    </DragDropProvider>
   )
   )
 }
 }
 
 

+ 2 - 1
packages/desktop/src/components/file-tree.tsx

@@ -1,6 +1,7 @@
 import { useLocal } from "@/context"
 import { useLocal } from "@/context"
 import type { LocalFile } from "@/context/local"
 import type { LocalFile } from "@/context/local"
-import { Collapsible, FileIcon, Tooltip } from "@/ui"
+import { Tooltip } from "@opencode-ai/ui"
+import { Collapsible, FileIcon } from "@/ui"
 import { For, Match, Switch, Show, type ComponentProps, type ParentProps } from "solid-js"
 import { For, Match, Switch, Show, type ComponentProps, type ParentProps } from "solid-js"
 import { Dynamic } from "solid-js/web"
 import { Dynamic } from "solid-js/web"
 
 

+ 2 - 2
packages/desktop/src/components/prompt-form.tsx

@@ -1,8 +1,8 @@
 import { For, Show, createMemo, onCleanup, type JSX } from "solid-js"
 import { For, Show, createMemo, onCleanup, type JSX } from "solid-js"
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
 import { Popover } from "@kobalte/core/popover"
 import { Popover } from "@kobalte/core/popover"
-import { Button, FileIcon, Icon, IconButton, Tooltip } from "@/ui"
-import { Select } from "@/components/select"
+import { Tooltip, Button, Icon, Select } from "@opencode-ai/ui"
+import { FileIcon, IconButton } from "@/ui"
 import { useLocal } from "@/context"
 import { useLocal } from "@/context"
 import type { FileContext, LocalFile } from "@/context/local"
 import type { FileContext, LocalFile } from "@/context/local"
 import { getDirectory, getFilename } from "@/utils"
 import { getDirectory, getFilename } from "@/utils"

+ 0 - 217
packages/desktop/src/components/resizeable-pane.tsx

@@ -1,217 +0,0 @@
-import { batch, createContext, createMemo, createSignal, onCleanup, Show, useContext } from "solid-js"
-import type { ComponentProps, JSX } from "solid-js"
-import { createStore } from "solid-js/store"
-import { useLocal } from "@/context"
-
-type PaneDefault = number | { size: number; visible?: boolean }
-
-type LayoutContextValue = {
-  id: string
-  register: (pane: string, options: { min?: number | string; max?: number | string }) => void
-  size: (pane: string) => number
-  visible: (pane: string) => boolean
-  percent: (pane: string) => number
-  next: (pane: string) => string | undefined
-  startDrag: (left: string, right: string | undefined, event: MouseEvent) => void
-  dragging: () => string | undefined
-}
-
-const LayoutContext = createContext<LayoutContextValue | undefined>(undefined)
-
-export interface ResizeableLayoutProps {
-  id: string
-  defaults: Record<string, PaneDefault>
-  class?: ComponentProps<"div">["class"]
-  classList?: ComponentProps<"div">["classList"]
-  children: JSX.Element
-}
-
-export interface ResizeablePaneProps {
-  id: string
-  minSize?: number | string
-  maxSize?: number | string
-  class?: ComponentProps<"div">["class"]
-  classList?: ComponentProps<"div">["classList"]
-  children: JSX.Element
-}
-
-export function ResizeableLayout(props: ResizeableLayoutProps) {
-  const local = useLocal()
-  const [meta, setMeta] = createStore<Record<string, { min: number; max: number; minPx?: number; maxPx?: number }>>({})
-  const [dragging, setDragging] = createSignal<string>()
-  let container: HTMLDivElement | undefined
-
-  local.layout.ensure(props.id, props.defaults)
-
-  const order = createMemo(() => local.layout.order(props.id))
-  const visibleOrder = createMemo(() => order().filter((pane) => local.layout.visible(props.id, pane)))
-  const totalVisible = createMemo(() => {
-    const panes = visibleOrder()
-    if (!panes.length) return 0
-    return panes.reduce((total, pane) => total + local.layout.size(props.id, pane), 0)
-  })
-
-  const percent = (pane: string) => {
-    const panes = visibleOrder()
-    if (!panes.length) return 0
-    const total = totalVisible()
-    if (!total) return 100 / panes.length
-    return (local.layout.size(props.id, pane) / total) * 100
-  }
-
-  const nextPane = (pane: string) => {
-    const panes = visibleOrder()
-    const index = panes.indexOf(pane)
-    if (index === -1) return undefined
-    return panes[index + 1]
-  }
-
-  const minMax = (pane: string) => meta[pane] ?? { min: 5, max: 95 }
-
-  const pxToPercent = (px: number, total: number) => (px / total) * 100
-
-  const boundsForPair = (left: string, right: string, total: number) => {
-    const leftMeta = minMax(left)
-    const rightMeta = minMax(right)
-    const containerWidth = container?.getBoundingClientRect().width ?? 0
-
-    let minLeft = leftMeta.min
-    let maxLeft = leftMeta.max
-    let minRight = rightMeta.min
-    let maxRight = rightMeta.max
-
-    if (containerWidth && leftMeta.minPx !== undefined) minLeft = pxToPercent(leftMeta.minPx, containerWidth)
-    if (containerWidth && leftMeta.maxPx !== undefined) maxLeft = pxToPercent(leftMeta.maxPx, containerWidth)
-    if (containerWidth && rightMeta.minPx !== undefined) minRight = pxToPercent(rightMeta.minPx, containerWidth)
-    if (containerWidth && rightMeta.maxPx !== undefined) maxRight = pxToPercent(rightMeta.maxPx, containerWidth)
-
-    const finalMinLeft = Math.max(minLeft, total - maxRight)
-    const finalMaxLeft = Math.min(maxLeft, total - minRight)
-    return {
-      min: Math.min(finalMinLeft, finalMaxLeft),
-      max: Math.max(finalMinLeft, finalMaxLeft),
-    }
-  }
-
-  const setPair = (left: string, right: string, leftSize: number, rightSize: number) => {
-    batch(() => {
-      local.layout.setSize(props.id, left, leftSize)
-      local.layout.setSize(props.id, right, rightSize)
-    })
-  }
-
-  const startDrag = (left: string, right: string | undefined, event: MouseEvent) => {
-    if (!right) return
-    if (!container) return
-    const rect = container.getBoundingClientRect()
-    if (!rect.width) return
-    event.preventDefault()
-    const startX = event.clientX
-    const startLeft = local.layout.size(props.id, left)
-    const startRight = local.layout.size(props.id, right)
-    const total = startLeft + startRight
-    const bounds = boundsForPair(left, right, total)
-    const move = (moveEvent: MouseEvent) => {
-      const delta = ((moveEvent.clientX - startX) / rect.width) * 100
-      const nextLeft = Math.max(bounds.min, Math.min(bounds.max, startLeft + delta))
-      const nextRight = total - nextLeft
-      setPair(left, right, nextLeft, nextRight)
-    }
-    const stop = () => {
-      setDragging()
-      document.removeEventListener("mousemove", move)
-      document.removeEventListener("mouseup", stop)
-    }
-    setDragging(left)
-    document.addEventListener("mousemove", move)
-    document.addEventListener("mouseup", stop)
-    onCleanup(() => stop())
-  }
-
-  const register = (pane: string, options: { min?: number | string; max?: number | string }) => {
-    let min = 5
-    let max = 95
-    let minPx: number | undefined
-    let maxPx: number | undefined
-
-    if (typeof options.min === "string" && options.min.endsWith("px")) {
-      minPx = parseInt(options.min)
-      min = 0
-    } else if (typeof options.min === "number") {
-      min = options.min
-    }
-
-    if (typeof options.max === "string" && options.max.endsWith("px")) {
-      maxPx = parseInt(options.max)
-      max = 100
-    } else if (typeof options.max === "number") {
-      max = options.max
-    }
-
-    setMeta(pane, () => ({ min, max, minPx, maxPx }))
-    const fallback = props.defaults[pane]
-    local.layout.ensurePane(props.id, pane, fallback ?? { size: min, visible: true })
-  }
-
-  const contextValue: LayoutContextValue = {
-    id: props.id,
-    register,
-    size: (pane) => local.layout.size(props.id, pane),
-    visible: (pane) => local.layout.visible(props.id, pane),
-    percent,
-    next: nextPane,
-    startDrag,
-    dragging,
-  }
-
-  return (
-    <LayoutContext.Provider value={contextValue}>
-      <div
-        ref={(node) => {
-          container = node ?? undefined
-        }}
-        class={props.class ? `relative flex h-full w-full ${props.class}` : "relative flex h-full w-full"}
-        classList={props.classList}
-      >
-        {props.children}
-      </div>
-    </LayoutContext.Provider>
-  )
-}
-
-export function ResizeablePane(props: ResizeablePaneProps) {
-  const context = useContext(LayoutContext)!
-  context.register(props.id, { min: props.minSize, max: props.maxSize })
-  const visible = () => context.visible(props.id)
-  const width = () => context.percent(props.id)
-  const next = () => context.next(props.id)
-  const dragging = () => context.dragging() === props.id
-
-  return (
-    <Show when={visible()}>
-      <div
-        class={props.class ? `relative flex h-full flex-col ${props.class}` : "relative flex h-full flex-col"}
-        classList={props.classList}
-        style={{
-          width: `${width()}%`,
-          flex: `0 0 ${width()}%`,
-        }}
-      >
-        {props.children}
-        <Show when={next()}>
-          <div
-            class="absolute top-0 -right-1 h-full w-1.5 cursor-col-resize z-50 group"
-            onMouseDown={(event) => context.startDrag(props.id, next(), event)}
-          >
-            <div
-              classList={{
-                "w-0.5 h-full bg-transparent transition-colors group-hover:bg-border-active": true,
-                "bg-border-active!": dragging(),
-              }}
-            />
-          </div>
-        </Show>
-      </div>
-    </Show>
-  )
-}

+ 2 - 1
packages/desktop/src/components/select-dialog.tsx

@@ -1,6 +1,7 @@
 import { createEffect, Show, For, createMemo, type JSX, createResource } from "solid-js"
 import { createEffect, Show, For, createMemo, type JSX, createResource } from "solid-js"
 import { Dialog } from "@kobalte/core/dialog"
 import { Dialog } from "@kobalte/core/dialog"
-import { Icon, IconButton } from "@/ui"
+import { Icon } from "@opencode-ai/ui"
+import { IconButton } from "@/ui"
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
 import { entries, flatMap, groupBy, map, pipe } from "remeda"
 import { entries, flatMap, groupBy, map, pipe } from "remeda"
 import { createList } from "solid-list"
 import { createList } from "solid-list"

+ 0 - 108
packages/desktop/src/components/select.tsx

@@ -1,108 +0,0 @@
-import { Select as KobalteSelect } from "@kobalte/core/select"
-import { createMemo } from "solid-js"
-import type { ComponentProps } from "solid-js"
-import { Icon } from "@/ui/icon"
-import { pipe, groupBy, entries, map } from "remeda"
-import { Button, type ButtonProps } from "@/ui"
-
-export interface SelectProps<T> {
-  placeholder?: string
-  options: T[]
-  current?: T
-  value?: (x: T) => string
-  label?: (x: T) => string
-  groupBy?: (x: T) => string
-  onSelect?: (value: T | undefined) => void
-  class?: ComponentProps<"div">["class"]
-  classList?: ComponentProps<"div">["classList"]
-}
-
-export function Select<T>(props: SelectProps<T> & ButtonProps) {
-  const grouped = createMemo(() => {
-    const result = pipe(
-      props.options,
-      groupBy((x) => (props.groupBy ? props.groupBy(x) : "")),
-      // mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))),
-      entries(),
-      map(([k, v]) => ({ category: k, options: v })),
-    )
-    return result
-  })
-
-  return (
-    <KobalteSelect<T, { category: string; options: T[] }>
-      value={props.current}
-      options={grouped()}
-      optionValue={(x) => (props.value ? props.value(x) : (x as string))}
-      optionTextValue={(x) => (props.label ? props.label(x) : (x as string))}
-      optionGroupChildren="options"
-      placeholder={props.placeholder}
-      sectionComponent={(props) => (
-        <KobalteSelect.Section class="text-xs uppercase text-text-muted/60 font-light mt-3 first:mt-0 ml-2">
-          {props.section.rawValue.category}
-        </KobalteSelect.Section>
-      )}
-      itemComponent={(itemProps) => (
-        <KobalteSelect.Item
-          classList={{
-            "relative flex cursor-pointer select-none items-center": true,
-            "rounded-sm px-2 py-0.5 text-xs outline-none text-text": true,
-            "transition-colors data-[disabled]:pointer-events-none": true,
-            "data-[highlighted]:bg-background-element data-[disabled]:opacity-50": true,
-            [props.class ?? ""]: !!props.class,
-          }}
-          {...itemProps}
-        >
-          <KobalteSelect.ItemLabel>
-            {props.label ? props.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)}
-          </KobalteSelect.ItemLabel>
-          <KobalteSelect.ItemIndicator class="ml-auto">
-            <Icon name="checkmark" size={16} />
-          </KobalteSelect.ItemIndicator>
-        </KobalteSelect.Item>
-      )}
-      onChange={(v) => {
-        props.onSelect?.(v ?? undefined)
-      }}
-    >
-      <KobalteSelect.Trigger
-        as={Button}
-        size={props.size || "sm"}
-        variant={props.variant || "secondary"}
-        classList={{
-          ...(props.classList ?? {}),
-          [props.class ?? ""]: !!props.class,
-        }}
-      >
-        <KobalteSelect.Value<T> class="truncate">
-          {(state) => {
-            const selected = state.selectedOption() ?? props.current
-            if (!selected) return props.placeholder || ""
-            if (props.label) return props.label(selected)
-            return selected as string
-          }}
-        </KobalteSelect.Value>
-        <KobalteSelect.Icon
-          classList={{
-            "group size-fit shrink-0 text-text-muted transition-transform duration-100": true,
-          }}
-        >
-          <Icon name="chevron-up" size={16} class="-my-2 group-data-[expanded]:rotate-180" />
-          <Icon name="chevron-down" size={16} class="-my-2 group-data-[expanded]:rotate-180" />
-        </KobalteSelect.Icon>
-      </KobalteSelect.Trigger>
-      <KobalteSelect.Portal>
-        <KobalteSelect.Content
-          classList={{
-            "min-w-32 overflow-hidden rounded-md border border-border-subtle/40": true,
-            "bg-background-panel p-1 shadow-md z-50": true,
-            "data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95": true,
-            "data-[expanded]:animate-in data-[expanded]:fade-in-0 data-[expanded]:zoom-in-95": true,
-          }}
-        >
-          <KobalteSelect.Listbox class="overflow-y-auto max-h-48 whitespace-nowrap overflow-x-hidden" />
-        </KobalteSelect.Content>
-      </KobalteSelect.Portal>
-    </KobalteSelect>
-  )
-}

+ 1 - 1
packages/desktop/src/components/session-list.tsx

@@ -1,5 +1,5 @@
 import { useSync, useLocal } from "@/context"
 import { useSync, useLocal } from "@/context"
-import { Tooltip } from "@/ui"
+import { Tooltip } from "@opencode-ai/ui"
 import { DateTime } from "luxon"
 import { DateTime } from "luxon"
 import { VList } from "virtua/solid"
 import { VList } from "virtua/solid"
 
 

+ 2 - 1
packages/desktop/src/components/session-timeline.tsx

@@ -1,5 +1,6 @@
 import { useLocal, useSync } from "@/context"
 import { useLocal, useSync } from "@/context"
-import { Collapsible, Icon } from "@/ui"
+import { Icon } from "@opencode-ai/ui"
+import { Collapsible } from "@/ui"
 import type { Part, ToolPart } from "@opencode-ai/sdk"
 import type { Part, ToolPart } from "@opencode-ai/sdk"
 import { DateTime } from "luxon"
 import { DateTime } from "luxon"
 import {
 import {

+ 0 - 48
packages/desktop/src/components/sidebar-nav.tsx

@@ -1,48 +0,0 @@
-import { For } from "solid-js"
-import { Icon, Link, Logo, Tooltip } from "@/ui"
-import { useLocation } from "@solidjs/router"
-
-const navigation = [
-  { name: "Sessions", href: "/sessions", icon: "dashboard" as const },
-  { name: "Commands", href: "/commands", icon: "slash" as const },
-  { name: "Agents", href: "/agents", icon: "bolt" as const },
-  { name: "Providers", href: "/providers", icon: "cloud" as const },
-  { name: "Tools (MCP)", href: "/tools", icon: "hammer" as const },
-  { name: "LSP", href: "/lsp", icon: "code" as const },
-  { name: "Settings", href: "/settings", icon: "settings" as const },
-]
-
-export default function SidebarNav() {
-  const location = useLocation()
-  return (
-    <div class="hidden md:fixed md:inset-y-0 md:left-0 md:z-50 md:block md:w-16 md:overflow-y-auto md:bg-background-panel md:pb-4">
-      <div class="flex h-16 shrink-0 items-center justify-center">
-        <Logo variant="mark" size={28} />
-      </div>
-      <nav class="mt-5">
-        <ul role="list" class="flex flex-col items-center space-y-1">
-          <For each={navigation}>
-            {(item) => (
-              <li>
-                <Tooltip placement="right" value={item.name}>
-                  <Link
-                    href={item.href}
-                    classList={{
-                      "bg-background-element text-text": location.pathname.startsWith(item.href),
-                      "text-text-muted hover:bg-background-element hover:text-text": location.pathname !== item.href,
-                      "flex gap-x-3 rounded-md p-3 text-sm font-semibold": true,
-                      "focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-border-active": true,
-                    }}
-                  >
-                    <Icon name={item.icon} size={20} />
-                    <span class="sr-only">{item.name}</span>
-                  </Link>
-                </Tooltip>
-              </li>
-            )}
-          </For>
-        </ul>
-      </nav>
-    </div>
-  )
-}

+ 0 - 1
packages/desktop/src/context/index.ts

@@ -4,4 +4,3 @@ export { MarkedProvider, useMarked } from "./marked"
 export { SDKProvider, useSDK } from "./sdk"
 export { SDKProvider, useSDK } from "./sdk"
 export { ShikiProvider, useShiki } from "./shiki"
 export { ShikiProvider, useShiki } from "./shiki"
 export { SyncProvider, useSync } from "./sync"
 export { SyncProvider, useSync } from "./sync"
-export { ThemeProvider, useTheme } from "./theme"

+ 25 - 25
packages/desktop/src/context/local.tsx

@@ -131,31 +131,31 @@ function init() {
     const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path)))
     const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path)))
     const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b)))
     const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b)))
 
 
-    createEffect((prev: FileStatus[]) => {
-      const removed = prev.filter((p) => !sync.data.changes.find((c) => c.path === p.path))
-      for (const p of removed) {
-        setStore(
-          "node",
-          p.path,
-          produce((draft) => {
-            draft.status = undefined
-            draft.view = "raw"
-          }),
-        )
-        load(p.path)
-      }
-      for (const p of sync.data.changes) {
-        if (store.node[p.path] === undefined) {
-          fetch(p.path).then(() => {
-            if (store.node[p.path] === undefined) return
-            setStore("node", p.path, "status", p)
-          })
-        } else {
-          setStore("node", p.path, "status", p)
-        }
-      }
-      return sync.data.changes
-    }, sync.data.changes)
+    // createEffect((prev: FileStatus[]) => {
+    //   const removed = prev.filter((p) => !sync.data.changes.find((c) => c.path === p.path))
+    //   for (const p of removed) {
+    //     setStore(
+    //       "node",
+    //       p.path,
+    //       produce((draft) => {
+    //         draft.status = undefined
+    //         draft.view = "raw"
+    //       }),
+    //     )
+    //     load(p.path)
+    //   }
+    //   for (const p of sync.data.changes) {
+    //     if (store.node[p.path] === undefined) {
+    //       fetch(p.path).then(() => {
+    //         if (store.node[p.path] === undefined) return
+    //         setStore("node", p.path, "status", p)
+    //       })
+    //     } else {
+    //       setStore("node", p.path, "status", p)
+    //     }
+    //   }
+    //   return sync.data.changes
+    // }, sync.data.changes)
 
 
     const changed = (path: string) => {
     const changed = (path: string) => {
       const node = store.node[path]
       const node = store.node[path]

+ 0 - 92
packages/desktop/src/context/theme.tsx

@@ -1,92 +0,0 @@
-import {
-  createContext,
-  useContext,
-  createSignal,
-  createEffect,
-  onMount,
-  type ParentComponent,
-  onCleanup,
-} from "solid-js"
-
-export interface ThemeContextValue {
-  theme: string | undefined
-  isDark: boolean
-  setTheme: (themeName: string) => void
-  setDarkMode: (isDark: boolean) => void
-}
-
-const ThemeContext = createContext<ThemeContextValue>()
-
-export const useTheme = () => {
-  const context = useContext(ThemeContext)
-  if (!context) {
-    throw new Error("useTheme must be used within a ThemeProvider")
-  }
-  return context
-}
-
-interface ThemeProviderProps {
-  defaultTheme?: string
-  defaultDarkMode?: boolean
-}
-
-const themes = ["opencode", "tokyonight", "ayu", "nord", "catppuccin"]
-
-export const ThemeProvider: ParentComponent<ThemeProviderProps> = (props) => {
-  const [theme, setThemeSignal] = createSignal<string | undefined>()
-  const [isDark, setIsDark] = createSignal(props.defaultDarkMode ?? false)
-
-  const handleKeyDown = (event: KeyboardEvent) => {
-    if (event.key === "t" && event.ctrlKey) {
-      event.preventDefault()
-      const current = theme()
-      if (!current) return
-      const index = themes.indexOf(current)
-      const next = themes[(index + 1) % themes.length]
-      setTheme(next)
-    }
-  }
-
-  onMount(() => {
-    window.addEventListener("keydown", handleKeyDown)
-  })
-
-  onCleanup(() => {
-    window.removeEventListener("keydown", handleKeyDown)
-  })
-
-  onMount(() => {
-    const savedTheme = localStorage.getItem("theme") ?? "opencode"
-    const savedDarkMode = localStorage.getItem("darkMode") ?? "true"
-    setIsDark(savedDarkMode === "true")
-    setTheme(savedTheme)
-  })
-
-  createEffect(() => {
-    const currentTheme = theme()
-    const darkMode = isDark()
-    if (currentTheme) {
-      document.documentElement.setAttribute("data-theme", currentTheme)
-      document.documentElement.setAttribute("data-dark", darkMode.toString())
-    }
-  })
-
-  const setTheme = async (theme: string) => {
-    setThemeSignal(theme)
-    localStorage.setItem("theme", theme)
-  }
-
-  const setDarkMode = (dark: boolean) => {
-    setIsDark(dark)
-    localStorage.setItem("darkMode", dark.toString())
-  }
-
-  const contextValue: ThemeContextValue = {
-    theme: theme(),
-    isDark: isDark(),
-    setTheme,
-    setDarkMode,
-  }
-
-  return <ThemeContext.Provider value={contextValue}>{props.children}</ThemeContext.Provider>
-}

+ 1 - 168
packages/desktop/src/index.css

@@ -1,168 +1 @@
-@import "tailwindcss";
-
-:root {
-  interpolate-size: allow-keywords;
-}
-
-@layer components {
-  [data-popper-positioner] {
-    pointer-events: none;
-  }
-
-  body {
-    line-height: 1;
-  }
-
-  ::selection {
-    background-color: color-mix(in srgb, var(--color-primary) 33%, transparent);
-    /* background-color: var(--color-primary); */
-    /* color: var(--color-background); */
-  }
-
-  ::-webkit-scrollbar-track {
-    background: var(--theme-background-panel);
-  }
-
-  ::-webkit-scrollbar-thumb {
-    background-color: var(--theme-border-subtle);
-    border-radius: 6px;
-  }
-
-  * {
-    scrollbar-color: var(--theme-border-subtle) var(--theme-background-panel);
-  }
-
-  .prose h1 {
-    color: var(--color-text);
-    font-size: var(--text-sm);
-    line-height: var(--text-sm--line-height);
-    margin-bottom: calc(var(--spacing) * 3);
-  }
-  .prose h2 {
-    color: var(--color-text);
-    font-size: var(--text-sm);
-    line-height: var(--text-sm--line-height);
-    margin-bottom: calc(var(--spacing) * 3);
-  }
-  .prose h3 {
-    color: var(--color-text);
-    font-size: var(--text-xs);
-    line-height: var(--text-xs--line-height);
-    margin-bottom: calc(var(--spacing) * 2);
-  }
-  .prose h4 {
-    color: var(--color-text);
-    font-size: var(--text-xs);
-    line-height: var(--text-xs--line-height);
-    margin-bottom: calc(var(--spacing) * 2);
-  }
-  .prose h5 {
-    color: var(--color-text);
-    font-size: var(--text-xs);
-    line-height: var(--text-xs--line-height);
-    margin-bottom: calc(var(--spacing) * 2);
-  }
-  .prose h6 {
-    color: var(--color-text);
-    font-size: var(--text-xs);
-    line-height: var(--text-xs--line-height);
-    margin-bottom: calc(var(--spacing) * 2);
-  }
-  .prose p {
-    font-size: var(--text-xs);
-    line-height: var(--text-xs--line-height);
-    margin-bottom: calc(var(--spacing) * 2);
-  }
-  .prose strong {
-    color: var(--color-text);
-  }
-  .prose ul,
-  ol {
-    list-style-type: disc;
-    list-style-position: inside;
-    margin-bottom: calc(var(--spacing) * 2);
-  }
-  .prose pre {
-    background-color: var(--color-background-panel);
-    padding: calc(var(--spacing) * 2);
-    border-radius: var(--radius-md);
-    border: 1px solid var(--color-border-subtle);
-    overflow-x: auto;
-    white-space: pre;
-    margin-bottom: calc(var(--spacing) * 2);
-    @apply no-scrollbar;
-  }
-  .prose code {
-    font-family: var(--font-mono);
-    font-size: var(--text-xs);
-    line-height: var(--text-xs--line-height);
-  }
-  .prose blockquote {
-    margin-bottom: calc(var(--spacing) * 2);
-  }
-}
-
-@utility no-scrollbar {
-  &::-webkit-scrollbar {
-    display: none;
-  }
-  /* Hide scrollbar for IE, Edge and Firefox */
-  & {
-    -ms-overflow-style: none; /* IE and Edge */
-    scrollbar-width: none; /* Firefox */
-  }
-}
-
-@theme {
-  --color-*: initial;
-  --color-primary: var(--theme-primary);
-  --color-secondary: var(--theme-secondary);
-  --color-accent: var(--theme-accent);
-  --color-error: var(--theme-error);
-  --color-warning: var(--theme-warning);
-  --color-success: var(--theme-success);
-  --color-info: var(--theme-info);
-  --color-text: var(--theme-text);
-  --color-text-muted: var(--theme-text-muted);
-  --color-background: var(--theme-background);
-  --color-background-panel: var(--theme-background-panel);
-  --color-background-element: var(--theme-background-element);
-  --color-border: var(--theme-border);
-  --color-border-active: var(--theme-border-active);
-  --color-border-subtle: var(--theme-border-subtle);
-  --color-diff-added: var(--theme-diff-added);
-  --color-diff-removed: var(--theme-diff-removed);
-  --color-diff-context: var(--theme-diff-context);
-  --color-diff-hunk-header: var(--theme-diff-hunk-header);
-  --color-diff-highlight-added: var(--theme-diff-highlight-added);
-  --color-diff-highlight-removed: var(--theme-diff-highlight-removed);
-  --color-diff-added-bg: var(--theme-diff-added-bg);
-  --color-diff-removed-bg: var(--theme-diff-removed-bg);
-  --color-diff-context-bg: var(--theme-diff-context-bg);
-  --color-diff-line-number: var(--theme-diff-line-number);
-  --color-diff-added-line-number-bg: var(--theme-diff-added-line-number-bg);
-  --color-diff-removed-line-number-bg: var(--theme-diff-removed-line-number-bg);
-  --color-markdown-text: var(--theme-markdown-text);
-  --color-markdown-heading: var(--theme-markdown-heading);
-  --color-markdown-link: var(--theme-markdown-link);
-  --color-markdown-link-text: var(--theme-markdown-link-text);
-  --color-markdown-code: var(--theme-markdown-code);
-  --color-markdown-block-quote: var(--theme-markdown-block-quote);
-  --color-markdown-emph: var(--theme-markdown-emph);
-  --color-markdown-strong: var(--theme-markdown-strong);
-  --color-markdown-horizontal-rule: var(--theme-markdown-horizontal-rule);
-  --color-markdown-list-item: var(--theme-markdown-list-item);
-  --color-markdown-list-enumeration: var(--theme-markdown-list-enumeration);
-  --color-markdown-image: var(--theme-markdown-image);
-  --color-markdown-image-text: var(--theme-markdown-image-text);
-  --color-markdown-code-block: var(--theme-markdown-code-block);
-  --color-syntax-comment: var(--theme-syntax-comment);
-  --color-syntax-keyword: var(--theme-syntax-keyword);
-  --color-syntax-function: var(--theme-syntax-function);
-  --color-syntax-variable: var(--theme-syntax-variable);
-  --color-syntax-string: var(--theme-syntax-string);
-  --color-syntax-number: var(--theme-syntax-number);
-  --color-syntax-type: var(--theme-syntax-type);
-  --color-syntax-operator: var(--theme-syntax-operator);
-  --color-syntax-punctuation: var(--theme-syntax-punctuation);
-}
+@import "@opencode-ai/css/tailwind.css";

+ 22 - 31
packages/desktop/src/index.tsx

@@ -1,21 +1,13 @@
 /* @refresh reload */
 /* @refresh reload */
+import "@/index.css"
 import { render } from "solid-js/web"
 import { render } from "solid-js/web"
 import { Router, Route } from "@solidjs/router"
 import { Router, Route } from "@solidjs/router"
-import "@/index.css"
-import Layout from "@/pages/layout"
+import { MetaProvider } from "@solidjs/meta"
+import { EventProvider, SDKProvider, SyncProvider, LocalProvider, ShikiProvider, MarkedProvider } from "@/context"
+import { Fonts } from "@opencode-ai/ui"
 import Home from "@/pages"
 import Home from "@/pages"
-import {
-  EventProvider,
-  SDKProvider,
-  SyncProvider,
-  LocalProvider,
-  ThemeProvider,
-  ShikiProvider,
-  MarkedProvider,
-} from "@/context"
 
 
 const root = document.getElementById("root")
 const root = document.getElementById("root")
-
 if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
 if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
   throw new Error(
   throw new Error(
     "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
     "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
@@ -24,25 +16,24 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
 
 
 render(
 render(
   () => (
   () => (
-    <div class="h-full bg-background text-text-muted">
-      <ThemeProvider defaultTheme="opencode" defaultDarkMode={true}>
-        <ShikiProvider>
-          <MarkedProvider>
-            <SDKProvider>
-              <EventProvider>
-                <SyncProvider>
-                  <LocalProvider>
-                    <Router root={Layout}>
-                      <Route path="/" component={Home} />
-                    </Router>
-                  </LocalProvider>
-                </SyncProvider>
-              </EventProvider>
-            </SDKProvider>
-          </MarkedProvider>
-        </ShikiProvider>
-      </ThemeProvider>
-    </div>
+    <ShikiProvider>
+      <MarkedProvider>
+        <SDKProvider>
+          <EventProvider>
+            <SyncProvider>
+              <LocalProvider>
+                <MetaProvider>
+                  <Fonts />
+                  <Router>
+                    <Route path="/" component={Home} />
+                  </Router>
+                </MetaProvider>
+              </LocalProvider>
+            </SyncProvider>
+          </EventProvider>
+        </SDKProvider>
+      </MarkedProvider>
+    </ShikiProvider>
   ),
   ),
   root!,
   root!,
 )
 )

+ 56 - 54
packages/desktop/src/pages/index.tsx

@@ -1,5 +1,5 @@
-import { FileIcon, Icon, IconButton, Tooltip } from "@/ui"
-import * as KobalteTabs from "@kobalte/core/tabs"
+import { Icon, Tooltip } from "@opencode-ai/ui"
+import { FileIcon, IconButton } from "@/ui"
 import FileTree from "@/components/file-tree"
 import FileTree from "@/components/file-tree"
 import EditorPane from "@/components/editor-pane"
 import EditorPane from "@/components/editor-pane"
 import { For, Match, onCleanup, onMount, Show, Switch } from "solid-js"
 import { For, Match, onCleanup, onMount, Show, Switch } from "solid-js"
@@ -11,9 +11,7 @@ import SessionTimeline from "@/components/session-timeline"
 import PromptForm, { type PromptContentPart, type PromptSubmitValue } from "@/components/prompt-form"
 import PromptForm, { type PromptContentPart, type PromptSubmitValue } from "@/components/prompt-form"
 import { createStore } from "solid-js/store"
 import { createStore } from "solid-js/store"
 import { getDirectory, getFilename } from "@/utils"
 import { getDirectory, getFilename } from "@/utils"
-import { Select } from "@/components/select"
-import { Tabs } from "@/ui/tabs"
-import { Code } from "@/components/code"
+import { PromptInput } from "@/components/prompt-input"
 
 
 export default function Page() {
 export default function Page() {
   const local = useLocal()
   const local = useLocal()
@@ -52,7 +50,7 @@ export default function Page() {
     const focused = document.activeElement === inputRef
     const focused = document.activeElement === inputRef
     if (focused) {
     if (focused) {
       if (event.key === "Escape") {
       if (event.key === "Escape") {
-        inputRef?.blur()
+        // inputRef?.blur()
       }
       }
       return
       return
     }
     }
@@ -79,7 +77,7 @@ export default function Page() {
     }
     }
 
 
     if (event.key.length === 1 && event.key !== "Unidentified") {
     if (event.key.length === 1 && event.key !== "Unidentified") {
-      inputRef?.focus()
+      // inputRef?.focus()
     }
     }
   }
   }
 
 
@@ -106,6 +104,8 @@ export default function Page() {
     }
     }
   }
   }
 
 
+  const handlePromptSubmit2 = () => {}
+
   const handlePromptSubmit = async (prompt: PromptSubmitValue) => {
   const handlePromptSubmit = async (prompt: PromptSubmitValue) => {
     const existingSession = local.session.active()
     const existingSession = local.session.active()
     let session = existingSession
     let session = existingSession
@@ -231,51 +231,54 @@ export default function Page() {
         <div class="shrink-0 w-70">
         <div class="shrink-0 w-70">
           <SessionList />
           <SessionList />
         </div>
         </div>
-        <div class="grow w-full min-w-0 overflow-y-auto flex justify-center">
-          <Show when={local.session.active()}>
-            {(activeSession) => <SessionTimeline session={activeSession().id} class="max-w-xl" />}
-          </Show>
-        </div>
-        <div class="hidden shrink-0 w-56 p-2 h-full overflow-y-auto">
-          <FileTree path="" onFileClick={handleFileClick} />
-        </div>
-        <div class="hidden shrink-0 w-56 p-2">
-          <Show
-            when={local.file.changes().length}
-            fallback={<div class="px-2 text-xs text-text-muted">No changes</div>}
-          >
-            <ul class="">
-              <For each={local.file.changes()}>
-                {(path) => (
-                  <li>
-                    <button
-                      onClick={() => local.file.open(path, { view: "diff-unified", pinned: true })}
-                      class="w-full flex items-center px-2 py-0.5 gap-x-2 text-text-muted grow min-w-0 cursor-pointer hover:bg-background-element"
-                    >
-                      <FileIcon node={{ path, type: "file" }} class="shrink-0 size-3" />
-                      <span class="text-xs text-text whitespace-nowrap">{getFilename(path)}</span>
-                      <span class="text-xs text-text-muted/60 whitespace-nowrap truncate min-w-0">
-                        {getDirectory(path)}
-                      </span>
-                    </button>
-                  </li>
-                )}
-              </For>
-            </ul>
-          </Show>
-        </div>
-        <div class="hidden grow min-w-0">
-          <EditorPane onFileClick={handleFileClick} />
-        </div>
-        <div class="absolute bottom-4 inset-x-0 p-2 flex flex-col justify-center items-center gap-2 z-50">
-          <PromptForm
-            class="w-xl"
-            onSubmit={handlePromptSubmit}
-            onOpenModelSelect={() => setStore("modelSelectOpen", true)}
-            onInputRefChange={(element: HTMLTextAreaElement | undefined) => {
-              inputRef = element ?? undefined
-            }}
-          />
+        <div class="relative grid grid-cols-2">
+          <div class="min-w-0 overflow-y-auto no-scrollbar flex justify-center">
+            <Show when={local.session.active()}>
+              {(activeSession) => <SessionTimeline session={activeSession().id} class="w-full" />}
+            </Show>
+          </div>
+          <div class="p-1.5 pl-px flex flex-col items-center justify-center overflow-y-auto no-scrollbar">
+            <EditorPane onFileClick={handleFileClick} />
+          </div>
+          <div class="absolute bottom-4 inset-x-0 p-2 flex flex-col justify-center items-center z-50">
+            <PromptInput onSubmit={handlePromptSubmit2} />
+            {/* <PromptForm */}
+            {/*   class="w-2xl" */}
+            {/*   onSubmit={handlePromptSubmit} */}
+            {/*   onOpenModelSelect={() => setStore("modelSelectOpen", true)} */}
+            {/*   onInputRefChange={(element: HTMLTextAreaElement | undefined) => { */}
+            {/*     inputRef = element ?? undefined */}
+            {/*   }} */}
+            {/* /> */}
+          </div>
+          <div class="hidden shrink-0 w-56 p-2 h-full overflow-y-auto">
+            <FileTree path="" onFileClick={handleFileClick} />
+          </div>
+          <div class="hidden shrink-0 w-56 p-2">
+            <Show
+              when={local.file.changes().length}
+              fallback={<div class="px-2 text-xs text-text-muted">No changes</div>}
+            >
+              <ul class="">
+                <For each={local.file.changes()}>
+                  {(path) => (
+                    <li>
+                      <button
+                        onClick={() => local.file.open(path, { view: "diff-unified", pinned: true })}
+                        class="w-full flex items-center px-2 py-0.5 gap-x-2 text-text-muted grow min-w-0 cursor-pointer hover:bg-background-element"
+                      >
+                        <FileIcon node={{ path, type: "file" }} class="shrink-0 size-3" />
+                        <span class="text-xs text-text whitespace-nowrap">{getFilename(path)}</span>
+                        <span class="text-xs text-text-muted/60 whitespace-nowrap truncate min-w-0">
+                          {getDirectory(path)}
+                        </span>
+                      </button>
+                    </li>
+                  )}
+                </For>
+              </ul>
+            </Show>
+          </div>
         </div>
         </div>
       </main>
       </main>
       <Show when={store.modelSelectOpen}>
       <Show when={store.modelSelectOpen}>
@@ -343,8 +346,7 @@ export default function Page() {
             </div>
             </div>
           )}
           )}
           onClose={() => setStore("fileSelectOpen", false)}
           onClose={() => setStore("fileSelectOpen", false)}
-          onSelect={(x) => (x ? local.context.openFile(x) : undefined)}
-          // onSelect={(x) => (x ? local.file.open(x, { pinned: true }) : undefined)}
+          onSelect={(x) => (x ? local.file.open(x, { pinned: true }) : undefined)}
         />
         />
       </Show>
       </Show>
     </div>
     </div>

+ 0 - 5
packages/desktop/src/pages/layout.tsx

@@ -1,5 +0,0 @@
-import { type ParentProps } from "solid-js"
-
-export default function Layout(props: ParentProps) {
-  return <main class="">{props.children}</main>
-}

+ 0 - 36
packages/desktop/src/ui/button.tsx

@@ -1,36 +0,0 @@
-import { Button as Kobalte } from "@kobalte/core/button"
-import { type ComponentProps, splitProps } from "solid-js"
-
-export interface ButtonProps {
-  variant?: "primary" | "secondary" | "ghost"
-  size?: "sm" | "md" | "lg"
-}
-
-export function Button(props: ComponentProps<"button"> & ButtonProps) {
-  const [split, rest] = splitProps(props, ["variant", "size", "class", "classList"])
-  return (
-    <Kobalte
-      {...rest}
-      data-size={split.size || "sm"}
-      data-variant={split.variant || "secondary"}
-      class="inline-flex items-center justify-center rounded-md cursor-pointer font-medium transition-colors
-             min-w-0 whitespace-nowrap truncate
-             data-[size=sm]:h-6 data-[size=sm]:pl-2 data-[size=sm]:text-xs
-             data-[size=md]:h-8 data-[size=md]:pl-3 data-[size=md]:text-sm
-             data-[size=lg]:h-10 data-[size=lg]:pl-4 data-[size=lg]:text-base
-             data-[variant=primary]:bg-primary data-[variant=primary]:text-background 
-             data-[variant=primary]:hover:bg-secondary data-[variant=primary]:focus-visible:ring-primary
-             data-[variant=secondary]:bg-background-element data-[variant=secondary]:text-text
-             data-[variant=secondary]:hover:bg-background-element data-[variant=secondary]:focus-visible:ring-secondary
-             data-[variant=ghost]:text-text data-[variant=ghost]:hover:bg-background-panel data-[variant=ghost]:focus-visible:ring-border-active
-             focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-transparent 
-             disabled:pointer-events-none disabled:opacity-50"
-      classList={{
-        ...(split.classList ?? {}),
-        [split.class ?? ""]: !!split.class,
-      }}
-    >
-      {props.children}
-    </Kobalte>
-  )
-}

+ 1 - 1
packages/desktop/src/ui/collapsible.tsx

@@ -1,7 +1,7 @@
 import { Collapsible as KobalteCollapsible } from "@kobalte/core/collapsible"
 import { Collapsible as KobalteCollapsible } from "@kobalte/core/collapsible"
 import { splitProps } from "solid-js"
 import { splitProps } from "solid-js"
 import type { ComponentProps, ParentProps } from "solid-js"
 import type { ComponentProps, ParentProps } from "solid-js"
-import { Icon, type IconProps } from "./icon"
+import { Icon, type IconProps } from "@opencode-ai/ui"
 
 
 export interface CollapsibleProps extends ComponentProps<typeof KobalteCollapsible> {}
 export interface CollapsibleProps extends ComponentProps<typeof KobalteCollapsible> {}
 export interface CollapsibleTriggerProps extends ComponentProps<typeof KobalteCollapsible.Trigger> {}
 export interface CollapsibleTriggerProps extends ComponentProps<typeof KobalteCollapsible.Trigger> {}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 109
packages/desktop/src/ui/icon.tsx


+ 0 - 5
packages/desktop/src/ui/index.ts

@@ -1,4 +1,3 @@
-export { Button, type ButtonProps } from "./button"
 export {
 export {
   Collapsible,
   Collapsible,
   type CollapsibleProps,
   type CollapsibleProps,
@@ -6,8 +5,4 @@ export {
   type CollapsibleContentProps,
   type CollapsibleContentProps,
 } from "./collapsible"
 } from "./collapsible"
 export { FileIcon, type FileIconProps } from "./file-icon"
 export { FileIcon, type FileIconProps } from "./file-icon"
-export { Icon, type IconProps } from "./icon"
 export { IconButton, type IconButtonProps } from "./icon-button"
 export { IconButton, type IconButtonProps } from "./icon-button"
-export { Link, type LinkProps } from "./link"
-export { Logo, type LogoProps } from "./logo"
-export { Tooltip, type TooltipProps } from "./tooltip"

+ 0 - 13
packages/desktop/src/ui/link.tsx

@@ -1,13 +0,0 @@
-import { A } from "@solidjs/router"
-import { splitProps } from "solid-js"
-import type { ComponentProps } from "solid-js"
-
-export interface LinkProps extends ComponentProps<typeof A> {
-  variant?: "primary" | "secondary" | "ghost"
-  size?: "sm" | "md" | "lg"
-}
-
-export function Link(props: LinkProps) {
-  const [, others] = splitProps(props, ["variant", "size", "class"])
-  return <A {...others} />
-}

+ 0 - 125
packages/desktop/src/ui/logo.tsx

@@ -1,125 +0,0 @@
-import type { ComponentProps } from "solid-js"
-
-export interface LogoProps extends ComponentProps<"svg"> {
-  variant?: "mark" | "full" | "ornate"
-  size?: number
-}
-
-export function Logo(props: LogoProps) {
-  const { variant = "mark", size = 64, ...others } = props
-
-  if (variant === "mark") {
-    return (
-      <svg
-        width={size}
-        height={size * (42 / 64)}
-        viewBox="0 0 64 42"
-        fill="none"
-        xmlns="http://www.w3.org/2000/svg"
-        class={`text-text ${props.class ?? ""}`}
-        {...others}
-      >
-        <path
-          fill-rule="evenodd"
-          clip-rule="evenodd"
-          d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z"
-          fill="currentColor"
-        />
-        <path d="M40 0H64V8.5H48V33H64V41.5H40V0Z" fill="currentColor" />
-      </svg>
-    )
-  }
-
-  if (variant === "full") {
-    return (
-      <svg
-        width={size * (289 / 42)}
-        height={size}
-        viewBox="0 0 289 42"
-        fill="none"
-        xmlns="http://www.w3.org/2000/svg"
-        {...others}
-      >
-        <path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="currentColor" />
-        <path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="currentColor" />
-        <path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="currentColor" />
-        <path
-          fill-rule="evenodd"
-          clip-rule="evenodd"
-          d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z"
-          fill="currentColor"
-        />
-        <path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="currentColor" />
-        <path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="currentColor" />
-        <path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="currentColor" />
-        <path
-          fill-rule="evenodd"
-          clip-rule="evenodd"
-          d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z"
-          fill="currentColor"
-        />
-        <path
-          fill-rule="evenodd"
-          clip-rule="evenodd"
-          d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z"
-          fill="currentColor"
-        />
-        <path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="currentColor" />
-      </svg>
-    )
-  }
-
-  return (
-    <svg
-      width={size * (289 / 42)}
-      height={size}
-      viewBox="0 0 289 50"
-      fill="none"
-      xmlns="http://www.w3.org/2000/svg"
-      {...others}
-    >
-      <path d="M8.5 16.5H24.5V33H8.5V16.5Z" fill="currentColor" fill-opacity="0.2" />
-      <path d="M48.5 16.5H64.5V33H48.5V16.5Z" fill="currentColor" fill-opacity="0.2" />
-      <path d="M120.5 16.5H136.5V33H120.5V16.5Z" fill="currentColor" fill-opacity="0.2" />
-      <path d="M160.5 16.5H176.5V33H160.5V16.5Z" fill="currentColor" fill-opacity="0.2" />
-      <path d="M192.5 16.5H208.5V33H192.5V16.5Z" fill="currentColor" fill-opacity="0.2" />
-      <path d="M232.5 16.5H248.5V33H232.5V16.5Z" fill="currentColor" fill-opacity="0.2" />
-      <path
-        d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z"
-        fill="currentColor"
-        fill-opacity="0.95"
-      />
-      <path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="currentColor" fill-opacity="0.95" />
-      <path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="currentColor" fill-opacity="0.95" />
-      <path
-        fill-rule="evenodd"
-        clip-rule="evenodd"
-        d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z"
-        fill="currentColor"
-        fill-opacity="0.95"
-      />
-      <path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="currentColor" fill-opacity="0.5" />
-      <path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="currentColor" fill-opacity="0.5" />
-      <path
-        d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z"
-        fill="currentColor"
-        fill-opacity="0.5"
-      />
-      <path
-        fill-rule="evenodd"
-        clip-rule="evenodd"
-        d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z"
-        fill="currentColor"
-        fill-opacity="0.5"
-      />
-      <path
-        fill-rule="evenodd"
-        clip-rule="evenodd"
-        d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z"
-        fill="currentColor"
-        fill-opacity="0.5"
-      />
-      <path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="currentColor" fill-opacity="0.95" />
-    </svg>
-  )
-}

+ 0 - 71
packages/desktop/src/ui/tabs.tsx

@@ -1,71 +0,0 @@
-import { Tabs as KobalteTabs } from "@kobalte/core/tabs"
-import { splitProps } from "solid-js"
-import type { ComponentProps, ParentProps } from "solid-js"
-
-export interface TabsProps extends ComponentProps<typeof KobalteTabs> {}
-export interface TabsListProps extends ComponentProps<typeof KobalteTabs.List> {}
-export interface TabsTriggerProps extends ComponentProps<typeof KobalteTabs.Trigger> {}
-export interface TabsContentProps extends ComponentProps<typeof KobalteTabs.Content> {}
-
-function TabsRoot(props: TabsProps) {
-  return <KobalteTabs {...props} />
-}
-
-function TabsList(props: TabsListProps) {
-  const [local, others] = splitProps(props, ["class"])
-  return (
-    <KobalteTabs.List
-      classList={{
-        "relative flex items-center bg-background overflow-x-auto no-scrollbar": true,
-        "divide-x divide-border-subtle/40": true,
-        "after:content-[''] after:block after:grow after:h-8": true,
-        "after:border-l empty:after:border-l-0! after:border-b after:border-border-subtle/40": true,
-        [local.class ?? ""]: !!local.class,
-      }}
-      {...others}
-    />
-  )
-}
-
-function TabsTrigger(props: ParentProps<TabsTriggerProps>) {
-  const [local, others] = splitProps(props, ["class", "children"])
-  return (
-    <KobalteTabs.Trigger
-      classList={{
-        "relative px-3 h-8 flex items-center": true,
-        "text-sm font-medium text-text-muted/60 cursor-pointer": true,
-        "whitespace-nowrap shrink-0 border-b border-border-subtle/40": true,
-        "disabled:pointer-events-none disabled:opacity-50": true,
-        "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring": true,
-        "data-[selected]:text-text data-[selected]:bg-background-panel": true,
-        "data-[selected]:!border-b-transparent": true,
-        [local.class ?? ""]: !!local.class,
-      }}
-      {...others}
-    >
-      {local.children}
-    </KobalteTabs.Trigger>
-  )
-}
-
-function TabsContent(props: ParentProps<TabsContentProps>) {
-  const [local, others] = splitProps(props, ["class", "children"])
-  return (
-    <KobalteTabs.Content
-      classList={{
-        "bg-background-panel overflow-y-auto h-full no-scrollbar": true,
-        "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring": true,
-        [local.class ?? ""]: !!local.class,
-      }}
-      {...others}
-    >
-      {local.children}
-    </KobalteTabs.Content>
-  )
-}
-
-export const Tabs = Object.assign(TabsRoot, {
-  List: TabsList,
-  Trigger: TabsTrigger,
-  Content: TabsContent,
-})

+ 0 - 2
packages/desktop/vite.config.ts

@@ -3,7 +3,6 @@ import solidPlugin from "vite-plugin-solid"
 import tailwindcss from "@tailwindcss/vite"
 import tailwindcss from "@tailwindcss/vite"
 import path from "path"
 import path from "path"
 import { iconsSpritesheet } from "vite-plugin-icons-spritesheet"
 import { iconsSpritesheet } from "vite-plugin-icons-spritesheet"
-import { generateThemeCSS } from "./scripts/vite-theme-plugin"
 
 
 export default defineConfig({
 export default defineConfig({
   resolve: {
   resolve: {
@@ -12,7 +11,6 @@ export default defineConfig({
     },
     },
   },
   },
   plugins: [
   plugins: [
-    generateThemeCSS(),
     tailwindcss(),
     tailwindcss(),
     solidPlugin(),
     solidPlugin(),
     iconsSpritesheet({
     iconsSpritesheet({

+ 2 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@opencode-ai/ui",
   "name": "@opencode-ai/ui",
-  "version": "0.15.5",
+  "version": "0.15.4",
   "type": "module",
   "type": "module",
   "exports": {
   "exports": {
     ".": "./src/components/index.ts",
     ".": "./src/components/index.ts",
@@ -17,6 +17,7 @@
   "dependencies": {
   "dependencies": {
     "remeda": "catalog:",
     "remeda": "catalog:",
     "solid-js": "catalog:",
     "solid-js": "catalog:",
+    "@solidjs/meta": "catalog:",
     "@kobalte/core": "catalog:",
     "@kobalte/core": "catalog:",
     "@opencode-ai/css": "workspace:*"
     "@opencode-ai/css": "workspace:*"
   }
   }

+ 32 - 6
packages/ui/src/app.tsx

@@ -1,6 +1,5 @@
 import type { Component } from "solid-js"
 import type { Component } from "solid-js"
-import { Button, Select, Tabs } from "./components"
-import "@opencode-ai/css"
+import { Button, Select, Tabs, Tooltip, Fonts } from "./components"
 import "./index.css"
 import "./index.css"
 
 
 const App: Component = () => {
 const App: Component = () => {
@@ -17,6 +16,9 @@ const App: Component = () => {
         <Button variant="ghost" size="normal">
         <Button variant="ghost" size="normal">
           Normal Ghost
           Normal Ghost
         </Button>
         </Button>
+        <Button variant="secondary" size="normal" disabled>
+          Normal Disabled
+        </Button>
         <Button variant="primary" size="large">
         <Button variant="primary" size="large">
           Large Primary
           Large Primary
         </Button>
         </Button>
@@ -26,6 +28,9 @@ const App: Component = () => {
         <Button variant="ghost" size="large">
         <Button variant="ghost" size="large">
           Large Ghost
           Large Ghost
         </Button>
         </Button>
+        <Button variant="secondary" size="large" disabled>
+          Large Disabled
+        </Button>
       </section>
       </section>
       <h3>Select</h3>
       <h3>Select</h3>
       <section>
       <section>
@@ -88,14 +93,35 @@ const App: Component = () => {
           </Tabs.Content>
           </Tabs.Content>
         </Tabs>
         </Tabs>
       </section>
       </section>
+      <h3>Tooltips</h3>
+      <section>
+        <Tooltip value="This is a top tooltip" placement="top">
+          <Button variant="secondary">Top Tooltip</Button>
+        </Tooltip>
+        <Tooltip value="This is a bottom tooltip" placement="bottom">
+          <Button variant="secondary">Bottom Tooltip</Button>
+        </Tooltip>
+        <Tooltip value="This is a left tooltip" placement="left">
+          <Button variant="secondary">Left Tooltip</Button>
+        </Tooltip>
+        <Tooltip value="This is a right tooltip" placement="right">
+          <Button variant="secondary">Right Tooltip</Button>
+        </Tooltip>
+        <Tooltip value={() => `Dynamic tooltip: ${new Date().toLocaleTimeString()}`} placement="top">
+          <Button variant="primary">Dynamic Tooltip</Button>
+        </Tooltip>
+      </section>
     </div>
     </div>
   )
   )
 
 
   return (
   return (
-    <main>
-      <Content />
-      <Content dark />
-    </main>
+    <>
+      <Fonts />
+      <main>
+        <Content />
+        <Content dark />
+      </main>
+    </>
   )
   )
 }
 }
 
 

+ 44 - 0
packages/ui/src/components/fonts.tsx

@@ -0,0 +1,44 @@
+import { Style, Link } from "@solidjs/meta"
+import geist from "@opencode-ai/css/fonts/geist.woff2"
+import geistMono from "@opencode-ai/css/fonts/geist-mono.woff2"
+
+export const Fonts = () => {
+  return (
+    <>
+      <Style>{`
+        @font-face {
+          font-family: "geist";
+          src: url("${geist}") format("woff2-variations");
+          font-display: swap;
+          font-style: normal;
+          font-weight: 100 900;
+        }
+        @font-face {
+          font-family: "geist-fallback";
+          src: local("Arial");
+          size-adjust: 100%;
+          ascent-override: 97%;
+          descent-override: 25%;
+          line-gap-override: 1%;
+        }
+        @font-face {
+          font-family: "geist-mono";
+          src: url("${geistMono}") format("woff2-variations");
+          font-display: swap;
+          font-style: normal;
+          font-weight: 100 900;
+        }
+        @font-face {
+          font-family: "geist-mono-fallback";
+          src: local("Courier New");
+          size-adjust: 100%;
+          ascent-override: 97%;
+          descent-override: 25%;
+          line-gap-override: 1%;
+        }
+      `}</Style>
+      <Link rel="preload" href={geist} as="font" type="font/woff2" crossorigin="anonymous" />
+      <Link rel="preload" href={geistMono} as="font" type="font/woff2" crossorigin="anonymous" />
+    </>
+  )
+}

+ 2 - 0
packages/ui/src/components/index.ts

@@ -1,4 +1,6 @@
 export * from "./button"
 export * from "./button"
 export * from "./icon"
 export * from "./icon"
+export * from "./fonts"
 export * from "./select"
 export * from "./select"
 export * from "./tabs"
 export * from "./tabs"
+export * from "./tooltip"

+ 4 - 18
packages/desktop/src/ui/tooltip.tsx → packages/ui/src/components/tooltip.tsx

@@ -9,7 +9,7 @@ export interface TooltipProps extends ComponentProps<typeof KobalteTooltip> {
 
 
 export function Tooltip(props: TooltipProps) {
 export function Tooltip(props: TooltipProps) {
   const [open, setOpen] = createSignal(false)
   const [open, setOpen] = createSignal(false)
-  const [local, others] = splitProps(props, ["class", "children"])
+  const [local, others] = splitProps(props, ["children", "class"])
 
 
   const c = children(() => local.children)
   const c = children(() => local.children)
 
 
@@ -30,27 +30,13 @@ export function Tooltip(props: TooltipProps) {
 
 
   return (
   return (
     <KobalteTooltip forceMount {...others} open={open()} onOpenChange={setOpen}>
     <KobalteTooltip forceMount {...others} open={open()} onOpenChange={setOpen}>
-      <KobalteTooltip.Trigger as={"div"} class="flex items-center">
+      <KobalteTooltip.Trigger as={"div"} data-component="tooltip-trigger">
         {c()}
         {c()}
       </KobalteTooltip.Trigger>
       </KobalteTooltip.Trigger>
       <KobalteTooltip.Portal>
       <KobalteTooltip.Portal>
-        <KobalteTooltip.Content
-          classList={{
-            "z-[1000] max-w-[320px] rounded-md bg-background-element px-2 py-1": true,
-            "text-xs font-medium text-text shadow-md pointer-events-none!": true,
-            "transition-all duration-150 ease-out": true,
-            "transform-gpu transform-origin-[var(--kb-tooltip-content-transform-origin)]": true,
-            "data-closed:opacity-0": true,
-            "data-expanded:opacity-100 data-expanded:translate-y-0 data-expanded:translate-x-0": true,
-            "data-closed:translate-y-1": props.placement === "top",
-            "data-closed:-translate-y-1": props.placement === "bottom",
-            "data-closed:translate-x-1": props.placement === "left",
-            "data-closed:-translate-x-1": props.placement === "right",
-            [local.class ?? ""]: !!local.class,
-          }}
-        >
+        <KobalteTooltip.Content data-component="tooltip" data-placement={props.placement} class={local.class}>
           {typeof others.value === "function" ? others.value() : others.value}
           {typeof others.value === "function" ? others.value() : others.value}
-          <KobalteTooltip.Arrow size={18} />
+          <KobalteTooltip.Arrow data-slot="arrow" size={18} />
         </KobalteTooltip.Content>
         </KobalteTooltip.Content>
       </KobalteTooltip.Portal>
       </KobalteTooltip.Portal>
     </KobalteTooltip>
     </KobalteTooltip>

+ 6 - 4
packages/ui/src/index.css

@@ -1,8 +1,10 @@
+@import "@opencode-ai/css";
+
 :root {
 :root {
   body {
   body {
     margin: 0;
     margin: 0;
-    background-color: var(--background-background);
-    color: var(--text-default-text);
+    background-color: var(--background-base);
+    color: var(--text-base);
   }
   }
   main {
   main {
     display: flex;
     display: flex;
@@ -35,6 +37,6 @@
 }
 }
 
 
 .dark {
 .dark {
-  background-color: var(--background-background);
-  color: var(--text-default-text);
+  background-color: var(--background-base);
+  color: var(--text-base);
 }
 }

+ 9 - 1
packages/ui/src/index.tsx

@@ -1,5 +1,6 @@
 /* @refresh reload */
 /* @refresh reload */
 import { render } from "solid-js/web"
 import { render } from "solid-js/web"
+import { MetaProvider } from "@solidjs/meta"
 
 
 import App from "./app"
 import App from "./app"
 
 
@@ -11,4 +12,11 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
   )
   )
 }
 }
 
 
-render(() => <App />, root!)
+render(
+  () => (
+    <MetaProvider>
+      <App />
+    </MetaProvider>
+  ),
+  root!,
+)

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor