From d77324886419840e80f576f60e8c4518b8d4dac4 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 11:09:29 -0700 Subject: [PATCH 01/85] feat(web): add rehype-pretty-code and shiki for docs highlighting --- apps/web/package.json | 2 + pnpm-lock.yaml | 254 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) diff --git a/apps/web/package.json b/apps/web/package.json index ebb4b6f4..f85428b6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -18,7 +18,9 @@ "next": "16.2.3", "react": "19.2.0", "react-dom": "19.2.0", + "rehype-pretty-code": "^0.14.3", "remark-gfm": "^4.0.1", + "shiki": "^4.0.2", "tailwindcss": "^4.2.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4f3f39c..d253b20f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,9 +57,15 @@ importers: react-dom: specifier: 19.2.0 version: 19.2.0(react@19.2.0) + rehype-pretty-code: + specifier: ^0.14.3 + version: 0.14.3(shiki@4.0.2) remark-gfm: specifier: ^4.0.1 version: 4.0.1 + shiki: + specifier: ^4.0.2 + version: 4.0.2 tailwindcss: specifier: ^4.2.4 version: 4.2.4 @@ -955,6 +961,37 @@ packages: '@rolldown/pluginutils@1.0.0-rc.15': resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} + '@shikijs/core@4.0.2': + resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.0.2': + resolution: {integrity: sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@4.0.2': + resolution: {integrity: sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==} + engines: {node: '>=20'} + + '@shikijs/langs@4.0.2': + resolution: {integrity: sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.0.2': + resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==} + engines: {node: '>=20'} + + '@shikijs/themes@4.0.2': + resolution: {integrity: sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==} + engines: {node: '>=20'} + + '@shikijs/types@4.0.2': + resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1358,6 +1395,10 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1524,15 +1565,36 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + hast-util-to-estree@3.1.3: resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + human-id@4.1.3: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true @@ -1949,6 +2011,12 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + openai@4.104.0: resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==} hasBin: true @@ -2009,6 +2077,12 @@ packages: parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-numeric-range@1.3.0: + resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2089,6 +2163,24 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-pretty-code@0.14.3: + resolution: {integrity: sha512-Cz692FeYusTjT5cfFWLc4r7JhgC3/JlJptgUh4iffBxWxUnWW1oqbWFi7jGCeq00DYUm8yzoTnvpocaYa5x82g==} + engines: {node: '>=18'} + peerDependencies: + shiki: ^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 + rehype-recma@1.0.0: resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} @@ -2157,6 +2249,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@4.0.2: + resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} + engines: {node: '>=20'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -2322,6 +2418,9 @@ packages: deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -2412,6 +2511,9 @@ packages: jsdom: optional: true + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + web-streams-polyfill@4.0.0-beta.3: resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} engines: {node: '>= 14'} @@ -3097,6 +3199,46 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.15': {} + '@shikijs/core@4.0.2': + dependencies: + '@shikijs/primitive': 4.0.2 + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + + '@shikijs/primitive@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/themes@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + + '@shikijs/types@4.0.2': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@standard-schema/spec@1.1.0': {} '@swc/helpers@0.5.15': @@ -3433,6 +3575,8 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@6.0.1: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -3646,6 +3790,30 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-estree@3.1.3: dependencies: '@types/estree': 1.0.8 @@ -3667,6 +3835,20 @@ snapshots: transitivePeerDependencies: - supports-color + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + hast-util-to-jsx-runtime@2.3.6: dependencies: '@types/estree': 1.0.8 @@ -3687,10 +3869,24 @@ snapshots: transitivePeerDependencies: - supports-color + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.4 + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + html-void-elements@3.0.0: {} + human-id@4.1.3: {} humanize-ms@1.2.1: @@ -4314,6 +4510,14 @@ snapshots: obug@2.1.1: {} + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + openai@4.104.0(zod@3.24.4): dependencies: '@types/node': 18.19.130 @@ -4393,6 +4597,12 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse-numeric-range@1.3.0: {} + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -4473,6 +4683,32 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-pretty-code@0.14.3(shiki@4.0.2): + dependencies: + '@types/hast': 3.0.4 + hast-util-to-string: 3.0.1 + parse-numeric-range: 1.3.0 + rehype-parse: 9.0.1 + shiki: 4.0.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + rehype-recma@1.0.0: dependencies: '@types/estree': 1.0.8 @@ -4603,6 +4839,17 @@ snapshots: shebang-regex@3.0.0: {} + shiki@4.0.2: + dependencies: + '@shikijs/core': 4.0.2 + '@shikijs/engine-javascript': 4.0.2 + '@shikijs/engine-oniguruma': 4.0.2 + '@shikijs/langs': 4.0.2 + '@shikijs/themes': 4.0.2 + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -4746,6 +4993,11 @@ snapshots: uuid@9.0.1: {} + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -4797,6 +5049,8 @@ snapshots: transitivePeerDependencies: - msw + web-namespaces@2.0.1: {} + web-streams-polyfill@4.0.0-beta.3: {} webidl-conversions@3.0.1: {} From 4e1ff9c4dc9a54b8cf855a2a8ae0736a79c364c2 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 11:11:25 -0700 Subject: [PATCH 02/85] feat(web): add custom Dawn shiki theme --- apps/web/lib/shiki/dawn-theme.ts | 70 ++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 apps/web/lib/shiki/dawn-theme.ts diff --git a/apps/web/lib/shiki/dawn-theme.ts b/apps/web/lib/shiki/dawn-theme.ts new file mode 100644 index 00000000..fa0d1880 --- /dev/null +++ b/apps/web/lib/shiki/dawn-theme.ts @@ -0,0 +1,70 @@ +import type { ThemeRegistration } from "shiki" + +// Hex values mirror the resolved values of the Dawn brand tokens defined in +// apps/web/app/globals.css. Update both when the palette changes. +// +// Token map: +// --color-text-secondary → #c8c8cc (default text) +// --color-text-muted → #8b8fa3 (comments) +// --color-accent-purple → #c4a7e7 (keywords / control flow) +// --color-accent-amber-deep → #f5a524 (type names) +// --color-accent-amber → #fbbf24 (constants / numbers) +// --color-accent-green → #34c759 (strings) +// --color-accent-blue → #7fc8ff (functions / methods) +export const dawnTheme: ThemeRegistration = { + name: "dawn", + type: "dark", + // Transparent background — the surrounding container owns its background. + colors: { + "editor.background": "#00000000", + "editor.foreground": "#c8c8cc", + }, + tokenColors: [ + { + scope: ["comment", "punctuation.definition.comment", "string.comment"], + settings: { foreground: "#8b8fa3", fontStyle: "italic" }, + }, + { + scope: ["string", "string.template", "constant.other.symbol"], + settings: { foreground: "#34c759" }, + }, + { + scope: ["constant.numeric", "constant.language", "constant.character"], + settings: { foreground: "#fbbf24" }, + }, + { + scope: ["keyword", "keyword.control", "storage", "storage.type", "storage.modifier"], + settings: { foreground: "#c4a7e7" }, + }, + { + scope: [ + "entity.name.type", + "entity.name.class", + "support.type", + "support.class", + "entity.other.inherited-class", + ], + settings: { foreground: "#f5a524" }, + }, + { + scope: ["entity.name.function", "support.function", "meta.function-call entity.name.function"], + settings: { foreground: "#7fc8ff" }, + }, + { + scope: ["variable", "variable.other", "meta.definition.variable"], + settings: { foreground: "#c8c8cc" }, + }, + { + scope: ["punctuation", "meta.brace", "meta.delimiter"], + settings: { foreground: "#c8c8cc" }, + }, + { + scope: ["entity.name.tag", "meta.tag"], + settings: { foreground: "#7fc8ff" }, + }, + { + scope: ["entity.other.attribute-name"], + settings: { foreground: "#fbbf24" }, + }, + ], +} From 87cdb78915b563d8909dc75702abe4ce233ead0c Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 11:12:09 -0700 Subject: [PATCH 03/85] feat(web): wire rehype-pretty-code into MDX pipeline with Dawn theme --- apps/web/next.config.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 01276119..3b4a57b9 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,5 +1,6 @@ import createMDX from "@next/mdx" import type { NextConfig } from "next" +import { dawnTheme } from "./lib/shiki/dawn-theme" const nextConfig: NextConfig = { reactStrictMode: true, @@ -10,6 +11,16 @@ const withMDX = createMDX({ options: { // Turbopack requires serializable plugin references — pass as module-path strings remarkPlugins: [["remark-gfm", {}]], + rehypePlugins: [ + [ + "rehype-pretty-code", + { + theme: dawnTheme, + keepBackground: false, + defaultLang: "plaintext", + }, + ], + ], }, }) From 48c242d7c8a0dd120264f07f065f90cd6f661dff Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 11:13:31 -0700 Subject: [PATCH 04/85] feat(web): add CodeGroup MDX component for multi-file tabs --- apps/web/app/components/mdx/CodeGroup.tsx | 49 +++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 apps/web/app/components/mdx/CodeGroup.tsx diff --git a/apps/web/app/components/mdx/CodeGroup.tsx b/apps/web/app/components/mdx/CodeGroup.tsx new file mode 100644 index 00000000..1c3b6bf6 --- /dev/null +++ b/apps/web/app/components/mdx/CodeGroup.tsx @@ -0,0 +1,49 @@ +"use client" + +import { Children, isValidElement, type ReactElement, type ReactNode, useState } from "react" + +interface CodeGroupProps { + readonly children: ReactNode +} + +interface PreElementProps { + readonly "data-rehype-pretty-code-title"?: string + readonly "data-language"?: string + readonly children?: ReactNode +} + +export function CodeGroup({ children }: CodeGroupProps) { + const blocks = Children.toArray(children).filter((c): c is ReactElement => + isValidElement(c), + ) + const [active, setActive] = useState(0) + if (blocks.length === 0) return null + + const titles = blocks.map( + (block, i) => block.props["data-rehype-pretty-code-title"] ?? `File ${i + 1}`, + ) + + return ( +
+
+ {titles.map((title, i) => ( + + ))} +
+
{blocks[active]}
+
+ ) +} From 0937968eaa60d0dac34b4c2148b484a8d47d94e2 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 11:12:50 -0700 Subject: [PATCH 05/85] feat(web): add Pre and InlineCode MDX components --- apps/web/app/components/mdx/CodeBlock.tsx | 96 +++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 apps/web/app/components/mdx/CodeBlock.tsx diff --git a/apps/web/app/components/mdx/CodeBlock.tsx b/apps/web/app/components/mdx/CodeBlock.tsx new file mode 100644 index 00000000..7fa41fc1 --- /dev/null +++ b/apps/web/app/components/mdx/CodeBlock.tsx @@ -0,0 +1,96 @@ +"use client" + +import { type HTMLAttributes, type ReactNode, useRef, useState } from "react" + +function CopyIcon() { + return ( + + + + + ) +} + +function CheckIcon() { + return ( + + + + ) +} + +interface PreProps extends HTMLAttributes { + readonly children?: ReactNode + readonly "data-language"?: string + readonly "data-theme"?: string +} + +export function Pre({ children, className, ...rest }: PreProps) { + const ref = useRef(null) + const [copied, setCopied] = useState(false) + const title = (rest as Record)["data-rehype-pretty-code-title"] as + | string + | undefined + const language = rest["data-language"] + + const copy = async () => { + const text = ref.current?.textContent ?? "" + await navigator.clipboard.writeText(text) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + return ( +
+ {title ? ( +
+ {title} +
+ {language ? ( + + {language} + + ) : null} + +
+
+ ) : ( +
+ +
+ )} +
+        {children}
+      
+
+ ) +} + +function CopyButton({ onCopy, copied }: { readonly onCopy: () => void; readonly copied: boolean }) { + return ( + + ) +} + +export function InlineCode({ children, className }: { readonly children?: ReactNode; readonly className?: string }) { + return ( + + {children} + + ) +} From 522e937b4888c449adf5d965a7dd958848919683 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 11:14:39 -0700 Subject: [PATCH 06/85] feat(web): wire Pre, InlineCode, and CodeGroup into MDX components --- apps/web/mdx-components.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/web/mdx-components.tsx b/apps/web/mdx-components.tsx index 1b560c3a..f9af1a63 100644 --- a/apps/web/mdx-components.tsx +++ b/apps/web/mdx-components.tsx @@ -1,11 +1,14 @@ import type { MDXComponents } from "mdx/types" import { Callout } from "./app/components/mdx/Callout" +import { InlineCode, Pre } from "./app/components/mdx/CodeBlock" +import { CodeGroup } from "./app/components/mdx/CodeGroup" import { Step, Steps } from "./app/components/mdx/Steps" import { Tab, Tabs } from "./app/components/mdx/Tabs" export function useMDXComponents(components: MDXComponents): MDXComponents { return { Callout, + CodeGroup, Steps, Step, Tabs, @@ -30,16 +33,8 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {

{children}

), p: ({ children }) =>

{children}

, - code: ({ children }) => ( - - {children} - - ), - pre: ({ children }) => ( -
-        {children}
-      
- ), + code: InlineCode, + pre: Pre, ul: ({ children }) => (