Browse Source

Merge branch 'dev' of https://github.com/sst/opencode into dev

David Hill 5 months ago
parent
commit
9d53628e19

+ 1 - 0
STATS.md

@@ -87,3 +87,4 @@
 | 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
 | 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
 | 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331)  |
 | 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331)  |
 | 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161)  |
 | 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161)  |
+| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636)  |

+ 3 - 0
bun.lock

@@ -19,6 +19,7 @@
         "@kobalte/core": "0.13.11",
         "@kobalte/core": "0.13.11",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "@shikijs/transformers": "3.9.2",
         "@shikijs/transformers": "3.9.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/router": "0.15.3",
         "@solidjs/router": "0.15.3",
@@ -996,6 +997,8 @@
 
 
     "@smithy/util-utf8": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/util-buffer-from": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ=="],
     "@smithy/util-utf8": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/util-buffer-from": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ=="],
 
 
+    "@solid-primitives/event-bus": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-l+n10/51neGcMaP3ypYt21bXfoeWh8IaC8k7fYuY3ww2a8S1Zv2N2a7FF5Qn+waTu86l0V8/nRHjkyqVIZBYwA=="],
+
     "@solid-primitives/event-listener": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="],
     "@solid-primitives/event-listener": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="],
 
 
     "@solid-primitives/keyed": ["@solid-primitives/[email protected]", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-BgoEdqPw48URnI+L5sZIHdF4ua4Las1eWEBBPaoSFs42kkhnHue+rwCBPL2Z9ebOyQ75sUhUfOETdJfmv0D6Kg=="],
     "@solid-primitives/keyed": ["@solid-primitives/[email protected]", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-BgoEdqPw48URnI+L5sZIHdF4ua4Las1eWEBBPaoSFs42kkhnHue+rwCBPL2Z9ebOyQ75sUhUfOETdJfmv0D6Kg=="],

+ 1 - 0
packages/app/.gitignore

@@ -0,0 +1 @@
+src/assets/theme.css

+ 1 - 0
packages/app/package.json

@@ -24,6 +24,7 @@
     "@kobalte/core": "0.13.11",
     "@kobalte/core": "0.13.11",
     "@opencode-ai/sdk": "workspace:*",
     "@opencode-ai/sdk": "workspace:*",
     "@shikijs/transformers": "3.9.2",
     "@shikijs/transformers": "3.9.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/router": "0.15.3",
     "@solidjs/router": "0.15.3",

+ 3 - 3
packages/app/scripts/vite-theme-plugin.ts

@@ -37,7 +37,7 @@ class ColorResolver {
       if (typeof value === "string") {
       if (typeof value === "string") {
         if (value === "none") return { dark: value, light: value }
         if (value === "none") return { dark: value, light: value }
         if (value.startsWith("#")) {
         if (value.startsWith("#")) {
-          return { dark: value.toUpperCase(), light: value.toUpperCase() }
+          return { dark: value.toLowerCase(), light: value.toLowerCase() }
         }
         }
         const resolved = this.resolveReference(value)
         const resolved = this.resolveReference(value)
         return { dark: resolved, light: resolved }
         return { dark: resolved, light: resolved }
@@ -57,7 +57,7 @@ class ColorResolver {
     if (typeof value === "string") {
     if (typeof value === "string") {
       if (value === "none") return value
       if (value === "none") return value
       if (value.startsWith("#")) {
       if (value.startsWith("#")) {
-        return value.toUpperCase()
+        return value.toLowerCase()
       }
       }
       return this.resolveReference(value)
       return this.resolveReference(value)
     }
     }
@@ -72,7 +72,7 @@ class ColorResolver {
     if (typeof colorValue === "string") {
     if (typeof colorValue === "string") {
       if (colorValue === "none") return colorValue
       if (colorValue === "none") return colorValue
       if (colorValue.startsWith("#")) {
       if (colorValue.startsWith("#")) {
-        return colorValue.toUpperCase()
+        return colorValue.toLowerCase()
       }
       }
       return this.resolveReference(colorValue)
       return this.resolveReference(colorValue)
     }
     }

+ 0 - 2385
packages/app/src/assets/theme.css

@@ -1,2385 +0,0 @@
-/* Auto-generated theme CSS - Do not edit manually */
-:root {
-  --theme-primary: #3b7dd8;
-  --theme-secondary: #7b5bb6;
-  --theme-accent: #d68c27;
-  --theme-error: #d1383d;
-  --theme-warning: #d68c27;
-  --theme-success: #3d9a57;
-  --theme-info: #318795;
-  --theme-text: #1a1a1a;
-  --theme-text-muted: #8a8a8a;
-  --theme-background: #ffffff;
-  --theme-background-panel: #fafafa;
-  --theme-background-element: #f5f5f5;
-  --theme-border: #b8b8b8;
-  --theme-border-active: #a0a0a0;
-  --theme-border-subtle: #d4d4d4;
-  --theme-diff-added: #1e725c;
-  --theme-diff-removed: #c53b53;
-  --theme-diff-context: #7086b5;
-  --theme-diff-hunk-header: #7086b5;
-  --theme-diff-highlight-added: #4db380;
-  --theme-diff-highlight-removed: #f52a65;
-  --theme-diff-added-bg: #d5e5d5;
-  --theme-diff-removed-bg: #f7d8db;
-  --theme-diff-context-bg: #fafafa;
-  --theme-diff-line-number: #f5f5f5;
-  --theme-diff-added-line-number-bg: #c5d5c5;
-  --theme-diff-removed-line-number-bg: #e7c8cb;
-  --theme-markdown-text: #1a1a1a;
-  --theme-markdown-heading: #d68c27;
-  --theme-markdown-link: #3b7dd8;
-  --theme-markdown-link-text: #318795;
-  --theme-markdown-code: #3d9a57;
-  --theme-markdown-block-quote: #b0851f;
-  --theme-markdown-emph: #b0851f;
-  --theme-markdown-strong: #d68c27;
-  --theme-markdown-horizontal-rule: #8a8a8a;
-  --theme-markdown-list-item: #3b7dd8;
-  --theme-markdown-list-enumeration: #318795;
-  --theme-markdown-image: #3b7dd8;
-  --theme-markdown-image-text: #318795;
-  --theme-markdown-code-block: #1a1a1a;
-  --theme-syntax-comment: #8a8a8a;
-  --theme-syntax-keyword: #d68c27;
-  --theme-syntax-function: #3b7dd8;
-  --theme-syntax-variable: #d1383d;
-  --theme-syntax-string: #3d9a57;
-  --theme-syntax-number: #d68c27;
-  --theme-syntax-type: #b0851f;
-  --theme-syntax-operator: #318795;
-  --theme-syntax-punctuation: #1a1a1a;
-}
-
-[data-theme="aura"][data-dark="false"] {
-  --theme-primary: #a277ff;
-  --theme-secondary: #f694ff;
-  --theme-accent: #a277ff;
-  --theme-error: #ff6767;
-  --theme-warning: #ffca85;
-  --theme-success: #61ffca;
-  --theme-info: #a277ff;
-  --theme-text: #edecee;
-  --theme-text-muted: #6d6d6d;
-  --theme-background: #0f0f0f;
-  --theme-background-panel: #15141b;
-  --theme-background-element: #15141b;
-  --theme-border: #2d2d2d;
-  --theme-border-active: #6d6d6d;
-  --theme-border-subtle: #2d2d2d;
-  --theme-diff-added: #61ffca;
-  --theme-diff-removed: #ff6767;
-  --theme-diff-context: #6d6d6d;
-  --theme-diff-hunk-header: #6d6d6d;
-  --theme-diff-highlight-added: #61ffca;
-  --theme-diff-highlight-removed: #ff6767;
-  --theme-diff-added-bg: #354933;
-  --theme-diff-removed-bg: #3f191a;
-  --theme-diff-context-bg: #15141b;
-  --theme-diff-line-number: #2d2d2d;
-  --theme-diff-added-line-number-bg: #162620;
-  --theme-diff-removed-line-number-bg: #26161a;
-  --theme-markdown-text: #edecee;
-  --theme-markdown-heading: #a277ff;
-  --theme-markdown-link: #f694ff;
-  --theme-markdown-link-text: #a277ff;
-  --theme-markdown-code: #61ffca;
-  --theme-markdown-block-quote: #6d6d6d;
-  --theme-markdown-emph: #ffca85;
-  --theme-markdown-strong: #a277ff;
-  --theme-markdown-horizontal-rule: #6d6d6d;
-  --theme-markdown-list-item: #a277ff;
-  --theme-markdown-list-enumeration: #a277ff;
-  --theme-markdown-image: #f694ff;
-  --theme-markdown-image-text: #a277ff;
-  --theme-markdown-code-block: #edecee;
-  --theme-syntax-comment: #6d6d6d;
-  --theme-syntax-keyword: #f694ff;
-  --theme-syntax-function: #a277ff;
-  --theme-syntax-variable: #a277ff;
-  --theme-syntax-string: #61ffca;
-  --theme-syntax-number: #9dff65;
-  --theme-syntax-type: #a277ff;
-  --theme-syntax-operator: #f694ff;
-  --theme-syntax-punctuation: #edecee;
-}
-
-[data-theme="aura"][data-dark="true"] {
-  --theme-primary: #a277ff;
-  --theme-secondary: #f694ff;
-  --theme-accent: #a277ff;
-  --theme-error: #ff6767;
-  --theme-warning: #ffca85;
-  --theme-success: #61ffca;
-  --theme-info: #a277ff;
-  --theme-text: #edecee;
-  --theme-text-muted: #6d6d6d;
-  --theme-background: #0f0f0f;
-  --theme-background-panel: #15141b;
-  --theme-background-element: #15141b;
-  --theme-border: #2d2d2d;
-  --theme-border-active: #6d6d6d;
-  --theme-border-subtle: #2d2d2d;
-  --theme-diff-added: #61ffca;
-  --theme-diff-removed: #ff6767;
-  --theme-diff-context: #6d6d6d;
-  --theme-diff-hunk-header: #6d6d6d;
-  --theme-diff-highlight-added: #61ffca;
-  --theme-diff-highlight-removed: #ff6767;
-  --theme-diff-added-bg: #354933;
-  --theme-diff-removed-bg: #3f191a;
-  --theme-diff-context-bg: #15141b;
-  --theme-diff-line-number: #2d2d2d;
-  --theme-diff-added-line-number-bg: #162620;
-  --theme-diff-removed-line-number-bg: #26161a;
-  --theme-markdown-text: #edecee;
-  --theme-markdown-heading: #a277ff;
-  --theme-markdown-link: #f694ff;
-  --theme-markdown-link-text: #a277ff;
-  --theme-markdown-code: #61ffca;
-  --theme-markdown-block-quote: #6d6d6d;
-  --theme-markdown-emph: #ffca85;
-  --theme-markdown-strong: #a277ff;
-  --theme-markdown-horizontal-rule: #6d6d6d;
-  --theme-markdown-list-item: #a277ff;
-  --theme-markdown-list-enumeration: #a277ff;
-  --theme-markdown-image: #f694ff;
-  --theme-markdown-image-text: #a277ff;
-  --theme-markdown-code-block: #edecee;
-  --theme-syntax-comment: #6d6d6d;
-  --theme-syntax-keyword: #f694ff;
-  --theme-syntax-function: #a277ff;
-  --theme-syntax-variable: #a277ff;
-  --theme-syntax-string: #61ffca;
-  --theme-syntax-number: #9dff65;
-  --theme-syntax-type: #a277ff;
-  --theme-syntax-operator: #f694ff;
-  --theme-syntax-punctuation: #edecee;
-}
-
-[data-theme="ayu"][data-dark="false"] {
-  --theme-primary: #59c2ff;
-  --theme-secondary: #d2a6ff;
-  --theme-accent: #e6b450;
-  --theme-error: #d95757;
-  --theme-warning: #e6b673;
-  --theme-success: #7fd962;
-  --theme-info: #39bae6;
-  --theme-text: #bfbdb6;
-  --theme-text-muted: #565b66;
-  --theme-background: #0b0e14;
-  --theme-background-panel: #0f131a;
-  --theme-background-element: #0d1017;
-  --theme-border: #6c7380;
-  --theme-border-active: #6c7380;
-  --theme-border-subtle: #11151c;
-  --theme-diff-added: #7fd962;
-  --theme-diff-removed: #f26d78;
-  --theme-diff-context: #acb6bf;
-  --theme-diff-hunk-header: #acb6bf;
-  --theme-diff-highlight-added: #aad94c;
-  --theme-diff-highlight-removed: #f07178;
-  --theme-diff-added-bg: #20303b;
-  --theme-diff-removed-bg: #37222c;
-  --theme-diff-context-bg: #0f131a;
-  --theme-diff-line-number: #6c7380;
-  --theme-diff-added-line-number-bg: #1b2b34;
-  --theme-diff-removed-line-number-bg: #2d1f26;
-  --theme-markdown-text: #bfbdb6;
-  --theme-markdown-heading: #d2a6ff;
-  --theme-markdown-link: #59c2ff;
-  --theme-markdown-link-text: #39bae6;
-  --theme-markdown-code: #aad94c;
-  --theme-markdown-block-quote: #e6b673;
-  --theme-markdown-emph: #e6b673;
-  --theme-markdown-strong: #ffb454;
-  --theme-markdown-horizontal-rule: #565b66;
-  --theme-markdown-list-item: #59c2ff;
-  --theme-markdown-list-enumeration: #39bae6;
-  --theme-markdown-image: #59c2ff;
-  --theme-markdown-image-text: #39bae6;
-  --theme-markdown-code-block: #bfbdb6;
-  --theme-syntax-comment: #acb6bf;
-  --theme-syntax-keyword: #ff8f40;
-  --theme-syntax-function: #ffb454;
-  --theme-syntax-variable: #59c2ff;
-  --theme-syntax-string: #aad94c;
-  --theme-syntax-number: #d2a6ff;
-  --theme-syntax-type: #e6b673;
-  --theme-syntax-operator: #f29668;
-  --theme-syntax-punctuation: #bfbdb6;
-}
-
-[data-theme="ayu"][data-dark="true"] {
-  --theme-primary: #59c2ff;
-  --theme-secondary: #d2a6ff;
-  --theme-accent: #e6b450;
-  --theme-error: #d95757;
-  --theme-warning: #e6b673;
-  --theme-success: #7fd962;
-  --theme-info: #39bae6;
-  --theme-text: #bfbdb6;
-  --theme-text-muted: #565b66;
-  --theme-background: #0b0e14;
-  --theme-background-panel: #0f131a;
-  --theme-background-element: #0d1017;
-  --theme-border: #6c7380;
-  --theme-border-active: #6c7380;
-  --theme-border-subtle: #11151c;
-  --theme-diff-added: #7fd962;
-  --theme-diff-removed: #f26d78;
-  --theme-diff-context: #acb6bf;
-  --theme-diff-hunk-header: #acb6bf;
-  --theme-diff-highlight-added: #aad94c;
-  --theme-diff-highlight-removed: #f07178;
-  --theme-diff-added-bg: #20303b;
-  --theme-diff-removed-bg: #37222c;
-  --theme-diff-context-bg: #0f131a;
-  --theme-diff-line-number: #6c7380;
-  --theme-diff-added-line-number-bg: #1b2b34;
-  --theme-diff-removed-line-number-bg: #2d1f26;
-  --theme-markdown-text: #bfbdb6;
-  --theme-markdown-heading: #d2a6ff;
-  --theme-markdown-link: #59c2ff;
-  --theme-markdown-link-text: #39bae6;
-  --theme-markdown-code: #aad94c;
-  --theme-markdown-block-quote: #e6b673;
-  --theme-markdown-emph: #e6b673;
-  --theme-markdown-strong: #ffb454;
-  --theme-markdown-horizontal-rule: #565b66;
-  --theme-markdown-list-item: #59c2ff;
-  --theme-markdown-list-enumeration: #39bae6;
-  --theme-markdown-image: #59c2ff;
-  --theme-markdown-image-text: #39bae6;
-  --theme-markdown-code-block: #bfbdb6;
-  --theme-syntax-comment: #acb6bf;
-  --theme-syntax-keyword: #ff8f40;
-  --theme-syntax-function: #ffb454;
-  --theme-syntax-variable: #59c2ff;
-  --theme-syntax-string: #aad94c;
-  --theme-syntax-number: #d2a6ff;
-  --theme-syntax-type: #e6b673;
-  --theme-syntax-operator: #f29668;
-  --theme-syntax-punctuation: #bfbdb6;
-}
-
-[data-theme="catppuccin"][data-dark="false"] {
-  --theme-primary: #1e66f5;
-  --theme-secondary: #8839ef;
-  --theme-accent: #ea76cb;
-  --theme-error: #d20f39;
-  --theme-warning: #df8e1d;
-  --theme-success: #40a02b;
-  --theme-info: #179299;
-  --theme-text: #4c4f69;
-  --theme-text-muted: #5c5f77;
-  --theme-background: #eff1f5;
-  --theme-background-panel: #e6e9ef;
-  --theme-background-element: #dce0e8;
-  --theme-border: #ccd0da;
-  --theme-border-active: #bcc0cc;
-  --theme-border-subtle: #acb0be;
-  --theme-diff-added: #40a02b;
-  --theme-diff-removed: #d20f39;
-  --theme-diff-context: #7c7f93;
-  --theme-diff-hunk-header: #fe640b;
-  --theme-diff-highlight-added: #40a02b;
-  --theme-diff-highlight-removed: #d20f39;
-  --theme-diff-added-bg: #d6f0d9;
-  --theme-diff-removed-bg: #f6dfe2;
-  --theme-diff-context-bg: #e6e9ef;
-  --theme-diff-line-number: #bcc0cc;
-  --theme-diff-added-line-number-bg: #c9e3cb;
-  --theme-diff-removed-line-number-bg: #e9d3d6;
-  --theme-markdown-text: #4c4f69;
-  --theme-markdown-heading: #8839ef;
-  --theme-markdown-link: #1e66f5;
-  --theme-markdown-link-text: #04a5e5;
-  --theme-markdown-code: #40a02b;
-  --theme-markdown-block-quote: #df8e1d;
-  --theme-markdown-emph: #df8e1d;
-  --theme-markdown-strong: #fe640b;
-  --theme-markdown-horizontal-rule: #6c6f85;
-  --theme-markdown-list-item: #1e66f5;
-  --theme-markdown-list-enumeration: #04a5e5;
-  --theme-markdown-image: #1e66f5;
-  --theme-markdown-image-text: #04a5e5;
-  --theme-markdown-code-block: #4c4f69;
-  --theme-syntax-comment: #7c7f93;
-  --theme-syntax-keyword: #8839ef;
-  --theme-syntax-function: #1e66f5;
-  --theme-syntax-variable: #d20f39;
-  --theme-syntax-string: #40a02b;
-  --theme-syntax-number: #fe640b;
-  --theme-syntax-type: #df8e1d;
-  --theme-syntax-operator: #04a5e5;
-  --theme-syntax-punctuation: #4c4f69;
-}
-
-[data-theme="catppuccin"][data-dark="true"] {
-  --theme-primary: #89b4fa;
-  --theme-secondary: #cba6f7;
-  --theme-accent: #f5c2e7;
-  --theme-error: #f38ba8;
-  --theme-warning: #f9e2af;
-  --theme-success: #a6e3a1;
-  --theme-info: #94e2d5;
-  --theme-text: #cdd6f4;
-  --theme-text-muted: #bac2de;
-  --theme-background: #1e1e2e;
-  --theme-background-panel: #181825;
-  --theme-background-element: #11111b;
-  --theme-border: #313244;
-  --theme-border-active: #45475a;
-  --theme-border-subtle: #585b70;
-  --theme-diff-added: #a6e3a1;
-  --theme-diff-removed: #f38ba8;
-  --theme-diff-context: #9399b2;
-  --theme-diff-hunk-header: #fab387;
-  --theme-diff-highlight-added: #a6e3a1;
-  --theme-diff-highlight-removed: #f38ba8;
-  --theme-diff-added-bg: #24312b;
-  --theme-diff-removed-bg: #3c2a32;
-  --theme-diff-context-bg: #181825;
-  --theme-diff-line-number: #45475a;
-  --theme-diff-added-line-number-bg: #1e2a25;
-  --theme-diff-removed-line-number-bg: #32232a;
-  --theme-markdown-text: #cdd6f4;
-  --theme-markdown-heading: #cba6f7;
-  --theme-markdown-link: #89b4fa;
-  --theme-markdown-link-text: #89dceb;
-  --theme-markdown-code: #a6e3a1;
-  --theme-markdown-block-quote: #f9e2af;
-  --theme-markdown-emph: #f9e2af;
-  --theme-markdown-strong: #fab387;
-  --theme-markdown-horizontal-rule: #a6adc8;
-  --theme-markdown-list-item: #89b4fa;
-  --theme-markdown-list-enumeration: #89dceb;
-  --theme-markdown-image: #89b4fa;
-  --theme-markdown-image-text: #89dceb;
-  --theme-markdown-code-block: #cdd6f4;
-  --theme-syntax-comment: #9399b2;
-  --theme-syntax-keyword: #cba6f7;
-  --theme-syntax-function: #89b4fa;
-  --theme-syntax-variable: #f38ba8;
-  --theme-syntax-string: #a6e3a1;
-  --theme-syntax-number: #fab387;
-  --theme-syntax-type: #f9e2af;
-  --theme-syntax-operator: #89dceb;
-  --theme-syntax-punctuation: #cdd6f4;
-}
-
-[data-theme="cobalt2"][data-dark="false"] {
-  --theme-primary: #0066cc;
-  --theme-secondary: #7c4dff;
-  --theme-accent: #00acc1;
-  --theme-error: #e91e63;
-  --theme-warning: #ff9800;
-  --theme-success: #4caf50;
-  --theme-info: #ff5722;
-  --theme-text: #193549;
-  --theme-text-muted: #5c6b7d;
-  --theme-background: #ffffff;
-  --theme-background-panel: #f5f7fa;
-  --theme-background-element: #e8ecf1;
-  --theme-border: #d3dae3;
-  --theme-border-active: #0066cc;
-  --theme-border-subtle: #e8ecf1;
-  --theme-diff-added: #4caf50;
-  --theme-diff-removed: #e91e63;
-  --theme-diff-context: #5c6b7d;
-  --theme-diff-hunk-header: #00acc1;
-  --theme-diff-highlight-added: #4caf50;
-  --theme-diff-highlight-removed: #e91e63;
-  --theme-diff-added-bg: #e8f5e9;
-  --theme-diff-removed-bg: #ffebee;
-  --theme-diff-context-bg: #f5f7fa;
-  --theme-diff-line-number: #b0bec5;
-  --theme-diff-added-line-number-bg: #e8f5e9;
-  --theme-diff-removed-line-number-bg: #ffebee;
-  --theme-markdown-text: #193549;
-  --theme-markdown-heading: #ff9800;
-  --theme-markdown-link: #0066cc;
-  --theme-markdown-link-text: #00acc1;
-  --theme-markdown-code: #4caf50;
-  --theme-markdown-block-quote: #5c6b7d;
-  --theme-markdown-emph: #ff5722;
-  --theme-markdown-strong: #e91e63;
-  --theme-markdown-horizontal-rule: #d3dae3;
-  --theme-markdown-list-item: #0066cc;
-  --theme-markdown-list-enumeration: #00acc1;
-  --theme-markdown-image: #0066cc;
-  --theme-markdown-image-text: #00acc1;
-  --theme-markdown-code-block: #193549;
-  --theme-syntax-comment: #5c6b7d;
-  --theme-syntax-keyword: #ff5722;
-  --theme-syntax-function: #ff9800;
-  --theme-syntax-variable: #193549;
-  --theme-syntax-string: #4caf50;
-  --theme-syntax-number: #e91e63;
-  --theme-syntax-type: #00acc1;
-  --theme-syntax-operator: #ff5722;
-  --theme-syntax-punctuation: #193549;
-}
-
-[data-theme="cobalt2"][data-dark="true"] {
-  --theme-primary: #0088ff;
-  --theme-secondary: #9a5feb;
-  --theme-accent: #2affdf;
-  --theme-error: #ff0088;
-  --theme-warning: #ffc600;
-  --theme-success: #9eff80;
-  --theme-info: #ff9d00;
-  --theme-text: #ffffff;
-  --theme-text-muted: #adb7c9;
-  --theme-background: #193549;
-  --theme-background-panel: #122738;
-  --theme-background-element: #1f4662;
-  --theme-border: #1f4662;
-  --theme-border-active: #0088ff;
-  --theme-border-subtle: #0e1e2e;
-  --theme-diff-added: #9eff80;
-  --theme-diff-removed: #ff0088;
-  --theme-diff-context: #adb7c9;
-  --theme-diff-hunk-header: #2affdf;
-  --theme-diff-highlight-added: #b9ff9f;
-  --theme-diff-highlight-removed: #ff5fb3;
-  --theme-diff-added-bg: #1a3a2a;
-  --theme-diff-removed-bg: #3a1a2a;
-  --theme-diff-context-bg: #122738;
-  --theme-diff-line-number: #2d5a7b;
-  --theme-diff-added-line-number-bg: #1a3a2a;
-  --theme-diff-removed-line-number-bg: #3a1a2a;
-  --theme-markdown-text: #ffffff;
-  --theme-markdown-heading: #ffc600;
-  --theme-markdown-link: #0088ff;
-  --theme-markdown-link-text: #2affdf;
-  --theme-markdown-code: #9eff80;
-  --theme-markdown-block-quote: #adb7c9;
-  --theme-markdown-emph: #ff9d00;
-  --theme-markdown-strong: #ff628c;
-  --theme-markdown-horizontal-rule: #2d5a7b;
-  --theme-markdown-list-item: #0088ff;
-  --theme-markdown-list-enumeration: #2affdf;
-  --theme-markdown-image: #0088ff;
-  --theme-markdown-image-text: #2affdf;
-  --theme-markdown-code-block: #ffffff;
-  --theme-syntax-comment: #0088ff;
-  --theme-syntax-keyword: #ff9d00;
-  --theme-syntax-function: #ffc600;
-  --theme-syntax-variable: #ffffff;
-  --theme-syntax-string: #9eff80;
-  --theme-syntax-number: #ff628c;
-  --theme-syntax-type: #2affdf;
-  --theme-syntax-operator: #ff9d00;
-  --theme-syntax-punctuation: #ffffff;
-}
-
-[data-theme="dracula"][data-dark="false"] {
-  --theme-primary: #bd93f9;
-  --theme-secondary: #ff79c6;
-  --theme-accent: #8be9fd;
-  --theme-error: #ff5555;
-  --theme-warning: #f1fa8c;
-  --theme-success: #50fa7b;
-  --theme-info: #ffb86c;
-  --theme-text: #282a36;
-  --theme-text-muted: #6272a4;
-  --theme-background: #f8f8f2;
-  --theme-background-panel: #e8e8e2;
-  --theme-background-element: #d8d8d2;
-  --theme-border: #c8c8c2;
-  --theme-border-active: #bd93f9;
-  --theme-border-subtle: #e0e0e0;
-  --theme-diff-added: #50fa7b;
-  --theme-diff-removed: #ff5555;
-  --theme-diff-context: #6272a4;
-  --theme-diff-hunk-header: #6272a4;
-  --theme-diff-highlight-added: #50fa7b;
-  --theme-diff-highlight-removed: #ff5555;
-  --theme-diff-added-bg: #e0ffe0;
-  --theme-diff-removed-bg: #ffe0e0;
-  --theme-diff-context-bg: #e8e8e2;
-  --theme-diff-line-number: #c8c8c2;
-  --theme-diff-added-line-number-bg: #e0ffe0;
-  --theme-diff-removed-line-number-bg: #ffe0e0;
-  --theme-markdown-text: #282a36;
-  --theme-markdown-heading: #bd93f9;
-  --theme-markdown-link: #8be9fd;
-  --theme-markdown-link-text: #ff79c6;
-  --theme-markdown-code: #50fa7b;
-  --theme-markdown-block-quote: #6272a4;
-  --theme-markdown-emph: #f1fa8c;
-  --theme-markdown-strong: #ffb86c;
-  --theme-markdown-horizontal-rule: #6272a4;
-  --theme-markdown-list-item: #bd93f9;
-  --theme-markdown-list-enumeration: #8be9fd;
-  --theme-markdown-image: #8be9fd;
-  --theme-markdown-image-text: #ff79c6;
-  --theme-markdown-code-block: #282a36;
-  --theme-syntax-comment: #6272a4;
-  --theme-syntax-keyword: #ff79c6;
-  --theme-syntax-function: #50fa7b;
-  --theme-syntax-variable: #282a36;
-  --theme-syntax-string: #f1fa8c;
-  --theme-syntax-number: #bd93f9;
-  --theme-syntax-type: #8be9fd;
-  --theme-syntax-operator: #ff79c6;
-  --theme-syntax-punctuation: #282a36;
-}
-
-[data-theme="dracula"][data-dark="true"] {
-  --theme-primary: #bd93f9;
-  --theme-secondary: #ff79c6;
-  --theme-accent: #8be9fd;
-  --theme-error: #ff5555;
-  --theme-warning: #f1fa8c;
-  --theme-success: #50fa7b;
-  --theme-info: #ffb86c;
-  --theme-text: #f8f8f2;
-  --theme-text-muted: #6272a4;
-  --theme-background: #282a36;
-  --theme-background-panel: #21222c;
-  --theme-background-element: #44475a;
-  --theme-border: #44475a;
-  --theme-border-active: #bd93f9;
-  --theme-border-subtle: #191a21;
-  --theme-diff-added: #50fa7b;
-  --theme-diff-removed: #ff5555;
-  --theme-diff-context: #6272a4;
-  --theme-diff-hunk-header: #6272a4;
-  --theme-diff-highlight-added: #50fa7b;
-  --theme-diff-highlight-removed: #ff5555;
-  --theme-diff-added-bg: #1a3a1a;
-  --theme-diff-removed-bg: #3a1a1a;
-  --theme-diff-context-bg: #21222c;
-  --theme-diff-line-number: #44475a;
-  --theme-diff-added-line-number-bg: #1a3a1a;
-  --theme-diff-removed-line-number-bg: #3a1a1a;
-  --theme-markdown-text: #f8f8f2;
-  --theme-markdown-heading: #bd93f9;
-  --theme-markdown-link: #8be9fd;
-  --theme-markdown-link-text: #ff79c6;
-  --theme-markdown-code: #50fa7b;
-  --theme-markdown-block-quote: #6272a4;
-  --theme-markdown-emph: #f1fa8c;
-  --theme-markdown-strong: #ffb86c;
-  --theme-markdown-horizontal-rule: #6272a4;
-  --theme-markdown-list-item: #bd93f9;
-  --theme-markdown-list-enumeration: #8be9fd;
-  --theme-markdown-image: #8be9fd;
-  --theme-markdown-image-text: #ff79c6;
-  --theme-markdown-code-block: #f8f8f2;
-  --theme-syntax-comment: #6272a4;
-  --theme-syntax-keyword: #ff79c6;
-  --theme-syntax-function: #50fa7b;
-  --theme-syntax-variable: #f8f8f2;
-  --theme-syntax-string: #f1fa8c;
-  --theme-syntax-number: #bd93f9;
-  --theme-syntax-type: #8be9fd;
-  --theme-syntax-operator: #ff79c6;
-  --theme-syntax-punctuation: #f8f8f2;
-}
-
-[data-theme="everforest"][data-dark="false"] {
-  --theme-primary: #8da101;
-  --theme-secondary: #3a94c5;
-  --theme-accent: #df69ba;
-  --theme-error: #f85552;
-  --theme-warning: #f57d26;
-  --theme-success: #8da101;
-  --theme-info: #35a77c;
-  --theme-text: #5c6a72;
-  --theme-text-muted: #a6b0a0;
-  --theme-background: #fdf6e3;
-  --theme-background-panel: #efebd4;
-  --theme-background-element: #f4f0d9;
-  --theme-border: #939f91;
-  --theme-border-active: #829181;
-  --theme-border-subtle: #a6b0a0;
-  --theme-diff-added: #1e725c;
-  --theme-diff-removed: #c53b53;
-  --theme-diff-context: #7086b5;
-  --theme-diff-hunk-header: #7086b5;
-  --theme-diff-highlight-added: #4db380;
-  --theme-diff-highlight-removed: #f52a65;
-  --theme-diff-added-bg: #d5e5d5;
-  --theme-diff-removed-bg: #f7d8db;
-  --theme-diff-context-bg: #efebd4;
-  --theme-diff-line-number: #f4f0d9;
-  --theme-diff-added-line-number-bg: #c5d5c5;
-  --theme-diff-removed-line-number-bg: #e7c8cb;
-  --theme-markdown-text: #5c6a72;
-  --theme-markdown-heading: #df69ba;
-  --theme-markdown-link: #8da101;
-  --theme-markdown-link-text: #35a77c;
-  --theme-markdown-code: #8da101;
-  --theme-markdown-block-quote: #dfa000;
-  --theme-markdown-emph: #dfa000;
-  --theme-markdown-strong: #f57d26;
-  --theme-markdown-horizontal-rule: #a6b0a0;
-  --theme-markdown-list-item: #8da101;
-  --theme-markdown-list-enumeration: #35a77c;
-  --theme-markdown-image: #8da101;
-  --theme-markdown-image-text: #35a77c;
-  --theme-markdown-code-block: #5c6a72;
-  --theme-syntax-comment: #a6b0a0;
-  --theme-syntax-keyword: #df69ba;
-  --theme-syntax-function: #8da101;
-  --theme-syntax-variable: #f85552;
-  --theme-syntax-string: #8da101;
-  --theme-syntax-number: #f57d26;
-  --theme-syntax-type: #dfa000;
-  --theme-syntax-operator: #35a77c;
-  --theme-syntax-punctuation: #5c6a72;
-}
-
-[data-theme="everforest"][data-dark="true"] {
-  --theme-primary: #a7c080;
-  --theme-secondary: #7fbbb3;
-  --theme-accent: #d699b6;
-  --theme-error: #e67e80;
-  --theme-warning: #e69875;
-  --theme-success: #a7c080;
-  --theme-info: #83c092;
-  --theme-text: #d3c6aa;
-  --theme-text-muted: #7a8478;
-  --theme-background: #2d353b;
-  --theme-background-panel: #333c43;
-  --theme-background-element: #343f44;
-  --theme-border: #859289;
-  --theme-border-active: #9da9a0;
-  --theme-border-subtle: #7a8478;
-  --theme-diff-added: #4fd6be;
-  --theme-diff-removed: #c53b53;
-  --theme-diff-context: #828bb8;
-  --theme-diff-hunk-header: #828bb8;
-  --theme-diff-highlight-added: #b8db87;
-  --theme-diff-highlight-removed: #e26a75;
-  --theme-diff-added-bg: #20303b;
-  --theme-diff-removed-bg: #37222c;
-  --theme-diff-context-bg: #333c43;
-  --theme-diff-line-number: #343f44;
-  --theme-diff-added-line-number-bg: #1b2b34;
-  --theme-diff-removed-line-number-bg: #2d1f26;
-  --theme-markdown-text: #d3c6aa;
-  --theme-markdown-heading: #d699b6;
-  --theme-markdown-link: #a7c080;
-  --theme-markdown-link-text: #83c092;
-  --theme-markdown-code: #a7c080;
-  --theme-markdown-block-quote: #dbbc7f;
-  --theme-markdown-emph: #dbbc7f;
-  --theme-markdown-strong: #e69875;
-  --theme-markdown-horizontal-rule: #7a8478;
-  --theme-markdown-list-item: #a7c080;
-  --theme-markdown-list-enumeration: #83c092;
-  --theme-markdown-image: #a7c080;
-  --theme-markdown-image-text: #83c092;
-  --theme-markdown-code-block: #d3c6aa;
-  --theme-syntax-comment: #7a8478;
-  --theme-syntax-keyword: #d699b6;
-  --theme-syntax-function: #a7c080;
-  --theme-syntax-variable: #e67e80;
-  --theme-syntax-string: #a7c080;
-  --theme-syntax-number: #e69875;
-  --theme-syntax-type: #dbbc7f;
-  --theme-syntax-operator: #83c092;
-  --theme-syntax-punctuation: #d3c6aa;
-}
-
-[data-theme="github"][data-dark="false"] {
-  --theme-primary: #0969da;
-  --theme-secondary: #8250df;
-  --theme-accent: #1b7c83;
-  --theme-error: #cf222e;
-  --theme-warning: #9a6700;
-  --theme-success: #1a7f37;
-  --theme-info: #bc4c00;
-  --theme-text: #24292f;
-  --theme-text-muted: #57606a;
-  --theme-background: #ffffff;
-  --theme-background-panel: #f6f8fa;
-  --theme-background-element: #f0f3f6;
-  --theme-border: #d0d7de;
-  --theme-border-active: #0969da;
-  --theme-border-subtle: #d8dee4;
-  --theme-diff-added: #1a7f37;
-  --theme-diff-removed: #cf222e;
-  --theme-diff-context: #57606a;
-  --theme-diff-hunk-header: #0969da;
-  --theme-diff-highlight-added: #1a7f37;
-  --theme-diff-highlight-removed: #cf222e;
-  --theme-diff-added-bg: #dafbe1;
-  --theme-diff-removed-bg: #ffebe9;
-  --theme-diff-context-bg: #f6f8fa;
-  --theme-diff-line-number: #afb8c1;
-  --theme-diff-added-line-number-bg: #dafbe1;
-  --theme-diff-removed-line-number-bg: #ffebe9;
-  --theme-markdown-text: #24292f;
-  --theme-markdown-heading: #0969da;
-  --theme-markdown-link: #0969da;
-  --theme-markdown-link-text: #1b7c83;
-  --theme-markdown-code: #bf3989;
-  --theme-markdown-block-quote: #57606a;
-  --theme-markdown-emph: #9a6700;
-  --theme-markdown-strong: #bc4c00;
-  --theme-markdown-horizontal-rule: #d0d7de;
-  --theme-markdown-list-item: #0969da;
-  --theme-markdown-list-enumeration: #1b7c83;
-  --theme-markdown-image: #0969da;
-  --theme-markdown-image-text: #1b7c83;
-  --theme-markdown-code-block: #24292f;
-  --theme-syntax-comment: #57606a;
-  --theme-syntax-keyword: #cf222e;
-  --theme-syntax-function: #8250df;
-  --theme-syntax-variable: #bc4c00;
-  --theme-syntax-string: #0969da;
-  --theme-syntax-number: #1b7c83;
-  --theme-syntax-type: #bc4c00;
-  --theme-syntax-operator: #cf222e;
-  --theme-syntax-punctuation: #24292f;
-}
-
-[data-theme="github"][data-dark="true"] {
-  --theme-primary: #58a6ff;
-  --theme-secondary: #bc8cff;
-  --theme-accent: #39c5cf;
-  --theme-error: #f85149;
-  --theme-warning: #e3b341;
-  --theme-success: #3fb950;
-  --theme-info: #d29922;
-  --theme-text: #c9d1d9;
-  --theme-text-muted: #8b949e;
-  --theme-background: #0d1117;
-  --theme-background-panel: #010409;
-  --theme-background-element: #161b22;
-  --theme-border: #30363d;
-  --theme-border-active: #58a6ff;
-  --theme-border-subtle: #21262d;
-  --theme-diff-added: #3fb950;
-  --theme-diff-removed: #f85149;
-  --theme-diff-context: #8b949e;
-  --theme-diff-hunk-header: #58a6ff;
-  --theme-diff-highlight-added: #3fb950;
-  --theme-diff-highlight-removed: #f85149;
-  --theme-diff-added-bg: #033a16;
-  --theme-diff-removed-bg: #67060c;
-  --theme-diff-context-bg: #010409;
-  --theme-diff-line-number: #484f58;
-  --theme-diff-added-line-number-bg: #033a16;
-  --theme-diff-removed-line-number-bg: #67060c;
-  --theme-markdown-text: #c9d1d9;
-  --theme-markdown-heading: #58a6ff;
-  --theme-markdown-link: #58a6ff;
-  --theme-markdown-link-text: #39c5cf;
-  --theme-markdown-code: #ff7b72;
-  --theme-markdown-block-quote: #8b949e;
-  --theme-markdown-emph: #e3b341;
-  --theme-markdown-strong: #d29922;
-  --theme-markdown-horizontal-rule: #30363d;
-  --theme-markdown-list-item: #58a6ff;
-  --theme-markdown-list-enumeration: #39c5cf;
-  --theme-markdown-image: #58a6ff;
-  --theme-markdown-image-text: #39c5cf;
-  --theme-markdown-code-block: #c9d1d9;
-  --theme-syntax-comment: #8b949e;
-  --theme-syntax-keyword: #ff7b72;
-  --theme-syntax-function: #bc8cff;
-  --theme-syntax-variable: #d29922;
-  --theme-syntax-string: #39c5cf;
-  --theme-syntax-number: #58a6ff;
-  --theme-syntax-type: #d29922;
-  --theme-syntax-operator: #ff7b72;
-  --theme-syntax-punctuation: #c9d1d9;
-}
-
-[data-theme="gruvbox"][data-dark="false"] {
-  --theme-primary: #076678;
-  --theme-secondary: #8f3f71;
-  --theme-accent: #427b58;
-  --theme-error: #9d0006;
-  --theme-warning: #af3a03;
-  --theme-success: #79740e;
-  --theme-info: #b57614;
-  --theme-text: #3c3836;
-  --theme-text-muted: #7c6f64;
-  --theme-background: #fbf1c7;
-  --theme-background-panel: #ebdbb2;
-  --theme-background-element: #d5c4a1;
-  --theme-border: #bdae93;
-  --theme-border-active: #3c3836;
-  --theme-border-subtle: #d5c4a1;
-  --theme-diff-added: #79740e;
-  --theme-diff-removed: #9d0006;
-  --theme-diff-context: #7c6f64;
-  --theme-diff-hunk-header: #427b58;
-  --theme-diff-highlight-added: #79740e;
-  --theme-diff-highlight-removed: #9d0006;
-  --theme-diff-added-bg: #e2e0b5;
-  --theme-diff-removed-bg: #e9d8d5;
-  --theme-diff-context-bg: #ebdbb2;
-  --theme-diff-line-number: #bdae93;
-  --theme-diff-added-line-number-bg: #d4d2a9;
-  --theme-diff-removed-line-number-bg: #d8cbc8;
-  --theme-markdown-text: #3c3836;
-  --theme-markdown-heading: #076678;
-  --theme-markdown-link: #427b58;
-  --theme-markdown-link-text: #79740e;
-  --theme-markdown-code: #b57614;
-  --theme-markdown-block-quote: #7c6f64;
-  --theme-markdown-emph: #8f3f71;
-  --theme-markdown-strong: #af3a03;
-  --theme-markdown-horizontal-rule: #7c6f64;
-  --theme-markdown-list-item: #076678;
-  --theme-markdown-list-enumeration: #427b58;
-  --theme-markdown-image: #427b58;
-  --theme-markdown-image-text: #79740e;
-  --theme-markdown-code-block: #3c3836;
-  --theme-syntax-comment: #7c6f64;
-  --theme-syntax-keyword: #9d0006;
-  --theme-syntax-function: #79740e;
-  --theme-syntax-variable: #076678;
-  --theme-syntax-string: #b57614;
-  --theme-syntax-number: #8f3f71;
-  --theme-syntax-type: #427b58;
-  --theme-syntax-operator: #af3a03;
-  --theme-syntax-punctuation: #3c3836;
-}
-
-[data-theme="gruvbox"][data-dark="true"] {
-  --theme-primary: #83a598;
-  --theme-secondary: #d3869b;
-  --theme-accent: #8ec07c;
-  --theme-error: #fb4934;
-  --theme-warning: #fe8019;
-  --theme-success: #b8bb26;
-  --theme-info: #fabd2f;
-  --theme-text: #ebdbb2;
-  --theme-text-muted: #928374;
-  --theme-background: #282828;
-  --theme-background-panel: #3c3836;
-  --theme-background-element: #504945;
-  --theme-border: #665c54;
-  --theme-border-active: #ebdbb2;
-  --theme-border-subtle: #504945;
-  --theme-diff-added: #98971a;
-  --theme-diff-removed: #cc241d;
-  --theme-diff-context: #928374;
-  --theme-diff-hunk-header: #689d6a;
-  --theme-diff-highlight-added: #b8bb26;
-  --theme-diff-highlight-removed: #fb4934;
-  --theme-diff-added-bg: #32302f;
-  --theme-diff-removed-bg: #322929;
-  --theme-diff-context-bg: #3c3836;
-  --theme-diff-line-number: #665c54;
-  --theme-diff-added-line-number-bg: #2a2827;
-  --theme-diff-removed-line-number-bg: #2a2222;
-  --theme-markdown-text: #ebdbb2;
-  --theme-markdown-heading: #83a598;
-  --theme-markdown-link: #8ec07c;
-  --theme-markdown-link-text: #b8bb26;
-  --theme-markdown-code: #fabd2f;
-  --theme-markdown-block-quote: #928374;
-  --theme-markdown-emph: #d3869b;
-  --theme-markdown-strong: #fe8019;
-  --theme-markdown-horizontal-rule: #928374;
-  --theme-markdown-list-item: #83a598;
-  --theme-markdown-list-enumeration: #8ec07c;
-  --theme-markdown-image: #8ec07c;
-  --theme-markdown-image-text: #b8bb26;
-  --theme-markdown-code-block: #ebdbb2;
-  --theme-syntax-comment: #928374;
-  --theme-syntax-keyword: #fb4934;
-  --theme-syntax-function: #b8bb26;
-  --theme-syntax-variable: #83a598;
-  --theme-syntax-string: #fabd2f;
-  --theme-syntax-number: #d3869b;
-  --theme-syntax-type: #8ec07c;
-  --theme-syntax-operator: #fe8019;
-  --theme-syntax-punctuation: #ebdbb2;
-}
-
-[data-theme="kanagawa"][data-dark="false"] {
-  --theme-primary: #2d4f67;
-  --theme-secondary: #957fb8;
-  --theme-accent: #d27e99;
-  --theme-error: #e82424;
-  --theme-warning: #d7a657;
-  --theme-success: #98bb6c;
-  --theme-info: #76946a;
-  --theme-text: #54433a;
-  --theme-text-muted: #9e9389;
-  --theme-background: #f2e9de;
-  --theme-background-panel: #eae4d7;
-  --theme-background-element: #e3dcd2;
-  --theme-border: #d4cbbf;
-  --theme-border-active: #c38d9d;
-  --theme-border-subtle: #dcd4c9;
-  --theme-diff-added: #98bb6c;
-  --theme-diff-removed: #e82424;
-  --theme-diff-context: #9e9389;
-  --theme-diff-hunk-header: #2d4f67;
-  --theme-diff-highlight-added: #89af5b;
-  --theme-diff-highlight-removed: #d61f1f;
-  --theme-diff-added-bg: #eaf3e4;
-  --theme-diff-removed-bg: #fbe6e6;
-  --theme-diff-context-bg: #eae4d7;
-  --theme-diff-line-number: #c7beb4;
-  --theme-diff-added-line-number-bg: #dde8d6;
-  --theme-diff-removed-line-number-bg: #f2dada;
-  --theme-markdown-text: #54433a;
-  --theme-markdown-heading: #957fb8;
-  --theme-markdown-link: #2d4f67;
-  --theme-markdown-link-text: #76946a;
-  --theme-markdown-code: #98bb6c;
-  --theme-markdown-block-quote: #9e9389;
-  --theme-markdown-emph: #c38d9d;
-  --theme-markdown-strong: #d7a657;
-  --theme-markdown-horizontal-rule: #9e9389;
-  --theme-markdown-list-item: #2d4f67;
-  --theme-markdown-list-enumeration: #76946a;
-  --theme-markdown-image: #2d4f67;
-  --theme-markdown-image-text: #76946a;
-  --theme-markdown-code-block: #54433a;
-  --theme-syntax-comment: #9e9389;
-  --theme-syntax-keyword: #957fb8;
-  --theme-syntax-function: #2d4f67;
-  --theme-syntax-variable: #54433a;
-  --theme-syntax-string: #98bb6c;
-  --theme-syntax-number: #d7a657;
-  --theme-syntax-type: #c38d9d;
-  --theme-syntax-operator: #d27e99;
-  --theme-syntax-punctuation: #54433a;
-}
-
-[data-theme="kanagawa"][data-dark="true"] {
-  --theme-primary: #7e9cd8;
-  --theme-secondary: #957fb8;
-  --theme-accent: #d27e99;
-  --theme-error: #e82424;
-  --theme-warning: #d7a657;
-  --theme-success: #98bb6c;
-  --theme-info: #76946a;
-  --theme-text: #dcd7ba;
-  --theme-text-muted: #727169;
-  --theme-background: #1f1f28;
-  --theme-background-panel: #2a2a37;
-  --theme-background-element: #363646;
-  --theme-border: #54546d;
-  --theme-border-active: #c38d9d;
-  --theme-border-subtle: #363646;
-  --theme-diff-added: #98bb6c;
-  --theme-diff-removed: #e82424;
-  --theme-diff-context: #727169;
-  --theme-diff-hunk-header: #2d4f67;
-  --theme-diff-highlight-added: #a9d977;
-  --theme-diff-highlight-removed: #f24a4a;
-  --theme-diff-added-bg: #252e25;
-  --theme-diff-removed-bg: #362020;
-  --theme-diff-context-bg: #2a2a37;
-  --theme-diff-line-number: #54546d;
-  --theme-diff-added-line-number-bg: #202820;
-  --theme-diff-removed-line-number-bg: #2d1c1c;
-  --theme-markdown-text: #dcd7ba;
-  --theme-markdown-heading: #957fb8;
-  --theme-markdown-link: #7e9cd8;
-  --theme-markdown-link-text: #76946a;
-  --theme-markdown-code: #98bb6c;
-  --theme-markdown-block-quote: #727169;
-  --theme-markdown-emph: #c38d9d;
-  --theme-markdown-strong: #d7a657;
-  --theme-markdown-horizontal-rule: #727169;
-  --theme-markdown-list-item: #7e9cd8;
-  --theme-markdown-list-enumeration: #76946a;
-  --theme-markdown-image: #7e9cd8;
-  --theme-markdown-image-text: #76946a;
-  --theme-markdown-code-block: #dcd7ba;
-  --theme-syntax-comment: #727169;
-  --theme-syntax-keyword: #957fb8;
-  --theme-syntax-function: #7e9cd8;
-  --theme-syntax-variable: #dcd7ba;
-  --theme-syntax-string: #98bb6c;
-  --theme-syntax-number: #d7a657;
-  --theme-syntax-type: #c38d9d;
-  --theme-syntax-operator: #d27e99;
-  --theme-syntax-punctuation: #dcd7ba;
-}
-
-[data-theme="material"][data-dark="false"] {
-  --theme-primary: #6182b8;
-  --theme-secondary: #7c4dff;
-  --theme-accent: #39adb5;
-  --theme-error: #e53935;
-  --theme-warning: #ffb300;
-  --theme-success: #91b859;
-  --theme-info: #f4511e;
-  --theme-text: #263238;
-  --theme-text-muted: #90a4ae;
-  --theme-background: #fafafa;
-  --theme-background-panel: #f5f5f5;
-  --theme-background-element: #e7e7e8;
-  --theme-border: #e0e0e0;
-  --theme-border-active: #6182b8;
-  --theme-border-subtle: #eeeeee;
-  --theme-diff-added: #91b859;
-  --theme-diff-removed: #e53935;
-  --theme-diff-context: #90a4ae;
-  --theme-diff-hunk-header: #39adb5;
-  --theme-diff-highlight-added: #91b859;
-  --theme-diff-highlight-removed: #e53935;
-  --theme-diff-added-bg: #e8f5e9;
-  --theme-diff-removed-bg: #ffebee;
-  --theme-diff-context-bg: #f5f5f5;
-  --theme-diff-line-number: #cfd8dc;
-  --theme-diff-added-line-number-bg: #e8f5e9;
-  --theme-diff-removed-line-number-bg: #ffebee;
-  --theme-markdown-text: #263238;
-  --theme-markdown-heading: #6182b8;
-  --theme-markdown-link: #39adb5;
-  --theme-markdown-link-text: #7c4dff;
-  --theme-markdown-code: #91b859;
-  --theme-markdown-block-quote: #90a4ae;
-  --theme-markdown-emph: #ffb300;
-  --theme-markdown-strong: #f4511e;
-  --theme-markdown-horizontal-rule: #e0e0e0;
-  --theme-markdown-list-item: #6182b8;
-  --theme-markdown-list-enumeration: #39adb5;
-  --theme-markdown-image: #39adb5;
-  --theme-markdown-image-text: #7c4dff;
-  --theme-markdown-code-block: #263238;
-  --theme-syntax-comment: #90a4ae;
-  --theme-syntax-keyword: #7c4dff;
-  --theme-syntax-function: #6182b8;
-  --theme-syntax-variable: #263238;
-  --theme-syntax-string: #91b859;
-  --theme-syntax-number: #f4511e;
-  --theme-syntax-type: #ffb300;
-  --theme-syntax-operator: #39adb5;
-  --theme-syntax-punctuation: #263238;
-}
-
-[data-theme="material"][data-dark="true"] {
-  --theme-primary: #82aaff;
-  --theme-secondary: #c792ea;
-  --theme-accent: #89ddff;
-  --theme-error: #f07178;
-  --theme-warning: #ffcb6b;
-  --theme-success: #c3e88d;
-  --theme-info: #ffcb6b;
-  --theme-text: #eeffff;
-  --theme-text-muted: #546e7a;
-  --theme-background: #263238;
-  --theme-background-panel: #1e272c;
-  --theme-background-element: #37474f;
-  --theme-border: #37474f;
-  --theme-border-active: #82aaff;
-  --theme-border-subtle: #1e272c;
-  --theme-diff-added: #c3e88d;
-  --theme-diff-removed: #f07178;
-  --theme-diff-context: #546e7a;
-  --theme-diff-hunk-header: #89ddff;
-  --theme-diff-highlight-added: #c3e88d;
-  --theme-diff-highlight-removed: #f07178;
-  --theme-diff-added-bg: #2e3c2b;
-  --theme-diff-removed-bg: #3c2b2b;
-  --theme-diff-context-bg: #1e272c;
-  --theme-diff-line-number: #37474f;
-  --theme-diff-added-line-number-bg: #2e3c2b;
-  --theme-diff-removed-line-number-bg: #3c2b2b;
-  --theme-markdown-text: #eeffff;
-  --theme-markdown-heading: #82aaff;
-  --theme-markdown-link: #89ddff;
-  --theme-markdown-link-text: #c792ea;
-  --theme-markdown-code: #c3e88d;
-  --theme-markdown-block-quote: #546e7a;
-  --theme-markdown-emph: #ffcb6b;
-  --theme-markdown-strong: #ffcb6b;
-  --theme-markdown-horizontal-rule: #37474f;
-  --theme-markdown-list-item: #82aaff;
-  --theme-markdown-list-enumeration: #89ddff;
-  --theme-markdown-image: #89ddff;
-  --theme-markdown-image-text: #c792ea;
-  --theme-markdown-code-block: #eeffff;
-  --theme-syntax-comment: #546e7a;
-  --theme-syntax-keyword: #c792ea;
-  --theme-syntax-function: #82aaff;
-  --theme-syntax-variable: #eeffff;
-  --theme-syntax-string: #c3e88d;
-  --theme-syntax-number: #ffcb6b;
-  --theme-syntax-type: #ffcb6b;
-  --theme-syntax-operator: #89ddff;
-  --theme-syntax-punctuation: #eeffff;
-}
-
-[data-theme="matrix"][data-dark="false"] {
-  --theme-primary: #1cc24b;
-  --theme-secondary: #24f6d9;
-  --theme-accent: #c770ff;
-  --theme-error: #ff4b4b;
-  --theme-warning: #e6ff57;
-  --theme-success: #1cc24b;
-  --theme-info: #30b3ff;
-  --theme-text: #203022;
-  --theme-text-muted: #748476;
-  --theme-background: #eef3ea;
-  --theme-background-panel: #e4ebe1;
-  --theme-background-element: #dae1d7;
-  --theme-border: #748476;
-  --theme-border-active: #1cc24b;
-  --theme-border-subtle: #dae1d7;
-  --theme-diff-added: #1cc24b;
-  --theme-diff-removed: #ff4b4b;
-  --theme-diff-context: #748476;
-  --theme-diff-hunk-header: #30b3ff;
-  --theme-diff-highlight-added: #5dac7e;
-  --theme-diff-highlight-removed: #d53a3a;
-  --theme-diff-added-bg: #e0efde;
-  --theme-diff-removed-bg: #f9e5e5;
-  --theme-diff-context-bg: #e4ebe1;
-  --theme-diff-line-number: #748476;
-  --theme-diff-added-line-number-bg: #d6e7d2;
-  --theme-diff-removed-line-number-bg: #f2d2d2;
-  --theme-markdown-text: #203022;
-  --theme-markdown-heading: #24f6d9;
-  --theme-markdown-link: #30b3ff;
-  --theme-markdown-link-text: #24f6d9;
-  --theme-markdown-code: #1cc24b;
-  --theme-markdown-block-quote: #748476;
-  --theme-markdown-emph: #ffa83d;
-  --theme-markdown-strong: #e6ff57;
-  --theme-markdown-horizontal-rule: #748476;
-  --theme-markdown-list-item: #30b3ff;
-  --theme-markdown-list-enumeration: #24f6d9;
-  --theme-markdown-image: #30b3ff;
-  --theme-markdown-image-text: #24f6d9;
-  --theme-markdown-code-block: #203022;
-  --theme-syntax-comment: #748476;
-  --theme-syntax-keyword: #c770ff;
-  --theme-syntax-function: #30b3ff;
-  --theme-syntax-variable: #203022;
-  --theme-syntax-string: #1cc24b;
-  --theme-syntax-number: #ffa83d;
-  --theme-syntax-type: #e6ff57;
-  --theme-syntax-operator: #24f6d9;
-  --theme-syntax-punctuation: #203022;
-}
-
-[data-theme="matrix"][data-dark="true"] {
-  --theme-primary: #2eff6a;
-  --theme-secondary: #00efff;
-  --theme-accent: #c770ff;
-  --theme-error: #ff4b4b;
-  --theme-warning: #e6ff57;
-  --theme-success: #62ff94;
-  --theme-info: #30b3ff;
-  --theme-text: #62ff94;
-  --theme-text-muted: #8ca391;
-  --theme-background: #0a0e0a;
-  --theme-background-panel: #0e130d;
-  --theme-background-element: #141c12;
-  --theme-border: #1e2a1b;
-  --theme-border-active: #2eff6a;
-  --theme-border-subtle: #141c12;
-  --theme-diff-added: #1cc24b;
-  --theme-diff-removed: #ff4b4b;
-  --theme-diff-context: #8ca391;
-  --theme-diff-hunk-header: #30b3ff;
-  --theme-diff-highlight-added: #77ffaf;
-  --theme-diff-highlight-removed: #ff7171;
-  --theme-diff-added-bg: #132616;
-  --theme-diff-removed-bg: #261212;
-  --theme-diff-context-bg: #0e130d;
-  --theme-diff-line-number: #1e2a1b;
-  --theme-diff-added-line-number-bg: #0f1b11;
-  --theme-diff-removed-line-number-bg: #1b1414;
-  --theme-markdown-text: #62ff94;
-  --theme-markdown-heading: #00efff;
-  --theme-markdown-link: #30b3ff;
-  --theme-markdown-link-text: #24f6d9;
-  --theme-markdown-code: #1cc24b;
-  --theme-markdown-block-quote: #8ca391;
-  --theme-markdown-emph: #ffa83d;
-  --theme-markdown-strong: #e6ff57;
-  --theme-markdown-horizontal-rule: #8ca391;
-  --theme-markdown-list-item: #30b3ff;
-  --theme-markdown-list-enumeration: #24f6d9;
-  --theme-markdown-image: #30b3ff;
-  --theme-markdown-image-text: #24f6d9;
-  --theme-markdown-code-block: #62ff94;
-  --theme-syntax-comment: #8ca391;
-  --theme-syntax-keyword: #c770ff;
-  --theme-syntax-function: #30b3ff;
-  --theme-syntax-variable: #62ff94;
-  --theme-syntax-string: #1cc24b;
-  --theme-syntax-number: #ffa83d;
-  --theme-syntax-type: #e6ff57;
-  --theme-syntax-operator: #24f6d9;
-  --theme-syntax-punctuation: #62ff94;
-}
-
-[data-theme="monokai"][data-dark="false"] {
-  --theme-primary: #66d9ef;
-  --theme-secondary: #ae81ff;
-  --theme-accent: #a6e22e;
-  --theme-error: #f92672;
-  --theme-warning: #fd971f;
-  --theme-success: #a6e22e;
-  --theme-info: #fd971f;
-  --theme-text: #272822;
-  --theme-text-muted: #75715e;
-  --theme-background: #fafafa;
-  --theme-background-panel: #f0f0f0;
-  --theme-background-element: #e0e0e0;
-  --theme-border: #d0d0d0;
-  --theme-border-active: #66d9ef;
-  --theme-border-subtle: #e8e8e8;
-  --theme-diff-added: #a6e22e;
-  --theme-diff-removed: #f92672;
-  --theme-diff-context: #75715e;
-  --theme-diff-hunk-header: #75715e;
-  --theme-diff-highlight-added: #a6e22e;
-  --theme-diff-highlight-removed: #f92672;
-  --theme-diff-added-bg: #e0ffe0;
-  --theme-diff-removed-bg: #ffe0e0;
-  --theme-diff-context-bg: #f0f0f0;
-  --theme-diff-line-number: #d0d0d0;
-  --theme-diff-added-line-number-bg: #e0ffe0;
-  --theme-diff-removed-line-number-bg: #ffe0e0;
-  --theme-markdown-text: #272822;
-  --theme-markdown-heading: #f92672;
-  --theme-markdown-link: #66d9ef;
-  --theme-markdown-link-text: #ae81ff;
-  --theme-markdown-code: #a6e22e;
-  --theme-markdown-block-quote: #75715e;
-  --theme-markdown-emph: #fd971f;
-  --theme-markdown-strong: #fd971f;
-  --theme-markdown-horizontal-rule: #75715e;
-  --theme-markdown-list-item: #66d9ef;
-  --theme-markdown-list-enumeration: #ae81ff;
-  --theme-markdown-image: #66d9ef;
-  --theme-markdown-image-text: #ae81ff;
-  --theme-markdown-code-block: #272822;
-  --theme-syntax-comment: #75715e;
-  --theme-syntax-keyword: #f92672;
-  --theme-syntax-function: #a6e22e;
-  --theme-syntax-variable: #272822;
-  --theme-syntax-string: #fd971f;
-  --theme-syntax-number: #ae81ff;
-  --theme-syntax-type: #66d9ef;
-  --theme-syntax-operator: #f92672;
-  --theme-syntax-punctuation: #272822;
-}
-
-[data-theme="monokai"][data-dark="true"] {
-  --theme-primary: #66d9ef;
-  --theme-secondary: #ae81ff;
-  --theme-accent: #a6e22e;
-  --theme-error: #f92672;
-  --theme-warning: #e6db74;
-  --theme-success: #a6e22e;
-  --theme-info: #fd971f;
-  --theme-text: #f8f8f2;
-  --theme-text-muted: #75715e;
-  --theme-background: #272822;
-  --theme-background-panel: #1e1f1c;
-  --theme-background-element: #3e3d32;
-  --theme-border: #3e3d32;
-  --theme-border-active: #66d9ef;
-  --theme-border-subtle: #1e1f1c;
-  --theme-diff-added: #a6e22e;
-  --theme-diff-removed: #f92672;
-  --theme-diff-context: #75715e;
-  --theme-diff-hunk-header: #75715e;
-  --theme-diff-highlight-added: #a6e22e;
-  --theme-diff-highlight-removed: #f92672;
-  --theme-diff-added-bg: #1a3a1a;
-  --theme-diff-removed-bg: #3a1a1a;
-  --theme-diff-context-bg: #1e1f1c;
-  --theme-diff-line-number: #3e3d32;
-  --theme-diff-added-line-number-bg: #1a3a1a;
-  --theme-diff-removed-line-number-bg: #3a1a1a;
-  --theme-markdown-text: #f8f8f2;
-  --theme-markdown-heading: #f92672;
-  --theme-markdown-link: #66d9ef;
-  --theme-markdown-link-text: #ae81ff;
-  --theme-markdown-code: #a6e22e;
-  --theme-markdown-block-quote: #75715e;
-  --theme-markdown-emph: #e6db74;
-  --theme-markdown-strong: #fd971f;
-  --theme-markdown-horizontal-rule: #75715e;
-  --theme-markdown-list-item: #66d9ef;
-  --theme-markdown-list-enumeration: #ae81ff;
-  --theme-markdown-image: #66d9ef;
-  --theme-markdown-image-text: #ae81ff;
-  --theme-markdown-code-block: #f8f8f2;
-  --theme-syntax-comment: #75715e;
-  --theme-syntax-keyword: #f92672;
-  --theme-syntax-function: #a6e22e;
-  --theme-syntax-variable: #f8f8f2;
-  --theme-syntax-string: #e6db74;
-  --theme-syntax-number: #ae81ff;
-  --theme-syntax-type: #66d9ef;
-  --theme-syntax-operator: #f92672;
-  --theme-syntax-punctuation: #f8f8f2;
-}
-
-[data-theme="nord"][data-dark="false"] {
-  --theme-primary: #5e81ac;
-  --theme-secondary: #81a1c1;
-  --theme-accent: #8fbcbb;
-  --theme-error: #bf616a;
-  --theme-warning: #d08770;
-  --theme-success: #a3be8c;
-  --theme-info: #5e81ac;
-  --theme-text: #2e3440;
-  --theme-text-muted: #3b4252;
-  --theme-background: #eceff4;
-  --theme-background-panel: #e5e9f0;
-  --theme-background-element: #d8dee9;
-  --theme-border: #4c566a;
-  --theme-border-active: #434c5e;
-  --theme-border-subtle: #4c566a;
-  --theme-diff-added: #a3be8c;
-  --theme-diff-removed: #bf616a;
-  --theme-diff-context: #4c566a;
-  --theme-diff-hunk-header: #4c566a;
-  --theme-diff-highlight-added: #a3be8c;
-  --theme-diff-highlight-removed: #bf616a;
-  --theme-diff-added-bg: #e5e9f0;
-  --theme-diff-removed-bg: #e5e9f0;
-  --theme-diff-context-bg: #e5e9f0;
-  --theme-diff-line-number: #d8dee9;
-  --theme-diff-added-line-number-bg: #e5e9f0;
-  --theme-diff-removed-line-number-bg: #e5e9f0;
-  --theme-markdown-text: #2e3440;
-  --theme-markdown-heading: #5e81ac;
-  --theme-markdown-link: #81a1c1;
-  --theme-markdown-link-text: #8fbcbb;
-  --theme-markdown-code: #a3be8c;
-  --theme-markdown-block-quote: #4c566a;
-  --theme-markdown-emph: #d08770;
-  --theme-markdown-strong: #ebcb8b;
-  --theme-markdown-horizontal-rule: #4c566a;
-  --theme-markdown-list-item: #5e81ac;
-  --theme-markdown-list-enumeration: #8fbcbb;
-  --theme-markdown-image: #81a1c1;
-  --theme-markdown-image-text: #8fbcbb;
-  --theme-markdown-code-block: #2e3440;
-  --theme-syntax-comment: #4c566a;
-  --theme-syntax-keyword: #81a1c1;
-  --theme-syntax-function: #88c0d0;
-  --theme-syntax-variable: #8fbcbb;
-  --theme-syntax-string: #a3be8c;
-  --theme-syntax-number: #b48ead;
-  --theme-syntax-type: #8fbcbb;
-  --theme-syntax-operator: #81a1c1;
-  --theme-syntax-punctuation: #2e3440;
-}
-
-[data-theme="nord"][data-dark="true"] {
-  --theme-primary: #88c0d0;
-  --theme-secondary: #81a1c1;
-  --theme-accent: #8fbcbb;
-  --theme-error: #bf616a;
-  --theme-warning: #d08770;
-  --theme-success: #a3be8c;
-  --theme-info: #88c0d0;
-  --theme-text: #eceff4;
-  --theme-text-muted: #8b95a7;
-  --theme-background: #2e3440;
-  --theme-background-panel: #3b4252;
-  --theme-background-element: #434c5e;
-  --theme-border: #434c5e;
-  --theme-border-active: #4c566a;
-  --theme-border-subtle: #434c5e;
-  --theme-diff-added: #a3be8c;
-  --theme-diff-removed: #bf616a;
-  --theme-diff-context: #8b95a7;
-  --theme-diff-hunk-header: #8b95a7;
-  --theme-diff-highlight-added: #a3be8c;
-  --theme-diff-highlight-removed: #bf616a;
-  --theme-diff-added-bg: #3b4252;
-  --theme-diff-removed-bg: #3b4252;
-  --theme-diff-context-bg: #3b4252;
-  --theme-diff-line-number: #434c5e;
-  --theme-diff-added-line-number-bg: #3b4252;
-  --theme-diff-removed-line-number-bg: #3b4252;
-  --theme-markdown-text: #d8dee9;
-  --theme-markdown-heading: #88c0d0;
-  --theme-markdown-link: #81a1c1;
-  --theme-markdown-link-text: #8fbcbb;
-  --theme-markdown-code: #a3be8c;
-  --theme-markdown-block-quote: #8b95a7;
-  --theme-markdown-emph: #d08770;
-  --theme-markdown-strong: #ebcb8b;
-  --theme-markdown-horizontal-rule: #8b95a7;
-  --theme-markdown-list-item: #88c0d0;
-  --theme-markdown-list-enumeration: #8fbcbb;
-  --theme-markdown-image: #81a1c1;
-  --theme-markdown-image-text: #8fbcbb;
-  --theme-markdown-code-block: #d8dee9;
-  --theme-syntax-comment: #8b95a7;
-  --theme-syntax-keyword: #81a1c1;
-  --theme-syntax-function: #88c0d0;
-  --theme-syntax-variable: #8fbcbb;
-  --theme-syntax-string: #a3be8c;
-  --theme-syntax-number: #b48ead;
-  --theme-syntax-type: #8fbcbb;
-  --theme-syntax-operator: #81a1c1;
-  --theme-syntax-punctuation: #d8dee9;
-}
-
-[data-theme="one-dark"][data-dark="false"] {
-  --theme-primary: #4078f2;
-  --theme-secondary: #a626a4;
-  --theme-accent: #0184bc;
-  --theme-error: #e45649;
-  --theme-warning: #c18401;
-  --theme-success: #50a14f;
-  --theme-info: #986801;
-  --theme-text: #383a42;
-  --theme-text-muted: #a0a1a7;
-  --theme-background: #fafafa;
-  --theme-background-panel: #f0f0f1;
-  --theme-background-element: #eaeaeb;
-  --theme-border: #d1d1d2;
-  --theme-border-active: #4078f2;
-  --theme-border-subtle: #e0e0e1;
-  --theme-diff-added: #50a14f;
-  --theme-diff-removed: #e45649;
-  --theme-diff-context: #a0a1a7;
-  --theme-diff-hunk-header: #0184bc;
-  --theme-diff-highlight-added: #489447;
-  --theme-diff-highlight-removed: #d65145;
-  --theme-diff-added-bg: #eafbe9;
-  --theme-diff-removed-bg: #fce9e8;
-  --theme-diff-context-bg: #f0f0f1;
-  --theme-diff-line-number: #c9c9ca;
-  --theme-diff-added-line-number-bg: #e1f3df;
-  --theme-diff-removed-line-number-bg: #f5e2e1;
-  --theme-markdown-text: #383a42;
-  --theme-markdown-heading: #a626a4;
-  --theme-markdown-link: #4078f2;
-  --theme-markdown-link-text: #0184bc;
-  --theme-markdown-code: #50a14f;
-  --theme-markdown-block-quote: #a0a1a7;
-  --theme-markdown-emph: #c18401;
-  --theme-markdown-strong: #986801;
-  --theme-markdown-horizontal-rule: #a0a1a7;
-  --theme-markdown-list-item: #4078f2;
-  --theme-markdown-list-enumeration: #0184bc;
-  --theme-markdown-image: #4078f2;
-  --theme-markdown-image-text: #0184bc;
-  --theme-markdown-code-block: #383a42;
-  --theme-syntax-comment: #a0a1a7;
-  --theme-syntax-keyword: #a626a4;
-  --theme-syntax-function: #4078f2;
-  --theme-syntax-variable: #e45649;
-  --theme-syntax-string: #50a14f;
-  --theme-syntax-number: #986801;
-  --theme-syntax-type: #c18401;
-  --theme-syntax-operator: #0184bc;
-  --theme-syntax-punctuation: #383a42;
-}
-
-[data-theme="one-dark"][data-dark="true"] {
-  --theme-primary: #61afef;
-  --theme-secondary: #c678dd;
-  --theme-accent: #56b6c2;
-  --theme-error: #e06c75;
-  --theme-warning: #e5c07b;
-  --theme-success: #98c379;
-  --theme-info: #d19a66;
-  --theme-text: #abb2bf;
-  --theme-text-muted: #5c6370;
-  --theme-background: #282c34;
-  --theme-background-panel: #21252b;
-  --theme-background-element: #353b45;
-  --theme-border: #393f4a;
-  --theme-border-active: #61afef;
-  --theme-border-subtle: #2c313a;
-  --theme-diff-added: #98c379;
-  --theme-diff-removed: #e06c75;
-  --theme-diff-context: #5c6370;
-  --theme-diff-hunk-header: #56b6c2;
-  --theme-diff-highlight-added: #aad482;
-  --theme-diff-highlight-removed: #e8828b;
-  --theme-diff-added-bg: #2c382b;
-  --theme-diff-removed-bg: #3a2d2f;
-  --theme-diff-context-bg: #21252b;
-  --theme-diff-line-number: #495162;
-  --theme-diff-added-line-number-bg: #283427;
-  --theme-diff-removed-line-number-bg: #36292b;
-  --theme-markdown-text: #abb2bf;
-  --theme-markdown-heading: #c678dd;
-  --theme-markdown-link: #61afef;
-  --theme-markdown-link-text: #56b6c2;
-  --theme-markdown-code: #98c379;
-  --theme-markdown-block-quote: #5c6370;
-  --theme-markdown-emph: #e5c07b;
-  --theme-markdown-strong: #d19a66;
-  --theme-markdown-horizontal-rule: #5c6370;
-  --theme-markdown-list-item: #61afef;
-  --theme-markdown-list-enumeration: #56b6c2;
-  --theme-markdown-image: #61afef;
-  --theme-markdown-image-text: #56b6c2;
-  --theme-markdown-code-block: #abb2bf;
-  --theme-syntax-comment: #5c6370;
-  --theme-syntax-keyword: #c678dd;
-  --theme-syntax-function: #61afef;
-  --theme-syntax-variable: #e06c75;
-  --theme-syntax-string: #98c379;
-  --theme-syntax-number: #d19a66;
-  --theme-syntax-type: #e5c07b;
-  --theme-syntax-operator: #56b6c2;
-  --theme-syntax-punctuation: #abb2bf;
-}
-
-[data-theme="opencode"][data-dark="false"] {
-  --theme-primary: #3b7dd8;
-  --theme-secondary: #7b5bb6;
-  --theme-accent: #d68c27;
-  --theme-error: #d1383d;
-  --theme-warning: #d68c27;
-  --theme-success: #3d9a57;
-  --theme-info: #318795;
-  --theme-text: #1a1a1a;
-  --theme-text-muted: #8a8a8a;
-  --theme-background: #ffffff;
-  --theme-background-panel: #fafafa;
-  --theme-background-element: #f5f5f5;
-  --theme-border: #b8b8b8;
-  --theme-border-active: #a0a0a0;
-  --theme-border-subtle: #d4d4d4;
-  --theme-diff-added: #1e725c;
-  --theme-diff-removed: #c53b53;
-  --theme-diff-context: #7086b5;
-  --theme-diff-hunk-header: #7086b5;
-  --theme-diff-highlight-added: #4db380;
-  --theme-diff-highlight-removed: #f52a65;
-  --theme-diff-added-bg: #d5e5d5;
-  --theme-diff-removed-bg: #f7d8db;
-  --theme-diff-context-bg: #fafafa;
-  --theme-diff-line-number: #f5f5f5;
-  --theme-diff-added-line-number-bg: #c5d5c5;
-  --theme-diff-removed-line-number-bg: #e7c8cb;
-  --theme-markdown-text: #1a1a1a;
-  --theme-markdown-heading: #d68c27;
-  --theme-markdown-link: #3b7dd8;
-  --theme-markdown-link-text: #318795;
-  --theme-markdown-code: #3d9a57;
-  --theme-markdown-block-quote: #b0851f;
-  --theme-markdown-emph: #b0851f;
-  --theme-markdown-strong: #d68c27;
-  --theme-markdown-horizontal-rule: #8a8a8a;
-  --theme-markdown-list-item: #3b7dd8;
-  --theme-markdown-list-enumeration: #318795;
-  --theme-markdown-image: #3b7dd8;
-  --theme-markdown-image-text: #318795;
-  --theme-markdown-code-block: #1a1a1a;
-  --theme-syntax-comment: #8a8a8a;
-  --theme-syntax-keyword: #d68c27;
-  --theme-syntax-function: #3b7dd8;
-  --theme-syntax-variable: #d1383d;
-  --theme-syntax-string: #3d9a57;
-  --theme-syntax-number: #d68c27;
-  --theme-syntax-type: #b0851f;
-  --theme-syntax-operator: #318795;
-  --theme-syntax-punctuation: #1a1a1a;
-}
-
-[data-theme="opencode"][data-dark="true"] {
-  --theme-primary: #fab283;
-  --theme-secondary: #5c9cf5;
-  --theme-accent: #9d7cd8;
-  --theme-error: #e06c75;
-  --theme-warning: #f5a742;
-  --theme-success: #7fd88f;
-  --theme-info: #56b6c2;
-  --theme-text: #eeeeee;
-  --theme-text-muted: #808080;
-  --theme-background: #0a0a0a;
-  --theme-background-panel: #141414;
-  --theme-background-element: #1e1e1e;
-  --theme-border: #484848;
-  --theme-border-active: #606060;
-  --theme-border-subtle: #3c3c3c;
-  --theme-diff-added: #4fd6be;
-  --theme-diff-removed: #c53b53;
-  --theme-diff-context: #828bb8;
-  --theme-diff-hunk-header: #828bb8;
-  --theme-diff-highlight-added: #b8db87;
-  --theme-diff-highlight-removed: #e26a75;
-  --theme-diff-added-bg: #20303b;
-  --theme-diff-removed-bg: #37222c;
-  --theme-diff-context-bg: #141414;
-  --theme-diff-line-number: #1e1e1e;
-  --theme-diff-added-line-number-bg: #1b2b34;
-  --theme-diff-removed-line-number-bg: #2d1f26;
-  --theme-markdown-text: #eeeeee;
-  --theme-markdown-heading: #9d7cd8;
-  --theme-markdown-link: #fab283;
-  --theme-markdown-link-text: #56b6c2;
-  --theme-markdown-code: #7fd88f;
-  --theme-markdown-block-quote: #e5c07b;
-  --theme-markdown-emph: #e5c07b;
-  --theme-markdown-strong: #f5a742;
-  --theme-markdown-horizontal-rule: #808080;
-  --theme-markdown-list-item: #fab283;
-  --theme-markdown-list-enumeration: #56b6c2;
-  --theme-markdown-image: #fab283;
-  --theme-markdown-image-text: #56b6c2;
-  --theme-markdown-code-block: #eeeeee;
-  --theme-syntax-comment: #808080;
-  --theme-syntax-keyword: #9d7cd8;
-  --theme-syntax-function: #fab283;
-  --theme-syntax-variable: #e06c75;
-  --theme-syntax-string: #7fd88f;
-  --theme-syntax-number: #f5a742;
-  --theme-syntax-type: #e5c07b;
-  --theme-syntax-operator: #56b6c2;
-  --theme-syntax-punctuation: #eeeeee;
-}
-
-[data-theme="palenight"][data-dark="false"] {
-  --theme-primary: #4976eb;
-  --theme-secondary: #a854f2;
-  --theme-accent: #00acc1;
-  --theme-error: #e53935;
-  --theme-warning: #ffb300;
-  --theme-success: #91b859;
-  --theme-info: #f4511e;
-  --theme-text: #292d3e;
-  --theme-text-muted: #8796b0;
-  --theme-background: #fafafa;
-  --theme-background-panel: #f5f5f5;
-  --theme-background-element: #e7e7e8;
-  --theme-border: #e0e0e0;
-  --theme-border-active: #4976eb;
-  --theme-border-subtle: #eeeeee;
-  --theme-diff-added: #91b859;
-  --theme-diff-removed: #e53935;
-  --theme-diff-context: #8796b0;
-  --theme-diff-hunk-header: #00acc1;
-  --theme-diff-highlight-added: #91b859;
-  --theme-diff-highlight-removed: #e53935;
-  --theme-diff-added-bg: #e8f5e9;
-  --theme-diff-removed-bg: #ffebee;
-  --theme-diff-context-bg: #f5f5f5;
-  --theme-diff-line-number: #cfd8dc;
-  --theme-diff-added-line-number-bg: #e8f5e9;
-  --theme-diff-removed-line-number-bg: #ffebee;
-  --theme-markdown-text: #292d3e;
-  --theme-markdown-heading: #a854f2;
-  --theme-markdown-link: #4976eb;
-  --theme-markdown-link-text: #00acc1;
-  --theme-markdown-code: #91b859;
-  --theme-markdown-block-quote: #8796b0;
-  --theme-markdown-emph: #ffb300;
-  --theme-markdown-strong: #f4511e;
-  --theme-markdown-horizontal-rule: #8796b0;
-  --theme-markdown-list-item: #4976eb;
-  --theme-markdown-list-enumeration: #00acc1;
-  --theme-markdown-image: #4976eb;
-  --theme-markdown-image-text: #00acc1;
-  --theme-markdown-code-block: #292d3e;
-  --theme-syntax-comment: #8796b0;
-  --theme-syntax-keyword: #a854f2;
-  --theme-syntax-function: #4976eb;
-  --theme-syntax-variable: #292d3e;
-  --theme-syntax-string: #91b859;
-  --theme-syntax-number: #f4511e;
-  --theme-syntax-type: #ffb300;
-  --theme-syntax-operator: #00acc1;
-  --theme-syntax-punctuation: #292d3e;
-}
-
-[data-theme="palenight"][data-dark="true"] {
-  --theme-primary: #82aaff;
-  --theme-secondary: #c792ea;
-  --theme-accent: #89ddff;
-  --theme-error: #f07178;
-  --theme-warning: #ffcb6b;
-  --theme-success: #c3e88d;
-  --theme-info: #f78c6c;
-  --theme-text: #a6accd;
-  --theme-text-muted: #676e95;
-  --theme-background: #292d3e;
-  --theme-background-panel: #1e2132;
-  --theme-background-element: #32364a;
-  --theme-border: #32364a;
-  --theme-border-active: #82aaff;
-  --theme-border-subtle: #1e2132;
-  --theme-diff-added: #c3e88d;
-  --theme-diff-removed: #f07178;
-  --theme-diff-context: #676e95;
-  --theme-diff-hunk-header: #89ddff;
-  --theme-diff-highlight-added: #c3e88d;
-  --theme-diff-highlight-removed: #f07178;
-  --theme-diff-added-bg: #2e3c2b;
-  --theme-diff-removed-bg: #3c2b2b;
-  --theme-diff-context-bg: #1e2132;
-  --theme-diff-line-number: #444760;
-  --theme-diff-added-line-number-bg: #2e3c2b;
-  --theme-diff-removed-line-number-bg: #3c2b2b;
-  --theme-markdown-text: #a6accd;
-  --theme-markdown-heading: #c792ea;
-  --theme-markdown-link: #82aaff;
-  --theme-markdown-link-text: #89ddff;
-  --theme-markdown-code: #c3e88d;
-  --theme-markdown-block-quote: #676e95;
-  --theme-markdown-emph: #ffcb6b;
-  --theme-markdown-strong: #f78c6c;
-  --theme-markdown-horizontal-rule: #676e95;
-  --theme-markdown-list-item: #82aaff;
-  --theme-markdown-list-enumeration: #89ddff;
-  --theme-markdown-image: #82aaff;
-  --theme-markdown-image-text: #89ddff;
-  --theme-markdown-code-block: #a6accd;
-  --theme-syntax-comment: #676e95;
-  --theme-syntax-keyword: #c792ea;
-  --theme-syntax-function: #82aaff;
-  --theme-syntax-variable: #a6accd;
-  --theme-syntax-string: #c3e88d;
-  --theme-syntax-number: #f78c6c;
-  --theme-syntax-type: #ffcb6b;
-  --theme-syntax-operator: #89ddff;
-  --theme-syntax-punctuation: #a6accd;
-}
-
-[data-theme="rosepine"][data-dark="false"] {
-  --theme-primary: #31748f;
-  --theme-secondary: #907aa9;
-  --theme-accent: #d7827e;
-  --theme-error: #b4637a;
-  --theme-warning: #ea9d34;
-  --theme-success: #286983;
-  --theme-info: #56949f;
-  --theme-text: #575279;
-  --theme-text-muted: #9893a5;
-  --theme-background: #faf4ed;
-  --theme-background-panel: #fffaf3;
-  --theme-background-element: #f2e9e1;
-  --theme-border: #dfdad9;
-  --theme-border-active: #31748f;
-  --theme-border-subtle: #f4ede8;
-  --theme-diff-added: #286983;
-  --theme-diff-removed: #b4637a;
-  --theme-diff-context: #9893a5;
-  --theme-diff-hunk-header: #907aa9;
-  --theme-diff-highlight-added: #286983;
-  --theme-diff-highlight-removed: #b4637a;
-  --theme-diff-added-bg: #e5f2f3;
-  --theme-diff-removed-bg: #fce5e8;
-  --theme-diff-context-bg: #fffaf3;
-  --theme-diff-line-number: #9893a5;
-  --theme-diff-added-line-number-bg: #e5f2f3;
-  --theme-diff-removed-line-number-bg: #fce5e8;
-  --theme-markdown-text: #575279;
-  --theme-markdown-heading: #907aa9;
-  --theme-markdown-link: #31748f;
-  --theme-markdown-link-text: #d7827e;
-  --theme-markdown-code: #286983;
-  --theme-markdown-block-quote: #9893a5;
-  --theme-markdown-emph: #ea9d34;
-  --theme-markdown-strong: #b4637a;
-  --theme-markdown-horizontal-rule: #dfdad9;
-  --theme-markdown-list-item: #31748f;
-  --theme-markdown-list-enumeration: #d7827e;
-  --theme-markdown-image: #31748f;
-  --theme-markdown-image-text: #d7827e;
-  --theme-markdown-code-block: #575279;
-  --theme-syntax-comment: #9893a5;
-  --theme-syntax-keyword: #286983;
-  --theme-syntax-function: #d7827e;
-  --theme-syntax-variable: #575279;
-  --theme-syntax-string: #ea9d34;
-  --theme-syntax-number: #907aa9;
-  --theme-syntax-type: #56949f;
-  --theme-syntax-operator: #797593;
-  --theme-syntax-punctuation: #797593;
-}
-
-[data-theme="rosepine"][data-dark="true"] {
-  --theme-primary: #9ccfd8;
-  --theme-secondary: #c4a7e7;
-  --theme-accent: #ebbcba;
-  --theme-error: #eb6f92;
-  --theme-warning: #f6c177;
-  --theme-success: #31748f;
-  --theme-info: #9ccfd8;
-  --theme-text: #e0def4;
-  --theme-text-muted: #6e6a86;
-  --theme-background: #191724;
-  --theme-background-panel: #1f1d2e;
-  --theme-background-element: #26233a;
-  --theme-border: #403d52;
-  --theme-border-active: #9ccfd8;
-  --theme-border-subtle: #21202e;
-  --theme-diff-added: #31748f;
-  --theme-diff-removed: #eb6f92;
-  --theme-diff-context: #6e6a86;
-  --theme-diff-hunk-header: #c4a7e7;
-  --theme-diff-highlight-added: #31748f;
-  --theme-diff-highlight-removed: #eb6f92;
-  --theme-diff-added-bg: #1f2d3a;
-  --theme-diff-removed-bg: #3a1f2d;
-  --theme-diff-context-bg: #1f1d2e;
-  --theme-diff-line-number: #6e6a86;
-  --theme-diff-added-line-number-bg: #1f2d3a;
-  --theme-diff-removed-line-number-bg: #3a1f2d;
-  --theme-markdown-text: #e0def4;
-  --theme-markdown-heading: #c4a7e7;
-  --theme-markdown-link: #9ccfd8;
-  --theme-markdown-link-text: #ebbcba;
-  --theme-markdown-code: #31748f;
-  --theme-markdown-block-quote: #6e6a86;
-  --theme-markdown-emph: #f6c177;
-  --theme-markdown-strong: #eb6f92;
-  --theme-markdown-horizontal-rule: #403d52;
-  --theme-markdown-list-item: #9ccfd8;
-  --theme-markdown-list-enumeration: #ebbcba;
-  --theme-markdown-image: #9ccfd8;
-  --theme-markdown-image-text: #ebbcba;
-  --theme-markdown-code-block: #e0def4;
-  --theme-syntax-comment: #6e6a86;
-  --theme-syntax-keyword: #31748f;
-  --theme-syntax-function: #ebbcba;
-  --theme-syntax-variable: #e0def4;
-  --theme-syntax-string: #f6c177;
-  --theme-syntax-number: #c4a7e7;
-  --theme-syntax-type: #9ccfd8;
-  --theme-syntax-operator: #908caa;
-  --theme-syntax-punctuation: #908caa;
-}
-
-[data-theme="solarized"][data-dark="false"] {
-  --theme-primary: #268bd2;
-  --theme-secondary: #6c71c4;
-  --theme-accent: #2aa198;
-  --theme-error: #dc322f;
-  --theme-warning: #b58900;
-  --theme-success: #859900;
-  --theme-info: #cb4b16;
-  --theme-text: #657b83;
-  --theme-text-muted: #93a1a1;
-  --theme-background: #fdf6e3;
-  --theme-background-panel: #eee8d5;
-  --theme-background-element: #eee8d5;
-  --theme-border: #eee8d5;
-  --theme-border-active: #93a1a1;
-  --theme-border-subtle: #eee8d5;
-  --theme-diff-added: #859900;
-  --theme-diff-removed: #dc322f;
-  --theme-diff-context: #93a1a1;
-  --theme-diff-hunk-header: #93a1a1;
-  --theme-diff-highlight-added: #859900;
-  --theme-diff-highlight-removed: #dc322f;
-  --theme-diff-added-bg: #eee8d5;
-  --theme-diff-removed-bg: #eee8d5;
-  --theme-diff-context-bg: #eee8d5;
-  --theme-diff-line-number: #93a1a1;
-  --theme-diff-added-line-number-bg: #eee8d5;
-  --theme-diff-removed-line-number-bg: #eee8d5;
-  --theme-markdown-text: #657b83;
-  --theme-markdown-heading: #268bd2;
-  --theme-markdown-link: #2aa198;
-  --theme-markdown-link-text: #6c71c4;
-  --theme-markdown-code: #859900;
-  --theme-markdown-block-quote: #93a1a1;
-  --theme-markdown-emph: #b58900;
-  --theme-markdown-strong: #cb4b16;
-  --theme-markdown-horizontal-rule: #93a1a1;
-  --theme-markdown-list-item: #268bd2;
-  --theme-markdown-list-enumeration: #2aa198;
-  --theme-markdown-image: #2aa198;
-  --theme-markdown-image-text: #6c71c4;
-  --theme-markdown-code-block: #657b83;
-  --theme-syntax-comment: #93a1a1;
-  --theme-syntax-keyword: #859900;
-  --theme-syntax-function: #268bd2;
-  --theme-syntax-variable: #2aa198;
-  --theme-syntax-string: #2aa198;
-  --theme-syntax-number: #d33682;
-  --theme-syntax-type: #b58900;
-  --theme-syntax-operator: #859900;
-  --theme-syntax-punctuation: #657b83;
-}
-
-[data-theme="solarized"][data-dark="true"] {
-  --theme-primary: #268bd2;
-  --theme-secondary: #6c71c4;
-  --theme-accent: #2aa198;
-  --theme-error: #dc322f;
-  --theme-warning: #b58900;
-  --theme-success: #859900;
-  --theme-info: #cb4b16;
-  --theme-text: #839496;
-  --theme-text-muted: #586e75;
-  --theme-background: #002b36;
-  --theme-background-panel: #073642;
-  --theme-background-element: #073642;
-  --theme-border: #073642;
-  --theme-border-active: #586e75;
-  --theme-border-subtle: #073642;
-  --theme-diff-added: #859900;
-  --theme-diff-removed: #dc322f;
-  --theme-diff-context: #586e75;
-  --theme-diff-hunk-header: #586e75;
-  --theme-diff-highlight-added: #859900;
-  --theme-diff-highlight-removed: #dc322f;
-  --theme-diff-added-bg: #073642;
-  --theme-diff-removed-bg: #073642;
-  --theme-diff-context-bg: #073642;
-  --theme-diff-line-number: #586e75;
-  --theme-diff-added-line-number-bg: #073642;
-  --theme-diff-removed-line-number-bg: #073642;
-  --theme-markdown-text: #839496;
-  --theme-markdown-heading: #268bd2;
-  --theme-markdown-link: #2aa198;
-  --theme-markdown-link-text: #6c71c4;
-  --theme-markdown-code: #859900;
-  --theme-markdown-block-quote: #586e75;
-  --theme-markdown-emph: #b58900;
-  --theme-markdown-strong: #cb4b16;
-  --theme-markdown-horizontal-rule: #586e75;
-  --theme-markdown-list-item: #268bd2;
-  --theme-markdown-list-enumeration: #2aa198;
-  --theme-markdown-image: #2aa198;
-  --theme-markdown-image-text: #6c71c4;
-  --theme-markdown-code-block: #839496;
-  --theme-syntax-comment: #586e75;
-  --theme-syntax-keyword: #859900;
-  --theme-syntax-function: #268bd2;
-  --theme-syntax-variable: #2aa198;
-  --theme-syntax-string: #2aa198;
-  --theme-syntax-number: #d33682;
-  --theme-syntax-type: #b58900;
-  --theme-syntax-operator: #859900;
-  --theme-syntax-punctuation: #839496;
-}
-
-[data-theme="synthwave84"][data-dark="false"] {
-  --theme-primary: #00bcd4;
-  --theme-secondary: #e91e63;
-  --theme-accent: #9c27b0;
-  --theme-error: #f44336;
-  --theme-warning: #ff9800;
-  --theme-success: #4caf50;
-  --theme-info: #ff5722;
-  --theme-text: #262335;
-  --theme-text-muted: #5c5c8a;
-  --theme-background: #fafafa;
-  --theme-background-panel: #f5f5f5;
-  --theme-background-element: #eeeeee;
-  --theme-border: #e0e0e0;
-  --theme-border-active: #00bcd4;
-  --theme-border-subtle: #f0f0f0;
-  --theme-diff-added: #4caf50;
-  --theme-diff-removed: #f44336;
-  --theme-diff-context: #5c5c8a;
-  --theme-diff-hunk-header: #9c27b0;
-  --theme-diff-highlight-added: #4caf50;
-  --theme-diff-highlight-removed: #f44336;
-  --theme-diff-added-bg: #e8f5e9;
-  --theme-diff-removed-bg: #ffebee;
-  --theme-diff-context-bg: #f5f5f5;
-  --theme-diff-line-number: #b0b0b0;
-  --theme-diff-added-line-number-bg: #e8f5e9;
-  --theme-diff-removed-line-number-bg: #ffebee;
-  --theme-markdown-text: #262335;
-  --theme-markdown-heading: #e91e63;
-  --theme-markdown-link: #00bcd4;
-  --theme-markdown-link-text: #9c27b0;
-  --theme-markdown-code: #4caf50;
-  --theme-markdown-block-quote: #5c5c8a;
-  --theme-markdown-emph: #ff9800;
-  --theme-markdown-strong: #ff5722;
-  --theme-markdown-horizontal-rule: #e0e0e0;
-  --theme-markdown-list-item: #00bcd4;
-  --theme-markdown-list-enumeration: #9c27b0;
-  --theme-markdown-image: #00bcd4;
-  --theme-markdown-image-text: #9c27b0;
-  --theme-markdown-code-block: #262335;
-  --theme-syntax-comment: #5c5c8a;
-  --theme-syntax-keyword: #e91e63;
-  --theme-syntax-function: #ff5722;
-  --theme-syntax-variable: #262335;
-  --theme-syntax-string: #ff9800;
-  --theme-syntax-number: #9c27b0;
-  --theme-syntax-type: #00bcd4;
-  --theme-syntax-operator: #e91e63;
-  --theme-syntax-punctuation: #262335;
-}
-
-[data-theme="synthwave84"][data-dark="true"] {
-  --theme-primary: #36f9f6;
-  --theme-secondary: #ff7edb;
-  --theme-accent: #b084eb;
-  --theme-error: #fe4450;
-  --theme-warning: #fede5d;
-  --theme-success: #72f1b8;
-  --theme-info: #ff8b39;
-  --theme-text: #ffffff;
-  --theme-text-muted: #848bbd;
-  --theme-background: #262335;
-  --theme-background-panel: #1e1a29;
-  --theme-background-element: #2a2139;
-  --theme-border: #495495;
-  --theme-border-active: #36f9f6;
-  --theme-border-subtle: #241b2f;
-  --theme-diff-added: #72f1b8;
-  --theme-diff-removed: #fe4450;
-  --theme-diff-context: #848bbd;
-  --theme-diff-hunk-header: #b084eb;
-  --theme-diff-highlight-added: #97f1d8;
-  --theme-diff-highlight-removed: #ff5e5b;
-  --theme-diff-added-bg: #1a3a2a;
-  --theme-diff-removed-bg: #3a1a2a;
-  --theme-diff-context-bg: #1e1a29;
-  --theme-diff-line-number: #495495;
-  --theme-diff-added-line-number-bg: #1a3a2a;
-  --theme-diff-removed-line-number-bg: #3a1a2a;
-  --theme-markdown-text: #ffffff;
-  --theme-markdown-heading: #ff7edb;
-  --theme-markdown-link: #36f9f6;
-  --theme-markdown-link-text: #b084eb;
-  --theme-markdown-code: #72f1b8;
-  --theme-markdown-block-quote: #848bbd;
-  --theme-markdown-emph: #fede5d;
-  --theme-markdown-strong: #ff8b39;
-  --theme-markdown-horizontal-rule: #495495;
-  --theme-markdown-list-item: #36f9f6;
-  --theme-markdown-list-enumeration: #b084eb;
-  --theme-markdown-image: #36f9f6;
-  --theme-markdown-image-text: #b084eb;
-  --theme-markdown-code-block: #ffffff;
-  --theme-syntax-comment: #848bbd;
-  --theme-syntax-keyword: #ff7edb;
-  --theme-syntax-function: #ff8b39;
-  --theme-syntax-variable: #ffffff;
-  --theme-syntax-string: #fede5d;
-  --theme-syntax-number: #b084eb;
-  --theme-syntax-type: #36f9f6;
-  --theme-syntax-operator: #ff7edb;
-  --theme-syntax-punctuation: #ffffff;
-}
-
-[data-theme="tokyonight"][data-dark="false"] {
-  --theme-primary: #2e7de9;
-  --theme-secondary: #9854f1;
-  --theme-accent: #b15c00;
-  --theme-error: #f52a65;
-  --theme-warning: #b15c00;
-  --theme-success: #587539;
-  --theme-info: #2e7de9;
-  --theme-text: #3760bf;
-  --theme-text-muted: #8990a3;
-  --theme-background: #e1e2e7;
-  --theme-background-panel: #d5d6db;
-  --theme-background-element: #c8c9ce;
-  --theme-border: #737a8c;
-  --theme-border-active: #5a607d;
-  --theme-border-subtle: #9699a8;
-  --theme-diff-added: #1e725c;
-  --theme-diff-removed: #c53b53;
-  --theme-diff-context: #7086b5;
-  --theme-diff-hunk-header: #7086b5;
-  --theme-diff-highlight-added: #4db380;
-  --theme-diff-highlight-removed: #f52a65;
-  --theme-diff-added-bg: #d5e5d5;
-  --theme-diff-removed-bg: #f7d8db;
-  --theme-diff-context-bg: #d5d6db;
-  --theme-diff-line-number: #c8c9ce;
-  --theme-diff-added-line-number-bg: #c5d5c5;
-  --theme-diff-removed-line-number-bg: #e7c8cb;
-  --theme-markdown-text: #3760bf;
-  --theme-markdown-heading: #9854f1;
-  --theme-markdown-link: #2e7de9;
-  --theme-markdown-link-text: #007197;
-  --theme-markdown-code: #587539;
-  --theme-markdown-block-quote: #8c6c3e;
-  --theme-markdown-emph: #8c6c3e;
-  --theme-markdown-strong: #b15c00;
-  --theme-markdown-horizontal-rule: #8990a3;
-  --theme-markdown-list-item: #2e7de9;
-  --theme-markdown-list-enumeration: #007197;
-  --theme-markdown-image: #2e7de9;
-  --theme-markdown-image-text: #007197;
-  --theme-markdown-code-block: #3760bf;
-  --theme-syntax-comment: #8990a3;
-  --theme-syntax-keyword: #9854f1;
-  --theme-syntax-function: #2e7de9;
-  --theme-syntax-variable: #f52a65;
-  --theme-syntax-string: #587539;
-  --theme-syntax-number: #b15c00;
-  --theme-syntax-type: #8c6c3e;
-  --theme-syntax-operator: #007197;
-  --theme-syntax-punctuation: #3760bf;
-}
-
-[data-theme="tokyonight"][data-dark="true"] {
-  --theme-primary: #82aaff;
-  --theme-secondary: #c099ff;
-  --theme-accent: #ff966c;
-  --theme-error: #ff757f;
-  --theme-warning: #ff966c;
-  --theme-success: #c3e88d;
-  --theme-info: #82aaff;
-  --theme-text: #c8d3f5;
-  --theme-text-muted: #828bb8;
-  --theme-background: #1a1b26;
-  --theme-background-panel: #1e2030;
-  --theme-background-element: #222436;
-  --theme-border: #737aa2;
-  --theme-border-active: #9099b2;
-  --theme-border-subtle: #545c7e;
-  --theme-diff-added: #4fd6be;
-  --theme-diff-removed: #c53b53;
-  --theme-diff-context: #828bb8;
-  --theme-diff-hunk-header: #828bb8;
-  --theme-diff-highlight-added: #b8db87;
-  --theme-diff-highlight-removed: #e26a75;
-  --theme-diff-added-bg: #20303b;
-  --theme-diff-removed-bg: #37222c;
-  --theme-diff-context-bg: #1e2030;
-  --theme-diff-line-number: #222436;
-  --theme-diff-added-line-number-bg: #1b2b34;
-  --theme-diff-removed-line-number-bg: #2d1f26;
-  --theme-markdown-text: #c8d3f5;
-  --theme-markdown-heading: #c099ff;
-  --theme-markdown-link: #82aaff;
-  --theme-markdown-link-text: #86e1fc;
-  --theme-markdown-code: #c3e88d;
-  --theme-markdown-block-quote: #ffc777;
-  --theme-markdown-emph: #ffc777;
-  --theme-markdown-strong: #ff966c;
-  --theme-markdown-horizontal-rule: #828bb8;
-  --theme-markdown-list-item: #82aaff;
-  --theme-markdown-list-enumeration: #86e1fc;
-  --theme-markdown-image: #82aaff;
-  --theme-markdown-image-text: #86e1fc;
-  --theme-markdown-code-block: #c8d3f5;
-  --theme-syntax-comment: #828bb8;
-  --theme-syntax-keyword: #c099ff;
-  --theme-syntax-function: #82aaff;
-  --theme-syntax-variable: #ff757f;
-  --theme-syntax-string: #c3e88d;
-  --theme-syntax-number: #ff966c;
-  --theme-syntax-type: #ffc777;
-  --theme-syntax-operator: #86e1fc;
-  --theme-syntax-punctuation: #c8d3f5;
-}
-
-[data-theme="vesper"][data-dark="false"] {
-  --theme-primary: #ffc799;
-  --theme-secondary: #99ffe4;
-  --theme-accent: #ffc799;
-  --theme-error: #ff8080;
-  --theme-warning: #ffc799;
-  --theme-success: #99ffe4;
-  --theme-info: #ffc799;
-  --theme-text: #101010;
-  --theme-text-muted: #a0a0a0;
-  --theme-background: #fff;
-  --theme-background-panel: #f0f0f0;
-  --theme-background-element: #e0e0e0;
-  --theme-border: #d0d0d0;
-  --theme-border-active: #ffc799;
-  --theme-border-subtle: #e8e8e8;
-  --theme-diff-added: #99ffe4;
-  --theme-diff-removed: #ff8080;
-  --theme-diff-context: #a0a0a0;
-  --theme-diff-hunk-header: #a0a0a0;
-  --theme-diff-highlight-added: #99ffe4;
-  --theme-diff-highlight-removed: #ff8080;
-  --theme-diff-added-bg: #e8f5e8;
-  --theme-diff-removed-bg: #f5e8e8;
-  --theme-diff-context-bg: #f8f8f8;
-  --theme-diff-line-number: #808080;
-  --theme-diff-added-line-number-bg: #e8f5e8;
-  --theme-diff-removed-line-number-bg: #f5e8e8;
-  --theme-markdown-text: #101010;
-  --theme-markdown-heading: #ffc799;
-  --theme-markdown-link: #ffc799;
-  --theme-markdown-link-text: #a0a0a0;
-  --theme-markdown-code: #a0a0a0;
-  --theme-markdown-block-quote: #101010;
-  --theme-markdown-emph: #101010;
-  --theme-markdown-strong: #101010;
-  --theme-markdown-horizontal-rule: #65737e;
-  --theme-markdown-list-item: #101010;
-  --theme-markdown-list-enumeration: #101010;
-  --theme-markdown-image: #ffc799;
-  --theme-markdown-image-text: #a0a0a0;
-  --theme-markdown-code-block: #101010;
-  --theme-syntax-comment: #8b8b8b94;
-  --theme-syntax-keyword: #a0a0a0;
-  --theme-syntax-function: #ffc799;
-  --theme-syntax-variable: #101010;
-  --theme-syntax-string: #99ffe4;
-  --theme-syntax-number: #ffc799;
-  --theme-syntax-type: #ffc799;
-  --theme-syntax-operator: #a0a0a0;
-  --theme-syntax-punctuation: #101010;
-}
-
-[data-theme="vesper"][data-dark="true"] {
-  --theme-primary: #ffc799;
-  --theme-secondary: #99ffe4;
-  --theme-accent: #ffc799;
-  --theme-error: #ff8080;
-  --theme-warning: #ffc799;
-  --theme-success: #99ffe4;
-  --theme-info: #ffc799;
-  --theme-text: #fff;
-  --theme-text-muted: #a0a0a0;
-  --theme-background: #101010;
-  --theme-background-panel: #101010;
-  --theme-background-element: #101010;
-  --theme-border: #282828;
-  --theme-border-active: #ffc799;
-  --theme-border-subtle: #1c1c1c;
-  --theme-diff-added: #99ffe4;
-  --theme-diff-removed: #ff8080;
-  --theme-diff-context: #a0a0a0;
-  --theme-diff-hunk-header: #a0a0a0;
-  --theme-diff-highlight-added: #99ffe4;
-  --theme-diff-highlight-removed: #ff8080;
-  --theme-diff-added-bg: #0d2818;
-  --theme-diff-removed-bg: #281a1a;
-  --theme-diff-context-bg: #101010;
-  --theme-diff-line-number: #505050;
-  --theme-diff-added-line-number-bg: #0d2818;
-  --theme-diff-removed-line-number-bg: #281a1a;
-  --theme-markdown-text: #fff;
-  --theme-markdown-heading: #ffc799;
-  --theme-markdown-link: #ffc799;
-  --theme-markdown-link-text: #a0a0a0;
-  --theme-markdown-code: #a0a0a0;
-  --theme-markdown-block-quote: #fff;
-  --theme-markdown-emph: #fff;
-  --theme-markdown-strong: #fff;
-  --theme-markdown-horizontal-rule: #65737e;
-  --theme-markdown-list-item: #fff;
-  --theme-markdown-list-enumeration: #fff;
-  --theme-markdown-image: #ffc799;
-  --theme-markdown-image-text: #a0a0a0;
-  --theme-markdown-code-block: #fff;
-  --theme-syntax-comment: #8b8b8b94;
-  --theme-syntax-keyword: #a0a0a0;
-  --theme-syntax-function: #ffc799;
-  --theme-syntax-variable: #fff;
-  --theme-syntax-string: #99ffe4;
-  --theme-syntax-number: #ffc799;
-  --theme-syntax-type: #ffc799;
-  --theme-syntax-operator: #a0a0a0;
-  --theme-syntax-punctuation: #fff;
-}
-
-[data-theme="zenburn"][data-dark="false"] {
-  --theme-primary: #5f7f8f;
-  --theme-secondary: #8f5f8f;
-  --theme-accent: #5f8f8f;
-  --theme-error: #8f5f5f;
-  --theme-warning: #8f8f5f;
-  --theme-success: #5f8f5f;
-  --theme-info: #8f7f5f;
-  --theme-text: #3f3f3f;
-  --theme-text-muted: #6f6f6f;
-  --theme-background: #ffffef;
-  --theme-background-panel: #f5f5e5;
-  --theme-background-element: #ebebdb;
-  --theme-border: #d0d0c0;
-  --theme-border-active: #5f7f8f;
-  --theme-border-subtle: #e0e0d0;
-  --theme-diff-added: #5f8f5f;
-  --theme-diff-removed: #8f5f5f;
-  --theme-diff-context: #6f6f6f;
-  --theme-diff-hunk-header: #5f8f8f;
-  --theme-diff-highlight-added: #5f8f5f;
-  --theme-diff-highlight-removed: #8f5f5f;
-  --theme-diff-added-bg: #efffef;
-  --theme-diff-removed-bg: #ffefef;
-  --theme-diff-context-bg: #f5f5e5;
-  --theme-diff-line-number: #b0b0a0;
-  --theme-diff-added-line-number-bg: #efffef;
-  --theme-diff-removed-line-number-bg: #ffefef;
-  --theme-markdown-text: #3f3f3f;
-  --theme-markdown-heading: #8f8f5f;
-  --theme-markdown-link: #5f7f8f;
-  --theme-markdown-link-text: #5f8f8f;
-  --theme-markdown-code: #5f8f5f;
-  --theme-markdown-block-quote: #6f6f6f;
-  --theme-markdown-emph: #8f8f5f;
-  --theme-markdown-strong: #8f7f5f;
-  --theme-markdown-horizontal-rule: #6f6f6f;
-  --theme-markdown-list-item: #5f7f8f;
-  --theme-markdown-list-enumeration: #5f8f8f;
-  --theme-markdown-image: #5f7f8f;
-  --theme-markdown-image-text: #5f8f8f;
-  --theme-markdown-code-block: #3f3f3f;
-  --theme-syntax-comment: #5f7f5f;
-  --theme-syntax-keyword: #8f8f5f;
-  --theme-syntax-function: #5f7f8f;
-  --theme-syntax-variable: #3f3f3f;
-  --theme-syntax-string: #8f5f5f;
-  --theme-syntax-number: #5f8f5f;
-  --theme-syntax-type: #5f8f8f;
-  --theme-syntax-operator: #8f8f5f;
-  --theme-syntax-punctuation: #3f3f3f;
-}
-
-[data-theme="zenburn"][data-dark="true"] {
-  --theme-primary: #8cd0d3;
-  --theme-secondary: #dc8cc3;
-  --theme-accent: #93e0e3;
-  --theme-error: #cc9393;
-  --theme-warning: #f0dfaf;
-  --theme-success: #7f9f7f;
-  --theme-info: #dfaf8f;
-  --theme-text: #dcdccc;
-  --theme-text-muted: #9f9f9f;
-  --theme-background: #3f3f3f;
-  --theme-background-panel: #4f4f4f;
-  --theme-background-element: #5f5f5f;
-  --theme-border: #5f5f5f;
-  --theme-border-active: #8cd0d3;
-  --theme-border-subtle: #4f4f4f;
-  --theme-diff-added: #7f9f7f;
-  --theme-diff-removed: #cc9393;
-  --theme-diff-context: #9f9f9f;
-  --theme-diff-hunk-header: #93e0e3;
-  --theme-diff-highlight-added: #8fb28f;
-  --theme-diff-highlight-removed: #dca3a3;
-  --theme-diff-added-bg: #4f5f4f;
-  --theme-diff-removed-bg: #5f4f4f;
-  --theme-diff-context-bg: #4f4f4f;
-  --theme-diff-line-number: #6f6f6f;
-  --theme-diff-added-line-number-bg: #4f5f4f;
-  --theme-diff-removed-line-number-bg: #5f4f4f;
-  --theme-markdown-text: #dcdccc;
-  --theme-markdown-heading: #f0dfaf;
-  --theme-markdown-link: #8cd0d3;
-  --theme-markdown-link-text: #93e0e3;
-  --theme-markdown-code: #7f9f7f;
-  --theme-markdown-block-quote: #9f9f9f;
-  --theme-markdown-emph: #e0cf9f;
-  --theme-markdown-strong: #dfaf8f;
-  --theme-markdown-horizontal-rule: #9f9f9f;
-  --theme-markdown-list-item: #8cd0d3;
-  --theme-markdown-list-enumeration: #93e0e3;
-  --theme-markdown-image: #8cd0d3;
-  --theme-markdown-image-text: #93e0e3;
-  --theme-markdown-code-block: #dcdccc;
-  --theme-syntax-comment: #7f9f7f;
-  --theme-syntax-keyword: #f0dfaf;
-  --theme-syntax-function: #8cd0d3;
-  --theme-syntax-variable: #dcdccc;
-  --theme-syntax-string: #cc9393;
-  --theme-syntax-number: #8fb28f;
-  --theme-syntax-type: #93e0e3;
-  --theme-syntax-operator: #f0dfaf;
-  --theme-syntax-punctuation: #dcdccc;
-}

+ 686 - 1257
packages/app/src/components/code.tsx

@@ -1,15 +1,6 @@
-import { bundledLanguages, codeToHtml, type ShikiTransformer } from "shiki"
-import {
-  createResource,
-  splitProps,
-  Suspense,
-  type ComponentProps,
-  createEffect,
-  onMount,
-  onCleanup,
-  createMemo,
-} from "solid-js"
-import { useLocal } from "@/context"
+import { bundledLanguages, type BundledLanguage, type ShikiTransformer } from "shiki"
+import { splitProps, type ComponentProps, createEffect, onMount, onCleanup, createMemo, createResource } from "solid-js"
+import { useLocal, useShiki } from "@/context"
 import { getFileExtension, getNodeOffsetInLine, getSelectionInContainer } from "@/utils"
 import { getFileExtension, getNodeOffsetInLine, getSelectionInContainer } from "@/utils"
 
 
 interface Props extends ComponentProps<"div"> {
 interface Props extends ComponentProps<"div"> {
@@ -17,1342 +8,780 @@ interface Props extends ComponentProps<"div"> {
   path: string
   path: string
 }
 }
 
 
-function transformerUnifiedDiff(): ShikiTransformer {
-  const kinds = new Map<number, string>()
-  const meta = new Map<number, { old?: number; new?: number; sign?: string }>()
-  let isDiff = false
-
-  return {
-    name: "unified-diff",
-    preprocess(input) {
-      kinds.clear()
-      meta.clear()
-      isDiff = false
-
-      const ls = input.split(/\r?\n/)
-      const out: Array<string> = []
-      let oldNo = 0
-      let newNo = 0
-      let inHunk = false
-
-      for (let i = 0; i < ls.length; i++) {
-        const s = ls[i]
-
-        const m = s.match(/^@@\s*-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s*@@/)
-        if (m) {
-          isDiff = true
-          inHunk = true
-          oldNo = parseInt(m[1], 10)
-          newNo = parseInt(m[3], 10)
-          continue
-        }
-
-        if (
-          /^diff --git /.test(s) ||
-          /^Index: /.test(s) ||
-          /^--- /.test(s) ||
-          /^\+\+\+ /.test(s) ||
-          /^[=]{3,}$/.test(s) ||
-          /^\*{3,}$/.test(s) ||
-          /^\\ No newline at end of file$/.test(s)
-        ) {
-          isDiff = true
-          continue
-        }
-
-        if (!inHunk) {
-          out.push(s)
-          continue
-        }
-
-        if (/^\+/.test(s)) {
-          out.push(s)
-          const ln = out.length
-          kinds.set(ln, "add")
-          meta.set(ln, { new: newNo, sign: "+" })
-          newNo++
-          continue
-        }
-
-        if (/^-/.test(s)) {
-          out.push(s)
-          const ln = out.length
-          kinds.set(ln, "remove")
-          meta.set(ln, { old: oldNo, sign: "-" })
-          oldNo++
-          continue
-        }
+export function Code(props: Props) {
+  const ctx = useLocal()
+  const highlighter = useShiki()
+  const [local, others] = splitProps(props, ["class", "classList", "code", "path"])
+  const lang = createMemo(() => getFileExtension(local.path))
 
 
-        if (/^ /.test(s)) {
-          out.push(s)
-          const ln = out.length
-          kinds.set(ln, "context")
-          meta.set(ln, { old: oldNo, new: newNo })
-          oldNo++
-          newNo++
-          continue
-        }
+  let container: HTMLDivElement | undefined
+  let isProgrammaticSelection = false
 
 
-        // fallback in hunks
-        out.push(s)
-      }
+  const [html] = createResource(async () => {
+    if (!highlighter.getLoadedLanguages().includes(lang())) {
+      await highlighter.loadLanguage(lang() as BundledLanguage)
+    }
+    return highlighter.codeToHtml(local.code || "", {
+      lang: lang() && lang() in bundledLanguages ? lang() : "text",
+      theme: "opencode",
+      transformers: [transformerUnifiedDiff(), transformerDiffGroups()],
+    }) as string
+  })
 
 
-      return out.join("\n")
-    },
-    code(node) {
-      if (isDiff) this.addClassToHast(node, "code-diff")
-    },
-    pre(node) {
-      if (isDiff) this.addClassToHast(node, "code-diff")
-    },
-    line(node, line) {
-      if (!isDiff) return
-      const kind = kinds.get(line)
-      if (!kind) return
+  onMount(() => {
+    if (!container) return
 
 
-      const m = meta.get(line) || {}
+    let ticking = false
+    const onScroll = () => {
+      if (!container) return
+      if (ctx.file.active()?.path !== local.path) return
+      if (ticking) return
+      ticking = true
+      requestAnimationFrame(() => {
+        ticking = false
+        ctx.file.scroll(local.path, container!.scrollTop)
+      })
+    }
 
 
-      this.addClassToHast(node, "diff-line")
-      this.addClassToHast(node, `diff-${kind}`)
-      node.properties = node.properties || {}
-      ;(node.properties as any)["data-diff"] = kind
-      if (m.old != undefined) (node.properties as any)["data-old"] = String(m.old)
-      if (m.new != undefined) (node.properties as any)["data-new"] = String(m.new)
+    const onSelectionChange = () => {
+      if (!container) return
+      if (isProgrammaticSelection) return
+      if (ctx.file.active()?.path !== local.path) return
+      const d = getSelectionInContainer(container)
+      if (!d) return
+      const p = ctx.file.node(local.path)?.selection
+      if (p && p.startLine === d.sl && p.endLine === d.el && p.startChar === d.sch && p.endChar === d.ech) return
+      ctx.file.select(local.path, { startLine: d.sl, startChar: d.sch, endLine: d.el, endChar: d.ech })
+    }
 
 
-      const oldSpan = {
-        type: "element",
-        tagName: "span",
-        properties: { className: ["diff-oldln"] },
-        children: [{ type: "text", value: m.old != undefined ? String(m.old) : " " }],
-      }
-      const newSpan = {
-        type: "element",
-        tagName: "span",
-        properties: { className: ["diff-newln"] },
-        children: [{ type: "text", value: m.new != undefined ? String(m.new) : " " }],
+    const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control"
+    const onKeyDown = (e: KeyboardEvent) => {
+      if (ctx.file.active()?.path !== local.path) return
+      const ae = document.activeElement as HTMLElement | undefined
+      const tag = (ae?.tagName || "").toLowerCase()
+      const inputFocused = !!ae && (tag === "input" || tag === "textarea" || ae.isContentEditable)
+      if (inputFocused) return
+      if (e.getModifierState(MOD) && e.key.toLowerCase() === "a") {
+        e.preventDefault()
+        if (!container) return
+        const element = container.querySelector("code") as HTMLElement | undefined
+        if (!element) return
+        const lines = Array.from(element.querySelectorAll(".line"))
+        if (!lines.length) return
+        const r = document.createRange()
+        const last = lines[lines.length - 1]
+        r.selectNodeContents(last)
+        const lastLen = r.toString().length
+        ctx.file.select(local.path, { startLine: 1, startChar: 0, endLine: lines.length, endChar: lastLen })
       }
       }
+    }
 
 
-      if (kind === "add" || kind === "remove" || kind === "context") {
-        const first = (node.children && (node.children as any[])[0]) as any
-        if (first && first.type === "element" && first.children && first.children.length > 0) {
-          const t = first.children[0]
-          if (t && t.type === "text" && typeof t.value === "string" && t.value.length > 0) {
-            const ch = t.value[0]
-            if (ch === "+" || ch === "-" || ch === " ") t.value = t.value.slice(1)
-          }
-        }
-      }
+    container.addEventListener("scroll", onScroll)
+    document.addEventListener("selectionchange", onSelectionChange)
+    document.addEventListener("keydown", onKeyDown)
 
 
-      const signSpan = {
-        type: "element",
-        tagName: "span",
-        properties: { className: ["diff-sign"] },
-        children: [{ type: "text", value: (m as any).sign || " " }],
-      }
+    onCleanup(() => {
+      container?.removeEventListener("scroll", onScroll)
+      document.removeEventListener("selectionchange", onSelectionChange)
+      document.removeEventListener("keydown", onKeyDown)
+    })
+  })
 
 
-      // @ts-expect-error hast typing across versions
-      node.children = [oldSpan, newSpan, signSpan, ...(node.children || [])]
-    },
-  }
-}
+  // Restore scroll position from store when content is ready
+  createEffect(() => {
+    const content = html()
+    if (!container || !content) return
+    const top = ctx.file.node(local.path)?.scrollTop
+    if (top !== undefined && container.scrollTop !== top) container.scrollTop = top
+  })
 
 
-function transformerDiffGroups(): ShikiTransformer {
-  let group = -1
-  let inGroup = false
-  return {
-    name: "diff-groups",
-    pre() {
-      group = -1
-      inGroup = false
-    },
-    line(node) {
-      const props = (node.properties || {}) as any
-      const kind = props["data-diff"] as string | undefined
-      if (kind === "add" || kind === "remove") {
-        if (!inGroup) {
-          group += 1
-          inGroup = true
-        }
-        ;(node.properties as any)["data-chgrp"] = String(group)
-      } else {
-        inGroup = false
+  // Sync selection from store -> DOM
+  createEffect(() => {
+    const content = html()
+    if (!container || !content) return
+    if (ctx.file.active()?.path !== local.path) return
+    const codeEl = container.querySelector("code") as HTMLElement | undefined
+    if (!codeEl) return
+    const target = ctx.file.node(local.path)?.selection
+    const current = getSelectionInContainer(container)
+    const sel = window.getSelection()
+    if (!sel) return
+    if (!target) {
+      if (current) {
+        isProgrammaticSelection = true
+        sel.removeAllRanges()
+        queueMicrotask(() => {
+          isProgrammaticSelection = false
+        })
       }
       }
-    },
-  }
-}
-
-function applyDiffFolding(
-  root: HTMLElement,
-  context = 3,
-  options?: { expanded?: string[]; onExpand?: (key: string) => void; side?: "left" | "right" },
-) {
-  if (!root.classList.contains("code-diff")) return
-
-  // Cleanup: unwrap previous collapsed blocks and remove toggles
-  const blocks = Array.from(root.querySelectorAll<HTMLElement>(".diff-collapsed-block"))
-  for (const block of blocks) {
-    const p = block.parentNode
-    if (!p) {
-      block.remove()
-      continue
+      return
     }
     }
-    while (block.firstChild) p.insertBefore(block.firstChild, block)
-    block.remove()
-  }
-  const toggles = Array.from(root.querySelectorAll<HTMLElement>(".diff-collapsed"))
-  for (const t of toggles) t.remove()
+    const matches = !!(
+      current &&
+      current.sl === target.startLine &&
+      current.sch === target.startChar &&
+      current.el === target.endLine &&
+      current.ech === target.endChar
+    )
+    if (matches) return
+    const lines = Array.from(codeEl.querySelectorAll(".line"))
+    if (lines.length === 0) return
+    let sIdx = Math.max(0, target.startLine - 1)
+    let eIdx = Math.max(0, target.endLine - 1)
+    let sChar = Math.max(0, target.startChar || 0)
+    let eChar = Math.max(0, target.endChar || 0)
+    if (sIdx > eIdx || (sIdx === eIdx && sChar > eChar)) {
+      const ti = sIdx
+      sIdx = eIdx
+      eIdx = ti
+      const tc = sChar
+      sChar = eChar
+      eChar = tc
+    }
+    if (eChar === 0 && eIdx > sIdx) {
+      eIdx = eIdx - 1
+      eChar = Number.POSITIVE_INFINITY
+    }
+    if (sIdx >= lines.length) return
+    if (eIdx >= lines.length) eIdx = lines.length - 1
+    const s = getNodeOffsetInLine(lines[sIdx], sChar) ?? { node: lines[sIdx], offset: 0 }
+    const e = getNodeOffsetInLine(lines[eIdx], eChar) ?? { node: lines[eIdx], offset: lines[eIdx].childNodes.length }
+    const range = document.createRange()
+    range.setStart(s.node, s.offset)
+    range.setEnd(e.node, e.offset)
+    isProgrammaticSelection = true
+    sel.removeAllRanges()
+    sel.addRange(range)
+    queueMicrotask(() => {
+      isProgrammaticSelection = false
+    })
+  })
 
 
-  const lines = Array.from(root.querySelectorAll<HTMLElement>(".diff-line"))
-  if (lines.length === 0) return
+  // Build/toggle split layout and apply folding (both unified and split)
+  createEffect(() => {
+    const content = html()
+    if (!container || !content) return
+    const view = ctx.file.view(local.path)
 
 
-  const n = lines.length
-  const isChange = lines.map((l) => l.dataset["diff"] === "add" || l.dataset["diff"] === "remove")
-  const isContext = lines.map((l) => l.dataset["diff"] === "context")
-  if (!isChange.some(Boolean)) return
+    const pres = Array.from(container.querySelectorAll<HTMLPreElement>("pre"))
+    if (pres.length === 0) return
+    const originalPre = pres[0]
 
 
-  const visible = new Array(n).fill(false) as boolean[]
-  for (let i = 0; i < n; i++) if (isChange[i]) visible[i] = true
-  for (let i = 0; i < n; i++) {
-    if (isChange[i]) {
-      const s = Math.max(0, i - context)
-      const e = Math.min(n - 1, i + context)
-      for (let j = s; j <= e; j++) if (isContext[j]) visible[j] = true
+    const split = container.querySelector<HTMLElement>(".diff-split")
+    if (view === "diff-split") {
+      applySplitDiff(container)
+      const next = container.querySelector<HTMLElement>(".diff-split")
+      if (next) next.style.display = ""
+      originalPre.style.display = "none"
+    } else {
+      if (split) split.style.display = "none"
+      originalPre.style.display = ""
     }
     }
-  }
 
 
-  type Range = { start: number; end: number }
-  const ranges: Range[] = []
-  let i = 0
-  while (i < n) {
-    if (!visible[i] && isContext[i]) {
-      let j = i
-      while (j + 1 < n && !visible[j + 1] && isContext[j + 1]) j++
-      ranges.push({ start: i, end: j })
-      i = j + 1
+    const expanded = ctx.file.folded(local.path)
+    if (view === "diff-split") {
+      const left = container.querySelector<HTMLElement>(".diff-split pre:nth-child(1) code")
+      const right = container.querySelector<HTMLElement>(".diff-split pre:nth-child(2) code")
+      if (left)
+        applyDiffFolding(left, 3, { expanded, onExpand: (key) => ctx.file.unfold(local.path, key), side: "left" })
+      if (right)
+        applyDiffFolding(right, 3, { expanded, onExpand: (key) => ctx.file.unfold(local.path, key), side: "right" })
     } else {
     } else {
-      i++
+      const code = container.querySelector<HTMLElement>("pre code")
+      if (code)
+        applyDiffFolding(code, 3, {
+          expanded,
+          onExpand: (key) => ctx.file.unfold(local.path, key),
+        })
     }
     }
+  })
+
+  // Highlight groups + scroll coupling
+  const clearHighlights = () => {
+    if (!container) return
+    container.querySelectorAll<HTMLElement>(".diff-selected").forEach((el) => el.classList.remove("diff-selected"))
   }
   }
 
 
-  for (const r of ranges) {
-    const start = lines[r.start]
-    const end = lines[r.end]
-    const count = r.end - r.start + 1
-    const minCollapse = 20
-    if (count < minCollapse) {
-      continue
+  const applyHighlight = (idx: number, scroll?: boolean) => {
+    if (!container) return
+    const view = ctx.file.view(local.path)
+    if (view === "raw") return
+
+    clearHighlights()
+
+    const nodes: HTMLElement[] = []
+    if (view === "diff-split") {
+      const left = container.querySelector<HTMLElement>(".diff-split pre:nth-child(1) code")
+      const right = container.querySelector<HTMLElement>(".diff-split pre:nth-child(2) code")
+      if (left)
+        nodes.push(...Array.from(left.querySelectorAll<HTMLElement>(`[data-chgrp="${idx}"][data-diff="remove"]`)))
+      if (right)
+        nodes.push(...Array.from(right.querySelectorAll<HTMLElement>(`[data-chgrp="${idx}"][data-diff="add"]`)))
+    } else {
+      const code = container.querySelector<HTMLElement>("pre code")
+      if (code) nodes.push(...Array.from(code.querySelectorAll<HTMLElement>(`[data-chgrp="${idx}"]`)))
     }
     }
 
 
-    // Wrap the entire collapsed chunk (including trailing newline) so it takes no space
-    const block = document.createElement("span")
-    block.className = "diff-collapsed-block"
-    start.parentElement?.insertBefore(block, start)
+    for (const n of nodes) n.classList.add("diff-selected")
+    if (scroll && nodes.length) nodes[0].scrollIntoView({ block: "center", behavior: "smooth" })
+  }
 
 
-    let cur: Node | undefined = start
-    while (cur) {
-      const next: Node | undefined = cur.nextSibling || undefined
-      block.appendChild(cur)
-      if (cur === end) {
-        // Also move the newline after the last line into the block
-        if (next && next.nodeType === Node.TEXT_NODE && (next.textContent || "").startsWith("\n")) {
-          block.appendChild(next)
-        }
-        break
-      }
-      cur = next
+  const countGroups = () => {
+    if (!container) return 0
+    const code = container.querySelector<HTMLElement>("pre code")
+    if (!code) return 0
+    const set = new Set<string>()
+    for (const el of Array.from(code.querySelectorAll<HTMLElement>(".diff-line[data-chgrp]"))) {
+      const v = el.getAttribute("data-chgrp")
+      if (v != undefined) set.add(v)
     }
     }
+    return set.size
+  }
 
 
-    block.style.display = "none"
-    const row = document.createElement("span")
-    row.className = "line diff-collapsed"
-    row.setAttribute("data-kind", "collapsed")
-    row.setAttribute("data-count", String(count))
-    row.setAttribute("tabindex", "0")
-    row.setAttribute("role", "button")
+  let lastIdx: number | undefined = undefined
+  let lastView: string | undefined
+  let lastContent: string | undefined
+  let lastRawIdx: number | undefined = undefined
+  createEffect(() => {
+    const content = html()
+    if (!container || !content) return
+    const view = ctx.file.view(local.path)
+    const raw = ctx.file.changeIndex(local.path)
+    if (raw === undefined) return
+    const total = countGroups()
+    if (total <= 0) return
+    const next = ((raw % total) + total) % total
 
 
-    const oldln = document.createElement("span")
-    oldln.className = "diff-oldln"
-    oldln.textContent = " "
+    const navigated = lastRawIdx !== undefined && lastRawIdx !== raw
 
 
-    const newln = document.createElement("span")
-    newln.className = "diff-newln"
-    newln.textContent = " "
+    if (next !== raw) {
+      ctx.file.setChangeIndex(local.path, next)
+      applyHighlight(next, true)
+    } else {
+      if (lastView !== view || lastContent !== content) applyHighlight(next)
+      if ((lastIdx !== undefined && lastIdx !== next) || navigated) applyHighlight(next, true)
+    }
 
 
-    const sign = document.createElement("span")
-    sign.className = "diff-sign"
-    sign.textContent = "…"
+    lastRawIdx = raw
+    lastIdx = next
+    lastView = view
+    lastContent = content
+  })
 
 
-    const label = document.createElement("span")
-    label.textContent = `show ${count} unchanged line${count > 1 ? "s" : ""}`
+  return (
+    <div
+      ref={(el) => {
+        container = el
+      }}
+      innerHTML={html()}
+      class="
+          font-mono text-xs tracking-wide overflow-y-auto no-scrollbar h-full
+          [&]:[counter-reset:line]
+          [&_pre]:focus-visible:outline-none
+          [&_pre]:overflow-x-auto [&_pre]:no-scrollbar
+          [&_code]:min-w-full [&_code]:inline-block [&_code]:pb-40
+          [&_.tab]:relative
+          [&_.tab::before]:content['⇥']
+          [&_.tab::before]:absolute
+          [&_.tab::before]:opacity-0
+          [&_.space]:relative
+          [&_.space::before]:content-['·']
+          [&_.space::before]:absolute
+          [&_.space::before]:opacity-0
+          [&_.line]:inline-block [&_.line]:w-full
+          [&_.line]:hover:bg-background-element
+          [&_.line::before]:sticky [&_.line::before]:left-0
+          [&_.line::before]:w-12 [&_.line::before]:pr-4
+          [&_.line::before]:z-10
+          [&_.line::before]:bg-background-panel
+          [&_.line::before]:text-text-muted/60
+          [&_.line::before]:text-right [&_.line::before]:inline-block
+          [&_.line::before]:select-none
+          [&_.line::before]:[counter-increment:line]
+          [&_.line::before]:content-[counter(line)]
+          [&_code.code-diff_.line::before]:content-['']
+          [&_code.code-diff_.line::before]:w-0
+          [&_code.code-diff_.line::before]:pr-0
+          [&_.diff-split_code.code-diff::before]:w-10
+          [&_.diff-split_.diff-newln]:left-0
+          [&_.diff-oldln]:sticky [&_.diff-oldln]:left-0
+          [&_.diff-oldln]:w-10 [&_.diff-oldln]:pr-2
+          [&_.diff-oldln]:z-40
+          [&_.diff-oldln]:text-text-muted/60
+          [&_.diff-oldln]:text-right [&_.diff-oldln]:inline-block
+          [&_.diff-oldln]:select-none
+          [&_.diff-oldln]:bg-background-panel
+          [&_.diff-newln]:sticky [&_.diff-newln]:left-10
+          [&_.diff-newln]:w-10 [&_.diff-newln]:pr-2
+          [&_.diff-newln]:z-40
+          [&_.diff-newln]:text-text-muted/60
+          [&_.diff-newln]:text-right [&_.diff-newln]:inline-block
+          [&_.diff-newln]:select-none
+          [&_.diff-newln]:bg-background-panel
+          [&_.diff-add]:bg-success/20!
+          [&_.diff-add.diff-selected]:bg-success/50!
+          [&_.diff-add_.diff-oldln]:bg-success!
+          [&_.diff-add_.diff-oldln]:text-background-panel!
+          [&_.diff-add_.diff-newln]:bg-success!
+          [&_.diff-add_.diff-newln]:text-background-panel!
+          [&_.diff-remove]:bg-error/20!
+          [&_.diff-remove.diff-selected]:bg-error/50!
+          [&_.diff-remove_.diff-newln]:bg-error!
+          [&_.diff-remove_.diff-newln]:text-background-panel!
+          [&_.diff-remove_.diff-oldln]:bg-error!
+          [&_.diff-remove_.diff-oldln]:text-background-panel!
+          [&_.diff-sign]:inline-block [&_.diff-sign]:px-2 [&_.diff-sign]:select-none
+          [&_.diff-blank]:bg-background-element
+          [&_.diff-blank_.diff-oldln]:bg-background-element
+          [&_.diff-blank_.diff-newln]:bg-background-element
+          [&_.diff-collapsed]:block! [&_.diff-collapsed]:w-full [&_.diff-collapsed]:relative
+          [&_.diff-collapsed]:cursor-pointer [&_.diff-collapsed]:select-none
+          [&_.diff-collapsed]:bg-info/20 [&_.diff-collapsed]:hover:bg-info/40!
+          [&_.diff-collapsed]:text-info/80 [&_.diff-collapsed]:hover:text-info
+          [&_.diff-collapsed]:text-xs
+          [&_.diff-collapsed_.diff-oldln]:bg-info!
+          [&_.diff-collapsed_.diff-newln]:bg-info!
+        "
+      classList={{
+        ...(local.classList || {}),
+        [local.class ?? ""]: !!local.class,
+      }}
+      {...others}
+    ></div>
+  )
+}
 
 
-    const key = `o${start.dataset["old"] || ""}-${end.dataset["old"] || ""}:n${start.dataset["new"] || ""}-${end.dataset["new"] || ""}`
+function transformerUnifiedDiff(): ShikiTransformer {
+  const kinds = new Map<number, string>()
+  const meta = new Map<number, { old?: number; new?: number; sign?: string }>()
+  let isDiff = false
 
 
-    const show = (record = true) => {
-      if (record) options?.onExpand?.(key)
-      const p = block.parentNode
-      if (p) {
-        while (block.firstChild) p.insertBefore(block.firstChild, block)
-        block.remove()
-      }
-      row.remove()
-    }
+  return {
+    name: "unified-diff",
+    preprocess(input) {
+      kinds.clear()
+      meta.clear()
+      isDiff = false
 
 
-    row.addEventListener("click", () => show(true))
-    row.addEventListener("keydown", (ev) => {
-      if (ev.key === "Enter" || ev.key === " ") {
-        ev.preventDefault()
-        show(true)
-      }
-    })
+      const ls = input.split(/\r?\n/)
+      const out: Array<string> = []
+      let oldNo = 0
+      let newNo = 0
+      let inHunk = false
 
 
-    block.parentElement?.insertBefore(row, block)
-    if (!options?.side || options.side === "left") row.appendChild(oldln)
-    if (!options?.side || options.side === "right") row.appendChild(newln)
-    row.appendChild(sign)
-    row.appendChild(label)
+      for (let i = 0; i < ls.length; i++) {
+        const s = ls[i]
 
 
-    if (options?.expanded && options.expanded.includes(key)) {
-      show(false)
-    }
-  }
-}
+        const m = s.match(/^@@\s*-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s*@@/)
+        if (m) {
+          isDiff = true
+          inHunk = true
+          oldNo = parseInt(m[1], 10)
+          newNo = parseInt(m[3], 10)
+          continue
+        }
 
 
-function applySplitDiff(container: HTMLElement) {
-  const pres = Array.from(container.querySelectorAll<HTMLPreElement>("pre"))
-  if (pres.length === 0) return
-  const originalPre = pres[0]
-  const originalCode = originalPre.querySelector("code") as HTMLElement | undefined
-  if (!originalCode || !originalCode.classList.contains("code-diff")) return
+        if (
+          /^diff --git /.test(s) ||
+          /^Index: /.test(s) ||
+          /^--- /.test(s) ||
+          /^\+\+\+ /.test(s) ||
+          /^[=]{3,}$/.test(s) ||
+          /^\*{3,}$/.test(s) ||
+          /^\\ No newline at end of file$/.test(s)
+        ) {
+          isDiff = true
+          continue
+        }
 
 
-  // Rebuild split each time to match current content
-  const existing = container.querySelector<HTMLElement>(".diff-split")
-  if (existing) existing.remove()
+        if (!inHunk) {
+          out.push(s)
+          continue
+        }
 
 
-  const grid = document.createElement("div")
-  grid.className = "diff-split grid grid-cols-2 gap-x-6"
+        if (/^\+/.test(s)) {
+          out.push(s)
+          const ln = out.length
+          kinds.set(ln, "add")
+          meta.set(ln, { new: newNo, sign: "+" })
+          newNo++
+          continue
+        }
 
 
-  const makeColumn = () => {
-    const pre = document.createElement("pre")
-    pre.className = originalPre.className
-    const code = document.createElement("code")
-    code.className = originalCode.className
-    pre.appendChild(code)
-    return { pre, code }
-  }
+        if (/^-/.test(s)) {
+          out.push(s)
+          const ln = out.length
+          kinds.set(ln, "remove")
+          meta.set(ln, { old: oldNo, sign: "-" })
+          oldNo++
+          continue
+        }
 
 
-  const left = makeColumn()
-  const right = makeColumn()
+        if (/^ /.test(s)) {
+          out.push(s)
+          const ln = out.length
+          kinds.set(ln, "context")
+          meta.set(ln, { old: oldNo, new: newNo })
+          oldNo++
+          newNo++
+          continue
+        }
 
 
-  // Helpers
-  const cloneSide = (line: HTMLElement, side: "old" | "new"): HTMLElement => {
-    const clone = line.cloneNode(true) as HTMLElement
-    const oldln = clone.querySelector(".diff-oldln")
-    const newln = clone.querySelector(".diff-newln")
-    if (side === "old") {
-      if (newln) newln.remove()
-    } else {
-      if (oldln) oldln.remove()
-    }
-    return clone
-  }
+        // fallback in hunks
+        out.push(s)
+      }
 
 
-  const blankLine = (side: "old" | "new", kind: "add" | "remove"): HTMLElement => {
-    const span = document.createElement("span")
-    span.className = "line diff-line diff-blank"
-    span.setAttribute("data-diff", kind)
-    const ln = document.createElement("span")
-    ln.className = side === "old" ? "diff-oldln" : "diff-newln"
-    ln.textContent = " "
-    span.appendChild(ln)
-    return span
-  }
+      return out.join("\n")
+    },
+    code(node) {
+      if (isDiff) this.addClassToHast(node, "code-diff")
+    },
+    pre(node) {
+      if (isDiff) this.addClassToHast(node, "code-diff")
+    },
+    line(node, line) {
+      if (!isDiff) return
+      const kind = kinds.get(line)
+      if (!kind) return
 
 
-  const lines = Array.from(originalCode.querySelectorAll<HTMLElement>(".diff-line"))
-  let i = 0
-  while (i < lines.length) {
-    const cur = lines[i]
-    const kind = cur.dataset["diff"]
+      const m = meta.get(line) || {}
 
 
-    if (kind === "context") {
-      left.code.appendChild(cloneSide(cur, "old"))
-      left.code.appendChild(document.createTextNode("\n"))
-      right.code.appendChild(cloneSide(cur, "new"))
-      right.code.appendChild(document.createTextNode("\n"))
-      i++
-      continue
-    }
+      this.addClassToHast(node, "diff-line")
+      this.addClassToHast(node, `diff-${kind}`)
+      node.properties = node.properties || {}
+      ;(node.properties as any)["data-diff"] = kind
+      if (m.old != undefined) (node.properties as any)["data-old"] = String(m.old)
+      if (m.new != undefined) (node.properties as any)["data-new"] = String(m.new)
 
 
-    if (kind === "remove") {
-      // Batch consecutive removes and following adds, then pair
-      const removes: HTMLElement[] = []
-      const adds: HTMLElement[] = []
-      let j = i
-      while (j < lines.length && lines[j].dataset["diff"] === "remove") {
-        removes.push(lines[j])
-        j++
+      const oldSpan = {
+        type: "element",
+        tagName: "span",
+        properties: { className: ["diff-oldln"] },
+        children: [{ type: "text", value: m.old != undefined ? String(m.old) : " " }],
       }
       }
-      let k = j
-      while (k < lines.length && lines[k].dataset["diff"] === "add") {
-        adds.push(lines[k])
-        k++
+      const newSpan = {
+        type: "element",
+        tagName: "span",
+        properties: { className: ["diff-newln"] },
+        children: [{ type: "text", value: m.new != undefined ? String(m.new) : " " }],
       }
       }
 
 
-      const pairs = Math.min(removes.length, adds.length)
-      for (let p = 0; p < pairs; p++) {
-        left.code.appendChild(cloneSide(removes[p], "old"))
-        left.code.appendChild(document.createTextNode("\n"))
-        right.code.appendChild(cloneSide(adds[p], "new"))
-        right.code.appendChild(document.createTextNode("\n"))
-      }
-      for (let p = pairs; p < removes.length; p++) {
-        left.code.appendChild(cloneSide(removes[p], "old"))
-        left.code.appendChild(document.createTextNode("\n"))
-        right.code.appendChild(blankLine("new", "remove"))
-        right.code.appendChild(document.createTextNode("\n"))
-      }
-      for (let p = pairs; p < adds.length; p++) {
-        left.code.appendChild(blankLine("old", "add"))
-        left.code.appendChild(document.createTextNode("\n"))
-        right.code.appendChild(cloneSide(adds[p], "new"))
-        right.code.appendChild(document.createTextNode("\n"))
+      if (kind === "add" || kind === "remove" || kind === "context") {
+        const first = (node.children && (node.children as any[])[0]) as any
+        if (first && first.type === "element" && first.children && first.children.length > 0) {
+          const t = first.children[0]
+          if (t && t.type === "text" && typeof t.value === "string" && t.value.length > 0) {
+            const ch = t.value[0]
+            if (ch === "+" || ch === "-" || ch === " ") t.value = t.value.slice(1)
+          }
+        }
       }
       }
 
 
-      i = k
-      continue
-    }
-
-    if (kind === "add") {
-      // Run of adds not preceded by removes
-      const adds: HTMLElement[] = []
-      let j = i
-      while (j < lines.length && lines[j].dataset["diff"] === "add") {
-        adds.push(lines[j])
-        j++
-      }
-      for (let p = 0; p < adds.length; p++) {
-        left.code.appendChild(blankLine("old", "add"))
-        left.code.appendChild(document.createTextNode("\n"))
-        right.code.appendChild(cloneSide(adds[p], "new"))
-        right.code.appendChild(document.createTextNode("\n"))
+      const signSpan = {
+        type: "element",
+        tagName: "span",
+        properties: { className: ["diff-sign"] },
+        children: [{ type: "text", value: (m as any).sign || " " }],
       }
       }
-      i = j
-      continue
-    }
 
 
-    // Any other kind: mirror as context
-    left.code.appendChild(cloneSide(cur, "old"))
-    left.code.appendChild(document.createTextNode("\n"))
-    right.code.appendChild(cloneSide(cur, "new"))
-    right.code.appendChild(document.createTextNode("\n"))
-    i++
+      // @ts-expect-error hast typing across versions
+      node.children = [oldSpan, newSpan, signSpan, ...(node.children || [])]
+    },
   }
   }
+}
 
 
-  grid.appendChild(left.pre)
-  grid.appendChild(right.pre)
-  container.appendChild(grid)
+function transformerDiffGroups(): ShikiTransformer {
+  let group = -1
+  let inGroup = false
+  return {
+    name: "diff-groups",
+    pre() {
+      group = -1
+      inGroup = false
+    },
+    line(node) {
+      const props = (node.properties || {}) as any
+      const kind = props["data-diff"] as string | undefined
+      if (kind === "add" || kind === "remove") {
+        if (!inGroup) {
+          group += 1
+          inGroup = true
+        }
+        ;(node.properties as any)["data-chgrp"] = String(group)
+      } else {
+        inGroup = false
+      }
+    },
+  }
 }
 }
 
 
-export function Code(props: Props) {
-  const ctx = useLocal()
-  const [local, others] = splitProps(props, ["class", "classList", "code", "path"])
-  const lang = createMemo(() => getFileExtension(local.path))
+function applyDiffFolding(
+  root: HTMLElement,
+  context = 3,
+  options?: { expanded?: string[]; onExpand?: (key: string) => void; side?: "left" | "right" },
+) {
+  if (!root.classList.contains("code-diff")) return
+
+  // Cleanup: unwrap previous collapsed blocks and remove toggles
+  const blocks = Array.from(root.querySelectorAll<HTMLElement>(".diff-collapsed-block"))
+  for (const block of blocks) {
+    const p = block.parentNode
+    if (!p) {
+      block.remove()
+      continue
+    }
+    while (block.firstChild) p.insertBefore(block.firstChild, block)
+    block.remove()
+  }
+  const toggles = Array.from(root.querySelectorAll<HTMLElement>(".diff-collapsed"))
+  for (const t of toggles) t.remove()
 
 
-  let container: HTMLDivElement | undefined
-  let isProgrammaticSelection = false
+  const lines = Array.from(root.querySelectorAll<HTMLElement>(".diff-line"))
+  if (lines.length === 0) return
 
 
-  const [html] = createResource(
-    () => [local.code, lang()],
-    async ([code, lang]) => {
-      return (await codeToHtml(code || "", {
-        lang: lang && lang in bundledLanguages ? lang : "text",
-        theme: {
-          colors: {
-            "actionBar.toggledBackground": "var(--theme-background-element)",
-            "activityBarBadge.background": "var(--theme-accent)",
-            "checkbox.border": "var(--theme-border)",
-            "editor.background": "transparent",
-            "editor.foreground": "var(--theme-text)",
-            "editor.inactiveSelectionBackground": "var(--theme-background-element)",
-            "editor.selectionHighlightBackground": "var(--theme-border-active)",
-            "editorIndentGuide.activeBackground1": "var(--theme-border-subtle)",
-            "editorIndentGuide.background1": "var(--theme-border-subtle)",
-            "input.placeholderForeground": "var(--theme-text-muted)",
-            "list.activeSelectionIconForeground": "var(--theme-text)",
-            "list.dropBackground": "var(--theme-background-element)",
-            "menu.background": "var(--theme-background-panel)",
-            "menu.border": "var(--theme-border)",
-            "menu.foreground": "var(--theme-text)",
-            "menu.selectionBackground": "var(--theme-primary)",
-            "menu.separatorBackground": "var(--theme-border)",
-            "ports.iconRunningProcessForeground": "var(--theme-success)",
-            "sideBarSectionHeader.background": "transparent",
-            "sideBarSectionHeader.border": "var(--theme-border-subtle)",
-            "sideBarTitle.foreground": "var(--theme-text-muted)",
-            "statusBarItem.remoteBackground": "var(--theme-success)",
-            "statusBarItem.remoteForeground": "var(--theme-text)",
-            "tab.lastPinnedBorder": "var(--theme-border-subtle)",
-            "tab.selectedBackground": "var(--theme-background-element)",
-            "tab.selectedForeground": "var(--theme-text-muted)",
-            "terminal.inactiveSelectionBackground": "var(--theme-background-element)",
-            "widget.border": "var(--theme-border)",
-          },
-          displayName: "opencode",
-          name: "opencode",
-          semanticHighlighting: true,
-          semanticTokenColors: {
-            customLiteral: "var(--theme-syntax-function)",
-            newOperator: "var(--theme-syntax-operator)",
-            numberLiteral: "var(--theme-syntax-number)",
-            stringLiteral: "var(--theme-syntax-string)",
-          },
-          tokenColors: [
-            {
-              scope: [
-                "meta.embedded",
-                "source.groovy.embedded",
-                "string meta.image.inline.markdown",
-                "variable.legacy.builtin.python",
-              ],
-              settings: {
-                foreground: "var(--theme-text)",
-              },
-            },
-            {
-              scope: "emphasis",
-              settings: {
-                fontStyle: "italic",
-              },
-            },
-            {
-              scope: "strong",
-              settings: {
-                fontStyle: "bold",
-              },
-            },
-            {
-              scope: "header",
-              settings: {
-                foreground: "var(--theme-markdown-heading)",
-              },
-            },
-            {
-              scope: "comment",
-              settings: {
-                foreground: "var(--theme-syntax-comment)",
-              },
-            },
-            {
-              scope: "constant.language",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: [
-                "constant.numeric",
-                "variable.other.enummember",
-                "keyword.operator.plus.exponent",
-                "keyword.operator.minus.exponent",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-number)",
-              },
-            },
-            {
-              scope: "constant.regexp",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "entity.name.tag",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: ["entity.name.tag.css", "entity.name.tag.less"],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "entity.other.attribute-name",
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: [
-                "entity.other.attribute-name.class.css",
-                "source.css entity.other.attribute-name.class",
-                "entity.other.attribute-name.id.css",
-                "entity.other.attribute-name.parent-selector.css",
-                "entity.other.attribute-name.parent.less",
-                "source.css entity.other.attribute-name.pseudo-class",
-                "entity.other.attribute-name.pseudo-element.css",
-                "source.css.less entity.other.attribute-name.id",
-                "entity.other.attribute-name.scss",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "invalid",
-              settings: {
-                foreground: "var(--theme-error)",
-              },
-            },
-            {
-              scope: "markup.underline",
-              settings: {
-                fontStyle: "underline",
-              },
-            },
-            {
-              scope: "markup.bold",
-              settings: {
-                fontStyle: "bold",
-                foreground: "var(--theme-markdown-strong)",
-              },
-            },
-            {
-              scope: "markup.heading",
-              settings: {
-                fontStyle: "bold",
-                foreground: "var(--theme-markdown-heading)",
-              },
-            },
-            {
-              scope: "markup.italic",
-              settings: {
-                fontStyle: "italic",
-              },
-            },
-            {
-              scope: "markup.strikethrough",
-              settings: {
-                fontStyle: "strikethrough",
-              },
-            },
-            {
-              scope: "markup.inserted",
-              settings: {
-                foreground: "var(--theme-diff-added)",
-              },
-            },
-            {
-              scope: "markup.deleted",
-              settings: {
-                foreground: "var(--theme-diff-removed)",
-              },
-            },
-            {
-              scope: "markup.changed",
-              settings: {
-                foreground: "var(--theme-diff-context)",
-              },
-            },
-            {
-              scope: "punctuation.definition.quote.begin.markdown",
-              settings: {
-                foreground: "var(--theme-markdown-block-quote)",
-              },
-            },
-            {
-              scope: "punctuation.definition.list.begin.markdown",
-              settings: {
-                foreground: "var(--theme-markdown-list-enumeration)",
-              },
-            },
-            {
-              scope: "markup.inline.raw",
-              settings: {
-                foreground: "var(--theme-markdown-code)",
-              },
-            },
-            {
-              scope: "punctuation.definition.tag",
-              settings: {
-                foreground: "var(--theme-syntax-punctuation)",
-              },
-            },
-            {
-              scope: ["meta.preprocessor", "entity.name.function.preprocessor"],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "meta.preprocessor.string",
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: "meta.preprocessor.numeric",
-              settings: {
-                foreground: "var(--theme-syntax-number)",
-              },
-            },
-            {
-              scope: "meta.structure.dictionary.key.python",
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: "meta.diff.header",
-              settings: {
-                foreground: "var(--theme-diff-hunk-header)",
-              },
-            },
-            {
-              scope: "storage",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "storage.type",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: ["storage.modifier", "keyword.operator.noexcept"],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: ["string", "meta.embedded.assembly"],
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: "string.tag",
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: "string.value",
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: "string.regexp",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: [
-                "punctuation.definition.template-expression.begin",
-                "punctuation.definition.template-expression.end",
-                "punctuation.section.embedded",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: ["meta.template.expression"],
-              settings: {
-                foreground: "var(--theme-text)",
-              },
-            },
-            {
-              scope: [
-                "support.type.vendored.property-name",
-                "support.type.property-name",
-                "source.css variable",
-                "source.coffee.embedded",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: "keyword",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "keyword.control",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "keyword.operator",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: [
-                "keyword.operator.new",
-                "keyword.operator.expression",
-                "keyword.operator.cast",
-                "keyword.operator.sizeof",
-                "keyword.operator.alignof",
-                "keyword.operator.typeid",
-                "keyword.operator.alignas",
-                "keyword.operator.instanceof",
-                "keyword.operator.logical.python",
-                "keyword.operator.wordlike",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "keyword.other.unit",
-              settings: {
-                foreground: "var(--theme-syntax-number)",
-              },
-            },
-            {
-              scope: ["punctuation.section.embedded.begin.php", "punctuation.section.embedded.end.php"],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "support.function.git-rebase",
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: "constant.sha.git-rebase",
-              settings: {
-                foreground: "var(--theme-syntax-number)",
-              },
-            },
-            {
-              scope: [
-                "storage.modifier.import.java",
-                "variable.language.wildcard.java",
-                "storage.modifier.package.java",
-              ],
-              settings: {
-                foreground: "var(--theme-text)",
-              },
-            },
-            {
-              scope: "variable.language",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: [
-                "entity.name.function",
-                "support.function",
-                "support.constant.handlebars",
-                "source.powershell variable.other.member",
-                "entity.name.operator.custom-literal",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-function)",
-              },
-            },
-            {
-              scope: [
-                "support.class",
-                "support.type",
-                "entity.name.type",
-                "entity.name.namespace",
-                "entity.other.attribute",
-                "entity.name.scope-resolution",
-                "entity.name.class",
-                "storage.type.numeric.go",
-                "storage.type.byte.go",
-                "storage.type.boolean.go",
-                "storage.type.string.go",
-                "storage.type.uintptr.go",
-                "storage.type.error.go",
-                "storage.type.rune.go",
-                "storage.type.cs",
-                "storage.type.generic.cs",
-                "storage.type.modifier.cs",
-                "storage.type.variable.cs",
-                "storage.type.annotation.java",
-                "storage.type.generic.java",
-                "storage.type.java",
-                "storage.type.object.array.java",
-                "storage.type.primitive.array.java",
-                "storage.type.primitive.java",
-                "storage.type.token.java",
-                "storage.type.groovy",
-                "storage.type.annotation.groovy",
-                "storage.type.parameters.groovy",
-                "storage.type.generic.groovy",
-                "storage.type.object.array.groovy",
-                "storage.type.primitive.array.groovy",
-                "storage.type.primitive.groovy",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-type)",
-              },
-            },
-            {
-              scope: [
-                "meta.type.cast.expr",
-                "meta.type.new.expr",
-                "support.constant.math",
-                "support.constant.dom",
-                "support.constant.json",
-                "entity.other.inherited-class",
-                "punctuation.separator.namespace.ruby",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-type)",
-              },
-            },
-            {
-              scope: [
-                "keyword.control",
-                "source.cpp keyword.operator.new",
-                "keyword.operator.delete",
-                "keyword.other.using",
-                "keyword.other.directive.using",
-                "keyword.other.operator",
-                "entity.name.operator",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: [
-                "variable",
-                "meta.definition.variable.name",
-                "support.variable",
-                "entity.name.variable",
-                "constant.other.placeholder",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: ["variable.other.constant", "variable.other.enummember"],
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: ["meta.object-literal.key"],
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: [
-                "support.constant.property-value",
-                "support.constant.font-name",
-                "support.constant.media-type",
-                "support.constant.media",
-                "constant.other.color.rgb-value",
-                "constant.other.rgb-value",
-                "support.constant.color",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: [
-                "punctuation.definition.group.regexp",
-                "punctuation.definition.group.assertion.regexp",
-                "punctuation.definition.character-class.regexp",
-                "punctuation.character.set.begin.regexp",
-                "punctuation.character.set.end.regexp",
-                "keyword.operator.negation.regexp",
-                "support.other.parenthesis.regexp",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: [
-                "constant.character.character-class.regexp",
-                "constant.other.character-class.set.regexp",
-                "constant.other.character-class.regexp",
-                "constant.character.set.regexp",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: ["keyword.operator.or.regexp", "keyword.control.anchor.regexp"],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "keyword.operator.quantifier.regexp",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: ["constant.character", "constant.other.option"],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "constant.character.escape",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "entity.name.label",
-              settings: {
-                foreground: "var(--theme-text-muted)",
-              },
-            },
-          ],
-          type: "dark",
-        },
-        transformers: [transformerUnifiedDiff(), transformerDiffGroups()],
-      })) as string
-    },
-  )
+  const n = lines.length
+  const isChange = lines.map((l) => l.dataset["diff"] === "add" || l.dataset["diff"] === "remove")
+  const isContext = lines.map((l) => l.dataset["diff"] === "context")
+  if (!isChange.some(Boolean)) return
 
 
-  onMount(() => {
-    if (!container) return
+  const visible = new Array(n).fill(false) as boolean[]
+  for (let i = 0; i < n; i++) if (isChange[i]) visible[i] = true
+  for (let i = 0; i < n; i++) {
+    if (isChange[i]) {
+      const s = Math.max(0, i - context)
+      const e = Math.min(n - 1, i + context)
+      for (let j = s; j <= e; j++) if (isContext[j]) visible[j] = true
+    }
+  }
 
 
-    let ticking = false
-    const onScroll = () => {
-      if (!container) return
-      if (ctx.file.active()?.path !== local.path) return
-      if (ticking) return
-      ticking = true
-      requestAnimationFrame(() => {
-        ticking = false
-        ctx.file.scroll(local.path, container!.scrollTop)
-      })
+  type Range = { start: number; end: number }
+  const ranges: Range[] = []
+  let i = 0
+  while (i < n) {
+    if (!visible[i] && isContext[i]) {
+      let j = i
+      while (j + 1 < n && !visible[j + 1] && isContext[j + 1]) j++
+      ranges.push({ start: i, end: j })
+      i = j + 1
+    } else {
+      i++
     }
     }
+  }
 
 
-    const onSelectionChange = () => {
-      if (!container) return
-      if (isProgrammaticSelection) return
-      if (ctx.file.active()?.path !== local.path) return
-      const d = getSelectionInContainer(container)
-      if (!d) return
-      const p = ctx.file.node(local.path)?.selection
-      if (p && p.startLine === d.sl && p.endLine === d.el && p.startChar === d.sch && p.endChar === d.ech) return
-      ctx.file.select(local.path, { startLine: d.sl, startChar: d.sch, endLine: d.el, endChar: d.ech })
+  for (const r of ranges) {
+    const start = lines[r.start]
+    const end = lines[r.end]
+    const count = r.end - r.start + 1
+    const minCollapse = 20
+    if (count < minCollapse) {
+      continue
     }
     }
 
 
-    const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control"
-    const onKeyDown = (e: KeyboardEvent) => {
-      if (ctx.file.active()?.path !== local.path) return
-      const ae = document.activeElement as HTMLElement | undefined
-      const tag = (ae?.tagName || "").toLowerCase()
-      const inputFocused = !!ae && (tag === "input" || tag === "textarea" || ae.isContentEditable)
-      if (inputFocused) return
-      if (e.getModifierState(MOD) && e.key.toLowerCase() === "a") {
-        e.preventDefault()
-        if (!container) return
-        const element = container.querySelector("code") as HTMLElement | undefined
-        if (!element) return
-        const lines = Array.from(element.querySelectorAll(".line"))
-        if (!lines.length) return
-        const r = document.createRange()
-        const last = lines[lines.length - 1]
-        r.selectNodeContents(last)
-        const lastLen = r.toString().length
-        ctx.file.select(local.path, { startLine: 1, startChar: 0, endLine: lines.length, endChar: lastLen })
+    // Wrap the entire collapsed chunk (including trailing newline) so it takes no space
+    const block = document.createElement("span")
+    block.className = "diff-collapsed-block"
+    start.parentElement?.insertBefore(block, start)
+
+    let cur: Node | undefined = start
+    while (cur) {
+      const next: Node | undefined = cur.nextSibling || undefined
+      block.appendChild(cur)
+      if (cur === end) {
+        // Also move the newline after the last line into the block
+        if (next && next.nodeType === Node.TEXT_NODE && (next.textContent || "").startsWith("\n")) {
+          block.appendChild(next)
+        }
+        break
       }
       }
+      cur = next
     }
     }
 
 
-    container.addEventListener("scroll", onScroll)
-    document.addEventListener("selectionchange", onSelectionChange)
-    document.addEventListener("keydown", onKeyDown)
+    block.style.display = "none"
+    const row = document.createElement("span")
+    row.className = "line diff-collapsed"
+    row.setAttribute("data-kind", "collapsed")
+    row.setAttribute("data-count", String(count))
+    row.setAttribute("tabindex", "0")
+    row.setAttribute("role", "button")
 
 
-    onCleanup(() => {
-      container?.removeEventListener("scroll", onScroll)
-      document.removeEventListener("selectionchange", onSelectionChange)
-      document.removeEventListener("keydown", onKeyDown)
-    })
-  })
+    const oldln = document.createElement("span")
+    oldln.className = "diff-oldln"
+    oldln.textContent = " "
 
 
-  // Restore scroll position from store when content is ready
-  createEffect(() => {
-    const content = html()
-    if (!container || !content) return
-    const top = ctx.file.node(local.path)?.scrollTop
-    if (top !== undefined && container.scrollTop !== top) container.scrollTop = top
-  })
+    const newln = document.createElement("span")
+    newln.className = "diff-newln"
+    newln.textContent = " "
 
 
-  // Sync selection from store -> DOM
-  createEffect(() => {
-    const content = html()
-    if (!container || !content) return
-    if (ctx.file.active()?.path !== local.path) return
-    const codeEl = container.querySelector("code") as HTMLElement | undefined
-    if (!codeEl) return
-    const target = ctx.file.node(local.path)?.selection
-    const current = getSelectionInContainer(container)
-    const sel = window.getSelection()
-    if (!sel) return
-    if (!target) {
-      if (current) {
-        isProgrammaticSelection = true
-        sel.removeAllRanges()
-        queueMicrotask(() => {
-          isProgrammaticSelection = false
-        })
-      }
-      return
-    }
-    const matches = !!(
-      current &&
-      current.sl === target.startLine &&
-      current.sch === target.startChar &&
-      current.el === target.endLine &&
-      current.ech === target.endChar
-    )
-    if (matches) return
-    const lines = Array.from(codeEl.querySelectorAll(".line"))
-    if (lines.length === 0) return
-    let sIdx = Math.max(0, target.startLine - 1)
-    let eIdx = Math.max(0, target.endLine - 1)
-    let sChar = Math.max(0, target.startChar || 0)
-    let eChar = Math.max(0, target.endChar || 0)
-    if (sIdx > eIdx || (sIdx === eIdx && sChar > eChar)) {
-      const ti = sIdx
-      sIdx = eIdx
-      eIdx = ti
-      const tc = sChar
-      sChar = eChar
-      eChar = tc
-    }
-    if (eChar === 0 && eIdx > sIdx) {
-      eIdx = eIdx - 1
-      eChar = Number.POSITIVE_INFINITY
-    }
-    if (sIdx >= lines.length) return
-    if (eIdx >= lines.length) eIdx = lines.length - 1
-    const s = getNodeOffsetInLine(lines[sIdx], sChar) ?? { node: lines[sIdx], offset: 0 }
-    const e = getNodeOffsetInLine(lines[eIdx], eChar) ?? { node: lines[eIdx], offset: lines[eIdx].childNodes.length }
-    const range = document.createRange()
-    range.setStart(s.node, s.offset)
-    range.setEnd(e.node, e.offset)
-    isProgrammaticSelection = true
-    sel.removeAllRanges()
-    sel.addRange(range)
-    queueMicrotask(() => {
-      isProgrammaticSelection = false
-    })
-  })
+    const sign = document.createElement("span")
+    sign.className = "diff-sign"
+    sign.textContent = "…"
 
 
-  // Build/toggle split layout and apply folding (both unified and split)
-  createEffect(() => {
-    const content = html()
-    if (!container || !content) return
-    const view = ctx.file.view(local.path)
+    const label = document.createElement("span")
+    label.textContent = `show ${count} unchanged line${count > 1 ? "s" : ""}`
 
 
-    const pres = Array.from(container.querySelectorAll<HTMLPreElement>("pre"))
-    if (pres.length === 0) return
-    const originalPre = pres[0]
+    const key = `o${start.dataset["old"] || ""}-${end.dataset["old"] || ""}:n${start.dataset["new"] || ""}-${end.dataset["new"] || ""}`
 
 
-    const split = container.querySelector<HTMLElement>(".diff-split")
-    if (view === "diff-split") {
-      applySplitDiff(container)
-      const next = container.querySelector<HTMLElement>(".diff-split")
-      if (next) next.style.display = ""
-      originalPre.style.display = "none"
-    } else {
-      if (split) split.style.display = "none"
-      originalPre.style.display = ""
+    const show = (record = true) => {
+      if (record) options?.onExpand?.(key)
+      const p = block.parentNode
+      if (p) {
+        while (block.firstChild) p.insertBefore(block.firstChild, block)
+        block.remove()
+      }
+      row.remove()
     }
     }
 
 
-    const expanded = ctx.file.folded(local.path)
-    if (view === "diff-split") {
-      const left = container.querySelector<HTMLElement>(".diff-split pre:nth-child(1) code")
-      const right = container.querySelector<HTMLElement>(".diff-split pre:nth-child(2) code")
-      if (left)
-        applyDiffFolding(left, 3, { expanded, onExpand: (key) => ctx.file.unfold(local.path, key), side: "left" })
-      if (right)
-        applyDiffFolding(right, 3, { expanded, onExpand: (key) => ctx.file.unfold(local.path, key), side: "right" })
-    } else {
-      const code = container.querySelector<HTMLElement>("pre code")
-      if (code)
-        applyDiffFolding(code, 3, {
-          expanded,
-          onExpand: (key) => ctx.file.unfold(local.path, key),
-        })
-    }
-  })
+    row.addEventListener("click", () => show(true))
+    row.addEventListener("keydown", (ev) => {
+      if (ev.key === "Enter" || ev.key === " ") {
+        ev.preventDefault()
+        show(true)
+      }
+    })
 
 
-  // Highlight groups + scroll coupling
-  const clearHighlights = () => {
-    if (!container) return
-    container.querySelectorAll<HTMLElement>(".diff-selected").forEach((el) => el.classList.remove("diff-selected"))
+    block.parentElement?.insertBefore(row, block)
+    if (!options?.side || options.side === "left") row.appendChild(oldln)
+    if (!options?.side || options.side === "right") row.appendChild(newln)
+    row.appendChild(sign)
+    row.appendChild(label)
+
+    if (options?.expanded && options.expanded.includes(key)) {
+      show(false)
+    }
   }
   }
+}
 
 
-  const applyHighlight = (idx: number, scroll?: boolean) => {
-    if (!container) return
-    const view = ctx.file.view(local.path)
-    if (view === "raw") return
+function applySplitDiff(container: HTMLElement) {
+  const pres = Array.from(container.querySelectorAll<HTMLPreElement>("pre"))
+  if (pres.length === 0) return
+  const originalPre = pres[0]
+  const originalCode = originalPre.querySelector("code") as HTMLElement | undefined
+  if (!originalCode || !originalCode.classList.contains("code-diff")) return
 
 
-    clearHighlights()
+  // Rebuild split each time to match current content
+  const existing = container.querySelector<HTMLElement>(".diff-split")
+  if (existing) existing.remove()
 
 
-    const nodes: HTMLElement[] = []
-    if (view === "diff-split") {
-      const left = container.querySelector<HTMLElement>(".diff-split pre:nth-child(1) code")
-      const right = container.querySelector<HTMLElement>(".diff-split pre:nth-child(2) code")
-      if (left)
-        nodes.push(...Array.from(left.querySelectorAll<HTMLElement>(`[data-chgrp="${idx}"][data-diff="remove"]`)))
-      if (right)
-        nodes.push(...Array.from(right.querySelectorAll<HTMLElement>(`[data-chgrp="${idx}"][data-diff="add"]`)))
+  const grid = document.createElement("div")
+  grid.className = "diff-split grid grid-cols-2 gap-x-6"
+
+  const makeColumn = () => {
+    const pre = document.createElement("pre")
+    pre.className = originalPre.className
+    const code = document.createElement("code")
+    code.className = originalCode.className
+    pre.appendChild(code)
+    return { pre, code }
+  }
+
+  const left = makeColumn()
+  const right = makeColumn()
+
+  // Helpers
+  const cloneSide = (line: HTMLElement, side: "old" | "new"): HTMLElement => {
+    const clone = line.cloneNode(true) as HTMLElement
+    const oldln = clone.querySelector(".diff-oldln")
+    const newln = clone.querySelector(".diff-newln")
+    if (side === "old") {
+      if (newln) newln.remove()
     } else {
     } else {
-      const code = container.querySelector<HTMLElement>("pre code")
-      if (code) nodes.push(...Array.from(code.querySelectorAll<HTMLElement>(`[data-chgrp="${idx}"]`)))
+      if (oldln) oldln.remove()
     }
     }
+    return clone
+  }
 
 
-    for (const n of nodes) n.classList.add("diff-selected")
-    if (scroll && nodes.length) nodes[0].scrollIntoView({ block: "center", behavior: "smooth" })
+  const blankLine = (side: "old" | "new", kind: "add" | "remove"): HTMLElement => {
+    const span = document.createElement("span")
+    span.className = "line diff-line diff-blank"
+    span.setAttribute("data-diff", kind)
+    const ln = document.createElement("span")
+    ln.className = side === "old" ? "diff-oldln" : "diff-newln"
+    ln.textContent = " "
+    span.appendChild(ln)
+    return span
   }
   }
 
 
-  const countGroups = () => {
-    if (!container) return 0
-    const code = container.querySelector<HTMLElement>("pre code")
-    if (!code) return 0
-    const set = new Set<string>()
-    for (const el of Array.from(code.querySelectorAll<HTMLElement>(".diff-line[data-chgrp]"))) {
-      const v = el.getAttribute("data-chgrp")
-      if (v != undefined) set.add(v)
+  const lines = Array.from(originalCode.querySelectorAll<HTMLElement>(".diff-line"))
+  let i = 0
+  while (i < lines.length) {
+    const cur = lines[i]
+    const kind = cur.dataset["diff"]
+
+    if (kind === "context") {
+      left.code.appendChild(cloneSide(cur, "old"))
+      left.code.appendChild(document.createTextNode("\n"))
+      right.code.appendChild(cloneSide(cur, "new"))
+      right.code.appendChild(document.createTextNode("\n"))
+      i++
+      continue
     }
     }
-    return set.size
-  }
 
 
-  let lastIdx: number | undefined = undefined
-  let lastView: string | undefined
-  let lastContent: string | undefined
-  let lastRawIdx: number | undefined = undefined
-  createEffect(() => {
-    const content = html()
-    if (!container || !content) return
-    const view = ctx.file.view(local.path)
-    const raw = ctx.file.changeIndex(local.path)
-    if (raw === undefined) return
-    const total = countGroups()
-    if (total <= 0) return
-    const next = ((raw % total) + total) % total
+    if (kind === "remove") {
+      // Batch consecutive removes and following adds, then pair
+      const removes: HTMLElement[] = []
+      const adds: HTMLElement[] = []
+      let j = i
+      while (j < lines.length && lines[j].dataset["diff"] === "remove") {
+        removes.push(lines[j])
+        j++
+      }
+      let k = j
+      while (k < lines.length && lines[k].dataset["diff"] === "add") {
+        adds.push(lines[k])
+        k++
+      }
 
 
-    const navigated = lastRawIdx !== undefined && lastRawIdx !== raw
+      const pairs = Math.min(removes.length, adds.length)
+      for (let p = 0; p < pairs; p++) {
+        left.code.appendChild(cloneSide(removes[p], "old"))
+        left.code.appendChild(document.createTextNode("\n"))
+        right.code.appendChild(cloneSide(adds[p], "new"))
+        right.code.appendChild(document.createTextNode("\n"))
+      }
+      for (let p = pairs; p < removes.length; p++) {
+        left.code.appendChild(cloneSide(removes[p], "old"))
+        left.code.appendChild(document.createTextNode("\n"))
+        right.code.appendChild(blankLine("new", "remove"))
+        right.code.appendChild(document.createTextNode("\n"))
+      }
+      for (let p = pairs; p < adds.length; p++) {
+        left.code.appendChild(blankLine("old", "add"))
+        left.code.appendChild(document.createTextNode("\n"))
+        right.code.appendChild(cloneSide(adds[p], "new"))
+        right.code.appendChild(document.createTextNode("\n"))
+      }
 
 
-    if (next !== raw) {
-      ctx.file.setChangeIndex(local.path, next)
-      applyHighlight(next, true)
-    } else {
-      if (lastView !== view || lastContent !== content) applyHighlight(next)
-      if ((lastIdx !== undefined && lastIdx !== next) || navigated) applyHighlight(next, true)
+      i = k
+      continue
     }
     }
 
 
-    lastRawIdx = raw
-    lastIdx = next
-    lastView = view
-    lastContent = content
-  })
+    if (kind === "add") {
+      // Run of adds not preceded by removes
+      const adds: HTMLElement[] = []
+      let j = i
+      while (j < lines.length && lines[j].dataset["diff"] === "add") {
+        adds.push(lines[j])
+        j++
+      }
+      for (let p = 0; p < adds.length; p++) {
+        left.code.appendChild(blankLine("old", "add"))
+        left.code.appendChild(document.createTextNode("\n"))
+        right.code.appendChild(cloneSide(adds[p], "new"))
+        right.code.appendChild(document.createTextNode("\n"))
+      }
+      i = j
+      continue
+    }
 
 
-  return (
-    <Suspense>
-      <div
-        ref={(el) => {
-          container = el
-        }}
-        innerHTML={html()}
-        class="
-          font-mono text-xs tracking-wide overflow-y-auto no-scrollbar h-full
-          [&]:[counter-reset:line]
-          [&_pre]:focus-visible:outline-none
-          [&_pre]:overflow-x-auto [&_pre]:no-scrollbar
-          [&_code]:min-w-full [&_code]:inline-block [&_code]:pb-40
-          [&_.tab]:relative
-          [&_.tab::before]:content['⇥']
-          [&_.tab::before]:absolute
-          [&_.tab::before]:opacity-0
-          [&_.space]:relative
-          [&_.space::before]:content-['·']
-          [&_.space::before]:absolute
-          [&_.space::before]:opacity-0
-          [&_.line]:inline-block [&_.line]:w-full
-          [&_.line]:hover:bg-background-element
-          [&_.line::before]:sticky [&_.line::before]:left-0
-          [&_.line::before]:w-12 [&_.line::before]:pr-4
-          [&_.line::before]:z-10
-          [&_.line::before]:bg-background-panel
-          [&_.line::before]:text-text-muted/60
-          [&_.line::before]:text-right [&_.line::before]:inline-block
-          [&_.line::before]:select-none
-          [&_.line::before]:[counter-increment:line]
-          [&_.line::before]:content-[counter(line)]
-          [&_code.code-diff_.line::before]:content-['']
-          [&_code.code-diff_.line::before]:w-0
-          [&_code.code-diff_.line::before]:pr-0
-          [&_.diff-split_code.code-diff::before]:w-10
-          [&_.diff-split_.diff-newln]:left-0
-          [&_.diff-oldln]:sticky [&_.diff-oldln]:left-0
-          [&_.diff-oldln]:w-10 [&_.diff-oldln]:pr-2
-          [&_.diff-oldln]:z-40
-          [&_.diff-oldln]:text-text-muted/60
-          [&_.diff-oldln]:text-right [&_.diff-oldln]:inline-block
-          [&_.diff-oldln]:select-none
-          [&_.diff-oldln]:bg-background-panel
-          [&_.diff-newln]:sticky [&_.diff-newln]:left-10
-          [&_.diff-newln]:w-10 [&_.diff-newln]:pr-2
-          [&_.diff-newln]:z-40
-          [&_.diff-newln]:text-text-muted/60
-          [&_.diff-newln]:text-right [&_.diff-newln]:inline-block
-          [&_.diff-newln]:select-none
-          [&_.diff-newln]:bg-background-panel
-          [&_.diff-add]:bg-success/20!
-          [&_.diff-add.diff-selected]:bg-success/50!
-          [&_.diff-add_.diff-oldln]:bg-success!
-          [&_.diff-add_.diff-oldln]:text-background-panel!
-          [&_.diff-add_.diff-newln]:bg-success!
-          [&_.diff-add_.diff-newln]:text-background-panel!
-          [&_.diff-remove]:bg-error/20!
-          [&_.diff-remove.diff-selected]:bg-error/50!
-          [&_.diff-remove_.diff-newln]:bg-error!
-          [&_.diff-remove_.diff-newln]:text-background-panel!
-          [&_.diff-remove_.diff-oldln]:bg-error!
-          [&_.diff-remove_.diff-oldln]:text-background-panel!
-          [&_.diff-sign]:inline-block [&_.diff-sign]:px-2 [&_.diff-sign]:select-none
-          [&_.diff-blank]:bg-background-element
-          [&_.diff-blank_.diff-oldln]:bg-background-element
-          [&_.diff-blank_.diff-newln]:bg-background-element
-          [&_.diff-collapsed]:block! [&_.diff-collapsed]:w-full [&_.diff-collapsed]:relative
-          [&_.diff-collapsed]:cursor-pointer [&_.diff-collapsed]:select-none
-          [&_.diff-collapsed]:bg-info/20 [&_.diff-collapsed]:hover:bg-info/40!
-          [&_.diff-collapsed]:text-info/80 [&_.diff-collapsed]:hover:text-info
-          [&_.diff-collapsed]:text-xs
-          [&_.diff-collapsed_.diff-oldln]:bg-info!
-          [&_.diff-collapsed_.diff-newln]:bg-info!
-        "
-        classList={{
-          ...(local.classList || {}),
-          [local.class ?? ""]: !!local.class,
-        }}
-        {...others}
-      />
-    </Suspense>
-  )
+    // Any other kind: mirror as context
+    left.code.appendChild(cloneSide(cur, "old"))
+    left.code.appendChild(document.createTextNode("\n"))
+    right.code.appendChild(cloneSide(cur, "new"))
+    right.code.appendChild(document.createTextNode("\n"))
+    i++
+  }
+
+  grid.appendChild(left.pre)
+  grid.appendChild(right.pre)
+  container.appendChild(grid)
 }
 }

+ 3 - 580
packages/app/src/components/markdown.tsx

@@ -1,584 +1,6 @@
-import { transformerNotationDiff } from "@shikijs/transformers"
-import { marked } from "marked"
-import markedShiki from "marked-shiki"
-import { codeToHtml } from "shiki"
+import { useMarked } from "@/context"
 import { createResource } from "solid-js"
 import { createResource } from "solid-js"
 
 
-const markedWithShiki = marked.use(
-  markedShiki({
-    highlight(code, lang) {
-      return codeToHtml(code, {
-        // structure: "inline",
-        lang: lang || "text",
-        tabindex: false,
-        theme: {
-          colors: {
-            "actionBar.toggledBackground": "var(--theme-background-element)",
-            "activityBarBadge.background": "var(--theme-accent)",
-            "checkbox.border": "var(--theme-border)",
-            "editor.background": "transparent",
-            "editor.foreground": "var(--theme-text)",
-            "editor.inactiveSelectionBackground": "var(--theme-background-element)",
-            "editor.selectionHighlightBackground": "var(--theme-border-active)",
-            "editorIndentGuide.activeBackground1": "var(--theme-border-subtle)",
-            "editorIndentGuide.background1": "var(--theme-border-subtle)",
-            "input.placeholderForeground": "var(--theme-text-muted)",
-            "list.activeSelectionIconForeground": "var(--theme-text)",
-            "list.dropBackground": "var(--theme-background-element)",
-            "menu.background": "var(--theme-background-panel)",
-            "menu.border": "var(--theme-border)",
-            "menu.foreground": "var(--theme-text)",
-            "menu.selectionBackground": "var(--theme-primary)",
-            "menu.separatorBackground": "var(--theme-border)",
-            "ports.iconRunningProcessForeground": "var(--theme-success)",
-            "sideBarSectionHeader.background": "transparent",
-            "sideBarSectionHeader.border": "var(--theme-border-subtle)",
-            "sideBarTitle.foreground": "var(--theme-text-muted)",
-            "statusBarItem.remoteBackground": "var(--theme-success)",
-            "statusBarItem.remoteForeground": "var(--theme-text)",
-            "tab.lastPinnedBorder": "var(--theme-border-subtle)",
-            "tab.selectedBackground": "var(--theme-background-element)",
-            "tab.selectedForeground": "var(--theme-text-muted)",
-            "terminal.inactiveSelectionBackground": "var(--theme-background-element)",
-            "widget.border": "var(--theme-border)",
-          },
-          displayName: "opencode",
-          name: "opencode",
-          semanticHighlighting: true,
-          semanticTokenColors: {
-            customLiteral: "var(--theme-syntax-function)",
-            newOperator: "var(--theme-syntax-operator)",
-            numberLiteral: "var(--theme-syntax-number)",
-            stringLiteral: "var(--theme-syntax-string)",
-          },
-          tokenColors: [
-            {
-              scope: [
-                "meta.embedded",
-                "source.groovy.embedded",
-                "string meta.image.inline.markdown",
-                "variable.legacy.builtin.python",
-              ],
-              settings: {
-                foreground: "var(--theme-text)",
-              },
-            },
-            {
-              scope: "emphasis",
-              settings: {
-                fontStyle: "italic",
-              },
-            },
-            {
-              scope: "strong",
-              settings: {
-                fontStyle: "bold",
-              },
-            },
-            {
-              scope: "header",
-              settings: {
-                foreground: "var(--theme-markdown-heading)",
-              },
-            },
-            {
-              scope: "comment",
-              settings: {
-                foreground: "var(--theme-syntax-comment)",
-              },
-            },
-            {
-              scope: "constant.language",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: [
-                "constant.numeric",
-                "variable.other.enummember",
-                "keyword.operator.plus.exponent",
-                "keyword.operator.minus.exponent",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-number)",
-              },
-            },
-            {
-              scope: "constant.regexp",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "entity.name.tag",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: ["entity.name.tag.css", "entity.name.tag.less"],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "entity.other.attribute-name",
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: [
-                "entity.other.attribute-name.class.css",
-                "source.css entity.other.attribute-name.class",
-                "entity.other.attribute-name.id.css",
-                "entity.other.attribute-name.parent-selector.css",
-                "entity.other.attribute-name.parent.less",
-                "source.css entity.other.attribute-name.pseudo-class",
-                "entity.other.attribute-name.pseudo-element.css",
-                "source.css.less entity.other.attribute-name.id",
-                "entity.other.attribute-name.scss",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "invalid",
-              settings: {
-                foreground: "var(--theme-error)",
-              },
-            },
-            {
-              scope: "markup.underline",
-              settings: {
-                fontStyle: "underline",
-              },
-            },
-            {
-              scope: "markup.bold",
-              settings: {
-                fontStyle: "bold",
-                foreground: "var(--theme-markdown-strong)",
-              },
-            },
-            {
-              scope: "markup.heading",
-              settings: {
-                fontStyle: "bold",
-                foreground: "var(--theme-markdown-heading)",
-              },
-            },
-            {
-              scope: "markup.italic",
-              settings: {
-                fontStyle: "italic",
-              },
-            },
-            {
-              scope: "markup.strikethrough",
-              settings: {
-                fontStyle: "strikethrough",
-              },
-            },
-            {
-              scope: "markup.inserted",
-              settings: {
-                foreground: "var(--theme-diff-added)",
-              },
-            },
-            {
-              scope: "markup.deleted",
-              settings: {
-                foreground: "var(--theme-diff-removed)",
-              },
-            },
-            {
-              scope: "markup.changed",
-              settings: {
-                foreground: "var(--theme-diff-context)",
-              },
-            },
-            {
-              scope: "punctuation.definition.quote.begin.markdown",
-              settings: {
-                foreground: "var(--theme-markdown-block-quote)",
-              },
-            },
-            {
-              scope: "punctuation.definition.list.begin.markdown",
-              settings: {
-                foreground: "var(--theme-markdown-list-enumeration)",
-              },
-            },
-            {
-              scope: "markup.inline.raw",
-              settings: {
-                foreground: "var(--theme-markdown-code)",
-              },
-            },
-            {
-              scope: "punctuation.definition.tag",
-              settings: {
-                foreground: "var(--theme-syntax-punctuation)",
-              },
-            },
-            {
-              scope: ["meta.preprocessor", "entity.name.function.preprocessor"],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "meta.preprocessor.string",
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: "meta.preprocessor.numeric",
-              settings: {
-                foreground: "var(--theme-syntax-number)",
-              },
-            },
-            {
-              scope: "meta.structure.dictionary.key.python",
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: "meta.diff.header",
-              settings: {
-                foreground: "var(--theme-diff-hunk-header)",
-              },
-            },
-            {
-              scope: "storage",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "storage.type",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: ["storage.modifier", "keyword.operator.noexcept"],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: ["string", "meta.embedded.assembly"],
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: "string.tag",
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: "string.value",
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: "string.regexp",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: [
-                "punctuation.definition.template-expression.begin",
-                "punctuation.definition.template-expression.end",
-                "punctuation.section.embedded",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: ["meta.template.expression"],
-              settings: {
-                foreground: "var(--theme-text)",
-              },
-            },
-            {
-              scope: [
-                "support.type.vendored.property-name",
-                "support.type.property-name",
-                "source.css variable",
-                "source.coffee.embedded",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: "keyword",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "keyword.control",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "keyword.operator",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: [
-                "keyword.operator.new",
-                "keyword.operator.expression",
-                "keyword.operator.cast",
-                "keyword.operator.sizeof",
-                "keyword.operator.alignof",
-                "keyword.operator.typeid",
-                "keyword.operator.alignas",
-                "keyword.operator.instanceof",
-                "keyword.operator.logical.python",
-                "keyword.operator.wordlike",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "keyword.other.unit",
-              settings: {
-                foreground: "var(--theme-syntax-number)",
-              },
-            },
-            {
-              scope: ["punctuation.section.embedded.begin.php", "punctuation.section.embedded.end.php"],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "support.function.git-rebase",
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: "constant.sha.git-rebase",
-              settings: {
-                foreground: "var(--theme-syntax-number)",
-              },
-            },
-            {
-              scope: [
-                "storage.modifier.import.java",
-                "variable.language.wildcard.java",
-                "storage.modifier.package.java",
-              ],
-              settings: {
-                foreground: "var(--theme-text)",
-              },
-            },
-            {
-              scope: "variable.language",
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: [
-                "entity.name.function",
-                "support.function",
-                "support.constant.handlebars",
-                "source.powershell variable.other.member",
-                "entity.name.operator.custom-literal",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-function)",
-              },
-            },
-            {
-              scope: [
-                "support.class",
-                "support.type",
-                "entity.name.type",
-                "entity.name.namespace",
-                "entity.other.attribute",
-                "entity.name.scope-resolution",
-                "entity.name.class",
-                "storage.type.numeric.go",
-                "storage.type.byte.go",
-                "storage.type.boolean.go",
-                "storage.type.string.go",
-                "storage.type.uintptr.go",
-                "storage.type.error.go",
-                "storage.type.rune.go",
-                "storage.type.cs",
-                "storage.type.generic.cs",
-                "storage.type.modifier.cs",
-                "storage.type.variable.cs",
-                "storage.type.annotation.java",
-                "storage.type.generic.java",
-                "storage.type.java",
-                "storage.type.object.array.java",
-                "storage.type.primitive.array.java",
-                "storage.type.primitive.java",
-                "storage.type.token.java",
-                "storage.type.groovy",
-                "storage.type.annotation.groovy",
-                "storage.type.parameters.groovy",
-                "storage.type.generic.groovy",
-                "storage.type.object.array.groovy",
-                "storage.type.primitive.array.groovy",
-                "storage.type.primitive.groovy",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-type)",
-              },
-            },
-            {
-              scope: [
-                "meta.type.cast.expr",
-                "meta.type.new.expr",
-                "support.constant.math",
-                "support.constant.dom",
-                "support.constant.json",
-                "entity.other.inherited-class",
-                "punctuation.separator.namespace.ruby",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-type)",
-              },
-            },
-            {
-              scope: [
-                "keyword.control",
-                "source.cpp keyword.operator.new",
-                "keyword.operator.delete",
-                "keyword.other.using",
-                "keyword.other.directive.using",
-                "keyword.other.operator",
-                "entity.name.operator",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: [
-                "variable",
-                "meta.definition.variable.name",
-                "support.variable",
-                "entity.name.variable",
-                "constant.other.placeholder",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: ["variable.other.constant", "variable.other.enummember"],
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: ["meta.object-literal.key"],
-              settings: {
-                foreground: "var(--theme-syntax-variable)",
-              },
-            },
-            {
-              scope: [
-                "support.constant.property-value",
-                "support.constant.font-name",
-                "support.constant.media-type",
-                "support.constant.media",
-                "constant.other.color.rgb-value",
-                "constant.other.rgb-value",
-                "support.constant.color",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: [
-                "punctuation.definition.group.regexp",
-                "punctuation.definition.group.assertion.regexp",
-                "punctuation.definition.character-class.regexp",
-                "punctuation.character.set.begin.regexp",
-                "punctuation.character.set.end.regexp",
-                "keyword.operator.negation.regexp",
-                "support.other.parenthesis.regexp",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-string)",
-              },
-            },
-            {
-              scope: [
-                "constant.character.character-class.regexp",
-                "constant.other.character-class.set.regexp",
-                "constant.other.character-class.regexp",
-                "constant.character.set.regexp",
-              ],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: ["keyword.operator.or.regexp", "keyword.control.anchor.regexp"],
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "keyword.operator.quantifier.regexp",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: ["constant.character", "constant.other.option"],
-              settings: {
-                foreground: "var(--theme-syntax-keyword)",
-              },
-            },
-            {
-              scope: "constant.character.escape",
-              settings: {
-                foreground: "var(--theme-syntax-operator)",
-              },
-            },
-            {
-              scope: "entity.name.label",
-              settings: {
-                foreground: "var(--theme-text-muted)",
-              },
-            },
-          ],
-          type: "dark",
-        },
-        transformers: [transformerNotationDiff()],
-      })
-    },
-  }),
-)
-
 function strip(text: string): string {
 function strip(text: string): string {
   const wrappedRe = /^\s*<([A-Za-z]\w*)>\s*([\s\S]*?)\s*<\/\1>\s*$/
   const wrappedRe = /^\s*<([A-Za-z]\w*)>\s*([\s\S]*?)\s*<\/\1>\s*$/
   const match = text.match(wrappedRe)
   const match = text.match(wrappedRe)
@@ -586,10 +8,11 @@ function strip(text: string): string {
 }
 }
 
 
 export default function Markdown(props: { text: string; class?: string }) {
 export default function Markdown(props: { text: string; class?: string }) {
+  const marked = useMarked()
   const [html] = createResource(
   const [html] = createResource(
     () => strip(props.text),
     () => strip(props.text),
     async (markdown) => {
     async (markdown) => {
-      return markedWithShiki.parse(markdown)
+      return marked.parse(markdown)
     },
     },
   )
   )
   return (
   return (

+ 34 - 0
packages/app/src/context/event.tsx

@@ -0,0 +1,34 @@
+import { createContext, useContext, type ParentProps } from "solid-js"
+import { createEventBus } from "@solid-primitives/event-bus"
+import type { Event as SDKEvent } from "@opencode-ai/sdk"
+import { useSDK } from "@/context"
+
+export type Event = SDKEvent // can extend with custom events later
+
+function init() {
+  const sdk = useSDK()
+  const bus = createEventBus<Event>()
+  sdk.event.subscribe().then(async (events) => {
+    for await (const event of events.stream) {
+      bus.emit(event)
+    }
+  })
+  return bus
+}
+
+type EventContext = ReturnType<typeof init>
+
+const ctx = createContext<EventContext>()
+
+export function EventProvider(props: ParentProps) {
+  const value = init()
+  return <ctx.Provider value={value}>{props.children}</ctx.Provider>
+}
+
+export function useEvent() {
+  const value = useContext(ctx)
+  if (!value) {
+    throw new Error("useEvent must be used within a EventProvider")
+  }
+  return value
+}

+ 3 - 0
packages/app/src/context/index.ts

@@ -1,4 +1,7 @@
+export { EventProvider, useEvent } from "./event"
 export { LocalProvider, useLocal } from "./local"
 export { LocalProvider, useLocal } from "./local"
+export { MarkedProvider, useMarked } from "./marked"
 export { SDKProvider, useSDK } from "./sdk"
 export { SDKProvider, useSDK } from "./sdk"
+export { ShikiProvider, useShiki } from "./shiki"
 export { SyncProvider, useSync } from "./sync"
 export { SyncProvider, useSync } from "./sync"
 export { ThemeProvider, useTheme } from "./theme"
 export { ThemeProvider, useTheme } from "./theme"

+ 26 - 25
packages/app/src/context/local.tsx

@@ -1,9 +1,8 @@
 import { createStore, produce, reconcile } from "solid-js/store"
 import { createStore, produce, reconcile } from "solid-js/store"
 import { batch, createContext, createEffect, createMemo, useContext, type ParentProps } from "solid-js"
 import { batch, createContext, createEffect, createMemo, useContext, type ParentProps } from "solid-js"
-import { useSync } from "./sync"
 import { uniqueBy } from "remeda"
 import { uniqueBy } from "remeda"
 import type { FileContent, FileNode } from "@opencode-ai/sdk"
 import type { FileContent, FileNode } from "@opencode-ai/sdk"
-import { useSDK } from "./sdk"
+import { useSDK, useEvent, useSync } from "@/context"
 
 
 export type LocalFile = FileNode &
 export type LocalFile = FileNode &
   Partial<{
   Partial<{
@@ -165,17 +164,19 @@ function init() {
       })
       })
     }
     }
 
 
-    const load = async (path: string) =>
-      sdk.file.read({ query: { path } }).then((x) => {
+    const load = async (path: string) => {
+      const relative = path.replace(sync.data.path.directory + "/", "")
+      sdk.file.read({ query: { path: relative } }).then((x) => {
         setStore(
         setStore(
           "node",
           "node",
-          path,
+          relative,
           produce((draft) => {
           produce((draft) => {
             draft.loaded = true
             draft.loaded = true
             draft.content = x.data
             draft.content = x.data
           }),
           }),
         )
         )
       })
       })
+    }
 
 
     const open = async (path: string) => {
     const open = async (path: string) => {
       const relative = path.replace(sync.data.path.directory + "/", "")
       const relative = path.replace(sync.data.path.directory + "/", "")
@@ -213,27 +214,27 @@ function init() {
       })
       })
     }
     }
 
 
-    sdk.event.subscribe().then(async (events) => {
-      for await (const event of events.stream) {
-        switch (event.type) {
-          case "message.part.updated":
-            const part = event.properties.part
-            if (part.type === "tool" && part.state.status === "completed") {
-              switch (part.tool) {
-                case "read":
-                  console.log("read", part.state.input)
-                  break
-                case "edit":
-                  const absolute = part.state.input["filePath"] as string
-                  const path = absolute.replace(sync.data.path.directory + "/", "")
-                  load(path)
-                  break
-                default:
-                  break
-              }
+    const bus = useEvent()
+    bus.listen((event) => {
+      switch (event.type) {
+        case "message.part.updated":
+          const part = event.properties.part
+          if (part.type === "tool" && part.state.status === "completed") {
+            switch (part.tool) {
+              case "read":
+                console.log("read", part.state.input)
+                break
+              case "edit":
+                load(part.state.input["filePath"] as string)
+                break
+              default:
+                break
             }
             }
-            break
-        }
+          }
+          break
+        case "file.watcher.updated":
+          load(event.properties.file)
+          break
       }
       }
     })
     })
 
 

+ 40 - 0
packages/app/src/context/marked.tsx

@@ -0,0 +1,40 @@
+import { createContext, useContext, type ParentProps } from "solid-js"
+import { useShiki } from "@/context"
+import { marked } from "marked"
+import markedShiki from "marked-shiki"
+import type { BundledLanguage } from "shiki"
+
+function init(highlighter: ReturnType<typeof useShiki>) {
+  return marked.use(
+    markedShiki({
+      async highlight(code, lang) {
+        if (!highlighter.getLoadedLanguages().includes(lang)) {
+          await highlighter.loadLanguage(lang as BundledLanguage)
+        }
+        return highlighter.codeToHtml(code, {
+          lang: lang || "text",
+          theme: "opencode",
+          tabindex: false,
+        })
+      },
+    }),
+  )
+}
+
+type MarkedContext = ReturnType<typeof init>
+
+const ctx = createContext<MarkedContext>()
+
+export function MarkedProvider(props: ParentProps) {
+  const highlighter = useShiki()
+  const value = init(highlighter)
+  return <ctx.Provider value={value}>{props.children}</ctx.Provider>
+}
+
+export function useMarked() {
+  const value = useContext(ctx)
+  if (!value) {
+    throw new Error("useMarked must be used within a MarkedProvider")
+  }
+  return value
+}

+ 582 - 0
packages/app/src/context/shiki.tsx

@@ -0,0 +1,582 @@
+import { createHighlighter, type ThemeInput } from "shiki"
+import { createContext, useContext, type ParentProps } from "solid-js"
+
+const theme: ThemeInput = {
+  colors: {
+    "actionBar.toggledBackground": "var(--theme-background-element)",
+    "activityBarBadge.background": "var(--theme-accent)",
+    "checkbox.border": "var(--theme-border)",
+    "editor.background": "transparent",
+    "editor.foreground": "var(--theme-text)",
+    "editor.inactiveSelectionBackground": "var(--theme-background-element)",
+    "editor.selectionHighlightBackground": "var(--theme-border-active)",
+    "editorIndentGuide.activeBackground1": "var(--theme-border-subtle)",
+    "editorIndentGuide.background1": "var(--theme-border-subtle)",
+    "input.placeholderForeground": "var(--theme-text-muted)",
+    "list.activeSelectionIconForeground": "var(--theme-text)",
+    "list.dropBackground": "var(--theme-background-element)",
+    "menu.background": "var(--theme-background-panel)",
+    "menu.border": "var(--theme-border)",
+    "menu.foreground": "var(--theme-text)",
+    "menu.selectionBackground": "var(--theme-primary)",
+    "menu.separatorBackground": "var(--theme-border)",
+    "ports.iconRunningProcessForeground": "var(--theme-success)",
+    "sideBarSectionHeader.background": "transparent",
+    "sideBarSectionHeader.border": "var(--theme-border-subtle)",
+    "sideBarTitle.foreground": "var(--theme-text-muted)",
+    "statusBarItem.remoteBackground": "var(--theme-success)",
+    "statusBarItem.remoteForeground": "var(--theme-text)",
+    "tab.lastPinnedBorder": "var(--theme-border-subtle)",
+    "tab.selectedBackground": "var(--theme-background-element)",
+    "tab.selectedForeground": "var(--theme-text-muted)",
+    "terminal.inactiveSelectionBackground": "var(--theme-background-element)",
+    "widget.border": "var(--theme-border)",
+  },
+  displayName: "opencode",
+  name: "opencode",
+  semanticHighlighting: true,
+  semanticTokenColors: {
+    customLiteral: "var(--theme-syntax-function)",
+    newOperator: "var(--theme-syntax-operator)",
+    numberLiteral: "var(--theme-syntax-number)",
+    stringLiteral: "var(--theme-syntax-string)",
+  },
+  tokenColors: [
+    {
+      scope: [
+        "meta.embedded",
+        "source.groovy.embedded",
+        "string meta.image.inline.markdown",
+        "variable.legacy.builtin.python",
+      ],
+      settings: {
+        foreground: "var(--theme-text)",
+      },
+    },
+    {
+      scope: "emphasis",
+      settings: {
+        fontStyle: "italic",
+      },
+    },
+    {
+      scope: "strong",
+      settings: {
+        fontStyle: "bold",
+      },
+    },
+    {
+      scope: "header",
+      settings: {
+        foreground: "var(--theme-markdown-heading)",
+      },
+    },
+    {
+      scope: "comment",
+      settings: {
+        foreground: "var(--theme-syntax-comment)",
+      },
+    },
+    {
+      scope: "constant.language",
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: [
+        "constant.numeric",
+        "variable.other.enummember",
+        "keyword.operator.plus.exponent",
+        "keyword.operator.minus.exponent",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-number)",
+      },
+    },
+    {
+      scope: "constant.regexp",
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: "entity.name.tag",
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: ["entity.name.tag.css", "entity.name.tag.less"],
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: "entity.other.attribute-name",
+      settings: {
+        foreground: "var(--theme-syntax-variable)",
+      },
+    },
+    {
+      scope: [
+        "entity.other.attribute-name.class.css",
+        "source.css entity.other.attribute-name.class",
+        "entity.other.attribute-name.id.css",
+        "entity.other.attribute-name.parent-selector.css",
+        "entity.other.attribute-name.parent.less",
+        "source.css entity.other.attribute-name.pseudo-class",
+        "entity.other.attribute-name.pseudo-element.css",
+        "source.css.less entity.other.attribute-name.id",
+        "entity.other.attribute-name.scss",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: "invalid",
+      settings: {
+        foreground: "var(--theme-error)",
+      },
+    },
+    {
+      scope: "markup.underline",
+      settings: {
+        fontStyle: "underline",
+      },
+    },
+    {
+      scope: "markup.bold",
+      settings: {
+        fontStyle: "bold",
+        foreground: "var(--theme-markdown-strong)",
+      },
+    },
+    {
+      scope: "markup.heading",
+      settings: {
+        fontStyle: "bold",
+        foreground: "var(--theme-markdown-heading)",
+      },
+    },
+    {
+      scope: "markup.italic",
+      settings: {
+        fontStyle: "italic",
+      },
+    },
+    {
+      scope: "markup.strikethrough",
+      settings: {
+        fontStyle: "strikethrough",
+      },
+    },
+    {
+      scope: "markup.inserted",
+      settings: {
+        foreground: "var(--theme-diff-added)",
+      },
+    },
+    {
+      scope: "markup.deleted",
+      settings: {
+        foreground: "var(--theme-diff-removed)",
+      },
+    },
+    {
+      scope: "markup.changed",
+      settings: {
+        foreground: "var(--theme-diff-context)",
+      },
+    },
+    {
+      scope: "punctuation.definition.quote.begin.markdown",
+      settings: {
+        foreground: "var(--theme-markdown-block-quote)",
+      },
+    },
+    {
+      scope: "punctuation.definition.list.begin.markdown",
+      settings: {
+        foreground: "var(--theme-markdown-list-enumeration)",
+      },
+    },
+    {
+      scope: "markup.inline.raw",
+      settings: {
+        foreground: "var(--theme-markdown-code)",
+      },
+    },
+    {
+      scope: "punctuation.definition.tag",
+      settings: {
+        foreground: "var(--theme-syntax-punctuation)",
+      },
+    },
+    {
+      scope: ["meta.preprocessor", "entity.name.function.preprocessor"],
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: "meta.preprocessor.string",
+      settings: {
+        foreground: "var(--theme-syntax-string)",
+      },
+    },
+    {
+      scope: "meta.preprocessor.numeric",
+      settings: {
+        foreground: "var(--theme-syntax-number)",
+      },
+    },
+    {
+      scope: "meta.structure.dictionary.key.python",
+      settings: {
+        foreground: "var(--theme-syntax-variable)",
+      },
+    },
+    {
+      scope: "meta.diff.header",
+      settings: {
+        foreground: "var(--theme-diff-hunk-header)",
+      },
+    },
+    {
+      scope: "storage",
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: "storage.type",
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: ["storage.modifier", "keyword.operator.noexcept"],
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: ["string", "meta.embedded.assembly"],
+      settings: {
+        foreground: "var(--theme-syntax-string)",
+      },
+    },
+    {
+      scope: "string.tag",
+      settings: {
+        foreground: "var(--theme-syntax-string)",
+      },
+    },
+    {
+      scope: "string.value",
+      settings: {
+        foreground: "var(--theme-syntax-string)",
+      },
+    },
+    {
+      scope: "string.regexp",
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: [
+        "punctuation.definition.template-expression.begin",
+        "punctuation.definition.template-expression.end",
+        "punctuation.section.embedded",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: ["meta.template.expression"],
+      settings: {
+        foreground: "var(--theme-text)",
+      },
+    },
+    {
+      scope: [
+        "support.type.vendored.property-name",
+        "support.type.property-name",
+        "source.css variable",
+        "source.coffee.embedded",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-variable)",
+      },
+    },
+    {
+      scope: "keyword",
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: "keyword.control",
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: "keyword.operator",
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: [
+        "keyword.operator.new",
+        "keyword.operator.expression",
+        "keyword.operator.cast",
+        "keyword.operator.sizeof",
+        "keyword.operator.alignof",
+        "keyword.operator.typeid",
+        "keyword.operator.alignas",
+        "keyword.operator.instanceof",
+        "keyword.operator.logical.python",
+        "keyword.operator.wordlike",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: "keyword.other.unit",
+      settings: {
+        foreground: "var(--theme-syntax-number)",
+      },
+    },
+    {
+      scope: ["punctuation.section.embedded.begin.php", "punctuation.section.embedded.end.php"],
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: "support.function.git-rebase",
+      settings: {
+        foreground: "var(--theme-syntax-variable)",
+      },
+    },
+    {
+      scope: "constant.sha.git-rebase",
+      settings: {
+        foreground: "var(--theme-syntax-number)",
+      },
+    },
+    {
+      scope: ["storage.modifier.import.java", "variable.language.wildcard.java", "storage.modifier.package.java"],
+      settings: {
+        foreground: "var(--theme-text)",
+      },
+    },
+    {
+      scope: "variable.language",
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: [
+        "entity.name.function",
+        "support.function",
+        "support.constant.handlebars",
+        "source.powershell variable.other.member",
+        "entity.name.operator.custom-literal",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-function)",
+      },
+    },
+    {
+      scope: [
+        "support.class",
+        "support.type",
+        "entity.name.type",
+        "entity.name.namespace",
+        "entity.other.attribute",
+        "entity.name.scope-resolution",
+        "entity.name.class",
+        "storage.type.numeric.go",
+        "storage.type.byte.go",
+        "storage.type.boolean.go",
+        "storage.type.string.go",
+        "storage.type.uintptr.go",
+        "storage.type.error.go",
+        "storage.type.rune.go",
+        "storage.type.cs",
+        "storage.type.generic.cs",
+        "storage.type.modifier.cs",
+        "storage.type.variable.cs",
+        "storage.type.annotation.java",
+        "storage.type.generic.java",
+        "storage.type.java",
+        "storage.type.object.array.java",
+        "storage.type.primitive.array.java",
+        "storage.type.primitive.java",
+        "storage.type.token.java",
+        "storage.type.groovy",
+        "storage.type.annotation.groovy",
+        "storage.type.parameters.groovy",
+        "storage.type.generic.groovy",
+        "storage.type.object.array.groovy",
+        "storage.type.primitive.array.groovy",
+        "storage.type.primitive.groovy",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-type)",
+      },
+    },
+    {
+      scope: [
+        "meta.type.cast.expr",
+        "meta.type.new.expr",
+        "support.constant.math",
+        "support.constant.dom",
+        "support.constant.json",
+        "entity.other.inherited-class",
+        "punctuation.separator.namespace.ruby",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-type)",
+      },
+    },
+    {
+      scope: [
+        "keyword.control",
+        "source.cpp keyword.operator.new",
+        "keyword.operator.delete",
+        "keyword.other.using",
+        "keyword.other.directive.using",
+        "keyword.other.operator",
+        "entity.name.operator",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: [
+        "variable",
+        "meta.definition.variable.name",
+        "support.variable",
+        "entity.name.variable",
+        "constant.other.placeholder",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-variable)",
+      },
+    },
+    {
+      scope: ["variable.other.constant", "variable.other.enummember"],
+      settings: {
+        foreground: "var(--theme-syntax-variable)",
+      },
+    },
+    {
+      scope: ["meta.object-literal.key"],
+      settings: {
+        foreground: "var(--theme-syntax-variable)",
+      },
+    },
+    {
+      scope: [
+        "support.constant.property-value",
+        "support.constant.font-name",
+        "support.constant.media-type",
+        "support.constant.media",
+        "constant.other.color.rgb-value",
+        "constant.other.rgb-value",
+        "support.constant.color",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-string)",
+      },
+    },
+    {
+      scope: [
+        "punctuation.definition.group.regexp",
+        "punctuation.definition.group.assertion.regexp",
+        "punctuation.definition.character-class.regexp",
+        "punctuation.character.set.begin.regexp",
+        "punctuation.character.set.end.regexp",
+        "keyword.operator.negation.regexp",
+        "support.other.parenthesis.regexp",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-string)",
+      },
+    },
+    {
+      scope: [
+        "constant.character.character-class.regexp",
+        "constant.other.character-class.set.regexp",
+        "constant.other.character-class.regexp",
+        "constant.character.set.regexp",
+      ],
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: ["keyword.operator.or.regexp", "keyword.control.anchor.regexp"],
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: "keyword.operator.quantifier.regexp",
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: ["constant.character", "constant.other.option"],
+      settings: {
+        foreground: "var(--theme-syntax-keyword)",
+      },
+    },
+    {
+      scope: "constant.character.escape",
+      settings: {
+        foreground: "var(--theme-syntax-operator)",
+      },
+    },
+    {
+      scope: "entity.name.label",
+      settings: {
+        foreground: "var(--theme-text-muted)",
+      },
+    },
+  ],
+  type: "dark",
+}
+
+const highlighter = await createHighlighter({
+  themes: [theme],
+  langs: [],
+})
+
+type ShikiContext = typeof highlighter
+
+const ctx = createContext<ShikiContext>()
+
+export function ShikiProvider(props: ParentProps) {
+  return <ctx.Provider value={highlighter}>{props.children}</ctx.Provider>
+}
+
+export function useShiki() {
+  const value = useContext(ctx)
+  if (!value) {
+    throw new Error("useShiki must be used within a ShikiProvider")
+  }
+  return value
+}

+ 52 - 54
packages/app/src/context/sync.tsx

@@ -1,7 +1,7 @@
 import type { Message, Agent, Provider, Session, Part, Config, Path, File, FileNode } from "@opencode-ai/sdk"
 import type { Message, Agent, Provider, Session, Part, Config, Path, File, FileNode } from "@opencode-ai/sdk"
 import { createStore, produce, reconcile } from "solid-js/store"
 import { createStore, produce, reconcile } from "solid-js/store"
-import { useSDK } from "./sdk"
 import { createContext, Show, useContext, type ParentProps } from "solid-js"
 import { createContext, Show, useContext, type ParentProps } from "solid-js"
+import { useSDK, useEvent } from "@/context"
 import { Binary } from "@/utils/binary"
 import { Binary } from "@/utils/binary"
 
 
 function init() {
 function init() {
@@ -33,69 +33,67 @@ function init() {
     changes: [],
     changes: [],
   })
   })
 
 
-  const sdk = useSDK()
-
-  sdk.event.subscribe().then(async (events) => {
-    for await (const event of events.stream) {
-      switch (event.type) {
-        case "session.updated": {
-          const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
-          if (result.found) {
-            setStore("session", result.index, reconcile(event.properties.info))
-            break
-          }
-          setStore(
-            "session",
-            produce((draft) => {
-              draft.splice(result.index, 0, event.properties.info)
-            }),
-          )
+  const bus = useEvent()
+  bus.listen((event) => {
+    switch (event.type) {
+      case "session.updated": {
+        const result = Binary.search(store.session, event.properties.info.id, (s) => s.id)
+        if (result.found) {
+          setStore("session", result.index, reconcile(event.properties.info))
           break
           break
         }
         }
-        case "message.updated": {
-          const messages = store.message[event.properties.info.sessionID]
-          if (!messages) {
-            setStore("message", event.properties.info.sessionID, [event.properties.info])
-            break
-          }
-          const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
-          if (result.found) {
-            setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
-            break
-          }
-          setStore(
-            "message",
-            event.properties.info.sessionID,
-            produce((draft) => {
-              draft.splice(result.index, 0, event.properties.info)
-            }),
-          )
+        setStore(
+          "session",
+          produce((draft) => {
+            draft.splice(result.index, 0, event.properties.info)
+          }),
+        )
+        break
+      }
+      case "message.updated": {
+        const messages = store.message[event.properties.info.sessionID]
+        if (!messages) {
+          setStore("message", event.properties.info.sessionID, [event.properties.info])
           break
           break
         }
         }
-        case "message.part.updated": {
-          const parts = store.part[event.properties.part.messageID]
-          if (!parts) {
-            setStore("part", event.properties.part.messageID, [event.properties.part])
-            break
-          }
-          const result = Binary.search(parts, event.properties.part.id, (p) => p.id)
-          if (result.found) {
-            setStore("part", event.properties.part.messageID, result.index, reconcile(event.properties.part))
-            break
-          }
-          setStore(
-            "part",
-            event.properties.part.messageID,
-            produce((draft) => {
-              draft.splice(result.index, 0, event.properties.part)
-            }),
-          )
+        const result = Binary.search(messages, event.properties.info.id, (m) => m.id)
+        if (result.found) {
+          setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info))
           break
           break
         }
         }
+        setStore(
+          "message",
+          event.properties.info.sessionID,
+          produce((draft) => {
+            draft.splice(result.index, 0, event.properties.info)
+          }),
+        )
+        break
+      }
+      case "message.part.updated": {
+        const parts = store.part[event.properties.part.messageID]
+        if (!parts) {
+          setStore("part", event.properties.part.messageID, [event.properties.part])
+          break
+        }
+        const result = Binary.search(parts, event.properties.part.id, (p) => p.id)
+        if (result.found) {
+          setStore("part", event.properties.part.messageID, result.index, reconcile(event.properties.part))
+          break
+        }
+        setStore(
+          "part",
+          event.properties.part.messageID,
+          produce((draft) => {
+            draft.splice(result.index, 0, event.properties.part)
+          }),
+        )
+        break
       }
       }
     }
     }
   })
   })
 
 
+  const sdk = useSDK()
   Promise.all([
   Promise.all([
     sdk.config.providers().then((x) => setStore("provider", x.data!.providers)),
     sdk.config.providers().then((x) => setStore("provider", x.data!.providers)),
     sdk.path.get().then((x) => setStore("path", x.data!)),
     sdk.path.get().then((x) => setStore("path", x.data!)),

+ 24 - 10
packages/app/src/index.tsx

@@ -4,7 +4,15 @@ import { Router, Route } from "@solidjs/router"
 import "@/index.css"
 import "@/index.css"
 import Layout from "@/pages/layout"
 import Layout from "@/pages/layout"
 import Home from "@/pages"
 import Home from "@/pages"
-import { SDKProvider, SyncProvider, LocalProvider, ThemeProvider } from "@/context"
+import {
+  EventProvider,
+  SDKProvider,
+  SyncProvider,
+  LocalProvider,
+  ThemeProvider,
+  ShikiProvider,
+  MarkedProvider,
+} from "@/context"
 
 
 const root = document.getElementById("root")
 const root = document.getElementById("root")
 
 
@@ -18,15 +26,21 @@ render(
   () => (
   () => (
     <div class="h-full bg-background text-text-muted">
     <div class="h-full bg-background text-text-muted">
       <ThemeProvider defaultTheme="opencode" defaultDarkMode={true}>
       <ThemeProvider defaultTheme="opencode" defaultDarkMode={true}>
-        <SDKProvider>
-          <SyncProvider>
-            <LocalProvider>
-              <Router root={Layout}>
-                <Route path="/" component={Home} />
-              </Router>
-            </LocalProvider>
-          </SyncProvider>
-        </SDKProvider>
+        <ShikiProvider>
+          <MarkedProvider>
+            <SDKProvider>
+              <EventProvider>
+                <SyncProvider>
+                  <LocalProvider>
+                    <Router root={Layout}>
+                      <Route path="/" component={Home} />
+                    </Router>
+                  </LocalProvider>
+                </SyncProvider>
+              </EventProvider>
+            </SDKProvider>
+          </MarkedProvider>
+        </ShikiProvider>
       </ThemeProvider>
       </ThemeProvider>
     </div>
     </div>
   ),
   ),

+ 5 - 0
packages/opencode/src/cli/cmd/debug/lsp.ts

@@ -2,6 +2,7 @@ import { LSP } from "../../../lsp"
 import { bootstrap } from "../../bootstrap"
 import { bootstrap } from "../../bootstrap"
 import { cmd } from "../cmd"
 import { cmd } from "../cmd"
 import { Log } from "../../../util/log"
 import { Log } from "../../../util/log"
+import { UI } from "../../ui"
 
 
 export const LSPCommand = cmd({
 export const LSPCommand = cmd({
   command: "lsp",
   command: "lsp",
@@ -15,6 +16,10 @@ const DiagnosticsCommand = cmd({
   builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }),
   builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }),
   async handler(args) {
   async handler(args) {
     await bootstrap(process.cwd(), async () => {
     await bootstrap(process.cwd(), async () => {
+      if (!(await Bun.file(args.file).exists())) {
+        UI.error(`File ${args.file} does not exist`)
+        return
+      }
       await LSP.touchFile(args.file, true)
       await LSP.touchFile(args.file, true)
       console.log(JSON.stringify(await LSP.diagnostics(), null, 2))
       console.log(JSON.stringify(await LSP.diagnostics(), null, 2))
     })
     })

+ 0 - 1
packages/opencode/src/file/watcher.ts

@@ -28,7 +28,6 @@ export namespace FileWatcher {
       const ignore = (cfg.watcher?.ignore ?? []).map((v) => new Bun.Glob(v))
       const ignore = (cfg.watcher?.ignore ?? []).map((v) => new Bun.Glob(v))
       const watcher = chokidar.watch(Instance.directory, {
       const watcher = chokidar.watch(Instance.directory, {
         ignoreInitial: true,
         ignoreInitial: true,
-        awaitWriteFinish: true,
         ignored: (filepath) => {
         ignored: (filepath) => {
           return FileIgnore.match(filepath, {
           return FileIgnore.match(filepath, {
             extra: ignore,
             extra: ignore,

+ 1 - 1
packages/opencode/src/lsp/index.ts

@@ -72,7 +72,7 @@ export namespace LSP {
           ...existing,
           ...existing,
           id: name,
           id: name,
           root: existing?.root ?? (async () => Instance.directory),
           root: existing?.root ?? (async () => Instance.directory),
-          extensions: item.extensions ?? existing.extensions,
+          extensions: item.extensions ?? existing?.extensions ?? [],
           spawn: async (root) => {
           spawn: async (root) => {
             return {
             return {
               process: spawn(item.command[0], item.command.slice(1), {
               process: spawn(item.command[0], item.command.slice(1), {

+ 1 - 3
packages/web/src/content/docs/agents.mdx

@@ -495,9 +495,7 @@ For quick reference, here are common setups.
 }
 }
 ```
 ```
 
 
-See the full permissions guide for more patterns.
-
-- /docs/permissions
+See the full [permissions guide](/docs/permissions) for more patterns.
 
 
 ---
 ---
 
 

+ 1 - 1
packages/web/src/content/docs/lsp.mdx

@@ -3,7 +3,7 @@ title: LSP Servers
 description: opencode integrates with your LSP servers.
 description: opencode integrates with your LSP servers.
 ---
 ---
 
 
-opencode integrates with your Language Server Protocol (LSP) to help the LLM interacts with your codebase. It uses diagnostics to provide feedback to the LLM.
+opencode integrates with your Language Server Protocol (LSP) to help the LLM interact with your codebase. It uses diagnostics to provide feedback to the LLM.
 
 
 ---
 ---
 
 

+ 2 - 0
packages/web/src/content/docs/providers.mdx

@@ -356,6 +356,8 @@ To use your GitHub Copilot subscription with opencode:
 :::note
 :::note
 Some models might need a [Pro+
 Some models might need a [Pro+
 subscription](https://github.com/features/copilot/plans) to use.
 subscription](https://github.com/features/copilot/plans) to use.
+
+Some models need to be manually enabled in your [GitHub Copilot settings](https://docs.github.com/en/copilot/how-tos/use-ai-models/configure-access-to-ai-models#setup-for-individual-use).
 :::
 :::
 
 
 1. Run `opencode auth login` and select GitHub Copilot.
 1. Run `opencode auth login` and select GitHub Copilot.