From 7e95b5ada6a1bffe321cffe2ecf28eb95f124c6e Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sun, 5 Apr 2026 12:19:13 -0400 Subject: [PATCH 01/12] Upgrade Vite 8, Vitest 4.1, React 19.2, ReScript 12.2, React Router 7.14, tsdown 0.21 Vite ecosystem: - vite: ^7.0.6 -> ^8.0.3 - @vitejs/plugin-react: ^4.7.0 -> ^6.0.1 - @tailwindcss/vite: ^4.1.13 -> ^4.2.2 - vite-plugin-page-reload: ^0.2.2 -> ^0.2.3 Vitest ecosystem: - vitest: ^4.0.18 -> ^4.1.2 - @vitest/browser-playwright: ^4.0.18 -> ^4.1.2 - vitest-browser-react: ^2.0.5 -> ^2.2.0 React: - react: ^19.1.0 -> ^19.2.4 - react-dom: ^19.1.0 -> ^19.2.4 - @types/react: ^19.2.2 -> ^19.2.14 ReScript: - rescript: ^12.0.0 -> ^12.2.0 - @rescript/react: ^0.14.0 -> ^0.14.2 - Migrate Exn.Error -> JsExn (deprecated in 12.2) React Router: - react-router: ^7.12.0 -> ^7.14.0 - react-router-dom: ^7.9.4 -> ^7.14.0 - @react-router/node: ^7.8.1 -> ^7.14.0 - @react-router/dev: ^7.8.1 -> ^7.14.0 tsdown: 0.20.0 -> 0.21.7 (in build:scripts) --- app/routes.res | 12 +- package.json | 34 +- src/MdxFile.res | 19 +- src/common/HighlightJs.res | 2 +- src/components/Search.res | 2 +- yarn.lock | 1650 +++++++++++++++++------------------- 6 files changed, 821 insertions(+), 898 deletions(-) diff --git a/app/routes.res b/app/routes.res index 83637584b..6f5b9b8fc 100644 --- a/app/routes.res +++ b/app/routes.res @@ -33,13 +33,13 @@ let blogArticleRoutes = route(path, "./routes/BlogArticleRoute.jsx", ~options={id: path}) ) -let mdxRoutes = - mdxRoutes("./routes/MdxRoute.jsx")->Array.filter(r => - !(r.path - ->Option.map(path => path === "blog" || String.startsWith(path, "blog/")) - ->Option.getOr(false) - ) +let mdxRoutes = mdxRoutes("./routes/MdxRoute.jsx")->Array.filter(r => + !( + r.path + ->Option.map(path => path === "blog" || String.startsWith(path, "blog/")) + ->Option.getOr(false) ) +) let default = [ index("./routes/LandingPageRoute.jsx"), diff --git a/package.json b/package.json index 8f43a6852..73ee8dc59 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "packageManager": "yarn@4.12.0", "type": "module", "scripts": { - "build:scripts": "yarn dlx tsdown@0.20.0 scripts/*.jsx -d _scripts --no-clean --ext .mjs", + "build:scripts": "yarn dlx tsdown@0.21.7 scripts/*.jsx -d _scripts --no-clean --ext .mjs", "build:generate-llms": "node _scripts/generate_llms.mjs", "build:res": "rescript build --warn-error +3+8+11+12+26+27+31+32+33+34+35+39+44+45+110", "build:sync-bundles": "node scripts/sync-playground-bundles.mjs", @@ -50,9 +50,9 @@ "@lezer/highlight": "^1.2.1", "@mdx-js/mdx": "^3.1.1", "@node-cli/static-server": "^3.1.4", - "@react-router/node": "^7.8.1", + "@react-router/node": "^7.14.0", "@replit/codemirror-vim": "^6.3.0", - "@rescript/react": "^0.14.0", + "@rescript/react": "^0.14.2", "@rescript/webapi": "0.1.0-experimental-29db5f4", "@tsnobip/rescript-lezer": "^0.8.0", "docson": "^2.1.0", @@ -65,11 +65,11 @@ "mdast-util-from-markdown": "^2.0.2", "mdast-util-to-string": "^4.0.0", "mdast-util-toc": "^7.1.0", - "react": "^19.1.0", - "react-dom": "^19.1.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", "react-markdown": "^10.1.0", - "react-router": "^7.12.0", - "react-router-dom": "^7.9.4", + "react-router": "^7.14.0", + "react-router-dom": "^7.14.0", "react-router-mdx": "patch:react-router-mdx@npm%3A1.0.8#~/.yarn/patches/react-router-mdx-npm-1.0.8-d4402c3003.patch", "rehype-slug": "^6.0.0", "rehype-stringify": "^10.0.1", @@ -78,17 +78,17 @@ "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", "remark-validate-links": "^13.1.0", - "rescript": "^12.0.0", + "rescript": "^12.2.0", "unified": "^11.0.5", "vfile-matter": "^5.0.0" }, "devDependencies": { "@prettier/plugin-oxc": "^0.0.4", - "@react-router/dev": "^7.8.1", - "@tailwindcss/vite": "^4.1.13", - "@types/react": "^19.2.2", - "@vitejs/plugin-react": "^4.7.0", - "@vitest/browser-playwright": "^4.0.18", + "@react-router/dev": "^7.14.0", + "@tailwindcss/vite": "^4.2.2", + "@types/react": "^19.2.14", + "@vitejs/plugin-react": "^6.0.1", + "@vitest/browser-playwright": "^4.1.2", "auto-image-converter": "^2.1.2", "chokidar": "^4.0.3", "dotenv": "^16.4.7", @@ -102,12 +102,12 @@ "tailwindcss": "^4", "to-vfile": "^8.0.0", "vfile-reporter": "^8.1.1", - "vite": "^7.0.6", + "vite": "^8.0.3", "vite-plugin-devtools-json": "^1.0.0", "vite-plugin-env-compatible": "^2.0.1", - "vite-plugin-page-reload": "^0.2.2", - "vitest": "^4.0.18", - "vitest-browser-react": "^2.0.5", + "vite-plugin-page-reload": "^0.2.3", + "vitest": "^4.1.2", + "vitest-browser-react": "^2.2.0", "wrangler": "^4.63.0" } } diff --git a/src/MdxFile.res b/src/MdxFile.res index a9bbf9d9f..0366cf8b3 100644 --- a/src/MdxFile.res +++ b/src/MdxFile.res @@ -30,16 +30,15 @@ let resolveFilePath = (pathname, ~dir, ~alias) => { } else { pathname } - let relativePath = - if path->String.startsWith(alias ++ "/") { - let rest = path->String.slice(~start=String.length(alias) + 1, ~end=String.length(path)) - Node.Path.join2(dir, rest) - } else if path->String.startsWith(alias) { - let rest = path->String.slice(~start=String.length(alias), ~end=String.length(path)) - Node.Path.join2(dir, rest) - } else { - path - } + let relativePath = if path->String.startsWith(alias ++ "/") { + let rest = path->String.slice(~start=String.length(alias) + 1, ~end=String.length(path)) + Node.Path.join2(dir, rest) + } else if path->String.startsWith(alias) { + let rest = path->String.slice(~start=String.length(alias), ~end=String.length(path)) + Node.Path.join2(dir, rest) + } else { + path + } relativePath ++ ".mdx" } diff --git a/src/common/HighlightJs.res b/src/common/HighlightJs.res index ecc9bfd0d..f22311a55 100644 --- a/src/common/HighlightJs.res +++ b/src/common/HighlightJs.res @@ -10,7 +10,7 @@ let renderHLJS = (~highlightedLines=[], ~darkmode=false, ~code: string, ~lang: s // If the language couldn't be parsed, we will fall back to text let options = {language: lang} let (lang, highlighted) = try (lang, highlight(~code, ~options)->valueGet) catch { - | Exn.Error(_) => ("text", code) + | JsExn(_) => ("text", code) } // Add line highlighting as well diff --git a/src/components/Search.res b/src/components/Search.res index 452e7cf31..b9ccbb103 100644 --- a/src/components/Search.res +++ b/src/components/Search.res @@ -51,7 +51,7 @@ let transformItems = (items: DocSearch.transformItems) => { items ->Array.filterMap(item => { let url = try WebAPI.URL.make(~url=item.url)->Some catch { - | Exn.Error(obj) => + | JsExn(obj) => Console.error2(`Failed to parse URL ${item.url}`, obj) None } diff --git a/yarn.lock b/yarn.lock index 6ccb0d181..bdd5ac415 100644 --- a/yarn.lock +++ b/yarn.lock @@ -274,7 +274,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.23.7, @babel/core@npm:^7.27.7, @babel/core@npm:^7.28.0": +"@babel/core@npm:^7.23.7, @babel/core@npm:^7.27.7": version: 7.28.5 resolution: "@babel/core@npm:7.28.5" dependencies: @@ -459,7 +459,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.6, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.7, @babel/parser@npm:^7.28.5": +"@babel/parser@npm:^7.23.6, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.7, @babel/parser@npm:^7.28.5": version: 7.28.5 resolution: "@babel/parser@npm:7.28.5" dependencies: @@ -504,28 +504,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-self@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-react-jsx-self@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/00a4f917b70a608f9aca2fb39aabe04a60aa33165a7e0105fd44b3a8531630eb85bf5572e9f242f51e6ad2fa38c2e7e780902176c863556c58b5ba6f6e164031 - languageName: node - linkType: hard - -"@babel/plugin-transform-react-jsx-source@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-react-jsx-source@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5e67b56c39c4d03e59e03ba80692b24c5a921472079b63af711b1d250fc37c1733a17069b63537f750f3e937ec44a42b1ee6a46cd23b1a0df5163b17f741f7f2 - languageName: node - linkType: hard - "@babel/plugin-transform-typescript@npm:^7.28.5": version: 7.28.5 resolution: "@babel/plugin-transform-typescript@npm:7.28.5" @@ -582,7 +560,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.23.6, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.7, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": +"@babel/types@npm:^7.23.6, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.7, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": version: 7.28.5 resolution: "@babel/types@npm:7.28.5" dependencies: @@ -592,6 +570,13 @@ __metadata: languageName: node linkType: hard +"@blazediff/core@npm:1.9.1": + version: 1.9.1 + resolution: "@blazediff/core@npm:1.9.1" + checksum: 10c0/fd45cdd0544002341d74831a179ef693a81414abd348c1ff0c01086c0ea03f5e5ee284c4e16c2e6fb3670c265f90a3d85752b9360320efa9a835928e604dae77 + languageName: node + linkType: hard + "@cloudflare/kv-asset-handler@npm:0.4.2": version: 0.4.2 resolution: "@cloudflare/kv-asset-handler@npm:0.4.2" @@ -867,7 +852,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/core@npm:^1.4.3, @emnapi/core@npm:^1.5.0, @emnapi/core@npm:^1.6.0": +"@emnapi/core@npm:^1.4.3": version: 1.7.1 resolution: "@emnapi/core@npm:1.7.1" dependencies: @@ -877,7 +862,17 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:^1.4.3, @emnapi/runtime@npm:^1.5.0, @emnapi/runtime@npm:^1.6.0, @emnapi/runtime@npm:^1.7.0": +"@emnapi/core@npm:^1.8.1": + version: 1.9.2 + resolution: "@emnapi/core@npm:1.9.2" + dependencies: + "@emnapi/wasi-threads": "npm:1.2.1" + tslib: "npm:^2.4.0" + checksum: 10c0/5500393f953951bad0768fafaa9191f2d938956b20c6d6a79e5ab696a613a25ce6ad23422bc18e86e6ce8deb147619d8d0d7d413a69f84adc01a6633cc353cd9 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.4.3, @emnapi/runtime@npm:^1.7.0": version: 1.7.1 resolution: "@emnapi/runtime@npm:1.7.1" dependencies: @@ -886,6 +881,15 @@ __metadata: languageName: node linkType: hard +"@emnapi/runtime@npm:^1.8.1": + version: 1.9.2 + resolution: "@emnapi/runtime@npm:1.9.2" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/61c3a59e0c36784558b8d58eb02bd04815aa5fb0dbfbaf84d1b3050a78aa0cc63ea129ae806bd1e48062bfeb7fc36eb0e5431740d62f64ea51bdf426404b8caa + languageName: node + linkType: hard + "@emnapi/wasi-threads@npm:1.1.0, @emnapi/wasi-threads@npm:^1.1.0": version: 1.1.0 resolution: "@emnapi/wasi-threads@npm:1.1.0" @@ -895,6 +899,15 @@ __metadata: languageName: node linkType: hard +"@emnapi/wasi-threads@npm:1.2.1": + version: 1.2.1 + resolution: "@emnapi/wasi-threads@npm:1.2.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/32fcfa81ab396533b2ec1f4082b1ff779a05d9c836bbbd3f4398405b0e6814c0d9503b7993130e37bc6941dbc1ded49f55e9700ae9ca4e803bab2b5bc5deb331 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/aix-ppc64@npm:0.25.12" @@ -909,13 +922,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/aix-ppc64@npm:0.27.3" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/android-arm64@npm:0.25.12" @@ -930,13 +936,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/android-arm64@npm:0.27.3" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/android-arm@npm:0.25.12" @@ -951,13 +950,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/android-arm@npm:0.27.3" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@esbuild/android-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/android-x64@npm:0.25.12" @@ -972,13 +964,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/android-x64@npm:0.27.3" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "@esbuild/darwin-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/darwin-arm64@npm:0.25.12" @@ -993,13 +978,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/darwin-arm64@npm:0.27.3" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/darwin-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/darwin-x64@npm:0.25.12" @@ -1014,13 +992,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/darwin-x64@npm:0.27.3" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@esbuild/freebsd-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/freebsd-arm64@npm:0.25.12" @@ -1035,13 +1006,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/freebsd-arm64@npm:0.27.3" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/freebsd-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/freebsd-x64@npm:0.25.12" @@ -1056,13 +1020,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/freebsd-x64@npm:0.27.3" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/linux-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-arm64@npm:0.25.12" @@ -1077,13 +1034,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-arm64@npm:0.27.3" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/linux-arm@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-arm@npm:0.25.12" @@ -1098,13 +1048,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-arm@npm:0.27.3" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@esbuild/linux-ia32@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-ia32@npm:0.25.12" @@ -1119,13 +1062,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-ia32@npm:0.27.3" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/linux-loong64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-loong64@npm:0.25.12" @@ -1140,13 +1076,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-loong64@npm:0.27.3" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - "@esbuild/linux-mips64el@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-mips64el@npm:0.25.12" @@ -1161,13 +1090,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-mips64el@npm:0.27.3" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "@esbuild/linux-ppc64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-ppc64@npm:0.25.12" @@ -1182,13 +1104,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-ppc64@npm:0.27.3" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/linux-riscv64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-riscv64@npm:0.25.12" @@ -1203,13 +1118,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-riscv64@npm:0.27.3" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "@esbuild/linux-s390x@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-s390x@npm:0.25.12" @@ -1224,13 +1132,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-s390x@npm:0.27.3" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "@esbuild/linux-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/linux-x64@npm:0.25.12" @@ -1245,13 +1146,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/linux-x64@npm:0.27.3" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "@esbuild/netbsd-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/netbsd-arm64@npm:0.25.12" @@ -1266,13 +1160,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/netbsd-arm64@npm:0.27.3" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/netbsd-x64@npm:0.25.12" @@ -1287,13 +1174,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/netbsd-x64@npm:0.27.3" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openbsd-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/openbsd-arm64@npm:0.25.12" @@ -1308,13 +1188,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/openbsd-arm64@npm:0.27.3" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/openbsd-x64@npm:0.25.12" @@ -1329,13 +1202,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/openbsd-x64@npm:0.27.3" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openharmony-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/openharmony-arm64@npm:0.25.12" @@ -1350,13 +1216,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/openharmony-arm64@npm:0.27.3" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/sunos-x64@npm:0.25.12" @@ -1371,13 +1230,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/sunos-x64@npm:0.27.3" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "@esbuild/win32-arm64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/win32-arm64@npm:0.25.12" @@ -1392,13 +1244,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/win32-arm64@npm:0.27.3" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/win32-ia32@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/win32-ia32@npm:0.25.12" @@ -1413,13 +1258,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/win32-ia32@npm:0.27.3" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/win32-x64@npm:0.25.12": version: 0.25.12 resolution: "@esbuild/win32-x64@npm:0.25.12" @@ -1434,13 +1272,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.27.3": - version: 0.27.3 - resolution: "@esbuild/win32-x64@npm:0.27.3" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@fastify/accept-negotiator@npm:^2.0.0": version: 2.0.1 resolution: "@fastify/accept-negotiator@npm:2.0.1" @@ -1909,7 +1740,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/remapping@npm:^2.3.4, @jridgewell/remapping@npm:^2.3.5": +"@jridgewell/remapping@npm:^2.3.5": version: 2.3.5 resolution: "@jridgewell/remapping@npm:2.3.5" dependencies: @@ -2066,14 +1897,15 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-runtime@npm:^1.0.7": - version: 1.0.7 - resolution: "@napi-rs/wasm-runtime@npm:1.0.7" +"@napi-rs/wasm-runtime@npm:^1.1.1": + version: 1.1.2 + resolution: "@napi-rs/wasm-runtime@npm:1.1.2" dependencies: - "@emnapi/core": "npm:^1.5.0" - "@emnapi/runtime": "npm:^1.5.0" "@tybys/wasm-util": "npm:^0.10.1" - checksum: 10c0/2d8635498136abb49d6dbf7395b78c63422292240963bf055f307b77aeafbde57ae2c0ceaaef215601531b36d6eb92a2cdd6f5ba90ed2aa8127c27aff9c4ae55 + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + checksum: 10c0/725c30ec9c480a8d0c1a6a4ce31dc6c830365d485e23ad560e143d1cb9db89a0c95fbb5b9d53c07121729817a3683db6f1ab65d7e4f38fa7482a11b15ef6c6fd languageName: node linkType: hard @@ -2198,22 +2030,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/git@npm:^4.1.0": - version: 4.1.0 - resolution: "@npmcli/git@npm:4.1.0" - dependencies: - "@npmcli/promise-spawn": "npm:^6.0.0" - lru-cache: "npm:^7.4.4" - npm-pick-manifest: "npm:^8.0.0" - proc-log: "npm:^3.0.0" - promise-inflight: "npm:^1.0.1" - promise-retry: "npm:^2.0.1" - semver: "npm:^7.3.5" - which: "npm:^3.0.0" - checksum: 10c0/78591ba8f03de3954a5b5b83533455696635a8f8140c74038685fec4ee28674783a5b34a3d43840b2c5f9aa37fd0dce57eaf4ef136b52a8ec2ee183af2e40724 - languageName: node - linkType: hard - "@npmcli/git@npm:^5.0.0": version: 5.0.8 resolution: "@npmcli/git@npm:5.0.8" @@ -2250,21 +2066,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/package-json@npm:^4.0.1": - version: 4.0.1 - resolution: "@npmcli/package-json@npm:4.0.1" - dependencies: - "@npmcli/git": "npm:^4.1.0" - glob: "npm:^10.2.2" - hosted-git-info: "npm:^6.1.1" - json-parse-even-better-errors: "npm:^3.0.0" - normalize-package-data: "npm:^5.0.0" - proc-log: "npm:^3.0.0" - semver: "npm:^7.5.3" - checksum: 10c0/61adec288372827e482d4c6bda8186e239b1419a6f018552a0444520720022fb2903d08438f32881fe2eccabb8cf29dcb1c5c5c62c4fc970d79ad71fe9a41e46 - languageName: node - linkType: hard - "@npmcli/package-json@npm:^5.1.1": version: 5.2.1 resolution: "@npmcli/package-json@npm:5.2.1" @@ -2280,15 +2081,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/promise-spawn@npm:^6.0.0": - version: 6.0.2 - resolution: "@npmcli/promise-spawn@npm:6.0.2" - dependencies: - which: "npm:^3.0.0" - checksum: 10c0/d0696b8d9f7e16562cd1e520e4919000164be042b5c9998a45b4e87d41d9619fcecf2a343621c6fa85ed2671cbe87ab07e381a7faea4e5132c371dbb05893f31 - languageName: node - linkType: hard - "@npmcli/promise-spawn@npm:^7.0.0": version: 7.0.2 resolution: "@npmcli/promise-spawn@npm:7.0.2" @@ -2412,6 +2204,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/types@npm:=0.122.0": + version: 0.122.0 + resolution: "@oxc-project/types@npm:0.122.0" + checksum: 10c0/2c64dd0db949426fd0c86d4f61eded5902e7b7b166356a825bd3a248aeaa29a495f78918f66ab78e99644b67bd7556096e2a8123cec74ca4141c604f424f4f74 + languageName: node + linkType: hard + "@oxc-project/types@npm:^0.74.0": version: 0.74.0 resolution: "@oxc-project/types@npm:0.74.0" @@ -2536,9 +2335,9 @@ __metadata: languageName: node linkType: hard -"@react-router/dev@npm:^7.8.1": - version: 7.9.6 - resolution: "@react-router/dev@npm:7.9.6" +"@react-router/dev@npm:^7.14.0": + version: 7.14.0 + resolution: "@react-router/dev@npm:7.14.0" dependencies: "@babel/core": "npm:^7.27.7" "@babel/generator": "npm:^7.27.5" @@ -2547,9 +2346,8 @@ __metadata: "@babel/preset-typescript": "npm:^7.27.1" "@babel/traverse": "npm:^7.27.7" "@babel/types": "npm:^7.27.7" - "@npmcli/package-json": "npm:^4.0.1" - "@react-router/node": "npm:7.9.6" - "@remix-run/node-fetch-server": "npm:^0.9.0" + "@react-router/node": "npm:7.14.0" + "@remix-run/node-fetch-server": "npm:^0.13.0" arg: "npm:^5.0.1" babel-dead-code-elimination: "npm:^1.0.6" chokidar: "npm:^4.0.0" @@ -2562,46 +2360,50 @@ __metadata: p-map: "npm:^7.0.3" pathe: "npm:^1.1.2" picocolors: "npm:^1.1.1" + pkg-types: "npm:^2.3.0" prettier: "npm:^3.6.2" react-refresh: "npm:^0.14.0" semver: "npm:^7.3.7" tinyglobby: "npm:^0.2.14" - valibot: "npm:^1.1.0" + valibot: "npm:^1.2.0" vite-node: "npm:^3.2.2" peerDependencies: - "@react-router/serve": ^7.9.6 - "@vitejs/plugin-rsc": "*" - react-router: ^7.9.6 + "@react-router/serve": ^7.14.0 + "@vitejs/plugin-rsc": ~0.5.21 + react-router: ^7.14.0 + react-server-dom-webpack: ^19.2.3 typescript: ^5.1.0 - vite: ^5.1.0 || ^6.0.0 || ^7.0.0 + vite: ^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 wrangler: ^3.28.2 || ^4.0.0 peerDependenciesMeta: "@react-router/serve": optional: true "@vitejs/plugin-rsc": optional: true + react-server-dom-webpack: + optional: true typescript: optional: true wrangler: optional: true bin: react-router: bin.js - checksum: 10c0/018f3cc2a0fd92db815b949599bec6bab2ec9840016c06322c93b5c9606c7e3210b0c4a550c2c8e78dc431e9ef9e90b418a3b23bdae2a76a4432caba4fe88e4d + checksum: 10c0/e116cbd22de19ac189423bf8aa58ac6fce3325595b6d9d224678a9843012b8a4b477161cd614d06da1046d95a6594e410a5c3b1b7827d47c8c35f5f29e8035c8 languageName: node linkType: hard -"@react-router/node@npm:7.9.6, @react-router/node@npm:^7.8.1": - version: 7.9.6 - resolution: "@react-router/node@npm:7.9.6" +"@react-router/node@npm:7.14.0, @react-router/node@npm:^7.14.0": + version: 7.14.0 + resolution: "@react-router/node@npm:7.14.0" dependencies: "@mjackson/node-fetch-server": "npm:^0.2.0" peerDependencies: - react-router: 7.9.6 + react-router: 7.14.0 typescript: ^5.1.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/0c4680c04acd9989e6b7d5af4cad6c69dc713d17d1fe9d9f7064ca8703fb87740f7a6b97aea8c0f459e121f9d6008b3ddf6cde1c818c512ec14bca91b73c1d62 + checksum: 10c0/b2895ba6a191395d4eece03c26c7cbcdba1ddc263b3b10be939dec7739f74384778cac216f679e3d777ba0a9d9b810bc353ef1177791e18fcd005c04d94da918 languageName: node linkType: hard @@ -2634,10 +2436,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/node-fetch-server@npm:^0.9.0": - version: 0.9.0 - resolution: "@remix-run/node-fetch-server@npm:0.9.0" - checksum: 10c0/b0ac06bb9ab6e225668f75b60c6fe994ac8c310f9b1333de2b8e1d0b3b1d80ce4bedfc3940d0c20a94b524bba7424eb4d7e70376d5c97439f11af5193322fd58 +"@remix-run/node-fetch-server@npm:^0.13.0": + version: 0.13.0 + resolution: "@remix-run/node-fetch-server@npm:0.13.0" + checksum: 10c0/ad490eb8173d019afcbbce24f694c9a170a658aacf5d073bb45c7cc3d1665e5e4191bf40dcd9f0cf8803f0441a9ef0b09776d5facc00080eddc331426eaca6a7 languageName: node linkType: hard @@ -2654,48 +2456,48 @@ __metadata: languageName: node linkType: hard -"@rescript/darwin-arm64@npm:12.0.0": - version: 12.0.0 - resolution: "@rescript/darwin-arm64@npm:12.0.0" +"@rescript/darwin-arm64@npm:12.2.0": + version: 12.2.0 + resolution: "@rescript/darwin-arm64@npm:12.2.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rescript/darwin-x64@npm:12.0.0": - version: 12.0.0 - resolution: "@rescript/darwin-x64@npm:12.0.0" +"@rescript/darwin-x64@npm:12.2.0": + version: 12.2.0 + resolution: "@rescript/darwin-x64@npm:12.2.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rescript/linux-arm64@npm:12.0.0": - version: 12.0.0 - resolution: "@rescript/linux-arm64@npm:12.0.0" +"@rescript/linux-arm64@npm:12.2.0": + version: 12.2.0 + resolution: "@rescript/linux-arm64@npm:12.2.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@rescript/linux-x64@npm:12.0.0": - version: 12.0.0 - resolution: "@rescript/linux-x64@npm:12.0.0" +"@rescript/linux-x64@npm:12.2.0": + version: 12.2.0 + resolution: "@rescript/linux-x64@npm:12.2.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@rescript/react@npm:^0.14.0": - version: 0.14.0 - resolution: "@rescript/react@npm:0.14.0" +"@rescript/react@npm:^0.14.2": + version: 0.14.2 + resolution: "@rescript/react@npm:0.14.2" peerDependencies: - react: ">=19.0.0" - react-dom: ">=19.0.0" - checksum: 10c0/9463eb027df1d28aab60879d3779f0832270580b381fc7b980387ffe080b821548242fef419cfe0a625d9d9866c4f7d676ed77f500374c22b2affd83ebd8bbaa + react: ">=19.1.0" + react-dom: ">=19.1.0" + checksum: 10c0/6fbc0614ac4e4e5ad37abe474dcc137dc8cb7c3351dbc9afa4de92f080614e78809278a034c52da1676e89c299ab07154760d8b80f68f0a5cfd284e22035f713 languageName: node linkType: hard -"@rescript/runtime@npm:12.0.0": - version: 12.0.0 - resolution: "@rescript/runtime@npm:12.0.0" - checksum: 10c0/32121f3a78154cf9ab8ae4939819b9807d4a70552d7c2594872f838456c63026eddca4d6b562220fe5431d56afa96e0e276f09e5d9028b15b03f2570a52f1d30 +"@rescript/runtime@npm:12.2.0": + version: 12.2.0 + resolution: "@rescript/runtime@npm:12.2.0" + checksum: 10c0/59c0194ae52fbbaadc736bbf1cfac1ed6ffde92b2346e36b9f1a29bb136e38b7ef203c780647203912fa657d293eab5c4e208f04dd65ed4bf47d53843980f7e1 languageName: node linkType: hard @@ -2708,17 +2510,131 @@ __metadata: languageName: node linkType: hard -"@rescript/win32-x64@npm:12.0.0": - version: 12.0.0 - resolution: "@rescript/win32-x64@npm:12.0.0" +"@rescript/win32-x64@npm:12.2.0": + version: 12.2.0 + resolution: "@rescript/win32-x64@npm:12.2.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rolldown/pluginutils@npm:1.0.0-beta.27": - version: 1.0.0-beta.27 - resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27" - checksum: 10c0/9658f235b345201d4f6bfb1f32da9754ca164f892d1cb68154fe5f53c1df42bd675ecd409836dff46884a7847d6c00bdc38af870f7c81e05bba5c2645eb4ab9c +"@rolldown/binding-android-arm64@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.12" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.12" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.12" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.12" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.12" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.12" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.12" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.12" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.12" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.12" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.12" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.1.1" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.12" + checksum: 10c0/f785d1180ea4876bf6a6a67135822808d1c07f902409524ff1088779f7d5318f6e603d281fb107a5145c1ca54b7cabebd359629ec474ebbc2812f2cf53db4023 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-rc.7": + version: 1.0.0-rc.7 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.7" + checksum: 10c0/9d5490b5805b25bcd1720ca01c4c032b55a0ef953dab36a8dd42c568e82214576baa464f3027cd5dff3fabcfbe3bf3db2251d12b60220f5d1cd2ffde5ee37082 languageName: node linkType: hard @@ -2897,6 +2813,13 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.1.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 + languageName: node + linkType: hard + "@swc/helpers@npm:^0.5.0": version: 0.5.17 resolution: "@swc/helpers@npm:0.5.17" @@ -2906,128 +2829,128 @@ __metadata: languageName: node linkType: hard -"@tailwindcss/node@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/node@npm:4.1.17" +"@tailwindcss/node@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/node@npm:4.2.2" dependencies: - "@jridgewell/remapping": "npm:^2.3.4" - enhanced-resolve: "npm:^5.18.3" + "@jridgewell/remapping": "npm:^2.3.5" + enhanced-resolve: "npm:^5.19.0" jiti: "npm:^2.6.1" - lightningcss: "npm:1.30.2" + lightningcss: "npm:1.32.0" magic-string: "npm:^0.30.21" source-map-js: "npm:^1.2.1" - tailwindcss: "npm:4.1.17" - checksum: 10c0/80b542e9b7eb09499dd14d65fd7d9544321d6bcdc00d29914396001d00e009906392cf493d20cc655dfd42769c823060cb9bf2eacacb43838a47e897634a446b + tailwindcss: "npm:4.2.2" + checksum: 10c0/4c0019355cd85a08f93ba3e179de37b83cc233b8ded4bd7714e633f89dd108928742e50966593257c2c1ab8db8914ea187dae007b5c692c869ceace11aeccede languageName: node linkType: hard -"@tailwindcss/oxide-android-arm64@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-android-arm64@npm:4.1.17" +"@tailwindcss/oxide-android-arm64@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-android-arm64@npm:4.2.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@tailwindcss/oxide-darwin-arm64@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-darwin-arm64@npm:4.1.17" +"@tailwindcss/oxide-darwin-arm64@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-darwin-arm64@npm:4.2.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@tailwindcss/oxide-darwin-x64@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-darwin-x64@npm:4.1.17" +"@tailwindcss/oxide-darwin-x64@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-darwin-x64@npm:4.2.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@tailwindcss/oxide-freebsd-x64@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-freebsd-x64@npm:4.1.17" +"@tailwindcss/oxide-freebsd-x64@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-freebsd-x64@npm:4.2.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@tailwindcss/oxide-linux-arm-gnueabihf@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-linux-arm-gnueabihf@npm:4.1.17" +"@tailwindcss/oxide-linux-arm-gnueabihf@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-linux-arm-gnueabihf@npm:4.2.2" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@tailwindcss/oxide-linux-arm64-gnu@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-linux-arm64-gnu@npm:4.1.17" +"@tailwindcss/oxide-linux-arm64-gnu@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-linux-arm64-gnu@npm:4.2.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@tailwindcss/oxide-linux-arm64-musl@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-linux-arm64-musl@npm:4.1.17" +"@tailwindcss/oxide-linux-arm64-musl@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-linux-arm64-musl@npm:4.2.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@tailwindcss/oxide-linux-x64-gnu@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-linux-x64-gnu@npm:4.1.17" +"@tailwindcss/oxide-linux-x64-gnu@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-linux-x64-gnu@npm:4.2.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@tailwindcss/oxide-linux-x64-musl@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-linux-x64-musl@npm:4.1.17" +"@tailwindcss/oxide-linux-x64-musl@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-linux-x64-musl@npm:4.2.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@tailwindcss/oxide-wasm32-wasi@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-wasm32-wasi@npm:4.1.17" +"@tailwindcss/oxide-wasm32-wasi@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-wasm32-wasi@npm:4.2.2" dependencies: - "@emnapi/core": "npm:^1.6.0" - "@emnapi/runtime": "npm:^1.6.0" + "@emnapi/core": "npm:^1.8.1" + "@emnapi/runtime": "npm:^1.8.1" "@emnapi/wasi-threads": "npm:^1.1.0" - "@napi-rs/wasm-runtime": "npm:^1.0.7" + "@napi-rs/wasm-runtime": "npm:^1.1.1" "@tybys/wasm-util": "npm:^0.10.1" - tslib: "npm:^2.4.0" + tslib: "npm:^2.8.1" conditions: cpu=wasm32 languageName: node linkType: hard -"@tailwindcss/oxide-win32-arm64-msvc@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-win32-arm64-msvc@npm:4.1.17" +"@tailwindcss/oxide-win32-arm64-msvc@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-win32-arm64-msvc@npm:4.2.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@tailwindcss/oxide-win32-x64-msvc@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide-win32-x64-msvc@npm:4.1.17" +"@tailwindcss/oxide-win32-x64-msvc@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide-win32-x64-msvc@npm:4.2.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@tailwindcss/oxide@npm:4.1.17": - version: 4.1.17 - resolution: "@tailwindcss/oxide@npm:4.1.17" - dependencies: - "@tailwindcss/oxide-android-arm64": "npm:4.1.17" - "@tailwindcss/oxide-darwin-arm64": "npm:4.1.17" - "@tailwindcss/oxide-darwin-x64": "npm:4.1.17" - "@tailwindcss/oxide-freebsd-x64": "npm:4.1.17" - "@tailwindcss/oxide-linux-arm-gnueabihf": "npm:4.1.17" - "@tailwindcss/oxide-linux-arm64-gnu": "npm:4.1.17" - "@tailwindcss/oxide-linux-arm64-musl": "npm:4.1.17" - "@tailwindcss/oxide-linux-x64-gnu": "npm:4.1.17" - "@tailwindcss/oxide-linux-x64-musl": "npm:4.1.17" - "@tailwindcss/oxide-wasm32-wasi": "npm:4.1.17" - "@tailwindcss/oxide-win32-arm64-msvc": "npm:4.1.17" - "@tailwindcss/oxide-win32-x64-msvc": "npm:4.1.17" +"@tailwindcss/oxide@npm:4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/oxide@npm:4.2.2" + dependencies: + "@tailwindcss/oxide-android-arm64": "npm:4.2.2" + "@tailwindcss/oxide-darwin-arm64": "npm:4.2.2" + "@tailwindcss/oxide-darwin-x64": "npm:4.2.2" + "@tailwindcss/oxide-freebsd-x64": "npm:4.2.2" + "@tailwindcss/oxide-linux-arm-gnueabihf": "npm:4.2.2" + "@tailwindcss/oxide-linux-arm64-gnu": "npm:4.2.2" + "@tailwindcss/oxide-linux-arm64-musl": "npm:4.2.2" + "@tailwindcss/oxide-linux-x64-gnu": "npm:4.2.2" + "@tailwindcss/oxide-linux-x64-musl": "npm:4.2.2" + "@tailwindcss/oxide-wasm32-wasi": "npm:4.2.2" + "@tailwindcss/oxide-win32-arm64-msvc": "npm:4.2.2" + "@tailwindcss/oxide-win32-x64-msvc": "npm:4.2.2" dependenciesMeta: "@tailwindcss/oxide-android-arm64": optional: true @@ -3053,20 +2976,20 @@ __metadata: optional: true "@tailwindcss/oxide-win32-x64-msvc": optional: true - checksum: 10c0/cdd292760dde90976ac5cd486600687f9ac4043d9796001b356d43bfc4d0e1972d23844fe045970afdc4b4cda8451f262db15a9da4152c26e2b696a985e3686c + checksum: 10c0/22f78d73ffcec2d0d91f9fbfc29fed23c260e3e53f510f0b2598e322bf56a92ceb7e6f5a1c88ad1e3c7cfee9dd8d39285c411de5ec3225cdae2cbfdb737862e5 languageName: node linkType: hard -"@tailwindcss/vite@npm:^4.1.13": - version: 4.1.17 - resolution: "@tailwindcss/vite@npm:4.1.17" +"@tailwindcss/vite@npm:^4.2.2": + version: 4.2.2 + resolution: "@tailwindcss/vite@npm:4.2.2" dependencies: - "@tailwindcss/node": "npm:4.1.17" - "@tailwindcss/oxide": "npm:4.1.17" - tailwindcss: "npm:4.1.17" + "@tailwindcss/node": "npm:4.2.2" + "@tailwindcss/oxide": "npm:4.2.2" + tailwindcss: "npm:4.2.2" peerDependencies: - vite: ^5.2.0 || ^6 || ^7 - checksum: 10c0/47d9bdfb7bf7d2df0661b50e91656779863146cca97571e21e2c3f9351f468c27cbc7ed1d1d6c373f1e721dca66d32a3f12f77e9d3e74bed344e27afec199ad3 + vite: ^5.2.0 || ^6 || ^7 || ^8 + checksum: 10c0/f6ec4b0d6a8e79208873fb357a8ed9b6fd8eb3000d153ec2590c61dba5bfbe79c0951a215d187958d2b8a3c5b45c25ebcefac7a6dea882bb27b4b2898c54266f languageName: node linkType: hard @@ -3109,47 +3032,6 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:^7.20.5": - version: 7.20.5 - resolution: "@types/babel__core@npm:7.20.5" - dependencies: - "@babel/parser": "npm:^7.20.7" - "@babel/types": "npm:^7.20.7" - "@types/babel__generator": "npm:*" - "@types/babel__template": "npm:*" - "@types/babel__traverse": "npm:*" - checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff - languageName: node - linkType: hard - -"@types/babel__generator@npm:*": - version: 7.27.0 - resolution: "@types/babel__generator@npm:7.27.0" - dependencies: - "@babel/types": "npm:^7.0.0" - checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd - languageName: node - linkType: hard - -"@types/babel__template@npm:*": - version: 7.4.4 - resolution: "@types/babel__template@npm:7.4.4" - dependencies: - "@babel/parser": "npm:^7.1.0" - "@babel/types": "npm:^7.0.0" - checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b - languageName: node - linkType: hard - -"@types/babel__traverse@npm:*": - version: 7.28.0 - resolution: "@types/babel__traverse@npm:7.28.0" - dependencies: - "@babel/types": "npm:^7.28.2" - checksum: 10c0/b52d7d4e8fc6a9018fe7361c4062c1c190f5778cf2466817cb9ed19d69fbbb54f9a85ffedeb748ed8062d2cf7d4cc088ee739848f47c57740de1c48cbf0d0994 - languageName: node - linkType: hard - "@types/chai@npm:^5.2.2": version: 5.2.3 resolution: "@types/chai@npm:5.2.3" @@ -3265,12 +3147,12 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^19.2.2": - version: 19.2.7 - resolution: "@types/react@npm:19.2.7" +"@types/react@npm:^19.2.14": + version: 19.2.14 + resolution: "@types/react@npm:19.2.14" dependencies: csstype: "npm:^3.2.2" - checksum: 10c0/a7b75f1f9fcb34badd6f84098be5e35a0aeca614bc91f93d2698664c0b2ba5ad128422bd470ada598238cebe4f9e604a752aead7dc6f5a92261d0c7f9b27cfd1 + checksum: 10c0/7d25bf41b57719452d86d2ac0570b659210402707313a36ee612666bf11275a1c69824f8c3ee1fdca077ccfe15452f6da8f1224529b917050eb2d861e52b59b7 languageName: node linkType: hard @@ -3323,134 +3205,138 @@ __metadata: languageName: node linkType: hard -"@vitejs/plugin-react@npm:^4.7.0": - version: 4.7.0 - resolution: "@vitejs/plugin-react@npm:4.7.0" - dependencies: - "@babel/core": "npm:^7.28.0" - "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" - "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" - "@rolldown/pluginutils": "npm:1.0.0-beta.27" - "@types/babel__core": "npm:^7.20.5" - react-refresh: "npm:^0.17.0" +"@vitejs/plugin-react@npm:^6.0.1": + version: 6.0.1 + resolution: "@vitejs/plugin-react@npm:6.0.1" + dependencies: + "@rolldown/pluginutils": "npm:1.0.0-rc.7" peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/692f23960972879485d647713663ec299c478222c96567d60285acf7c7dc5c178e71abfe9d2eefddef1eeb01514dacbc2ed68aad84628debf9c7116134734253 + "@rolldown/plugin-babel": ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + "@rolldown/plugin-babel": + optional: true + babel-plugin-react-compiler: + optional: true + checksum: 10c0/6c42f53a970cb6b0776ba5b4203bb01690ac564c56fca706d4037b50aec965ddc0f11530ab58ab2cd0fbe8c12e14cff6966b22d90391283b4a53294e3ddd478d languageName: node linkType: hard -"@vitest/browser-playwright@npm:^4.0.18": - version: 4.0.18 - resolution: "@vitest/browser-playwright@npm:4.0.18" +"@vitest/browser-playwright@npm:^4.1.2": + version: 4.1.2 + resolution: "@vitest/browser-playwright@npm:4.1.2" dependencies: - "@vitest/browser": "npm:4.0.18" - "@vitest/mocker": "npm:4.0.18" - tinyrainbow: "npm:^3.0.3" + "@vitest/browser": "npm:4.1.2" + "@vitest/mocker": "npm:4.1.2" + tinyrainbow: "npm:^3.1.0" peerDependencies: playwright: "*" - vitest: 4.0.18 + vitest: 4.1.2 peerDependenciesMeta: playwright: optional: false - checksum: 10c0/505fafe6f957d020b74914ed328de57cba0be65ff82810da85297523776a0d7389669660e58734a416fc09ce262632b4d2cf257a9e8ab1115b695d133bba7bb5 + checksum: 10c0/701a750a16059be20dddb6884e9aaad43002e1d08da94df31b0dba9abed33d0c3faba8b1c56b7da25b61b0faab1e72597cbcedd2b969f4f6139b2e17a3fd4d06 languageName: node linkType: hard -"@vitest/browser@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/browser@npm:4.0.18" +"@vitest/browser@npm:4.1.2": + version: 4.1.2 + resolution: "@vitest/browser@npm:4.1.2" dependencies: - "@vitest/mocker": "npm:4.0.18" - "@vitest/utils": "npm:4.0.18" + "@blazediff/core": "npm:1.9.1" + "@vitest/mocker": "npm:4.1.2" + "@vitest/utils": "npm:4.1.2" magic-string: "npm:^0.30.21" - pixelmatch: "npm:7.1.0" pngjs: "npm:^7.0.0" sirv: "npm:^3.0.2" - tinyrainbow: "npm:^3.0.3" - ws: "npm:^8.18.3" + tinyrainbow: "npm:^3.1.0" + ws: "npm:^8.19.0" peerDependencies: - vitest: 4.0.18 - checksum: 10c0/6b7bda92fa2e8c68de3e51c97322161484c3f1dd7a7417cdeabb4f1d98eab7dba96c156ac4282ea537c58d55cc0e5959abb4b9d90d3823b3cc3071c3f7460633 + vitest: 4.1.2 + checksum: 10c0/8ff656df7c3796f24b38800f42cc59902b15196556ef1df1cf931faf0b095db9677109c2e855ed8915c36bc6aae804b4c53e22c069c749ed2b7e16d8eefddde5 languageName: node linkType: hard -"@vitest/expect@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/expect@npm:4.0.18" +"@vitest/expect@npm:4.1.2": + version: 4.1.2 + resolution: "@vitest/expect@npm:4.1.2" dependencies: - "@standard-schema/spec": "npm:^1.0.0" + "@standard-schema/spec": "npm:^1.1.0" "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.0.18" - "@vitest/utils": "npm:4.0.18" - chai: "npm:^6.2.1" - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/123b0aa111682e82ec5289186df18037b1a1768700e468ee0f9879709aaa320cf790463c15c0d8ee10df92b402f4394baf5d27797e604d78e674766d87bcaadc + "@vitest/spy": "npm:4.1.2" + "@vitest/utils": "npm:4.1.2" + chai: "npm:^6.2.2" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/e238c833b5555d31b074545807956d5e874a1ef725525ecc99f1885b71b230b2127d40d8d142a7253666b8565d5806723853e85e0e99265520ec7506fdc5890c languageName: node linkType: hard -"@vitest/mocker@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/mocker@npm:4.0.18" +"@vitest/mocker@npm:4.1.2": + version: 4.1.2 + resolution: "@vitest/mocker@npm:4.1.2" dependencies: - "@vitest/spy": "npm:4.0.18" + "@vitest/spy": "npm:4.1.2" estree-walker: "npm:^3.0.3" magic-string: "npm:^0.30.21" peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - checksum: 10c0/fb0a257e7e167759d4ad228d53fa7bad2267586459c4a62188f2043dd7163b4b02e1e496dc3c227837f776e7d73d6c4343613e89e7da379d9d30de8260f1ee4b + checksum: 10c0/f23094f3c7e1e5af42e6a468f0815c1ecdcab85cb3a56ab6f3f214a9808a40271467d4352cae972482b9738cc31c62c7312d8b0da227d6ea03d2b3aacb8d385f languageName: node linkType: hard -"@vitest/pretty-format@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/pretty-format@npm:4.0.18" +"@vitest/pretty-format@npm:4.1.2": + version: 4.1.2 + resolution: "@vitest/pretty-format@npm:4.1.2" dependencies: - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/0086b8c88eeca896d8e4b98fcdef452c8041a1b63eb9e85d3e0bcc96c8aa76d8e9e0b6990ebb0bb0a697c4ebab347e7735888b24f507dbff2742ddce7723fd94 + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/6f57519c707e6a3d1ff8630ca87ce78fda9bf7bb33f6e4a0c775a8b510f2a6cee109849e2cdb736b0280681c567bd03e4cff724cbf0962950c9ff81377f0b2bc languageName: node linkType: hard -"@vitest/runner@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/runner@npm:4.0.18" +"@vitest/runner@npm:4.1.2": + version: 4.1.2 + resolution: "@vitest/runner@npm:4.1.2" dependencies: - "@vitest/utils": "npm:4.0.18" + "@vitest/utils": "npm:4.1.2" pathe: "npm:^2.0.3" - checksum: 10c0/fdb4afa411475133c05ba266c8092eaf1e56cbd5fb601f92ec6ccb9bab7ca52e06733ee8626599355cba4ee71cb3a8f28c84d3b69dc972e41047edc50229bc01 + checksum: 10c0/35654a87bd27983443adc24d68529d624f7d70e0386176741dc5bcc4188b86a70af2c512405d7e97aa45c16d83e1c8566c1f99c8440430f95557275f18612d21 languageName: node linkType: hard -"@vitest/snapshot@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/snapshot@npm:4.0.18" +"@vitest/snapshot@npm:4.1.2": + version: 4.1.2 + resolution: "@vitest/snapshot@npm:4.1.2" dependencies: - "@vitest/pretty-format": "npm:4.0.18" + "@vitest/pretty-format": "npm:4.1.2" + "@vitest/utils": "npm:4.1.2" magic-string: "npm:^0.30.21" pathe: "npm:^2.0.3" - checksum: 10c0/d3bfefa558db9a69a66886ace6575eb96903a5ba59f4d9a5d0fecb4acc2bb8dbb443ef409f5ac1475f2e1add30bd1d71280f98912da35e89c75829df9e84ea43 + checksum: 10c0/6d20e92386937afddbc81344211e554b83a559e20fb10c1deb0b1c3532994dc9fc62d816706ac835bdb737eb1ab02e9c0bc9de80dd8316060e1e0aaa447ba48f languageName: node linkType: hard -"@vitest/spy@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/spy@npm:4.0.18" - checksum: 10c0/6de537890b3994fcadb8e8d8ac05942320ae184f071ec395d978a5fba7fa928cbb0c5de85af86a1c165706c466e840de8779eaff8c93450c511c7abaeb9b8a4e +"@vitest/spy@npm:4.1.2": + version: 4.1.2 + resolution: "@vitest/spy@npm:4.1.2" + checksum: 10c0/2b5888d536d3e2083c5f8939763e6d780c2c03cc60e1ab45f9d04eacf14467acb9724cae1c4778e4c06426d49d04517e190122882953054a4b13fda44780bb14 languageName: node linkType: hard -"@vitest/utils@npm:4.0.18": - version: 4.0.18 - resolution: "@vitest/utils@npm:4.0.18" +"@vitest/utils@npm:4.1.2": + version: 4.1.2 + resolution: "@vitest/utils@npm:4.1.2" dependencies: - "@vitest/pretty-format": "npm:4.0.18" - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/4a3c43c1421eb90f38576926496f6c80056167ba111e63f77cf118983902673737a1a38880b890d7c06ec0a12475024587344ee502b3c43093781533022f2aeb + "@vitest/pretty-format": "npm:4.1.2" + convert-source-map: "npm:^2.0.0" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/d96475e0703b6e5208c6c0f570c1235278cbac3f3913a9aa4203a3e617c9eaca85a184bfd5d13cf366b84754df787ab8bc85242c5e0c63105ee7176c186a2136 languageName: node linkType: hard @@ -4021,7 +3907,7 @@ __metadata: languageName: node linkType: hard -"chai@npm:^6.2.1": +"chai@npm:^6.2.2": version: 6.2.2 resolution: "chai@npm:6.2.2" checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 @@ -4225,6 +4111,13 @@ __metadata: languageName: node linkType: hard +"confbox@npm:^0.2.2": + version: 0.2.4 + resolution: "confbox@npm:0.2.4" + checksum: 10c0/4c36af33d9df7034300c452f7b289179264493bd0671fa81b995a0d70dc897b1d37f1af10d3ffb187f178d17ba1ed2ba167ed0f599ba3a139c271205dd553f73 + languageName: node + linkType: hard + "content-disposition@npm:0.5.4, content-disposition@npm:^0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" @@ -4668,13 +4561,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.18.3": - version: 5.18.3 - resolution: "enhanced-resolve@npm:5.18.3" +"enhanced-resolve@npm:^5.19.0": + version: 5.20.1 + resolution: "enhanced-resolve@npm:5.20.1" dependencies: graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.2.0" - checksum: 10c0/d413c23c2d494e4c1c9c9ac7d60b812083dc6d446699ed495e69c920988af0a3c66bf3f8d0e7a45cb1686c2d4c1df9f4e7352d973f5b56fe63d8d711dd0ccc54 + tapable: "npm:^2.3.0" + checksum: 10c0/c6503ee1b2d725843e047e774445ecb12b779aa52db25d11ebe18d4b3adc148d3d993d2038b3d0c38ad836c9c4b3930fbc55df42f72b44785e2f94e5530eda69 languageName: node linkType: hard @@ -4798,6 +4691,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^2.0.0": + version: 2.0.0 + resolution: "es-module-lexer@npm:2.0.0" + checksum: 10c0/ae78dbbd43035a4b972c46cfb6877e374ea290adfc62bc2f5a083fea242c0b2baaab25c5886af86be55f092f4a326741cb94334cd3c478c383fdc8a9ec5ff817 + languageName: node + linkType: hard + "es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": version: 1.1.1 resolution: "es-object-atoms@npm:1.1.1" @@ -4862,117 +4762,28 @@ __metadata: "@esbuild/android-arm": "npm:0.27.0" "@esbuild/android-arm64": "npm:0.27.0" "@esbuild/android-x64": "npm:0.27.0" - "@esbuild/darwin-arm64": "npm:0.27.0" - "@esbuild/darwin-x64": "npm:0.27.0" - "@esbuild/freebsd-arm64": "npm:0.27.0" - "@esbuild/freebsd-x64": "npm:0.27.0" - "@esbuild/linux-arm": "npm:0.27.0" - "@esbuild/linux-arm64": "npm:0.27.0" - "@esbuild/linux-ia32": "npm:0.27.0" - "@esbuild/linux-loong64": "npm:0.27.0" - "@esbuild/linux-mips64el": "npm:0.27.0" - "@esbuild/linux-ppc64": "npm:0.27.0" - "@esbuild/linux-riscv64": "npm:0.27.0" - "@esbuild/linux-s390x": "npm:0.27.0" - "@esbuild/linux-x64": "npm:0.27.0" - "@esbuild/netbsd-arm64": "npm:0.27.0" - "@esbuild/netbsd-x64": "npm:0.27.0" - "@esbuild/openbsd-arm64": "npm:0.27.0" - "@esbuild/openbsd-x64": "npm:0.27.0" - "@esbuild/openharmony-arm64": "npm:0.27.0" - "@esbuild/sunos-x64": "npm:0.27.0" - "@esbuild/win32-arm64": "npm:0.27.0" - "@esbuild/win32-ia32": "npm:0.27.0" - "@esbuild/win32-x64": "npm:0.27.0" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/a3a1deec285337b7dfe25cbb9aa8765d27a0192b610a8477a39bf5bd907a6bdb75e98898b61fb4337114cfadb13163bd95977db14e241373115f548e235b40a2 - languageName: node - linkType: hard - -"esbuild@npm:^0.25.0": - version: 0.25.12 - resolution: "esbuild@npm:0.25.12" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.12" - "@esbuild/android-arm": "npm:0.25.12" - "@esbuild/android-arm64": "npm:0.25.12" - "@esbuild/android-x64": "npm:0.25.12" - "@esbuild/darwin-arm64": "npm:0.25.12" - "@esbuild/darwin-x64": "npm:0.25.12" - "@esbuild/freebsd-arm64": "npm:0.25.12" - "@esbuild/freebsd-x64": "npm:0.25.12" - "@esbuild/linux-arm": "npm:0.25.12" - "@esbuild/linux-arm64": "npm:0.25.12" - "@esbuild/linux-ia32": "npm:0.25.12" - "@esbuild/linux-loong64": "npm:0.25.12" - "@esbuild/linux-mips64el": "npm:0.25.12" - "@esbuild/linux-ppc64": "npm:0.25.12" - "@esbuild/linux-riscv64": "npm:0.25.12" - "@esbuild/linux-s390x": "npm:0.25.12" - "@esbuild/linux-x64": "npm:0.25.12" - "@esbuild/netbsd-arm64": "npm:0.25.12" - "@esbuild/netbsd-x64": "npm:0.25.12" - "@esbuild/openbsd-arm64": "npm:0.25.12" - "@esbuild/openbsd-x64": "npm:0.25.12" - "@esbuild/openharmony-arm64": "npm:0.25.12" - "@esbuild/sunos-x64": "npm:0.25.12" - "@esbuild/win32-arm64": "npm:0.25.12" - "@esbuild/win32-ia32": "npm:0.25.12" - "@esbuild/win32-x64": "npm:0.25.12" + "@esbuild/darwin-arm64": "npm:0.27.0" + "@esbuild/darwin-x64": "npm:0.27.0" + "@esbuild/freebsd-arm64": "npm:0.27.0" + "@esbuild/freebsd-x64": "npm:0.27.0" + "@esbuild/linux-arm": "npm:0.27.0" + "@esbuild/linux-arm64": "npm:0.27.0" + "@esbuild/linux-ia32": "npm:0.27.0" + "@esbuild/linux-loong64": "npm:0.27.0" + "@esbuild/linux-mips64el": "npm:0.27.0" + "@esbuild/linux-ppc64": "npm:0.27.0" + "@esbuild/linux-riscv64": "npm:0.27.0" + "@esbuild/linux-s390x": "npm:0.27.0" + "@esbuild/linux-x64": "npm:0.27.0" + "@esbuild/netbsd-arm64": "npm:0.27.0" + "@esbuild/netbsd-x64": "npm:0.27.0" + "@esbuild/openbsd-arm64": "npm:0.27.0" + "@esbuild/openbsd-x64": "npm:0.27.0" + "@esbuild/openharmony-arm64": "npm:0.27.0" + "@esbuild/sunos-x64": "npm:0.27.0" + "@esbuild/win32-arm64": "npm:0.27.0" + "@esbuild/win32-ia32": "npm:0.27.0" + "@esbuild/win32-x64": "npm:0.27.0" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -5028,40 +4839,40 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b + checksum: 10c0/a3a1deec285337b7dfe25cbb9aa8765d27a0192b610a8477a39bf5bd907a6bdb75e98898b61fb4337114cfadb13163bd95977db14e241373115f548e235b40a2 languageName: node linkType: hard -"esbuild@npm:^0.27.0": - version: 0.27.3 - resolution: "esbuild@npm:0.27.3" - dependencies: - "@esbuild/aix-ppc64": "npm:0.27.3" - "@esbuild/android-arm": "npm:0.27.3" - "@esbuild/android-arm64": "npm:0.27.3" - "@esbuild/android-x64": "npm:0.27.3" - "@esbuild/darwin-arm64": "npm:0.27.3" - "@esbuild/darwin-x64": "npm:0.27.3" - "@esbuild/freebsd-arm64": "npm:0.27.3" - "@esbuild/freebsd-x64": "npm:0.27.3" - "@esbuild/linux-arm": "npm:0.27.3" - "@esbuild/linux-arm64": "npm:0.27.3" - "@esbuild/linux-ia32": "npm:0.27.3" - "@esbuild/linux-loong64": "npm:0.27.3" - "@esbuild/linux-mips64el": "npm:0.27.3" - "@esbuild/linux-ppc64": "npm:0.27.3" - "@esbuild/linux-riscv64": "npm:0.27.3" - "@esbuild/linux-s390x": "npm:0.27.3" - "@esbuild/linux-x64": "npm:0.27.3" - "@esbuild/netbsd-arm64": "npm:0.27.3" - "@esbuild/netbsd-x64": "npm:0.27.3" - "@esbuild/openbsd-arm64": "npm:0.27.3" - "@esbuild/openbsd-x64": "npm:0.27.3" - "@esbuild/openharmony-arm64": "npm:0.27.3" - "@esbuild/sunos-x64": "npm:0.27.3" - "@esbuild/win32-arm64": "npm:0.27.3" - "@esbuild/win32-ia32": "npm:0.27.3" - "@esbuild/win32-x64": "npm:0.27.3" +"esbuild@npm:^0.25.0": + version: 0.25.12 + resolution: "esbuild@npm:0.25.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.12" + "@esbuild/android-arm": "npm:0.25.12" + "@esbuild/android-arm64": "npm:0.25.12" + "@esbuild/android-x64": "npm:0.25.12" + "@esbuild/darwin-arm64": "npm:0.25.12" + "@esbuild/darwin-x64": "npm:0.25.12" + "@esbuild/freebsd-arm64": "npm:0.25.12" + "@esbuild/freebsd-x64": "npm:0.25.12" + "@esbuild/linux-arm": "npm:0.25.12" + "@esbuild/linux-arm64": "npm:0.25.12" + "@esbuild/linux-ia32": "npm:0.25.12" + "@esbuild/linux-loong64": "npm:0.25.12" + "@esbuild/linux-mips64el": "npm:0.25.12" + "@esbuild/linux-ppc64": "npm:0.25.12" + "@esbuild/linux-riscv64": "npm:0.25.12" + "@esbuild/linux-s390x": "npm:0.25.12" + "@esbuild/linux-x64": "npm:0.25.12" + "@esbuild/netbsd-arm64": "npm:0.25.12" + "@esbuild/netbsd-x64": "npm:0.25.12" + "@esbuild/openbsd-arm64": "npm:0.25.12" + "@esbuild/openbsd-x64": "npm:0.25.12" + "@esbuild/openharmony-arm64": "npm:0.25.12" + "@esbuild/sunos-x64": "npm:0.25.12" + "@esbuild/win32-arm64": "npm:0.25.12" + "@esbuild/win32-ia32": "npm:0.25.12" + "@esbuild/win32-x64": "npm:0.25.12" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -5117,7 +4928,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1 + checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b languageName: node linkType: hard @@ -5255,7 +5066,7 @@ __metadata: languageName: node linkType: hard -"expect-type@npm:^1.2.2": +"expect-type@npm:^1.3.0": version: 1.3.0 resolution: "expect-type@npm:1.3.0" checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd @@ -5308,6 +5119,13 @@ __metadata: languageName: node linkType: hard +"exsolve@npm:^1.0.7": + version: 1.0.8 + resolution: "exsolve@npm:1.0.8" + checksum: 10c0/65e44ae05bd4a4a5d87cfdbbd6b8f24389282cf9f85fa5feb17ca87ad3f354877e6af4cd99e02fc29044174891f82d1d68c77f69234410eb8f163530e6278c67 + languageName: node + linkType: hard + "extend-shallow@npm:^2.0.1": version: 2.0.1 resolution: "extend-shallow@npm:2.0.1" @@ -5991,15 +5809,6 @@ __metadata: languageName: node linkType: hard -"hosted-git-info@npm:^6.0.0, hosted-git-info@npm:^6.1.1": - version: 6.1.3 - resolution: "hosted-git-info@npm:6.1.3" - dependencies: - lru-cache: "npm:^7.5.1" - checksum: 10c0/a1fc10faf67d04d575ebabf89cd5c9e3ebca041d99f42f31143bc8027684da4612c2f6deaf7cf2c09ac3b04dd502ad3957caa49d913628f0558964b2e1e7b414 - languageName: node - linkType: hard - "hosted-git-info@npm:^7.0.0": version: 7.0.2 resolution: "hosted-git-info@npm:7.0.2" @@ -6296,15 +6105,6 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.8.1": - version: 2.16.1 - resolution: "is-core-module@npm:2.16.1" - dependencies: - hasown: "npm:^2.0.2" - checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd - languageName: node - linkType: hard - "is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": version: 1.0.2 resolution: "is-data-view@npm:1.0.2" @@ -6953,6 +6753,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-android-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-android-arm64@npm:1.32.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-darwin-arm64@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-darwin-arm64@npm:1.30.2" @@ -6960,6 +6767,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-arm64@npm:1.32.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-darwin-x64@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-darwin-x64@npm:1.30.2" @@ -6967,6 +6781,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-x64@npm:1.32.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "lightningcss-freebsd-x64@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-freebsd-x64@npm:1.30.2" @@ -6974,6 +6795,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-freebsd-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-freebsd-x64@npm:1.32.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "lightningcss-linux-arm-gnueabihf@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-arm-gnueabihf@npm:1.30.2" @@ -6981,6 +6809,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm-gnueabihf@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "lightningcss-linux-arm64-gnu@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-arm64-gnu@npm:1.30.2" @@ -6988,6 +6823,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "lightningcss-linux-arm64-musl@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-arm64-musl@npm:1.30.2" @@ -6995,6 +6837,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "lightningcss-linux-x64-gnu@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-x64-gnu@npm:1.30.2" @@ -7002,6 +6851,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-x64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "lightningcss-linux-x64-musl@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-linux-x64-musl@npm:1.30.2" @@ -7009,6 +6865,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-x64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-musl@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "lightningcss-win32-arm64-msvc@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-win32-arm64-msvc@npm:1.30.2" @@ -7016,6 +6879,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-win32-arm64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-win32-x64-msvc@npm:1.30.2": version: 1.30.2 resolution: "lightningcss-win32-x64-msvc@npm:1.30.2" @@ -7023,7 +6893,57 @@ __metadata: languageName: node linkType: hard -"lightningcss@npm:1.30.2, lightningcss@npm:^1.30.1": +"lightningcss-win32-x64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:1.32.0, lightningcss@npm:^1.32.0": + version: 1.32.0 + resolution: "lightningcss@npm:1.32.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.32.0" + lightningcss-darwin-arm64: "npm:1.32.0" + lightningcss-darwin-x64: "npm:1.32.0" + lightningcss-freebsd-x64: "npm:1.32.0" + lightningcss-linux-arm-gnueabihf: "npm:1.32.0" + lightningcss-linux-arm64-gnu: "npm:1.32.0" + lightningcss-linux-arm64-musl: "npm:1.32.0" + lightningcss-linux-x64-gnu: "npm:1.32.0" + lightningcss-linux-x64-musl: "npm:1.32.0" + lightningcss-win32-arm64-msvc: "npm:1.32.0" + lightningcss-win32-x64-msvc: "npm:1.32.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/70945bd55097af46fc9fab7f5ed09cd5869d85940a2acab7ee06d0117004a1d68155708a2d462531cea2fc3c67aefc9333a7068c80b0b78dd404c16838809e03 + languageName: node + linkType: hard + +"lightningcss@npm:^1.30.1": version: 1.30.2 resolution: "lightningcss@npm:1.30.2" dependencies: @@ -7137,13 +7057,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^7.4.4, lru-cache@npm:^7.5.1": - version: 7.18.3 - resolution: "lru-cache@npm:7.18.3" - checksum: 10c0/b3a452b491433db885beed95041eb104c157ef7794b9c9b4d647be503be91769d11206bb573849a16b4cc0d03cbd15ffd22df7960997788b74c1d399ac7a4fed - languageName: node - linkType: hard - "lru_map@npm:^0.3.3": version: 0.3.3 resolution: "lru_map@npm:0.3.3" @@ -8290,18 +8203,6 @@ __metadata: languageName: node linkType: hard -"normalize-package-data@npm:^5.0.0": - version: 5.0.0 - resolution: "normalize-package-data@npm:5.0.0" - dependencies: - hosted-git-info: "npm:^6.0.0" - is-core-module: "npm:^2.8.1" - semver: "npm:^7.3.5" - validate-npm-package-license: "npm:^3.0.4" - checksum: 10c0/705fe66279edad2f93f6e504d5dc37984e404361a3df921a76ab61447eb285132d20ff261cc0bee9566b8ce895d75fcfec913417170add267e2873429fe38392 - languageName: node - linkType: hard - "normalize-package-data@npm:^6.0.0": version: 6.0.2 resolution: "normalize-package-data@npm:6.0.2" @@ -8336,18 +8237,6 @@ __metadata: languageName: node linkType: hard -"npm-package-arg@npm:^10.0.0": - version: 10.1.0 - resolution: "npm-package-arg@npm:10.1.0" - dependencies: - hosted-git-info: "npm:^6.0.0" - proc-log: "npm:^3.0.0" - semver: "npm:^7.3.5" - validate-npm-package-name: "npm:^5.0.0" - checksum: 10c0/ab56ed775b48e22755c324536336e3749b6a17763602bc0fb0d7e8b298100c2de8b5e2fb1d4fb3f451e9e076707a27096782e9b3a8da0c5b7de296be184b5a90 - languageName: node - linkType: hard - "npm-package-arg@npm:^11.0.0": version: 11.0.3 resolution: "npm-package-arg@npm:11.0.3" @@ -8360,18 +8249,6 @@ __metadata: languageName: node linkType: hard -"npm-pick-manifest@npm:^8.0.0": - version: 8.0.2 - resolution: "npm-pick-manifest@npm:8.0.2" - dependencies: - npm-install-checks: "npm:^6.0.0" - npm-normalize-package-bin: "npm:^3.0.0" - npm-package-arg: "npm:^10.0.0" - semver: "npm:^7.3.5" - checksum: 10c0/9e58f7732203dbfdd7a338d6fd691c564017fd2ebfaa0ea39528a21db0c99f26370c759d99a0c5684307b79dbf76fa20e387010358a8651e273dc89930e922a0 - languageName: node - linkType: hard - "npm-pick-manifest@npm:^9.0.0": version: 9.1.0 resolution: "npm-pick-manifest@npm:9.1.0" @@ -8707,6 +8584,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + "pino-abstract-transport@npm:^2.0.0": version: 2.0.0 resolution: "pino-abstract-transport@npm:2.0.0" @@ -8767,14 +8651,14 @@ __metadata: languageName: node linkType: hard -"pixelmatch@npm:7.1.0": - version: 7.1.0 - resolution: "pixelmatch@npm:7.1.0" +"pkg-types@npm:^2.3.0": + version: 2.3.0 + resolution: "pkg-types@npm:2.3.0" dependencies: - pngjs: "npm:^7.0.0" - bin: - pixelmatch: bin/pixelmatch - checksum: 10c0/ff069f92edaa841ac9b58b0ab74e1afa1f3b5e770eea0218c96bac1da4e752f5f6b79a0f9c4ba6b02afb955d39b8c78bcc3cc884f8122b67a1f2efbbccbe1a73 + confbox: "npm:^0.2.2" + exsolve: "npm:^1.0.7" + pathe: "npm:^2.0.3" + checksum: 10c0/d2bbddc5b81bd4741e1529c08ef4c5f1542bbdcf63498b73b8e1d84cff71806d1b8b1577800549bb569cb7aa20056257677b979bff48c97967cba7e64f72ae12 languageName: node linkType: hard @@ -8837,6 +8721,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.8": + version: 8.5.8 + resolution: "postcss@npm:8.5.8" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/dd918f7127ee7c60a0295bae2e72b3787892296e1d1c3c564d7a2a00c68d8df83cadc3178491259daa19ccc54804fb71ed8c937c6787e08d8bd4bedf8d17044c + languageName: node + linkType: hard + "prettier@npm:^3.6.2": version: 3.6.2 resolution: "prettier@npm:3.6.2" @@ -8846,13 +8741,6 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^3.0.0": - version: 3.0.0 - resolution: "proc-log@npm:3.0.0" - checksum: 10c0/f66430e4ff947dbb996058f6fd22de2c66612ae1a89b097744e17fb18a4e8e7a86db99eda52ccf15e53f00b63f4ec0b0911581ff2aac0355b625c8eac509b0dc - languageName: node - linkType: hard - "proc-log@npm:^4.0.0, proc-log@npm:^4.2.0": version: 4.2.0 resolution: "proc-log@npm:4.2.0" @@ -9015,14 +8903,14 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^19.1.0": - version: 19.2.0 - resolution: "react-dom@npm:19.2.0" +"react-dom@npm:^19.2.4": + version: 19.2.4 + resolution: "react-dom@npm:19.2.4" dependencies: scheduler: "npm:^0.27.0" peerDependencies: - react: ^19.2.0 - checksum: 10c0/fa2cae05248d01288e91523b590ce4e7635b1e13f1344e225f850d722a8da037bf0782f63b1c1d46353334e0c696909b82e582f8cad607948fde6f7646cc18d9 + react: ^19.2.4 + checksum: 10c0/f0c63f1794dedb154136d4d0f59af00b41907f4859571c155940296808f4b94bf9c0c20633db75b5b2112ec13d8d7dd4f9bf57362ed48782f317b11d05a44f35 languageName: node linkType: hard @@ -9055,22 +8943,15 @@ __metadata: languageName: node linkType: hard -"react-refresh@npm:^0.17.0": - version: 0.17.0 - resolution: "react-refresh@npm:0.17.0" - checksum: 10c0/002cba940384c9930008c0bce26cac97a9d5682bc623112c2268ba0c155127d9c178a9a5cc2212d560088d60dfd503edd808669a25f9b377f316a32361d0b23c - languageName: node - linkType: hard - -"react-router-dom@npm:^7.9.4": - version: 7.9.6 - resolution: "react-router-dom@npm:7.9.6" +"react-router-dom@npm:^7.14.0": + version: 7.14.0 + resolution: "react-router-dom@npm:7.14.0" dependencies: - react-router: "npm:7.9.6" + react-router: "npm:7.14.0" peerDependencies: react: ">=18" react-dom: ">=18" - checksum: 10c0/63984c46385da232655b9e3a8a99f6dd7b94c36827be6e954f246c362f83740b5f59b1de99cae81da3b0cef2220d701dcc22e4fafb4a84600541e1c0450b9d57 + checksum: 10c0/f7130c7083c2a8921aa59e9a9755ae4b79ef98b4df0ae84052ab0fd95b27612a7ebd2539b83d299b8073f8b5fc41595e8cc82bf748837d95d166f8ee19bf5f24 languageName: node linkType: hard @@ -9110,25 +8991,9 @@ __metadata: languageName: node linkType: hard -"react-router@npm:7.9.6": - version: 7.9.6 - resolution: "react-router@npm:7.9.6" - dependencies: - cookie: "npm:^1.0.1" - set-cookie-parser: "npm:^2.6.0" - peerDependencies: - react: ">=18" - react-dom: ">=18" - peerDependenciesMeta: - react-dom: - optional: true - checksum: 10c0/2a177bbe19021e3b8211df849ea5b3f3a4f482327e6de3341aaeaa4f1406dc9be7b675b229eefea6761e04a59a40ccaaf8188f2ee88eb2d0b2a6b6448daea368 - languageName: node - linkType: hard - -"react-router@npm:^7.12.0": - version: 7.12.0 - resolution: "react-router@npm:7.12.0" +"react-router@npm:7.14.0, react-router@npm:^7.14.0": + version: 7.14.0 + resolution: "react-router@npm:7.14.0" dependencies: cookie: "npm:^1.0.1" set-cookie-parser: "npm:^2.6.0" @@ -9138,14 +9003,14 @@ __metadata: peerDependenciesMeta: react-dom: optional: true - checksum: 10c0/abde366f716cb3961a5a390c278375c0591bace5773e1b4420001f0a913b4dd53d490e7dea866acebcac2c0fa07378aa83702769d449449027406ed517a8ea00 + checksum: 10c0/a496489973cd5e87dcc5c1c7312f4cc99463eb5e0a0f97b3f298467531b754a3227562a83e0c9019b9d2452fd0681d05882ee061af2e0cafb0818f857578b805 languageName: node linkType: hard -"react@npm:^19.1.0": - version: 19.2.0 - resolution: "react@npm:19.2.0" - checksum: 10c0/1b6d64eacb9324725bfe1e7860cb7a6b8a34bc89a482920765ebff5c10578eb487e6b46b2f0df263bd27a25edbdae2c45e5ea5d81ae61404301c1a7192c38330 +"react@npm:^19.2.4": + version: 19.2.4 + resolution: "react@npm:19.2.4" + checksum: 10c0/cd2c9ff67a720799cc3b38a516009986f7fc4cb8d3e15716c6211cf098d1357ee3e348ab05ad0600042bbb0fd888530ba92e329198c92eafa0994f5213396596 languageName: node linkType: hard @@ -9492,16 +9357,16 @@ __metadata: "@mdx-js/mdx": "npm:^3.1.1" "@node-cli/static-server": "npm:^3.1.4" "@prettier/plugin-oxc": "npm:^0.0.4" - "@react-router/dev": "npm:^7.8.1" - "@react-router/node": "npm:^7.8.1" + "@react-router/dev": "npm:^7.14.0" + "@react-router/node": "npm:^7.14.0" "@replit/codemirror-vim": "npm:^6.3.0" - "@rescript/react": "npm:^0.14.0" + "@rescript/react": "npm:^0.14.2" "@rescript/webapi": "npm:0.1.0-experimental-29db5f4" - "@tailwindcss/vite": "npm:^4.1.13" + "@tailwindcss/vite": "npm:^4.2.2" "@tsnobip/rescript-lezer": "npm:^0.8.0" - "@types/react": "npm:^19.2.2" - "@vitejs/plugin-react": "npm:^4.7.0" - "@vitest/browser-playwright": "npm:^4.0.18" + "@types/react": "npm:^19.2.14" + "@vitejs/plugin-react": "npm:^6.0.1" + "@vitest/browser-playwright": "npm:^4.1.2" auto-image-converter: "npm:^2.1.2" chokidar: "npm:^4.0.3" docson: "npm:^2.1.0" @@ -9520,11 +9385,11 @@ __metadata: mdast-util-toc: "npm:^7.1.0" playwright: "npm:^1.58.2" prettier: "npm:^3.6.2" - react: "npm:^19.1.0" - react-dom: "npm:^19.1.0" + react: "npm:^19.2.4" + react-dom: "npm:^19.2.4" react-markdown: "npm:^10.1.0" - react-router: "npm:^7.12.0" - react-router-dom: "npm:^7.9.4" + react-router: "npm:^7.14.0" + react-router-dom: "npm:^7.14.0" react-router-mdx: "patch:react-router-mdx@npm%3A1.0.8#~/.yarn/patches/react-router-mdx-npm-1.0.8-d4402c3003.patch" rehype-slug: "npm:^6.0.0" rehype-stringify: "npm:^10.0.1" @@ -9534,33 +9399,33 @@ __metadata: remark-frontmatter: "npm:^5.0.0" remark-gfm: "npm:^4.0.1" remark-validate-links: "npm:^13.1.0" - rescript: "npm:^12.0.0" + rescript: "npm:^12.2.0" search-insights: "npm:^2.17.3" tailwindcss: "npm:^4" to-vfile: "npm:^8.0.0" unified: "npm:^11.0.5" vfile-matter: "npm:^5.0.0" vfile-reporter: "npm:^8.1.1" - vite: "npm:^7.0.6" + vite: "npm:^8.0.3" vite-plugin-devtools-json: "npm:^1.0.0" vite-plugin-env-compatible: "npm:^2.0.1" - vite-plugin-page-reload: "npm:^0.2.2" - vitest: "npm:^4.0.18" - vitest-browser-react: "npm:^2.0.5" + vite-plugin-page-reload: "npm:^0.2.3" + vitest: "npm:^4.1.2" + vitest-browser-react: "npm:^2.2.0" wrangler: "npm:^4.63.0" languageName: unknown linkType: soft -"rescript@npm:^12.0.0, rescript@npm:^12.0.0-beta.3": - version: 12.0.0 - resolution: "rescript@npm:12.0.0" +"rescript@npm:^12.0.0-beta.3, rescript@npm:^12.2.0": + version: 12.2.0 + resolution: "rescript@npm:12.2.0" dependencies: - "@rescript/darwin-arm64": "npm:12.0.0" - "@rescript/darwin-x64": "npm:12.0.0" - "@rescript/linux-arm64": "npm:12.0.0" - "@rescript/linux-x64": "npm:12.0.0" - "@rescript/runtime": "npm:12.0.0" - "@rescript/win32-x64": "npm:12.0.0" + "@rescript/darwin-arm64": "npm:12.2.0" + "@rescript/darwin-x64": "npm:12.2.0" + "@rescript/linux-arm64": "npm:12.2.0" + "@rescript/linux-x64": "npm:12.2.0" + "@rescript/runtime": "npm:12.2.0" + "@rescript/win32-x64": "npm:12.2.0" dependenciesMeta: "@rescript/darwin-arm64": optional: true @@ -9578,7 +9443,7 @@ __metadata: rescript: cli/rescript.js rescript-legacy: cli/rescript-legacy.js rescript-tools: cli/rescript-tools.js - checksum: 10c0/8fc92a806d86825fe593cc2c23accc39c95236b4885f9c6d9874fc1b375dd6dd07f8a99ef4c8bbbb273a6f44d47439b5cb4a62577a57ab9ecb0c7ad15021846c + checksum: 10c0/7650a77a66e2f2ba2523eac54fd930f0b70bad922e9b498678e701e842a56c7b6facd0daf1ef9c7b71d344d9483a0293d17db72a67515a22d07856bdfc34fb46 languageName: node linkType: hard @@ -9620,6 +9485,64 @@ __metadata: languageName: node linkType: hard +"rolldown@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "rolldown@npm:1.0.0-rc.12" + dependencies: + "@oxc-project/types": "npm:=0.122.0" + "@rolldown/binding-android-arm64": "npm:1.0.0-rc.12" + "@rolldown/binding-darwin-arm64": "npm:1.0.0-rc.12" + "@rolldown/binding-darwin-x64": "npm:1.0.0-rc.12" + "@rolldown/binding-freebsd-x64": "npm:1.0.0-rc.12" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-rc.12" + "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-rc.12" + "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-rc.12" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.0.0-rc.12" + "@rolldown/binding-linux-s390x-gnu": "npm:1.0.0-rc.12" + "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-rc.12" + "@rolldown/binding-linux-x64-musl": "npm:1.0.0-rc.12" + "@rolldown/binding-openharmony-arm64": "npm:1.0.0-rc.12" + "@rolldown/binding-wasm32-wasi": "npm:1.0.0-rc.12" + "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-rc.12" + "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-rc.12" + "@rolldown/pluginutils": "npm:1.0.0-rc.12" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: bin/cli.mjs + checksum: 10c0/0c4e5e3cdcdddce282cb2d84e1c98d6ad8d4e452d5c1402e498b35ec1060026e552dd783efc9f4ba876d7c0863b5973edc79b6a546f565e9832dc1077ec18c2c + languageName: node + linkType: hard + "rollup@npm:^4.43.0": version: 4.53.3 resolution: "rollup@npm:4.53.3" @@ -10289,10 +10212,10 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.10.0": - version: 3.10.0 - resolution: "std-env@npm:3.10.0" - checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f +"std-env@npm:^4.0.0-rc.1": + version: 4.0.0 + resolution: "std-env@npm:4.0.0" + checksum: 10c0/63b1716eae27947adde49e21b7225a0f75fb2c3d410273ae9de8333c07c7d5fc7a0628ae4c8af6b4b49b4274ed46c2bf118ed69b64f1261c9d8213d76ed1c16c languageName: node linkType: hard @@ -10527,17 +10450,24 @@ __metadata: languageName: node linkType: hard -"tailwindcss@npm:4.1.17, tailwindcss@npm:^4": +"tailwindcss@npm:4.2.2": + version: 4.2.2 + resolution: "tailwindcss@npm:4.2.2" + checksum: 10c0/6eae8a125c35d504ba6c518d26ec64fba694ff4a9ab9b9cd9883050128e0b7afdf491388c472d9bed2624664c1c7d4a133d19b653151a6b52e6ce6953168a857 + languageName: node + linkType: hard + +"tailwindcss@npm:^4": version: 4.1.17 resolution: "tailwindcss@npm:4.1.17" checksum: 10c0/1fecf618ba9895e068e5a6d842b978f56a815bc849a28338cebbcb07b13df763715c2f8848def938403c73d59f08ffff33a4b83a977a9e38fa56adc60d1d56c8 languageName: node linkType: hard -"tapable@npm:^2.2.0": - version: 2.3.0 - resolution: "tapable@npm:2.3.0" - checksum: 10c0/cb9d67cc2c6a74dedc812ef3085d9d681edd2c1fa18e4aef57a3c0605fdbe44e6b8ea00bd9ef21bc74dd45314e39d31227aa031ebf2f5e38164df514136f2681 +"tapable@npm:^2.3.0": + version: 2.3.2 + resolution: "tapable@npm:2.3.2" + checksum: 10c0/45ec8bd8963907f35bba875f9b3e9a5afa5ba11a9a4e4a2d7b2313d983cb2741386fd7dd3e54b13055b2be942971aac369d197e02263ec9216c59c0a8069ed7f languageName: node linkType: hard @@ -10611,10 +10541,10 @@ __metadata: languageName: node linkType: hard -"tinyrainbow@npm:^3.0.3": - version: 3.0.3 - resolution: "tinyrainbow@npm:3.0.3" - checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c +"tinyrainbow@npm:^3.1.0": + version: 3.1.0 + resolution: "tinyrainbow@npm:3.1.0" + checksum: 10c0/f11cf387a26c5c9255bec141a90ac511b26172981b10c3e50053bc6700ea7d2336edcc4a3a21dbb8412fe7c013477d2ba4d7e4877800f3f8107be5105aad6511 languageName: node linkType: hard @@ -10718,7 +10648,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.4.0, tslib@npm:^2.8.0": +"tslib@npm:^2.4.0, tslib@npm:^2.8.0, tslib@npm:^2.8.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -11097,15 +11027,15 @@ __metadata: languageName: node linkType: hard -"valibot@npm:^1.1.0": - version: 1.2.0 - resolution: "valibot@npm:1.2.0" +"valibot@npm:^1.2.0": + version: 1.3.1 + resolution: "valibot@npm:1.3.1" peerDependencies: typescript: ">=5" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/e6897ed2008fc900380a6ce39b62bc5fca45fd5e070f70571c6380ede3ba026d0b7016230215d87f7f3d672a28dbde5a0522d39830b493fdc3dccd1a59ef4ee6 + checksum: 10c0/e20a4097fa726f57530da1e64558af47ddd2303129c77978fe93c522c66cf4c79540ea3af864523589283ea25e347c3d65b8044fa4913376208dde576b9f6382 languageName: node linkType: hard @@ -11235,19 +11165,19 @@ __metadata: languageName: node linkType: hard -"vite-plugin-page-reload@npm:^0.2.2": - version: 0.2.2 - resolution: "vite-plugin-page-reload@npm:0.2.2" +"vite-plugin-page-reload@npm:^0.2.3": + version: 0.2.3 + resolution: "vite-plugin-page-reload@npm:0.2.3" dependencies: picocolors: "npm:^1.0.0" picomatch: "npm:^2.3.1" peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/7959914fb6102889b0aa7406d97734b29b3e208fa2afad13f2f9ed677daa296f7bca9cd1746160b681474081c0415a98a2bba13726de5bbaeaf4cf96e2b09fd6 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/8949e95fb1830a9f360a8d6a110d5c7766279655329de90e038b7490f6f4527e51126cfcfafcda188d871f9e69dc39e4e6f9ceef214997c3fb62cdb37c0c0014 languageName: node linkType: hard -"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0, vite@npm:^7.0.6": +"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0": version: 7.2.4 resolution: "vite@npm:7.2.4" dependencies: @@ -11302,22 +11232,22 @@ __metadata: languageName: node linkType: hard -"vite@npm:^6.0.0 || ^7.0.0": - version: 7.3.1 - resolution: "vite@npm:7.3.1" +"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0, vite@npm:^8.0.3": + version: 8.0.3 + resolution: "vite@npm:8.0.3" dependencies: - esbuild: "npm:^0.27.0" - fdir: "npm:^6.5.0" fsevents: "npm:~2.3.3" - picomatch: "npm:^4.0.3" - postcss: "npm:^8.5.6" - rollup: "npm:^4.43.0" + lightningcss: "npm:^1.32.0" + picomatch: "npm:^4.0.4" + postcss: "npm:^8.5.8" + rolldown: "npm:1.0.0-rc.12" tinyglobby: "npm:^0.2.15" peerDependencies: "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.1.0 + esbuild: ^0.27.0 jiti: ">=1.21.0" less: ^4.0.0 - lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 stylus: ">=0.54.8" @@ -11331,12 +11261,14 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true jiti: optional: true less: optional: true - lightningcss: - optional: true sass: optional: true sass-embedded: @@ -11353,13 +11285,13 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/5c7548f5f43a23533e53324304db4ad85f1896b1bfd3ee32ae9b866bac2933782c77b350eb2b52a02c625c8ad1ddd4c000df077419410650c982cd97fde8d014 + checksum: 10c0/bed9520358080393a02fe22565b3309b4b3b8f916afe4c97577528f3efb05c1bf4b29f7b552179bc5b3938629e50fbd316231727457411dbc96648fa5c9d14bf languageName: node linkType: hard -"vitest-browser-react@npm:^2.0.5": - version: 2.0.5 - resolution: "vitest-browser-react@npm:2.0.5" +"vitest-browser-react@npm:^2.2.0": + version: 2.2.0 + resolution: "vitest-browser-react@npm:2.2.0" peerDependencies: "@types/react": ^18.0.0 || ^19.0.0 "@types/react-dom": ^18.0.0 || ^19.0.0 @@ -11371,44 +11303,45 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10c0/ae972fa20895c73622c2e724a2e2a716cc2a2e5148da19a60d1185323aeb5f5bd0653cfe3048d081bb086ee0efa68c0c360d28cdf42ddd8df6a5f2d17ffd0c9e + checksum: 10c0/d2e582e564cf7f65f19a5a9c36b0b136e84fc6dabd42566703d79e0b220094a5a88a9197a42b2c4779f38977d79c8f3306387cd7edd2ef8e57790b921a759975 languageName: node linkType: hard -"vitest@npm:^4.0.18": - version: 4.0.18 - resolution: "vitest@npm:4.0.18" - dependencies: - "@vitest/expect": "npm:4.0.18" - "@vitest/mocker": "npm:4.0.18" - "@vitest/pretty-format": "npm:4.0.18" - "@vitest/runner": "npm:4.0.18" - "@vitest/snapshot": "npm:4.0.18" - "@vitest/spy": "npm:4.0.18" - "@vitest/utils": "npm:4.0.18" - es-module-lexer: "npm:^1.7.0" - expect-type: "npm:^1.2.2" +"vitest@npm:^4.1.2": + version: 4.1.2 + resolution: "vitest@npm:4.1.2" + dependencies: + "@vitest/expect": "npm:4.1.2" + "@vitest/mocker": "npm:4.1.2" + "@vitest/pretty-format": "npm:4.1.2" + "@vitest/runner": "npm:4.1.2" + "@vitest/snapshot": "npm:4.1.2" + "@vitest/spy": "npm:4.1.2" + "@vitest/utils": "npm:4.1.2" + es-module-lexer: "npm:^2.0.0" + expect-type: "npm:^1.3.0" magic-string: "npm:^0.30.21" obug: "npm:^2.1.1" pathe: "npm:^2.0.3" picomatch: "npm:^4.0.3" - std-env: "npm:^3.10.0" + std-env: "npm:^4.0.0-rc.1" tinybench: "npm:^2.9.0" tinyexec: "npm:^1.0.2" tinyglobby: "npm:^0.2.15" - tinyrainbow: "npm:^3.0.3" - vite: "npm:^6.0.0 || ^7.0.0" + tinyrainbow: "npm:^3.1.0" + vite: "npm:^6.0.0 || ^7.0.0 || ^8.0.0" why-is-node-running: "npm:^2.3.0" peerDependencies: "@edge-runtime/vm": "*" "@opentelemetry/api": ^1.9.0 "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.0.18 - "@vitest/browser-preview": 4.0.18 - "@vitest/browser-webdriverio": 4.0.18 - "@vitest/ui": 4.0.18 + "@vitest/browser-playwright": 4.1.2 + "@vitest/browser-preview": 4.1.2 + "@vitest/browser-webdriverio": 4.1.2 + "@vitest/ui": 4.1.2 happy-dom: "*" jsdom: "*" + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: "@edge-runtime/vm": optional: true @@ -11428,9 +11361,11 @@ __metadata: optional: true jsdom: optional: true + vite: + optional: false bin: vitest: vitest.mjs - checksum: 10c0/b913cd32032c95f29ff08c931f4b4c6fd6d2da498908d6770952c561a1b8d75c62499a1f04cadf82fb89cc0f9a33f29fb5dfdb899f6dbb27686a9d91571be5fa + checksum: 10c0/061fdd0319ba533c926b139b9377a7dbf91e63d815d86fe318a207bd19842b74ca6f6402ea61b26ed9d2924306bdb4d0b13f69c29e2a2a89b3b67602bcccb54c languageName: node linkType: hard @@ -11562,17 +11497,6 @@ __metadata: languageName: node linkType: hard -"which@npm:^3.0.0": - version: 3.0.1 - resolution: "which@npm:3.0.1" - dependencies: - isexe: "npm:^2.0.0" - bin: - node-which: bin/which.js - checksum: 10c0/15263b06161a7c377328fd2066cb1f093f5e8a8f429618b63212b5b8847489be7bcab0ab3eb07f3ecc0eda99a5a7ea52105cf5fa8266bedd083cc5a9f6da24f1 - languageName: node - linkType: hard - "which@npm:^4.0.0": version: 4.0.0 resolution: "which@npm:4.0.0" @@ -11747,9 +11671,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.18.3": - version: 8.19.0 - resolution: "ws@npm:8.19.0" +"ws@npm:^8.19.0": + version: 8.20.0 + resolution: "ws@npm:8.20.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -11758,7 +11682,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10c0/4741d9b9bc3f9c791880882414f96e36b8b254e34d4b503279d6400d9a4b87a033834856dbdd94ee4b637944df17ea8afc4bce0ff4a1560d2166be8855da5b04 + checksum: 10c0/956ac5f11738c914089b65878b9223692ace77337ba55379ae68e1ecbeae9b47a0c6eb9403688f609999a58c80d83d99865fe0029b229d308b08c1ef93d4ea14 languageName: node linkType: hard From 3d84fe249d55c9589d7e4f94c3ab563cce563fa2 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 6 Apr 2026 14:36:53 -0400 Subject: [PATCH 02/12] feat: improve Algolia search results --- .env | 7 +- algolia.mjs | 35 +++ package.json | 4 +- scripts/generate_search_index.res | 169 ++++++++++ src/bindings/Algolia.res | 54 ++++ src/bindings/DocSearch.res | 24 +- src/bindings/Env.res | 5 + src/common/SearchIndex.res | 495 ++++++++++++++++++++++++++++++ src/components/Meta.res | 2 - src/components/Search.res | 108 +------ yarn.lock | 177 +++++++++++ 11 files changed, 975 insertions(+), 105 deletions(-) create mode 100644 algolia.mjs create mode 100644 scripts/generate_search_index.res create mode 100644 src/bindings/Algolia.res create mode 100644 src/common/SearchIndex.res diff --git a/.env b/.env index 14e36c5ec..5bbf70d83 100644 --- a/.env +++ b/.env @@ -1,2 +1,5 @@ -VITE_VERSION_LATEST="v11.0.0" -VITE_VERSION_NEXT="v12.0.0" \ No newline at end of file +VITE_VERSION_LATEST=12.0.0 +VITE_VERSION_NEXT=13.0.0 +VITE_ALGOLIA_READ_API_KEY=667630d6ab41eff82df15fdc6a55153f +VITE_ALGOLIA_APP_ID=1T1PRULLJT +VITE_ALGOLIA_INDEX_NAME=dev_2026 diff --git a/algolia.mjs b/algolia.mjs new file mode 100644 index 000000000..7ba6a0cf8 --- /dev/null +++ b/algolia.mjs @@ -0,0 +1,35 @@ +// helloAlgolia.mjs +import { algoliasearch } from "algoliasearch"; + +const appID = "1T1PRULLJT"; +// API key with `addObject` and `editSettings` ACL +const apiKey = "999e5352ab7aed499de651ee79f573ee"; +const indexName = "dev_2026"; + +const client = algoliasearch(appID, apiKey); + +const record = { objectID: "object-1", name: "test record" }; + +// Add record to an index +const { taskID } = await client.saveObject({ + indexName, + body: record, +}); + +// Wait until indexing is done +await client.waitForTask({ + indexName, + taskID, +}); + +// Search for "test" +const { results } = await client.search({ + requests: [ + { + indexName, + query: "test", + }, + ], +}); + +console.log(JSON.stringify(results, null, 2)); diff --git a/package.json b/package.json index 73ee8dc59..062d17519 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "build:generate-llms": "node _scripts/generate_llms.mjs", "build:res": "rescript build --warn-error +3+8+11+12+26+27+31+32+33+34+35+39+44+45+110", "build:sync-bundles": "node scripts/sync-playground-bundles.mjs", - "build:update-index": "yarn build:generate-llms && node _scripts/generate_feed.mjs > public/blog/feed.xml", + "build:search-index": "node --env-file-if-exists=.env.local _scripts/generate_search_index.mjs", + "build:update-index": "yarn build:generate-llms && node _scripts/generate_feed.mjs > public/blog/feed.xml && yarn build:search-index", "build:vite": "react-router build", "build": "yarn build:res && yarn build:scripts && yarn build:update-index && yarn build:vite", "ci:format": "prettier . --check --experimental-cli", @@ -55,6 +56,7 @@ "@rescript/react": "^0.14.2", "@rescript/webapi": "0.1.0-experimental-29db5f4", "@tsnobip/rescript-lezer": "^0.8.0", + "algoliasearch": "^5.50.1", "docson": "^2.1.0", "fuse.js": "^6.4.3", "glob": "^7.1.4", diff --git a/scripts/generate_search_index.res b/scripts/generate_search_index.res new file mode 100644 index 000000000..b786cf117 --- /dev/null +++ b/scripts/generate_search_index.res @@ -0,0 +1,169 @@ +// Build script: reads all site content, builds Algolia search records, and uploads them. +// Runs as a standalone Node script via: node --env-file-if-exists=.env.local _scripts/generate_search_index.mjs +// +// Required env vars: +// ALGOLIA_ADMIN_API_KEY -- API key with addObject/deleteObject/editSettings ACLs +// ALGOLIA_INDEX_NAME -- e.g. "rescript-lang-dev" or "rescript-lang" +// +// If either is missing, the script logs a warning and exits 0 (graceful skip). + +let getEnv = (key: string): option => + Node.Process.env + ->Dict.get(key) + ->Option.flatMap(v => + switch v { + | "" => None + | s => Some(s) + } + ) + +let main = async () => { + let appId = getEnv("ALGOLIA_APP_ID") + let adminApiKey = getEnv("ALGOLIA_ADMIN_API_KEY") + let indexName = getEnv("ALGOLIA_INDEX_NAME") + + switch (appId, adminApiKey, indexName) { + | (Some(appId), Some(apiKey), Some(idx)) => { + Console.log("[search-index] Building search index records...") + + // 1. Build records from all content sources + let manualRecords = SearchIndex.buildMarkdownRecords( + ~category="Manual", + ~basePath="/docs/manual", + ~dirPath="markdown-pages/docs/manual", + ~pageRank=100, + ) + Console.log( + `[search-index] Manual docs: ${Int.toString(Array.length(manualRecords))} records`, + ) + + let reactRecords = SearchIndex.buildMarkdownRecords( + ~category="React", + ~basePath="/docs/react", + ~dirPath="markdown-pages/docs/react", + ~pageRank=90, + ) + Console.log( + `[search-index] React docs: ${Int.toString(Array.length(reactRecords))} records`, + ) + + let communityRecords = SearchIndex.buildMarkdownRecords( + ~category="Community", + ~basePath="/community", + ~dirPath="markdown-pages/community", + ~pageRank=50, + ) + Console.log( + `[search-index] Community: ${Int.toString(Array.length(communityRecords))} records`, + ) + + let blogRecords = SearchIndex.buildBlogRecords(~dirPath="markdown-pages/blog", ~pageRank=40) + Console.log(`[search-index] Blog: ${Int.toString(Array.length(blogRecords))} records`) + + let syntaxRecords = SearchIndex.buildSyntaxLookupRecords( + ~dirPath="markdown-pages/syntax-lookup", + ~pageRank=70, + ) + Console.log( + `[search-index] Syntax lookup: ${Int.toString(Array.length(syntaxRecords))} records`, + ) + + let stdlibApiRecords = SearchIndex.buildApiRecords( + ~basePath="/docs/manual/api", + ~dirPath="markdown-pages/docs/api", + ~pageRank=80, + ~category="API / StdLib", + ~files=["stdlib.json"], + ) + Console.log( + `[search-index] API / StdLib: ${Int.toString(Array.length(stdlibApiRecords))} records`, + ) + + let beltApiRecords = SearchIndex.buildApiRecords( + ~basePath="/docs/manual/api", + ~dirPath="markdown-pages/docs/api", + ~pageRank=75, + ~category="API / Belt", + ~files=["belt.json"], + ) + Console.log( + `[search-index] API / Belt: ${Int.toString(Array.length(beltApiRecords))} records`, + ) + + let domApiRecords = SearchIndex.buildApiRecords( + ~basePath="/docs/manual/api", + ~dirPath="markdown-pages/docs/api", + ~pageRank=70, + ~category="API / DOM", + ~files=["dom.json"], + ) + Console.log( + `[search-index] API / DOM: ${Int.toString(Array.length(domApiRecords))} records`, + ) + + // 2. Concatenate all records + let allRecords = + [ + manualRecords, + reactRecords, + communityRecords, + blogRecords, + syntaxRecords, + stdlibApiRecords, + beltApiRecords, + domApiRecords, + ]->Array.flat + + let totalCount = Array.length(allRecords) + Console.log(`[search-index] Total: ${Int.toString(totalCount)} records`) + + // 3. Convert to JSON for Algolia + let jsonRecords = allRecords->Array.map(SearchIndex.toJson) + + // 4. Initialize Algolia client and upload + let client = Algolia.make(appId, apiKey) + + Console.log(`[search-index] Uploading to index "${idx}"...`) + let _ = await client->Algolia.replaceAllObjects({ + indexName: idx, + objects: jsonRecords, + batchSize: 1000, + }) + Console.log("[search-index] Records uploaded successfully.") + + // 5. Configure index settings + Console.log("[search-index] Updating index settings...") + let _ = await client->Algolia.setSettings({ + indexName: idx, + indexSettings: { + searchableAttributes: [ + "hierarchy.lvl0", + "hierarchy.lvl1", + "hierarchy.lvl2", + "hierarchy.lvl3", + "hierarchy.lvl4", + "hierarchy.lvl5", + "hierarchy.lvl6", + "content", + ], + ranking: ["typo", "words", "attribute", "exact", "custom", "proximity", "filters"], + exactOnSingleWordQuery: "word", + attributesForFaceting: ["type"], + customRanking: ["desc(weight.pageRank)", "desc(weight.level)", "asc(weight.position)"], + attributesToSnippet: ["content:30"], + attributeForDistinct: "hierarchy.lvl0", + }, + }) + Console.log("[search-index] Index settings updated.") + + Console.log("[search-index] Done.") + } + | (None, _, _) => Console.log("[search-index] ALGOLIA_APP_ID not set, skipping index upload.") + | (_, None, _) => Console.log( + "[search-index] ALGOLIA_ADMIN_API_KEY not set, skipping index upload.", + ) + | (_, _, None) => Console.log("[search-index] ALGOLIA_INDEX_NAME not set, skipping index upload.") + } +} + +let _ = main() diff --git a/src/bindings/Algolia.res b/src/bindings/Algolia.res new file mode 100644 index 000000000..30cf205ca --- /dev/null +++ b/src/bindings/Algolia.res @@ -0,0 +1,54 @@ +// Bindings for algoliasearch v5 SDK +// https://github.com/algolia/algoliasearch-client-javascript + +module SearchClient = { + type t +} + +module BatchResponse = { + type t +} + +module SetSettingsResponse = { + type t +} + +module IndexSettings = { + type t = { + searchableAttributes?: array, + attributesForFaceting?: array, + customRanking?: array, + ranking?: array, + attributesToSnippet?: array, + attributeForDistinct?: string, + exactOnSingleWordQuery?: string, + } +} + +module ReplaceAllObjectsOptions = { + type t = { + indexName: string, + objects: array, + batchSize?: int, + } +} + +module SetSettingsOptions = { + type t = { + indexName: string, + indexSettings: IndexSettings.t, + } +} + +@module("algoliasearch") +external make: (string, string) => SearchClient.t = "algoliasearch" + +@send +external replaceAllObjects: ( + SearchClient.t, + ReplaceAllObjectsOptions.t, +) => promise> = "replaceAllObjects" + +@send +external setSettings: (SearchClient.t, SetSettingsOptions.t) => promise = + "setSettings" diff --git a/src/bindings/DocSearch.res b/src/bindings/DocSearch.res index 0c42d8586..083dc7ba2 100644 --- a/src/bindings/DocSearch.res +++ b/src/bindings/DocSearch.res @@ -44,7 +44,11 @@ type item = {itemUrl: string} type navigator = {navigate: item => unit} -type searchParameters = {facetFilters: array} +type searchParameters = { + facetFilters?: array, + hitsPerPage?: int, + distinct?: int, +} @module("@docsearch/react") @react.component external make: ( @@ -58,3 +62,21 @@ external make: ( ~searchParameters: searchParameters=?, ~initialScrollY: int=?, ) => React.element = "DocSearchModal" + +let getContentSnippet: docSearchHit => option = %raw(` + function(hit) { + try { + var s = hit._snippetResult; + if (s && s.content && s.content.value) { + var val = s.content.value.trim(); + if (val !== '' && val !== '...') return val; + } + } catch(e) {} + var c = hit.content; + if (c != null) { + c = c.trim(); + if (c !== '') return c; + } + return undefined; + } +`) diff --git a/src/bindings/Env.res b/src/bindings/Env.res index 29a6c4e1b..174ec8da9 100644 --- a/src/bindings/Env.res +++ b/src/bindings/Env.res @@ -9,3 +9,8 @@ let root_url = switch deployment_url { | Some(url) => url | None => dev ? "http://localhost:5173/" : "https://rescript-lang.org/" } + +// Algolia search configuration (read from .env via Vite) +external algolia_app_id: string = "import.meta.env.VITE_ALGOLIA_APP_ID" +external algolia_read_api_key: string = "import.meta.env.VITE_ALGOLIA_READ_API_KEY" +external algolia_index_name: string = "import.meta.env.VITE_ALGOLIA_INDEX_NAME" diff --git a/src/common/SearchIndex.res b/src/common/SearchIndex.res new file mode 100644 index 000000000..7badc03b3 --- /dev/null +++ b/src/common/SearchIndex.res @@ -0,0 +1,495 @@ +type hierarchy = { + lvl0: string, + lvl1: string, + lvl2: option, + lvl3: option, + lvl4: option, + lvl5: option, + lvl6: option, +} + +type weight = { + pageRank: int, + level: int, + position: int, +} + +type record = { + objectID: string, + url: string, + url_without_anchor: string, + anchor: option, + content: option, + @as("type") type_: string, + hierarchy: hierarchy, + weight: weight, +} + +type heading = { + level: int, + text: string, + content: string, +} + +let maxContentLength = 500 + +let makeHierarchy = (~lvl0, ~lvl1, ~lvl2=?, ~lvl3=?, ~lvl4=?, ~lvl5=?, ~lvl6=?, ()) => { + lvl0, + lvl1, + lvl2, + lvl3, + lvl4, + lvl5, + lvl6, +} + +let truncate = (str: string, ~maxLen: int): string => + switch String.length(str) > maxLen { + | true => String.slice(str, ~start=0, ~end=maxLen) ++ "..." + | false => str + } + +// --- Helpers --- + +let slugify = (text: string): string => + text + ->String.toLowerCase + ->String.replaceRegExp(RegExp.fromString("\\s+", ~flags="g"), "-") + ->String.replaceRegExp(RegExp.fromString("[^a-z0-9\\-]", ~flags="g"), "") + +let stripMdxTags = (text: string): string => + text + ->String.replaceRegExp(RegExp.fromString("", ~flags="g"), "") + ->String.replaceRegExp(RegExp.fromString("<[^>]+>", ~flags="g"), "") + ->String.replaceRegExp(RegExp.fromString("```[\\s\\S]*?```", ~flags="g"), "") + ->String.replaceRegExp(RegExp.fromString("`([^`]+)`", ~flags="g"), "$1") + ->String.replaceRegExp(RegExp.fromString("\\*\\*([^*]+)\\*\\*", ~flags="g"), "$1") + ->String.replaceRegExp(RegExp.fromString("\\*([^*]+)\\*", ~flags="g"), "$1") + ->String.replaceRegExp(RegExp.fromString("\\[([^\\]]+)\\]\\([^)]*\\)", ~flags="g"), "$1") + ->String.replaceRegExp(RegExp.fromString("^#{1,6}\\s+", ~flags="gm"), "") + ->String.replaceRegExp(RegExp.fromString("\\n{2,}", ~flags="g"), "\n") + ->String.trim + +let cleanDocstring = (text: string): string => + text + // Take content before first heading + ->String.split("\n## ") + ->Array.get(0) + ->Option.getOr(text) + // Take content before first code block + ->String.split("\n```") + ->Array.get(0) + ->Option.getOr(text) + // Strip inline code backticks + ->String.replaceRegExp(RegExp.fromString("`([^`]+)`", ~flags="g"), "$1") + // Strip bold + ->String.replaceRegExp(RegExp.fromString("\\*\\*([^*]+)\\*\\*", ~flags="g"), "$1") + // Strip italic + ->String.replaceRegExp(RegExp.fromString("\\*([^*]+)\\*", ~flags="g"), "$1") + // Strip links + ->String.replaceRegExp(RegExp.fromString("\\[([^\\]]+)\\]\\([^)]*\\)", ~flags="g"), "$1") + // Collapse multiple newlines into space + ->String.replaceRegExp(RegExp.fromString("\\n{2,}", ~flags="g"), " ") + // Replace remaining newlines with space + ->String.replaceRegExp(RegExp.fromString("\\n", ~flags="g"), " ") + ->String.trim + +let extractIntro = (content: string): string => { + let parts = content->String.split("\n## ") + let intro = parts[0]->Option.getOr("") + intro + // Remove the # H1 heading line if present at the start + ->String.replaceRegExp(RegExp.fromString("^#[^#].*\\n", ~flags=""), "") + ->stripMdxTags + ->String.trim +} + +let findHeadingMatches: string => array<{..}> = %raw(` + function(content) { + var regex = /^(#{2,6})\s+(.+)$/gm; + var results = []; + var match; + while ((match = regex.exec(content)) !== null) { + results.push({ index: match.index, level: match[1].length, text: match[2] }); + } + return results; + } +`) + +let extractHeadings = (content: string): array => { + let matches = findHeadingMatches(content) + + matches->Array.mapWithIndex((m, i) => { + let startIdx = m["index"] + String.length(m["text"]) + m["level"] + 2 + let endIdx = switch matches[i + 1] { + | Some(next) => next["index"] + | None => String.length(content) + } + let sectionContent = + content + ->String.slice(~start=startIdx, ~end=endIdx) + ->stripMdxTags + ->String.trim + ->truncate(~maxLen=maxContentLength) + + { + level: m["level"], + text: m["text"], + content: sectionContent, + } + }) +} + +// --- File collection --- + +let rec collectFiles = (dirPath: string): array => { + let entries = Node.Fs.readdirSync(dirPath) + entries->Array.reduce([], (acc, entry) => { + let fullPath = Node.Path.join([dirPath, entry]) + let stats = Node.Fs.statSync(fullPath) + switch stats["isDirectory"]() { + | true => acc->Array.concat(collectFiles(fullPath)) + | false => { + acc->Array.push(fullPath) + acc + } + } + }) +} + +let isMdxFile = (path: string): bool => Node.Path.extname(path) === ".mdx" + +let filenameWithoutExt = (path: string): string => + Node.Path.basename(path)->String.replace(".mdx", "") + +// --- Record builders --- + +let buildMarkdownRecords = ( + ~category: string, + ~basePath: string, + ~dirPath: string, + ~pageRank: int, +): array => { + collectFiles(dirPath) + ->Array.filter(isMdxFile) + ->Array.flatMap(filePath => { + let fileContent = Node.Fs.readFileSync2(filePath, "utf8") + let parsed = MarkdownParser.parseSync(fileContent) + + switch DocFrontmatter.decode(parsed.frontmatter) { + | None => [] + | Some(fm) => { + let pageUrl = switch fm.canonical->Null.toOption { + | Some(canonical) => canonical + | None => basePath ++ "/" ++ filenameWithoutExt(filePath) + } + + let introText = parsed.content->extractIntro->truncate(~maxLen=maxContentLength) + let pageContent = switch introText { + | "" => fm.description->Null.toOption->Option.getOr("") + | text => text + } + + let pageRecord = { + objectID: pageUrl, + url: pageUrl, + url_without_anchor: pageUrl, + anchor: None, + content: Some(pageContent->truncate(~maxLen=maxContentLength)), + type_: "lvl1", + hierarchy: makeHierarchy(~lvl0=category, ~lvl1=fm.title, ()), + weight: {pageRank, level: 100, position: 0}, + } + + let headingRecords = + parsed.content + ->extractHeadings + ->Array.mapWithIndex((heading, i) => { + let anchor = slugify(heading.text) + let headingUrl = pageUrl ++ "#" ++ anchor + let typeLvl = switch heading.level { + | 2 => "lvl2" + | 3 => "lvl3" + | 4 => "lvl4" + | 5 => "lvl5" + | _ => "lvl6" + } + let weightLevel = switch heading.level { + | 2 => 80 + | 3 => 60 + | 4 => 40 + | 5 => 20 + | _ => 10 + } + let hierarchy = switch heading.level { + | 2 => makeHierarchy(~lvl0=category, ~lvl1=fm.title, ~lvl2=heading.text, ()) + | 3 => + makeHierarchy( + ~lvl0=category, + ~lvl1=fm.title, + ~lvl2=heading.text, + ~lvl3=heading.text, + (), + ) + | 4 => + makeHierarchy( + ~lvl0=category, + ~lvl1=fm.title, + ~lvl2=heading.text, + ~lvl3=heading.text, + ~lvl4=heading.text, + (), + ) + | _ => makeHierarchy(~lvl0=category, ~lvl1=fm.title, ~lvl2=heading.text, ()) + } + + { + objectID: headingUrl, + url: headingUrl, + url_without_anchor: pageUrl, + anchor: Some(anchor), + content: switch heading.content { + | "" => None + | c => Some(c) + }, + type_: typeLvl, + hierarchy, + weight: {pageRank, level: weightLevel, position: i + 1}, + } + }) + + [pageRecord]->Array.concat(headingRecords) + } + } + }) +} + +let buildBlogRecords = (~dirPath: string, ~pageRank: int): array => { + open JSON + Node.Fs.readdirSync(dirPath) + ->Array.filter(entry => isMdxFile(entry) && entry !== "archived") + ->Array.filterMap(entry => { + let fullPath = Node.Path.join([dirPath, entry]) + let stats = Node.Fs.statSync(fullPath) + switch stats["isDirectory"]() { + | true => None + | false => { + let fileContent = Node.Fs.readFileSync2(fullPath, "utf8") + let parsed = MarkdownParser.parseSync(fileContent) + + switch parsed.frontmatter { + | Object(dict{"title": String(title), "description": ?description}) => { + let slug = filenameWithoutExt(fullPath) + let url = "/blog/" ++ slug + let desc = switch description { + | Some(String(d)) => Some(d->truncate(~maxLen=maxContentLength)) + | _ => None + } + + Some({ + objectID: url, + url, + url_without_anchor: url, + anchor: None, + content: desc, + type_: "lvl1", + hierarchy: makeHierarchy(~lvl0="Blog", ~lvl1=title, ()), + weight: {pageRank, level: 100, position: 0}, + }) + } + | _ => None + } + } + } + }) +} + +let buildSyntaxLookupRecords = (~dirPath: string, ~pageRank: int): array => { + open JSON + Node.Fs.readdirSync(dirPath) + ->Array.filter(isMdxFile) + ->Array.filterMap(entry => { + let fullPath = Node.Path.join([dirPath, entry]) + let fileContent = Node.Fs.readFileSync2(fullPath, "utf8") + let parsed = MarkdownParser.parseSync(fileContent) + + switch parsed.frontmatter { + | Object(dict{ + "id": String(id), + "name": String(name), + "summary": String(summary), + "keywords": ?_keywords, + }) => + Some({ + objectID: "syntax-" ++ id, + url: "/syntax-lookup", + url_without_anchor: "/syntax-lookup", + anchor: None, + content: Some(summary->truncate(~maxLen=maxContentLength)), + type_: "lvl1", + hierarchy: makeHierarchy(~lvl0="Syntax", ~lvl1=name, ()), + weight: {pageRank, level: 100, position: 0}, + }) + | _ => None + } + }) +} + +let buildApiRecords = ( + ~basePath: string, + ~dirPath: string, + ~pageRank: int, + ~category: string, + ~files: option>=?, +): array => { + open JSON + Node.Fs.readdirSync(dirPath) + ->Array.filter(entry => { + let isJson = String.endsWith(entry, ".json") && entry !== "toc_tree.json" + switch files { + | Some(allowed) => isJson && allowed->Array.includes(entry) + | None => isJson + } + }) + ->Array.flatMap(entry => { + let fullPath = Node.Path.join([dirPath, entry]) + let fileContent = Node.Fs.readFileSync2(fullPath, "utf8") + + switch JSON.parseOrThrow(fileContent) { + | Object(modules) => + modules + ->Dict.toArray + ->Array.flatMap(((key, moduleJson)) => { + switch moduleJson { + | Object(dict{ + "id": String(id), + "name": String(name), + "docstrings": Array(docstrings), + "items": Array(items), + }) => { + let moduleUrl = basePath ++ "/" ++ key + let moduleDocstring = switch docstrings[0] { + | Some(String(d)) => Some(d->cleanDocstring->truncate(~maxLen=maxContentLength)) + | _ => None + } + + let moduleRecord = { + objectID: id, + url: moduleUrl, + url_without_anchor: moduleUrl, + anchor: None, + content: moduleDocstring, + type_: "lvl1", + hierarchy: makeHierarchy(~lvl0=category, ~lvl1=name, ()), + weight: {pageRank, level: 90, position: 0}, + } + + let sortedItems = items->Array.toSorted( + (a, b) => { + switch (a, b) { + | (Object(dict{"name": String(nameA)}), Object(dict{"name": String(nameB)})) => + nameA->String.localeCompare(nameB) + | _ => 0. + } + }, + ) + + let itemRecords = sortedItems->Array.filterMapWithIndex( + (item, i) => { + switch item { + | Object(dict{ + "id": String(itemId), + "name": String(itemName), + "docstrings": Array(itemDocstrings), + "signature": ?signature, + "kind": String(kind), + }) => { + let kindPrefix = switch kind { + | "type" => "type-" + | _ => "value-" + } + let itemAnchor = kindPrefix ++ itemName + let itemUrl = moduleUrl ++ "#" ++ itemAnchor + let firstDocstring = switch itemDocstrings[0] { + | Some(String(d)) => Some(d->cleanDocstring) + | _ => None + } + let qualifiedName = name ++ "." ++ itemName + let content = switch firstDocstring { + | Some(d) if String.length(d) > 0 => + Some((qualifiedName ++ " - " ++ d)->truncate(~maxLen=maxContentLength)) + | _ => + switch signature { + | Some(String(s)) => + Some((qualifiedName ++ " - " ++ s)->truncate(~maxLen=maxContentLength)) + | _ => Some(qualifiedName) + } + } + + Some({ + objectID: itemId, + url: itemUrl, + url_without_anchor: moduleUrl, + anchor: Some(itemAnchor), + content, + type_: "lvl2", + hierarchy: makeHierarchy(~lvl0=category, ~lvl1=name, ~lvl2=qualifiedName, ()), + weight: {pageRank, level: 70, position: i}, + }) + } + | _ => None + } + }, + ) + + [moduleRecord]->Array.concat(itemRecords) + } + | _ => [] + } + }) + | _ => [] + | exception _ => [] + } + }) +} + +// --- JSON serialization --- + +let optionToJson = (opt: option): JSON.t => + switch opt { + | Some(s) => JSON.String(s) + | None => JSON.Null + } + +let hierarchyToJson = (h: hierarchy): JSON.t => { + let dict = Dict.make() + dict->Dict.set("lvl0", JSON.String(h.lvl0)) + dict->Dict.set("lvl1", JSON.String(h.lvl1)) + dict->Dict.set("lvl2", optionToJson(h.lvl2)) + dict->Dict.set("lvl3", optionToJson(h.lvl3)) + dict->Dict.set("lvl4", optionToJson(h.lvl4)) + dict->Dict.set("lvl5", optionToJson(h.lvl5)) + dict->Dict.set("lvl6", optionToJson(h.lvl6)) + JSON.Object(dict) +} + +let weightToJson = (w: weight): JSON.t => { + let dict = Dict.make() + dict->Dict.set("pageRank", JSON.Number(Int.toFloat(w.pageRank))) + dict->Dict.set("level", JSON.Number(Int.toFloat(w.level))) + dict->Dict.set("position", JSON.Number(Int.toFloat(w.position))) + JSON.Object(dict) +} + +let toJson = (r: record): JSON.t => { + let dict = Dict.make() + dict->Dict.set("objectID", JSON.String(r.objectID)) + dict->Dict.set("url", JSON.String(r.url)) + dict->Dict.set("url_without_anchor", JSON.String(r.url_without_anchor)) + dict->Dict.set("anchor", optionToJson(r.anchor)) + dict->Dict.set("content", optionToJson(r.content)) + dict->Dict.set("type", JSON.String(r.type_)) + dict->Dict.set("hierarchy", hierarchyToJson(r.hierarchy)) + dict->Dict.set("weight", weightToJson(r.weight)) + JSON.Object(dict) +} diff --git a/src/components/Meta.res b/src/components/Meta.res index 3479d57f5..903af53c8 100644 --- a/src/components/Meta.res +++ b/src/components/Meta.res @@ -65,8 +65,6 @@ let make = ( - // Docsearch meta tags - // Robots meta tag diff --git a/src/components/Search.res b/src/components/Search.res index b9ccbb103..2d2ce32fa 100644 --- a/src/components/Search.res +++ b/src/components/Search.res @@ -1,103 +1,13 @@ -let apiKey = "a2485ef172b8cd82a2dfa498d551399b" -let indexName = "rescript-lang" -let appId = "S32LNEY41T" +let apiKey = Env.algolia_read_api_key +let indexName = Env.algolia_index_name +let appId = Env.algolia_app_id type state = Active | Inactive -let hit = ({hit, children}: DocSearch.hitComponent) => { - let toTitle = str => str->String.charAt(0)->String.toUpperCase ++ String.slice(str, ~start=1) - - let description = switch hit.url - ->String.split("/") - ->Array.slice(~start=1) - ->List.fromArray { - | list{"blog" as r | "community" as r, ..._} => r->toTitle - | list{"docs", doc, version, ...rest} => - let path = rest->List.toArray - - let info = - path - ->Array.slice(~start=0, ~end=Array.length(path) - 1) - ->Array.map(path => - switch path { - | "api" => "API" - | other => toTitle(other) - } - ) - - [doc->toTitle, version->toTitle]->Array.concat(info)->Array.join(" / ") - | _ => "" - } - - let isDeprecated = hit.deprecated->Option.isSome - let deprecatedBadge = isDeprecated - ? - {"Deprecated"->React.string} - - : React.null - - - - {deprecatedBadge} - {description->React.string} - - children - -} - -let transformItems = (items: DocSearch.transformItems) => { - items - ->Array.filterMap(item => { - let url = try WebAPI.URL.make(~url=item.url)->Some catch { - | JsExn(obj) => - Console.error2(`Failed to parse URL ${item.url}`, obj) - None - } - switch url { - | Some({pathname, hash}) => - RegExp.test(/v(8|9|10|11)\./, pathname) - ? None - : { - // DocSearch internally calls .replace() on hierarchy.lvl1, so we must - // provide a fallback for items where lvl1 is null to prevent crashes - let hierarchy = item.hierarchy - let lvl0 = hierarchy.lvl0->Nullable.toOption->Option.getOr("") - let lvl1 = hierarchy.lvl1->Nullable.toOption->Option.getOr(lvl0) - Some({ - ...item, - deprecated: pathname->String.includes("api/js") || - pathname->String.includes("api/core") - ? Some("Deprecated") - : None, - url: pathname->String.replace("/v12.0.0/", "/") ++ hash, - hierarchy: { - ...hierarchy, - lvl0: Nullable.make(lvl0), - lvl1: Nullable.make(lvl1), - }, - }) - } - - | None => None - } - }) - // Sort deprecated items to the end - ->Array.toSorted((a, b) => { - switch (a.deprecated, b.deprecated) { - | (Some(_), None) => 1. // a is deprecated, b is not - put a after b - | (None, Some(_)) => -1. // a is not deprecated, b is - put a before b - | _ => 0. - } - }) - ->Array.toSorted((a, b) => { - switch (a.url->String.includes("api/stdlib"), b.url->String.includes("api/stdlib")) { - | (true, false) => -1. // a is a stdlib doc, b is not - put a before b - | (false, true) => 1. // a is not a stdlib doc, b is - put a after b - | _ => 0. // both same API status - maintain original order - } - }) +let navigator: DocSearch.navigator = { + navigate: ({itemUrl}) => { + ReactRouter.navigate(itemUrl) + }, } @react.component @@ -174,10 +84,10 @@ let make = () => { apiKey appId indexName + navigator onClose initialScrollY={window.scrollY->Float.toInt} - transformItems={transformItems} - hitComponent=hit + searchParameters={distinct: 3, hitsPerPage: 20} />, element, ) diff --git a/yarn.lock b/yarn.lock index bdd5ac415..549948745 100644 --- a/yarn.lock +++ b/yarn.lock @@ -70,6 +70,18 @@ __metadata: languageName: node linkType: hard +"@algolia/abtesting@npm:1.16.1": + version: 1.16.1 + resolution: "@algolia/abtesting@npm:1.16.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/0ca113338a447693b4827bdf87f37490ccd81bc1bbbe39b02c338ff79582379a68853c3d35fb2297fd5636fa43818dac9e04b59965a8b47851e8b1da041b45e8 + languageName: node + linkType: hard + "@algolia/autocomplete-core@npm:1.19.2": version: 1.19.2 resolution: "@algolia/autocomplete-core@npm:1.19.2" @@ -113,6 +125,18 @@ __metadata: languageName: node linkType: hard +"@algolia/client-abtesting@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/client-abtesting@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/a3fb097e72acc5f1b009694774c0b23e1a7701ec4f54bbf4b20114f9adc73565f8d8c7fba492d769b6f5becd1ef4bf6b92073fb289cd06bfb3e12b2f0989f9ae + languageName: node + linkType: hard + "@algolia/client-analytics@npm:5.45.0": version: 5.45.0 resolution: "@algolia/client-analytics@npm:5.45.0" @@ -125,6 +149,18 @@ __metadata: languageName: node linkType: hard +"@algolia/client-analytics@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/client-analytics@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/ade9f7ee8e8872f0c54149a9292fc32bad9e0b189068ca283f7110ce3f638b14c5078ce43d2c00c2bf752d3aa96e6bea63e4f1184cbe5bc36501074d96595d05 + languageName: node + linkType: hard + "@algolia/client-common@npm:5.45.0": version: 5.45.0 resolution: "@algolia/client-common@npm:5.45.0" @@ -132,6 +168,13 @@ __metadata: languageName: node linkType: hard +"@algolia/client-common@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/client-common@npm:5.50.1" + checksum: 10c0/4750773473748fec73a7a9be3081274e21f2c4ccac463618b2ec470113c44c1f6961a991382c999acf04bd83e074547cd57c6304c4218d31bb0089b5c1099bf3 + languageName: node + linkType: hard + "@algolia/client-insights@npm:5.45.0": version: 5.45.0 resolution: "@algolia/client-insights@npm:5.45.0" @@ -144,6 +187,18 @@ __metadata: languageName: node linkType: hard +"@algolia/client-insights@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/client-insights@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/62ca243328f38e9a245e2860c12d1e76529e9bf68d5a30881a053adf5cbaddda27af631edd33e23d879a9e5445c66e2654f0149695cd1b75b09b42ea57ef575f + languageName: node + linkType: hard + "@algolia/client-personalization@npm:5.45.0": version: 5.45.0 resolution: "@algolia/client-personalization@npm:5.45.0" @@ -156,6 +211,18 @@ __metadata: languageName: node linkType: hard +"@algolia/client-personalization@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/client-personalization@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/cbc099bd7a5f8ccefd4135a59dfa2b6136b751ed35d451a0c89738c8ad404195348d5553630ab8e59f056f17b8a284e918151696050b740d96e304c8f40174fd + languageName: node + linkType: hard + "@algolia/client-query-suggestions@npm:5.45.0": version: 5.45.0 resolution: "@algolia/client-query-suggestions@npm:5.45.0" @@ -168,6 +235,18 @@ __metadata: languageName: node linkType: hard +"@algolia/client-query-suggestions@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/client-query-suggestions@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/345e0ecaf587aec2a956c2039da817fd26e203c8689fe8e0d428baf6ab03f0809a936099ae420e779d3ec252bbcaf3061c6e8670c660d7a9d66e98627d8938df + languageName: node + linkType: hard + "@algolia/client-search@npm:5.45.0": version: 5.45.0 resolution: "@algolia/client-search@npm:5.45.0" @@ -180,6 +259,18 @@ __metadata: languageName: node linkType: hard +"@algolia/client-search@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/client-search@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/7910c074aa7b4fbbad2af082a7623d7d65ba0c19e0933d4658e43d588cd87ed2e851aad0c5428ce2a00a3e3248349fcda20ed5abb7700b93d03a475e2ce7a378 + languageName: node + linkType: hard + "@algolia/ingestion@npm:1.45.0": version: 1.45.0 resolution: "@algolia/ingestion@npm:1.45.0" @@ -192,6 +283,18 @@ __metadata: languageName: node linkType: hard +"@algolia/ingestion@npm:1.50.1": + version: 1.50.1 + resolution: "@algolia/ingestion@npm:1.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/0d5264db46783d648246406349fe88dbc6fa1cdd74ed16500bb8a4e5efb1bdfd7174780065566fcb7317f7ba8ac858677ffb0d5194a1315c0ce6003bd4219d87 + languageName: node + linkType: hard + "@algolia/monitoring@npm:1.45.0": version: 1.45.0 resolution: "@algolia/monitoring@npm:1.45.0" @@ -204,6 +307,18 @@ __metadata: languageName: node linkType: hard +"@algolia/monitoring@npm:1.50.1": + version: 1.50.1 + resolution: "@algolia/monitoring@npm:1.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/378076310011c77c91378a597d86d791d4821d1d00e3c500ec8828e72b9036bb974abb09bd0c10aa05fc75a50aa443be26985104ca78524a0a0cf34707536c70 + languageName: node + linkType: hard + "@algolia/recommend@npm:5.45.0": version: 5.45.0 resolution: "@algolia/recommend@npm:5.45.0" @@ -216,6 +331,18 @@ __metadata: languageName: node linkType: hard +"@algolia/recommend@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/recommend@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/0cf061bf2fc46240d93c6fe032693e143a5eb61a3fc27f619141ebea735b7e7f6c5c38b31b152e9ef074b61373549a1f72a76399d80ed55840251cc71438f829 + languageName: node + linkType: hard + "@algolia/requester-browser-xhr@npm:5.45.0": version: 5.45.0 resolution: "@algolia/requester-browser-xhr@npm:5.45.0" @@ -225,6 +352,15 @@ __metadata: languageName: node linkType: hard +"@algolia/requester-browser-xhr@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/requester-browser-xhr@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + checksum: 10c0/aa55122f483a0d1572da20b71b0b533493960894460ad545a6a50e1c73780affd4764d68aa5a1687894d23c31a972cc92886a0d8ed3324b6f5457efd58b424af + languageName: node + linkType: hard + "@algolia/requester-fetch@npm:5.45.0": version: 5.45.0 resolution: "@algolia/requester-fetch@npm:5.45.0" @@ -234,6 +370,15 @@ __metadata: languageName: node linkType: hard +"@algolia/requester-fetch@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/requester-fetch@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + checksum: 10c0/07232c12ff0a5b25e5e6dfeeed8e46765f347926f263774e9ae061e65bd1ddce029f78fd5feaa34e23c80e80b0a84874d8799f817368e924cc904aef4f8f8181 + languageName: node + linkType: hard + "@algolia/requester-node-http@npm:5.45.0": version: 5.45.0 resolution: "@algolia/requester-node-http@npm:5.45.0" @@ -243,6 +388,15 @@ __metadata: languageName: node linkType: hard +"@algolia/requester-node-http@npm:5.50.1": + version: 5.50.1 + resolution: "@algolia/requester-node-http@npm:5.50.1" + dependencies: + "@algolia/client-common": "npm:5.50.1" + checksum: 10c0/51be1452a28d4aeb97306121d164a3161fb55b775189df631f968bc752e00538a9872d0e0a2ad97744f8ca87c39f8352b526b8b290805ddaf5a2d4f43ae3360f + languageName: node + linkType: hard + "@asamuzakjp/css-color@npm:^3.2.0": version: 3.2.0 resolution: "@asamuzakjp/css-color@npm:3.2.0" @@ -3478,6 +3632,28 @@ __metadata: languageName: node linkType: hard +"algoliasearch@npm:^5.50.1": + version: 5.50.1 + resolution: "algoliasearch@npm:5.50.1" + dependencies: + "@algolia/abtesting": "npm:1.16.1" + "@algolia/client-abtesting": "npm:5.50.1" + "@algolia/client-analytics": "npm:5.50.1" + "@algolia/client-common": "npm:5.50.1" + "@algolia/client-insights": "npm:5.50.1" + "@algolia/client-personalization": "npm:5.50.1" + "@algolia/client-query-suggestions": "npm:5.50.1" + "@algolia/client-search": "npm:5.50.1" + "@algolia/ingestion": "npm:1.50.1" + "@algolia/monitoring": "npm:1.50.1" + "@algolia/recommend": "npm:5.50.1" + "@algolia/requester-browser-xhr": "npm:5.50.1" + "@algolia/requester-fetch": "npm:5.50.1" + "@algolia/requester-node-http": "npm:5.50.1" + checksum: 10c0/4b91f019c89324786e23f90b7773eb82b142e8075c95f204cf6fc07f320fcbbf623ca338509647d93b9776f4645a1f72debb2800627c4bf1b80e3ed8f2b398b1 + languageName: node + linkType: hard + "ansi-align@npm:^3.0.1": version: 3.0.1 resolution: "ansi-align@npm:3.0.1" @@ -9367,6 +9543,7 @@ __metadata: "@types/react": "npm:^19.2.14" "@vitejs/plugin-react": "npm:^6.0.1" "@vitest/browser-playwright": "npm:^4.1.2" + algoliasearch: "npm:^5.50.1" auto-image-converter: "npm:^2.1.2" chokidar: "npm:^4.0.3" docson: "npm:^2.1.0" From 6945d0f4f14afcfdb82c5afa9a89f12c1bc80501 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 6 Apr 2026 17:27:48 -0400 Subject: [PATCH 03/12] sorting and formatting --- package.json | 2 +- scripts/generate_search_index.res | 70 +++++++++++++++++++++++++---- src/bindings/DocSearch.res | 1 + src/common/SearchIndex.res | 75 ++++++++++++++++++++++++++----- src/components/Search.res | 41 ++++++++++++++++- styles/_docsearch.css | 12 ++++- 6 files changed, 179 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 062d17519..d3446d27e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "build:generate-llms": "node _scripts/generate_llms.mjs", "build:res": "rescript build --warn-error +3+8+11+12+26+27+31+32+33+34+35+39+44+45+110", "build:sync-bundles": "node scripts/sync-playground-bundles.mjs", - "build:search-index": "node --env-file-if-exists=.env.local _scripts/generate_search_index.mjs", + "build:search-index": "node --env-file-if-exists=.env --env-file-if-exists=.env.local _scripts/generate_search_index.mjs", "build:update-index": "yarn build:generate-llms && node _scripts/generate_feed.mjs > public/blog/feed.xml && yarn build:search-index", "build:vite": "react-router build", "build": "yarn build:res && yarn build:scripts && yarn build:update-index && yarn build:vite", diff --git a/scripts/generate_search_index.res b/scripts/generate_search_index.res index b786cf117..617d5a21f 100644 --- a/scripts/generate_search_index.res +++ b/scripts/generate_search_index.res @@ -1,5 +1,5 @@ // Build script: reads all site content, builds Algolia search records, and uploads them. -// Runs as a standalone Node script via: node --env-file-if-exists=.env.local _scripts/generate_search_index.mjs +// Runs as a standalone Node script via: node --env-file-if-exists=.env --env-file-if-exists=.env.local _scripts/generate_search_index.mjs // // Required env vars: // ALGOLIA_ADMIN_API_KEY -- API key with addObject/deleteObject/editSettings ACLs @@ -17,6 +17,59 @@ let getEnv = (key: string): option => } ) +let compareVersions = (a: string, b: string): float => { + let parse = (v: string) => + v + ->String.replaceRegExp(RegExp.fromString("^v", ~flags=""), "") + ->String.split(".") + ->Array.map(s => Int.fromString(s)->Option.getOr(0)) + let partsA = parse(a) + let partsB = parse(b) + switch (partsA[0], partsB[0]) { + | (Some(a0), Some(b0)) if a0 !== b0 => Int.toFloat(a0 - b0) + | _ => + switch (partsA[1], partsB[1]) { + | (Some(a1), Some(b1)) if a1 !== b1 => Int.toFloat(a1 - b1) + | _ => + switch (partsA[2], partsB[2]) { + | (Some(a2), Some(b2)) => Int.toFloat(a2 - b2) + | _ => 0.0 + } + } + } +} + +let resolveApiDir = (): option => { + let majorVersion = + getEnv("VITE_VERSION_LATEST") + ->Option.map(v => v->String.replaceRegExp(RegExp.fromString("^v", ~flags=""), "")) + ->Option.flatMap(v => v->String.split(".")->Array.get(0)) + switch majorVersion { + | None => { + Console.log("[search-index] VITE_VERSION_LATEST not set, cannot resolve API version.") + None + } + | Some(major) => { + let prefix = "v" ++ major ++ "." + let entries = Node.Fs.readdirSync("data/api") + let matching = + entries + ->Array.filter(entry => String.startsWith(entry, prefix)) + ->Array.toSorted(compareVersions) + switch matching->Array.at(-1) { + | Some(dir) => { + Console.log(`[search-index] Resolved API version: ${dir}`) + Some("data/api/" ++ dir) + } + | None => { + Console.log(`[search-index] No API version found matching v${major}.*`) + None + } + } + } + } +} + let main = async () => { let appId = getEnv("ALGOLIA_APP_ID") let adminApiKey = getEnv("ALGOLIA_ADMIN_API_KEY") @@ -26,6 +79,8 @@ let main = async () => { | (Some(appId), Some(apiKey), Some(idx)) => { Console.log("[search-index] Building search index records...") + let apiDir = resolveApiDir()->Option.getOr("markdown-pages/docs/api") + // 1. Build records from all content sources let manualRecords = SearchIndex.buildMarkdownRecords( ~category="Manual", @@ -70,7 +125,7 @@ let main = async () => { let stdlibApiRecords = SearchIndex.buildApiRecords( ~basePath="/docs/manual/api", - ~dirPath="markdown-pages/docs/api", + ~dirPath=apiDir, ~pageRank=80, ~category="API / StdLib", ~files=["stdlib.json"], @@ -81,7 +136,7 @@ let main = async () => { let beltApiRecords = SearchIndex.buildApiRecords( ~basePath="/docs/manual/api", - ~dirPath="markdown-pages/docs/api", + ~dirPath=apiDir, ~pageRank=75, ~category="API / Belt", ~files=["belt.json"], @@ -92,7 +147,7 @@ let main = async () => { let domApiRecords = SearchIndex.buildApiRecords( ~basePath="/docs/manual/api", - ~dirPath="markdown-pages/docs/api", + ~dirPath=apiDir, ~pageRank=70, ~category="API / DOM", ~files=["dom.json"], @@ -150,7 +205,7 @@ let main = async () => { exactOnSingleWordQuery: "word", attributesForFaceting: ["type"], customRanking: ["desc(weight.pageRank)", "desc(weight.level)", "asc(weight.position)"], - attributesToSnippet: ["content:30"], + attributesToSnippet: [], attributeForDistinct: "hierarchy.lvl0", }, }) @@ -159,9 +214,8 @@ let main = async () => { Console.log("[search-index] Done.") } | (None, _, _) => Console.log("[search-index] ALGOLIA_APP_ID not set, skipping index upload.") - | (_, None, _) => Console.log( - "[search-index] ALGOLIA_ADMIN_API_KEY not set, skipping index upload.", - ) + | (_, None, _) => + Console.log("[search-index] ALGOLIA_ADMIN_API_KEY not set, skipping index upload.") | (_, _, None) => Console.log("[search-index] ALGOLIA_INDEX_NAME not set, skipping index upload.") } } diff --git a/src/bindings/DocSearch.res b/src/bindings/DocSearch.res index 083dc7ba2..9efbf91b7 100644 --- a/src/bindings/DocSearch.res +++ b/src/bindings/DocSearch.res @@ -48,6 +48,7 @@ type searchParameters = { facetFilters?: array, hitsPerPage?: int, distinct?: int, + attributesToSnippet?: array, } @module("@docsearch/react") @react.component diff --git a/src/common/SearchIndex.res b/src/common/SearchIndex.res index 7badc03b3..23b48c1e7 100644 --- a/src/common/SearchIndex.res +++ b/src/common/SearchIndex.res @@ -94,6 +94,49 @@ let cleanDocstring = (text: string): string => ->String.replaceRegExp(RegExp.fromString("\\n", ~flags="g"), " ") ->String.trim +let stripMarkdownFull = (text: string): string => + text + // Strip code blocks but keep code content + ->String.replaceRegExp(RegExp.fromString("```\\w*\\n?([\\s\\S]*?)```", ~flags="g"), "$1") + // Strip inline code backticks + ->String.replaceRegExp(RegExp.fromString("`([^`]+)`", ~flags="g"), "$1") + // Strip bold + ->String.replaceRegExp(RegExp.fromString("\\*\\*([^*]+)\\*\\*", ~flags="g"), "$1") + // Strip italic + ->String.replaceRegExp(RegExp.fromString("\\*([^*]+)\\*", ~flags="g"), "$1") + // Strip links (keep text, handle empty URLs too) + ->String.replaceRegExp(RegExp.fromString("\\[([^\\]]+)\\]\\([^)]*\\)", ~flags="g"), "$1") + // Strip heading markers + ->String.replaceRegExp(RegExp.fromString("^#{1,6}\\s+", ~flags="gm"), "") + // Collapse multiple newlines + ->String.replaceRegExp(RegExp.fromString("\\n{2,}", ~flags="g"), " ") + // Replace remaining newlines with space + ->String.replaceRegExp(RegExp.fromString("\\n", ~flags="g"), " ") + ->String.trim + +let docstringToSearchHtml = (text: string): string => + text + // Strip code blocks but keep code content wrapped in + ->String.replaceRegExp( + RegExp.fromString("```\\w*\\n?([\\s\\S]*?)```", ~flags="g"), + "$1", + ) + // Convert inline code backticks to tags + ->String.replaceRegExp(RegExp.fromString("`([^`]+)`", ~flags="g"), "$1") + // Strip bold + ->String.replaceRegExp(RegExp.fromString("\\*\\*([^*]+)\\*\\*", ~flags="g"), "$1") + // Strip italic + ->String.replaceRegExp(RegExp.fromString("\\*([^*]+)\\*", ~flags="g"), "$1") + // Strip links (keep text, handle empty URLs too) + ->String.replaceRegExp(RegExp.fromString("\\[([^\\]]+)\\]\\([^)]*\\)", ~flags="g"), "$1") + // Strip heading markers + ->String.replaceRegExp(RegExp.fromString("^#{1,6}\\s+", ~flags="gm"), "") + // Convert double newlines to
+ ->String.replaceRegExp(RegExp.fromString("\\n{2,}", ~flags="g"), "
") + // Replace remaining newlines with space + ->String.replaceRegExp(RegExp.fromString("\\n", ~flags="g"), " ") + ->String.trim + let extractIntro = (content: string): string => { let parts = content->String.split("\n## ") let intro = parts[0]->Option.getOr("") @@ -410,19 +453,29 @@ let buildApiRecords = ( } let itemAnchor = kindPrefix ++ itemName let itemUrl = moduleUrl ++ "#" ++ itemAnchor - let firstDocstring = switch itemDocstrings[0] { - | Some(String(d)) => Some(d->cleanDocstring) + let qualifiedName = name ++ "." ++ itemName + let docstringIntro = switch itemDocstrings[0] { + | Some(String(d)) if String.length(d) > 0 => { + // Take content before first heading or code block + let intro = + d + ->String.split("\n## ") + ->Array.get(0) + ->Option.getOr(d) + ->String.split("\n```") + ->Array.get(0) + ->Option.getOr(d) + ->String.trim + Some(intro->truncate(~maxLen=2000)) + } | _ => None } - let qualifiedName = name ++ "." ++ itemName - let content = switch firstDocstring { - | Some(d) if String.length(d) > 0 => - Some((qualifiedName ++ " - " ++ d)->truncate(~maxLen=maxContentLength)) + let content = switch docstringIntro { + | Some(d) if String.length(d) > 0 => Some(d) | _ => switch signature { - | Some(String(s)) => - Some((qualifiedName ++ " - " ++ s)->truncate(~maxLen=maxContentLength)) - | _ => Some(qualifiedName) + | Some(String(s)) => Some(s) + | _ => None } } @@ -432,8 +485,8 @@ let buildApiRecords = ( url_without_anchor: moduleUrl, anchor: Some(itemAnchor), content, - type_: "lvl2", - hierarchy: makeHierarchy(~lvl0=category, ~lvl1=name, ~lvl2=qualifiedName, ()), + type_: "lvl1", + hierarchy: makeHierarchy(~lvl0=category, ~lvl1=qualifiedName, ()), weight: {pageRank, level: 70, position: i}, }) } diff --git a/src/components/Search.res b/src/components/Search.res index 2d2ce32fa..66bfb6c0d 100644 --- a/src/components/Search.res +++ b/src/components/Search.res @@ -10,6 +10,44 @@ let navigator: DocSearch.navigator = { }, } +let getHighlightedTitle: DocSearch.docSearchHit => string = %raw(` + function(hit) { + try { return hit._highlightResult.hierarchy.lvl1.value; } + catch(e) { return hit.hierarchy.lvl1 || ''; } + } +`) + +let markdownToHtml = (text: string): string => + text + ->String.replaceRegExp(RegExp.fromString("\\[([^\\]]+)\\]\\([^)]*\\)", ~flags="g"), "$1") + ->String.replaceRegExp(RegExp.fromString("\\x60([^\\x60]+)\\x60", ~flags="g"), "$1") + ->String.replaceRegExp( + RegExp.fromString("\\*\\*([^*]+)\\*\\*", ~flags="g"), + "$1", + ) + ->String.replaceRegExp(RegExp.fromString("\\*([^*]+)\\*", ~flags="g"), "$1") + ->String.replaceRegExp(RegExp.fromString("\\n{2,}", ~flags="g"), "
") + ->String.replaceRegExp(RegExp.fromString("\\n", ~flags="g"), " ") + ->String.trim + +let hitComponent = ({hit, children: _}: DocSearch.hitComponent): React.element => { + let titleHtml = getHighlightedTitle(hit) + let contentHtml = hit.content->Nullable.toOption->Option.map(markdownToHtml) + + +
+
+ + {switch contentHtml { + | Some(c) if String.length(c) > 0 => + + | _ => React.null + }} +
+
+
+} + @react.component let make = () => { let (state, setState) = React.useState(_ => Inactive) @@ -85,9 +123,10 @@ let make = () => { appId indexName navigator + hitComponent onClose initialScrollY={window.scrollY->Float.toInt} - searchParameters={distinct: 3, hitsPerPage: 20} + searchParameters={distinct: 3, hitsPerPage: 20, attributesToSnippet: ["content:9999"]} />, element, ) diff --git a/styles/_docsearch.css b/styles/_docsearch.css index ac8c167b7..b3d23c997 100644 --- a/styles/_docsearch.css +++ b/styles/_docsearch.css @@ -274,7 +274,17 @@ svg.DocSearch-Hit-Select-Icon { } .DocSearch-Hit-path { - @apply text-12; + @apply text-14 text-gray-60; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + white-space: normal !important; + text-overflow: ellipsis; +} + +.DocSearch-Hit-path code { + @apply bg-gray-10 text-black rounded-sm px-1 py-0.5 text-12 font-mono; } .DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-title, From 73b141a2747d4b4fd326bbe08f76a21b951da8f8 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 6 Apr 2026 17:30:53 -0400 Subject: [PATCH 04/12] remove MDN links --- src/components/Search.res | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/Search.res b/src/components/Search.res index 66bfb6c0d..efe25b2b1 100644 --- a/src/components/Search.res +++ b/src/components/Search.res @@ -19,6 +19,11 @@ let getHighlightedTitle: DocSearch.docSearchHit => string = %raw(` let markdownToHtml = (text: string): string => text + ->String.replaceRegExp( + RegExp.fromString("See\\s+\\[([^\\]]+)\\]\\([^)]*\\)\\s+on MDN\\.?", ~flags="g"), + "", + ) + ->String.replaceRegExp(RegExp.fromString("See\\s+\\S+\\s+on MDN\\.?", ~flags="g"), "") ->String.replaceRegExp(RegExp.fromString("\\[([^\\]]+)\\]\\([^)]*\\)", ~flags="g"), "$1") ->String.replaceRegExp(RegExp.fromString("\\x60([^\\x60]+)\\x60", ~flags="g"), "$1") ->String.replaceRegExp( From e2280ec43bbe1848c9d53fdfa0b6857abb5413d5 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 6 Apr 2026 17:34:05 -0400 Subject: [PATCH 05/12] improve visuals --- src/components/Search.res | 32 ++++++++++++++++++++++++++++++-- styles/_docsearch.css | 4 ++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/components/Search.res b/src/components/Search.res index efe25b2b1..1709e3d4a 100644 --- a/src/components/Search.res +++ b/src/components/Search.res @@ -12,13 +12,36 @@ let navigator: DocSearch.navigator = { let getHighlightedTitle: DocSearch.docSearchHit => string = %raw(` function(hit) { - try { return hit._highlightResult.hierarchy.lvl1.value; } - catch(e) { return hit.hierarchy.lvl1 || ''; } + var type = hit.type; + var h = hit._highlightResult && hit._highlightResult.hierarchy; + var raw = hit.hierarchy; + try { + if (type && type !== 'lvl1' && type !== 'lvl0') { + var lvl = h && h[type] && h[type].value; + if (lvl) return lvl; + } + if (h && h.lvl1 && h.lvl1.value) return h.lvl1.value; + } catch(e) {} + return (raw && raw.lvl1) || ''; + } +`) + +let getSubtitle: DocSearch.docSearchHit => option = %raw(` + function(hit) { + var type = hit.type; + if (type && type !== 'lvl1' && type !== 'lvl0') { + var raw = hit.hierarchy; + if (raw && raw.lvl1) return raw.lvl1; + } + return undefined; } `) let markdownToHtml = (text: string): string => text + // Strip stray backslashes from MDX processing + ->String.replaceRegExp(RegExp.fromString("^\\\\\\s+", ~flags=""), "") + ->String.replaceRegExp(RegExp.fromString("\\\\\\s+", ~flags="g"), " ") ->String.replaceRegExp( RegExp.fromString("See\\s+\\[([^\\]]+)\\]\\([^)]*\\)\\s+on MDN\\.?", ~flags="g"), "", @@ -37,12 +60,17 @@ let markdownToHtml = (text: string): string => let hitComponent = ({hit, children: _}: DocSearch.hitComponent): React.element => { let titleHtml = getHighlightedTitle(hit) + let subtitle = getSubtitle(hit) let contentHtml = hit.content->Nullable.toOption->Option.map(markdownToHtml)
+ {switch subtitle { + | Some(s) => {React.string(s)} + | None => React.null + }} {switch contentHtml { | Some(c) if String.length(c) > 0 => diff --git a/styles/_docsearch.css b/styles/_docsearch.css index b3d23c997..176a5e739 100644 --- a/styles/_docsearch.css +++ b/styles/_docsearch.css @@ -273,6 +273,10 @@ svg.DocSearch-Hit-Select-Icon { @apply text-14 text-gray-60; } +.DocSearch-Hit-subtitle { + @apply text-12 text-gray-40; +} + .DocSearch-Hit-path { @apply text-14 text-gray-60; display: -webkit-box; From aa24517e688462b6f8237074c10ee6cf613d68f9 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 6 Apr 2026 17:39:16 -0400 Subject: [PATCH 06/12] remove test file --- algolia.mjs | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 algolia.mjs diff --git a/algolia.mjs b/algolia.mjs deleted file mode 100644 index 7ba6a0cf8..000000000 --- a/algolia.mjs +++ /dev/null @@ -1,35 +0,0 @@ -// helloAlgolia.mjs -import { algoliasearch } from "algoliasearch"; - -const appID = "1T1PRULLJT"; -// API key with `addObject` and `editSettings` ACL -const apiKey = "999e5352ab7aed499de651ee79f573ee"; -const indexName = "dev_2026"; - -const client = algoliasearch(appID, apiKey); - -const record = { objectID: "object-1", name: "test record" }; - -// Add record to an index -const { taskID } = await client.saveObject({ - indexName, - body: record, -}); - -// Wait until indexing is done -await client.waitForTask({ - indexName, - taskID, -}); - -// Search for "test" -const { results } = await client.search({ - requests: [ - { - indexName, - query: "test", - }, - ], -}); - -console.log(JSON.stringify(results, null, 2)); From e6cc448ab42c3d3b660bd1a8c9d96a359f3eefb0 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 6 Apr 2026 17:40:58 -0400 Subject: [PATCH 07/12] remove unused css --- styles/_docsearch.css | 6 ------ 1 file changed, 6 deletions(-) diff --git a/styles/_docsearch.css b/styles/_docsearch.css index 176a5e739..42c5b36b6 100644 --- a/styles/_docsearch.css +++ b/styles/_docsearch.css @@ -279,12 +279,6 @@ svg.DocSearch-Hit-Select-Icon { .DocSearch-Hit-path { @apply text-14 text-gray-60; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; - white-space: normal !important; - text-overflow: ellipsis; } .DocSearch-Hit-path code { From fe972563e41e1cd8d153715632b7279d55b6c7f9 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 6 Apr 2026 17:49:43 -0400 Subject: [PATCH 08/12] pr feedback --- src/bindings/DocSearch.res | 18 ---------------- src/common/SearchIndex.res | 43 ------------------------------------- src/common/SearchIndex.resi | 22 +++++++++++++++++++ src/common/Url.res | 2 +- 4 files changed, 23 insertions(+), 62 deletions(-) create mode 100644 src/common/SearchIndex.resi diff --git a/src/bindings/DocSearch.res b/src/bindings/DocSearch.res index 9efbf91b7..a97c2540f 100644 --- a/src/bindings/DocSearch.res +++ b/src/bindings/DocSearch.res @@ -63,21 +63,3 @@ external make: ( ~searchParameters: searchParameters=?, ~initialScrollY: int=?, ) => React.element = "DocSearchModal" - -let getContentSnippet: docSearchHit => option = %raw(` - function(hit) { - try { - var s = hit._snippetResult; - if (s && s.content && s.content.value) { - var val = s.content.value.trim(); - if (val !== '' && val !== '...') return val; - } - } catch(e) {} - var c = hit.content; - if (c != null) { - c = c.trim(); - if (c !== '') return c; - } - return undefined; - } -`) diff --git a/src/common/SearchIndex.res b/src/common/SearchIndex.res index 23b48c1e7..79d4260fa 100644 --- a/src/common/SearchIndex.res +++ b/src/common/SearchIndex.res @@ -94,49 +94,6 @@ let cleanDocstring = (text: string): string => ->String.replaceRegExp(RegExp.fromString("\\n", ~flags="g"), " ") ->String.trim -let stripMarkdownFull = (text: string): string => - text - // Strip code blocks but keep code content - ->String.replaceRegExp(RegExp.fromString("```\\w*\\n?([\\s\\S]*?)```", ~flags="g"), "$1") - // Strip inline code backticks - ->String.replaceRegExp(RegExp.fromString("`([^`]+)`", ~flags="g"), "$1") - // Strip bold - ->String.replaceRegExp(RegExp.fromString("\\*\\*([^*]+)\\*\\*", ~flags="g"), "$1") - // Strip italic - ->String.replaceRegExp(RegExp.fromString("\\*([^*]+)\\*", ~flags="g"), "$1") - // Strip links (keep text, handle empty URLs too) - ->String.replaceRegExp(RegExp.fromString("\\[([^\\]]+)\\]\\([^)]*\\)", ~flags="g"), "$1") - // Strip heading markers - ->String.replaceRegExp(RegExp.fromString("^#{1,6}\\s+", ~flags="gm"), "") - // Collapse multiple newlines - ->String.replaceRegExp(RegExp.fromString("\\n{2,}", ~flags="g"), " ") - // Replace remaining newlines with space - ->String.replaceRegExp(RegExp.fromString("\\n", ~flags="g"), " ") - ->String.trim - -let docstringToSearchHtml = (text: string): string => - text - // Strip code blocks but keep code content wrapped in - ->String.replaceRegExp( - RegExp.fromString("```\\w*\\n?([\\s\\S]*?)```", ~flags="g"), - "$1", - ) - // Convert inline code backticks to tags - ->String.replaceRegExp(RegExp.fromString("`([^`]+)`", ~flags="g"), "$1") - // Strip bold - ->String.replaceRegExp(RegExp.fromString("\\*\\*([^*]+)\\*\\*", ~flags="g"), "$1") - // Strip italic - ->String.replaceRegExp(RegExp.fromString("\\*([^*]+)\\*", ~flags="g"), "$1") - // Strip links (keep text, handle empty URLs too) - ->String.replaceRegExp(RegExp.fromString("\\[([^\\]]+)\\]\\([^)]*\\)", ~flags="g"), "$1") - // Strip heading markers - ->String.replaceRegExp(RegExp.fromString("^#{1,6}\\s+", ~flags="gm"), "") - // Convert double newlines to
- ->String.replaceRegExp(RegExp.fromString("\\n{2,}", ~flags="g"), "
") - // Replace remaining newlines with space - ->String.replaceRegExp(RegExp.fromString("\\n", ~flags="g"), " ") - ->String.trim - let extractIntro = (content: string): string => { let parts = content->String.split("\n## ") let intro = parts[0]->Option.getOr("") diff --git a/src/common/SearchIndex.resi b/src/common/SearchIndex.resi new file mode 100644 index 000000000..797af9e72 --- /dev/null +++ b/src/common/SearchIndex.resi @@ -0,0 +1,22 @@ +type record + +let buildMarkdownRecords: ( + ~category: string, + ~basePath: string, + ~dirPath: string, + ~pageRank: int, +) => array + +let buildBlogRecords: (~dirPath: string, ~pageRank: int) => array + +let buildSyntaxLookupRecords: (~dirPath: string, ~pageRank: int) => array + +let buildApiRecords: ( + ~basePath: string, + ~dirPath: string, + ~pageRank: int, + ~category: string, + ~files: array=?, +) => array + +let toJson: record => JSON.t diff --git a/src/common/Url.res b/src/common/Url.res index fb31e28cd..0c7538b6d 100644 --- a/src/common/Url.res +++ b/src/common/Url.res @@ -58,7 +58,7 @@ let prettyString = (str: string) => { let parse = (route: string): t => { let fullpath = route->String.split("/")->Array.filter(s => s !== "") let foundVersionIndex = Array.findIndex(fullpath, chunk => { - RegExp.test(/latest|next|v\d+(\.\d+)?(\.\d+)?/, chunk) + RegExp.test(/latest|next|v?\d+(\.\d+)?(\.\d+)?/, chunk) }) let (version, base, pagepath) = if foundVersionIndex == -1 { From e033cccf14183bc794bde211f5f870f04a85d3b4 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 6 Apr 2026 18:28:04 -0400 Subject: [PATCH 09/12] add back icons --- src/components/Icon.res | 81 +++++++++++++++++++++++++++++++++++++++ src/components/Icon.resi | 20 ++++++++++ src/components/Search.res | 10 +++++ 3 files changed, 111 insertions(+) diff --git a/src/components/Icon.res b/src/components/Icon.res index daac2bdf4..7830a805d 100644 --- a/src/components/Icon.res +++ b/src/components/Icon.res @@ -291,3 +291,84 @@ module Clipboard = { } + +module DocPage = { + @react.component + let make = () => +
+ + + + + + + +
+} + +module DocHash = { + @react.component + let make = () => +
+ + + + + + +
+} + +module DocTree = { + @react.component + let make = () => + + + + + +} + +module DocSelect = { + @react.component + let make = () => +
+ + + + + + +
+} diff --git a/src/components/Icon.resi b/src/components/Icon.resi index 4087c13b6..df1f0e24b 100644 --- a/src/components/Icon.resi +++ b/src/components/Icon.resi @@ -82,3 +82,23 @@ module Clipboard: { @react.component let make: (~className: string=?) => React.element } + +module DocPage: { + @react.component + let make: unit => React.element +} + +module DocHash: { + @react.component + let make: unit => React.element +} + +module DocTree: { + @react.component + let make: unit => React.element +} + +module DocSelect: { + @react.component + let make: unit => React.element +} diff --git a/src/components/Search.res b/src/components/Search.res index 1709e3d4a..5f744f7de 100644 --- a/src/components/Search.res +++ b/src/components/Search.res @@ -58,13 +58,22 @@ let markdownToHtml = (text: string): string => ->String.replaceRegExp(RegExp.fromString("\\n", ~flags="g"), " ") ->String.trim +let isChildHit = (hit: DocSearch.docSearchHit) => + switch hit.type_ { + | Lvl2 | Lvl3 | Lvl4 | Lvl5 | Lvl6 | Content => true + | Lvl0 | Lvl1 => hit.url->String.includes("#") + } + let hitComponent = ({hit, children: _}: DocSearch.hitComponent): React.element => { let titleHtml = getHighlightedTitle(hit) let subtitle = getSubtitle(hit) let contentHtml = hit.content->Nullable.toOption->Option.map(markdownToHtml) + let isChild = isChildHit(hit)
+ {isChild ? : React.null} + {isChild ? : }
{switch subtitle { @@ -77,6 +86,7 @@ let hitComponent = ({hit, children: _}: DocSearch.hitComponent): React.element = | _ => React.null }}
+
} From 05bcd747e7ad7658e009973ae21585215f43e03b Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 6 Apr 2026 18:40:08 -0400 Subject: [PATCH 10/12] add unit tests --- __tests__/SearchIndex_.test.res | 541 ++++++++++++++++++ __tests__/Search_.test.res | 426 ++++++++++++++ __tests__/Url_.test.res | 75 +++ ...s-multiple-spaces-into-single-hyphen-1.png | Bin 0 -> 3973 bytes ...ode-stays-as-is--code-matched-first--1.png | Bin 0 -> 3973 bytes ...ripping-handles-link-with-empty-text-1.png | Bin 0 -> 3973 bytes ...ersion-detection-parses-next-keyword-1.png | Bin 0 -> 3973 bytes ...-version-without-v-prefix--PR--1231--1.png | Bin 0 -> 3973 bytes src/bindings/Vitest.res | 6 + src/common/SearchIndex.resi | 64 ++- 10 files changed, 1111 insertions(+), 1 deletion(-) create mode 100644 __tests__/SearchIndex_.test.res create mode 100644 __tests__/Search_.test.res create mode 100644 __tests__/Url_.test.res create mode 100644 __tests__/__screenshots__/SearchIndex_.test.jsx/slugify-collapses-multiple-spaces-into-single-hyphen-1.png create mode 100644 __tests__/__screenshots__/Search_.test.jsx/markdownToHtml-combined-transformations-bold-inside-code-stays-as-is--code-matched-first--1.png create mode 100644 __tests__/__screenshots__/Search_.test.jsx/markdownToHtml-markdown-link-stripping-handles-link-with-empty-text-1.png create mode 100644 __tests__/__screenshots__/Url_.test.jsx/Url-parse-version-detection-parses-next-keyword-1.png create mode 100644 __tests__/__screenshots__/Url_.test.jsx/Url-parse-version-detection-parses-version-without-v-prefix--PR--1231--1.png diff --git a/__tests__/SearchIndex_.test.res b/__tests__/SearchIndex_.test.res new file mode 100644 index 000000000..cb394f118 --- /dev/null +++ b/__tests__/SearchIndex_.test.res @@ -0,0 +1,541 @@ +open Vitest + +// --------------------------------------------------------------------------- +// maxContentLength +// --------------------------------------------------------------------------- + +describe("maxContentLength", () => { + test("is 500", async () => { + expect(SearchIndex.maxContentLength)->toBe(500) + }) +}) + +// --------------------------------------------------------------------------- +// truncate +// --------------------------------------------------------------------------- + +describe("truncate", () => { + test("returns string as-is when shorter than maxLen", async () => { + expect(SearchIndex.truncate("hello", ~maxLen=10))->toBe("hello") + }) + + test("returns string as-is when exactly maxLen", async () => { + expect(SearchIndex.truncate("hello", ~maxLen=5))->toBe("hello") + }) + + test("truncates and adds ellipsis when longer than maxLen", async () => { + expect(SearchIndex.truncate("hello world", ~maxLen=5))->toBe("hello...") + }) + + test("handles empty string", async () => { + expect(SearchIndex.truncate("", ~maxLen=5))->toBe("") + }) + + test("truncates to maxLen=0 with ellipsis", async () => { + expect(SearchIndex.truncate("abc", ~maxLen=0))->toBe("...") + }) + + test("truncates to single character with ellipsis", async () => { + expect(SearchIndex.truncate("abcdef", ~maxLen=1))->toBe("a...") + }) +}) + +// --------------------------------------------------------------------------- +// slugify +// --------------------------------------------------------------------------- + +describe("slugify", () => { + test("lowercases text", async () => { + expect(SearchIndex.slugify("Hello World"))->toBe("hello-world") + }) + + test("replaces spaces with hyphens", async () => { + expect(SearchIndex.slugify("foo bar baz"))->toBe("foo-bar-baz") + }) + + test("removes non-alphanumeric characters", async () => { + expect(SearchIndex.slugify("Hello, World!"))->toBe("hello-world") + }) + + test("collapses multiple spaces into single hyphen", async () => { + expect(SearchIndex.slugify("foo bar"))->toBe("foo-bar") + }) + + test("handles empty string", async () => { + expect(SearchIndex.slugify(""))->toBe("") + }) + + test("preserves numbers", async () => { + expect(SearchIndex.slugify("Section 42"))->toBe("section-42") + }) + + test("removes special characters like parentheses and dots", async () => { + expect(SearchIndex.slugify("Array.map()"))->toBe("arraymap") + }) + + test("handles already-slugified text", async () => { + expect(SearchIndex.slugify("already-slugified"))->toBe("already-slugified") + }) +}) + +// --------------------------------------------------------------------------- +// stripMdxTags +// --------------------------------------------------------------------------- + +describe("stripMdxTags", () => { + test("removes CodeTab blocks", async () => { + let input = "before\n\nsome code\n\nafter" + expect(SearchIndex.stripMdxTags(input))->toBe("before\nafter") + }) + + test("removes HTML tags", async () => { + expect(SearchIndex.stripMdxTags("
hello
"))->toBe("hello") + }) + + test("removes fenced code blocks", async () => { + let input = "before\n```rescript\nlet x = 1\n```\nafter" + expect(SearchIndex.stripMdxTags(input))->toBe("before\nafter") + }) + + test("strips inline code backticks", async () => { + expect(SearchIndex.stripMdxTags("use `Array.map` here"))->toBe("use Array.map here") + }) + + test("strips bold markers", async () => { + expect(SearchIndex.stripMdxTags("this is **bold** text"))->toBe("this is bold text") + }) + + test("strips italic markers", async () => { + expect(SearchIndex.stripMdxTags("this is *italic* text"))->toBe("this is italic text") + }) + + test("strips markdown links keeping link text", async () => { + expect(SearchIndex.stripMdxTags("click [here](https://example.com) now"))->toBe( + "click here now", + ) + }) + + test("removes heading markers", async () => { + expect(SearchIndex.stripMdxTags("## My Heading"))->toBe("My Heading") + }) + + test("removes h1 through h6 markers", async () => { + let input = "# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6" + expect(SearchIndex.stripMdxTags(input))->toBe("H1\nH2\nH3\nH4\nH5\nH6") + }) + + test("collapses multiple newlines to single", async () => { + expect(SearchIndex.stripMdxTags("a\n\n\nb"))->toBe("a\nb") + }) + + test("handles empty string", async () => { + expect(SearchIndex.stripMdxTags(""))->toBe("") + }) + + test("handles combined markdown formatting", async () => { + let input = "Use **`Array.map`** to [transform](http://x.com) items." + let result = SearchIndex.stripMdxTags(input) + expect(result)->toBe("Use Array.map to transform items.") + }) +}) + +// --------------------------------------------------------------------------- +// cleanDocstring +// --------------------------------------------------------------------------- + +describe("cleanDocstring", () => { + test("returns simple text as-is", async () => { + expect(SearchIndex.cleanDocstring("Simple description"))->toBe("Simple description") + }) + + test("takes content before first ## heading", async () => { + let input = "Intro text\n## Details\nMore info" + expect(SearchIndex.cleanDocstring(input))->toBe("Intro text") + }) + + test("takes content before first code block", async () => { + let input = "Intro text\n```rescript\nlet x = 1\n```" + expect(SearchIndex.cleanDocstring(input))->toBe("Intro text") + }) + + test("strips inline code backticks", async () => { + expect(SearchIndex.cleanDocstring("Returns `true` or `false`"))->toBe("Returns true or false") + }) + + test("strips bold formatting", async () => { + expect(SearchIndex.cleanDocstring("This is **important**"))->toBe("This is important") + }) + + test("strips italic formatting", async () => { + expect(SearchIndex.cleanDocstring("This is *emphasized*"))->toBe("This is emphasized") + }) + + test("strips markdown links", async () => { + expect(SearchIndex.cleanDocstring("See [docs](http://example.com)"))->toBe("See docs") + }) + + test("collapses multiple newlines to spaces", async () => { + let input = "line one\n\nline two\n\nline three" + expect(SearchIndex.cleanDocstring(input))->toBe("line one line two line three") + }) + + test("replaces single newlines with spaces", async () => { + let input = "line one\nline two" + expect(SearchIndex.cleanDocstring(input))->toBe("line one line two") + }) + + test("handles empty string", async () => { + expect(SearchIndex.cleanDocstring(""))->toBe("") + }) + + test("heading takes priority over code block", async () => { + let input = "Intro\n## Section\nText\n```\ncode\n```" + expect(SearchIndex.cleanDocstring(input))->toBe("Intro") + }) +}) + +// --------------------------------------------------------------------------- +// extractIntro +// --------------------------------------------------------------------------- + +describe("extractIntro", () => { + test("extracts text before first ## heading", async () => { + let input = "Some intro text.\n## First Section\nDetails here." + let result = SearchIndex.extractIntro(input) + expect(result)->toBe("Some intro text.") + }) + + test("removes H1 heading at start", async () => { + let input = "# Page Title\nIntro paragraph.\n## Section" + let result = SearchIndex.extractIntro(input) + expect(result)->toBe("Intro paragraph.") + }) + + test("returns stripped content when no headings", async () => { + let input = "Just some plain text content." + expect(SearchIndex.extractIntro(input))->toBe("Just some plain text content.") + }) + + test("handles empty string", async () => { + expect(SearchIndex.extractIntro(""))->toBe("") + }) + + test("strips MDX tags from intro", async () => { + let input = "Use **bold** and `code`.\n## Section" + expect(SearchIndex.extractIntro(input))->toBe("Use bold and code.") + }) + + test("removes H1 but preserves rest of content", async () => { + let input = "# Title\nFirst paragraph.\nSecond paragraph." + expect(SearchIndex.extractIntro(input))->toBe("First paragraph.\nSecond paragraph.") + }) +}) + +// --------------------------------------------------------------------------- +// extractHeadings +// --------------------------------------------------------------------------- + +describe("extractHeadings", () => { + test("extracts h2 headings", async () => { + let input = "Intro\n## First\nContent one.\n## Second\nContent two." + let headings = SearchIndex.extractHeadings(input) + expect(Array.length(headings))->toBe(2) + expect(headings[0]->Option.map(h => h.level))->toEqual(Some(2)) + expect(headings[0]->Option.map(h => h.text))->toEqual(Some("First")) + expect(headings[1]->Option.map(h => h.text))->toEqual(Some("Second")) + }) + + test("extracts h3 headings", async () => { + let input = "## Parent\n### Child\nSub content." + let headings = SearchIndex.extractHeadings(input) + expect(headings[0]->Option.map(h => h.level))->toEqual(Some(2)) + expect(headings[1]->Option.map(h => h.level))->toEqual(Some(3)) + expect(headings[1]->Option.map(h => h.text))->toEqual(Some("Child")) + }) + + test("does not extract h1 headings", async () => { + let input = "# Title\nSome text\n## Real Heading\nContent." + let headings = SearchIndex.extractHeadings(input) + expect(Array.length(headings))->toBe(1) + expect(headings[0]->Option.map(h => h.text))->toEqual(Some("Real Heading")) + }) + + test("returns empty array when no headings", async () => { + let input = "Just plain text with no headings." + let headings = SearchIndex.extractHeadings(input) + expect(Array.length(headings))->toBe(0) + }) + + test("includes section content between headings", async () => { + let input = "## Heading\nThis is the content of the section." + let headings = SearchIndex.extractHeadings(input) + expect(headings[0]->Option.map(h => h.content))->toEqual( + Some("This is the content of the section."), + ) + }) + + test("strips MDX tags from section content", async () => { + let input = "## Heading\nUse **bold** and `code` here." + let headings = SearchIndex.extractHeadings(input) + expect(headings[0]->Option.map(h => h.content))->toEqual(Some("Use bold and code here.")) + }) + + test("truncates section content to maxContentLength", async () => { + let longContent = String.repeat("a", 600) + let input = "## Heading\n" ++ longContent + let headings = SearchIndex.extractHeadings(input) + let contentLen = headings[0]->Option.map(h => String.length(h.content))->Option.getOr(0) + // 500 chars + "..." = 503 + expect(contentLen)->toBe(503) + }) + + test("handles multiple heading levels", async () => { + let input = "## H2\nA\n### H3\nB\n#### H4\nC\n##### H5\nD\n###### H6\nE" + let headings = SearchIndex.extractHeadings(input) + expect(Array.length(headings))->toBe(5) + expect(headings[0]->Option.map(h => h.level))->toEqual(Some(2)) + expect(headings[1]->Option.map(h => h.level))->toEqual(Some(3)) + expect(headings[2]->Option.map(h => h.level))->toEqual(Some(4)) + expect(headings[3]->Option.map(h => h.level))->toEqual(Some(5)) + expect(headings[4]->Option.map(h => h.level))->toEqual(Some(6)) + }) +}) + +// --------------------------------------------------------------------------- +// makeHierarchy +// --------------------------------------------------------------------------- + +describe("makeHierarchy", () => { + test("creates hierarchy with only required fields", async () => { + let h = SearchIndex.makeHierarchy(~lvl0="Docs", ~lvl1="Overview", ()) + expect(h.lvl0)->toBe("Docs") + expect(h.lvl1)->toBe("Overview") + expect(h.lvl2)->toEqual(None) + expect(h.lvl3)->toEqual(None) + expect(h.lvl4)->toEqual(None) + expect(h.lvl5)->toEqual(None) + expect(h.lvl6)->toEqual(None) + }) + + test("creates hierarchy with all optional fields", async () => { + let h = SearchIndex.makeHierarchy( + ~lvl0="Docs", + ~lvl1="Guide", + ~lvl2="Chapter", + ~lvl3="Section", + ~lvl4="Sub A", + ~lvl5="Sub B", + ~lvl6="Sub C", + (), + ) + expect(h.lvl0)->toBe("Docs") + expect(h.lvl1)->toBe("Guide") + expect(h.lvl2)->toEqual(Some("Chapter")) + expect(h.lvl3)->toEqual(Some("Section")) + expect(h.lvl4)->toEqual(Some("Sub A")) + expect(h.lvl5)->toEqual(Some("Sub B")) + expect(h.lvl6)->toEqual(Some("Sub C")) + }) + + test("creates hierarchy with partial optional fields", async () => { + let h = SearchIndex.makeHierarchy(~lvl0="API", ~lvl1="Array", ~lvl2="map", ()) + expect(h.lvl2)->toEqual(Some("map")) + expect(h.lvl3)->toEqual(None) + }) +}) + +// --------------------------------------------------------------------------- +// optionToJson +// --------------------------------------------------------------------------- + +describe("optionToJson", () => { + test("converts Some to JSON string", async () => { + expect(SearchIndex.optionToJson(Some("hello")))->toEqual(JSON.String("hello")) + }) + + test("converts None to JSON null", async () => { + expect(SearchIndex.optionToJson(None))->toEqual(JSON.Null) + }) + + test("converts Some empty string to JSON string", async () => { + expect(SearchIndex.optionToJson(Some("")))->toEqual(JSON.String("")) + }) +}) + +// --------------------------------------------------------------------------- +// hierarchyToJson +// --------------------------------------------------------------------------- + +describe("hierarchyToJson", () => { + test("serializes hierarchy with only required fields", async () => { + let h = SearchIndex.makeHierarchy(~lvl0="Docs", ~lvl1="Page", ()) + let json = SearchIndex.hierarchyToJson(h) + let expected = { + let d = Dict.make() + d->Dict.set("lvl0", JSON.String("Docs")) + d->Dict.set("lvl1", JSON.String("Page")) + d->Dict.set("lvl2", JSON.Null) + d->Dict.set("lvl3", JSON.Null) + d->Dict.set("lvl4", JSON.Null) + d->Dict.set("lvl5", JSON.Null) + d->Dict.set("lvl6", JSON.Null) + JSON.Object(d) + } + expect(json)->toEqual(expected) + }) + + test("serializes hierarchy with optional fields as JSON strings", async () => { + let h = SearchIndex.makeHierarchy(~lvl0="API", ~lvl1="Array", ~lvl2="map", ()) + let json = SearchIndex.hierarchyToJson(h) + let expected = { + let d = Dict.make() + d->Dict.set("lvl0", JSON.String("API")) + d->Dict.set("lvl1", JSON.String("Array")) + d->Dict.set("lvl2", JSON.String("map")) + d->Dict.set("lvl3", JSON.Null) + d->Dict.set("lvl4", JSON.Null) + d->Dict.set("lvl5", JSON.Null) + d->Dict.set("lvl6", JSON.Null) + JSON.Object(d) + } + expect(json)->toEqual(expected) + }) +}) + +// --------------------------------------------------------------------------- +// weightToJson +// --------------------------------------------------------------------------- + +describe("weightToJson", () => { + test("serializes weight to JSON object with number values", async () => { + let w: SearchIndex.weight = {pageRank: 10, level: 80, position: 3} + let json = SearchIndex.weightToJson(w) + let expected = { + let d = Dict.make() + d->Dict.set("pageRank", JSON.Number(10.0)) + d->Dict.set("level", JSON.Number(80.0)) + d->Dict.set("position", JSON.Number(3.0)) + JSON.Object(d) + } + expect(json)->toEqual(expected) + }) + + test("serializes zero values correctly", async () => { + let w: SearchIndex.weight = {pageRank: 0, level: 0, position: 0} + let json = SearchIndex.weightToJson(w) + let expected = { + let d = Dict.make() + d->Dict.set("pageRank", JSON.Number(0.0)) + d->Dict.set("level", JSON.Number(0.0)) + d->Dict.set("position", JSON.Number(0.0)) + JSON.Object(d) + } + expect(json)->toEqual(expected) + }) +}) + +// --------------------------------------------------------------------------- +// toJson +// --------------------------------------------------------------------------- + +describe("toJson", () => { + test("serializes a full record with all fields", async () => { + let r: SearchIndex.record = { + objectID: "docs/overview", + url: "/docs/overview#intro", + url_without_anchor: "/docs/overview", + anchor: Some("intro"), + content: Some("Introduction text"), + type_: "lvl2", + hierarchy: SearchIndex.makeHierarchy(~lvl0="Docs", ~lvl1="Overview", ~lvl2="Intro", ()), + weight: {pageRank: 5, level: 80, position: 1}, + } + let json = SearchIndex.toJson(r) + + let expected = { + let d = Dict.make() + d->Dict.set("objectID", JSON.String("docs/overview")) + d->Dict.set("url", JSON.String("/docs/overview#intro")) + d->Dict.set("url_without_anchor", JSON.String("/docs/overview")) + d->Dict.set("anchor", JSON.String("intro")) + d->Dict.set("content", JSON.String("Introduction text")) + d->Dict.set("type", JSON.String("lvl2")) + d->Dict.set( + "hierarchy", + { + let hd = Dict.make() + hd->Dict.set("lvl0", JSON.String("Docs")) + hd->Dict.set("lvl1", JSON.String("Overview")) + hd->Dict.set("lvl2", JSON.String("Intro")) + hd->Dict.set("lvl3", JSON.Null) + hd->Dict.set("lvl4", JSON.Null) + hd->Dict.set("lvl5", JSON.Null) + hd->Dict.set("lvl6", JSON.Null) + JSON.Object(hd) + }, + ) + d->Dict.set( + "weight", + { + let wd = Dict.make() + wd->Dict.set("pageRank", JSON.Number(5.0)) + wd->Dict.set("level", JSON.Number(80.0)) + wd->Dict.set("position", JSON.Number(1.0)) + JSON.Object(wd) + }, + ) + JSON.Object(d) + } + expect(json)->toEqual(expected) + }) + + test("serializes a record with None optional fields as null", async () => { + let r: SearchIndex.record = { + objectID: "page", + url: "/page", + url_without_anchor: "/page", + anchor: None, + content: None, + type_: "lvl1", + hierarchy: SearchIndex.makeHierarchy(~lvl0="Cat", ~lvl1="Page", ()), + weight: {pageRank: 1, level: 100, position: 0}, + } + let json = SearchIndex.toJson(r) + + let expected = { + let d = Dict.make() + d->Dict.set("objectID", JSON.String("page")) + d->Dict.set("url", JSON.String("/page")) + d->Dict.set("url_without_anchor", JSON.String("/page")) + d->Dict.set("anchor", JSON.Null) + d->Dict.set("content", JSON.Null) + d->Dict.set("type", JSON.String("lvl1")) + d->Dict.set( + "hierarchy", + { + let hd = Dict.make() + hd->Dict.set("lvl0", JSON.String("Cat")) + hd->Dict.set("lvl1", JSON.String("Page")) + hd->Dict.set("lvl2", JSON.Null) + hd->Dict.set("lvl3", JSON.Null) + hd->Dict.set("lvl4", JSON.Null) + hd->Dict.set("lvl5", JSON.Null) + hd->Dict.set("lvl6", JSON.Null) + JSON.Object(hd) + }, + ) + d->Dict.set( + "weight", + { + let wd = Dict.make() + wd->Dict.set("pageRank", JSON.Number(1.0)) + wd->Dict.set("level", JSON.Number(100.0)) + wd->Dict.set("position", JSON.Number(0.0)) + JSON.Object(wd) + }, + ) + JSON.Object(d) + } + expect(json)->toEqual(expected) + }) +}) diff --git a/__tests__/Search_.test.res b/__tests__/Search_.test.res new file mode 100644 index 000000000..12fcb1ce6 --- /dev/null +++ b/__tests__/Search_.test.res @@ -0,0 +1,426 @@ +open Vitest + +// --------------------------------------------------------------------------- +// Helper +// --------------------------------------------------------------------------- + +let makeHit = (~type_: DocSearch.contentType, ~url: string): DocSearch.docSearchHit => { + objectID: "test", + content: Nullable.null, + url, + url_without_anchor: url, + type_, + anchor: Nullable.null, + hierarchy: { + lvl0: Nullable.make("Test"), + lvl1: Nullable.make("Test Page"), + lvl2: Nullable.null, + lvl3: Nullable.null, + lvl4: Nullable.null, + lvl5: Nullable.null, + lvl6: Nullable.null, + }, + deprecated: None, + _highlightResult: Obj.magic(Dict.make()), + _snippetResult: Obj.magic(Dict.make()), +} + +// --------------------------------------------------------------------------- +// markdownToHtml +// --------------------------------------------------------------------------- + +describe("markdownToHtml", () => { + // --- backslash stripping --- + + describe("backslash stripping", () => { + test( + "strips leading backslash + whitespace", + async () => { + expect(Search.markdownToHtml("\\ hello"))->toBe("hello") + }, + ) + + test( + "replaces interior backslash + whitespace with a space", + async () => { + expect(Search.markdownToHtml("foo\\ bar"))->toBe("foo bar") + }, + ) + + test( + "handles multiple interior backslashes", + async () => { + expect(Search.markdownToHtml("a\\ b\\ c"))->toBe("a b c") + }, + ) + + test( + "strips leading and replaces interior backslashes together", + async () => { + expect(Search.markdownToHtml("\\ a\\ b"))->toBe("a b") + }, + ) + }) + + // --- MDN reference link removal --- + + describe("MDN reference removal", () => { + test( + "removes MDN reference with markdown link and trailing period", + async () => { + expect( + Search.markdownToHtml( + "Some text. See [Array](https://developer.mozilla.org/array) on MDN.", + ), + )->toBe("Some text.") + }, + ) + + test( + "removes MDN reference with markdown link without trailing period", + async () => { + expect( + Search.markdownToHtml( + "Some text. See [Array](https://developer.mozilla.org/array) on MDN", + ), + )->toBe("Some text.") + }, + ) + + test( + "removes MDN plain URL reference with trailing period", + async () => { + expect( + Search.markdownToHtml("Read more. See https://developer.mozilla.org/foo on MDN."), + )->toBe("Read more.") + }, + ) + + test( + "removes MDN plain URL reference without trailing period", + async () => { + expect( + Search.markdownToHtml("Read more. See https://developer.mozilla.org/foo on MDN"), + )->toBe("Read more.") + }, + ) + }) + + // --- markdown link stripping --- + + describe("markdown link stripping", () => { + test( + "converts markdown link to plain text", + async () => { + expect(Search.markdownToHtml("[click here](https://example.com)"))->toBe("click here") + }, + ) + + test( + "converts multiple markdown links", + async () => { + expect(Search.markdownToHtml("[foo](http://a.com) and [bar](http://b.com)"))->toBe( + "foo and bar", + ) + }, + ) + + test( + "passes through link with empty text (regex requires non-empty text)", + async () => { + expect(Search.markdownToHtml("[](https://example.com)"))->toBe("[](https://example.com)") + }, + ) + }) + + // --- inline code --- + + describe("backtick code", () => { + test( + "converts backtick code to tags", + async () => { + expect(Search.markdownToHtml("`Array.map`"))->toBe("Array.map") + }, + ) + + test( + "converts multiple backtick spans", + async () => { + expect(Search.markdownToHtml("Use `map` and `filter`"))->toBe( + "Use map and filter", + ) + }, + ) + }) + + // --- bold --- + + describe("bold", () => { + test( + "converts **text** to tags", + async () => { + expect(Search.markdownToHtml("**important**"))->toBe("important") + }, + ) + + test( + "converts bold within a sentence", + async () => { + expect(Search.markdownToHtml("This is **very** important"))->toBe( + "This is very important", + ) + }, + ) + }) + + // --- italic --- + + describe("italic", () => { + test( + "converts *text* to tags", + async () => { + expect(Search.markdownToHtml("*emphasis*"))->toBe("emphasis") + }, + ) + + test( + "converts italic within a sentence", + async () => { + expect(Search.markdownToHtml("This is *quite* nice"))->toBe("This is quite nice") + }, + ) + }) + + // --- newlines --- + + describe("newlines", () => { + test( + "converts double newline to
", + async () => { + expect(Search.markdownToHtml("first\n\nsecond"))->toBe("first
second") + }, + ) + + test( + "converts triple+ newlines to single
", + async () => { + expect(Search.markdownToHtml("first\n\n\nsecond"))->toBe("first
second") + }, + ) + + test( + "converts single newline to space", + async () => { + expect(Search.markdownToHtml("first\nsecond"))->toBe("first second") + }, + ) + }) + + // --- trimming --- + + describe("trimming", () => { + test( + "trims leading whitespace", + async () => { + expect(Search.markdownToHtml(" hello"))->toBe("hello") + }, + ) + + test( + "trims trailing whitespace", + async () => { + expect(Search.markdownToHtml("hello "))->toBe("hello") + }, + ) + + test( + "trims both sides", + async () => { + expect(Search.markdownToHtml(" hello "))->toBe("hello") + }, + ) + }) + + // --- combined / edge cases --- + + describe("combined transformations", () => { + test( + "handles empty string", + async () => { + expect(Search.markdownToHtml(""))->toBe("") + }, + ) + + test( + "plain text passes through unchanged", + async () => { + expect(Search.markdownToHtml("just plain text"))->toBe("just plain text") + }, + ) + + test( + "applies multiple transformations together", + async () => { + expect( + Search.markdownToHtml( + "Use `map` on **arrays**.\n\nSee [docs](http://x.com) for *details*.", + ), + )->toBe( + "Use map on arrays.
See docs for details.", + ) + }, + ) + + test( + "bold inside code still gets converted (sequential regex application)", + async () => { + expect(Search.markdownToHtml("`**notbold**`"))->toBe( + "notbold", + ) + }, + ) + }) +}) + +// --------------------------------------------------------------------------- +// isChildHit +// --------------------------------------------------------------------------- + +describe("isChildHit", () => { + // --- child-level types (always true) --- + + describe("child-level types", () => { + test( + "Lvl2 is a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl2, ~url="https://example.com/page")))->toBe(true) + }, + ) + + test( + "Lvl3 is a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl3, ~url="https://example.com/page")))->toBe(true) + }, + ) + + test( + "Lvl4 is a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl4, ~url="https://example.com/page")))->toBe(true) + }, + ) + + test( + "Lvl5 is a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl5, ~url="https://example.com/page")))->toBe(true) + }, + ) + + test( + "Lvl6 is a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl6, ~url="https://example.com/page")))->toBe(true) + }, + ) + + test( + "Content is a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Content, ~url="https://example.com/page")))->toBe( + true, + ) + }, + ) + + test( + "Lvl2 is a child hit even without hash in URL", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl2, ~url="https://example.com/no-hash")))->toBe( + true, + ) + }, + ) + + test( + "Content is a child hit even with hash in URL", + async () => { + expect( + Search.isChildHit(makeHit(~type_=Content, ~url="https://example.com/page#section")), + )->toBe(true) + }, + ) + }) + + // --- Lvl0 --- + + describe("Lvl0", () => { + test( + "Lvl0 without hash is not a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl0, ~url="https://example.com/page")))->toBe( + false, + ) + }, + ) + + test( + "Lvl0 with hash is a child hit", + async () => { + expect( + Search.isChildHit(makeHit(~type_=Lvl0, ~url="https://example.com/page#section")), + )->toBe(true) + }, + ) + + test( + "Lvl0 with hash at end of URL is a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl0, ~url="https://example.com/page#")))->toBe( + true, + ) + }, + ) + }) + + // --- Lvl1 --- + + describe("Lvl1", () => { + test( + "Lvl1 without hash is not a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl1, ~url="https://example.com/page")))->toBe( + false, + ) + }, + ) + + test( + "Lvl1 with hash is a child hit", + async () => { + expect( + Search.isChildHit(makeHit(~type_=Lvl1, ~url="https://example.com/page#heading")), + )->toBe(true) + }, + ) + + test( + "Lvl1 with deeply nested hash anchor is a child hit", + async () => { + expect( + Search.isChildHit( + makeHit(~type_=Lvl1, ~url="https://example.com/docs/manual/api#some-section"), + ), + )->toBe(true) + }, + ) + + test( + "Lvl1 with empty URL is not a child hit", + async () => { + expect(Search.isChildHit(makeHit(~type_=Lvl1, ~url="")))->toBe(false) + }, + ) + }) +}) diff --git a/__tests__/Url_.test.res b/__tests__/Url_.test.res new file mode 100644 index 000000000..5cf00796f --- /dev/null +++ b/__tests__/Url_.test.res @@ -0,0 +1,75 @@ +open Vitest + +// --------------------------------------------------------------------------- +// Url.parse – version detection +// --------------------------------------------------------------------------- + +describe("Url.parse version detection", () => { + test("parses v-prefixed semver version", async () => { + let result = Url.parse("/docs/manual/v12.0.0/introduction") + expect(result.version)->toEqual(Url.Version("v12.0.0")) + expect(result.base)->toEqual(["docs", "manual"]) + expect(result.pagepath)->toEqual(["introduction"]) + expect(result.fullpath)->toEqual(["docs", "manual", "v12.0.0", "introduction"]) + }) + + test("parses version without v prefix matching latest (PR #1231)", async () => { + let result = Url.parse("/docs/manual/12.0.0/introduction") + // 12.0.0 matches Constants.versions.latest, so it becomes Latest + expect(result.version)->toEqual(Url.Latest) + expect(result.base)->toEqual(["docs", "manual"]) + expect(result.pagepath)->toEqual(["introduction"]) + expect(result.fullpath)->toEqual(["docs", "manual", "12.0.0", "introduction"]) + }) + + test("parses latest keyword", async () => { + let result = Url.parse("/docs/manual/latest/arrays") + expect(result.version)->toEqual(Url.Latest) + expect(result.base)->toEqual(["docs", "manual"]) + expect(result.pagepath)->toEqual(["arrays"]) + }) + + test("parses 'next' string in URL (does not match env-based Next version)", async () => { + // "next" is matched by the regex, but Constants.versions.next is "13.0.0", not "next" + let result = Url.parse("/docs/manual/next/arrays") + expect(result.version)->toEqual(Url.Version("next")) + expect(result.base)->toEqual(["docs", "manual"]) + expect(result.pagepath)->toEqual(["arrays"]) + }) + + test("parses actual next version from env as Next", async () => { + let nextVer = Constants.versions.next + let result = Url.parse("/docs/manual/" ++ nextVer ++ "/arrays") + expect(result.version)->toEqual(Url.Next) + expect(result.base)->toEqual(["docs", "manual"]) + expect(result.pagepath)->toEqual(["arrays"]) + }) + + test("parses route with no version as NoVersion", async () => { + let result = Url.parse("/community/overview") + expect(result.version)->toEqual(Url.NoVersion) + expect(result.base)->toEqual(["community", "overview"]) + expect(result.pagepath)->toEqual([]) + }) + + test("parses short v-prefixed version (major.minor)", async () => { + let result = Url.parse("/apis/javascript/v7.1/node") + expect(result.version)->toEqual(Url.Version("v7.1")) + expect(result.base)->toEqual(["apis", "javascript"]) + expect(result.pagepath)->toEqual(["node"]) + }) + + test("parses short version without v prefix (major.minor, PR #1231)", async () => { + let result = Url.parse("/apis/javascript/7.1/node") + expect(result.version)->toEqual(Url.Version("7.1")) + expect(result.base)->toEqual(["apis", "javascript"]) + expect(result.pagepath)->toEqual(["node"]) + }) + + test("parses major-only version without v prefix (PR #1231)", async () => { + let result = Url.parse("/docs/manual/12/getting-started") + expect(result.version)->toEqual(Url.Version("12")) + expect(result.base)->toEqual(["docs", "manual"]) + expect(result.pagepath)->toEqual(["getting-started"]) + }) +}) diff --git a/__tests__/__screenshots__/SearchIndex_.test.jsx/slugify-collapses-multiple-spaces-into-single-hyphen-1.png b/__tests__/__screenshots__/SearchIndex_.test.jsx/slugify-collapses-multiple-spaces-into-single-hyphen-1.png new file mode 100644 index 0000000000000000000000000000000000000000..a35891721ab1844b9f03a3d8781f386e165a52b4 GIT binary patch literal 3973 zcmeAS@N?(olHy`uVBq!ia0y~yU}<1rV7kD;1Qbc1GUosT1HYB0i(^Q|oHy4Uc^MRV z7&f*(xc@2O;NB?ly}PY}s{WL-F)%c=G%zwSxUldrFeoS`07V5EnHd;5I3ySt99)2g zFeotrRS5_h0F`!(Djf|0nuY){sY#8dx6u$74S~@R7!85Z5Eu=C(GVC7fzc2ch!9|F zXrpOcmtnNSHE2VCpMl~3f8WUhUBC`78>kn}%y2-7W5LsxcEBDA1B0ilpUXO@geCwk C-QUIl literal 0 HcmV?d00001 diff --git a/__tests__/__screenshots__/Search_.test.jsx/markdownToHtml-combined-transformations-bold-inside-code-stays-as-is--code-matched-first--1.png b/__tests__/__screenshots__/Search_.test.jsx/markdownToHtml-combined-transformations-bold-inside-code-stays-as-is--code-matched-first--1.png new file mode 100644 index 0000000000000000000000000000000000000000..a35891721ab1844b9f03a3d8781f386e165a52b4 GIT binary patch literal 3973 zcmeAS@N?(olHy`uVBq!ia0y~yU}<1rV7kD;1Qbc1GUosT1HYB0i(^Q|oHy4Uc^MRV z7&f*(xc@2O;NB?ly}PY}s{WL-F)%c=G%zwSxUldrFeoS`07V5EnHd;5I3ySt99)2g zFeotrRS5_h0F`!(Djf|0nuY){sY#8dx6u$74S~@R7!85Z5Eu=C(GVC7fzc2ch!9|F zXrpOcmtnNSHE2VCpMl~3f8WUhUBC`78>kn}%y2-7W5LsxcEBDA1B0ilpUXO@geCwk C-QUIl literal 0 HcmV?d00001 diff --git a/__tests__/__screenshots__/Search_.test.jsx/markdownToHtml-markdown-link-stripping-handles-link-with-empty-text-1.png b/__tests__/__screenshots__/Search_.test.jsx/markdownToHtml-markdown-link-stripping-handles-link-with-empty-text-1.png new file mode 100644 index 0000000000000000000000000000000000000000..a35891721ab1844b9f03a3d8781f386e165a52b4 GIT binary patch literal 3973 zcmeAS@N?(olHy`uVBq!ia0y~yU}<1rV7kD;1Qbc1GUosT1HYB0i(^Q|oHy4Uc^MRV z7&f*(xc@2O;NB?ly}PY}s{WL-F)%c=G%zwSxUldrFeoS`07V5EnHd;5I3ySt99)2g zFeotrRS5_h0F`!(Djf|0nuY){sY#8dx6u$74S~@R7!85Z5Eu=C(GVC7fzc2ch!9|F zXrpOcmtnNSHE2VCpMl~3f8WUhUBC`78>kn}%y2-7W5LsxcEBDA1B0ilpUXO@geCwk C-QUIl literal 0 HcmV?d00001 diff --git a/__tests__/__screenshots__/Url_.test.jsx/Url-parse-version-detection-parses-next-keyword-1.png b/__tests__/__screenshots__/Url_.test.jsx/Url-parse-version-detection-parses-next-keyword-1.png new file mode 100644 index 0000000000000000000000000000000000000000..a35891721ab1844b9f03a3d8781f386e165a52b4 GIT binary patch literal 3973 zcmeAS@N?(olHy`uVBq!ia0y~yU}<1rV7kD;1Qbc1GUosT1HYB0i(^Q|oHy4Uc^MRV z7&f*(xc@2O;NB?ly}PY}s{WL-F)%c=G%zwSxUldrFeoS`07V5EnHd;5I3ySt99)2g zFeotrRS5_h0F`!(Djf|0nuY){sY#8dx6u$74S~@R7!85Z5Eu=C(GVC7fzc2ch!9|F zXrpOcmtnNSHE2VCpMl~3f8WUhUBC`78>kn}%y2-7W5LsxcEBDA1B0ilpUXO@geCwk C-QUIl literal 0 HcmV?d00001 diff --git a/__tests__/__screenshots__/Url_.test.jsx/Url-parse-version-detection-parses-version-without-v-prefix--PR--1231--1.png b/__tests__/__screenshots__/Url_.test.jsx/Url-parse-version-detection-parses-version-without-v-prefix--PR--1231--1.png new file mode 100644 index 0000000000000000000000000000000000000000..a35891721ab1844b9f03a3d8781f386e165a52b4 GIT binary patch literal 3973 zcmeAS@N?(olHy`uVBq!ia0y~yU}<1rV7kD;1Qbc1GUosT1HYB0i(^Q|oHy4Uc^MRV z7&f*(xc@2O;NB?ly}PY}s{WL-F)%c=G%zwSxUldrFeoS`07V5EnHd;5I3ySt99)2g zFeotrRS5_h0F`!(Djf|0nuY){sY#8dx6u$74S~@R7!85Z5Eu=C(GVC7fzc2ch!9|F zXrpOcmtnNSHE2VCpMl~3f8WUhUBC`78>kn}%y2-7W5LsxcEBDA1B0ilpUXO@geCwk C-QUIl literal 0 HcmV?d00001 diff --git a/src/bindings/Vitest.res b/src/bindings/Vitest.res index b8f20fef8..c6b7cbe74 100644 --- a/src/bindings/Vitest.res +++ b/src/bindings/Vitest.res @@ -9,6 +9,9 @@ type mock @module("vitest") external test: (string, unit => promise) => unit = "test" +@module("vitest") +external describe: (string, unit => unit) => unit = "describe" + @module("vitest") @scope("vi") external fn: unit => 'a => 'b = "fn" @@ -65,6 +68,9 @@ external click: element => promise = "click" @send external toBe: (expect, 'a) => unit = "toBe" +@send +external toEqual: (expect, 'a) => unit = "toEqual" + @send external toHaveBeenCalled: expect => unit = "toHaveBeenCalled" diff --git a/src/common/SearchIndex.resi b/src/common/SearchIndex.resi index 797af9e72..435e81eb2 100644 --- a/src/common/SearchIndex.resi +++ b/src/common/SearchIndex.resi @@ -1,4 +1,66 @@ -type record +type hierarchy = { + lvl0: string, + lvl1: string, + lvl2: option, + lvl3: option, + lvl4: option, + lvl5: option, + lvl6: option, +} + +type weight = { + pageRank: int, + level: int, + position: int, +} + +type record = { + objectID: string, + url: string, + url_without_anchor: string, + anchor: option, + content: option, + @as("type") type_: string, + hierarchy: hierarchy, + weight: weight, +} + +type heading = { + level: int, + text: string, + content: string, +} + +let maxContentLength: int + +let makeHierarchy: ( + ~lvl0: string, + ~lvl1: string, + ~lvl2: string=?, + ~lvl3: string=?, + ~lvl4: string=?, + ~lvl5: string=?, + ~lvl6: string=?, + unit, +) => hierarchy + +let truncate: (string, ~maxLen: int) => string + +let slugify: string => string + +let stripMdxTags: string => string + +let cleanDocstring: string => string + +let extractIntro: string => string + +let extractHeadings: string => array + +let optionToJson: option => JSON.t + +let hierarchyToJson: hierarchy => JSON.t + +let weightToJson: weight => JSON.t let buildMarkdownRecords: ( ~category: string, From 9016a08819ca6af97f3cab84406bfdcf88ca085b Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Thu, 9 Apr 2026 11:19:36 -0400 Subject: [PATCH 11/12] Update DocSearch styles for footer and command keys - Hide clear and cancel buttons for cleaner UI - Redesign footer with border and spacing adjustments - Add styles for command key display in footer --- styles/_docsearch.css | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/styles/_docsearch.css b/styles/_docsearch.css index 42c5b36b6..e770e20f2 100644 --- a/styles/_docsearch.css +++ b/styles/_docsearch.css @@ -137,17 +137,17 @@ @apply hidden; } +.DocSearch-Clear { + @apply hidden; +} + .DocSearch-LoadingIndicator svg, .DocSearch-MagnifierLabel svg { @apply w-4 h-4; } .DocSearch-Cancel { - font-size: 0; - background-image: url("data:image/svg+xml,%3Csvg width='16' height='7' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M.506 6h3.931V4.986H1.736v-1.39h2.488V2.583H1.736V1.196h2.69V.182H.506V6ZM8.56 1.855h1.18C9.721.818 8.87.102 7.574.102c-1.276 0-2.21.705-2.205 1.762-.003.858.602 1.35 1.585 1.585l.634.159c.633.153.986.335.988.727-.002.426-.406.716-1.03.716-.64 0-1.1-.295-1.14-.878h-1.19c.03 1.259.931 1.91 2.343 1.91 1.42 0 2.256-.68 2.259-1.745-.003-.969-.733-1.483-1.744-1.71l-.523-.125c-.506-.117-.93-.304-.92-.722 0-.375.332-.65.934-.65.588 0 .949.267.994.724ZM15.78 2.219C15.618.875 14.6.102 13.254.102c-1.537 0-2.71 1.086-2.71 2.989 0 1.898 1.153 2.989 2.71 2.989 1.492 0 2.392-.992 2.526-2.063l-1.244-.006c-.117.623-.606.98-1.262.98-.883 0-1.483-.656-1.483-1.9 0-1.21.591-1.9 1.492-1.9.673 0 1.159.389 1.253 1.028h1.244Z' fill='%2394a3b8'/%3E%3C/svg%3E") !important; - background-size: 57.1428571429% auto; - @apply w-9 h-7 bg-no-repeat bg-center appearance-none border border-gray-20 - rounded; + display: none !important; } /* Modal Dropdown */ @@ -327,12 +327,22 @@ svg.DocSearch-Hit-Select-Icon { /* Modal Footer */ .DocSearch-Footer { - @apply flex flex-row-reverse flex-shrink-0 justify-between relative - select-none w-full z-100 p-4; + border-top: 1px solid; + @apply flex flex-shrink-0 items-center justify-between relative + select-none w-full z-100 px-4 py-3 border-gray-20; } .DocSearch-Commands { - display: none !important; + @apply flex items-center gap-3 list-none m-0 p-0; +} + +.DocSearch-Commands li { + @apply flex items-center gap-1.5 text-12 text-gray-40; +} + +.DocSearch-Commands-Key { + @apply inline-flex items-center justify-center w-5 h-5 rounded + border border-gray-20 bg-gray-5 text-11 text-gray-60 font-medium; } /* Responsive */ From e29a26e2daab596eb612a02d3e29075f55deb881 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Thu, 9 Apr 2026 11:30:31 -0400 Subject: [PATCH 12/12] Remove Escape key handler from Search component Update DocSearch commands to show 'to clear' when input has text and 'to close' when empty --- src/components/Search.res | 1 - styles/_docsearch.css | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/Search.res b/src/components/Search.res index 5f744f7de..4da1eb36d 100644 --- a/src/components/Search.res +++ b/src/components/Search.res @@ -131,7 +131,6 @@ let make = () => { switch e.key { | "/" => focusSearch(e) | "k" if e.ctrlKey || e.metaKey => focusSearch(e) - | "Escape" => handleCloseModal() | _ => () } } diff --git a/styles/_docsearch.css b/styles/_docsearch.css index e770e20f2..ba5f02688 100644 --- a/styles/_docsearch.css +++ b/styles/_docsearch.css @@ -345,6 +345,24 @@ svg.DocSearch-Hit-Select-Icon { border border-gray-20 bg-gray-5 text-11 text-gray-60 font-medium; } +/* Swap "to close" / "to clear" based on whether the input has a query. + :placeholder-shown is true when the input is empty, false when it has text. */ +.DocSearch-Commands li:last-child .DocSearch-Label { + font-size: 0; +} + +.DocSearch-Commands li:last-child .DocSearch-Label::after { + content: "to close"; + font-size: 0.75rem; +} + +.DocSearch-Modal:has(.DocSearch-Input:not(:placeholder-shown)) + .DocSearch-Commands + li:last-child + .DocSearch-Label::after { + content: "to clear"; +} + /* Responsive */ @media (max-width: 750px) { .DocSearch-Dropdown {