From 5026728c84b43e4d4ecaa7c9db49c73e09b469b7 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sat, 25 Apr 2026 19:12:51 -0400 Subject: [PATCH 01/32] chore: add yarn workspaces --- apps/docs/package.json | 117 +++++++++++++++++++++++++++++ package.json | 132 ++++++++------------------------- yarn.lock | 162 ++++++++++++++++++++++------------------- 3 files changed, 235 insertions(+), 176 deletions(-) create mode 100644 apps/docs/package.json diff --git a/apps/docs/package.json b/apps/docs/package.json new file mode 100644 index 000000000..cc999a5ec --- /dev/null +++ b/apps/docs/package.json @@ -0,0 +1,117 @@ +{ + "name": "@rescript-lang/docs", + "version": "1.0.0", + "private": true, + "license": "MIT", + "author": "Patrick Ecker ", + "type": "module", + "scripts": { + "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", + "build:update-index": "yarn build:generate-llms && node _scripts/generate_feed.mjs > public/blog/feed.xml", + "build:vite": "react-router build", + "build": "yarn build:res && yarn build:scripts && yarn build:update-index && yarn build:vite", + "ci:format": "oxfmt --check", + "ci:test": "yarn vitest --run --browser.headless", + "clean:res": "rescript clean", + "convert-images": "auto-convert-images", + "dev:res": "rescript watch", + "dev:vite": "react-router dev --host", + "dev:wrangler": "yarn wrangler pages dev build/client", + "dev": "yarn prepare && yarn dev:res & yarn dev:vite & yarn dev:wrangler", + "format": "oxfmt && rescript format", + "prepare": "yarn build:res && yarn build:scripts && yarn build:update-index", + "preview": "yarn build && static-server build/client", + "reanalyze": "rescript-tools reanalyze -all-cmt .", + "test": "node scripts/test.mjs", + "cy:open": "yarn build:res && cypress open --e2e --browser electron", + "cy:run": "yarn build:res && cypress run", + "cy:e2e": "yarn build:res && cypress run --browser chrome", + "vitest": "vitest", + "vitest:update": "vitest --run --browser.headless --update" + }, + "dependencies": { + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.2", + "@babel/traverse": "^7.29.0", + "@cloudflare/pages-plugin-vercel-og": "^0.1.2", + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/language": "^6.12.3", + "@codemirror/lint": "^6.9.5", + "@codemirror/search": "^6.6.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.41.0", + "@docsearch/react": "^4.6.2", + "@headlessui/react": "^2.2.9", + "@lezer/highlight": "^1.2.3", + "@mdx-js/mdx": "^3.1.1", + "@node-cli/static-server": "^3.1.10", + "@react-router/node": "^7.14.0", + "@replit/codemirror-vim": "^6.3.0", + "@rescript/react": "^0.14.2", + "@rescript/webapi": "0.1.0-experimental-29db5f4", + "@tsnobip/rescript-lezer": "^0.8.0", + "docson": "^2.1.0", + "fuse.js": "^6.6.2", + "highlight.js": "^11.11.1", + "highlightjs-rescript": "^0.2.2", + "isbot": "^5.1.37", + "lz-string": "^1.5.0", + "mdast-util-from-markdown": "^2.0.3", + "mdast-util-to-string": "^4.0.0", + "mdast-util-toc": "^7.1.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-markdown": "^10.1.0", + "react-router": "^7.14.0", + "react-router-dom": "^7.14.0", + "rehype-slug": "^6.0.0", + "rehype-stringify": "^10.0.1", + "remark": "^15.0.1", + "remark-comment": "^1.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", + "remark-validate-links": "^13.1.0", + "rescript": "^12.2.0", + "tinyglobby": "^0.2.15", + "unified": "^11.0.5", + "vfile-matter": "^5.0.1" + }, + "devDependencies": { + "@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.2.0", + "chokidar": "^4.0.3", + "cypress": "^15.13.1", + "dotenv": "^16.6.1", + "jsdom": "^26.1.0", + "lefthook": "^2.1.4", + "lightningcss": "^1.32.0", + "oxfmt": "^0.46.0", + "playwright": "^1.59.1", + "remark-cli": "^12.0.1", + "search-insights": "^2.17.3", + "tailwindcss": "^4", + "to-vfile": "^8.0.0", + "vfile-reporter": "^8.1.1", + "vite": "^8.0.3", + "vite-plugin-devtools-json": "^1.0.0", + "vite-plugin-env-compatible": "^2.0.1", + "vite-plugin-page-reload": "^0.2.3", + "vitest": "^4.1.2", + "vitest-browser-react": "^2.2.0", + "wrangler": "^4.85.0" + }, + "resolutions": { + "marked": "4.0.10" + }, + "engines": { + "node": ">=22" + } +} diff --git a/package.json b/package.json index 4822e005a..7a54deb2f 100644 --- a/package.json +++ b/package.json @@ -1,115 +1,47 @@ { - "name": "rescript-lang.org", + "name": "rescript-lang.org-monorepo", "version": "1.0.0", "private": true, "license": "MIT", "author": "Patrick Ecker ", "type": "module", + "workspaces": [ + "apps/*", + "packages/*" + ], "scripts": { - "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", - "build:update-index": "yarn build:generate-llms && node _scripts/generate_feed.mjs > public/blog/feed.xml", - "build:vite": "react-router build", - "build": "yarn build:res && yarn build:scripts && yarn build:update-index && yarn build:vite", + "build:scripts": "yarn workspace @rescript-lang/docs build:scripts", + "build:generate-llms": "yarn workspace @rescript-lang/docs build:generate-llms", + "build:res": "yarn workspace @rescript-lang/docs build:res", + "build:sync-bundles": "yarn workspace @rescript-lang/docs build:sync-bundles", + "build:search-index": "yarn workspace @rescript-lang/docs build:search-index", + "build:update-index": "yarn workspace @rescript-lang/docs build:update-index", + "build:vite": "yarn workspace @rescript-lang/docs build:vite", + "check:algolia-public-env": "yarn workspace @rescript-lang/docs check:algolia-public-env", + "build": "yarn workspace @rescript-lang/docs build", "ci:format": "oxfmt --check", - "ci:test": "yarn vitest --run --browser.headless", - "clean:res": "rescript clean", - "convert-images": "auto-convert-images", - "dev:res": "rescript watch", - "dev:vite": "react-router dev --host", - "dev:wrangler": "yarn wrangler pages dev build/client", - "dev": "yarn prepare && yarn dev:res & yarn dev:vite & yarn dev:wrangler", - "format": "oxfmt && rescript format", - "prepare": "yarn build:res && yarn build:scripts && yarn build:update-index", - "preview": "yarn build && static-server build/client", - "reanalyze": "rescript-tools reanalyze -all-cmt .", - "test": "node scripts/test.mjs", - "cy:open": "yarn build:res && cypress open --e2e --browser electron", - "cy:run": "yarn build:res && cypress run", - "cy:e2e": "yarn build:res && cypress run --browser chrome", - "vitest": "vitest", - "vitest:update": "vitest --run --browser.headless --update" - }, - "dependencies": { - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.2", - "@babel/traverse": "^7.29.0", - "@cloudflare/pages-plugin-vercel-og": "^0.1.2", - "@codemirror/commands": "^6.10.3", - "@codemirror/lang-javascript": "^6.2.5", - "@codemirror/language": "^6.12.3", - "@codemirror/lint": "^6.9.5", - "@codemirror/search": "^6.6.0", - "@codemirror/state": "^6.6.0", - "@codemirror/view": "^6.41.0", - "@docsearch/react": "^4.6.2", - "@headlessui/react": "^2.2.9", - "@lezer/highlight": "^1.2.3", - "@mdx-js/mdx": "^3.1.1", - "@node-cli/static-server": "^3.1.10", - "@react-router/node": "^7.14.0", - "@replit/codemirror-vim": "^6.3.0", - "@rescript/react": "^0.14.2", - "@rescript/webapi": "0.1.0-experimental-29db5f4", - "@tsnobip/rescript-lezer": "^0.8.0", - "docson": "^2.1.0", - "fuse.js": "^6.6.2", - "highlight.js": "^11.11.1", - "highlightjs-rescript": "^0.2.2", - "isbot": "^5.1.37", - "lz-string": "^1.5.0", - "mdast-util-from-markdown": "^2.0.3", - "mdast-util-to-string": "^4.0.0", - "mdast-util-toc": "^7.1.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-markdown": "^10.1.0", - "react-router": "^7.14.0", - "react-router-dom": "^7.14.0", - "rehype-slug": "^6.0.0", - "rehype-stringify": "^10.0.1", - "remark": "^15.0.1", - "remark-comment": "^1.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.1", - "remark-validate-links": "^13.1.0", - "rescript": "^12.2.0", - "tinyglobby": "^0.2.15", - "unified": "^11.0.5", - "vfile-matter": "^5.0.1" + "ci:test": "yarn workspace @rescript-lang/docs ci:test", + "clean:res": "yarn workspace @rescript-lang/docs clean:res", + "convert-images": "yarn workspace @rescript-lang/docs convert-images", + "dev:res": "yarn workspace @rescript-lang/docs dev:res", + "dev:vite": "yarn workspace @rescript-lang/docs dev:vite", + "dev:wrangler": "yarn workspace @rescript-lang/docs dev:wrangler", + "dev": "yarn workspace @rescript-lang/docs dev", + "format": "oxfmt && yarn workspace @rescript-lang/docs format", + "prepare": "yarn workspace @rescript-lang/docs prepare", + "preview": "yarn workspace @rescript-lang/docs preview", + "reanalyze": "yarn workspace @rescript-lang/docs reanalyze", + "test": "yarn workspace @rescript-lang/docs test", + "cy:open": "yarn workspace @rescript-lang/docs cy:open", + "cy:run": "yarn workspace @rescript-lang/docs cy:run", + "cy:e2e": "yarn workspace @rescript-lang/docs cy:e2e", + "vitest": "yarn workspace @rescript-lang/docs vitest", + "vitest:update": "yarn workspace @rescript-lang/docs vitest:update" }, "devDependencies": { - "@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.2.0", - "chokidar": "^4.0.3", - "cypress": "^15.13.1", - "dotenv": "^16.6.1", - "jsdom": "^26.1.0", "lefthook": "^2.1.4", - "lightningcss": "^1.32.0", "oxfmt": "^0.46.0", - "playwright": "^1.59.1", - "remark-cli": "^12.0.1", - "search-insights": "^2.17.3", - "tailwindcss": "^4", - "to-vfile": "^8.0.0", - "vfile-reporter": "^8.1.1", - "vite": "^8.0.3", - "vite-plugin-devtools-json": "^1.0.0", - "vite-plugin-env-compatible": "^2.0.1", - "vite-plugin-page-reload": "^0.2.3", - "vitest": "^4.1.2", - "vitest-browser-react": "^2.2.0", - "wrangler": "^4.85.0" - }, - "resolutions": { - "marked": "4.0.10" + "rescript": "^12.2.0" }, "engines": { "node": ">=22" diff --git a/yarn.lock b/yarn.lock index 895464783..53c85cc3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2230,6 +2230,85 @@ __metadata: languageName: node linkType: hard +"@rescript-lang/docs@workspace:apps/docs": + version: 0.0.0-use.local + resolution: "@rescript-lang/docs@workspace:apps/docs" + dependencies: + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.2" + "@babel/traverse": "npm:^7.29.0" + "@cloudflare/pages-plugin-vercel-og": "npm:^0.1.2" + "@codemirror/commands": "npm:^6.10.3" + "@codemirror/lang-javascript": "npm:^6.2.5" + "@codemirror/language": "npm:^6.12.3" + "@codemirror/lint": "npm:^6.9.5" + "@codemirror/search": "npm:^6.6.0" + "@codemirror/state": "npm:^6.6.0" + "@codemirror/view": "npm:^6.41.0" + "@docsearch/react": "npm:^4.6.2" + "@headlessui/react": "npm:^2.2.9" + "@lezer/highlight": "npm:^1.2.3" + "@mdx-js/mdx": "npm:^3.1.1" + "@node-cli/static-server": "npm:^3.1.10" + "@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.2" + "@rescript/webapi": "npm:0.1.0-experimental-29db5f4" + "@tailwindcss/vite": "npm:^4.2.2" + "@tsnobip/rescript-lezer": "npm:^0.8.0" + "@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.2.0" + chokidar: "npm:^4.0.3" + cypress: "npm:^15.13.1" + docson: "npm:^2.1.0" + dotenv: "npm:^16.6.1" + fuse.js: "npm:^6.6.2" + highlight.js: "npm:^11.11.1" + highlightjs-rescript: "npm:^0.2.2" + isbot: "npm:^5.1.37" + jsdom: "npm:^26.1.0" + lefthook: "npm:^2.1.4" + lightningcss: "npm:^1.32.0" + lz-string: "npm:^1.5.0" + mdast-util-from-markdown: "npm:^2.0.3" + mdast-util-to-string: "npm:^4.0.0" + mdast-util-toc: "npm:^7.1.0" + oxfmt: "npm:^0.46.0" + playwright: "npm:^1.59.1" + react: "npm:^19.2.4" + react-dom: "npm:^19.2.4" + react-markdown: "npm:^10.1.0" + react-router: "npm:^7.14.0" + react-router-dom: "npm:^7.14.0" + rehype-slug: "npm:^6.0.0" + rehype-stringify: "npm:^10.0.1" + remark: "npm:^15.0.1" + remark-cli: "npm:^12.0.1" + remark-comment: "npm:^1.0.0" + remark-frontmatter: "npm:^5.0.0" + remark-gfm: "npm:^4.0.1" + remark-validate-links: "npm:^13.1.0" + rescript: "npm:^12.2.0" + search-insights: "npm:^2.17.3" + tailwindcss: "npm:^4" + tinyglobby: "npm:^0.2.15" + to-vfile: "npm:^8.0.0" + unified: "npm:^11.0.5" + vfile-matter: "npm:^5.0.1" + vfile-reporter: "npm:^8.1.1" + 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.3" + vitest: "npm:^4.1.2" + vitest-browser-react: "npm:^2.2.0" + wrangler: "npm:^4.85.0" + languageName: unknown + linkType: soft + "@rescript/darwin-arm64@npm:12.2.0": version: 12.2.0 resolution: "@rescript/darwin-arm64@npm:12.2.0" @@ -7246,12 +7325,12 @@ __metadata: languageName: node linkType: hard -"marked@npm:4.0.10": - version: 4.0.10 - resolution: "marked@npm:4.0.10" +"marked@npm:^0.3.14": + version: 0.3.19 + resolution: "marked@npm:0.3.19" bin: - marked: bin/marked.js - checksum: 10c0/137660cd1eca54cfcdcec9d9c7dea786fc57ba3663da9043b721aff4c1419fc869d21bc38f6d5907062b82d4ef354f4fcac6605cac5f4f9dc1595a743b856d91 + marked: ./bin/marked + checksum: 10c0/ee5e268716de56a7543c245268d72e5eb1a66f67022e0392cab9744b3b38768d1db289c173679ff696cdbf1bcd82ff10520cae2296f3293989e07a17f9218705 languageName: node linkType: hard @@ -9502,82 +9581,13 @@ __metadata: languageName: node linkType: hard -"rescript-lang.org@workspace:.": +"rescript-lang.org-monorepo@workspace:.": version: 0.0.0-use.local - resolution: "rescript-lang.org@workspace:." + resolution: "rescript-lang.org-monorepo@workspace:." dependencies: - "@babel/generator": "npm:^7.29.1" - "@babel/parser": "npm:^7.29.2" - "@babel/traverse": "npm:^7.29.0" - "@cloudflare/pages-plugin-vercel-og": "npm:^0.1.2" - "@codemirror/commands": "npm:^6.10.3" - "@codemirror/lang-javascript": "npm:^6.2.5" - "@codemirror/language": "npm:^6.12.3" - "@codemirror/lint": "npm:^6.9.5" - "@codemirror/search": "npm:^6.6.0" - "@codemirror/state": "npm:^6.6.0" - "@codemirror/view": "npm:^6.41.0" - "@docsearch/react": "npm:^4.6.2" - "@headlessui/react": "npm:^2.2.9" - "@lezer/highlight": "npm:^1.2.3" - "@mdx-js/mdx": "npm:^3.1.1" - "@node-cli/static-server": "npm:^3.1.10" - "@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.2" - "@rescript/webapi": "npm:0.1.0-experimental-29db5f4" - "@tailwindcss/vite": "npm:^4.2.2" - "@tsnobip/rescript-lezer": "npm:^0.8.0" - "@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.2.0" - chokidar: "npm:^4.0.3" - cypress: "npm:^15.13.1" - docson: "npm:^2.1.0" - dotenv: "npm:^16.6.1" - fuse.js: "npm:^6.6.2" - highlight.js: "npm:^11.11.1" - highlightjs-rescript: "npm:^0.2.2" - isbot: "npm:^5.1.37" - jsdom: "npm:^26.1.0" lefthook: "npm:^2.1.4" - lightningcss: "npm:^1.32.0" - lz-string: "npm:^1.5.0" - mdast-util-from-markdown: "npm:^2.0.3" - mdast-util-to-string: "npm:^4.0.0" - mdast-util-toc: "npm:^7.1.0" oxfmt: "npm:^0.46.0" - playwright: "npm:^1.59.1" - react: "npm:^19.2.4" - react-dom: "npm:^19.2.4" - react-markdown: "npm:^10.1.0" - react-router: "npm:^7.14.0" - react-router-dom: "npm:^7.14.0" - rehype-slug: "npm:^6.0.0" - rehype-stringify: "npm:^10.0.1" - remark: "npm:^15.0.1" - remark-cli: "npm:^12.0.1" - remark-comment: "npm:^1.0.0" - remark-frontmatter: "npm:^5.0.0" - remark-gfm: "npm:^4.0.1" - remark-validate-links: "npm:^13.1.0" rescript: "npm:^12.2.0" - search-insights: "npm:^2.17.3" - tailwindcss: "npm:^4" - tinyglobby: "npm:^0.2.15" - to-vfile: "npm:^8.0.0" - unified: "npm:^11.0.5" - vfile-matter: "npm:^5.0.1" - vfile-reporter: "npm:^8.1.1" - 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.3" - vitest: "npm:^4.1.2" - vitest-browser-react: "npm:^2.2.0" - wrangler: "npm:^4.85.0" languageName: unknown linkType: soft From 9c7eb50456dae916d58f39044ddf74c06b87f284 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sat, 25 Apr 2026 19:22:41 -0400 Subject: [PATCH 02/32] fix: honor workspace resolutions at root --- apps/docs/package.json | 3 --- package.json | 5 +++-- yarn.lock | 10 +++++----- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index cc999a5ec..7faf7b327 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -108,9 +108,6 @@ "vitest-browser-react": "^2.2.0", "wrangler": "^4.85.0" }, - "resolutions": { - "marked": "4.0.10" - }, "engines": { "node": ">=22" } diff --git a/package.json b/package.json index 7a54deb2f..c2e81c7e2 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,8 @@ "build:generate-llms": "yarn workspace @rescript-lang/docs build:generate-llms", "build:res": "yarn workspace @rescript-lang/docs build:res", "build:sync-bundles": "yarn workspace @rescript-lang/docs build:sync-bundles", - "build:search-index": "yarn workspace @rescript-lang/docs build:search-index", "build:update-index": "yarn workspace @rescript-lang/docs build:update-index", "build:vite": "yarn workspace @rescript-lang/docs build:vite", - "check:algolia-public-env": "yarn workspace @rescript-lang/docs check:algolia-public-env", "build": "yarn workspace @rescript-lang/docs build", "ci:format": "oxfmt --check", "ci:test": "yarn workspace @rescript-lang/docs ci:test", @@ -43,6 +41,9 @@ "oxfmt": "^0.46.0", "rescript": "^12.2.0" }, + "resolutions": { + "marked": "4.0.10" + }, "engines": { "node": ">=22" }, diff --git a/yarn.lock b/yarn.lock index 53c85cc3f..c2e215e47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7325,12 +7325,12 @@ __metadata: languageName: node linkType: hard -"marked@npm:^0.3.14": - version: 0.3.19 - resolution: "marked@npm:0.3.19" +"marked@npm:4.0.10": + version: 4.0.10 + resolution: "marked@npm:4.0.10" bin: - marked: ./bin/marked - checksum: 10c0/ee5e268716de56a7543c245268d72e5eb1a66f67022e0392cab9744b3b38768d1db289c173679ff696cdbf1bcd82ff10520cae2296f3293989e07a17f9218705 + marked: bin/marked.js + checksum: 10c0/137660cd1eca54cfcdcec9d9c7dea786fc57ba3663da9043b721aff4c1419fc869d21bc38f6d5907062b82d4ef354f4fcac6605cac5f4f9dc1595a743b856d91 languageName: node linkType: hard From 3bc1c9a0d37a8281c95a842cf209f2cce0211f3b Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sat, 25 Apr 2026 19:33:07 -0400 Subject: [PATCH 03/32] chore: move docs app into workspace --- .../docs/__tests__}/ApiOverviewLayout_.test.res | 0 {__tests__ => apps/docs/__tests__}/Banner_.test.res | 0 .../docs/__tests__}/BlogArticle_.test.res | 0 {__tests__ => apps/docs/__tests__}/Blog_.test.res | 0 {__tests__ => apps/docs/__tests__}/Button_.test.res | 0 .../docs/__tests__}/CodeExample_.test.res | 0 .../docs/__tests__}/CommunityLayout_.test.res | 0 .../docs/__tests__}/DocsLayout_.test.res | 0 .../docs/__tests__}/DocsOverview_.test.res | 0 {__tests__ => apps/docs/__tests__}/Footer_.test.res | 0 .../docs/__tests__}/LandingPage_.test.res | 0 .../docs/__tests__}/MainLayout_.test.res | 0 .../docs/__tests__}/MarkdownComponents_.test.res | 0 .../docs/__tests__}/MetaDescription_.test.res | 0 .../docs/__tests__}/NavbarPrimary_.test.res | 0 .../docs/__tests__}/NavbarSecondary_.test.res | 0 .../docs/__tests__}/NavbarTertiary_.test.res | 0 .../docs/__tests__}/SearchBox_.test.res | 0 .../docs/__tests__}/SidebarLayout_.test.res | 0 .../docs/__tests__}/SyntaxLookup_.test.res | 0 {__tests__ => apps/docs/__tests__}/Tag_.test.res | 0 .../docs/__tests__}/VersionSelect_.test.res | 0 .../api-old-docs-warning-chromium-linux.png | Bin .../desktop-api-layout-chromium-linux.png | Bin .../mobile-api-layout-chromium-linux.png | Bin .../desktop-api-overview-chromium-linux.png | Bin ...top-api-overview-with-content-chromium-linux.png | Bin .../mobile-api-overview-chromium-linux.png | Bin .../tablet-api-overview-chromium-linux.png | Bin .../banner-with-content-chromium-linux.png | Bin .../mobile-banner-chromium-linux.png | Bin ...desktop-blog-article-archived-chromium-linux.png | Bin .../desktop-blog-article-chromium-linux.png | Bin ...esktop-blog-article-coauthors-chromium-linux.png | Bin ...p-blog-article-no-description-chromium-linux.png | Bin ...sktop-blog-article-with-image-chromium-linux.png | Bin .../mobile-blog-article-chromium-linux.png | Bin .../desktop-blog-archived-chromium-linux.png | Bin ...esktop-blog-category-selector-chromium-linux.png | Bin .../desktop-blog-empty-chromium-linux.png | Bin .../desktop-blog-index-chromium-linux.png | Bin .../desktop-blog-single-post-chromium-linux.png | Bin .../mobile-blog-index-chromium-linux.png | Bin .../button-primary-blue-chromium-linux.png | Bin .../button-primary-red-chromium-linux.png | Bin .../button-secondary-red-chromium-linux.png | Bin .../button-small-chromium-linux.png | Bin .../code-example-highlighted-chromium-linux.png | Bin .../code-example-no-label-chromium-linux.png | Bin .../code-example-rescript-chromium-linux.png | Bin .../code-toggle-js-selected-chromium-linux.png | Bin .../code-toggle-tabs-chromium-linux.png | Bin .../desktop-community-layout-chromium-linux.png | Bin ...unity-layout-multi-categories-chromium-linux.png | Bin .../mobile-community-layout-chromium-linux.png | Bin .../tablet-community-layout-chromium-linux.png | Bin ...sktop-docs-layout-active-item-chromium-linux.png | Bin .../desktop-docs-layout-chromium-linux.png | Bin ...esktop-docs-layout-pagination-chromium-linux.png | Bin .../desktop-docs-layout-with-toc-chromium-linux.png | Bin .../mobile-docs-layout-chromium-linux.png | Bin .../desktop-docs-overview-chromium-linux.png | Bin ...sktop-docs-overview-ecosystem-chromium-linux.png | Bin .../mobile-docs-overview-chromium-linux.png | Bin .../desktop-footer-chromium-linux.png | Bin .../mobile-footer-chromium-linux.png | Bin .../desktop-main-layout-chromium-linux.png | Bin .../mobile-main-layout-chromium-linux.png | Bin .../markdown-admonitions-chromium-linux.png | Bin .../markdown-anchor-chromium-linux.png | Bin .../markdown-blockquote-chromium-linux.png | Bin .../markdown-cite-chromium-linux.png | Bin .../markdown-cite-no-author-chromium-linux.png | Bin .../markdown-headings-chromium-linux.png | Bin .../markdown-hr-chromium-linux.png | Bin .../markdown-image-chromium-linux.png | Bin .../markdown-image-small-chromium-linux.png | Bin .../markdown-inline-code-chromium-linux.png | Bin .../markdown-links-chromium-linux.png | Bin .../markdown-lists-chromium-linux.png | Bin .../markdown-nested-lists-chromium-linux.png | Bin .../markdown-strong-chromium-linux.png | Bin .../markdown-table-chromium-linux.png | Bin .../markdown-text-elements-chromium-linux.png | Bin .../desktop-navbar-primary-chromium-linux.png | Bin .../mobile-navbar-primary-chromium-linux.png | Bin ...mobile-overlay-navbar-primary-chromium-linux.png | Bin .../tablet-navbar-primary-chromium-linux.png | Bin .../desktop-navbar-secondary-chromium-linux.png | Bin ...navbar-secondary-react-active-chromium-linux.png | Bin .../mobile-navbar-secondary-chromium-linux.png | Bin .../desktop-navbar-tertiary-chromium-linux.png | Bin .../mobile-navbar-tertiary-chromium-linux.png | Bin .../searchbox-empty-chromium-linux.png | Bin .../searchbox-with-value-chromium-linux.png | Bin .../sidebar-breadcrumbs-chromium-linux.png | Bin .../sidebar-breadcrumbs-deep-chromium-linux.png | Bin .../sidebar-category-active-item-chromium-linux.png | Bin .../sidebar-category-chromium-linux.png | Bin .../sidebar-category-many-items-chromium-linux.png | Bin .../sidebar-category-with-toc-chromium-linux.png | Bin .../desktop-syntax-lookup-active-chromium-linux.png | Bin .../desktop-syntax-lookup-chromium-linux.png | Bin ...ktop-syntax-lookup-deprecated-chromium-linux.png | Bin ...top-syntax-lookup-pipe-detail-chromium-linux.png | Bin .../mobile-syntax-lookup-active-chromium-linux.png | Bin .../mobile-syntax-lookup-chromium-linux.png | Bin .../Tag_.test.jsx/tag-subtle-chromium-linux.png | Bin .../Tag_.test.jsx/tags-multiple-chromium-linux.png | Bin {app => apps/docs/app}/layouts/DocsLayoutRoute.res | 0 {app => apps/docs/app}/layouts/DocsLayoutRoute.resi | 0 {app => apps/docs/app}/root.res | 0 {app => apps/docs/app}/root.resi | 0 {app => apps/docs/app}/routes.res | 0 {app => apps/docs/app}/routes.resi | 0 {app => apps/docs/app}/routes/ApiDocs.res | 0 {app => apps/docs/app}/routes/ApiOverviewRoute.res | 0 {app => apps/docs/app}/routes/ApiOverviewRoute.resi | 0 {app => apps/docs/app}/routes/ApiRoute.res | 0 {app => apps/docs/app}/routes/ApiRoute.resi | 0 {app => apps/docs/app}/routes/Blog.res | 0 {app => apps/docs/app}/routes/BlogArticle.res | 0 {app => apps/docs/app}/routes/BlogArticle.resi | 0 {app => apps/docs/app}/routes/BlogArticleRoute.res | 0 {app => apps/docs/app}/routes/BlogArticleRoute.resi | 0 {app => apps/docs/app}/routes/BlogRoute.res | 0 {app => apps/docs/app}/routes/BlogRoute.resi | 0 {app => apps/docs/app}/routes/CommunityRoute.res | 0 {app => apps/docs/app}/routes/CommunityRoute.resi | 0 .../docs/app}/routes/DocsGuidelinesRoute.res | 0 .../docs/app}/routes/DocsGuidelinesRoute.resi | 0 {app => apps/docs/app}/routes/DocsManualRoute.res | 0 {app => apps/docs/app}/routes/DocsManualRoute.resi | 0 {app => apps/docs/app}/routes/DocsOverview.res | 0 {app => apps/docs/app}/routes/DocsOverview.resi | 0 {app => apps/docs/app}/routes/DocsReactRoute.res | 0 {app => apps/docs/app}/routes/DocsReactRoute.resi | 0 {app => apps/docs/app}/routes/LandingPage.res | 0 {app => apps/docs/app}/routes/LandingPage.resi | 0 {app => apps/docs/app}/routes/LandingPageRoute.res | 0 {app => apps/docs/app}/routes/LandingPageRoute.resi | 0 {app => apps/docs/app}/routes/NotFoundRoute.res | 0 {app => apps/docs/app}/routes/NotFoundRoute.resi | 0 {app => apps/docs/app}/routes/Packages.res | 0 {app => apps/docs/app}/routes/Packages.resi | 0 {app => apps/docs/app}/routes/PackagesRoute.res | 0 {app => apps/docs/app}/routes/PackagesRoute.resi | 0 {app => apps/docs/app}/routes/SyntaxLookup.res | 0 .../docs/app}/routes/SyntaxLookupDetailRoute.res | 0 .../docs/app}/routes/SyntaxLookupDetailRoute.resi | 0 {app => apps/docs/app}/routes/SyntaxLookupRoute.res | 0 .../docs/app}/routes/SyntaxLookupRoute.resi | 0 {app => apps/docs/app}/routes/TryRoute.res | 0 {app => apps/docs/app}/routes/TryRoute.resi | 0 {compilers => apps/docs/compilers}/README.md | 0 {compilers => apps/docs/compilers}/dummy/Dummy.res | 0 .../docs/compilers}/package-lock.json | 0 {compilers => apps/docs/compilers}/package.json | 0 {compilers => apps/docs/compilers}/rescript.json | 0 cypress.config.mjs => apps/docs/cypress.config.mjs | 0 {cypress => apps/docs/cypress}/support/e2e.js | 0 {data => apps/docs/data}/api/v12.0.0/belt.json | 0 {data => apps/docs/data}/api/v12.0.0/dom.json | 0 {data => apps/docs/data}/api/v12.0.0/js.json | 0 {data => apps/docs/data}/api/v12.0.0/stdlib.json | 0 {data => apps/docs/data}/api/v12.0.0/toc_tree.json | 0 {data => apps/docs/data}/api/v12.0.1/belt.json | 0 {data => apps/docs/data}/api/v12.0.1/dom.json | 0 {data => apps/docs/data}/api/v12.0.1/js.json | 0 {data => apps/docs/data}/api/v12.0.1/stdlib.json | 0 {data => apps/docs/data}/api/v12.0.1/toc_tree.json | 0 {data => apps/docs/data}/api/v12.0.2/belt.json | 0 {data => apps/docs/data}/api/v12.0.2/dom.json | 0 {data => apps/docs/data}/api/v12.0.2/js.json | 0 {data => apps/docs/data}/api/v12.0.2/stdlib.json | 0 {data => apps/docs/data}/api/v12.0.2/toc_tree.json | 0 {data => apps/docs/data}/api/v12.1.0/belt.json | 0 {data => apps/docs/data}/api/v12.1.0/dom.json | 0 {data => apps/docs/data}/api/v12.1.0/js.json | 0 {data => apps/docs/data}/api/v12.1.0/stdlib.json | 0 {data => apps/docs/data}/api/v12.1.0/toc_tree.json | 0 {data => apps/docs/data}/api/v12.2.0/belt.json | 0 {data => apps/docs/data}/api/v12.2.0/dom.json | 0 {data => apps/docs/data}/api/v12.2.0/js.json | 0 {data => apps/docs/data}/api/v12.2.0/stdlib.json | 0 {data => apps/docs/data}/api/v12.2.0/toc_tree.json | 0 {data => apps/docs/data}/api/v13.0.0/belt.json | 0 {data => apps/docs/data}/api/v13.0.0/dom.json | 0 {data => apps/docs/data}/api/v13.0.0/js.json | 0 {data => apps/docs/data}/api/v13.0.0/stdlib.json | 0 {data => apps/docs/data}/api/v13.0.0/toc_tree.json | 0 {e2e => apps/docs/e2e}/Navigation.cy.res | 0 {e2e => apps/docs/e2e}/Playground.cy.res | 0 {e2e => apps/docs/e2e}/bindings/Cy.res | 0 .../docs/functions}/ogimage/[[path]]/index.png.res | 0 .../docs/generate-route-types.mjs | 0 .../docs/image-converter.config.mjs | 0 .../a-note-on-bucklescripts-future-commitments.mdx | 0 .../blog/archived/a-small-step-for-bucklescript.mdx | 0 .../blog/archived/a-story-of-exception-encoding.mdx | 0 .../blog/archived/a-story-of-lazy-encoding.mdx | 0 .../blog/archived/another-encoding.mdx | 0 .../markdown-pages}/blog/archived/arity-zero.mdx | 0 .../blog/archived/bucklescript-8-1-new-syntax.mdx | 0 .../blog/archived/bucklescript-release-1-0.mdx | 0 .../blog/archived/bucklescript-release-1-4-2.mdx | 0 .../blog/archived/bucklescript-release-1-4-3.mdx | 0 .../blog/archived/bucklescript-release-1-5-0.mdx | 0 .../blog/archived/bucklescript-release-1-5-1.mdx | 0 .../blog/archived/bucklescript-release-1-5-2.mdx | 0 .../blog/archived/bucklescript-release-1-7-0.mdx | 0 .../blog/archived/bucklescript-release-1-7-4.mdx | 0 .../blog/archived/bucklescript-release-1-7-5.mdx | 0 .../blog/archived/bucklescript-release-3-0-0.mdx | 0 .../blog/archived/bucklescript-release-3-1-0.mdx | 0 .../blog/archived/bucklescript-release-3-1-4.mdx | 0 .../archived/bucklescript-release-4-0-0-pt1.mdx | 0 .../archived/bucklescript-release-4-0-0-pt2.mdx | 0 .../blog/archived/bucklescript-release-4-0-17.mdx | 0 .../blog/archived/bucklescript-release-4-0-8.mdx | 0 .../blog/archived/bucklescript-release-5-0-1.mdx | 0 .../blog/archived/bucklescript-release-5-0-4.mdx | 0 .../blog/archived/bucklescript-release-5-0-5.mdx | 0 .../blog/archived/bucklescript-release-5-0.mdx | 0 .../blog/archived/bucklescript-release-5-1-0.mdx | 0 .../blog/archived/bucklescript-release-5-2-0.mdx | 0 .../blog/archived/bucklescript-release-6-0.mdx | 0 .../blog/archived/bucklescript-release-7-0-2.mdx | 0 .../blog/archived/bucklescript-release-7-1-0.mdx | 0 .../blog/archived/bucklescript-release-7-2.mdx | 0 .../blog/archived/bucklescript-release-7-3.mdx | 0 .../blog/archived/bucklescript-release-8-1-1.mdx | 0 .../blog/archived/bucklescript-release-8-2.mdx | 0 .../archived/bucklescript-roadmap-q3-4-2018.mdx | 0 .../blog/archived/feature-preview-variadic.mdx | 0 .../markdown-pages}/blog/archived/ffi-overview.mdx | 0 .../blog/archived/generalize-uncurry.mdx | 0 .../blog/archived/loading-stdlib-in-memory.mdx | 0 .../blog/archived/overview-of-new_encoding.mdx | 0 .../docs/markdown-pages}/blog/archived/scalable.mdx | 0 .../archived/state-of-reasonml-org-2020-q2-pt1.mdx | 0 .../archived/state-of-reasonml-org-2020-q2-pt2.mdx | 0 .../archived/state-of-reasonml-org-2020-q2-pt3.mdx | 0 .../archived/state-of-reasonml-org-2020-q2-pt4.mdx | 0 .../archived/string-literal-types-in-reason.mdx | 0 .../blog/archived/union-types-in-bucklescript.mdx | 0 .../blog/archived/whats-new-in-7-pt1.mdx | 0 .../blog/archived/whats-new-in-7-pt2.mdx | 0 .../blog/bucklescript-is-rebranding.mdx | 0 .../editor-support-custom-operators-and-more.mdx | 0 .../blog/editor-support-release-1-0.mdx | 0 .../blog/enhanced-ergonomics-for-record-types.mdx | 0 .../blog/first-class-dynamic-import-support.mdx | 0 .../docs/markdown-pages}/blog/improving-interop.mdx | 0 .../blog/introducing-unified-operators.mdx | 0 .../docs/markdown-pages}/blog/new-rescript-logo.mdx | 0 .../docs/markdown-pages}/blog/reactive-analysis.mdx | 0 .../markdown-pages}/blog/reforging-build-system.mdx | 0 .../docs/markdown-pages}/blog/release-10-0-0.mdx | 0 .../docs/markdown-pages}/blog/release-10-1.mdx | 0 .../docs/markdown-pages}/blog/release-11-0-0.mdx | 0 .../docs/markdown-pages}/blog/release-11-1-0.mdx | 0 .../docs/markdown-pages}/blog/release-12-0-0.mdx | 0 .../docs/markdown-pages}/blog/release-8-3-2.mdx | 0 .../docs/markdown-pages}/blog/release-8-3.mdx | 0 .../docs/markdown-pages}/blog/release-8-4.mdx | 0 .../docs/markdown-pages}/blog/release-9-0.mdx | 0 .../docs/markdown-pages}/blog/release-9-1.mdx | 0 .../blog/rescript-association-rebranding.mdx | 0 .../docs/markdown-pages}/blog/retreats.mdx | 0 .../blog/roadmap-2021-and-new-landing-page.mdx | 0 .../docs/markdown-pages}/blog/uncurried-mode.mdx | 0 .../blog/what-can-i-do-with-rescript.mdx | 0 .../markdown-pages}/community/code-of-conduct.mdx | 0 .../docs/markdown-pages}/community/content.mdx | 0 .../docs/markdown-pages}/community/overview.mdx | 0 .../docs/markdown-pages}/community/roadmap.mdx | 0 .../docs/markdown-pages}/community/translations.mdx | 0 .../docs/markdown-pages}/docs/api/belt.json | 0 .../docs/markdown-pages}/docs/api/dom.json | 0 .../docs/markdown-pages}/docs/api/js.json | 0 .../docs/markdown-pages}/docs/api/stdlib.json | 0 .../docs/markdown-pages}/docs/api/toc_tree.json | 0 .../docs/guidelines/publishing-packages.mdx | 0 .../docs/markdown-pages}/docs/manual/api.mdx | 0 .../markdown-pages}/docs/manual/array-and-list.mdx | 0 .../markdown-pages}/docs/manual/async-await.mdx | 0 .../docs/markdown-pages}/docs/manual/attribute.mdx | 0 .../docs/manual/bind-to-global-js-values.mdx | 0 .../docs/manual/bind-to-js-function.mdx | 0 .../docs/manual/bind-to-js-object.mdx | 0 .../docs/manual/build-configuration-schema.mdx | 0 .../docs/manual/build-configuration.mdx | 0 .../docs/manual/build-monorepo-setup.mdx | 0 .../markdown-pages}/docs/manual/build-overview.mdx | 0 .../docs/manual/build-performance.mdx | 0 .../markdown-pages}/docs/manual/control-flow.mdx | 0 .../docs/manual/converting-from-js.mdx | 0 .../docs/markdown-pages}/docs/manual/dict.mdx | 0 .../docs/manual/editor-code-analysis.mdx | 0 .../markdown-pages}/docs/manual/editor-plugins.mdx | 0 .../docs/manual/embed-raw-javascript.mdx | 0 .../docs/manual/equality-comparison.mdx | 0 .../docs/markdown-pages}/docs/manual/exception.mdx | 0 .../docs/manual/extensible-variant.mdx | 0 .../docs/markdown-pages}/docs/manual/external.mdx | 0 .../docs/markdown-pages}/docs/manual/function.mdx | 0 .../manual/generalized-algebraic-data-types.mdx | 0 .../docs/manual/generate-converters-accessors.mdx | 0 .../markdown-pages}/docs/manual/import-export.mdx | 0 .../docs/manual/import-from-export-to-js.mdx | 0 .../docs/manual/inlining-constants.mdx | 0 .../markdown-pages}/docs/manual/installation.mdx | 0 .../docs/manual/interop-cheatsheet.mdx | 0 .../docs/manual/interop-with-js-build-systems.mdx | 0 .../markdown-pages}/docs/manual/introduction.mdx | 0 .../docs/markdown-pages}/docs/manual/json.mdx | 0 .../docs/markdown-pages}/docs/manual/jsx.mdx | 0 .../markdown-pages}/docs/manual/lazy-values.mdx | 0 .../markdown-pages}/docs/manual/let-binding.mdx | 0 .../docs/markdown-pages}/docs/manual/libraries.mdx | 0 .../docs/markdown-pages}/docs/manual/llms.mdx | 0 .../markdown-pages}/docs/manual/migrate-to-v11.mdx | 0 .../markdown-pages}/docs/manual/migrate-to-v12.mdx | 0 .../docs/manual/module-functions.mdx | 0 .../docs/markdown-pages}/docs/manual/module.mdx | 0 .../docs/markdown-pages}/docs/manual/mutation.mdx | 0 .../docs/manual/null-undefined-option.mdx | 0 .../docs/markdown-pages}/docs/manual/object.mdx | 0 .../docs/markdown-pages}/docs/manual/overview.mdx | 0 .../docs/manual/pattern-matching-destructuring.mdx | 0 .../docs/markdown-pages}/docs/manual/pipe.mdx | 0 .../docs/manual/polymorphic-variant.mdx | 0 .../markdown-pages}/docs/manual/primitive-types.mdx | 0 .../docs/manual/project-structure.mdx | 0 .../docs/markdown-pages}/docs/manual/promise.mdx | 0 .../docs/markdown-pages}/docs/manual/record.mdx | 0 .../manual/rescript-for-javascript-developers.mdx | 0 .../docs/manual/reserved-keywords.mdx | 0 .../docs/manual/scoped-polymorphic-types.mdx | 0 .../docs/manual/shared-data-types.mdx | 0 .../docs/manual/tagged-templates.mdx | 0 .../docs/markdown-pages}/docs/manual/try.mdx | 0 .../docs/markdown-pages}/docs/manual/tuple.mdx | 0 .../docs/markdown-pages}/docs/manual/type.mdx | 0 .../docs/manual/typescript-integration.mdx | 0 .../docs/manual/use-illegal-identifier-names.mdx | 0 .../docs/markdown-pages}/docs/manual/variant.mdx | 0 .../markdown-pages}/docs/manual/warning-numbers.mdx | 0 .../markdown-pages}/docs/react/arrays-and-keys.mdx | 0 .../docs/markdown-pages}/docs/react/beyond-jsx.mdx | 0 .../docs/react/components-and-props.mdx | 0 .../docs/markdown-pages}/docs/react/context.mdx | 0 .../markdown-pages}/docs/react/elements-and-jsx.mdx | 0 .../docs/markdown-pages}/docs/react/events.mdx | 0 .../docs/react/extensions-of-props.mdx | 0 .../markdown-pages}/docs/react/forwarding-refs.mdx | 0 .../markdown-pages}/docs/react/hooks-context.mdx | 0 .../markdown-pages}/docs/react/hooks-custom.mdx | 0 .../markdown-pages}/docs/react/hooks-effect.mdx | 0 .../markdown-pages}/docs/react/hooks-overview.mdx | 0 .../markdown-pages}/docs/react/hooks-reducer.mdx | 0 .../docs/markdown-pages}/docs/react/hooks-ref.mdx | 0 .../docs/markdown-pages}/docs/react/hooks-state.mdx | 0 .../docs/react/import-export-reactjs.mdx | 0 .../markdown-pages}/docs/react/installation.mdx | 0 .../markdown-pages}/docs/react/introduction.mdx | 0 .../markdown-pages}/docs/react/lazy-components.mdx | 0 .../docs/markdown-pages}/docs/react/llms.mdx | 0 .../docs/markdown-pages}/docs/react/memo.mdx | 0 .../markdown-pages}/docs/react/refs-and-the-dom.mdx | 0 .../docs/react/rendering-elements.mdx | 0 .../docs/markdown-pages}/docs/react/router.mdx | 0 .../docs/react/server-components.mdx | 0 .../docs/markdown-pages}/docs/react/styling.mdx | 0 .../markdown-pages}/syntax-lookup/decorator_as.mdx | 0 .../syntax-lookup/decorator_dead.mdx | 0 .../syntax-lookup/decorator_deriving.mdx | 0 .../syntax-lookup/decorator_directive.mdx | 0 .../syntax-lookup/decorator_does_not_raise.mdx | 0 .../syntax-lookup/decorator_does_not_throw.mdx | 0 .../decorator_expression_deprecated.mdx | 0 .../syntax-lookup/decorator_expression_warning.mdx | 0 .../syntax-lookup/decorator_gentype.mdx | 0 .../markdown-pages}/syntax-lookup/decorator_get.mdx | 0 .../syntax-lookup/decorator_get_index.mdx | 0 .../syntax-lookup/decorator_ignore.mdx | 0 .../syntax-lookup/decorator_inline.mdx | 0 .../markdown-pages}/syntax-lookup/decorator_int.mdx | 0 .../syntax-lookup/decorator_jsx_component.mdx | 0 .../syntax-lookup/decorator_live.mdx | 0 .../syntax-lookup/decorator_meth.mdx | 0 .../syntax-lookup/decorator_module.mdx | 0 .../syntax-lookup/decorator_module_deprecated.mdx | 0 .../syntax-lookup/decorator_module_warning.mdx | 0 .../markdown-pages}/syntax-lookup/decorator_new.mdx | 0 .../syntax-lookup/decorator_not_undefined.mdx | 0 .../syntax-lookup/decorator_raises.mdx | 0 .../syntax-lookup/decorator_react_component.mdx | 0 .../decorator_react_component_with_props.mdx | 0 .../syntax-lookup/decorator_return.mdx | 0 .../syntax-lookup/decorator_scope.mdx | 0 .../syntax-lookup/decorator_send.mdx | 0 .../syntax-lookup/decorator_send_pipe.mdx | 0 .../markdown-pages}/syntax-lookup/decorator_set.mdx | 0 .../syntax-lookup/decorator_set_index.mdx | 0 .../syntax-lookup/decorator_string.mdx | 0 .../markdown-pages}/syntax-lookup/decorator_tag.mdx | 0 .../syntax-lookup/decorator_taggedTemplate.mdx | 0 .../syntax-lookup/decorator_this.mdx | 0 .../syntax-lookup/decorator_throws.mdx | 0 .../syntax-lookup/decorator_unboxed.mdx | 0 .../syntax-lookup/decorator_unwrap.mdx | 0 .../markdown-pages}/syntax-lookup/decorator_val.mdx | 0 .../syntax-lookup/decorator_variadic.mdx | 0 .../syntax-lookup/extension_debugger.mdx | 0 .../syntax-lookup/extension_identity.mdx | 0 .../syntax-lookup/extension_private_let.mdx | 0 .../syntax-lookup/extension_raw_expression.mdx | 0 .../extension_raw_top_level_expression.mdx | 0 .../syntax-lookup/extension_regular_expression.mdx | 0 .../syntax-lookup/extension_todo.mdx | 0 .../markdown-pages}/syntax-lookup/language_and.mdx | 0 .../syntax-lookup/language_async.mdx | 0 .../syntax-lookup/language_attached_doc_comment.mdx | 0 .../syntax-lookup/language_await.mdx | 0 .../syntax-lookup/language_block_comment.mdx | 0 .../syntax-lookup/language_char_literal.mdx | 0 .../language_covariant_type_parameter.mdx | 0 .../markdown-pages}/syntax-lookup/language_dict.mdx | 0 .../syntax-lookup/language_doc_comment.mdx | 0 .../syntax-lookup/language_empty_object_type.mdx | 0 .../syntax-lookup/language_exception.mdx | 0 .../syntax-lookup/language_external.mdx | 0 .../markdown-pages}/syntax-lookup/language_for.mdx | 0 .../syntax-lookup/language_function.mdx | 0 .../syntax-lookup/language_if_else.mdx | 0 .../syntax-lookup/language_include.mdx | 0 .../syntax-lookup/language_jsx_component.mdx | 0 .../syntax-lookup/language_labeled_argument.mdx | 0 .../markdown-pages}/syntax-lookup/language_let.mdx | 0 .../syntax-lookup/language_let_rec.mdx | 0 .../syntax-lookup/language_line_comment.mdx | 0 .../syntax-lookup/language_module.mdx | 0 .../syntax-lookup/language_module_function.mdx | 0 .../syntax-lookup/language_module_type.mdx | 0 .../syntax-lookup/language_module_type_of.mdx | 0 .../syntax-lookup/language_object_type.mdx | 0 .../markdown-pages}/syntax-lookup/language_open.mdx | 0 .../syntax-lookup/language_open_object_type.mdx | 0 .../language_optional_labeled_argument.mdx | 0 .../language_optional_record_field.mdx | 0 .../syntax-lookup/language_or_pattern.mdx | 0 .../syntax-lookup/language_placeholder.mdx | 0 .../syntax-lookup/language_polyvar.mdx | 0 .../syntax-lookup/language_record_type.mdx | 0 .../markdown-pages}/syntax-lookup/language_ref.mdx | 0 .../syntax-lookup/language_regular_expression.mdx | 0 .../language_scoped_polymorphic_type.mdx | 0 .../syntax-lookup/language_spreads.mdx | 0 .../syntax-lookup/language_string_interpolation.mdx | 0 .../syntax-lookup/language_string_literal.mdx | 0 .../syntax-lookup/language_switch.mdx | 0 .../syntax-lookup/language_ternary.mdx | 0 .../markdown-pages}/syntax-lookup/language_type.mdx | 0 .../syntax-lookup/language_type_parameter.mdx | 0 .../syntax-lookup/language_type_rec.mdx | 0 .../syntax-lookup/language_variant_type.mdx | 0 .../syntax-lookup/language_while.mdx | 0 .../syntax-lookup/operator_ref_value_assignment.mdx | 0 .../syntax-lookup/operators_bitwise_and.mdx | 0 .../syntax-lookup/operators_bitwise_not.mdx | 0 .../syntax-lookup/operators_bitwise_or.mdx | 0 .../syntax-lookup/operators_bitwise_xor.mdx | 0 .../syntax-lookup/operators_boolean_and.mdx | 0 .../syntax-lookup/operators_boolean_not.mdx | 0 .../syntax-lookup/operators_boolean_or.mdx | 0 .../syntax-lookup/operators_exponentiation.mdx | 0 .../syntax-lookup/operators_float_addition.mdx | 0 .../syntax-lookup/operators_float_division.mdx | 0 .../operators_float_multiplication.mdx | 0 .../syntax-lookup/operators_float_subtraction.mdx | 0 .../syntax-lookup/operators_integer_addition.mdx | 0 .../syntax-lookup/operators_integer_division.mdx | 0 .../operators_integer_multiplication.mdx | 0 .../syntax-lookup/operators_integer_subtraction.mdx | 0 .../markdown-pages}/syntax-lookup/operators_mod.mdx | 0 .../syntax-lookup/operators_pipe.mdx | 0 .../syntax-lookup/operators_shift_left.mdx | 0 .../syntax-lookup/operators_shift_right.mdx | 0 .../operators_shift_right_unsigned.mdx | 0 .../operators_string_concatenation.mdx | 0 .../syntax-lookup/operators_type_coercion.mdx | 0 .../syntax-lookup/specialvalues_file.mdx | 0 .../syntax-lookup/specialvalues_line.mdx | 0 .../syntax-lookup/specialvalues_line_of.mdx | 0 .../syntax-lookup/specialvalues_loc.mdx | 0 .../syntax-lookup/specialvalues_loc_of.mdx | 0 .../syntax-lookup/specialvalues_module.mdx | 0 .../syntax-lookup/specialvalues_pos.mdx | 0 .../syntax-lookup/specialvalues_pos_of.mdx | 0 {plugins => apps/docs/plugins}/cm6-reason-mode.js | 0 .../docs/plugins}/reason-highlightjs.js | 0 .../docs/public}/Art-3-rescript-launch.avif | Bin {public => apps/docs/public}/_redirects | 0 {public => apps/docs/public}/blog/.gitkeep | 0 .../docs/public}/blog/archive/label-error.avif | Bin .../public}/blog/archive/playground-mockup.avif | Bin .../docs/public}/blog/archive/poly-error.avif | Bin .../archive/reasonml-org-color-palette-retina.avif | Bin .../blog/archive/reasonml-org-structure-retina.avif | Bin .../docs/public}/blog/archive/recursive-error.avif | Bin .../docs/public}/blog/archive/recursive.avif | Bin .../docs/public}/blog/archive/search-mockup.avif | Bin .../state-of-reasonml-2020-q2-pt2-articleimg.avif | Bin .../blog/archive/state-of-reasonml-org-q2-2020.avif | Bin .../blog/archive/state-of-reasonml-pt1-hero.avif | Bin .../archive/state-of-reasonml-q1-2020-card.avif | Bin .../docs/public}/blog/archive/uncurry-label.avif | Bin .../blog/archive/youtube-search-reasonml.avif | Bin .../docs/public}/blog/compiler_release_11_0.avif | Bin .../docs/public}/blog/compiler_release_11_1.avif | Bin .../docs/public}/blog/compiler_release_12_0.webp | Bin .../public}/blog/compiler_release_12_0_article.webp | Bin .../docs/public}/blog/compiler_release_9_0.avif | Bin .../docs/public}/blog/compiler_release_9_1.avif | Bin .../docs/public}/blog/editor_support_article.avif | Bin .../docs/public}/blog/editor_support_preview.avif | Bin {public => apps/docs/public}/blog/grid_0.avif | Bin .../docs/public}/blog/landing_page_figma.avif | Bin .../public}/blog/reactive-analysis/fixpoint.mmd | 0 .../public}/blog/reactive-analysis/fixpoint.svg | 0 .../reactive-analysis/reactive-pipeline-simple.mmd | 0 .../reactive-analysis/reactive-pipeline-simple.svg | 0 .../blog/rescript-12-reforging-build-system.webp | Bin .../public}/blog/rescript-launch/ReScript-1.avif | Bin .../public}/blog/rescript-launch/ReScript-2.avif | Bin .../public}/blog/rescript-launch/ReScript-3.avif | Bin .../public}/blog/rescript-launch/ReScript-4.avif | Bin .../docs/public}/blog/rescript-retreat-2025.webp | Bin .../docs/public}/blog/rescript-team-2025.webp | Bin .../public}/blog/rescript_assoc_rename_preview.avif | Bin .../docs/public}/blog/rescript_retreat_2024.avif | Bin .../blog/rescript_retreat_2024_group_work.avif | Bin .../blog/rescript_retreat_2024_talk_parser.avif | Bin .../public}/blog/rescript_retreat_2024_winery.avif | Bin ...lowing_54e33c58-aa14-4f1d-8249-dae636dfc0e9.avif | Bin .../docs/public}/brand/rescript-brandmark.avif | Bin .../docs/public}/brand/rescript-brandmark.svg | 0 .../docs/public}/brand/rescript-logo-white.avif | Bin .../docs/public}/brand/rescript-logo-white.svg | 0 .../docs/public}/brand/rescript-logo.avif | Bin .../docs/public}/brand/rescript-logo.svg | 0 {public => apps/docs/public}/docson/box.html | 0 {public => apps/docs/public}/docson/signature.html | 0 {public => apps/docs/public}/favicon.ico | Bin .../public}/favicon/android-chrome-192x192.avif | Bin .../public}/favicon/android-chrome-512x512.avif | Bin .../docs/public}/favicon/apple-touch-icon.avif | Bin .../docs/public}/favicon/favicon-16x16.avif | Bin .../docs/public}/favicon/favicon-32x32.avif | Bin .../docs/public}/favicon/site.webmanifest | 0 .../docs/public}/fonts/roboto-mono-400.woff2 | Bin .../docs/public}/fonts/roboto-mono-700.woff2 | Bin .../docs/public}/fonts/subset-Inter-Bold.woff2 | Bin .../docs/public}/fonts/subset-Inter-Italic.woff2 | Bin .../docs/public}/fonts/subset-Inter-Medium.woff2 | Bin .../docs/public}/fonts/subset-Inter-Regular.woff2 | Bin .../docs/public}/fonts/subset-Inter-SemiBold.woff2 | Bin {public => apps/docs/public}/hero.avif | Bin {public => apps/docs/public}/hyperlink.svg | 0 {public => apps/docs/public}/ic_gentype@2x.avif | Bin {public => apps/docs/public}/ic_manual@2x.avif | Bin {public => apps/docs/public}/ic_package.svg | 0 {public => apps/docs/public}/ic_reanalyze@2x.avif | Bin .../docs/public}/ic_rescript_react@2x.avif | Bin {public => apps/docs/public}/ic_search.svg | 0 {public => apps/docs/public}/ic_sidebar_drawer.svg | 0 .../docs/public}/illu_index_rescript@2x.avif | Bin {public => apps/docs/public}/img/bstracing.avif | Bin .../docs/public}/img/debugger-after.avif | Bin .../docs/public}/img/debugger-before.avif | Bin .../docs/public}/img/debugger-inspector.avif | Bin .../docs/public}/img/landing_page_figma.avif | Bin .../docs/public}/llms/manual/template.mdx | 0 .../docs/public}/llms/manual/template.txt | 0 .../docs/public}/llms/react/template.mdx | 0 .../docs/public}/llms/react/template.txt | 0 {public => apps/docs/public}/lp/aivero.svg | 0 {public => apps/docs/public}/lp/arizon.svg | 0 {public => apps/docs/public}/lp/bandprotocol.svg | 0 {public => apps/docs/public}/lp/bettercart.svg | 0 {public => apps/docs/public}/lp/bettervim.svg | 0 {public => apps/docs/public}/lp/camelo.svg | 0 {public => apps/docs/public}/lp/cardoc.svg | 0 {public => apps/docs/public}/lp/carla.svg | 0 {public => apps/docs/public}/lp/cca-io-color.svg | 0 {public => apps/docs/public}/lp/cca-io.svg | 0 .../docs/public}/lp/collectiveaudience.svg | 0 {public => apps/docs/public}/lp/community-1.avif | Bin {public => apps/docs/public}/lp/community-2.avif | Bin {public => apps/docs/public}/lp/community-3.avif | Bin {public => apps/docs/public}/lp/darklang.svg | 0 {public => apps/docs/public}/lp/dev-it-jobs.svg | 0 {public => apps/docs/public}/lp/draftbit.svg | 0 .../docs/public}/lp/easy-to-unadopt.avif | Bin .../docs/public}/lp/editor-tooling-1.avif | Bin {public => apps/docs/public}/lp/envio.svg | 0 .../docs/public}/lp/fast-build-preview.avif | Bin {public => apps/docs/public}/lp/frontman.svg | 0 {public => apps/docs/public}/lp/greenlabs.svg | 0 {public => apps/docs/public}/lp/grid.svg | 0 {public => apps/docs/public}/lp/grid2.svg | 0 {public => apps/docs/public}/lp/humaans.svg | 0 {public => apps/docs/public}/lp/illu_left.avif | Bin {public => apps/docs/public}/lp/illu_right.avif | Bin {public => apps/docs/public}/lp/instapainting.avif | Bin .../docs/public}/lp/interop-example-preview.avif | Bin {public => apps/docs/public}/lp/juspay.svg | 0 {public => apps/docs/public}/lp/komplio.svg | 0 {public => apps/docs/public}/lp/maker.svg | 0 {public => apps/docs/public}/lp/nomadic_labs.svg | 0 {public => apps/docs/public}/lp/ohne-makler.svg | 0 {public => apps/docs/public}/lp/porter.svg | 0 {public => apps/docs/public}/lp/pupilfirst.svg | 0 {public => apps/docs/public}/lp/reka_market.svg | 0 {public => apps/docs/public}/lp/resmume.svg | 0 {public => apps/docs/public}/lp/rohea.svg | 0 {public => apps/docs/public}/lp/sanjagh.svg | 0 .../docs/public}/lp/seamonster-studios.svg | 0 {public => apps/docs/public}/lp/sensonomic.svg | 0 {public => apps/docs/public}/lp/silq.svg | 0 {public => apps/docs/public}/lp/stencil.svg | 0 {public => apps/docs/public}/lp/tiny.svg | 0 {public => apps/docs/public}/lp/travelworld.svg | 0 .../docs/public}/lp/type-better-preview.avif | Bin {public => apps/docs/public}/lp/walnut.svg | 0 {public => apps/docs/public}/lp/webcurate.svg | 0 {public => apps/docs/public}/lp/wino.svg | 0 {public => apps/docs/public}/lp/ybru.svg | 0 .../docs/public}/messenger-logo-64@2x.avif | Bin {public => apps/docs/public}/nav-logo-full@2x.avif | Bin {public => apps/docs/public}/nav-logo@2x.avif | Bin .../docs/public}/nextjs_starter_logo.svg | 0 .../docs/public}/nodejs_starter_logo.svg | 0 {public => apps/docs/public}/og/try.avif | Bin {public => apps/docs/public}/partners/ahrefs.svg | 0 .../docs/public}/partners/tezos_foundation.svg | 0 {public => apps/docs/public}/pupilfirst-logo.avif | Bin .../docs/public}/rescript_logo_black.svg | 0 {public => apps/docs/public}/star.svg | 0 .../docs/public}/vitejs_starter_logo.avif | Bin .../docs/public}/vitejs_starter_logo.svg | 0 .../docs/public}/vitejs_starter_logo@2x.avif | Bin .../docs/react-router.config.mjs | 0 rescript.json => apps/docs/rescript.json | 0 .../docs/scripts}/__tests__/test-examples.test.mjs | 0 .../docs/scripts}/__tests__/test-runner.test.mjs | 0 {scripts => apps/docs/scripts}/extract-syntax.mjs | 0 {scripts => apps/docs/scripts}/figma-fetch.js | 0 {scripts => apps/docs/scripts}/gendocs.res | 0 {scripts => apps/docs/scripts}/generate_feed.res | 0 {scripts => apps/docs/scripts}/generate_llms.res | 0 {scripts => apps/docs/scripts}/markdown.js | 0 .../docs/scripts}/sync-playground-bundles.mjs | 0 {scripts => apps/docs/scripts}/sync-redirects.mjs | 0 {scripts => apps/docs/scripts}/test-examples.mjs | 0 {scripts => apps/docs/scripts}/test-hrefs.mjs | 0 {scripts => apps/docs/scripts}/test.mjs | 0 {scripts => apps/docs/scripts}/watch-tests.mjs | 0 {src => apps/docs/src}/bindings/Babel.res | 0 {src => apps/docs/src}/bindings/Cloudflare.res | 0 {src => apps/docs/src}/bindings/DocSearch.res | 0 {src => apps/docs/src}/bindings/Env.res | 0 {src => apps/docs/src}/bindings/Fuse.res | 0 {src => apps/docs/src}/bindings/HeadlessUI.res | 0 {src => apps/docs/src}/bindings/Jsdom.res | 0 {src => apps/docs/src}/bindings/Mdast.res | 0 {src => apps/docs/src}/bindings/Node.res | 0 {src => apps/docs/src}/bindings/ReactMarkdown.res | 0 {src => apps/docs/src}/bindings/ReactRouter.res | 0 {src => apps/docs/src}/bindings/Rehype.res | 0 {src => apps/docs/src}/bindings/Remark.res | 0 .../docs/src}/bindings/RescriptCompilerApi.res | 0 .../docs/src}/bindings/RescriptCompilerApi.resi | 0 {src => apps/docs/src}/bindings/Vitest.res | 0 {src => apps/docs/src}/common/Ansi.res | 0 {src => apps/docs/src}/common/Ansi.resi | 0 {src => apps/docs/src}/common/ColorTheme.res | 0 {src => apps/docs/src}/common/ColorTheme.resi | 0 {src => apps/docs/src}/common/Constants.res | 0 {src => apps/docs/src}/common/DateStr.res | 0 {src => apps/docs/src}/common/DateStr.resi | 0 .../docs/src}/common/EnableCollapsibleNavbar.res | 0 {src => apps/docs/src}/common/HighlightJs.res | 0 {src => apps/docs/src}/common/HighlightJs.resi | 0 {src => apps/docs/src}/common/Hooks.res | 0 {src => apps/docs/src}/common/MetaDescription.res | 0 {src => apps/docs/src}/common/Path.res | 0 {src => apps/docs/src}/common/ScrollLockContext.res | 0 {src => apps/docs/src}/common/Semver.res | 0 {src => apps/docs/src}/common/Semver.resi | 0 {src => apps/docs/src}/common/Url.res | 0 {src => apps/docs/src}/common/Url.resi | 0 {src => apps/docs/src}/common/Util.res | 0 {src => apps/docs/src}/common/Util.resi | 0 .../docs/src}/common/WarningFlagDescription.res | 0 .../docs/src}/common/WarningFlagDescription.resi | 0 {src => apps/docs/src}/components/AnsiPre.res | 0 {src => apps/docs/src}/components/AnsiPre.resi | 0 {src => apps/docs/src}/components/ApiIntro.res | 0 {src => apps/docs/src}/components/ApiIntro.resi | 0 {src => apps/docs/src}/components/ApiMarkdown.res | 0 {src => apps/docs/src}/components/ApiMarkdown.resi | 0 {src => apps/docs/src}/components/Banner.res | 0 {src => apps/docs/src}/components/BreadCrumbs.res | 0 {src => apps/docs/src}/components/Button.res | 0 {src => apps/docs/src}/components/Button.resi | 0 {src => apps/docs/src}/components/CodeExample.res | 0 {src => apps/docs/src}/components/CodeExample.resi | 0 {src => apps/docs/src}/components/CodeMirror.res | 0 {src => apps/docs/src}/components/CodeMirror.resi | 0 .../docs/src}/components/CodeMirrorSetup.js | 0 .../docs/src}/components/CommunityContent.res | 0 {src => apps/docs/src}/components/DocsSidebar.res | 0 {src => apps/docs/src}/components/Docson.res | 0 {src => apps/docs/src}/components/Docson.resi | 0 {src => apps/docs/src}/components/DocsonLazy.res | 0 {src => apps/docs/src}/components/Footer.res | 0 {src => apps/docs/src}/components/Footer.resi | 0 {src => apps/docs/src}/components/Icon.res | 0 {src => apps/docs/src}/components/Icon.resi | 0 {src => apps/docs/src}/components/Image.res | 0 {src => apps/docs/src}/components/ImageGallery.res | 0 {src => apps/docs/src}/components/Intro.res | 0 {src => apps/docs/src}/components/Markdown.res | 0 {src => apps/docs/src}/components/Markdown.resi | 0 .../docs/src}/components/MarkdownComponents.res | 0 {src => apps/docs/src}/components/MdxContent.res | 0 {src => apps/docs/src}/components/MdxContent.resi | 0 {src => apps/docs/src}/components/Meta.res | 0 {src => apps/docs/src}/components/Meta.resi | 0 .../docs/src}/components/NavbarMobileOverlay.res | 0 {src => apps/docs/src}/components/NavbarPrimary.res | 0 .../docs/src}/components/NavbarPrimary.resi | 0 .../docs/src}/components/NavbarSecondary.res | 0 .../docs/src}/components/NavbarTertiary.res | 0 {src => apps/docs/src}/components/NavbarUtils.res | 0 {src => apps/docs/src}/components/Navigation.res | 0 {src => apps/docs/src}/components/Navigation.resi | 0 {src => apps/docs/src}/components/Search.res | 0 {src => apps/docs/src}/components/SearchBox.res | 0 {src => apps/docs/src}/components/Tag.res | 0 {src => apps/docs/src}/components/Tag.resi | 0 {src => apps/docs/src}/components/Text.res | 0 {src => apps/docs/src}/components/Text.resi | 0 {src => apps/docs/src}/components/ToggleButton.res | 0 {src => apps/docs/src}/components/VersionSelect.res | 0 .../docs/src}/components/VersionSelect.resi | 0 {src => apps/docs/src}/components/Video.res | 0 {src => apps/docs/src}/components/WarningTable.res | 0 {src => apps/docs/src}/data/BlogApi.res | 0 {src => apps/docs/src}/data/BlogApi.resi | 0 {src => apps/docs/src}/data/BlogLoader.res | 0 {src => apps/docs/src}/data/CommunityResources.res | 0 {src => apps/docs/src}/data/MetaTagsApi.res | 0 {src => apps/docs/src}/data/OurUsers.res | 0 {src => apps/docs/src}/ffi/loadScript.js | 0 {src => apps/docs/src}/ffi/parse-numeric-range.js | 0 .../docs/src}/ffi/react-codemirror-hooks.js | 0 {src => apps/docs/src}/layouts/CommunityLayout.res | 0 {src => apps/docs/src}/layouts/DocsLayout.res | 0 {src => apps/docs/src}/layouts/MainLayout.res | 0 {src => apps/docs/src}/layouts/MainLayout.resi | 0 {src => apps/docs/src}/layouts/SidebarLayout.res | 0 {src => apps/docs/src}/layouts/SidebarLayout.resi | 0 {src => apps/docs/src}/markdown/BlogFrontmatter.res | 0 {src => apps/docs/src}/markdown/CompiledMdx.res | 0 {src => apps/docs/src}/markdown/CompiledMdx.resi | 0 {src => apps/docs/src}/markdown/DocFrontmatter.res | 0 {src => apps/docs/src}/markdown/DocFrontmatter.resi | 0 .../docs/src}/markdown/FrontmatterUtils.res | 0 {src => apps/docs/src}/markdown/MarkdownParser.res | 0 {src => apps/docs/src}/markdown/MarkdownParser.resi | 0 {src => apps/docs/src}/markdown/Mdx.res | 0 {src => apps/docs/src}/markdown/MdxFile.res | 0 {src => apps/docs/src}/markdown/MdxFile.resi | 0 {src => apps/docs/src}/markdown/MdxLegacy.res | 0 {src => apps/docs/src}/markdown/SidebarHelpers.res | 0 {src => apps/docs/src}/markdown/SidebarHelpers.resi | 0 {src => apps/docs/src}/markdown/TableOfContents.res | 0 {src => apps/docs/src}/markdown/TocUtils.res | 0 .../docs/src}/playground/CompilerManagerHook.res | 0 .../docs/src}/playground/CompilerManagerHook.resi | 0 {src => apps/docs/src}/playground/ConsolePanel.res | 0 {src => apps/docs/src}/playground/EvalIFrame.res | 0 {src => apps/docs/src}/playground/LzString.res | 0 {src => apps/docs/src}/playground/OutputPanel.res | 0 {src => apps/docs/src}/playground/Playground.res | 0 {src => apps/docs/src}/playground/Playground.resi | 0 .../docs/src}/playground/PlaygroundLazy.res | 0 {src => apps/docs/src}/playground/RenderPanel.res | 0 {src => apps/docs/src}/playground/RenderPanel.resi | 0 {src => apps/docs/src}/shims/Shims.res | 0 {src => apps/docs/src}/shims/_shims.mjs | 0 stdlib-toc.json => apps/docs/stdlib-toc.json | 0 {styles => apps/docs/styles}/_docsearch.css | 0 {styles => apps/docs/styles}/_fonts.css | 0 {styles => apps/docs/styles}/_hljs.css | 0 {styles => apps/docs/styles}/_markdown.css | 0 {styles => apps/docs/styles}/_theme.css | 0 {styles => apps/docs/styles}/docson.css | 0 {styles => apps/docs/styles}/main.css | 0 {styles => apps/docs/styles}/test-overrides.css | 0 {styles => apps/docs/styles}/utils.css | 0 vite.config.mjs => apps/docs/vite.config.mjs | 0 vitest.config.mjs => apps/docs/vitest.config.mjs | 0 vitest.setup.mjs => apps/docs/vitest.setup.mjs | 0 818 files changed, 0 insertions(+), 0 deletions(-) rename {__tests__ => apps/docs/__tests__}/ApiOverviewLayout_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/Banner_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/BlogArticle_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/Blog_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/Button_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/CodeExample_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/CommunityLayout_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/DocsLayout_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/DocsOverview_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/Footer_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/LandingPage_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/MainLayout_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/MarkdownComponents_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/MetaDescription_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/NavbarPrimary_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/NavbarSecondary_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/NavbarTertiary_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/SearchBox_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/SidebarLayout_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/SyntaxLookup_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/Tag_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/VersionSelect_.test.res (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/ApiLayout_.test.jsx/api-old-docs-warning-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/ApiLayout_.test.jsx/desktop-api-layout-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/ApiLayout_.test.jsx/mobile-api-layout-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-with-content-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/ApiOverviewLayout_.test.jsx/mobile-api-overview-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/ApiOverviewLayout_.test.jsx/tablet-api-overview-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Banner_.test.jsx/banner-with-content-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Banner_.test.jsx/mobile-banner-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-archived-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-coauthors-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-no-description-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-image-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/BlogArticle_.test.jsx/mobile-blog-article-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Blog_.test.jsx/desktop-blog-archived-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Blog_.test.jsx/desktop-blog-category-selector-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Blog_.test.jsx/desktop-blog-empty-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Blog_.test.jsx/desktop-blog-index-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Blog_.test.jsx/desktop-blog-single-post-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Blog_.test.jsx/mobile-blog-index-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Button_.test.jsx/button-primary-blue-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Button_.test.jsx/button-primary-red-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Button_.test.jsx/button-secondary-red-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Button_.test.jsx/button-small-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/CodeExample_.test.jsx/code-example-highlighted-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/CodeExample_.test.jsx/code-example-no-label-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/CodeExample_.test.jsx/code-example-rescript-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/CodeExample_.test.jsx/code-toggle-js-selected-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/CodeExample_.test.jsx/code-toggle-tabs-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-multi-categories-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/CommunityLayout_.test.jsx/mobile-community-layout-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/CommunityLayout_.test.jsx/tablet-community-layout-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-active-item-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-pagination-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-with-toc-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/DocsLayout_.test.jsx/mobile-docs-layout-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-ecosystem-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/DocsOverview_.test.jsx/mobile-docs-overview-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Footer_.test.jsx/desktop-footer-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Footer_.test.jsx/mobile-footer-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MainLayout_.test.jsx/desktop-main-layout-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MainLayout_.test.jsx/mobile-main-layout-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-admonitions-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-anchor-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-blockquote-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-no-author-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-headings-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-hr-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-small-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-inline-code-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-links-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-lists-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-nested-lists-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-strong-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-table-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/MarkdownComponents_.test.jsx/markdown-text-elements-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/NavbarPrimary_.test.jsx/tablet-navbar-primary-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-react-active-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/NavbarSecondary_.test.jsx/mobile-navbar-secondary-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/NavbarTertiary_.test.jsx/desktop-navbar-tertiary-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/NavbarTertiary_.test.jsx/mobile-navbar-tertiary-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SearchBox_.test.jsx/searchbox-empty-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SearchBox_.test.jsx/searchbox-with-value-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-deep-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-active-item-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-many-items-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-toc-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-active-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-deprecated-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-pipe-detail-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-active-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Tag_.test.jsx/tag-subtle-chromium-linux.png (100%) rename {__tests__ => apps/docs/__tests__}/__screenshots__/Tag_.test.jsx/tags-multiple-chromium-linux.png (100%) rename {app => apps/docs/app}/layouts/DocsLayoutRoute.res (100%) rename {app => apps/docs/app}/layouts/DocsLayoutRoute.resi (100%) rename {app => apps/docs/app}/root.res (100%) rename {app => apps/docs/app}/root.resi (100%) rename {app => apps/docs/app}/routes.res (100%) rename {app => apps/docs/app}/routes.resi (100%) rename {app => apps/docs/app}/routes/ApiDocs.res (100%) rename {app => apps/docs/app}/routes/ApiOverviewRoute.res (100%) rename {app => apps/docs/app}/routes/ApiOverviewRoute.resi (100%) rename {app => apps/docs/app}/routes/ApiRoute.res (100%) rename {app => apps/docs/app}/routes/ApiRoute.resi (100%) rename {app => apps/docs/app}/routes/Blog.res (100%) rename {app => apps/docs/app}/routes/BlogArticle.res (100%) rename {app => apps/docs/app}/routes/BlogArticle.resi (100%) rename {app => apps/docs/app}/routes/BlogArticleRoute.res (100%) rename {app => apps/docs/app}/routes/BlogArticleRoute.resi (100%) rename {app => apps/docs/app}/routes/BlogRoute.res (100%) rename {app => apps/docs/app}/routes/BlogRoute.resi (100%) rename {app => apps/docs/app}/routes/CommunityRoute.res (100%) rename {app => apps/docs/app}/routes/CommunityRoute.resi (100%) rename {app => apps/docs/app}/routes/DocsGuidelinesRoute.res (100%) rename {app => apps/docs/app}/routes/DocsGuidelinesRoute.resi (100%) rename {app => apps/docs/app}/routes/DocsManualRoute.res (100%) rename {app => apps/docs/app}/routes/DocsManualRoute.resi (100%) rename {app => apps/docs/app}/routes/DocsOverview.res (100%) rename {app => apps/docs/app}/routes/DocsOverview.resi (100%) rename {app => apps/docs/app}/routes/DocsReactRoute.res (100%) rename {app => apps/docs/app}/routes/DocsReactRoute.resi (100%) rename {app => apps/docs/app}/routes/LandingPage.res (100%) rename {app => apps/docs/app}/routes/LandingPage.resi (100%) rename {app => apps/docs/app}/routes/LandingPageRoute.res (100%) rename {app => apps/docs/app}/routes/LandingPageRoute.resi (100%) rename {app => apps/docs/app}/routes/NotFoundRoute.res (100%) rename {app => apps/docs/app}/routes/NotFoundRoute.resi (100%) rename {app => apps/docs/app}/routes/Packages.res (100%) rename {app => apps/docs/app}/routes/Packages.resi (100%) rename {app => apps/docs/app}/routes/PackagesRoute.res (100%) rename {app => apps/docs/app}/routes/PackagesRoute.resi (100%) rename {app => apps/docs/app}/routes/SyntaxLookup.res (100%) rename {app => apps/docs/app}/routes/SyntaxLookupDetailRoute.res (100%) rename {app => apps/docs/app}/routes/SyntaxLookupDetailRoute.resi (100%) rename {app => apps/docs/app}/routes/SyntaxLookupRoute.res (100%) rename {app => apps/docs/app}/routes/SyntaxLookupRoute.resi (100%) rename {app => apps/docs/app}/routes/TryRoute.res (100%) rename {app => apps/docs/app}/routes/TryRoute.resi (100%) rename {compilers => apps/docs/compilers}/README.md (100%) rename {compilers => apps/docs/compilers}/dummy/Dummy.res (100%) rename {compilers => apps/docs/compilers}/package-lock.json (100%) rename {compilers => apps/docs/compilers}/package.json (100%) rename {compilers => apps/docs/compilers}/rescript.json (100%) rename cypress.config.mjs => apps/docs/cypress.config.mjs (100%) rename {cypress => apps/docs/cypress}/support/e2e.js (100%) rename {data => apps/docs/data}/api/v12.0.0/belt.json (100%) rename {data => apps/docs/data}/api/v12.0.0/dom.json (100%) rename {data => apps/docs/data}/api/v12.0.0/js.json (100%) rename {data => apps/docs/data}/api/v12.0.0/stdlib.json (100%) rename {data => apps/docs/data}/api/v12.0.0/toc_tree.json (100%) rename {data => apps/docs/data}/api/v12.0.1/belt.json (100%) rename {data => apps/docs/data}/api/v12.0.1/dom.json (100%) rename {data => apps/docs/data}/api/v12.0.1/js.json (100%) rename {data => apps/docs/data}/api/v12.0.1/stdlib.json (100%) rename {data => apps/docs/data}/api/v12.0.1/toc_tree.json (100%) rename {data => apps/docs/data}/api/v12.0.2/belt.json (100%) rename {data => apps/docs/data}/api/v12.0.2/dom.json (100%) rename {data => apps/docs/data}/api/v12.0.2/js.json (100%) rename {data => apps/docs/data}/api/v12.0.2/stdlib.json (100%) rename {data => apps/docs/data}/api/v12.0.2/toc_tree.json (100%) rename {data => apps/docs/data}/api/v12.1.0/belt.json (100%) rename {data => apps/docs/data}/api/v12.1.0/dom.json (100%) rename {data => apps/docs/data}/api/v12.1.0/js.json (100%) rename {data => apps/docs/data}/api/v12.1.0/stdlib.json (100%) rename {data => apps/docs/data}/api/v12.1.0/toc_tree.json (100%) rename {data => apps/docs/data}/api/v12.2.0/belt.json (100%) rename {data => apps/docs/data}/api/v12.2.0/dom.json (100%) rename {data => apps/docs/data}/api/v12.2.0/js.json (100%) rename {data => apps/docs/data}/api/v12.2.0/stdlib.json (100%) rename {data => apps/docs/data}/api/v12.2.0/toc_tree.json (100%) rename {data => apps/docs/data}/api/v13.0.0/belt.json (100%) rename {data => apps/docs/data}/api/v13.0.0/dom.json (100%) rename {data => apps/docs/data}/api/v13.0.0/js.json (100%) rename {data => apps/docs/data}/api/v13.0.0/stdlib.json (100%) rename {data => apps/docs/data}/api/v13.0.0/toc_tree.json (100%) rename {e2e => apps/docs/e2e}/Navigation.cy.res (100%) rename {e2e => apps/docs/e2e}/Playground.cy.res (100%) rename {e2e => apps/docs/e2e}/bindings/Cy.res (100%) rename {functions => apps/docs/functions}/ogimage/[[path]]/index.png.res (100%) rename generate-route-types.mjs => apps/docs/generate-route-types.mjs (100%) rename image-converter.config.mjs => apps/docs/image-converter.config.mjs (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/a-note-on-bucklescripts-future-commitments.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/a-small-step-for-bucklescript.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/a-story-of-exception-encoding.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/a-story-of-lazy-encoding.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/another-encoding.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/arity-zero.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-8-1-new-syntax.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-1-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-1-4-2.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-1-4-3.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-1-5-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-1-5-1.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-1-5-2.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-1-7-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-1-7-4.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-1-7-5.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-3-0-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-3-1-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-3-1-4.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-4-0-0-pt1.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-4-0-0-pt2.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-4-0-17.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-4-0-8.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-5-0-1.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-5-0-4.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-5-0-5.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-5-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-5-1-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-5-2-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-6-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-7-0-2.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-7-1-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-7-2.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-7-3.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-8-1-1.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-release-8-2.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/bucklescript-roadmap-q3-4-2018.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/feature-preview-variadic.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/ffi-overview.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/generalize-uncurry.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/loading-stdlib-in-memory.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/overview-of-new_encoding.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/scalable.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/state-of-reasonml-org-2020-q2-pt1.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/state-of-reasonml-org-2020-q2-pt2.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/state-of-reasonml-org-2020-q2-pt3.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/state-of-reasonml-org-2020-q2-pt4.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/string-literal-types-in-reason.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/union-types-in-bucklescript.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/whats-new-in-7-pt1.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/archived/whats-new-in-7-pt2.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/bucklescript-is-rebranding.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/editor-support-custom-operators-and-more.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/editor-support-release-1-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/enhanced-ergonomics-for-record-types.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/first-class-dynamic-import-support.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/improving-interop.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/introducing-unified-operators.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/new-rescript-logo.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/reactive-analysis.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/reforging-build-system.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-10-0-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-10-1.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-11-0-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-11-1-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-12-0-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-8-3-2.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-8-3.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-8-4.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-9-0.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/release-9-1.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/rescript-association-rebranding.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/retreats.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/roadmap-2021-and-new-landing-page.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/uncurried-mode.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/blog/what-can-i-do-with-rescript.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/community/code-of-conduct.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/community/content.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/community/overview.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/community/roadmap.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/community/translations.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/api/belt.json (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/api/dom.json (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/api/js.json (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/api/stdlib.json (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/api/toc_tree.json (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/guidelines/publishing-packages.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/api.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/array-and-list.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/async-await.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/attribute.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/bind-to-global-js-values.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/bind-to-js-function.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/bind-to-js-object.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/build-configuration-schema.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/build-configuration.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/build-monorepo-setup.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/build-overview.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/build-performance.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/control-flow.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/converting-from-js.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/dict.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/editor-code-analysis.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/editor-plugins.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/embed-raw-javascript.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/equality-comparison.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/exception.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/extensible-variant.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/external.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/function.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/generalized-algebraic-data-types.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/generate-converters-accessors.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/import-export.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/import-from-export-to-js.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/inlining-constants.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/installation.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/interop-cheatsheet.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/interop-with-js-build-systems.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/introduction.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/json.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/jsx.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/lazy-values.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/let-binding.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/libraries.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/llms.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/migrate-to-v11.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/migrate-to-v12.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/module-functions.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/module.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/mutation.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/null-undefined-option.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/object.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/overview.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/pattern-matching-destructuring.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/pipe.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/polymorphic-variant.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/primitive-types.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/project-structure.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/promise.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/record.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/rescript-for-javascript-developers.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/reserved-keywords.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/scoped-polymorphic-types.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/shared-data-types.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/tagged-templates.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/try.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/tuple.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/type.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/typescript-integration.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/use-illegal-identifier-names.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/variant.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/manual/warning-numbers.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/arrays-and-keys.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/beyond-jsx.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/components-and-props.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/context.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/elements-and-jsx.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/events.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/extensions-of-props.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/forwarding-refs.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/hooks-context.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/hooks-custom.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/hooks-effect.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/hooks-overview.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/hooks-reducer.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/hooks-ref.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/hooks-state.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/import-export-reactjs.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/installation.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/introduction.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/lazy-components.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/llms.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/memo.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/refs-and-the-dom.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/rendering-elements.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/router.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/server-components.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/docs/react/styling.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_as.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_dead.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_deriving.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_directive.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_does_not_raise.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_does_not_throw.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_expression_deprecated.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_expression_warning.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_gentype.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_get.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_get_index.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_ignore.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_inline.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_int.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_jsx_component.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_live.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_meth.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_module.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_module_deprecated.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_module_warning.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_new.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_not_undefined.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_raises.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_react_component.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_react_component_with_props.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_return.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_scope.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_send.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_send_pipe.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_set.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_set_index.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_string.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_tag.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_taggedTemplate.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_this.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_throws.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_unboxed.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_unwrap.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_val.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/decorator_variadic.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/extension_debugger.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/extension_identity.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/extension_private_let.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/extension_raw_expression.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/extension_raw_top_level_expression.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/extension_regular_expression.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/extension_todo.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_and.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_async.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_attached_doc_comment.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_await.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_block_comment.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_char_literal.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_covariant_type_parameter.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_dict.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_doc_comment.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_empty_object_type.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_exception.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_external.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_for.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_function.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_if_else.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_include.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_jsx_component.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_labeled_argument.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_let.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_let_rec.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_line_comment.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_module.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_module_function.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_module_type.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_module_type_of.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_object_type.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_open.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_open_object_type.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_optional_labeled_argument.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_optional_record_field.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_or_pattern.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_placeholder.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_polyvar.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_record_type.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_ref.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_regular_expression.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_scoped_polymorphic_type.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_spreads.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_string_interpolation.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_string_literal.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_switch.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_ternary.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_type.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_type_parameter.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_type_rec.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_variant_type.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/language_while.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operator_ref_value_assignment.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_bitwise_and.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_bitwise_not.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_bitwise_or.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_bitwise_xor.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_boolean_and.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_boolean_not.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_boolean_or.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_exponentiation.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_float_addition.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_float_division.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_float_multiplication.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_float_subtraction.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_integer_addition.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_integer_division.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_integer_multiplication.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_integer_subtraction.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_mod.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_pipe.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_shift_left.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_shift_right.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_shift_right_unsigned.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_string_concatenation.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/operators_type_coercion.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/specialvalues_file.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/specialvalues_line.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/specialvalues_line_of.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/specialvalues_loc.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/specialvalues_loc_of.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/specialvalues_module.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/specialvalues_pos.mdx (100%) rename {markdown-pages => apps/docs/markdown-pages}/syntax-lookup/specialvalues_pos_of.mdx (100%) rename {plugins => apps/docs/plugins}/cm6-reason-mode.js (100%) rename {plugins => apps/docs/plugins}/reason-highlightjs.js (100%) rename {public => apps/docs/public}/Art-3-rescript-launch.avif (100%) rename {public => apps/docs/public}/_redirects (100%) rename {public => apps/docs/public}/blog/.gitkeep (100%) rename {public => apps/docs/public}/blog/archive/label-error.avif (100%) rename {public => apps/docs/public}/blog/archive/playground-mockup.avif (100%) rename {public => apps/docs/public}/blog/archive/poly-error.avif (100%) rename {public => apps/docs/public}/blog/archive/reasonml-org-color-palette-retina.avif (100%) rename {public => apps/docs/public}/blog/archive/reasonml-org-structure-retina.avif (100%) rename {public => apps/docs/public}/blog/archive/recursive-error.avif (100%) rename {public => apps/docs/public}/blog/archive/recursive.avif (100%) rename {public => apps/docs/public}/blog/archive/search-mockup.avif (100%) rename {public => apps/docs/public}/blog/archive/state-of-reasonml-2020-q2-pt2-articleimg.avif (100%) rename {public => apps/docs/public}/blog/archive/state-of-reasonml-org-q2-2020.avif (100%) rename {public => apps/docs/public}/blog/archive/state-of-reasonml-pt1-hero.avif (100%) rename {public => apps/docs/public}/blog/archive/state-of-reasonml-q1-2020-card.avif (100%) rename {public => apps/docs/public}/blog/archive/uncurry-label.avif (100%) rename {public => apps/docs/public}/blog/archive/youtube-search-reasonml.avif (100%) rename {public => apps/docs/public}/blog/compiler_release_11_0.avif (100%) rename {public => apps/docs/public}/blog/compiler_release_11_1.avif (100%) rename {public => apps/docs/public}/blog/compiler_release_12_0.webp (100%) rename {public => apps/docs/public}/blog/compiler_release_12_0_article.webp (100%) rename {public => apps/docs/public}/blog/compiler_release_9_0.avif (100%) rename {public => apps/docs/public}/blog/compiler_release_9_1.avif (100%) rename {public => apps/docs/public}/blog/editor_support_article.avif (100%) rename {public => apps/docs/public}/blog/editor_support_preview.avif (100%) rename {public => apps/docs/public}/blog/grid_0.avif (100%) rename {public => apps/docs/public}/blog/landing_page_figma.avif (100%) rename {public => apps/docs/public}/blog/reactive-analysis/fixpoint.mmd (100%) rename {public => apps/docs/public}/blog/reactive-analysis/fixpoint.svg (100%) rename {public => apps/docs/public}/blog/reactive-analysis/reactive-pipeline-simple.mmd (100%) rename {public => apps/docs/public}/blog/reactive-analysis/reactive-pipeline-simple.svg (100%) rename {public => apps/docs/public}/blog/rescript-12-reforging-build-system.webp (100%) rename {public => apps/docs/public}/blog/rescript-launch/ReScript-1.avif (100%) rename {public => apps/docs/public}/blog/rescript-launch/ReScript-2.avif (100%) rename {public => apps/docs/public}/blog/rescript-launch/ReScript-3.avif (100%) rename {public => apps/docs/public}/blog/rescript-launch/ReScript-4.avif (100%) rename {public => apps/docs/public}/blog/rescript-retreat-2025.webp (100%) rename {public => apps/docs/public}/blog/rescript-team-2025.webp (100%) rename {public => apps/docs/public}/blog/rescript_assoc_rename_preview.avif (100%) rename {public => apps/docs/public}/blog/rescript_retreat_2024.avif (100%) rename {public => apps/docs/public}/blog/rescript_retreat_2024_group_work.avif (100%) rename {public => apps/docs/public}/blog/rescript_retreat_2024_talk_parser.avif (100%) rename {public => apps/docs/public}/blog/rescript_retreat_2024_winery.avif (100%) rename {public => apps/docs/public}/blog/wizard_typing_on_a_keyboard_in_a_sea_of_lava_flowing_54e33c58-aa14-4f1d-8249-dae636dfc0e9.avif (100%) rename {public => apps/docs/public}/brand/rescript-brandmark.avif (100%) rename {public => apps/docs/public}/brand/rescript-brandmark.svg (100%) rename {public => apps/docs/public}/brand/rescript-logo-white.avif (100%) rename {public => apps/docs/public}/brand/rescript-logo-white.svg (100%) rename {public => apps/docs/public}/brand/rescript-logo.avif (100%) rename {public => apps/docs/public}/brand/rescript-logo.svg (100%) rename {public => apps/docs/public}/docson/box.html (100%) rename {public => apps/docs/public}/docson/signature.html (100%) rename {public => apps/docs/public}/favicon.ico (100%) rename {public => apps/docs/public}/favicon/android-chrome-192x192.avif (100%) rename {public => apps/docs/public}/favicon/android-chrome-512x512.avif (100%) rename {public => apps/docs/public}/favicon/apple-touch-icon.avif (100%) rename {public => apps/docs/public}/favicon/favicon-16x16.avif (100%) rename {public => apps/docs/public}/favicon/favicon-32x32.avif (100%) rename {public => apps/docs/public}/favicon/site.webmanifest (100%) rename {public => apps/docs/public}/fonts/roboto-mono-400.woff2 (100%) rename {public => apps/docs/public}/fonts/roboto-mono-700.woff2 (100%) rename {public => apps/docs/public}/fonts/subset-Inter-Bold.woff2 (100%) rename {public => apps/docs/public}/fonts/subset-Inter-Italic.woff2 (100%) rename {public => apps/docs/public}/fonts/subset-Inter-Medium.woff2 (100%) rename {public => apps/docs/public}/fonts/subset-Inter-Regular.woff2 (100%) rename {public => apps/docs/public}/fonts/subset-Inter-SemiBold.woff2 (100%) rename {public => apps/docs/public}/hero.avif (100%) rename {public => apps/docs/public}/hyperlink.svg (100%) rename {public => apps/docs/public}/ic_gentype@2x.avif (100%) rename {public => apps/docs/public}/ic_manual@2x.avif (100%) rename {public => apps/docs/public}/ic_package.svg (100%) rename {public => apps/docs/public}/ic_reanalyze@2x.avif (100%) rename {public => apps/docs/public}/ic_rescript_react@2x.avif (100%) rename {public => apps/docs/public}/ic_search.svg (100%) rename {public => apps/docs/public}/ic_sidebar_drawer.svg (100%) rename {public => apps/docs/public}/illu_index_rescript@2x.avif (100%) rename {public => apps/docs/public}/img/bstracing.avif (100%) rename {public => apps/docs/public}/img/debugger-after.avif (100%) rename {public => apps/docs/public}/img/debugger-before.avif (100%) rename {public => apps/docs/public}/img/debugger-inspector.avif (100%) rename {public => apps/docs/public}/img/landing_page_figma.avif (100%) rename {public => apps/docs/public}/llms/manual/template.mdx (100%) rename {public => apps/docs/public}/llms/manual/template.txt (100%) rename {public => apps/docs/public}/llms/react/template.mdx (100%) rename {public => apps/docs/public}/llms/react/template.txt (100%) rename {public => apps/docs/public}/lp/aivero.svg (100%) rename {public => apps/docs/public}/lp/arizon.svg (100%) rename {public => apps/docs/public}/lp/bandprotocol.svg (100%) rename {public => apps/docs/public}/lp/bettercart.svg (100%) rename {public => apps/docs/public}/lp/bettervim.svg (100%) rename {public => apps/docs/public}/lp/camelo.svg (100%) rename {public => apps/docs/public}/lp/cardoc.svg (100%) rename {public => apps/docs/public}/lp/carla.svg (100%) rename {public => apps/docs/public}/lp/cca-io-color.svg (100%) rename {public => apps/docs/public}/lp/cca-io.svg (100%) rename {public => apps/docs/public}/lp/collectiveaudience.svg (100%) rename {public => apps/docs/public}/lp/community-1.avif (100%) rename {public => apps/docs/public}/lp/community-2.avif (100%) rename {public => apps/docs/public}/lp/community-3.avif (100%) rename {public => apps/docs/public}/lp/darklang.svg (100%) rename {public => apps/docs/public}/lp/dev-it-jobs.svg (100%) rename {public => apps/docs/public}/lp/draftbit.svg (100%) rename {public => apps/docs/public}/lp/easy-to-unadopt.avif (100%) rename {public => apps/docs/public}/lp/editor-tooling-1.avif (100%) rename {public => apps/docs/public}/lp/envio.svg (100%) rename {public => apps/docs/public}/lp/fast-build-preview.avif (100%) rename {public => apps/docs/public}/lp/frontman.svg (100%) rename {public => apps/docs/public}/lp/greenlabs.svg (100%) rename {public => apps/docs/public}/lp/grid.svg (100%) rename {public => apps/docs/public}/lp/grid2.svg (100%) rename {public => apps/docs/public}/lp/humaans.svg (100%) rename {public => apps/docs/public}/lp/illu_left.avif (100%) rename {public => apps/docs/public}/lp/illu_right.avif (100%) rename {public => apps/docs/public}/lp/instapainting.avif (100%) rename {public => apps/docs/public}/lp/interop-example-preview.avif (100%) rename {public => apps/docs/public}/lp/juspay.svg (100%) rename {public => apps/docs/public}/lp/komplio.svg (100%) rename {public => apps/docs/public}/lp/maker.svg (100%) rename {public => apps/docs/public}/lp/nomadic_labs.svg (100%) rename {public => apps/docs/public}/lp/ohne-makler.svg (100%) rename {public => apps/docs/public}/lp/porter.svg (100%) rename {public => apps/docs/public}/lp/pupilfirst.svg (100%) rename {public => apps/docs/public}/lp/reka_market.svg (100%) rename {public => apps/docs/public}/lp/resmume.svg (100%) rename {public => apps/docs/public}/lp/rohea.svg (100%) rename {public => apps/docs/public}/lp/sanjagh.svg (100%) rename {public => apps/docs/public}/lp/seamonster-studios.svg (100%) rename {public => apps/docs/public}/lp/sensonomic.svg (100%) rename {public => apps/docs/public}/lp/silq.svg (100%) rename {public => apps/docs/public}/lp/stencil.svg (100%) rename {public => apps/docs/public}/lp/tiny.svg (100%) rename {public => apps/docs/public}/lp/travelworld.svg (100%) rename {public => apps/docs/public}/lp/type-better-preview.avif (100%) rename {public => apps/docs/public}/lp/walnut.svg (100%) rename {public => apps/docs/public}/lp/webcurate.svg (100%) rename {public => apps/docs/public}/lp/wino.svg (100%) rename {public => apps/docs/public}/lp/ybru.svg (100%) rename {public => apps/docs/public}/messenger-logo-64@2x.avif (100%) rename {public => apps/docs/public}/nav-logo-full@2x.avif (100%) rename {public => apps/docs/public}/nav-logo@2x.avif (100%) rename {public => apps/docs/public}/nextjs_starter_logo.svg (100%) rename {public => apps/docs/public}/nodejs_starter_logo.svg (100%) rename {public => apps/docs/public}/og/try.avif (100%) rename {public => apps/docs/public}/partners/ahrefs.svg (100%) rename {public => apps/docs/public}/partners/tezos_foundation.svg (100%) rename {public => apps/docs/public}/pupilfirst-logo.avif (100%) rename {public => apps/docs/public}/rescript_logo_black.svg (100%) rename {public => apps/docs/public}/star.svg (100%) rename {public => apps/docs/public}/vitejs_starter_logo.avif (100%) rename {public => apps/docs/public}/vitejs_starter_logo.svg (100%) rename {public => apps/docs/public}/vitejs_starter_logo@2x.avif (100%) rename react-router.config.mjs => apps/docs/react-router.config.mjs (100%) rename rescript.json => apps/docs/rescript.json (100%) rename {scripts => apps/docs/scripts}/__tests__/test-examples.test.mjs (100%) rename {scripts => apps/docs/scripts}/__tests__/test-runner.test.mjs (100%) rename {scripts => apps/docs/scripts}/extract-syntax.mjs (100%) rename {scripts => apps/docs/scripts}/figma-fetch.js (100%) rename {scripts => apps/docs/scripts}/gendocs.res (100%) rename {scripts => apps/docs/scripts}/generate_feed.res (100%) rename {scripts => apps/docs/scripts}/generate_llms.res (100%) rename {scripts => apps/docs/scripts}/markdown.js (100%) rename {scripts => apps/docs/scripts}/sync-playground-bundles.mjs (100%) rename {scripts => apps/docs/scripts}/sync-redirects.mjs (100%) rename {scripts => apps/docs/scripts}/test-examples.mjs (100%) rename {scripts => apps/docs/scripts}/test-hrefs.mjs (100%) rename {scripts => apps/docs/scripts}/test.mjs (100%) rename {scripts => apps/docs/scripts}/watch-tests.mjs (100%) rename {src => apps/docs/src}/bindings/Babel.res (100%) rename {src => apps/docs/src}/bindings/Cloudflare.res (100%) rename {src => apps/docs/src}/bindings/DocSearch.res (100%) rename {src => apps/docs/src}/bindings/Env.res (100%) rename {src => apps/docs/src}/bindings/Fuse.res (100%) rename {src => apps/docs/src}/bindings/HeadlessUI.res (100%) rename {src => apps/docs/src}/bindings/Jsdom.res (100%) rename {src => apps/docs/src}/bindings/Mdast.res (100%) rename {src => apps/docs/src}/bindings/Node.res (100%) rename {src => apps/docs/src}/bindings/ReactMarkdown.res (100%) rename {src => apps/docs/src}/bindings/ReactRouter.res (100%) rename {src => apps/docs/src}/bindings/Rehype.res (100%) rename {src => apps/docs/src}/bindings/Remark.res (100%) rename {src => apps/docs/src}/bindings/RescriptCompilerApi.res (100%) rename {src => apps/docs/src}/bindings/RescriptCompilerApi.resi (100%) rename {src => apps/docs/src}/bindings/Vitest.res (100%) rename {src => apps/docs/src}/common/Ansi.res (100%) rename {src => apps/docs/src}/common/Ansi.resi (100%) rename {src => apps/docs/src}/common/ColorTheme.res (100%) rename {src => apps/docs/src}/common/ColorTheme.resi (100%) rename {src => apps/docs/src}/common/Constants.res (100%) rename {src => apps/docs/src}/common/DateStr.res (100%) rename {src => apps/docs/src}/common/DateStr.resi (100%) rename {src => apps/docs/src}/common/EnableCollapsibleNavbar.res (100%) rename {src => apps/docs/src}/common/HighlightJs.res (100%) rename {src => apps/docs/src}/common/HighlightJs.resi (100%) rename {src => apps/docs/src}/common/Hooks.res (100%) rename {src => apps/docs/src}/common/MetaDescription.res (100%) rename {src => apps/docs/src}/common/Path.res (100%) rename {src => apps/docs/src}/common/ScrollLockContext.res (100%) rename {src => apps/docs/src}/common/Semver.res (100%) rename {src => apps/docs/src}/common/Semver.resi (100%) rename {src => apps/docs/src}/common/Url.res (100%) rename {src => apps/docs/src}/common/Url.resi (100%) rename {src => apps/docs/src}/common/Util.res (100%) rename {src => apps/docs/src}/common/Util.resi (100%) rename {src => apps/docs/src}/common/WarningFlagDescription.res (100%) rename {src => apps/docs/src}/common/WarningFlagDescription.resi (100%) rename {src => apps/docs/src}/components/AnsiPre.res (100%) rename {src => apps/docs/src}/components/AnsiPre.resi (100%) rename {src => apps/docs/src}/components/ApiIntro.res (100%) rename {src => apps/docs/src}/components/ApiIntro.resi (100%) rename {src => apps/docs/src}/components/ApiMarkdown.res (100%) rename {src => apps/docs/src}/components/ApiMarkdown.resi (100%) rename {src => apps/docs/src}/components/Banner.res (100%) rename {src => apps/docs/src}/components/BreadCrumbs.res (100%) rename {src => apps/docs/src}/components/Button.res (100%) rename {src => apps/docs/src}/components/Button.resi (100%) rename {src => apps/docs/src}/components/CodeExample.res (100%) rename {src => apps/docs/src}/components/CodeExample.resi (100%) rename {src => apps/docs/src}/components/CodeMirror.res (100%) rename {src => apps/docs/src}/components/CodeMirror.resi (100%) rename {src => apps/docs/src}/components/CodeMirrorSetup.js (100%) rename {src => apps/docs/src}/components/CommunityContent.res (100%) rename {src => apps/docs/src}/components/DocsSidebar.res (100%) rename {src => apps/docs/src}/components/Docson.res (100%) rename {src => apps/docs/src}/components/Docson.resi (100%) rename {src => apps/docs/src}/components/DocsonLazy.res (100%) rename {src => apps/docs/src}/components/Footer.res (100%) rename {src => apps/docs/src}/components/Footer.resi (100%) rename {src => apps/docs/src}/components/Icon.res (100%) rename {src => apps/docs/src}/components/Icon.resi (100%) rename {src => apps/docs/src}/components/Image.res (100%) rename {src => apps/docs/src}/components/ImageGallery.res (100%) rename {src => apps/docs/src}/components/Intro.res (100%) rename {src => apps/docs/src}/components/Markdown.res (100%) rename {src => apps/docs/src}/components/Markdown.resi (100%) rename {src => apps/docs/src}/components/MarkdownComponents.res (100%) rename {src => apps/docs/src}/components/MdxContent.res (100%) rename {src => apps/docs/src}/components/MdxContent.resi (100%) rename {src => apps/docs/src}/components/Meta.res (100%) rename {src => apps/docs/src}/components/Meta.resi (100%) rename {src => apps/docs/src}/components/NavbarMobileOverlay.res (100%) rename {src => apps/docs/src}/components/NavbarPrimary.res (100%) rename {src => apps/docs/src}/components/NavbarPrimary.resi (100%) rename {src => apps/docs/src}/components/NavbarSecondary.res (100%) rename {src => apps/docs/src}/components/NavbarTertiary.res (100%) rename {src => apps/docs/src}/components/NavbarUtils.res (100%) rename {src => apps/docs/src}/components/Navigation.res (100%) rename {src => apps/docs/src}/components/Navigation.resi (100%) rename {src => apps/docs/src}/components/Search.res (100%) rename {src => apps/docs/src}/components/SearchBox.res (100%) rename {src => apps/docs/src}/components/Tag.res (100%) rename {src => apps/docs/src}/components/Tag.resi (100%) rename {src => apps/docs/src}/components/Text.res (100%) rename {src => apps/docs/src}/components/Text.resi (100%) rename {src => apps/docs/src}/components/ToggleButton.res (100%) rename {src => apps/docs/src}/components/VersionSelect.res (100%) rename {src => apps/docs/src}/components/VersionSelect.resi (100%) rename {src => apps/docs/src}/components/Video.res (100%) rename {src => apps/docs/src}/components/WarningTable.res (100%) rename {src => apps/docs/src}/data/BlogApi.res (100%) rename {src => apps/docs/src}/data/BlogApi.resi (100%) rename {src => apps/docs/src}/data/BlogLoader.res (100%) rename {src => apps/docs/src}/data/CommunityResources.res (100%) rename {src => apps/docs/src}/data/MetaTagsApi.res (100%) rename {src => apps/docs/src}/data/OurUsers.res (100%) rename {src => apps/docs/src}/ffi/loadScript.js (100%) rename {src => apps/docs/src}/ffi/parse-numeric-range.js (100%) rename {src => apps/docs/src}/ffi/react-codemirror-hooks.js (100%) rename {src => apps/docs/src}/layouts/CommunityLayout.res (100%) rename {src => apps/docs/src}/layouts/DocsLayout.res (100%) rename {src => apps/docs/src}/layouts/MainLayout.res (100%) rename {src => apps/docs/src}/layouts/MainLayout.resi (100%) rename {src => apps/docs/src}/layouts/SidebarLayout.res (100%) rename {src => apps/docs/src}/layouts/SidebarLayout.resi (100%) rename {src => apps/docs/src}/markdown/BlogFrontmatter.res (100%) rename {src => apps/docs/src}/markdown/CompiledMdx.res (100%) rename {src => apps/docs/src}/markdown/CompiledMdx.resi (100%) rename {src => apps/docs/src}/markdown/DocFrontmatter.res (100%) rename {src => apps/docs/src}/markdown/DocFrontmatter.resi (100%) rename {src => apps/docs/src}/markdown/FrontmatterUtils.res (100%) rename {src => apps/docs/src}/markdown/MarkdownParser.res (100%) rename {src => apps/docs/src}/markdown/MarkdownParser.resi (100%) rename {src => apps/docs/src}/markdown/Mdx.res (100%) rename {src => apps/docs/src}/markdown/MdxFile.res (100%) rename {src => apps/docs/src}/markdown/MdxFile.resi (100%) rename {src => apps/docs/src}/markdown/MdxLegacy.res (100%) rename {src => apps/docs/src}/markdown/SidebarHelpers.res (100%) rename {src => apps/docs/src}/markdown/SidebarHelpers.resi (100%) rename {src => apps/docs/src}/markdown/TableOfContents.res (100%) rename {src => apps/docs/src}/markdown/TocUtils.res (100%) rename {src => apps/docs/src}/playground/CompilerManagerHook.res (100%) rename {src => apps/docs/src}/playground/CompilerManagerHook.resi (100%) rename {src => apps/docs/src}/playground/ConsolePanel.res (100%) rename {src => apps/docs/src}/playground/EvalIFrame.res (100%) rename {src => apps/docs/src}/playground/LzString.res (100%) rename {src => apps/docs/src}/playground/OutputPanel.res (100%) rename {src => apps/docs/src}/playground/Playground.res (100%) rename {src => apps/docs/src}/playground/Playground.resi (100%) rename {src => apps/docs/src}/playground/PlaygroundLazy.res (100%) rename {src => apps/docs/src}/playground/RenderPanel.res (100%) rename {src => apps/docs/src}/playground/RenderPanel.resi (100%) rename {src => apps/docs/src}/shims/Shims.res (100%) rename {src => apps/docs/src}/shims/_shims.mjs (100%) rename stdlib-toc.json => apps/docs/stdlib-toc.json (100%) rename {styles => apps/docs/styles}/_docsearch.css (100%) rename {styles => apps/docs/styles}/_fonts.css (100%) rename {styles => apps/docs/styles}/_hljs.css (100%) rename {styles => apps/docs/styles}/_markdown.css (100%) rename {styles => apps/docs/styles}/_theme.css (100%) rename {styles => apps/docs/styles}/docson.css (100%) rename {styles => apps/docs/styles}/main.css (100%) rename {styles => apps/docs/styles}/test-overrides.css (100%) rename {styles => apps/docs/styles}/utils.css (100%) rename vite.config.mjs => apps/docs/vite.config.mjs (100%) rename vitest.config.mjs => apps/docs/vitest.config.mjs (100%) rename vitest.setup.mjs => apps/docs/vitest.setup.mjs (100%) diff --git a/__tests__/ApiOverviewLayout_.test.res b/apps/docs/__tests__/ApiOverviewLayout_.test.res similarity index 100% rename from __tests__/ApiOverviewLayout_.test.res rename to apps/docs/__tests__/ApiOverviewLayout_.test.res diff --git a/__tests__/Banner_.test.res b/apps/docs/__tests__/Banner_.test.res similarity index 100% rename from __tests__/Banner_.test.res rename to apps/docs/__tests__/Banner_.test.res diff --git a/__tests__/BlogArticle_.test.res b/apps/docs/__tests__/BlogArticle_.test.res similarity index 100% rename from __tests__/BlogArticle_.test.res rename to apps/docs/__tests__/BlogArticle_.test.res diff --git a/__tests__/Blog_.test.res b/apps/docs/__tests__/Blog_.test.res similarity index 100% rename from __tests__/Blog_.test.res rename to apps/docs/__tests__/Blog_.test.res diff --git a/__tests__/Button_.test.res b/apps/docs/__tests__/Button_.test.res similarity index 100% rename from __tests__/Button_.test.res rename to apps/docs/__tests__/Button_.test.res diff --git a/__tests__/CodeExample_.test.res b/apps/docs/__tests__/CodeExample_.test.res similarity index 100% rename from __tests__/CodeExample_.test.res rename to apps/docs/__tests__/CodeExample_.test.res diff --git a/__tests__/CommunityLayout_.test.res b/apps/docs/__tests__/CommunityLayout_.test.res similarity index 100% rename from __tests__/CommunityLayout_.test.res rename to apps/docs/__tests__/CommunityLayout_.test.res diff --git a/__tests__/DocsLayout_.test.res b/apps/docs/__tests__/DocsLayout_.test.res similarity index 100% rename from __tests__/DocsLayout_.test.res rename to apps/docs/__tests__/DocsLayout_.test.res diff --git a/__tests__/DocsOverview_.test.res b/apps/docs/__tests__/DocsOverview_.test.res similarity index 100% rename from __tests__/DocsOverview_.test.res rename to apps/docs/__tests__/DocsOverview_.test.res diff --git a/__tests__/Footer_.test.res b/apps/docs/__tests__/Footer_.test.res similarity index 100% rename from __tests__/Footer_.test.res rename to apps/docs/__tests__/Footer_.test.res diff --git a/__tests__/LandingPage_.test.res b/apps/docs/__tests__/LandingPage_.test.res similarity index 100% rename from __tests__/LandingPage_.test.res rename to apps/docs/__tests__/LandingPage_.test.res diff --git a/__tests__/MainLayout_.test.res b/apps/docs/__tests__/MainLayout_.test.res similarity index 100% rename from __tests__/MainLayout_.test.res rename to apps/docs/__tests__/MainLayout_.test.res diff --git a/__tests__/MarkdownComponents_.test.res b/apps/docs/__tests__/MarkdownComponents_.test.res similarity index 100% rename from __tests__/MarkdownComponents_.test.res rename to apps/docs/__tests__/MarkdownComponents_.test.res diff --git a/__tests__/MetaDescription_.test.res b/apps/docs/__tests__/MetaDescription_.test.res similarity index 100% rename from __tests__/MetaDescription_.test.res rename to apps/docs/__tests__/MetaDescription_.test.res diff --git a/__tests__/NavbarPrimary_.test.res b/apps/docs/__tests__/NavbarPrimary_.test.res similarity index 100% rename from __tests__/NavbarPrimary_.test.res rename to apps/docs/__tests__/NavbarPrimary_.test.res diff --git a/__tests__/NavbarSecondary_.test.res b/apps/docs/__tests__/NavbarSecondary_.test.res similarity index 100% rename from __tests__/NavbarSecondary_.test.res rename to apps/docs/__tests__/NavbarSecondary_.test.res diff --git a/__tests__/NavbarTertiary_.test.res b/apps/docs/__tests__/NavbarTertiary_.test.res similarity index 100% rename from __tests__/NavbarTertiary_.test.res rename to apps/docs/__tests__/NavbarTertiary_.test.res diff --git a/__tests__/SearchBox_.test.res b/apps/docs/__tests__/SearchBox_.test.res similarity index 100% rename from __tests__/SearchBox_.test.res rename to apps/docs/__tests__/SearchBox_.test.res diff --git a/__tests__/SidebarLayout_.test.res b/apps/docs/__tests__/SidebarLayout_.test.res similarity index 100% rename from __tests__/SidebarLayout_.test.res rename to apps/docs/__tests__/SidebarLayout_.test.res diff --git a/__tests__/SyntaxLookup_.test.res b/apps/docs/__tests__/SyntaxLookup_.test.res similarity index 100% rename from __tests__/SyntaxLookup_.test.res rename to apps/docs/__tests__/SyntaxLookup_.test.res diff --git a/__tests__/Tag_.test.res b/apps/docs/__tests__/Tag_.test.res similarity index 100% rename from __tests__/Tag_.test.res rename to apps/docs/__tests__/Tag_.test.res diff --git a/__tests__/VersionSelect_.test.res b/apps/docs/__tests__/VersionSelect_.test.res similarity index 100% rename from __tests__/VersionSelect_.test.res rename to apps/docs/__tests__/VersionSelect_.test.res diff --git a/__tests__/__screenshots__/ApiLayout_.test.jsx/api-old-docs-warning-chromium-linux.png b/apps/docs/__tests__/__screenshots__/ApiLayout_.test.jsx/api-old-docs-warning-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/ApiLayout_.test.jsx/api-old-docs-warning-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/ApiLayout_.test.jsx/api-old-docs-warning-chromium-linux.png diff --git a/__tests__/__screenshots__/ApiLayout_.test.jsx/desktop-api-layout-chromium-linux.png b/apps/docs/__tests__/__screenshots__/ApiLayout_.test.jsx/desktop-api-layout-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/ApiLayout_.test.jsx/desktop-api-layout-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/ApiLayout_.test.jsx/desktop-api-layout-chromium-linux.png diff --git a/__tests__/__screenshots__/ApiLayout_.test.jsx/mobile-api-layout-chromium-linux.png b/apps/docs/__tests__/__screenshots__/ApiLayout_.test.jsx/mobile-api-layout-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/ApiLayout_.test.jsx/mobile-api-layout-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/ApiLayout_.test.jsx/mobile-api-layout-chromium-linux.png diff --git a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-chromium-linux.png b/apps/docs/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-chromium-linux.png diff --git a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-with-content-chromium-linux.png b/apps/docs/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-with-content-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-with-content-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/desktop-api-overview-with-content-chromium-linux.png diff --git a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/mobile-api-overview-chromium-linux.png b/apps/docs/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/mobile-api-overview-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/ApiOverviewLayout_.test.jsx/mobile-api-overview-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/mobile-api-overview-chromium-linux.png diff --git a/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/tablet-api-overview-chromium-linux.png b/apps/docs/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/tablet-api-overview-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/ApiOverviewLayout_.test.jsx/tablet-api-overview-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/ApiOverviewLayout_.test.jsx/tablet-api-overview-chromium-linux.png diff --git a/__tests__/__screenshots__/Banner_.test.jsx/banner-with-content-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Banner_.test.jsx/banner-with-content-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Banner_.test.jsx/banner-with-content-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Banner_.test.jsx/banner-with-content-chromium-linux.png diff --git a/__tests__/__screenshots__/Banner_.test.jsx/mobile-banner-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Banner_.test.jsx/mobile-banner-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Banner_.test.jsx/mobile-banner-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Banner_.test.jsx/mobile-banner-chromium-linux.png diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-archived-chromium-linux.png b/apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-archived-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-archived-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-archived-chromium-linux.png diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-chromium-linux.png b/apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-chromium-linux.png diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-coauthors-chromium-linux.png b/apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-coauthors-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-coauthors-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-coauthors-chromium-linux.png diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-no-description-chromium-linux.png b/apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-no-description-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-no-description-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-no-description-chromium-linux.png diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-image-chromium-linux.png b/apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-image-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-image-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/desktop-blog-article-with-image-chromium-linux.png diff --git a/__tests__/__screenshots__/BlogArticle_.test.jsx/mobile-blog-article-chromium-linux.png b/apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/mobile-blog-article-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/BlogArticle_.test.jsx/mobile-blog-article-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/BlogArticle_.test.jsx/mobile-blog-article-chromium-linux.png diff --git a/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-archived-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-archived-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Blog_.test.jsx/desktop-blog-archived-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-archived-chromium-linux.png diff --git a/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-category-selector-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-category-selector-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Blog_.test.jsx/desktop-blog-category-selector-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-category-selector-chromium-linux.png diff --git a/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-empty-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-empty-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Blog_.test.jsx/desktop-blog-empty-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-empty-chromium-linux.png diff --git a/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-index-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-index-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Blog_.test.jsx/desktop-blog-index-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-index-chromium-linux.png diff --git a/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-single-post-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-single-post-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Blog_.test.jsx/desktop-blog-single-post-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Blog_.test.jsx/desktop-blog-single-post-chromium-linux.png diff --git a/__tests__/__screenshots__/Blog_.test.jsx/mobile-blog-index-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Blog_.test.jsx/mobile-blog-index-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Blog_.test.jsx/mobile-blog-index-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Blog_.test.jsx/mobile-blog-index-chromium-linux.png diff --git a/__tests__/__screenshots__/Button_.test.jsx/button-primary-blue-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Button_.test.jsx/button-primary-blue-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Button_.test.jsx/button-primary-blue-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Button_.test.jsx/button-primary-blue-chromium-linux.png diff --git a/__tests__/__screenshots__/Button_.test.jsx/button-primary-red-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Button_.test.jsx/button-primary-red-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Button_.test.jsx/button-primary-red-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Button_.test.jsx/button-primary-red-chromium-linux.png diff --git a/__tests__/__screenshots__/Button_.test.jsx/button-secondary-red-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Button_.test.jsx/button-secondary-red-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Button_.test.jsx/button-secondary-red-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Button_.test.jsx/button-secondary-red-chromium-linux.png diff --git a/__tests__/__screenshots__/Button_.test.jsx/button-small-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Button_.test.jsx/button-small-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Button_.test.jsx/button-small-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Button_.test.jsx/button-small-chromium-linux.png diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/code-example-highlighted-chromium-linux.png b/apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-example-highlighted-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/CodeExample_.test.jsx/code-example-highlighted-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-example-highlighted-chromium-linux.png diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/code-example-no-label-chromium-linux.png b/apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-example-no-label-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/CodeExample_.test.jsx/code-example-no-label-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-example-no-label-chromium-linux.png diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/code-example-rescript-chromium-linux.png b/apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-example-rescript-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/CodeExample_.test.jsx/code-example-rescript-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-example-rescript-chromium-linux.png diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/code-toggle-js-selected-chromium-linux.png b/apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-toggle-js-selected-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/CodeExample_.test.jsx/code-toggle-js-selected-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-toggle-js-selected-chromium-linux.png diff --git a/__tests__/__screenshots__/CodeExample_.test.jsx/code-toggle-tabs-chromium-linux.png b/apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-toggle-tabs-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/CodeExample_.test.jsx/code-toggle-tabs-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/CodeExample_.test.jsx/code-toggle-tabs-chromium-linux.png diff --git a/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-chromium-linux.png b/apps/docs/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-chromium-linux.png diff --git a/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-multi-categories-chromium-linux.png b/apps/docs/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-multi-categories-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-multi-categories-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/CommunityLayout_.test.jsx/desktop-community-layout-multi-categories-chromium-linux.png diff --git a/__tests__/__screenshots__/CommunityLayout_.test.jsx/mobile-community-layout-chromium-linux.png b/apps/docs/__tests__/__screenshots__/CommunityLayout_.test.jsx/mobile-community-layout-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/CommunityLayout_.test.jsx/mobile-community-layout-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/CommunityLayout_.test.jsx/mobile-community-layout-chromium-linux.png diff --git a/__tests__/__screenshots__/CommunityLayout_.test.jsx/tablet-community-layout-chromium-linux.png b/apps/docs/__tests__/__screenshots__/CommunityLayout_.test.jsx/tablet-community-layout-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/CommunityLayout_.test.jsx/tablet-community-layout-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/CommunityLayout_.test.jsx/tablet-community-layout-chromium-linux.png diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-active-item-chromium-linux.png b/apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-active-item-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-active-item-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-active-item-chromium-linux.png diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-chromium-linux.png b/apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-chromium-linux.png diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-pagination-chromium-linux.png b/apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-pagination-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-pagination-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-pagination-chromium-linux.png diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-with-toc-chromium-linux.png b/apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-with-toc-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-with-toc-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/desktop-docs-layout-with-toc-chromium-linux.png diff --git a/__tests__/__screenshots__/DocsLayout_.test.jsx/mobile-docs-layout-chromium-linux.png b/apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/mobile-docs-layout-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/DocsLayout_.test.jsx/mobile-docs-layout-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/DocsLayout_.test.jsx/mobile-docs-layout-chromium-linux.png diff --git a/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-chromium-linux.png b/apps/docs/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-chromium-linux.png diff --git a/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-ecosystem-chromium-linux.png b/apps/docs/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-ecosystem-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-ecosystem-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/DocsOverview_.test.jsx/desktop-docs-overview-ecosystem-chromium-linux.png diff --git a/__tests__/__screenshots__/DocsOverview_.test.jsx/mobile-docs-overview-chromium-linux.png b/apps/docs/__tests__/__screenshots__/DocsOverview_.test.jsx/mobile-docs-overview-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/DocsOverview_.test.jsx/mobile-docs-overview-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/DocsOverview_.test.jsx/mobile-docs-overview-chromium-linux.png diff --git a/__tests__/__screenshots__/Footer_.test.jsx/desktop-footer-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Footer_.test.jsx/desktop-footer-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Footer_.test.jsx/desktop-footer-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Footer_.test.jsx/desktop-footer-chromium-linux.png diff --git a/__tests__/__screenshots__/Footer_.test.jsx/mobile-footer-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Footer_.test.jsx/mobile-footer-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Footer_.test.jsx/mobile-footer-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Footer_.test.jsx/mobile-footer-chromium-linux.png diff --git a/__tests__/__screenshots__/MainLayout_.test.jsx/desktop-main-layout-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MainLayout_.test.jsx/desktop-main-layout-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MainLayout_.test.jsx/desktop-main-layout-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MainLayout_.test.jsx/desktop-main-layout-chromium-linux.png diff --git a/__tests__/__screenshots__/MainLayout_.test.jsx/mobile-main-layout-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MainLayout_.test.jsx/mobile-main-layout-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MainLayout_.test.jsx/mobile-main-layout-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MainLayout_.test.jsx/mobile-main-layout-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-admonitions-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-admonitions-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-admonitions-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-admonitions-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-anchor-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-anchor-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-anchor-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-anchor-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-blockquote-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-blockquote-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-blockquote-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-blockquote-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-no-author-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-no-author-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-no-author-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-cite-no-author-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-headings-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-headings-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-headings-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-headings-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-hr-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-hr-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-hr-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-hr-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-small-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-small-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-small-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-image-small-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-inline-code-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-inline-code-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-inline-code-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-inline-code-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-links-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-links-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-links-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-links-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-lists-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-lists-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-lists-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-lists-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-nested-lists-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-nested-lists-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-nested-lists-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-nested-lists-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-strong-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-strong-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-strong-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-strong-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-table-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-table-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-table-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-table-chromium-linux.png diff --git a/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-text-elements-chromium-linux.png b/apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-text-elements-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-text-elements-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/MarkdownComponents_.test.jsx/markdown-text-elements-chromium-linux.png diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png b/apps/docs/__tests__/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png b/apps/docs/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png b/apps/docs/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/tablet-navbar-primary-chromium-linux.png b/apps/docs/__tests__/__screenshots__/NavbarPrimary_.test.jsx/tablet-navbar-primary-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/NavbarPrimary_.test.jsx/tablet-navbar-primary-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/NavbarPrimary_.test.jsx/tablet-navbar-primary-chromium-linux.png diff --git a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-chromium-linux.png b/apps/docs/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-chromium-linux.png diff --git a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-react-active-chromium-linux.png b/apps/docs/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-react-active-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-react-active-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/NavbarSecondary_.test.jsx/desktop-navbar-secondary-react-active-chromium-linux.png diff --git a/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-navbar-secondary-chromium-linux.png b/apps/docs/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-navbar-secondary-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-navbar-secondary-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/NavbarSecondary_.test.jsx/mobile-navbar-secondary-chromium-linux.png diff --git a/__tests__/__screenshots__/NavbarTertiary_.test.jsx/desktop-navbar-tertiary-chromium-linux.png b/apps/docs/__tests__/__screenshots__/NavbarTertiary_.test.jsx/desktop-navbar-tertiary-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/NavbarTertiary_.test.jsx/desktop-navbar-tertiary-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/NavbarTertiary_.test.jsx/desktop-navbar-tertiary-chromium-linux.png diff --git a/__tests__/__screenshots__/NavbarTertiary_.test.jsx/mobile-navbar-tertiary-chromium-linux.png b/apps/docs/__tests__/__screenshots__/NavbarTertiary_.test.jsx/mobile-navbar-tertiary-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/NavbarTertiary_.test.jsx/mobile-navbar-tertiary-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/NavbarTertiary_.test.jsx/mobile-navbar-tertiary-chromium-linux.png diff --git a/__tests__/__screenshots__/SearchBox_.test.jsx/searchbox-empty-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SearchBox_.test.jsx/searchbox-empty-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SearchBox_.test.jsx/searchbox-empty-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SearchBox_.test.jsx/searchbox-empty-chromium-linux.png diff --git a/__tests__/__screenshots__/SearchBox_.test.jsx/searchbox-with-value-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SearchBox_.test.jsx/searchbox-with-value-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SearchBox_.test.jsx/searchbox-with-value-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SearchBox_.test.jsx/searchbox-with-value-chromium-linux.png diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-chromium-linux.png diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-deep-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-deep-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-deep-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-breadcrumbs-deep-chromium-linux.png diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-active-item-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-active-item-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-active-item-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-active-item-chromium-linux.png diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-chromium-linux.png diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-many-items-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-many-items-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-many-items-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-many-items-chromium-linux.png diff --git a/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-toc-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-toc-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-toc-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SidebarLayout_.test.jsx/sidebar-category-with-toc-chromium-linux.png diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-active-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-active-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-active-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-active-chromium-linux.png diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-chromium-linux.png diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-deprecated-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-deprecated-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-deprecated-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-deprecated-chromium-linux.png diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-pipe-detail-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-pipe-detail-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-pipe-detail-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/desktop-syntax-lookup-pipe-detail-chromium-linux.png diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-active-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-active-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-active-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-active-chromium-linux.png diff --git a/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-chromium-linux.png b/apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/SyntaxLookup_.test.jsx/mobile-syntax-lookup-chromium-linux.png diff --git a/__tests__/__screenshots__/Tag_.test.jsx/tag-subtle-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Tag_.test.jsx/tag-subtle-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Tag_.test.jsx/tag-subtle-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Tag_.test.jsx/tag-subtle-chromium-linux.png diff --git a/__tests__/__screenshots__/Tag_.test.jsx/tags-multiple-chromium-linux.png b/apps/docs/__tests__/__screenshots__/Tag_.test.jsx/tags-multiple-chromium-linux.png similarity index 100% rename from __tests__/__screenshots__/Tag_.test.jsx/tags-multiple-chromium-linux.png rename to apps/docs/__tests__/__screenshots__/Tag_.test.jsx/tags-multiple-chromium-linux.png diff --git a/app/layouts/DocsLayoutRoute.res b/apps/docs/app/layouts/DocsLayoutRoute.res similarity index 100% rename from app/layouts/DocsLayoutRoute.res rename to apps/docs/app/layouts/DocsLayoutRoute.res diff --git a/app/layouts/DocsLayoutRoute.resi b/apps/docs/app/layouts/DocsLayoutRoute.resi similarity index 100% rename from app/layouts/DocsLayoutRoute.resi rename to apps/docs/app/layouts/DocsLayoutRoute.resi diff --git a/app/root.res b/apps/docs/app/root.res similarity index 100% rename from app/root.res rename to apps/docs/app/root.res diff --git a/app/root.resi b/apps/docs/app/root.resi similarity index 100% rename from app/root.resi rename to apps/docs/app/root.resi diff --git a/app/routes.res b/apps/docs/app/routes.res similarity index 100% rename from app/routes.res rename to apps/docs/app/routes.res diff --git a/app/routes.resi b/apps/docs/app/routes.resi similarity index 100% rename from app/routes.resi rename to apps/docs/app/routes.resi diff --git a/app/routes/ApiDocs.res b/apps/docs/app/routes/ApiDocs.res similarity index 100% rename from app/routes/ApiDocs.res rename to apps/docs/app/routes/ApiDocs.res diff --git a/app/routes/ApiOverviewRoute.res b/apps/docs/app/routes/ApiOverviewRoute.res similarity index 100% rename from app/routes/ApiOverviewRoute.res rename to apps/docs/app/routes/ApiOverviewRoute.res diff --git a/app/routes/ApiOverviewRoute.resi b/apps/docs/app/routes/ApiOverviewRoute.resi similarity index 100% rename from app/routes/ApiOverviewRoute.resi rename to apps/docs/app/routes/ApiOverviewRoute.resi diff --git a/app/routes/ApiRoute.res b/apps/docs/app/routes/ApiRoute.res similarity index 100% rename from app/routes/ApiRoute.res rename to apps/docs/app/routes/ApiRoute.res diff --git a/app/routes/ApiRoute.resi b/apps/docs/app/routes/ApiRoute.resi similarity index 100% rename from app/routes/ApiRoute.resi rename to apps/docs/app/routes/ApiRoute.resi diff --git a/app/routes/Blog.res b/apps/docs/app/routes/Blog.res similarity index 100% rename from app/routes/Blog.res rename to apps/docs/app/routes/Blog.res diff --git a/app/routes/BlogArticle.res b/apps/docs/app/routes/BlogArticle.res similarity index 100% rename from app/routes/BlogArticle.res rename to apps/docs/app/routes/BlogArticle.res diff --git a/app/routes/BlogArticle.resi b/apps/docs/app/routes/BlogArticle.resi similarity index 100% rename from app/routes/BlogArticle.resi rename to apps/docs/app/routes/BlogArticle.resi diff --git a/app/routes/BlogArticleRoute.res b/apps/docs/app/routes/BlogArticleRoute.res similarity index 100% rename from app/routes/BlogArticleRoute.res rename to apps/docs/app/routes/BlogArticleRoute.res diff --git a/app/routes/BlogArticleRoute.resi b/apps/docs/app/routes/BlogArticleRoute.resi similarity index 100% rename from app/routes/BlogArticleRoute.resi rename to apps/docs/app/routes/BlogArticleRoute.resi diff --git a/app/routes/BlogRoute.res b/apps/docs/app/routes/BlogRoute.res similarity index 100% rename from app/routes/BlogRoute.res rename to apps/docs/app/routes/BlogRoute.res diff --git a/app/routes/BlogRoute.resi b/apps/docs/app/routes/BlogRoute.resi similarity index 100% rename from app/routes/BlogRoute.resi rename to apps/docs/app/routes/BlogRoute.resi diff --git a/app/routes/CommunityRoute.res b/apps/docs/app/routes/CommunityRoute.res similarity index 100% rename from app/routes/CommunityRoute.res rename to apps/docs/app/routes/CommunityRoute.res diff --git a/app/routes/CommunityRoute.resi b/apps/docs/app/routes/CommunityRoute.resi similarity index 100% rename from app/routes/CommunityRoute.resi rename to apps/docs/app/routes/CommunityRoute.resi diff --git a/app/routes/DocsGuidelinesRoute.res b/apps/docs/app/routes/DocsGuidelinesRoute.res similarity index 100% rename from app/routes/DocsGuidelinesRoute.res rename to apps/docs/app/routes/DocsGuidelinesRoute.res diff --git a/app/routes/DocsGuidelinesRoute.resi b/apps/docs/app/routes/DocsGuidelinesRoute.resi similarity index 100% rename from app/routes/DocsGuidelinesRoute.resi rename to apps/docs/app/routes/DocsGuidelinesRoute.resi diff --git a/app/routes/DocsManualRoute.res b/apps/docs/app/routes/DocsManualRoute.res similarity index 100% rename from app/routes/DocsManualRoute.res rename to apps/docs/app/routes/DocsManualRoute.res diff --git a/app/routes/DocsManualRoute.resi b/apps/docs/app/routes/DocsManualRoute.resi similarity index 100% rename from app/routes/DocsManualRoute.resi rename to apps/docs/app/routes/DocsManualRoute.resi diff --git a/app/routes/DocsOverview.res b/apps/docs/app/routes/DocsOverview.res similarity index 100% rename from app/routes/DocsOverview.res rename to apps/docs/app/routes/DocsOverview.res diff --git a/app/routes/DocsOverview.resi b/apps/docs/app/routes/DocsOverview.resi similarity index 100% rename from app/routes/DocsOverview.resi rename to apps/docs/app/routes/DocsOverview.resi diff --git a/app/routes/DocsReactRoute.res b/apps/docs/app/routes/DocsReactRoute.res similarity index 100% rename from app/routes/DocsReactRoute.res rename to apps/docs/app/routes/DocsReactRoute.res diff --git a/app/routes/DocsReactRoute.resi b/apps/docs/app/routes/DocsReactRoute.resi similarity index 100% rename from app/routes/DocsReactRoute.resi rename to apps/docs/app/routes/DocsReactRoute.resi diff --git a/app/routes/LandingPage.res b/apps/docs/app/routes/LandingPage.res similarity index 100% rename from app/routes/LandingPage.res rename to apps/docs/app/routes/LandingPage.res diff --git a/app/routes/LandingPage.resi b/apps/docs/app/routes/LandingPage.resi similarity index 100% rename from app/routes/LandingPage.resi rename to apps/docs/app/routes/LandingPage.resi diff --git a/app/routes/LandingPageRoute.res b/apps/docs/app/routes/LandingPageRoute.res similarity index 100% rename from app/routes/LandingPageRoute.res rename to apps/docs/app/routes/LandingPageRoute.res diff --git a/app/routes/LandingPageRoute.resi b/apps/docs/app/routes/LandingPageRoute.resi similarity index 100% rename from app/routes/LandingPageRoute.resi rename to apps/docs/app/routes/LandingPageRoute.resi diff --git a/app/routes/NotFoundRoute.res b/apps/docs/app/routes/NotFoundRoute.res similarity index 100% rename from app/routes/NotFoundRoute.res rename to apps/docs/app/routes/NotFoundRoute.res diff --git a/app/routes/NotFoundRoute.resi b/apps/docs/app/routes/NotFoundRoute.resi similarity index 100% rename from app/routes/NotFoundRoute.resi rename to apps/docs/app/routes/NotFoundRoute.resi diff --git a/app/routes/Packages.res b/apps/docs/app/routes/Packages.res similarity index 100% rename from app/routes/Packages.res rename to apps/docs/app/routes/Packages.res diff --git a/app/routes/Packages.resi b/apps/docs/app/routes/Packages.resi similarity index 100% rename from app/routes/Packages.resi rename to apps/docs/app/routes/Packages.resi diff --git a/app/routes/PackagesRoute.res b/apps/docs/app/routes/PackagesRoute.res similarity index 100% rename from app/routes/PackagesRoute.res rename to apps/docs/app/routes/PackagesRoute.res diff --git a/app/routes/PackagesRoute.resi b/apps/docs/app/routes/PackagesRoute.resi similarity index 100% rename from app/routes/PackagesRoute.resi rename to apps/docs/app/routes/PackagesRoute.resi diff --git a/app/routes/SyntaxLookup.res b/apps/docs/app/routes/SyntaxLookup.res similarity index 100% rename from app/routes/SyntaxLookup.res rename to apps/docs/app/routes/SyntaxLookup.res diff --git a/app/routes/SyntaxLookupDetailRoute.res b/apps/docs/app/routes/SyntaxLookupDetailRoute.res similarity index 100% rename from app/routes/SyntaxLookupDetailRoute.res rename to apps/docs/app/routes/SyntaxLookupDetailRoute.res diff --git a/app/routes/SyntaxLookupDetailRoute.resi b/apps/docs/app/routes/SyntaxLookupDetailRoute.resi similarity index 100% rename from app/routes/SyntaxLookupDetailRoute.resi rename to apps/docs/app/routes/SyntaxLookupDetailRoute.resi diff --git a/app/routes/SyntaxLookupRoute.res b/apps/docs/app/routes/SyntaxLookupRoute.res similarity index 100% rename from app/routes/SyntaxLookupRoute.res rename to apps/docs/app/routes/SyntaxLookupRoute.res diff --git a/app/routes/SyntaxLookupRoute.resi b/apps/docs/app/routes/SyntaxLookupRoute.resi similarity index 100% rename from app/routes/SyntaxLookupRoute.resi rename to apps/docs/app/routes/SyntaxLookupRoute.resi diff --git a/app/routes/TryRoute.res b/apps/docs/app/routes/TryRoute.res similarity index 100% rename from app/routes/TryRoute.res rename to apps/docs/app/routes/TryRoute.res diff --git a/app/routes/TryRoute.resi b/apps/docs/app/routes/TryRoute.resi similarity index 100% rename from app/routes/TryRoute.resi rename to apps/docs/app/routes/TryRoute.resi diff --git a/compilers/README.md b/apps/docs/compilers/README.md similarity index 100% rename from compilers/README.md rename to apps/docs/compilers/README.md diff --git a/compilers/dummy/Dummy.res b/apps/docs/compilers/dummy/Dummy.res similarity index 100% rename from compilers/dummy/Dummy.res rename to apps/docs/compilers/dummy/Dummy.res diff --git a/compilers/package-lock.json b/apps/docs/compilers/package-lock.json similarity index 100% rename from compilers/package-lock.json rename to apps/docs/compilers/package-lock.json diff --git a/compilers/package.json b/apps/docs/compilers/package.json similarity index 100% rename from compilers/package.json rename to apps/docs/compilers/package.json diff --git a/compilers/rescript.json b/apps/docs/compilers/rescript.json similarity index 100% rename from compilers/rescript.json rename to apps/docs/compilers/rescript.json diff --git a/cypress.config.mjs b/apps/docs/cypress.config.mjs similarity index 100% rename from cypress.config.mjs rename to apps/docs/cypress.config.mjs diff --git a/cypress/support/e2e.js b/apps/docs/cypress/support/e2e.js similarity index 100% rename from cypress/support/e2e.js rename to apps/docs/cypress/support/e2e.js diff --git a/data/api/v12.0.0/belt.json b/apps/docs/data/api/v12.0.0/belt.json similarity index 100% rename from data/api/v12.0.0/belt.json rename to apps/docs/data/api/v12.0.0/belt.json diff --git a/data/api/v12.0.0/dom.json b/apps/docs/data/api/v12.0.0/dom.json similarity index 100% rename from data/api/v12.0.0/dom.json rename to apps/docs/data/api/v12.0.0/dom.json diff --git a/data/api/v12.0.0/js.json b/apps/docs/data/api/v12.0.0/js.json similarity index 100% rename from data/api/v12.0.0/js.json rename to apps/docs/data/api/v12.0.0/js.json diff --git a/data/api/v12.0.0/stdlib.json b/apps/docs/data/api/v12.0.0/stdlib.json similarity index 100% rename from data/api/v12.0.0/stdlib.json rename to apps/docs/data/api/v12.0.0/stdlib.json diff --git a/data/api/v12.0.0/toc_tree.json b/apps/docs/data/api/v12.0.0/toc_tree.json similarity index 100% rename from data/api/v12.0.0/toc_tree.json rename to apps/docs/data/api/v12.0.0/toc_tree.json diff --git a/data/api/v12.0.1/belt.json b/apps/docs/data/api/v12.0.1/belt.json similarity index 100% rename from data/api/v12.0.1/belt.json rename to apps/docs/data/api/v12.0.1/belt.json diff --git a/data/api/v12.0.1/dom.json b/apps/docs/data/api/v12.0.1/dom.json similarity index 100% rename from data/api/v12.0.1/dom.json rename to apps/docs/data/api/v12.0.1/dom.json diff --git a/data/api/v12.0.1/js.json b/apps/docs/data/api/v12.0.1/js.json similarity index 100% rename from data/api/v12.0.1/js.json rename to apps/docs/data/api/v12.0.1/js.json diff --git a/data/api/v12.0.1/stdlib.json b/apps/docs/data/api/v12.0.1/stdlib.json similarity index 100% rename from data/api/v12.0.1/stdlib.json rename to apps/docs/data/api/v12.0.1/stdlib.json diff --git a/data/api/v12.0.1/toc_tree.json b/apps/docs/data/api/v12.0.1/toc_tree.json similarity index 100% rename from data/api/v12.0.1/toc_tree.json rename to apps/docs/data/api/v12.0.1/toc_tree.json diff --git a/data/api/v12.0.2/belt.json b/apps/docs/data/api/v12.0.2/belt.json similarity index 100% rename from data/api/v12.0.2/belt.json rename to apps/docs/data/api/v12.0.2/belt.json diff --git a/data/api/v12.0.2/dom.json b/apps/docs/data/api/v12.0.2/dom.json similarity index 100% rename from data/api/v12.0.2/dom.json rename to apps/docs/data/api/v12.0.2/dom.json diff --git a/data/api/v12.0.2/js.json b/apps/docs/data/api/v12.0.2/js.json similarity index 100% rename from data/api/v12.0.2/js.json rename to apps/docs/data/api/v12.0.2/js.json diff --git a/data/api/v12.0.2/stdlib.json b/apps/docs/data/api/v12.0.2/stdlib.json similarity index 100% rename from data/api/v12.0.2/stdlib.json rename to apps/docs/data/api/v12.0.2/stdlib.json diff --git a/data/api/v12.0.2/toc_tree.json b/apps/docs/data/api/v12.0.2/toc_tree.json similarity index 100% rename from data/api/v12.0.2/toc_tree.json rename to apps/docs/data/api/v12.0.2/toc_tree.json diff --git a/data/api/v12.1.0/belt.json b/apps/docs/data/api/v12.1.0/belt.json similarity index 100% rename from data/api/v12.1.0/belt.json rename to apps/docs/data/api/v12.1.0/belt.json diff --git a/data/api/v12.1.0/dom.json b/apps/docs/data/api/v12.1.0/dom.json similarity index 100% rename from data/api/v12.1.0/dom.json rename to apps/docs/data/api/v12.1.0/dom.json diff --git a/data/api/v12.1.0/js.json b/apps/docs/data/api/v12.1.0/js.json similarity index 100% rename from data/api/v12.1.0/js.json rename to apps/docs/data/api/v12.1.0/js.json diff --git a/data/api/v12.1.0/stdlib.json b/apps/docs/data/api/v12.1.0/stdlib.json similarity index 100% rename from data/api/v12.1.0/stdlib.json rename to apps/docs/data/api/v12.1.0/stdlib.json diff --git a/data/api/v12.1.0/toc_tree.json b/apps/docs/data/api/v12.1.0/toc_tree.json similarity index 100% rename from data/api/v12.1.0/toc_tree.json rename to apps/docs/data/api/v12.1.0/toc_tree.json diff --git a/data/api/v12.2.0/belt.json b/apps/docs/data/api/v12.2.0/belt.json similarity index 100% rename from data/api/v12.2.0/belt.json rename to apps/docs/data/api/v12.2.0/belt.json diff --git a/data/api/v12.2.0/dom.json b/apps/docs/data/api/v12.2.0/dom.json similarity index 100% rename from data/api/v12.2.0/dom.json rename to apps/docs/data/api/v12.2.0/dom.json diff --git a/data/api/v12.2.0/js.json b/apps/docs/data/api/v12.2.0/js.json similarity index 100% rename from data/api/v12.2.0/js.json rename to apps/docs/data/api/v12.2.0/js.json diff --git a/data/api/v12.2.0/stdlib.json b/apps/docs/data/api/v12.2.0/stdlib.json similarity index 100% rename from data/api/v12.2.0/stdlib.json rename to apps/docs/data/api/v12.2.0/stdlib.json diff --git a/data/api/v12.2.0/toc_tree.json b/apps/docs/data/api/v12.2.0/toc_tree.json similarity index 100% rename from data/api/v12.2.0/toc_tree.json rename to apps/docs/data/api/v12.2.0/toc_tree.json diff --git a/data/api/v13.0.0/belt.json b/apps/docs/data/api/v13.0.0/belt.json similarity index 100% rename from data/api/v13.0.0/belt.json rename to apps/docs/data/api/v13.0.0/belt.json diff --git a/data/api/v13.0.0/dom.json b/apps/docs/data/api/v13.0.0/dom.json similarity index 100% rename from data/api/v13.0.0/dom.json rename to apps/docs/data/api/v13.0.0/dom.json diff --git a/data/api/v13.0.0/js.json b/apps/docs/data/api/v13.0.0/js.json similarity index 100% rename from data/api/v13.0.0/js.json rename to apps/docs/data/api/v13.0.0/js.json diff --git a/data/api/v13.0.0/stdlib.json b/apps/docs/data/api/v13.0.0/stdlib.json similarity index 100% rename from data/api/v13.0.0/stdlib.json rename to apps/docs/data/api/v13.0.0/stdlib.json diff --git a/data/api/v13.0.0/toc_tree.json b/apps/docs/data/api/v13.0.0/toc_tree.json similarity index 100% rename from data/api/v13.0.0/toc_tree.json rename to apps/docs/data/api/v13.0.0/toc_tree.json diff --git a/e2e/Navigation.cy.res b/apps/docs/e2e/Navigation.cy.res similarity index 100% rename from e2e/Navigation.cy.res rename to apps/docs/e2e/Navigation.cy.res diff --git a/e2e/Playground.cy.res b/apps/docs/e2e/Playground.cy.res similarity index 100% rename from e2e/Playground.cy.res rename to apps/docs/e2e/Playground.cy.res diff --git a/e2e/bindings/Cy.res b/apps/docs/e2e/bindings/Cy.res similarity index 100% rename from e2e/bindings/Cy.res rename to apps/docs/e2e/bindings/Cy.res diff --git a/functions/ogimage/[[path]]/index.png.res b/apps/docs/functions/ogimage/[[path]]/index.png.res similarity index 100% rename from functions/ogimage/[[path]]/index.png.res rename to apps/docs/functions/ogimage/[[path]]/index.png.res diff --git a/generate-route-types.mjs b/apps/docs/generate-route-types.mjs similarity index 100% rename from generate-route-types.mjs rename to apps/docs/generate-route-types.mjs diff --git a/image-converter.config.mjs b/apps/docs/image-converter.config.mjs similarity index 100% rename from image-converter.config.mjs rename to apps/docs/image-converter.config.mjs diff --git a/markdown-pages/blog/archived/a-note-on-bucklescripts-future-commitments.mdx b/apps/docs/markdown-pages/blog/archived/a-note-on-bucklescripts-future-commitments.mdx similarity index 100% rename from markdown-pages/blog/archived/a-note-on-bucklescripts-future-commitments.mdx rename to apps/docs/markdown-pages/blog/archived/a-note-on-bucklescripts-future-commitments.mdx diff --git a/markdown-pages/blog/archived/a-small-step-for-bucklescript.mdx b/apps/docs/markdown-pages/blog/archived/a-small-step-for-bucklescript.mdx similarity index 100% rename from markdown-pages/blog/archived/a-small-step-for-bucklescript.mdx rename to apps/docs/markdown-pages/blog/archived/a-small-step-for-bucklescript.mdx diff --git a/markdown-pages/blog/archived/a-story-of-exception-encoding.mdx b/apps/docs/markdown-pages/blog/archived/a-story-of-exception-encoding.mdx similarity index 100% rename from markdown-pages/blog/archived/a-story-of-exception-encoding.mdx rename to apps/docs/markdown-pages/blog/archived/a-story-of-exception-encoding.mdx diff --git a/markdown-pages/blog/archived/a-story-of-lazy-encoding.mdx b/apps/docs/markdown-pages/blog/archived/a-story-of-lazy-encoding.mdx similarity index 100% rename from markdown-pages/blog/archived/a-story-of-lazy-encoding.mdx rename to apps/docs/markdown-pages/blog/archived/a-story-of-lazy-encoding.mdx diff --git a/markdown-pages/blog/archived/another-encoding.mdx b/apps/docs/markdown-pages/blog/archived/another-encoding.mdx similarity index 100% rename from markdown-pages/blog/archived/another-encoding.mdx rename to apps/docs/markdown-pages/blog/archived/another-encoding.mdx diff --git a/markdown-pages/blog/archived/arity-zero.mdx b/apps/docs/markdown-pages/blog/archived/arity-zero.mdx similarity index 100% rename from markdown-pages/blog/archived/arity-zero.mdx rename to apps/docs/markdown-pages/blog/archived/arity-zero.mdx diff --git a/markdown-pages/blog/archived/bucklescript-8-1-new-syntax.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-8-1-new-syntax.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-8-1-new-syntax.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-8-1-new-syntax.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-1-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-1-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-1-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-1-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-1-4-2.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-1-4-2.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-1-4-2.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-1-4-2.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-1-4-3.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-1-4-3.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-1-4-3.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-1-4-3.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-1-5-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-1-5-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-1-5-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-1-5-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-1-5-1.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-1-5-1.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-1-5-1.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-1-5-1.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-1-5-2.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-1-5-2.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-1-5-2.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-1-5-2.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-1-7-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-1-7-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-1-7-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-1-7-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-1-7-4.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-1-7-4.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-1-7-4.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-1-7-4.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-1-7-5.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-1-7-5.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-1-7-5.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-1-7-5.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-3-0-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-3-0-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-3-0-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-3-0-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-3-1-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-3-1-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-3-1-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-3-1-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-3-1-4.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-3-1-4.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-3-1-4.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-3-1-4.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-4-0-0-pt1.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-4-0-0-pt1.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-4-0-0-pt1.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-4-0-0-pt1.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-4-0-0-pt2.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-4-0-0-pt2.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-4-0-0-pt2.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-4-0-0-pt2.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-4-0-17.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-4-0-17.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-4-0-17.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-4-0-17.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-4-0-8.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-4-0-8.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-4-0-8.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-4-0-8.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-5-0-1.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-5-0-1.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-5-0-1.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-5-0-1.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-5-0-4.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-5-0-4.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-5-0-4.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-5-0-4.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-5-0-5.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-5-0-5.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-5-0-5.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-5-0-5.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-5-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-5-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-5-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-5-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-5-1-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-5-1-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-5-1-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-5-1-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-5-2-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-5-2-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-5-2-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-5-2-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-6-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-6-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-6-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-6-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-7-0-2.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-7-0-2.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-7-0-2.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-7-0-2.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-7-1-0.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-7-1-0.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-7-1-0.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-7-1-0.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-7-2.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-7-2.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-7-2.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-7-2.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-7-3.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-7-3.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-7-3.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-7-3.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-8-1-1.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-8-1-1.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-8-1-1.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-8-1-1.mdx diff --git a/markdown-pages/blog/archived/bucklescript-release-8-2.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-release-8-2.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-release-8-2.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-release-8-2.mdx diff --git a/markdown-pages/blog/archived/bucklescript-roadmap-q3-4-2018.mdx b/apps/docs/markdown-pages/blog/archived/bucklescript-roadmap-q3-4-2018.mdx similarity index 100% rename from markdown-pages/blog/archived/bucklescript-roadmap-q3-4-2018.mdx rename to apps/docs/markdown-pages/blog/archived/bucklescript-roadmap-q3-4-2018.mdx diff --git a/markdown-pages/blog/archived/feature-preview-variadic.mdx b/apps/docs/markdown-pages/blog/archived/feature-preview-variadic.mdx similarity index 100% rename from markdown-pages/blog/archived/feature-preview-variadic.mdx rename to apps/docs/markdown-pages/blog/archived/feature-preview-variadic.mdx diff --git a/markdown-pages/blog/archived/ffi-overview.mdx b/apps/docs/markdown-pages/blog/archived/ffi-overview.mdx similarity index 100% rename from markdown-pages/blog/archived/ffi-overview.mdx rename to apps/docs/markdown-pages/blog/archived/ffi-overview.mdx diff --git a/markdown-pages/blog/archived/generalize-uncurry.mdx b/apps/docs/markdown-pages/blog/archived/generalize-uncurry.mdx similarity index 100% rename from markdown-pages/blog/archived/generalize-uncurry.mdx rename to apps/docs/markdown-pages/blog/archived/generalize-uncurry.mdx diff --git a/markdown-pages/blog/archived/loading-stdlib-in-memory.mdx b/apps/docs/markdown-pages/blog/archived/loading-stdlib-in-memory.mdx similarity index 100% rename from markdown-pages/blog/archived/loading-stdlib-in-memory.mdx rename to apps/docs/markdown-pages/blog/archived/loading-stdlib-in-memory.mdx diff --git a/markdown-pages/blog/archived/overview-of-new_encoding.mdx b/apps/docs/markdown-pages/blog/archived/overview-of-new_encoding.mdx similarity index 100% rename from markdown-pages/blog/archived/overview-of-new_encoding.mdx rename to apps/docs/markdown-pages/blog/archived/overview-of-new_encoding.mdx diff --git a/markdown-pages/blog/archived/scalable.mdx b/apps/docs/markdown-pages/blog/archived/scalable.mdx similarity index 100% rename from markdown-pages/blog/archived/scalable.mdx rename to apps/docs/markdown-pages/blog/archived/scalable.mdx diff --git a/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt1.mdx b/apps/docs/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt1.mdx similarity index 100% rename from markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt1.mdx rename to apps/docs/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt1.mdx diff --git a/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt2.mdx b/apps/docs/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt2.mdx similarity index 100% rename from markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt2.mdx rename to apps/docs/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt2.mdx diff --git a/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt3.mdx b/apps/docs/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt3.mdx similarity index 100% rename from markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt3.mdx rename to apps/docs/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt3.mdx diff --git a/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt4.mdx b/apps/docs/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt4.mdx similarity index 100% rename from markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt4.mdx rename to apps/docs/markdown-pages/blog/archived/state-of-reasonml-org-2020-q2-pt4.mdx diff --git a/markdown-pages/blog/archived/string-literal-types-in-reason.mdx b/apps/docs/markdown-pages/blog/archived/string-literal-types-in-reason.mdx similarity index 100% rename from markdown-pages/blog/archived/string-literal-types-in-reason.mdx rename to apps/docs/markdown-pages/blog/archived/string-literal-types-in-reason.mdx diff --git a/markdown-pages/blog/archived/union-types-in-bucklescript.mdx b/apps/docs/markdown-pages/blog/archived/union-types-in-bucklescript.mdx similarity index 100% rename from markdown-pages/blog/archived/union-types-in-bucklescript.mdx rename to apps/docs/markdown-pages/blog/archived/union-types-in-bucklescript.mdx diff --git a/markdown-pages/blog/archived/whats-new-in-7-pt1.mdx b/apps/docs/markdown-pages/blog/archived/whats-new-in-7-pt1.mdx similarity index 100% rename from markdown-pages/blog/archived/whats-new-in-7-pt1.mdx rename to apps/docs/markdown-pages/blog/archived/whats-new-in-7-pt1.mdx diff --git a/markdown-pages/blog/archived/whats-new-in-7-pt2.mdx b/apps/docs/markdown-pages/blog/archived/whats-new-in-7-pt2.mdx similarity index 100% rename from markdown-pages/blog/archived/whats-new-in-7-pt2.mdx rename to apps/docs/markdown-pages/blog/archived/whats-new-in-7-pt2.mdx diff --git a/markdown-pages/blog/bucklescript-is-rebranding.mdx b/apps/docs/markdown-pages/blog/bucklescript-is-rebranding.mdx similarity index 100% rename from markdown-pages/blog/bucklescript-is-rebranding.mdx rename to apps/docs/markdown-pages/blog/bucklescript-is-rebranding.mdx diff --git a/markdown-pages/blog/editor-support-custom-operators-and-more.mdx b/apps/docs/markdown-pages/blog/editor-support-custom-operators-and-more.mdx similarity index 100% rename from markdown-pages/blog/editor-support-custom-operators-and-more.mdx rename to apps/docs/markdown-pages/blog/editor-support-custom-operators-and-more.mdx diff --git a/markdown-pages/blog/editor-support-release-1-0.mdx b/apps/docs/markdown-pages/blog/editor-support-release-1-0.mdx similarity index 100% rename from markdown-pages/blog/editor-support-release-1-0.mdx rename to apps/docs/markdown-pages/blog/editor-support-release-1-0.mdx diff --git a/markdown-pages/blog/enhanced-ergonomics-for-record-types.mdx b/apps/docs/markdown-pages/blog/enhanced-ergonomics-for-record-types.mdx similarity index 100% rename from markdown-pages/blog/enhanced-ergonomics-for-record-types.mdx rename to apps/docs/markdown-pages/blog/enhanced-ergonomics-for-record-types.mdx diff --git a/markdown-pages/blog/first-class-dynamic-import-support.mdx b/apps/docs/markdown-pages/blog/first-class-dynamic-import-support.mdx similarity index 100% rename from markdown-pages/blog/first-class-dynamic-import-support.mdx rename to apps/docs/markdown-pages/blog/first-class-dynamic-import-support.mdx diff --git a/markdown-pages/blog/improving-interop.mdx b/apps/docs/markdown-pages/blog/improving-interop.mdx similarity index 100% rename from markdown-pages/blog/improving-interop.mdx rename to apps/docs/markdown-pages/blog/improving-interop.mdx diff --git a/markdown-pages/blog/introducing-unified-operators.mdx b/apps/docs/markdown-pages/blog/introducing-unified-operators.mdx similarity index 100% rename from markdown-pages/blog/introducing-unified-operators.mdx rename to apps/docs/markdown-pages/blog/introducing-unified-operators.mdx diff --git a/markdown-pages/blog/new-rescript-logo.mdx b/apps/docs/markdown-pages/blog/new-rescript-logo.mdx similarity index 100% rename from markdown-pages/blog/new-rescript-logo.mdx rename to apps/docs/markdown-pages/blog/new-rescript-logo.mdx diff --git a/markdown-pages/blog/reactive-analysis.mdx b/apps/docs/markdown-pages/blog/reactive-analysis.mdx similarity index 100% rename from markdown-pages/blog/reactive-analysis.mdx rename to apps/docs/markdown-pages/blog/reactive-analysis.mdx diff --git a/markdown-pages/blog/reforging-build-system.mdx b/apps/docs/markdown-pages/blog/reforging-build-system.mdx similarity index 100% rename from markdown-pages/blog/reforging-build-system.mdx rename to apps/docs/markdown-pages/blog/reforging-build-system.mdx diff --git a/markdown-pages/blog/release-10-0-0.mdx b/apps/docs/markdown-pages/blog/release-10-0-0.mdx similarity index 100% rename from markdown-pages/blog/release-10-0-0.mdx rename to apps/docs/markdown-pages/blog/release-10-0-0.mdx diff --git a/markdown-pages/blog/release-10-1.mdx b/apps/docs/markdown-pages/blog/release-10-1.mdx similarity index 100% rename from markdown-pages/blog/release-10-1.mdx rename to apps/docs/markdown-pages/blog/release-10-1.mdx diff --git a/markdown-pages/blog/release-11-0-0.mdx b/apps/docs/markdown-pages/blog/release-11-0-0.mdx similarity index 100% rename from markdown-pages/blog/release-11-0-0.mdx rename to apps/docs/markdown-pages/blog/release-11-0-0.mdx diff --git a/markdown-pages/blog/release-11-1-0.mdx b/apps/docs/markdown-pages/blog/release-11-1-0.mdx similarity index 100% rename from markdown-pages/blog/release-11-1-0.mdx rename to apps/docs/markdown-pages/blog/release-11-1-0.mdx diff --git a/markdown-pages/blog/release-12-0-0.mdx b/apps/docs/markdown-pages/blog/release-12-0-0.mdx similarity index 100% rename from markdown-pages/blog/release-12-0-0.mdx rename to apps/docs/markdown-pages/blog/release-12-0-0.mdx diff --git a/markdown-pages/blog/release-8-3-2.mdx b/apps/docs/markdown-pages/blog/release-8-3-2.mdx similarity index 100% rename from markdown-pages/blog/release-8-3-2.mdx rename to apps/docs/markdown-pages/blog/release-8-3-2.mdx diff --git a/markdown-pages/blog/release-8-3.mdx b/apps/docs/markdown-pages/blog/release-8-3.mdx similarity index 100% rename from markdown-pages/blog/release-8-3.mdx rename to apps/docs/markdown-pages/blog/release-8-3.mdx diff --git a/markdown-pages/blog/release-8-4.mdx b/apps/docs/markdown-pages/blog/release-8-4.mdx similarity index 100% rename from markdown-pages/blog/release-8-4.mdx rename to apps/docs/markdown-pages/blog/release-8-4.mdx diff --git a/markdown-pages/blog/release-9-0.mdx b/apps/docs/markdown-pages/blog/release-9-0.mdx similarity index 100% rename from markdown-pages/blog/release-9-0.mdx rename to apps/docs/markdown-pages/blog/release-9-0.mdx diff --git a/markdown-pages/blog/release-9-1.mdx b/apps/docs/markdown-pages/blog/release-9-1.mdx similarity index 100% rename from markdown-pages/blog/release-9-1.mdx rename to apps/docs/markdown-pages/blog/release-9-1.mdx diff --git a/markdown-pages/blog/rescript-association-rebranding.mdx b/apps/docs/markdown-pages/blog/rescript-association-rebranding.mdx similarity index 100% rename from markdown-pages/blog/rescript-association-rebranding.mdx rename to apps/docs/markdown-pages/blog/rescript-association-rebranding.mdx diff --git a/markdown-pages/blog/retreats.mdx b/apps/docs/markdown-pages/blog/retreats.mdx similarity index 100% rename from markdown-pages/blog/retreats.mdx rename to apps/docs/markdown-pages/blog/retreats.mdx diff --git a/markdown-pages/blog/roadmap-2021-and-new-landing-page.mdx b/apps/docs/markdown-pages/blog/roadmap-2021-and-new-landing-page.mdx similarity index 100% rename from markdown-pages/blog/roadmap-2021-and-new-landing-page.mdx rename to apps/docs/markdown-pages/blog/roadmap-2021-and-new-landing-page.mdx diff --git a/markdown-pages/blog/uncurried-mode.mdx b/apps/docs/markdown-pages/blog/uncurried-mode.mdx similarity index 100% rename from markdown-pages/blog/uncurried-mode.mdx rename to apps/docs/markdown-pages/blog/uncurried-mode.mdx diff --git a/markdown-pages/blog/what-can-i-do-with-rescript.mdx b/apps/docs/markdown-pages/blog/what-can-i-do-with-rescript.mdx similarity index 100% rename from markdown-pages/blog/what-can-i-do-with-rescript.mdx rename to apps/docs/markdown-pages/blog/what-can-i-do-with-rescript.mdx diff --git a/markdown-pages/community/code-of-conduct.mdx b/apps/docs/markdown-pages/community/code-of-conduct.mdx similarity index 100% rename from markdown-pages/community/code-of-conduct.mdx rename to apps/docs/markdown-pages/community/code-of-conduct.mdx diff --git a/markdown-pages/community/content.mdx b/apps/docs/markdown-pages/community/content.mdx similarity index 100% rename from markdown-pages/community/content.mdx rename to apps/docs/markdown-pages/community/content.mdx diff --git a/markdown-pages/community/overview.mdx b/apps/docs/markdown-pages/community/overview.mdx similarity index 100% rename from markdown-pages/community/overview.mdx rename to apps/docs/markdown-pages/community/overview.mdx diff --git a/markdown-pages/community/roadmap.mdx b/apps/docs/markdown-pages/community/roadmap.mdx similarity index 100% rename from markdown-pages/community/roadmap.mdx rename to apps/docs/markdown-pages/community/roadmap.mdx diff --git a/markdown-pages/community/translations.mdx b/apps/docs/markdown-pages/community/translations.mdx similarity index 100% rename from markdown-pages/community/translations.mdx rename to apps/docs/markdown-pages/community/translations.mdx diff --git a/markdown-pages/docs/api/belt.json b/apps/docs/markdown-pages/docs/api/belt.json similarity index 100% rename from markdown-pages/docs/api/belt.json rename to apps/docs/markdown-pages/docs/api/belt.json diff --git a/markdown-pages/docs/api/dom.json b/apps/docs/markdown-pages/docs/api/dom.json similarity index 100% rename from markdown-pages/docs/api/dom.json rename to apps/docs/markdown-pages/docs/api/dom.json diff --git a/markdown-pages/docs/api/js.json b/apps/docs/markdown-pages/docs/api/js.json similarity index 100% rename from markdown-pages/docs/api/js.json rename to apps/docs/markdown-pages/docs/api/js.json diff --git a/markdown-pages/docs/api/stdlib.json b/apps/docs/markdown-pages/docs/api/stdlib.json similarity index 100% rename from markdown-pages/docs/api/stdlib.json rename to apps/docs/markdown-pages/docs/api/stdlib.json diff --git a/markdown-pages/docs/api/toc_tree.json b/apps/docs/markdown-pages/docs/api/toc_tree.json similarity index 100% rename from markdown-pages/docs/api/toc_tree.json rename to apps/docs/markdown-pages/docs/api/toc_tree.json diff --git a/markdown-pages/docs/guidelines/publishing-packages.mdx b/apps/docs/markdown-pages/docs/guidelines/publishing-packages.mdx similarity index 100% rename from markdown-pages/docs/guidelines/publishing-packages.mdx rename to apps/docs/markdown-pages/docs/guidelines/publishing-packages.mdx diff --git a/markdown-pages/docs/manual/api.mdx b/apps/docs/markdown-pages/docs/manual/api.mdx similarity index 100% rename from markdown-pages/docs/manual/api.mdx rename to apps/docs/markdown-pages/docs/manual/api.mdx diff --git a/markdown-pages/docs/manual/array-and-list.mdx b/apps/docs/markdown-pages/docs/manual/array-and-list.mdx similarity index 100% rename from markdown-pages/docs/manual/array-and-list.mdx rename to apps/docs/markdown-pages/docs/manual/array-and-list.mdx diff --git a/markdown-pages/docs/manual/async-await.mdx b/apps/docs/markdown-pages/docs/manual/async-await.mdx similarity index 100% rename from markdown-pages/docs/manual/async-await.mdx rename to apps/docs/markdown-pages/docs/manual/async-await.mdx diff --git a/markdown-pages/docs/manual/attribute.mdx b/apps/docs/markdown-pages/docs/manual/attribute.mdx similarity index 100% rename from markdown-pages/docs/manual/attribute.mdx rename to apps/docs/markdown-pages/docs/manual/attribute.mdx diff --git a/markdown-pages/docs/manual/bind-to-global-js-values.mdx b/apps/docs/markdown-pages/docs/manual/bind-to-global-js-values.mdx similarity index 100% rename from markdown-pages/docs/manual/bind-to-global-js-values.mdx rename to apps/docs/markdown-pages/docs/manual/bind-to-global-js-values.mdx diff --git a/markdown-pages/docs/manual/bind-to-js-function.mdx b/apps/docs/markdown-pages/docs/manual/bind-to-js-function.mdx similarity index 100% rename from markdown-pages/docs/manual/bind-to-js-function.mdx rename to apps/docs/markdown-pages/docs/manual/bind-to-js-function.mdx diff --git a/markdown-pages/docs/manual/bind-to-js-object.mdx b/apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx similarity index 100% rename from markdown-pages/docs/manual/bind-to-js-object.mdx rename to apps/docs/markdown-pages/docs/manual/bind-to-js-object.mdx diff --git a/markdown-pages/docs/manual/build-configuration-schema.mdx b/apps/docs/markdown-pages/docs/manual/build-configuration-schema.mdx similarity index 100% rename from markdown-pages/docs/manual/build-configuration-schema.mdx rename to apps/docs/markdown-pages/docs/manual/build-configuration-schema.mdx diff --git a/markdown-pages/docs/manual/build-configuration.mdx b/apps/docs/markdown-pages/docs/manual/build-configuration.mdx similarity index 100% rename from markdown-pages/docs/manual/build-configuration.mdx rename to apps/docs/markdown-pages/docs/manual/build-configuration.mdx diff --git a/markdown-pages/docs/manual/build-monorepo-setup.mdx b/apps/docs/markdown-pages/docs/manual/build-monorepo-setup.mdx similarity index 100% rename from markdown-pages/docs/manual/build-monorepo-setup.mdx rename to apps/docs/markdown-pages/docs/manual/build-monorepo-setup.mdx diff --git a/markdown-pages/docs/manual/build-overview.mdx b/apps/docs/markdown-pages/docs/manual/build-overview.mdx similarity index 100% rename from markdown-pages/docs/manual/build-overview.mdx rename to apps/docs/markdown-pages/docs/manual/build-overview.mdx diff --git a/markdown-pages/docs/manual/build-performance.mdx b/apps/docs/markdown-pages/docs/manual/build-performance.mdx similarity index 100% rename from markdown-pages/docs/manual/build-performance.mdx rename to apps/docs/markdown-pages/docs/manual/build-performance.mdx diff --git a/markdown-pages/docs/manual/control-flow.mdx b/apps/docs/markdown-pages/docs/manual/control-flow.mdx similarity index 100% rename from markdown-pages/docs/manual/control-flow.mdx rename to apps/docs/markdown-pages/docs/manual/control-flow.mdx diff --git a/markdown-pages/docs/manual/converting-from-js.mdx b/apps/docs/markdown-pages/docs/manual/converting-from-js.mdx similarity index 100% rename from markdown-pages/docs/manual/converting-from-js.mdx rename to apps/docs/markdown-pages/docs/manual/converting-from-js.mdx diff --git a/markdown-pages/docs/manual/dict.mdx b/apps/docs/markdown-pages/docs/manual/dict.mdx similarity index 100% rename from markdown-pages/docs/manual/dict.mdx rename to apps/docs/markdown-pages/docs/manual/dict.mdx diff --git a/markdown-pages/docs/manual/editor-code-analysis.mdx b/apps/docs/markdown-pages/docs/manual/editor-code-analysis.mdx similarity index 100% rename from markdown-pages/docs/manual/editor-code-analysis.mdx rename to apps/docs/markdown-pages/docs/manual/editor-code-analysis.mdx diff --git a/markdown-pages/docs/manual/editor-plugins.mdx b/apps/docs/markdown-pages/docs/manual/editor-plugins.mdx similarity index 100% rename from markdown-pages/docs/manual/editor-plugins.mdx rename to apps/docs/markdown-pages/docs/manual/editor-plugins.mdx diff --git a/markdown-pages/docs/manual/embed-raw-javascript.mdx b/apps/docs/markdown-pages/docs/manual/embed-raw-javascript.mdx similarity index 100% rename from markdown-pages/docs/manual/embed-raw-javascript.mdx rename to apps/docs/markdown-pages/docs/manual/embed-raw-javascript.mdx diff --git a/markdown-pages/docs/manual/equality-comparison.mdx b/apps/docs/markdown-pages/docs/manual/equality-comparison.mdx similarity index 100% rename from markdown-pages/docs/manual/equality-comparison.mdx rename to apps/docs/markdown-pages/docs/manual/equality-comparison.mdx diff --git a/markdown-pages/docs/manual/exception.mdx b/apps/docs/markdown-pages/docs/manual/exception.mdx similarity index 100% rename from markdown-pages/docs/manual/exception.mdx rename to apps/docs/markdown-pages/docs/manual/exception.mdx diff --git a/markdown-pages/docs/manual/extensible-variant.mdx b/apps/docs/markdown-pages/docs/manual/extensible-variant.mdx similarity index 100% rename from markdown-pages/docs/manual/extensible-variant.mdx rename to apps/docs/markdown-pages/docs/manual/extensible-variant.mdx diff --git a/markdown-pages/docs/manual/external.mdx b/apps/docs/markdown-pages/docs/manual/external.mdx similarity index 100% rename from markdown-pages/docs/manual/external.mdx rename to apps/docs/markdown-pages/docs/manual/external.mdx diff --git a/markdown-pages/docs/manual/function.mdx b/apps/docs/markdown-pages/docs/manual/function.mdx similarity index 100% rename from markdown-pages/docs/manual/function.mdx rename to apps/docs/markdown-pages/docs/manual/function.mdx diff --git a/markdown-pages/docs/manual/generalized-algebraic-data-types.mdx b/apps/docs/markdown-pages/docs/manual/generalized-algebraic-data-types.mdx similarity index 100% rename from markdown-pages/docs/manual/generalized-algebraic-data-types.mdx rename to apps/docs/markdown-pages/docs/manual/generalized-algebraic-data-types.mdx diff --git a/markdown-pages/docs/manual/generate-converters-accessors.mdx b/apps/docs/markdown-pages/docs/manual/generate-converters-accessors.mdx similarity index 100% rename from markdown-pages/docs/manual/generate-converters-accessors.mdx rename to apps/docs/markdown-pages/docs/manual/generate-converters-accessors.mdx diff --git a/markdown-pages/docs/manual/import-export.mdx b/apps/docs/markdown-pages/docs/manual/import-export.mdx similarity index 100% rename from markdown-pages/docs/manual/import-export.mdx rename to apps/docs/markdown-pages/docs/manual/import-export.mdx diff --git a/markdown-pages/docs/manual/import-from-export-to-js.mdx b/apps/docs/markdown-pages/docs/manual/import-from-export-to-js.mdx similarity index 100% rename from markdown-pages/docs/manual/import-from-export-to-js.mdx rename to apps/docs/markdown-pages/docs/manual/import-from-export-to-js.mdx diff --git a/markdown-pages/docs/manual/inlining-constants.mdx b/apps/docs/markdown-pages/docs/manual/inlining-constants.mdx similarity index 100% rename from markdown-pages/docs/manual/inlining-constants.mdx rename to apps/docs/markdown-pages/docs/manual/inlining-constants.mdx diff --git a/markdown-pages/docs/manual/installation.mdx b/apps/docs/markdown-pages/docs/manual/installation.mdx similarity index 100% rename from markdown-pages/docs/manual/installation.mdx rename to apps/docs/markdown-pages/docs/manual/installation.mdx diff --git a/markdown-pages/docs/manual/interop-cheatsheet.mdx b/apps/docs/markdown-pages/docs/manual/interop-cheatsheet.mdx similarity index 100% rename from markdown-pages/docs/manual/interop-cheatsheet.mdx rename to apps/docs/markdown-pages/docs/manual/interop-cheatsheet.mdx diff --git a/markdown-pages/docs/manual/interop-with-js-build-systems.mdx b/apps/docs/markdown-pages/docs/manual/interop-with-js-build-systems.mdx similarity index 100% rename from markdown-pages/docs/manual/interop-with-js-build-systems.mdx rename to apps/docs/markdown-pages/docs/manual/interop-with-js-build-systems.mdx diff --git a/markdown-pages/docs/manual/introduction.mdx b/apps/docs/markdown-pages/docs/manual/introduction.mdx similarity index 100% rename from markdown-pages/docs/manual/introduction.mdx rename to apps/docs/markdown-pages/docs/manual/introduction.mdx diff --git a/markdown-pages/docs/manual/json.mdx b/apps/docs/markdown-pages/docs/manual/json.mdx similarity index 100% rename from markdown-pages/docs/manual/json.mdx rename to apps/docs/markdown-pages/docs/manual/json.mdx diff --git a/markdown-pages/docs/manual/jsx.mdx b/apps/docs/markdown-pages/docs/manual/jsx.mdx similarity index 100% rename from markdown-pages/docs/manual/jsx.mdx rename to apps/docs/markdown-pages/docs/manual/jsx.mdx diff --git a/markdown-pages/docs/manual/lazy-values.mdx b/apps/docs/markdown-pages/docs/manual/lazy-values.mdx similarity index 100% rename from markdown-pages/docs/manual/lazy-values.mdx rename to apps/docs/markdown-pages/docs/manual/lazy-values.mdx diff --git a/markdown-pages/docs/manual/let-binding.mdx b/apps/docs/markdown-pages/docs/manual/let-binding.mdx similarity index 100% rename from markdown-pages/docs/manual/let-binding.mdx rename to apps/docs/markdown-pages/docs/manual/let-binding.mdx diff --git a/markdown-pages/docs/manual/libraries.mdx b/apps/docs/markdown-pages/docs/manual/libraries.mdx similarity index 100% rename from markdown-pages/docs/manual/libraries.mdx rename to apps/docs/markdown-pages/docs/manual/libraries.mdx diff --git a/markdown-pages/docs/manual/llms.mdx b/apps/docs/markdown-pages/docs/manual/llms.mdx similarity index 100% rename from markdown-pages/docs/manual/llms.mdx rename to apps/docs/markdown-pages/docs/manual/llms.mdx diff --git a/markdown-pages/docs/manual/migrate-to-v11.mdx b/apps/docs/markdown-pages/docs/manual/migrate-to-v11.mdx similarity index 100% rename from markdown-pages/docs/manual/migrate-to-v11.mdx rename to apps/docs/markdown-pages/docs/manual/migrate-to-v11.mdx diff --git a/markdown-pages/docs/manual/migrate-to-v12.mdx b/apps/docs/markdown-pages/docs/manual/migrate-to-v12.mdx similarity index 100% rename from markdown-pages/docs/manual/migrate-to-v12.mdx rename to apps/docs/markdown-pages/docs/manual/migrate-to-v12.mdx diff --git a/markdown-pages/docs/manual/module-functions.mdx b/apps/docs/markdown-pages/docs/manual/module-functions.mdx similarity index 100% rename from markdown-pages/docs/manual/module-functions.mdx rename to apps/docs/markdown-pages/docs/manual/module-functions.mdx diff --git a/markdown-pages/docs/manual/module.mdx b/apps/docs/markdown-pages/docs/manual/module.mdx similarity index 100% rename from markdown-pages/docs/manual/module.mdx rename to apps/docs/markdown-pages/docs/manual/module.mdx diff --git a/markdown-pages/docs/manual/mutation.mdx b/apps/docs/markdown-pages/docs/manual/mutation.mdx similarity index 100% rename from markdown-pages/docs/manual/mutation.mdx rename to apps/docs/markdown-pages/docs/manual/mutation.mdx diff --git a/markdown-pages/docs/manual/null-undefined-option.mdx b/apps/docs/markdown-pages/docs/manual/null-undefined-option.mdx similarity index 100% rename from markdown-pages/docs/manual/null-undefined-option.mdx rename to apps/docs/markdown-pages/docs/manual/null-undefined-option.mdx diff --git a/markdown-pages/docs/manual/object.mdx b/apps/docs/markdown-pages/docs/manual/object.mdx similarity index 100% rename from markdown-pages/docs/manual/object.mdx rename to apps/docs/markdown-pages/docs/manual/object.mdx diff --git a/markdown-pages/docs/manual/overview.mdx b/apps/docs/markdown-pages/docs/manual/overview.mdx similarity index 100% rename from markdown-pages/docs/manual/overview.mdx rename to apps/docs/markdown-pages/docs/manual/overview.mdx diff --git a/markdown-pages/docs/manual/pattern-matching-destructuring.mdx b/apps/docs/markdown-pages/docs/manual/pattern-matching-destructuring.mdx similarity index 100% rename from markdown-pages/docs/manual/pattern-matching-destructuring.mdx rename to apps/docs/markdown-pages/docs/manual/pattern-matching-destructuring.mdx diff --git a/markdown-pages/docs/manual/pipe.mdx b/apps/docs/markdown-pages/docs/manual/pipe.mdx similarity index 100% rename from markdown-pages/docs/manual/pipe.mdx rename to apps/docs/markdown-pages/docs/manual/pipe.mdx diff --git a/markdown-pages/docs/manual/polymorphic-variant.mdx b/apps/docs/markdown-pages/docs/manual/polymorphic-variant.mdx similarity index 100% rename from markdown-pages/docs/manual/polymorphic-variant.mdx rename to apps/docs/markdown-pages/docs/manual/polymorphic-variant.mdx diff --git a/markdown-pages/docs/manual/primitive-types.mdx b/apps/docs/markdown-pages/docs/manual/primitive-types.mdx similarity index 100% rename from markdown-pages/docs/manual/primitive-types.mdx rename to apps/docs/markdown-pages/docs/manual/primitive-types.mdx diff --git a/markdown-pages/docs/manual/project-structure.mdx b/apps/docs/markdown-pages/docs/manual/project-structure.mdx similarity index 100% rename from markdown-pages/docs/manual/project-structure.mdx rename to apps/docs/markdown-pages/docs/manual/project-structure.mdx diff --git a/markdown-pages/docs/manual/promise.mdx b/apps/docs/markdown-pages/docs/manual/promise.mdx similarity index 100% rename from markdown-pages/docs/manual/promise.mdx rename to apps/docs/markdown-pages/docs/manual/promise.mdx diff --git a/markdown-pages/docs/manual/record.mdx b/apps/docs/markdown-pages/docs/manual/record.mdx similarity index 100% rename from markdown-pages/docs/manual/record.mdx rename to apps/docs/markdown-pages/docs/manual/record.mdx diff --git a/markdown-pages/docs/manual/rescript-for-javascript-developers.mdx b/apps/docs/markdown-pages/docs/manual/rescript-for-javascript-developers.mdx similarity index 100% rename from markdown-pages/docs/manual/rescript-for-javascript-developers.mdx rename to apps/docs/markdown-pages/docs/manual/rescript-for-javascript-developers.mdx diff --git a/markdown-pages/docs/manual/reserved-keywords.mdx b/apps/docs/markdown-pages/docs/manual/reserved-keywords.mdx similarity index 100% rename from markdown-pages/docs/manual/reserved-keywords.mdx rename to apps/docs/markdown-pages/docs/manual/reserved-keywords.mdx diff --git a/markdown-pages/docs/manual/scoped-polymorphic-types.mdx b/apps/docs/markdown-pages/docs/manual/scoped-polymorphic-types.mdx similarity index 100% rename from markdown-pages/docs/manual/scoped-polymorphic-types.mdx rename to apps/docs/markdown-pages/docs/manual/scoped-polymorphic-types.mdx diff --git a/markdown-pages/docs/manual/shared-data-types.mdx b/apps/docs/markdown-pages/docs/manual/shared-data-types.mdx similarity index 100% rename from markdown-pages/docs/manual/shared-data-types.mdx rename to apps/docs/markdown-pages/docs/manual/shared-data-types.mdx diff --git a/markdown-pages/docs/manual/tagged-templates.mdx b/apps/docs/markdown-pages/docs/manual/tagged-templates.mdx similarity index 100% rename from markdown-pages/docs/manual/tagged-templates.mdx rename to apps/docs/markdown-pages/docs/manual/tagged-templates.mdx diff --git a/markdown-pages/docs/manual/try.mdx b/apps/docs/markdown-pages/docs/manual/try.mdx similarity index 100% rename from markdown-pages/docs/manual/try.mdx rename to apps/docs/markdown-pages/docs/manual/try.mdx diff --git a/markdown-pages/docs/manual/tuple.mdx b/apps/docs/markdown-pages/docs/manual/tuple.mdx similarity index 100% rename from markdown-pages/docs/manual/tuple.mdx rename to apps/docs/markdown-pages/docs/manual/tuple.mdx diff --git a/markdown-pages/docs/manual/type.mdx b/apps/docs/markdown-pages/docs/manual/type.mdx similarity index 100% rename from markdown-pages/docs/manual/type.mdx rename to apps/docs/markdown-pages/docs/manual/type.mdx diff --git a/markdown-pages/docs/manual/typescript-integration.mdx b/apps/docs/markdown-pages/docs/manual/typescript-integration.mdx similarity index 100% rename from markdown-pages/docs/manual/typescript-integration.mdx rename to apps/docs/markdown-pages/docs/manual/typescript-integration.mdx diff --git a/markdown-pages/docs/manual/use-illegal-identifier-names.mdx b/apps/docs/markdown-pages/docs/manual/use-illegal-identifier-names.mdx similarity index 100% rename from markdown-pages/docs/manual/use-illegal-identifier-names.mdx rename to apps/docs/markdown-pages/docs/manual/use-illegal-identifier-names.mdx diff --git a/markdown-pages/docs/manual/variant.mdx b/apps/docs/markdown-pages/docs/manual/variant.mdx similarity index 100% rename from markdown-pages/docs/manual/variant.mdx rename to apps/docs/markdown-pages/docs/manual/variant.mdx diff --git a/markdown-pages/docs/manual/warning-numbers.mdx b/apps/docs/markdown-pages/docs/manual/warning-numbers.mdx similarity index 100% rename from markdown-pages/docs/manual/warning-numbers.mdx rename to apps/docs/markdown-pages/docs/manual/warning-numbers.mdx diff --git a/markdown-pages/docs/react/arrays-and-keys.mdx b/apps/docs/markdown-pages/docs/react/arrays-and-keys.mdx similarity index 100% rename from markdown-pages/docs/react/arrays-and-keys.mdx rename to apps/docs/markdown-pages/docs/react/arrays-and-keys.mdx diff --git a/markdown-pages/docs/react/beyond-jsx.mdx b/apps/docs/markdown-pages/docs/react/beyond-jsx.mdx similarity index 100% rename from markdown-pages/docs/react/beyond-jsx.mdx rename to apps/docs/markdown-pages/docs/react/beyond-jsx.mdx diff --git a/markdown-pages/docs/react/components-and-props.mdx b/apps/docs/markdown-pages/docs/react/components-and-props.mdx similarity index 100% rename from markdown-pages/docs/react/components-and-props.mdx rename to apps/docs/markdown-pages/docs/react/components-and-props.mdx diff --git a/markdown-pages/docs/react/context.mdx b/apps/docs/markdown-pages/docs/react/context.mdx similarity index 100% rename from markdown-pages/docs/react/context.mdx rename to apps/docs/markdown-pages/docs/react/context.mdx diff --git a/markdown-pages/docs/react/elements-and-jsx.mdx b/apps/docs/markdown-pages/docs/react/elements-and-jsx.mdx similarity index 100% rename from markdown-pages/docs/react/elements-and-jsx.mdx rename to apps/docs/markdown-pages/docs/react/elements-and-jsx.mdx diff --git a/markdown-pages/docs/react/events.mdx b/apps/docs/markdown-pages/docs/react/events.mdx similarity index 100% rename from markdown-pages/docs/react/events.mdx rename to apps/docs/markdown-pages/docs/react/events.mdx diff --git a/markdown-pages/docs/react/extensions-of-props.mdx b/apps/docs/markdown-pages/docs/react/extensions-of-props.mdx similarity index 100% rename from markdown-pages/docs/react/extensions-of-props.mdx rename to apps/docs/markdown-pages/docs/react/extensions-of-props.mdx diff --git a/markdown-pages/docs/react/forwarding-refs.mdx b/apps/docs/markdown-pages/docs/react/forwarding-refs.mdx similarity index 100% rename from markdown-pages/docs/react/forwarding-refs.mdx rename to apps/docs/markdown-pages/docs/react/forwarding-refs.mdx diff --git a/markdown-pages/docs/react/hooks-context.mdx b/apps/docs/markdown-pages/docs/react/hooks-context.mdx similarity index 100% rename from markdown-pages/docs/react/hooks-context.mdx rename to apps/docs/markdown-pages/docs/react/hooks-context.mdx diff --git a/markdown-pages/docs/react/hooks-custom.mdx b/apps/docs/markdown-pages/docs/react/hooks-custom.mdx similarity index 100% rename from markdown-pages/docs/react/hooks-custom.mdx rename to apps/docs/markdown-pages/docs/react/hooks-custom.mdx diff --git a/markdown-pages/docs/react/hooks-effect.mdx b/apps/docs/markdown-pages/docs/react/hooks-effect.mdx similarity index 100% rename from markdown-pages/docs/react/hooks-effect.mdx rename to apps/docs/markdown-pages/docs/react/hooks-effect.mdx diff --git a/markdown-pages/docs/react/hooks-overview.mdx b/apps/docs/markdown-pages/docs/react/hooks-overview.mdx similarity index 100% rename from markdown-pages/docs/react/hooks-overview.mdx rename to apps/docs/markdown-pages/docs/react/hooks-overview.mdx diff --git a/markdown-pages/docs/react/hooks-reducer.mdx b/apps/docs/markdown-pages/docs/react/hooks-reducer.mdx similarity index 100% rename from markdown-pages/docs/react/hooks-reducer.mdx rename to apps/docs/markdown-pages/docs/react/hooks-reducer.mdx diff --git a/markdown-pages/docs/react/hooks-ref.mdx b/apps/docs/markdown-pages/docs/react/hooks-ref.mdx similarity index 100% rename from markdown-pages/docs/react/hooks-ref.mdx rename to apps/docs/markdown-pages/docs/react/hooks-ref.mdx diff --git a/markdown-pages/docs/react/hooks-state.mdx b/apps/docs/markdown-pages/docs/react/hooks-state.mdx similarity index 100% rename from markdown-pages/docs/react/hooks-state.mdx rename to apps/docs/markdown-pages/docs/react/hooks-state.mdx diff --git a/markdown-pages/docs/react/import-export-reactjs.mdx b/apps/docs/markdown-pages/docs/react/import-export-reactjs.mdx similarity index 100% rename from markdown-pages/docs/react/import-export-reactjs.mdx rename to apps/docs/markdown-pages/docs/react/import-export-reactjs.mdx diff --git a/markdown-pages/docs/react/installation.mdx b/apps/docs/markdown-pages/docs/react/installation.mdx similarity index 100% rename from markdown-pages/docs/react/installation.mdx rename to apps/docs/markdown-pages/docs/react/installation.mdx diff --git a/markdown-pages/docs/react/introduction.mdx b/apps/docs/markdown-pages/docs/react/introduction.mdx similarity index 100% rename from markdown-pages/docs/react/introduction.mdx rename to apps/docs/markdown-pages/docs/react/introduction.mdx diff --git a/markdown-pages/docs/react/lazy-components.mdx b/apps/docs/markdown-pages/docs/react/lazy-components.mdx similarity index 100% rename from markdown-pages/docs/react/lazy-components.mdx rename to apps/docs/markdown-pages/docs/react/lazy-components.mdx diff --git a/markdown-pages/docs/react/llms.mdx b/apps/docs/markdown-pages/docs/react/llms.mdx similarity index 100% rename from markdown-pages/docs/react/llms.mdx rename to apps/docs/markdown-pages/docs/react/llms.mdx diff --git a/markdown-pages/docs/react/memo.mdx b/apps/docs/markdown-pages/docs/react/memo.mdx similarity index 100% rename from markdown-pages/docs/react/memo.mdx rename to apps/docs/markdown-pages/docs/react/memo.mdx diff --git a/markdown-pages/docs/react/refs-and-the-dom.mdx b/apps/docs/markdown-pages/docs/react/refs-and-the-dom.mdx similarity index 100% rename from markdown-pages/docs/react/refs-and-the-dom.mdx rename to apps/docs/markdown-pages/docs/react/refs-and-the-dom.mdx diff --git a/markdown-pages/docs/react/rendering-elements.mdx b/apps/docs/markdown-pages/docs/react/rendering-elements.mdx similarity index 100% rename from markdown-pages/docs/react/rendering-elements.mdx rename to apps/docs/markdown-pages/docs/react/rendering-elements.mdx diff --git a/markdown-pages/docs/react/router.mdx b/apps/docs/markdown-pages/docs/react/router.mdx similarity index 100% rename from markdown-pages/docs/react/router.mdx rename to apps/docs/markdown-pages/docs/react/router.mdx diff --git a/markdown-pages/docs/react/server-components.mdx b/apps/docs/markdown-pages/docs/react/server-components.mdx similarity index 100% rename from markdown-pages/docs/react/server-components.mdx rename to apps/docs/markdown-pages/docs/react/server-components.mdx diff --git a/markdown-pages/docs/react/styling.mdx b/apps/docs/markdown-pages/docs/react/styling.mdx similarity index 100% rename from markdown-pages/docs/react/styling.mdx rename to apps/docs/markdown-pages/docs/react/styling.mdx diff --git a/markdown-pages/syntax-lookup/decorator_as.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_as.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_as.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_as.mdx diff --git a/markdown-pages/syntax-lookup/decorator_dead.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_dead.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_dead.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_dead.mdx diff --git a/markdown-pages/syntax-lookup/decorator_deriving.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_deriving.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_deriving.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_deriving.mdx diff --git a/markdown-pages/syntax-lookup/decorator_directive.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_directive.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_directive.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_directive.mdx diff --git a/markdown-pages/syntax-lookup/decorator_does_not_raise.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_does_not_raise.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_does_not_raise.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_does_not_raise.mdx diff --git a/markdown-pages/syntax-lookup/decorator_does_not_throw.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_does_not_throw.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_does_not_throw.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_does_not_throw.mdx diff --git a/markdown-pages/syntax-lookup/decorator_expression_deprecated.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_expression_deprecated.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_expression_deprecated.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_expression_deprecated.mdx diff --git a/markdown-pages/syntax-lookup/decorator_expression_warning.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_expression_warning.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_expression_warning.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_expression_warning.mdx diff --git a/markdown-pages/syntax-lookup/decorator_gentype.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_gentype.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_gentype.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_gentype.mdx diff --git a/markdown-pages/syntax-lookup/decorator_get.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_get.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_get.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_get.mdx diff --git a/markdown-pages/syntax-lookup/decorator_get_index.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_get_index.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_get_index.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_get_index.mdx diff --git a/markdown-pages/syntax-lookup/decorator_ignore.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_ignore.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_ignore.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_ignore.mdx diff --git a/markdown-pages/syntax-lookup/decorator_inline.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_inline.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_inline.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_inline.mdx diff --git a/markdown-pages/syntax-lookup/decorator_int.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_int.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_int.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_int.mdx diff --git a/markdown-pages/syntax-lookup/decorator_jsx_component.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_jsx_component.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_jsx_component.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_jsx_component.mdx diff --git a/markdown-pages/syntax-lookup/decorator_live.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_live.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_live.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_live.mdx diff --git a/markdown-pages/syntax-lookup/decorator_meth.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_meth.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_meth.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_meth.mdx diff --git a/markdown-pages/syntax-lookup/decorator_module.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_module.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_module.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_module.mdx diff --git a/markdown-pages/syntax-lookup/decorator_module_deprecated.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_module_deprecated.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_module_deprecated.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_module_deprecated.mdx diff --git a/markdown-pages/syntax-lookup/decorator_module_warning.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_module_warning.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_module_warning.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_module_warning.mdx diff --git a/markdown-pages/syntax-lookup/decorator_new.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_new.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_new.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_new.mdx diff --git a/markdown-pages/syntax-lookup/decorator_not_undefined.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_not_undefined.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_not_undefined.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_not_undefined.mdx diff --git a/markdown-pages/syntax-lookup/decorator_raises.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_raises.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_raises.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_raises.mdx diff --git a/markdown-pages/syntax-lookup/decorator_react_component.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_react_component.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_react_component.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_react_component.mdx diff --git a/markdown-pages/syntax-lookup/decorator_react_component_with_props.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_react_component_with_props.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_react_component_with_props.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_react_component_with_props.mdx diff --git a/markdown-pages/syntax-lookup/decorator_return.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_return.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_return.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_return.mdx diff --git a/markdown-pages/syntax-lookup/decorator_scope.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_scope.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_scope.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_scope.mdx diff --git a/markdown-pages/syntax-lookup/decorator_send.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_send.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_send.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_send.mdx diff --git a/markdown-pages/syntax-lookup/decorator_send_pipe.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_send_pipe.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_send_pipe.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_send_pipe.mdx diff --git a/markdown-pages/syntax-lookup/decorator_set.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_set.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_set.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_set.mdx diff --git a/markdown-pages/syntax-lookup/decorator_set_index.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_set_index.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_set_index.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_set_index.mdx diff --git a/markdown-pages/syntax-lookup/decorator_string.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_string.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_string.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_string.mdx diff --git a/markdown-pages/syntax-lookup/decorator_tag.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_tag.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_tag.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_tag.mdx diff --git a/markdown-pages/syntax-lookup/decorator_taggedTemplate.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_taggedTemplate.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_taggedTemplate.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_taggedTemplate.mdx diff --git a/markdown-pages/syntax-lookup/decorator_this.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_this.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_this.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_this.mdx diff --git a/markdown-pages/syntax-lookup/decorator_throws.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_throws.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_throws.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_throws.mdx diff --git a/markdown-pages/syntax-lookup/decorator_unboxed.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_unboxed.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_unboxed.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_unboxed.mdx diff --git a/markdown-pages/syntax-lookup/decorator_unwrap.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_unwrap.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_unwrap.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_unwrap.mdx diff --git a/markdown-pages/syntax-lookup/decorator_val.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_val.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_val.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_val.mdx diff --git a/markdown-pages/syntax-lookup/decorator_variadic.mdx b/apps/docs/markdown-pages/syntax-lookup/decorator_variadic.mdx similarity index 100% rename from markdown-pages/syntax-lookup/decorator_variadic.mdx rename to apps/docs/markdown-pages/syntax-lookup/decorator_variadic.mdx diff --git a/markdown-pages/syntax-lookup/extension_debugger.mdx b/apps/docs/markdown-pages/syntax-lookup/extension_debugger.mdx similarity index 100% rename from markdown-pages/syntax-lookup/extension_debugger.mdx rename to apps/docs/markdown-pages/syntax-lookup/extension_debugger.mdx diff --git a/markdown-pages/syntax-lookup/extension_identity.mdx b/apps/docs/markdown-pages/syntax-lookup/extension_identity.mdx similarity index 100% rename from markdown-pages/syntax-lookup/extension_identity.mdx rename to apps/docs/markdown-pages/syntax-lookup/extension_identity.mdx diff --git a/markdown-pages/syntax-lookup/extension_private_let.mdx b/apps/docs/markdown-pages/syntax-lookup/extension_private_let.mdx similarity index 100% rename from markdown-pages/syntax-lookup/extension_private_let.mdx rename to apps/docs/markdown-pages/syntax-lookup/extension_private_let.mdx diff --git a/markdown-pages/syntax-lookup/extension_raw_expression.mdx b/apps/docs/markdown-pages/syntax-lookup/extension_raw_expression.mdx similarity index 100% rename from markdown-pages/syntax-lookup/extension_raw_expression.mdx rename to apps/docs/markdown-pages/syntax-lookup/extension_raw_expression.mdx diff --git a/markdown-pages/syntax-lookup/extension_raw_top_level_expression.mdx b/apps/docs/markdown-pages/syntax-lookup/extension_raw_top_level_expression.mdx similarity index 100% rename from markdown-pages/syntax-lookup/extension_raw_top_level_expression.mdx rename to apps/docs/markdown-pages/syntax-lookup/extension_raw_top_level_expression.mdx diff --git a/markdown-pages/syntax-lookup/extension_regular_expression.mdx b/apps/docs/markdown-pages/syntax-lookup/extension_regular_expression.mdx similarity index 100% rename from markdown-pages/syntax-lookup/extension_regular_expression.mdx rename to apps/docs/markdown-pages/syntax-lookup/extension_regular_expression.mdx diff --git a/markdown-pages/syntax-lookup/extension_todo.mdx b/apps/docs/markdown-pages/syntax-lookup/extension_todo.mdx similarity index 100% rename from markdown-pages/syntax-lookup/extension_todo.mdx rename to apps/docs/markdown-pages/syntax-lookup/extension_todo.mdx diff --git a/markdown-pages/syntax-lookup/language_and.mdx b/apps/docs/markdown-pages/syntax-lookup/language_and.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_and.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_and.mdx diff --git a/markdown-pages/syntax-lookup/language_async.mdx b/apps/docs/markdown-pages/syntax-lookup/language_async.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_async.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_async.mdx diff --git a/markdown-pages/syntax-lookup/language_attached_doc_comment.mdx b/apps/docs/markdown-pages/syntax-lookup/language_attached_doc_comment.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_attached_doc_comment.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_attached_doc_comment.mdx diff --git a/markdown-pages/syntax-lookup/language_await.mdx b/apps/docs/markdown-pages/syntax-lookup/language_await.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_await.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_await.mdx diff --git a/markdown-pages/syntax-lookup/language_block_comment.mdx b/apps/docs/markdown-pages/syntax-lookup/language_block_comment.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_block_comment.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_block_comment.mdx diff --git a/markdown-pages/syntax-lookup/language_char_literal.mdx b/apps/docs/markdown-pages/syntax-lookup/language_char_literal.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_char_literal.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_char_literal.mdx diff --git a/markdown-pages/syntax-lookup/language_covariant_type_parameter.mdx b/apps/docs/markdown-pages/syntax-lookup/language_covariant_type_parameter.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_covariant_type_parameter.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_covariant_type_parameter.mdx diff --git a/markdown-pages/syntax-lookup/language_dict.mdx b/apps/docs/markdown-pages/syntax-lookup/language_dict.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_dict.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_dict.mdx diff --git a/markdown-pages/syntax-lookup/language_doc_comment.mdx b/apps/docs/markdown-pages/syntax-lookup/language_doc_comment.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_doc_comment.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_doc_comment.mdx diff --git a/markdown-pages/syntax-lookup/language_empty_object_type.mdx b/apps/docs/markdown-pages/syntax-lookup/language_empty_object_type.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_empty_object_type.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_empty_object_type.mdx diff --git a/markdown-pages/syntax-lookup/language_exception.mdx b/apps/docs/markdown-pages/syntax-lookup/language_exception.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_exception.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_exception.mdx diff --git a/markdown-pages/syntax-lookup/language_external.mdx b/apps/docs/markdown-pages/syntax-lookup/language_external.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_external.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_external.mdx diff --git a/markdown-pages/syntax-lookup/language_for.mdx b/apps/docs/markdown-pages/syntax-lookup/language_for.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_for.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_for.mdx diff --git a/markdown-pages/syntax-lookup/language_function.mdx b/apps/docs/markdown-pages/syntax-lookup/language_function.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_function.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_function.mdx diff --git a/markdown-pages/syntax-lookup/language_if_else.mdx b/apps/docs/markdown-pages/syntax-lookup/language_if_else.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_if_else.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_if_else.mdx diff --git a/markdown-pages/syntax-lookup/language_include.mdx b/apps/docs/markdown-pages/syntax-lookup/language_include.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_include.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_include.mdx diff --git a/markdown-pages/syntax-lookup/language_jsx_component.mdx b/apps/docs/markdown-pages/syntax-lookup/language_jsx_component.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_jsx_component.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_jsx_component.mdx diff --git a/markdown-pages/syntax-lookup/language_labeled_argument.mdx b/apps/docs/markdown-pages/syntax-lookup/language_labeled_argument.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_labeled_argument.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_labeled_argument.mdx diff --git a/markdown-pages/syntax-lookup/language_let.mdx b/apps/docs/markdown-pages/syntax-lookup/language_let.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_let.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_let.mdx diff --git a/markdown-pages/syntax-lookup/language_let_rec.mdx b/apps/docs/markdown-pages/syntax-lookup/language_let_rec.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_let_rec.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_let_rec.mdx diff --git a/markdown-pages/syntax-lookup/language_line_comment.mdx b/apps/docs/markdown-pages/syntax-lookup/language_line_comment.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_line_comment.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_line_comment.mdx diff --git a/markdown-pages/syntax-lookup/language_module.mdx b/apps/docs/markdown-pages/syntax-lookup/language_module.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_module.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_module.mdx diff --git a/markdown-pages/syntax-lookup/language_module_function.mdx b/apps/docs/markdown-pages/syntax-lookup/language_module_function.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_module_function.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_module_function.mdx diff --git a/markdown-pages/syntax-lookup/language_module_type.mdx b/apps/docs/markdown-pages/syntax-lookup/language_module_type.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_module_type.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_module_type.mdx diff --git a/markdown-pages/syntax-lookup/language_module_type_of.mdx b/apps/docs/markdown-pages/syntax-lookup/language_module_type_of.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_module_type_of.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_module_type_of.mdx diff --git a/markdown-pages/syntax-lookup/language_object_type.mdx b/apps/docs/markdown-pages/syntax-lookup/language_object_type.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_object_type.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_object_type.mdx diff --git a/markdown-pages/syntax-lookup/language_open.mdx b/apps/docs/markdown-pages/syntax-lookup/language_open.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_open.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_open.mdx diff --git a/markdown-pages/syntax-lookup/language_open_object_type.mdx b/apps/docs/markdown-pages/syntax-lookup/language_open_object_type.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_open_object_type.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_open_object_type.mdx diff --git a/markdown-pages/syntax-lookup/language_optional_labeled_argument.mdx b/apps/docs/markdown-pages/syntax-lookup/language_optional_labeled_argument.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_optional_labeled_argument.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_optional_labeled_argument.mdx diff --git a/markdown-pages/syntax-lookup/language_optional_record_field.mdx b/apps/docs/markdown-pages/syntax-lookup/language_optional_record_field.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_optional_record_field.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_optional_record_field.mdx diff --git a/markdown-pages/syntax-lookup/language_or_pattern.mdx b/apps/docs/markdown-pages/syntax-lookup/language_or_pattern.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_or_pattern.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_or_pattern.mdx diff --git a/markdown-pages/syntax-lookup/language_placeholder.mdx b/apps/docs/markdown-pages/syntax-lookup/language_placeholder.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_placeholder.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_placeholder.mdx diff --git a/markdown-pages/syntax-lookup/language_polyvar.mdx b/apps/docs/markdown-pages/syntax-lookup/language_polyvar.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_polyvar.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_polyvar.mdx diff --git a/markdown-pages/syntax-lookup/language_record_type.mdx b/apps/docs/markdown-pages/syntax-lookup/language_record_type.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_record_type.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_record_type.mdx diff --git a/markdown-pages/syntax-lookup/language_ref.mdx b/apps/docs/markdown-pages/syntax-lookup/language_ref.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_ref.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_ref.mdx diff --git a/markdown-pages/syntax-lookup/language_regular_expression.mdx b/apps/docs/markdown-pages/syntax-lookup/language_regular_expression.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_regular_expression.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_regular_expression.mdx diff --git a/markdown-pages/syntax-lookup/language_scoped_polymorphic_type.mdx b/apps/docs/markdown-pages/syntax-lookup/language_scoped_polymorphic_type.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_scoped_polymorphic_type.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_scoped_polymorphic_type.mdx diff --git a/markdown-pages/syntax-lookup/language_spreads.mdx b/apps/docs/markdown-pages/syntax-lookup/language_spreads.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_spreads.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_spreads.mdx diff --git a/markdown-pages/syntax-lookup/language_string_interpolation.mdx b/apps/docs/markdown-pages/syntax-lookup/language_string_interpolation.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_string_interpolation.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_string_interpolation.mdx diff --git a/markdown-pages/syntax-lookup/language_string_literal.mdx b/apps/docs/markdown-pages/syntax-lookup/language_string_literal.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_string_literal.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_string_literal.mdx diff --git a/markdown-pages/syntax-lookup/language_switch.mdx b/apps/docs/markdown-pages/syntax-lookup/language_switch.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_switch.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_switch.mdx diff --git a/markdown-pages/syntax-lookup/language_ternary.mdx b/apps/docs/markdown-pages/syntax-lookup/language_ternary.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_ternary.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_ternary.mdx diff --git a/markdown-pages/syntax-lookup/language_type.mdx b/apps/docs/markdown-pages/syntax-lookup/language_type.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_type.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_type.mdx diff --git a/markdown-pages/syntax-lookup/language_type_parameter.mdx b/apps/docs/markdown-pages/syntax-lookup/language_type_parameter.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_type_parameter.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_type_parameter.mdx diff --git a/markdown-pages/syntax-lookup/language_type_rec.mdx b/apps/docs/markdown-pages/syntax-lookup/language_type_rec.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_type_rec.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_type_rec.mdx diff --git a/markdown-pages/syntax-lookup/language_variant_type.mdx b/apps/docs/markdown-pages/syntax-lookup/language_variant_type.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_variant_type.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_variant_type.mdx diff --git a/markdown-pages/syntax-lookup/language_while.mdx b/apps/docs/markdown-pages/syntax-lookup/language_while.mdx similarity index 100% rename from markdown-pages/syntax-lookup/language_while.mdx rename to apps/docs/markdown-pages/syntax-lookup/language_while.mdx diff --git a/markdown-pages/syntax-lookup/operator_ref_value_assignment.mdx b/apps/docs/markdown-pages/syntax-lookup/operator_ref_value_assignment.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operator_ref_value_assignment.mdx rename to apps/docs/markdown-pages/syntax-lookup/operator_ref_value_assignment.mdx diff --git a/markdown-pages/syntax-lookup/operators_bitwise_and.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_bitwise_and.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_bitwise_and.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_bitwise_and.mdx diff --git a/markdown-pages/syntax-lookup/operators_bitwise_not.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_bitwise_not.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_bitwise_not.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_bitwise_not.mdx diff --git a/markdown-pages/syntax-lookup/operators_bitwise_or.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_bitwise_or.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_bitwise_or.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_bitwise_or.mdx diff --git a/markdown-pages/syntax-lookup/operators_bitwise_xor.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_bitwise_xor.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_bitwise_xor.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_bitwise_xor.mdx diff --git a/markdown-pages/syntax-lookup/operators_boolean_and.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_boolean_and.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_boolean_and.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_boolean_and.mdx diff --git a/markdown-pages/syntax-lookup/operators_boolean_not.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_boolean_not.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_boolean_not.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_boolean_not.mdx diff --git a/markdown-pages/syntax-lookup/operators_boolean_or.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_boolean_or.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_boolean_or.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_boolean_or.mdx diff --git a/markdown-pages/syntax-lookup/operators_exponentiation.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_exponentiation.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_exponentiation.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_exponentiation.mdx diff --git a/markdown-pages/syntax-lookup/operators_float_addition.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_float_addition.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_float_addition.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_float_addition.mdx diff --git a/markdown-pages/syntax-lookup/operators_float_division.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_float_division.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_float_division.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_float_division.mdx diff --git a/markdown-pages/syntax-lookup/operators_float_multiplication.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_float_multiplication.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_float_multiplication.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_float_multiplication.mdx diff --git a/markdown-pages/syntax-lookup/operators_float_subtraction.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_float_subtraction.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_float_subtraction.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_float_subtraction.mdx diff --git a/markdown-pages/syntax-lookup/operators_integer_addition.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_integer_addition.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_integer_addition.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_integer_addition.mdx diff --git a/markdown-pages/syntax-lookup/operators_integer_division.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_integer_division.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_integer_division.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_integer_division.mdx diff --git a/markdown-pages/syntax-lookup/operators_integer_multiplication.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_integer_multiplication.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_integer_multiplication.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_integer_multiplication.mdx diff --git a/markdown-pages/syntax-lookup/operators_integer_subtraction.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_integer_subtraction.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_integer_subtraction.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_integer_subtraction.mdx diff --git a/markdown-pages/syntax-lookup/operators_mod.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_mod.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_mod.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_mod.mdx diff --git a/markdown-pages/syntax-lookup/operators_pipe.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_pipe.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_pipe.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_pipe.mdx diff --git a/markdown-pages/syntax-lookup/operators_shift_left.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_shift_left.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_shift_left.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_shift_left.mdx diff --git a/markdown-pages/syntax-lookup/operators_shift_right.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_shift_right.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_shift_right.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_shift_right.mdx diff --git a/markdown-pages/syntax-lookup/operators_shift_right_unsigned.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_shift_right_unsigned.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_shift_right_unsigned.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_shift_right_unsigned.mdx diff --git a/markdown-pages/syntax-lookup/operators_string_concatenation.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_string_concatenation.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_string_concatenation.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_string_concatenation.mdx diff --git a/markdown-pages/syntax-lookup/operators_type_coercion.mdx b/apps/docs/markdown-pages/syntax-lookup/operators_type_coercion.mdx similarity index 100% rename from markdown-pages/syntax-lookup/operators_type_coercion.mdx rename to apps/docs/markdown-pages/syntax-lookup/operators_type_coercion.mdx diff --git a/markdown-pages/syntax-lookup/specialvalues_file.mdx b/apps/docs/markdown-pages/syntax-lookup/specialvalues_file.mdx similarity index 100% rename from markdown-pages/syntax-lookup/specialvalues_file.mdx rename to apps/docs/markdown-pages/syntax-lookup/specialvalues_file.mdx diff --git a/markdown-pages/syntax-lookup/specialvalues_line.mdx b/apps/docs/markdown-pages/syntax-lookup/specialvalues_line.mdx similarity index 100% rename from markdown-pages/syntax-lookup/specialvalues_line.mdx rename to apps/docs/markdown-pages/syntax-lookup/specialvalues_line.mdx diff --git a/markdown-pages/syntax-lookup/specialvalues_line_of.mdx b/apps/docs/markdown-pages/syntax-lookup/specialvalues_line_of.mdx similarity index 100% rename from markdown-pages/syntax-lookup/specialvalues_line_of.mdx rename to apps/docs/markdown-pages/syntax-lookup/specialvalues_line_of.mdx diff --git a/markdown-pages/syntax-lookup/specialvalues_loc.mdx b/apps/docs/markdown-pages/syntax-lookup/specialvalues_loc.mdx similarity index 100% rename from markdown-pages/syntax-lookup/specialvalues_loc.mdx rename to apps/docs/markdown-pages/syntax-lookup/specialvalues_loc.mdx diff --git a/markdown-pages/syntax-lookup/specialvalues_loc_of.mdx b/apps/docs/markdown-pages/syntax-lookup/specialvalues_loc_of.mdx similarity index 100% rename from markdown-pages/syntax-lookup/specialvalues_loc_of.mdx rename to apps/docs/markdown-pages/syntax-lookup/specialvalues_loc_of.mdx diff --git a/markdown-pages/syntax-lookup/specialvalues_module.mdx b/apps/docs/markdown-pages/syntax-lookup/specialvalues_module.mdx similarity index 100% rename from markdown-pages/syntax-lookup/specialvalues_module.mdx rename to apps/docs/markdown-pages/syntax-lookup/specialvalues_module.mdx diff --git a/markdown-pages/syntax-lookup/specialvalues_pos.mdx b/apps/docs/markdown-pages/syntax-lookup/specialvalues_pos.mdx similarity index 100% rename from markdown-pages/syntax-lookup/specialvalues_pos.mdx rename to apps/docs/markdown-pages/syntax-lookup/specialvalues_pos.mdx diff --git a/markdown-pages/syntax-lookup/specialvalues_pos_of.mdx b/apps/docs/markdown-pages/syntax-lookup/specialvalues_pos_of.mdx similarity index 100% rename from markdown-pages/syntax-lookup/specialvalues_pos_of.mdx rename to apps/docs/markdown-pages/syntax-lookup/specialvalues_pos_of.mdx diff --git a/plugins/cm6-reason-mode.js b/apps/docs/plugins/cm6-reason-mode.js similarity index 100% rename from plugins/cm6-reason-mode.js rename to apps/docs/plugins/cm6-reason-mode.js diff --git a/plugins/reason-highlightjs.js b/apps/docs/plugins/reason-highlightjs.js similarity index 100% rename from plugins/reason-highlightjs.js rename to apps/docs/plugins/reason-highlightjs.js diff --git a/public/Art-3-rescript-launch.avif b/apps/docs/public/Art-3-rescript-launch.avif similarity index 100% rename from public/Art-3-rescript-launch.avif rename to apps/docs/public/Art-3-rescript-launch.avif diff --git a/public/_redirects b/apps/docs/public/_redirects similarity index 100% rename from public/_redirects rename to apps/docs/public/_redirects diff --git a/public/blog/.gitkeep b/apps/docs/public/blog/.gitkeep similarity index 100% rename from public/blog/.gitkeep rename to apps/docs/public/blog/.gitkeep diff --git a/public/blog/archive/label-error.avif b/apps/docs/public/blog/archive/label-error.avif similarity index 100% rename from public/blog/archive/label-error.avif rename to apps/docs/public/blog/archive/label-error.avif diff --git a/public/blog/archive/playground-mockup.avif b/apps/docs/public/blog/archive/playground-mockup.avif similarity index 100% rename from public/blog/archive/playground-mockup.avif rename to apps/docs/public/blog/archive/playground-mockup.avif diff --git a/public/blog/archive/poly-error.avif b/apps/docs/public/blog/archive/poly-error.avif similarity index 100% rename from public/blog/archive/poly-error.avif rename to apps/docs/public/blog/archive/poly-error.avif diff --git a/public/blog/archive/reasonml-org-color-palette-retina.avif b/apps/docs/public/blog/archive/reasonml-org-color-palette-retina.avif similarity index 100% rename from public/blog/archive/reasonml-org-color-palette-retina.avif rename to apps/docs/public/blog/archive/reasonml-org-color-palette-retina.avif diff --git a/public/blog/archive/reasonml-org-structure-retina.avif b/apps/docs/public/blog/archive/reasonml-org-structure-retina.avif similarity index 100% rename from public/blog/archive/reasonml-org-structure-retina.avif rename to apps/docs/public/blog/archive/reasonml-org-structure-retina.avif diff --git a/public/blog/archive/recursive-error.avif b/apps/docs/public/blog/archive/recursive-error.avif similarity index 100% rename from public/blog/archive/recursive-error.avif rename to apps/docs/public/blog/archive/recursive-error.avif diff --git a/public/blog/archive/recursive.avif b/apps/docs/public/blog/archive/recursive.avif similarity index 100% rename from public/blog/archive/recursive.avif rename to apps/docs/public/blog/archive/recursive.avif diff --git a/public/blog/archive/search-mockup.avif b/apps/docs/public/blog/archive/search-mockup.avif similarity index 100% rename from public/blog/archive/search-mockup.avif rename to apps/docs/public/blog/archive/search-mockup.avif diff --git a/public/blog/archive/state-of-reasonml-2020-q2-pt2-articleimg.avif b/apps/docs/public/blog/archive/state-of-reasonml-2020-q2-pt2-articleimg.avif similarity index 100% rename from public/blog/archive/state-of-reasonml-2020-q2-pt2-articleimg.avif rename to apps/docs/public/blog/archive/state-of-reasonml-2020-q2-pt2-articleimg.avif diff --git a/public/blog/archive/state-of-reasonml-org-q2-2020.avif b/apps/docs/public/blog/archive/state-of-reasonml-org-q2-2020.avif similarity index 100% rename from public/blog/archive/state-of-reasonml-org-q2-2020.avif rename to apps/docs/public/blog/archive/state-of-reasonml-org-q2-2020.avif diff --git a/public/blog/archive/state-of-reasonml-pt1-hero.avif b/apps/docs/public/blog/archive/state-of-reasonml-pt1-hero.avif similarity index 100% rename from public/blog/archive/state-of-reasonml-pt1-hero.avif rename to apps/docs/public/blog/archive/state-of-reasonml-pt1-hero.avif diff --git a/public/blog/archive/state-of-reasonml-q1-2020-card.avif b/apps/docs/public/blog/archive/state-of-reasonml-q1-2020-card.avif similarity index 100% rename from public/blog/archive/state-of-reasonml-q1-2020-card.avif rename to apps/docs/public/blog/archive/state-of-reasonml-q1-2020-card.avif diff --git a/public/blog/archive/uncurry-label.avif b/apps/docs/public/blog/archive/uncurry-label.avif similarity index 100% rename from public/blog/archive/uncurry-label.avif rename to apps/docs/public/blog/archive/uncurry-label.avif diff --git a/public/blog/archive/youtube-search-reasonml.avif b/apps/docs/public/blog/archive/youtube-search-reasonml.avif similarity index 100% rename from public/blog/archive/youtube-search-reasonml.avif rename to apps/docs/public/blog/archive/youtube-search-reasonml.avif diff --git a/public/blog/compiler_release_11_0.avif b/apps/docs/public/blog/compiler_release_11_0.avif similarity index 100% rename from public/blog/compiler_release_11_0.avif rename to apps/docs/public/blog/compiler_release_11_0.avif diff --git a/public/blog/compiler_release_11_1.avif b/apps/docs/public/blog/compiler_release_11_1.avif similarity index 100% rename from public/blog/compiler_release_11_1.avif rename to apps/docs/public/blog/compiler_release_11_1.avif diff --git a/public/blog/compiler_release_12_0.webp b/apps/docs/public/blog/compiler_release_12_0.webp similarity index 100% rename from public/blog/compiler_release_12_0.webp rename to apps/docs/public/blog/compiler_release_12_0.webp diff --git a/public/blog/compiler_release_12_0_article.webp b/apps/docs/public/blog/compiler_release_12_0_article.webp similarity index 100% rename from public/blog/compiler_release_12_0_article.webp rename to apps/docs/public/blog/compiler_release_12_0_article.webp diff --git a/public/blog/compiler_release_9_0.avif b/apps/docs/public/blog/compiler_release_9_0.avif similarity index 100% rename from public/blog/compiler_release_9_0.avif rename to apps/docs/public/blog/compiler_release_9_0.avif diff --git a/public/blog/compiler_release_9_1.avif b/apps/docs/public/blog/compiler_release_9_1.avif similarity index 100% rename from public/blog/compiler_release_9_1.avif rename to apps/docs/public/blog/compiler_release_9_1.avif diff --git a/public/blog/editor_support_article.avif b/apps/docs/public/blog/editor_support_article.avif similarity index 100% rename from public/blog/editor_support_article.avif rename to apps/docs/public/blog/editor_support_article.avif diff --git a/public/blog/editor_support_preview.avif b/apps/docs/public/blog/editor_support_preview.avif similarity index 100% rename from public/blog/editor_support_preview.avif rename to apps/docs/public/blog/editor_support_preview.avif diff --git a/public/blog/grid_0.avif b/apps/docs/public/blog/grid_0.avif similarity index 100% rename from public/blog/grid_0.avif rename to apps/docs/public/blog/grid_0.avif diff --git a/public/blog/landing_page_figma.avif b/apps/docs/public/blog/landing_page_figma.avif similarity index 100% rename from public/blog/landing_page_figma.avif rename to apps/docs/public/blog/landing_page_figma.avif diff --git a/public/blog/reactive-analysis/fixpoint.mmd b/apps/docs/public/blog/reactive-analysis/fixpoint.mmd similarity index 100% rename from public/blog/reactive-analysis/fixpoint.mmd rename to apps/docs/public/blog/reactive-analysis/fixpoint.mmd diff --git a/public/blog/reactive-analysis/fixpoint.svg b/apps/docs/public/blog/reactive-analysis/fixpoint.svg similarity index 100% rename from public/blog/reactive-analysis/fixpoint.svg rename to apps/docs/public/blog/reactive-analysis/fixpoint.svg diff --git a/public/blog/reactive-analysis/reactive-pipeline-simple.mmd b/apps/docs/public/blog/reactive-analysis/reactive-pipeline-simple.mmd similarity index 100% rename from public/blog/reactive-analysis/reactive-pipeline-simple.mmd rename to apps/docs/public/blog/reactive-analysis/reactive-pipeline-simple.mmd diff --git a/public/blog/reactive-analysis/reactive-pipeline-simple.svg b/apps/docs/public/blog/reactive-analysis/reactive-pipeline-simple.svg similarity index 100% rename from public/blog/reactive-analysis/reactive-pipeline-simple.svg rename to apps/docs/public/blog/reactive-analysis/reactive-pipeline-simple.svg diff --git a/public/blog/rescript-12-reforging-build-system.webp b/apps/docs/public/blog/rescript-12-reforging-build-system.webp similarity index 100% rename from public/blog/rescript-12-reforging-build-system.webp rename to apps/docs/public/blog/rescript-12-reforging-build-system.webp diff --git a/public/blog/rescript-launch/ReScript-1.avif b/apps/docs/public/blog/rescript-launch/ReScript-1.avif similarity index 100% rename from public/blog/rescript-launch/ReScript-1.avif rename to apps/docs/public/blog/rescript-launch/ReScript-1.avif diff --git a/public/blog/rescript-launch/ReScript-2.avif b/apps/docs/public/blog/rescript-launch/ReScript-2.avif similarity index 100% rename from public/blog/rescript-launch/ReScript-2.avif rename to apps/docs/public/blog/rescript-launch/ReScript-2.avif diff --git a/public/blog/rescript-launch/ReScript-3.avif b/apps/docs/public/blog/rescript-launch/ReScript-3.avif similarity index 100% rename from public/blog/rescript-launch/ReScript-3.avif rename to apps/docs/public/blog/rescript-launch/ReScript-3.avif diff --git a/public/blog/rescript-launch/ReScript-4.avif b/apps/docs/public/blog/rescript-launch/ReScript-4.avif similarity index 100% rename from public/blog/rescript-launch/ReScript-4.avif rename to apps/docs/public/blog/rescript-launch/ReScript-4.avif diff --git a/public/blog/rescript-retreat-2025.webp b/apps/docs/public/blog/rescript-retreat-2025.webp similarity index 100% rename from public/blog/rescript-retreat-2025.webp rename to apps/docs/public/blog/rescript-retreat-2025.webp diff --git a/public/blog/rescript-team-2025.webp b/apps/docs/public/blog/rescript-team-2025.webp similarity index 100% rename from public/blog/rescript-team-2025.webp rename to apps/docs/public/blog/rescript-team-2025.webp diff --git a/public/blog/rescript_assoc_rename_preview.avif b/apps/docs/public/blog/rescript_assoc_rename_preview.avif similarity index 100% rename from public/blog/rescript_assoc_rename_preview.avif rename to apps/docs/public/blog/rescript_assoc_rename_preview.avif diff --git a/public/blog/rescript_retreat_2024.avif b/apps/docs/public/blog/rescript_retreat_2024.avif similarity index 100% rename from public/blog/rescript_retreat_2024.avif rename to apps/docs/public/blog/rescript_retreat_2024.avif diff --git a/public/blog/rescript_retreat_2024_group_work.avif b/apps/docs/public/blog/rescript_retreat_2024_group_work.avif similarity index 100% rename from public/blog/rescript_retreat_2024_group_work.avif rename to apps/docs/public/blog/rescript_retreat_2024_group_work.avif diff --git a/public/blog/rescript_retreat_2024_talk_parser.avif b/apps/docs/public/blog/rescript_retreat_2024_talk_parser.avif similarity index 100% rename from public/blog/rescript_retreat_2024_talk_parser.avif rename to apps/docs/public/blog/rescript_retreat_2024_talk_parser.avif diff --git a/public/blog/rescript_retreat_2024_winery.avif b/apps/docs/public/blog/rescript_retreat_2024_winery.avif similarity index 100% rename from public/blog/rescript_retreat_2024_winery.avif rename to apps/docs/public/blog/rescript_retreat_2024_winery.avif diff --git a/public/blog/wizard_typing_on_a_keyboard_in_a_sea_of_lava_flowing_54e33c58-aa14-4f1d-8249-dae636dfc0e9.avif b/apps/docs/public/blog/wizard_typing_on_a_keyboard_in_a_sea_of_lava_flowing_54e33c58-aa14-4f1d-8249-dae636dfc0e9.avif similarity index 100% rename from public/blog/wizard_typing_on_a_keyboard_in_a_sea_of_lava_flowing_54e33c58-aa14-4f1d-8249-dae636dfc0e9.avif rename to apps/docs/public/blog/wizard_typing_on_a_keyboard_in_a_sea_of_lava_flowing_54e33c58-aa14-4f1d-8249-dae636dfc0e9.avif diff --git a/public/brand/rescript-brandmark.avif b/apps/docs/public/brand/rescript-brandmark.avif similarity index 100% rename from public/brand/rescript-brandmark.avif rename to apps/docs/public/brand/rescript-brandmark.avif diff --git a/public/brand/rescript-brandmark.svg b/apps/docs/public/brand/rescript-brandmark.svg similarity index 100% rename from public/brand/rescript-brandmark.svg rename to apps/docs/public/brand/rescript-brandmark.svg diff --git a/public/brand/rescript-logo-white.avif b/apps/docs/public/brand/rescript-logo-white.avif similarity index 100% rename from public/brand/rescript-logo-white.avif rename to apps/docs/public/brand/rescript-logo-white.avif diff --git a/public/brand/rescript-logo-white.svg b/apps/docs/public/brand/rescript-logo-white.svg similarity index 100% rename from public/brand/rescript-logo-white.svg rename to apps/docs/public/brand/rescript-logo-white.svg diff --git a/public/brand/rescript-logo.avif b/apps/docs/public/brand/rescript-logo.avif similarity index 100% rename from public/brand/rescript-logo.avif rename to apps/docs/public/brand/rescript-logo.avif diff --git a/public/brand/rescript-logo.svg b/apps/docs/public/brand/rescript-logo.svg similarity index 100% rename from public/brand/rescript-logo.svg rename to apps/docs/public/brand/rescript-logo.svg diff --git a/public/docson/box.html b/apps/docs/public/docson/box.html similarity index 100% rename from public/docson/box.html rename to apps/docs/public/docson/box.html diff --git a/public/docson/signature.html b/apps/docs/public/docson/signature.html similarity index 100% rename from public/docson/signature.html rename to apps/docs/public/docson/signature.html diff --git a/public/favicon.ico b/apps/docs/public/favicon.ico similarity index 100% rename from public/favicon.ico rename to apps/docs/public/favicon.ico diff --git a/public/favicon/android-chrome-192x192.avif b/apps/docs/public/favicon/android-chrome-192x192.avif similarity index 100% rename from public/favicon/android-chrome-192x192.avif rename to apps/docs/public/favicon/android-chrome-192x192.avif diff --git a/public/favicon/android-chrome-512x512.avif b/apps/docs/public/favicon/android-chrome-512x512.avif similarity index 100% rename from public/favicon/android-chrome-512x512.avif rename to apps/docs/public/favicon/android-chrome-512x512.avif diff --git a/public/favicon/apple-touch-icon.avif b/apps/docs/public/favicon/apple-touch-icon.avif similarity index 100% rename from public/favicon/apple-touch-icon.avif rename to apps/docs/public/favicon/apple-touch-icon.avif diff --git a/public/favicon/favicon-16x16.avif b/apps/docs/public/favicon/favicon-16x16.avif similarity index 100% rename from public/favicon/favicon-16x16.avif rename to apps/docs/public/favicon/favicon-16x16.avif diff --git a/public/favicon/favicon-32x32.avif b/apps/docs/public/favicon/favicon-32x32.avif similarity index 100% rename from public/favicon/favicon-32x32.avif rename to apps/docs/public/favicon/favicon-32x32.avif diff --git a/public/favicon/site.webmanifest b/apps/docs/public/favicon/site.webmanifest similarity index 100% rename from public/favicon/site.webmanifest rename to apps/docs/public/favicon/site.webmanifest diff --git a/public/fonts/roboto-mono-400.woff2 b/apps/docs/public/fonts/roboto-mono-400.woff2 similarity index 100% rename from public/fonts/roboto-mono-400.woff2 rename to apps/docs/public/fonts/roboto-mono-400.woff2 diff --git a/public/fonts/roboto-mono-700.woff2 b/apps/docs/public/fonts/roboto-mono-700.woff2 similarity index 100% rename from public/fonts/roboto-mono-700.woff2 rename to apps/docs/public/fonts/roboto-mono-700.woff2 diff --git a/public/fonts/subset-Inter-Bold.woff2 b/apps/docs/public/fonts/subset-Inter-Bold.woff2 similarity index 100% rename from public/fonts/subset-Inter-Bold.woff2 rename to apps/docs/public/fonts/subset-Inter-Bold.woff2 diff --git a/public/fonts/subset-Inter-Italic.woff2 b/apps/docs/public/fonts/subset-Inter-Italic.woff2 similarity index 100% rename from public/fonts/subset-Inter-Italic.woff2 rename to apps/docs/public/fonts/subset-Inter-Italic.woff2 diff --git a/public/fonts/subset-Inter-Medium.woff2 b/apps/docs/public/fonts/subset-Inter-Medium.woff2 similarity index 100% rename from public/fonts/subset-Inter-Medium.woff2 rename to apps/docs/public/fonts/subset-Inter-Medium.woff2 diff --git a/public/fonts/subset-Inter-Regular.woff2 b/apps/docs/public/fonts/subset-Inter-Regular.woff2 similarity index 100% rename from public/fonts/subset-Inter-Regular.woff2 rename to apps/docs/public/fonts/subset-Inter-Regular.woff2 diff --git a/public/fonts/subset-Inter-SemiBold.woff2 b/apps/docs/public/fonts/subset-Inter-SemiBold.woff2 similarity index 100% rename from public/fonts/subset-Inter-SemiBold.woff2 rename to apps/docs/public/fonts/subset-Inter-SemiBold.woff2 diff --git a/public/hero.avif b/apps/docs/public/hero.avif similarity index 100% rename from public/hero.avif rename to apps/docs/public/hero.avif diff --git a/public/hyperlink.svg b/apps/docs/public/hyperlink.svg similarity index 100% rename from public/hyperlink.svg rename to apps/docs/public/hyperlink.svg diff --git a/public/ic_gentype@2x.avif b/apps/docs/public/ic_gentype@2x.avif similarity index 100% rename from public/ic_gentype@2x.avif rename to apps/docs/public/ic_gentype@2x.avif diff --git a/public/ic_manual@2x.avif b/apps/docs/public/ic_manual@2x.avif similarity index 100% rename from public/ic_manual@2x.avif rename to apps/docs/public/ic_manual@2x.avif diff --git a/public/ic_package.svg b/apps/docs/public/ic_package.svg similarity index 100% rename from public/ic_package.svg rename to apps/docs/public/ic_package.svg diff --git a/public/ic_reanalyze@2x.avif b/apps/docs/public/ic_reanalyze@2x.avif similarity index 100% rename from public/ic_reanalyze@2x.avif rename to apps/docs/public/ic_reanalyze@2x.avif diff --git a/public/ic_rescript_react@2x.avif b/apps/docs/public/ic_rescript_react@2x.avif similarity index 100% rename from public/ic_rescript_react@2x.avif rename to apps/docs/public/ic_rescript_react@2x.avif diff --git a/public/ic_search.svg b/apps/docs/public/ic_search.svg similarity index 100% rename from public/ic_search.svg rename to apps/docs/public/ic_search.svg diff --git a/public/ic_sidebar_drawer.svg b/apps/docs/public/ic_sidebar_drawer.svg similarity index 100% rename from public/ic_sidebar_drawer.svg rename to apps/docs/public/ic_sidebar_drawer.svg diff --git a/public/illu_index_rescript@2x.avif b/apps/docs/public/illu_index_rescript@2x.avif similarity index 100% rename from public/illu_index_rescript@2x.avif rename to apps/docs/public/illu_index_rescript@2x.avif diff --git a/public/img/bstracing.avif b/apps/docs/public/img/bstracing.avif similarity index 100% rename from public/img/bstracing.avif rename to apps/docs/public/img/bstracing.avif diff --git a/public/img/debugger-after.avif b/apps/docs/public/img/debugger-after.avif similarity index 100% rename from public/img/debugger-after.avif rename to apps/docs/public/img/debugger-after.avif diff --git a/public/img/debugger-before.avif b/apps/docs/public/img/debugger-before.avif similarity index 100% rename from public/img/debugger-before.avif rename to apps/docs/public/img/debugger-before.avif diff --git a/public/img/debugger-inspector.avif b/apps/docs/public/img/debugger-inspector.avif similarity index 100% rename from public/img/debugger-inspector.avif rename to apps/docs/public/img/debugger-inspector.avif diff --git a/public/img/landing_page_figma.avif b/apps/docs/public/img/landing_page_figma.avif similarity index 100% rename from public/img/landing_page_figma.avif rename to apps/docs/public/img/landing_page_figma.avif diff --git a/public/llms/manual/template.mdx b/apps/docs/public/llms/manual/template.mdx similarity index 100% rename from public/llms/manual/template.mdx rename to apps/docs/public/llms/manual/template.mdx diff --git a/public/llms/manual/template.txt b/apps/docs/public/llms/manual/template.txt similarity index 100% rename from public/llms/manual/template.txt rename to apps/docs/public/llms/manual/template.txt diff --git a/public/llms/react/template.mdx b/apps/docs/public/llms/react/template.mdx similarity index 100% rename from public/llms/react/template.mdx rename to apps/docs/public/llms/react/template.mdx diff --git a/public/llms/react/template.txt b/apps/docs/public/llms/react/template.txt similarity index 100% rename from public/llms/react/template.txt rename to apps/docs/public/llms/react/template.txt diff --git a/public/lp/aivero.svg b/apps/docs/public/lp/aivero.svg similarity index 100% rename from public/lp/aivero.svg rename to apps/docs/public/lp/aivero.svg diff --git a/public/lp/arizon.svg b/apps/docs/public/lp/arizon.svg similarity index 100% rename from public/lp/arizon.svg rename to apps/docs/public/lp/arizon.svg diff --git a/public/lp/bandprotocol.svg b/apps/docs/public/lp/bandprotocol.svg similarity index 100% rename from public/lp/bandprotocol.svg rename to apps/docs/public/lp/bandprotocol.svg diff --git a/public/lp/bettercart.svg b/apps/docs/public/lp/bettercart.svg similarity index 100% rename from public/lp/bettercart.svg rename to apps/docs/public/lp/bettercart.svg diff --git a/public/lp/bettervim.svg b/apps/docs/public/lp/bettervim.svg similarity index 100% rename from public/lp/bettervim.svg rename to apps/docs/public/lp/bettervim.svg diff --git a/public/lp/camelo.svg b/apps/docs/public/lp/camelo.svg similarity index 100% rename from public/lp/camelo.svg rename to apps/docs/public/lp/camelo.svg diff --git a/public/lp/cardoc.svg b/apps/docs/public/lp/cardoc.svg similarity index 100% rename from public/lp/cardoc.svg rename to apps/docs/public/lp/cardoc.svg diff --git a/public/lp/carla.svg b/apps/docs/public/lp/carla.svg similarity index 100% rename from public/lp/carla.svg rename to apps/docs/public/lp/carla.svg diff --git a/public/lp/cca-io-color.svg b/apps/docs/public/lp/cca-io-color.svg similarity index 100% rename from public/lp/cca-io-color.svg rename to apps/docs/public/lp/cca-io-color.svg diff --git a/public/lp/cca-io.svg b/apps/docs/public/lp/cca-io.svg similarity index 100% rename from public/lp/cca-io.svg rename to apps/docs/public/lp/cca-io.svg diff --git a/public/lp/collectiveaudience.svg b/apps/docs/public/lp/collectiveaudience.svg similarity index 100% rename from public/lp/collectiveaudience.svg rename to apps/docs/public/lp/collectiveaudience.svg diff --git a/public/lp/community-1.avif b/apps/docs/public/lp/community-1.avif similarity index 100% rename from public/lp/community-1.avif rename to apps/docs/public/lp/community-1.avif diff --git a/public/lp/community-2.avif b/apps/docs/public/lp/community-2.avif similarity index 100% rename from public/lp/community-2.avif rename to apps/docs/public/lp/community-2.avif diff --git a/public/lp/community-3.avif b/apps/docs/public/lp/community-3.avif similarity index 100% rename from public/lp/community-3.avif rename to apps/docs/public/lp/community-3.avif diff --git a/public/lp/darklang.svg b/apps/docs/public/lp/darklang.svg similarity index 100% rename from public/lp/darklang.svg rename to apps/docs/public/lp/darklang.svg diff --git a/public/lp/dev-it-jobs.svg b/apps/docs/public/lp/dev-it-jobs.svg similarity index 100% rename from public/lp/dev-it-jobs.svg rename to apps/docs/public/lp/dev-it-jobs.svg diff --git a/public/lp/draftbit.svg b/apps/docs/public/lp/draftbit.svg similarity index 100% rename from public/lp/draftbit.svg rename to apps/docs/public/lp/draftbit.svg diff --git a/public/lp/easy-to-unadopt.avif b/apps/docs/public/lp/easy-to-unadopt.avif similarity index 100% rename from public/lp/easy-to-unadopt.avif rename to apps/docs/public/lp/easy-to-unadopt.avif diff --git a/public/lp/editor-tooling-1.avif b/apps/docs/public/lp/editor-tooling-1.avif similarity index 100% rename from public/lp/editor-tooling-1.avif rename to apps/docs/public/lp/editor-tooling-1.avif diff --git a/public/lp/envio.svg b/apps/docs/public/lp/envio.svg similarity index 100% rename from public/lp/envio.svg rename to apps/docs/public/lp/envio.svg diff --git a/public/lp/fast-build-preview.avif b/apps/docs/public/lp/fast-build-preview.avif similarity index 100% rename from public/lp/fast-build-preview.avif rename to apps/docs/public/lp/fast-build-preview.avif diff --git a/public/lp/frontman.svg b/apps/docs/public/lp/frontman.svg similarity index 100% rename from public/lp/frontman.svg rename to apps/docs/public/lp/frontman.svg diff --git a/public/lp/greenlabs.svg b/apps/docs/public/lp/greenlabs.svg similarity index 100% rename from public/lp/greenlabs.svg rename to apps/docs/public/lp/greenlabs.svg diff --git a/public/lp/grid.svg b/apps/docs/public/lp/grid.svg similarity index 100% rename from public/lp/grid.svg rename to apps/docs/public/lp/grid.svg diff --git a/public/lp/grid2.svg b/apps/docs/public/lp/grid2.svg similarity index 100% rename from public/lp/grid2.svg rename to apps/docs/public/lp/grid2.svg diff --git a/public/lp/humaans.svg b/apps/docs/public/lp/humaans.svg similarity index 100% rename from public/lp/humaans.svg rename to apps/docs/public/lp/humaans.svg diff --git a/public/lp/illu_left.avif b/apps/docs/public/lp/illu_left.avif similarity index 100% rename from public/lp/illu_left.avif rename to apps/docs/public/lp/illu_left.avif diff --git a/public/lp/illu_right.avif b/apps/docs/public/lp/illu_right.avif similarity index 100% rename from public/lp/illu_right.avif rename to apps/docs/public/lp/illu_right.avif diff --git a/public/lp/instapainting.avif b/apps/docs/public/lp/instapainting.avif similarity index 100% rename from public/lp/instapainting.avif rename to apps/docs/public/lp/instapainting.avif diff --git a/public/lp/interop-example-preview.avif b/apps/docs/public/lp/interop-example-preview.avif similarity index 100% rename from public/lp/interop-example-preview.avif rename to apps/docs/public/lp/interop-example-preview.avif diff --git a/public/lp/juspay.svg b/apps/docs/public/lp/juspay.svg similarity index 100% rename from public/lp/juspay.svg rename to apps/docs/public/lp/juspay.svg diff --git a/public/lp/komplio.svg b/apps/docs/public/lp/komplio.svg similarity index 100% rename from public/lp/komplio.svg rename to apps/docs/public/lp/komplio.svg diff --git a/public/lp/maker.svg b/apps/docs/public/lp/maker.svg similarity index 100% rename from public/lp/maker.svg rename to apps/docs/public/lp/maker.svg diff --git a/public/lp/nomadic_labs.svg b/apps/docs/public/lp/nomadic_labs.svg similarity index 100% rename from public/lp/nomadic_labs.svg rename to apps/docs/public/lp/nomadic_labs.svg diff --git a/public/lp/ohne-makler.svg b/apps/docs/public/lp/ohne-makler.svg similarity index 100% rename from public/lp/ohne-makler.svg rename to apps/docs/public/lp/ohne-makler.svg diff --git a/public/lp/porter.svg b/apps/docs/public/lp/porter.svg similarity index 100% rename from public/lp/porter.svg rename to apps/docs/public/lp/porter.svg diff --git a/public/lp/pupilfirst.svg b/apps/docs/public/lp/pupilfirst.svg similarity index 100% rename from public/lp/pupilfirst.svg rename to apps/docs/public/lp/pupilfirst.svg diff --git a/public/lp/reka_market.svg b/apps/docs/public/lp/reka_market.svg similarity index 100% rename from public/lp/reka_market.svg rename to apps/docs/public/lp/reka_market.svg diff --git a/public/lp/resmume.svg b/apps/docs/public/lp/resmume.svg similarity index 100% rename from public/lp/resmume.svg rename to apps/docs/public/lp/resmume.svg diff --git a/public/lp/rohea.svg b/apps/docs/public/lp/rohea.svg similarity index 100% rename from public/lp/rohea.svg rename to apps/docs/public/lp/rohea.svg diff --git a/public/lp/sanjagh.svg b/apps/docs/public/lp/sanjagh.svg similarity index 100% rename from public/lp/sanjagh.svg rename to apps/docs/public/lp/sanjagh.svg diff --git a/public/lp/seamonster-studios.svg b/apps/docs/public/lp/seamonster-studios.svg similarity index 100% rename from public/lp/seamonster-studios.svg rename to apps/docs/public/lp/seamonster-studios.svg diff --git a/public/lp/sensonomic.svg b/apps/docs/public/lp/sensonomic.svg similarity index 100% rename from public/lp/sensonomic.svg rename to apps/docs/public/lp/sensonomic.svg diff --git a/public/lp/silq.svg b/apps/docs/public/lp/silq.svg similarity index 100% rename from public/lp/silq.svg rename to apps/docs/public/lp/silq.svg diff --git a/public/lp/stencil.svg b/apps/docs/public/lp/stencil.svg similarity index 100% rename from public/lp/stencil.svg rename to apps/docs/public/lp/stencil.svg diff --git a/public/lp/tiny.svg b/apps/docs/public/lp/tiny.svg similarity index 100% rename from public/lp/tiny.svg rename to apps/docs/public/lp/tiny.svg diff --git a/public/lp/travelworld.svg b/apps/docs/public/lp/travelworld.svg similarity index 100% rename from public/lp/travelworld.svg rename to apps/docs/public/lp/travelworld.svg diff --git a/public/lp/type-better-preview.avif b/apps/docs/public/lp/type-better-preview.avif similarity index 100% rename from public/lp/type-better-preview.avif rename to apps/docs/public/lp/type-better-preview.avif diff --git a/public/lp/walnut.svg b/apps/docs/public/lp/walnut.svg similarity index 100% rename from public/lp/walnut.svg rename to apps/docs/public/lp/walnut.svg diff --git a/public/lp/webcurate.svg b/apps/docs/public/lp/webcurate.svg similarity index 100% rename from public/lp/webcurate.svg rename to apps/docs/public/lp/webcurate.svg diff --git a/public/lp/wino.svg b/apps/docs/public/lp/wino.svg similarity index 100% rename from public/lp/wino.svg rename to apps/docs/public/lp/wino.svg diff --git a/public/lp/ybru.svg b/apps/docs/public/lp/ybru.svg similarity index 100% rename from public/lp/ybru.svg rename to apps/docs/public/lp/ybru.svg diff --git a/public/messenger-logo-64@2x.avif b/apps/docs/public/messenger-logo-64@2x.avif similarity index 100% rename from public/messenger-logo-64@2x.avif rename to apps/docs/public/messenger-logo-64@2x.avif diff --git a/public/nav-logo-full@2x.avif b/apps/docs/public/nav-logo-full@2x.avif similarity index 100% rename from public/nav-logo-full@2x.avif rename to apps/docs/public/nav-logo-full@2x.avif diff --git a/public/nav-logo@2x.avif b/apps/docs/public/nav-logo@2x.avif similarity index 100% rename from public/nav-logo@2x.avif rename to apps/docs/public/nav-logo@2x.avif diff --git a/public/nextjs_starter_logo.svg b/apps/docs/public/nextjs_starter_logo.svg similarity index 100% rename from public/nextjs_starter_logo.svg rename to apps/docs/public/nextjs_starter_logo.svg diff --git a/public/nodejs_starter_logo.svg b/apps/docs/public/nodejs_starter_logo.svg similarity index 100% rename from public/nodejs_starter_logo.svg rename to apps/docs/public/nodejs_starter_logo.svg diff --git a/public/og/try.avif b/apps/docs/public/og/try.avif similarity index 100% rename from public/og/try.avif rename to apps/docs/public/og/try.avif diff --git a/public/partners/ahrefs.svg b/apps/docs/public/partners/ahrefs.svg similarity index 100% rename from public/partners/ahrefs.svg rename to apps/docs/public/partners/ahrefs.svg diff --git a/public/partners/tezos_foundation.svg b/apps/docs/public/partners/tezos_foundation.svg similarity index 100% rename from public/partners/tezos_foundation.svg rename to apps/docs/public/partners/tezos_foundation.svg diff --git a/public/pupilfirst-logo.avif b/apps/docs/public/pupilfirst-logo.avif similarity index 100% rename from public/pupilfirst-logo.avif rename to apps/docs/public/pupilfirst-logo.avif diff --git a/public/rescript_logo_black.svg b/apps/docs/public/rescript_logo_black.svg similarity index 100% rename from public/rescript_logo_black.svg rename to apps/docs/public/rescript_logo_black.svg diff --git a/public/star.svg b/apps/docs/public/star.svg similarity index 100% rename from public/star.svg rename to apps/docs/public/star.svg diff --git a/public/vitejs_starter_logo.avif b/apps/docs/public/vitejs_starter_logo.avif similarity index 100% rename from public/vitejs_starter_logo.avif rename to apps/docs/public/vitejs_starter_logo.avif diff --git a/public/vitejs_starter_logo.svg b/apps/docs/public/vitejs_starter_logo.svg similarity index 100% rename from public/vitejs_starter_logo.svg rename to apps/docs/public/vitejs_starter_logo.svg diff --git a/public/vitejs_starter_logo@2x.avif b/apps/docs/public/vitejs_starter_logo@2x.avif similarity index 100% rename from public/vitejs_starter_logo@2x.avif rename to apps/docs/public/vitejs_starter_logo@2x.avif diff --git a/react-router.config.mjs b/apps/docs/react-router.config.mjs similarity index 100% rename from react-router.config.mjs rename to apps/docs/react-router.config.mjs diff --git a/rescript.json b/apps/docs/rescript.json similarity index 100% rename from rescript.json rename to apps/docs/rescript.json diff --git a/scripts/__tests__/test-examples.test.mjs b/apps/docs/scripts/__tests__/test-examples.test.mjs similarity index 100% rename from scripts/__tests__/test-examples.test.mjs rename to apps/docs/scripts/__tests__/test-examples.test.mjs diff --git a/scripts/__tests__/test-runner.test.mjs b/apps/docs/scripts/__tests__/test-runner.test.mjs similarity index 100% rename from scripts/__tests__/test-runner.test.mjs rename to apps/docs/scripts/__tests__/test-runner.test.mjs diff --git a/scripts/extract-syntax.mjs b/apps/docs/scripts/extract-syntax.mjs similarity index 100% rename from scripts/extract-syntax.mjs rename to apps/docs/scripts/extract-syntax.mjs diff --git a/scripts/figma-fetch.js b/apps/docs/scripts/figma-fetch.js similarity index 100% rename from scripts/figma-fetch.js rename to apps/docs/scripts/figma-fetch.js diff --git a/scripts/gendocs.res b/apps/docs/scripts/gendocs.res similarity index 100% rename from scripts/gendocs.res rename to apps/docs/scripts/gendocs.res diff --git a/scripts/generate_feed.res b/apps/docs/scripts/generate_feed.res similarity index 100% rename from scripts/generate_feed.res rename to apps/docs/scripts/generate_feed.res diff --git a/scripts/generate_llms.res b/apps/docs/scripts/generate_llms.res similarity index 100% rename from scripts/generate_llms.res rename to apps/docs/scripts/generate_llms.res diff --git a/scripts/markdown.js b/apps/docs/scripts/markdown.js similarity index 100% rename from scripts/markdown.js rename to apps/docs/scripts/markdown.js diff --git a/scripts/sync-playground-bundles.mjs b/apps/docs/scripts/sync-playground-bundles.mjs similarity index 100% rename from scripts/sync-playground-bundles.mjs rename to apps/docs/scripts/sync-playground-bundles.mjs diff --git a/scripts/sync-redirects.mjs b/apps/docs/scripts/sync-redirects.mjs similarity index 100% rename from scripts/sync-redirects.mjs rename to apps/docs/scripts/sync-redirects.mjs diff --git a/scripts/test-examples.mjs b/apps/docs/scripts/test-examples.mjs similarity index 100% rename from scripts/test-examples.mjs rename to apps/docs/scripts/test-examples.mjs diff --git a/scripts/test-hrefs.mjs b/apps/docs/scripts/test-hrefs.mjs similarity index 100% rename from scripts/test-hrefs.mjs rename to apps/docs/scripts/test-hrefs.mjs diff --git a/scripts/test.mjs b/apps/docs/scripts/test.mjs similarity index 100% rename from scripts/test.mjs rename to apps/docs/scripts/test.mjs diff --git a/scripts/watch-tests.mjs b/apps/docs/scripts/watch-tests.mjs similarity index 100% rename from scripts/watch-tests.mjs rename to apps/docs/scripts/watch-tests.mjs diff --git a/src/bindings/Babel.res b/apps/docs/src/bindings/Babel.res similarity index 100% rename from src/bindings/Babel.res rename to apps/docs/src/bindings/Babel.res diff --git a/src/bindings/Cloudflare.res b/apps/docs/src/bindings/Cloudflare.res similarity index 100% rename from src/bindings/Cloudflare.res rename to apps/docs/src/bindings/Cloudflare.res diff --git a/src/bindings/DocSearch.res b/apps/docs/src/bindings/DocSearch.res similarity index 100% rename from src/bindings/DocSearch.res rename to apps/docs/src/bindings/DocSearch.res diff --git a/src/bindings/Env.res b/apps/docs/src/bindings/Env.res similarity index 100% rename from src/bindings/Env.res rename to apps/docs/src/bindings/Env.res diff --git a/src/bindings/Fuse.res b/apps/docs/src/bindings/Fuse.res similarity index 100% rename from src/bindings/Fuse.res rename to apps/docs/src/bindings/Fuse.res diff --git a/src/bindings/HeadlessUI.res b/apps/docs/src/bindings/HeadlessUI.res similarity index 100% rename from src/bindings/HeadlessUI.res rename to apps/docs/src/bindings/HeadlessUI.res diff --git a/src/bindings/Jsdom.res b/apps/docs/src/bindings/Jsdom.res similarity index 100% rename from src/bindings/Jsdom.res rename to apps/docs/src/bindings/Jsdom.res diff --git a/src/bindings/Mdast.res b/apps/docs/src/bindings/Mdast.res similarity index 100% rename from src/bindings/Mdast.res rename to apps/docs/src/bindings/Mdast.res diff --git a/src/bindings/Node.res b/apps/docs/src/bindings/Node.res similarity index 100% rename from src/bindings/Node.res rename to apps/docs/src/bindings/Node.res diff --git a/src/bindings/ReactMarkdown.res b/apps/docs/src/bindings/ReactMarkdown.res similarity index 100% rename from src/bindings/ReactMarkdown.res rename to apps/docs/src/bindings/ReactMarkdown.res diff --git a/src/bindings/ReactRouter.res b/apps/docs/src/bindings/ReactRouter.res similarity index 100% rename from src/bindings/ReactRouter.res rename to apps/docs/src/bindings/ReactRouter.res diff --git a/src/bindings/Rehype.res b/apps/docs/src/bindings/Rehype.res similarity index 100% rename from src/bindings/Rehype.res rename to apps/docs/src/bindings/Rehype.res diff --git a/src/bindings/Remark.res b/apps/docs/src/bindings/Remark.res similarity index 100% rename from src/bindings/Remark.res rename to apps/docs/src/bindings/Remark.res diff --git a/src/bindings/RescriptCompilerApi.res b/apps/docs/src/bindings/RescriptCompilerApi.res similarity index 100% rename from src/bindings/RescriptCompilerApi.res rename to apps/docs/src/bindings/RescriptCompilerApi.res diff --git a/src/bindings/RescriptCompilerApi.resi b/apps/docs/src/bindings/RescriptCompilerApi.resi similarity index 100% rename from src/bindings/RescriptCompilerApi.resi rename to apps/docs/src/bindings/RescriptCompilerApi.resi diff --git a/src/bindings/Vitest.res b/apps/docs/src/bindings/Vitest.res similarity index 100% rename from src/bindings/Vitest.res rename to apps/docs/src/bindings/Vitest.res diff --git a/src/common/Ansi.res b/apps/docs/src/common/Ansi.res similarity index 100% rename from src/common/Ansi.res rename to apps/docs/src/common/Ansi.res diff --git a/src/common/Ansi.resi b/apps/docs/src/common/Ansi.resi similarity index 100% rename from src/common/Ansi.resi rename to apps/docs/src/common/Ansi.resi diff --git a/src/common/ColorTheme.res b/apps/docs/src/common/ColorTheme.res similarity index 100% rename from src/common/ColorTheme.res rename to apps/docs/src/common/ColorTheme.res diff --git a/src/common/ColorTheme.resi b/apps/docs/src/common/ColorTheme.resi similarity index 100% rename from src/common/ColorTheme.resi rename to apps/docs/src/common/ColorTheme.resi diff --git a/src/common/Constants.res b/apps/docs/src/common/Constants.res similarity index 100% rename from src/common/Constants.res rename to apps/docs/src/common/Constants.res diff --git a/src/common/DateStr.res b/apps/docs/src/common/DateStr.res similarity index 100% rename from src/common/DateStr.res rename to apps/docs/src/common/DateStr.res diff --git a/src/common/DateStr.resi b/apps/docs/src/common/DateStr.resi similarity index 100% rename from src/common/DateStr.resi rename to apps/docs/src/common/DateStr.resi diff --git a/src/common/EnableCollapsibleNavbar.res b/apps/docs/src/common/EnableCollapsibleNavbar.res similarity index 100% rename from src/common/EnableCollapsibleNavbar.res rename to apps/docs/src/common/EnableCollapsibleNavbar.res diff --git a/src/common/HighlightJs.res b/apps/docs/src/common/HighlightJs.res similarity index 100% rename from src/common/HighlightJs.res rename to apps/docs/src/common/HighlightJs.res diff --git a/src/common/HighlightJs.resi b/apps/docs/src/common/HighlightJs.resi similarity index 100% rename from src/common/HighlightJs.resi rename to apps/docs/src/common/HighlightJs.resi diff --git a/src/common/Hooks.res b/apps/docs/src/common/Hooks.res similarity index 100% rename from src/common/Hooks.res rename to apps/docs/src/common/Hooks.res diff --git a/src/common/MetaDescription.res b/apps/docs/src/common/MetaDescription.res similarity index 100% rename from src/common/MetaDescription.res rename to apps/docs/src/common/MetaDescription.res diff --git a/src/common/Path.res b/apps/docs/src/common/Path.res similarity index 100% rename from src/common/Path.res rename to apps/docs/src/common/Path.res diff --git a/src/common/ScrollLockContext.res b/apps/docs/src/common/ScrollLockContext.res similarity index 100% rename from src/common/ScrollLockContext.res rename to apps/docs/src/common/ScrollLockContext.res diff --git a/src/common/Semver.res b/apps/docs/src/common/Semver.res similarity index 100% rename from src/common/Semver.res rename to apps/docs/src/common/Semver.res diff --git a/src/common/Semver.resi b/apps/docs/src/common/Semver.resi similarity index 100% rename from src/common/Semver.resi rename to apps/docs/src/common/Semver.resi diff --git a/src/common/Url.res b/apps/docs/src/common/Url.res similarity index 100% rename from src/common/Url.res rename to apps/docs/src/common/Url.res diff --git a/src/common/Url.resi b/apps/docs/src/common/Url.resi similarity index 100% rename from src/common/Url.resi rename to apps/docs/src/common/Url.resi diff --git a/src/common/Util.res b/apps/docs/src/common/Util.res similarity index 100% rename from src/common/Util.res rename to apps/docs/src/common/Util.res diff --git a/src/common/Util.resi b/apps/docs/src/common/Util.resi similarity index 100% rename from src/common/Util.resi rename to apps/docs/src/common/Util.resi diff --git a/src/common/WarningFlagDescription.res b/apps/docs/src/common/WarningFlagDescription.res similarity index 100% rename from src/common/WarningFlagDescription.res rename to apps/docs/src/common/WarningFlagDescription.res diff --git a/src/common/WarningFlagDescription.resi b/apps/docs/src/common/WarningFlagDescription.resi similarity index 100% rename from src/common/WarningFlagDescription.resi rename to apps/docs/src/common/WarningFlagDescription.resi diff --git a/src/components/AnsiPre.res b/apps/docs/src/components/AnsiPre.res similarity index 100% rename from src/components/AnsiPre.res rename to apps/docs/src/components/AnsiPre.res diff --git a/src/components/AnsiPre.resi b/apps/docs/src/components/AnsiPre.resi similarity index 100% rename from src/components/AnsiPre.resi rename to apps/docs/src/components/AnsiPre.resi diff --git a/src/components/ApiIntro.res b/apps/docs/src/components/ApiIntro.res similarity index 100% rename from src/components/ApiIntro.res rename to apps/docs/src/components/ApiIntro.res diff --git a/src/components/ApiIntro.resi b/apps/docs/src/components/ApiIntro.resi similarity index 100% rename from src/components/ApiIntro.resi rename to apps/docs/src/components/ApiIntro.resi diff --git a/src/components/ApiMarkdown.res b/apps/docs/src/components/ApiMarkdown.res similarity index 100% rename from src/components/ApiMarkdown.res rename to apps/docs/src/components/ApiMarkdown.res diff --git a/src/components/ApiMarkdown.resi b/apps/docs/src/components/ApiMarkdown.resi similarity index 100% rename from src/components/ApiMarkdown.resi rename to apps/docs/src/components/ApiMarkdown.resi diff --git a/src/components/Banner.res b/apps/docs/src/components/Banner.res similarity index 100% rename from src/components/Banner.res rename to apps/docs/src/components/Banner.res diff --git a/src/components/BreadCrumbs.res b/apps/docs/src/components/BreadCrumbs.res similarity index 100% rename from src/components/BreadCrumbs.res rename to apps/docs/src/components/BreadCrumbs.res diff --git a/src/components/Button.res b/apps/docs/src/components/Button.res similarity index 100% rename from src/components/Button.res rename to apps/docs/src/components/Button.res diff --git a/src/components/Button.resi b/apps/docs/src/components/Button.resi similarity index 100% rename from src/components/Button.resi rename to apps/docs/src/components/Button.resi diff --git a/src/components/CodeExample.res b/apps/docs/src/components/CodeExample.res similarity index 100% rename from src/components/CodeExample.res rename to apps/docs/src/components/CodeExample.res diff --git a/src/components/CodeExample.resi b/apps/docs/src/components/CodeExample.resi similarity index 100% rename from src/components/CodeExample.resi rename to apps/docs/src/components/CodeExample.resi diff --git a/src/components/CodeMirror.res b/apps/docs/src/components/CodeMirror.res similarity index 100% rename from src/components/CodeMirror.res rename to apps/docs/src/components/CodeMirror.res diff --git a/src/components/CodeMirror.resi b/apps/docs/src/components/CodeMirror.resi similarity index 100% rename from src/components/CodeMirror.resi rename to apps/docs/src/components/CodeMirror.resi diff --git a/src/components/CodeMirrorSetup.js b/apps/docs/src/components/CodeMirrorSetup.js similarity index 100% rename from src/components/CodeMirrorSetup.js rename to apps/docs/src/components/CodeMirrorSetup.js diff --git a/src/components/CommunityContent.res b/apps/docs/src/components/CommunityContent.res similarity index 100% rename from src/components/CommunityContent.res rename to apps/docs/src/components/CommunityContent.res diff --git a/src/components/DocsSidebar.res b/apps/docs/src/components/DocsSidebar.res similarity index 100% rename from src/components/DocsSidebar.res rename to apps/docs/src/components/DocsSidebar.res diff --git a/src/components/Docson.res b/apps/docs/src/components/Docson.res similarity index 100% rename from src/components/Docson.res rename to apps/docs/src/components/Docson.res diff --git a/src/components/Docson.resi b/apps/docs/src/components/Docson.resi similarity index 100% rename from src/components/Docson.resi rename to apps/docs/src/components/Docson.resi diff --git a/src/components/DocsonLazy.res b/apps/docs/src/components/DocsonLazy.res similarity index 100% rename from src/components/DocsonLazy.res rename to apps/docs/src/components/DocsonLazy.res diff --git a/src/components/Footer.res b/apps/docs/src/components/Footer.res similarity index 100% rename from src/components/Footer.res rename to apps/docs/src/components/Footer.res diff --git a/src/components/Footer.resi b/apps/docs/src/components/Footer.resi similarity index 100% rename from src/components/Footer.resi rename to apps/docs/src/components/Footer.resi diff --git a/src/components/Icon.res b/apps/docs/src/components/Icon.res similarity index 100% rename from src/components/Icon.res rename to apps/docs/src/components/Icon.res diff --git a/src/components/Icon.resi b/apps/docs/src/components/Icon.resi similarity index 100% rename from src/components/Icon.resi rename to apps/docs/src/components/Icon.resi diff --git a/src/components/Image.res b/apps/docs/src/components/Image.res similarity index 100% rename from src/components/Image.res rename to apps/docs/src/components/Image.res diff --git a/src/components/ImageGallery.res b/apps/docs/src/components/ImageGallery.res similarity index 100% rename from src/components/ImageGallery.res rename to apps/docs/src/components/ImageGallery.res diff --git a/src/components/Intro.res b/apps/docs/src/components/Intro.res similarity index 100% rename from src/components/Intro.res rename to apps/docs/src/components/Intro.res diff --git a/src/components/Markdown.res b/apps/docs/src/components/Markdown.res similarity index 100% rename from src/components/Markdown.res rename to apps/docs/src/components/Markdown.res diff --git a/src/components/Markdown.resi b/apps/docs/src/components/Markdown.resi similarity index 100% rename from src/components/Markdown.resi rename to apps/docs/src/components/Markdown.resi diff --git a/src/components/MarkdownComponents.res b/apps/docs/src/components/MarkdownComponents.res similarity index 100% rename from src/components/MarkdownComponents.res rename to apps/docs/src/components/MarkdownComponents.res diff --git a/src/components/MdxContent.res b/apps/docs/src/components/MdxContent.res similarity index 100% rename from src/components/MdxContent.res rename to apps/docs/src/components/MdxContent.res diff --git a/src/components/MdxContent.resi b/apps/docs/src/components/MdxContent.resi similarity index 100% rename from src/components/MdxContent.resi rename to apps/docs/src/components/MdxContent.resi diff --git a/src/components/Meta.res b/apps/docs/src/components/Meta.res similarity index 100% rename from src/components/Meta.res rename to apps/docs/src/components/Meta.res diff --git a/src/components/Meta.resi b/apps/docs/src/components/Meta.resi similarity index 100% rename from src/components/Meta.resi rename to apps/docs/src/components/Meta.resi diff --git a/src/components/NavbarMobileOverlay.res b/apps/docs/src/components/NavbarMobileOverlay.res similarity index 100% rename from src/components/NavbarMobileOverlay.res rename to apps/docs/src/components/NavbarMobileOverlay.res diff --git a/src/components/NavbarPrimary.res b/apps/docs/src/components/NavbarPrimary.res similarity index 100% rename from src/components/NavbarPrimary.res rename to apps/docs/src/components/NavbarPrimary.res diff --git a/src/components/NavbarPrimary.resi b/apps/docs/src/components/NavbarPrimary.resi similarity index 100% rename from src/components/NavbarPrimary.resi rename to apps/docs/src/components/NavbarPrimary.resi diff --git a/src/components/NavbarSecondary.res b/apps/docs/src/components/NavbarSecondary.res similarity index 100% rename from src/components/NavbarSecondary.res rename to apps/docs/src/components/NavbarSecondary.res diff --git a/src/components/NavbarTertiary.res b/apps/docs/src/components/NavbarTertiary.res similarity index 100% rename from src/components/NavbarTertiary.res rename to apps/docs/src/components/NavbarTertiary.res diff --git a/src/components/NavbarUtils.res b/apps/docs/src/components/NavbarUtils.res similarity index 100% rename from src/components/NavbarUtils.res rename to apps/docs/src/components/NavbarUtils.res diff --git a/src/components/Navigation.res b/apps/docs/src/components/Navigation.res similarity index 100% rename from src/components/Navigation.res rename to apps/docs/src/components/Navigation.res diff --git a/src/components/Navigation.resi b/apps/docs/src/components/Navigation.resi similarity index 100% rename from src/components/Navigation.resi rename to apps/docs/src/components/Navigation.resi diff --git a/src/components/Search.res b/apps/docs/src/components/Search.res similarity index 100% rename from src/components/Search.res rename to apps/docs/src/components/Search.res diff --git a/src/components/SearchBox.res b/apps/docs/src/components/SearchBox.res similarity index 100% rename from src/components/SearchBox.res rename to apps/docs/src/components/SearchBox.res diff --git a/src/components/Tag.res b/apps/docs/src/components/Tag.res similarity index 100% rename from src/components/Tag.res rename to apps/docs/src/components/Tag.res diff --git a/src/components/Tag.resi b/apps/docs/src/components/Tag.resi similarity index 100% rename from src/components/Tag.resi rename to apps/docs/src/components/Tag.resi diff --git a/src/components/Text.res b/apps/docs/src/components/Text.res similarity index 100% rename from src/components/Text.res rename to apps/docs/src/components/Text.res diff --git a/src/components/Text.resi b/apps/docs/src/components/Text.resi similarity index 100% rename from src/components/Text.resi rename to apps/docs/src/components/Text.resi diff --git a/src/components/ToggleButton.res b/apps/docs/src/components/ToggleButton.res similarity index 100% rename from src/components/ToggleButton.res rename to apps/docs/src/components/ToggleButton.res diff --git a/src/components/VersionSelect.res b/apps/docs/src/components/VersionSelect.res similarity index 100% rename from src/components/VersionSelect.res rename to apps/docs/src/components/VersionSelect.res diff --git a/src/components/VersionSelect.resi b/apps/docs/src/components/VersionSelect.resi similarity index 100% rename from src/components/VersionSelect.resi rename to apps/docs/src/components/VersionSelect.resi diff --git a/src/components/Video.res b/apps/docs/src/components/Video.res similarity index 100% rename from src/components/Video.res rename to apps/docs/src/components/Video.res diff --git a/src/components/WarningTable.res b/apps/docs/src/components/WarningTable.res similarity index 100% rename from src/components/WarningTable.res rename to apps/docs/src/components/WarningTable.res diff --git a/src/data/BlogApi.res b/apps/docs/src/data/BlogApi.res similarity index 100% rename from src/data/BlogApi.res rename to apps/docs/src/data/BlogApi.res diff --git a/src/data/BlogApi.resi b/apps/docs/src/data/BlogApi.resi similarity index 100% rename from src/data/BlogApi.resi rename to apps/docs/src/data/BlogApi.resi diff --git a/src/data/BlogLoader.res b/apps/docs/src/data/BlogLoader.res similarity index 100% rename from src/data/BlogLoader.res rename to apps/docs/src/data/BlogLoader.res diff --git a/src/data/CommunityResources.res b/apps/docs/src/data/CommunityResources.res similarity index 100% rename from src/data/CommunityResources.res rename to apps/docs/src/data/CommunityResources.res diff --git a/src/data/MetaTagsApi.res b/apps/docs/src/data/MetaTagsApi.res similarity index 100% rename from src/data/MetaTagsApi.res rename to apps/docs/src/data/MetaTagsApi.res diff --git a/src/data/OurUsers.res b/apps/docs/src/data/OurUsers.res similarity index 100% rename from src/data/OurUsers.res rename to apps/docs/src/data/OurUsers.res diff --git a/src/ffi/loadScript.js b/apps/docs/src/ffi/loadScript.js similarity index 100% rename from src/ffi/loadScript.js rename to apps/docs/src/ffi/loadScript.js diff --git a/src/ffi/parse-numeric-range.js b/apps/docs/src/ffi/parse-numeric-range.js similarity index 100% rename from src/ffi/parse-numeric-range.js rename to apps/docs/src/ffi/parse-numeric-range.js diff --git a/src/ffi/react-codemirror-hooks.js b/apps/docs/src/ffi/react-codemirror-hooks.js similarity index 100% rename from src/ffi/react-codemirror-hooks.js rename to apps/docs/src/ffi/react-codemirror-hooks.js diff --git a/src/layouts/CommunityLayout.res b/apps/docs/src/layouts/CommunityLayout.res similarity index 100% rename from src/layouts/CommunityLayout.res rename to apps/docs/src/layouts/CommunityLayout.res diff --git a/src/layouts/DocsLayout.res b/apps/docs/src/layouts/DocsLayout.res similarity index 100% rename from src/layouts/DocsLayout.res rename to apps/docs/src/layouts/DocsLayout.res diff --git a/src/layouts/MainLayout.res b/apps/docs/src/layouts/MainLayout.res similarity index 100% rename from src/layouts/MainLayout.res rename to apps/docs/src/layouts/MainLayout.res diff --git a/src/layouts/MainLayout.resi b/apps/docs/src/layouts/MainLayout.resi similarity index 100% rename from src/layouts/MainLayout.resi rename to apps/docs/src/layouts/MainLayout.resi diff --git a/src/layouts/SidebarLayout.res b/apps/docs/src/layouts/SidebarLayout.res similarity index 100% rename from src/layouts/SidebarLayout.res rename to apps/docs/src/layouts/SidebarLayout.res diff --git a/src/layouts/SidebarLayout.resi b/apps/docs/src/layouts/SidebarLayout.resi similarity index 100% rename from src/layouts/SidebarLayout.resi rename to apps/docs/src/layouts/SidebarLayout.resi diff --git a/src/markdown/BlogFrontmatter.res b/apps/docs/src/markdown/BlogFrontmatter.res similarity index 100% rename from src/markdown/BlogFrontmatter.res rename to apps/docs/src/markdown/BlogFrontmatter.res diff --git a/src/markdown/CompiledMdx.res b/apps/docs/src/markdown/CompiledMdx.res similarity index 100% rename from src/markdown/CompiledMdx.res rename to apps/docs/src/markdown/CompiledMdx.res diff --git a/src/markdown/CompiledMdx.resi b/apps/docs/src/markdown/CompiledMdx.resi similarity index 100% rename from src/markdown/CompiledMdx.resi rename to apps/docs/src/markdown/CompiledMdx.resi diff --git a/src/markdown/DocFrontmatter.res b/apps/docs/src/markdown/DocFrontmatter.res similarity index 100% rename from src/markdown/DocFrontmatter.res rename to apps/docs/src/markdown/DocFrontmatter.res diff --git a/src/markdown/DocFrontmatter.resi b/apps/docs/src/markdown/DocFrontmatter.resi similarity index 100% rename from src/markdown/DocFrontmatter.resi rename to apps/docs/src/markdown/DocFrontmatter.resi diff --git a/src/markdown/FrontmatterUtils.res b/apps/docs/src/markdown/FrontmatterUtils.res similarity index 100% rename from src/markdown/FrontmatterUtils.res rename to apps/docs/src/markdown/FrontmatterUtils.res diff --git a/src/markdown/MarkdownParser.res b/apps/docs/src/markdown/MarkdownParser.res similarity index 100% rename from src/markdown/MarkdownParser.res rename to apps/docs/src/markdown/MarkdownParser.res diff --git a/src/markdown/MarkdownParser.resi b/apps/docs/src/markdown/MarkdownParser.resi similarity index 100% rename from src/markdown/MarkdownParser.resi rename to apps/docs/src/markdown/MarkdownParser.resi diff --git a/src/markdown/Mdx.res b/apps/docs/src/markdown/Mdx.res similarity index 100% rename from src/markdown/Mdx.res rename to apps/docs/src/markdown/Mdx.res diff --git a/src/markdown/MdxFile.res b/apps/docs/src/markdown/MdxFile.res similarity index 100% rename from src/markdown/MdxFile.res rename to apps/docs/src/markdown/MdxFile.res diff --git a/src/markdown/MdxFile.resi b/apps/docs/src/markdown/MdxFile.resi similarity index 100% rename from src/markdown/MdxFile.resi rename to apps/docs/src/markdown/MdxFile.resi diff --git a/src/markdown/MdxLegacy.res b/apps/docs/src/markdown/MdxLegacy.res similarity index 100% rename from src/markdown/MdxLegacy.res rename to apps/docs/src/markdown/MdxLegacy.res diff --git a/src/markdown/SidebarHelpers.res b/apps/docs/src/markdown/SidebarHelpers.res similarity index 100% rename from src/markdown/SidebarHelpers.res rename to apps/docs/src/markdown/SidebarHelpers.res diff --git a/src/markdown/SidebarHelpers.resi b/apps/docs/src/markdown/SidebarHelpers.resi similarity index 100% rename from src/markdown/SidebarHelpers.resi rename to apps/docs/src/markdown/SidebarHelpers.resi diff --git a/src/markdown/TableOfContents.res b/apps/docs/src/markdown/TableOfContents.res similarity index 100% rename from src/markdown/TableOfContents.res rename to apps/docs/src/markdown/TableOfContents.res diff --git a/src/markdown/TocUtils.res b/apps/docs/src/markdown/TocUtils.res similarity index 100% rename from src/markdown/TocUtils.res rename to apps/docs/src/markdown/TocUtils.res diff --git a/src/playground/CompilerManagerHook.res b/apps/docs/src/playground/CompilerManagerHook.res similarity index 100% rename from src/playground/CompilerManagerHook.res rename to apps/docs/src/playground/CompilerManagerHook.res diff --git a/src/playground/CompilerManagerHook.resi b/apps/docs/src/playground/CompilerManagerHook.resi similarity index 100% rename from src/playground/CompilerManagerHook.resi rename to apps/docs/src/playground/CompilerManagerHook.resi diff --git a/src/playground/ConsolePanel.res b/apps/docs/src/playground/ConsolePanel.res similarity index 100% rename from src/playground/ConsolePanel.res rename to apps/docs/src/playground/ConsolePanel.res diff --git a/src/playground/EvalIFrame.res b/apps/docs/src/playground/EvalIFrame.res similarity index 100% rename from src/playground/EvalIFrame.res rename to apps/docs/src/playground/EvalIFrame.res diff --git a/src/playground/LzString.res b/apps/docs/src/playground/LzString.res similarity index 100% rename from src/playground/LzString.res rename to apps/docs/src/playground/LzString.res diff --git a/src/playground/OutputPanel.res b/apps/docs/src/playground/OutputPanel.res similarity index 100% rename from src/playground/OutputPanel.res rename to apps/docs/src/playground/OutputPanel.res diff --git a/src/playground/Playground.res b/apps/docs/src/playground/Playground.res similarity index 100% rename from src/playground/Playground.res rename to apps/docs/src/playground/Playground.res diff --git a/src/playground/Playground.resi b/apps/docs/src/playground/Playground.resi similarity index 100% rename from src/playground/Playground.resi rename to apps/docs/src/playground/Playground.resi diff --git a/src/playground/PlaygroundLazy.res b/apps/docs/src/playground/PlaygroundLazy.res similarity index 100% rename from src/playground/PlaygroundLazy.res rename to apps/docs/src/playground/PlaygroundLazy.res diff --git a/src/playground/RenderPanel.res b/apps/docs/src/playground/RenderPanel.res similarity index 100% rename from src/playground/RenderPanel.res rename to apps/docs/src/playground/RenderPanel.res diff --git a/src/playground/RenderPanel.resi b/apps/docs/src/playground/RenderPanel.resi similarity index 100% rename from src/playground/RenderPanel.resi rename to apps/docs/src/playground/RenderPanel.resi diff --git a/src/shims/Shims.res b/apps/docs/src/shims/Shims.res similarity index 100% rename from src/shims/Shims.res rename to apps/docs/src/shims/Shims.res diff --git a/src/shims/_shims.mjs b/apps/docs/src/shims/_shims.mjs similarity index 100% rename from src/shims/_shims.mjs rename to apps/docs/src/shims/_shims.mjs diff --git a/stdlib-toc.json b/apps/docs/stdlib-toc.json similarity index 100% rename from stdlib-toc.json rename to apps/docs/stdlib-toc.json diff --git a/styles/_docsearch.css b/apps/docs/styles/_docsearch.css similarity index 100% rename from styles/_docsearch.css rename to apps/docs/styles/_docsearch.css diff --git a/styles/_fonts.css b/apps/docs/styles/_fonts.css similarity index 100% rename from styles/_fonts.css rename to apps/docs/styles/_fonts.css diff --git a/styles/_hljs.css b/apps/docs/styles/_hljs.css similarity index 100% rename from styles/_hljs.css rename to apps/docs/styles/_hljs.css diff --git a/styles/_markdown.css b/apps/docs/styles/_markdown.css similarity index 100% rename from styles/_markdown.css rename to apps/docs/styles/_markdown.css diff --git a/styles/_theme.css b/apps/docs/styles/_theme.css similarity index 100% rename from styles/_theme.css rename to apps/docs/styles/_theme.css diff --git a/styles/docson.css b/apps/docs/styles/docson.css similarity index 100% rename from styles/docson.css rename to apps/docs/styles/docson.css diff --git a/styles/main.css b/apps/docs/styles/main.css similarity index 100% rename from styles/main.css rename to apps/docs/styles/main.css diff --git a/styles/test-overrides.css b/apps/docs/styles/test-overrides.css similarity index 100% rename from styles/test-overrides.css rename to apps/docs/styles/test-overrides.css diff --git a/styles/utils.css b/apps/docs/styles/utils.css similarity index 100% rename from styles/utils.css rename to apps/docs/styles/utils.css diff --git a/vite.config.mjs b/apps/docs/vite.config.mjs similarity index 100% rename from vite.config.mjs rename to apps/docs/vite.config.mjs diff --git a/vitest.config.mjs b/apps/docs/vitest.config.mjs similarity index 100% rename from vitest.config.mjs rename to apps/docs/vitest.config.mjs diff --git a/vitest.setup.mjs b/apps/docs/vitest.setup.mjs similarity index 100% rename from vitest.setup.mjs rename to apps/docs/vitest.setup.mjs From b136bda1cd7de8d82bdd8bb7f6fa7f26c51ab2ed Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sat, 25 Apr 2026 19:41:39 -0400 Subject: [PATCH 04/32] chore: update workspace root tooling --- .gitignore | 66 ++++++++++++++++++++++++++++++++---------------------- Makefile | 10 ++++----- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 4e1e38164..a4fff1e5d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,55 +3,63 @@ *.swo .worktrees/ -# Generated via update-index script -public/blog/feed.xml +# Generated via docs update-index script +apps/docs/public/blog/feed.xml node_modules/ .next/ out/ +apps/docs/out/ index_data/*.json +apps/docs/index_data/*.json -# Generated via test examples script +# Generated via docs test examples script temp temp-js-output temp-jsx-preserve +apps/docs/temp +apps/docs/temp-js-output +apps/docs/temp-jsx-preserve .bsb.lock .merlin lib/ +apps/docs/lib/ .vercel -src/**/*.mjs -src/**/*.jsx -scripts/gendocs.mjs -scripts/generate_*.mjs -scripts/gendocs.jsx -scripts/generate_*.jsx +apps/docs/src/**/*.mjs +apps/docs/src/**/*.jsx +apps/docs/scripts/gendocs.mjs +apps/docs/scripts/generate_*.mjs +apps/docs/scripts/gendocs.jsx +apps/docs/scripts/generate_*.jsx # Generated via generate-llms script -public/llms/manual/**/llm*.txt -public/llms/react/**/llm*.txt -pages/docs/**/**/llms.mdx - -public/playground-bundles/ -!public/_redirects +apps/docs/public/llms/manual/**/llm*.txt +apps/docs/public/llms/react/**/llm*.txt +apps/docs/pages/docs/**/**/llms.mdx +apps/docs/public/playground-bundles/ +!apps/docs/public/_redirects dist build +apps/docs/dist +apps/docs/build .react-router -.worktrees/ +apps/docs/.react-router mdx-manifest.json - -app/**/*.mjs -app/**/*.jsx -functions/**/*.mjs -functions/**/*.jsx -__tests__/**/*.mjs -__tests__/**/*.jsx -e2e/**/*.mjs -e2e/**/*.jsx +apps/docs/mdx-manifest.json + +apps/docs/app/**/*.mjs +apps/docs/app/**/*.jsx +apps/docs/functions/**/*.mjs +apps/docs/functions/**/*.jsx +apps/docs/__tests__/**/*.mjs +apps/docs/__tests__/**/*.jsx +apps/docs/e2e/**/*.mjs +apps/docs/e2e/**/*.jsx !_shims.mjs !_shims.jsx @@ -65,16 +73,20 @@ e2e/**/*.jsx # wrangler .wrangler +apps/docs/.wrangler -# Scripts generated from rolldown to convert them from .jsx files to .mjs files +# Scripts generated from tsdown to convert them from .jsx files to .mjs files _scripts +apps/docs/_scripts # Local env files .env.local +apps/docs/.env.local # Local git worktrees .worktrees # Vitest screenshots -!__tests__/__screenshots__/**/* +!apps/docs/__tests__/__screenshots__/**/* .vitest-attachments +apps/docs/.vitest-attachments diff --git a/Makefile b/Makefile index 75e300207..a1ca65c89 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,11 @@ SHELL = /bin/bash node_modules/.bin/rescript: yarn install - yarn update-index + yarn build:update-index -build: node_modules/.bin/rescript - node_modules/.bin/rescript - yarn update-index +build: node_modules/.bin/rescript + yarn build:res + yarn build:update-index dev: build yarn dev @@ -15,7 +15,7 @@ test: build yarn test clean: - rm -r node_modules lib + rm -r node_modules apps/docs/lib apps/docs/build apps/docs/out .DEFAULT_GOAL := build From 26d2f1ed649370d1d397ce2cf9c9c9c82823bce6 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sat, 25 Apr 2026 19:49:19 -0400 Subject: [PATCH 05/32] fix: generate docs scripts before index updates --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index a1ca65c89..b04ee52e5 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,12 @@ SHELL = /bin/bash node_modules/.bin/rescript: yarn install + yarn build:scripts yarn build:update-index build: node_modules/.bin/rescript yarn build:res + yarn build:scripts yarn build:update-index dev: build From c95c842a59b87da27f29a0a87a1643e7db0ee228 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sun, 26 Apr 2026 11:30:09 -0400 Subject: [PATCH 06/32] fix: make root build work from clean checkout --- .gitignore | 2 +- Makefile | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a4fff1e5d..f9b1e0f1e 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,7 @@ apps/docs/scripts/generate_*.jsx # Generated via generate-llms script apps/docs/public/llms/manual/**/llm*.txt apps/docs/public/llms/react/**/llm*.txt -apps/docs/pages/docs/**/**/llms.mdx +apps/docs/markdown-pages/docs/**/**/llms.mdx apps/docs/public/playground-bundles/ !apps/docs/public/_redirects diff --git a/Makefile b/Makefile index b04ee52e5..11380d35a 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,6 @@ SHELL = /bin/bash node_modules/.bin/rescript: yarn install - yarn build:scripts - yarn build:update-index build: node_modules/.bin/rescript yarn build:res From 5237c1a879fb6819cb79c64e03c203416b188beb Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sun, 26 Apr 2026 11:44:46 -0400 Subject: [PATCH 07/32] fix: resolve docs workspace paths --- apps/docs/scripts/test-examples.mjs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/docs/scripts/test-examples.mjs b/apps/docs/scripts/test-examples.mjs index 1af3d7a22..93846af3b 100644 --- a/apps/docs/scripts/test-examples.mjs +++ b/apps/docs/scripts/test-examples.mjs @@ -16,6 +16,9 @@ const rescriptCliPath = path.join( "cli", "rescript.js", ); +const rescriptReactPackageRoot = path.dirname( + require.resolve("@rescript/react/package.json"), +); let makeRescriptJson = ({ preserve = false } = {}) => `{ "name": "temp", @@ -505,13 +508,9 @@ let ensureTempProject = ({ tempRoot, preserve = false }) => { let tempReactPackage = path.join(tempNodeModules, "react"); if (!fs.existsSync(tempReactPackage)) { fs.mkdirSync(tempNodeModules, { recursive: true }); - fs.cpSync( - path.join(projectRoot, "node_modules", "@rescript", "react"), - tempReactPackage, - { - recursive: true, - }, - ); + fs.cpSync(rescriptReactPackageRoot, tempReactPackage, { + recursive: true, + }); } }; From b962da66aa8896c23960599835065accbd1ea2e5 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sun, 26 Apr 2026 12:05:24 -0400 Subject: [PATCH 08/32] fix: update repository links for docs workspace --- apps/docs/app/routes/CommunityRoute.res | 3 ++- apps/docs/app/routes/DocsGuidelinesRoute.res | 3 ++- apps/docs/app/routes/DocsManualRoute.res | 3 ++- apps/docs/app/routes/DocsReactRoute.res | 3 ++- apps/docs/app/routes/LandingPage.res | 4 +++- apps/docs/rescript.json | 2 +- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/docs/app/routes/CommunityRoute.res b/apps/docs/app/routes/CommunityRoute.res index ee7b5125b..856668496 100644 --- a/apps/docs/app/routes/CommunityRoute.res +++ b/apps/docs/app/routes/CommunityRoute.res @@ -52,7 +52,8 @@ let loader: ReactRouter.Loader.t = async ({request}) => { let default = () => { let {compiledMdx, entries, filePath, categories} = ReactRouter.useLoaderData() - let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${filePath}` + let docsAppRoot = "apps/docs" + let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${docsAppRoot}/${filePath}` <> diff --git a/apps/docs/app/routes/DocsGuidelinesRoute.res b/apps/docs/app/routes/DocsGuidelinesRoute.res index 235818836..74ad454b9 100644 --- a/apps/docs/app/routes/DocsGuidelinesRoute.res +++ b/apps/docs/app/routes/DocsGuidelinesRoute.res @@ -36,7 +36,8 @@ let loader: ReactRouter.Loader.t = async ({request}) => { let default = () => { let {compiledMdx, entries, title, description, filePath} = ReactRouter.useLoaderData() - let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${filePath}` + let docsAppRoot = "apps/docs" + let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${docsAppRoot}/${filePath}` let categories: array = [] diff --git a/apps/docs/app/routes/DocsManualRoute.res b/apps/docs/app/routes/DocsManualRoute.res index 1a96977da..643b86203 100644 --- a/apps/docs/app/routes/DocsManualRoute.res +++ b/apps/docs/app/routes/DocsManualRoute.res @@ -71,7 +71,8 @@ let default = () => { }, } - let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${filePath}` + let docsAppRoot = "apps/docs" + let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${docsAppRoot}/${filePath}` let activeToc = {TableOfContents.title, entries} diff --git a/apps/docs/app/routes/DocsReactRoute.res b/apps/docs/app/routes/DocsReactRoute.res index e70cb1d57..8c52e7dca 100644 --- a/apps/docs/app/routes/DocsReactRoute.res +++ b/apps/docs/app/routes/DocsReactRoute.res @@ -64,7 +64,8 @@ let default = () => { }, } - let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${filePath}` + let docsAppRoot = "apps/docs" + let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${docsAppRoot}/${filePath}` let activeToc = {TableOfContents.title, entries} diff --git a/apps/docs/app/routes/LandingPage.res b/apps/docs/app/routes/LandingPage.res index feda1e7c2..36fa84650 100644 --- a/apps/docs/app/routes/LandingPage.res +++ b/apps/docs/app/routes/LandingPage.res @@ -523,6 +523,8 @@ module OtherSellingPoints = { module TrustedBy = { @react.component let make = () => { + let ourUsersSourcePath = "apps/docs/src/common/OurUsers.res" +

{React.string("Trusted by our users")} @@ -545,7 +547,7 @@ module TrustedBy = { ->React.array} diff --git a/apps/docs/rescript.json b/apps/docs/rescript.json index 9b50b9b33..ff18cc80b 100644 --- a/apps/docs/rescript.json +++ b/apps/docs/rescript.json @@ -1,5 +1,5 @@ { - "name": "rescript-lang.org", + "name": "@rescript-lang/docs", "namespace": false, "jsx": { "preserve": true, From b991130b0afb53d98cee9eb85be0bab272434646 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sun, 26 Apr 2026 12:13:34 -0400 Subject: [PATCH 09/32] fix: update deployment workflows for docs workspace --- .github/workflows/deploy-fork-preview.yml | 1 + .github/workflows/deploy.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/deploy-fork-preview.yml b/.github/workflows/deploy-fork-preview.yml index 7da9cfc2f..f2692a7b7 100644 --- a/.github/workflows/deploy-fork-preview.yml +++ b/.github/workflows/deploy-fork-preview.yml @@ -78,6 +78,7 @@ jobs: with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + workingDirectory: apps/docs command: pages deploy out --project-name=rescript-lang-org gitHubToken: ${{ secrets.GITHUB_TOKEN }} wranglerVersion: 4.61.1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index eb7a7b01d..7b4be86a7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -61,6 +61,7 @@ jobs: with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + workingDirectory: apps/docs command: pages deploy out --project-name=rescript-lang-org --branch=${{ env.SAFE_BRANCH }} gitHubToken: ${{ secrets.GITHUB_TOKEN }} wranglerVersion: 4.63.0 @@ -101,5 +102,6 @@ jobs: uses: cypress-io/github-action@v7 with: install: false + working-directory: apps/docs browser: chrome config: baseUrl=${{ needs.deploy.outputs.deployment-url }} From ec10200f2873bff0afd8dfa0cc983d2217cf2297 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sun, 26 Apr 2026 12:19:54 -0400 Subject: [PATCH 10/32] fix: update formatter ignores for docs workspace --- .oxfmtrc.json | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 702fe8415..47e98741a 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -4,22 +4,22 @@ "sortPackageJson": false, "ignorePatterns": [ "!_shims.mjs", - "!public/_redirects", + "!apps/docs/public/_redirects", ".DS_Store", ".worktrees/", ".next/", ".react-router", "*.swo", "*.swp", - "app/**/*.mjs", + "apps/docs/app/**/*.mjs", "build", - "data/api/**/*.json", + "apps/docs/data/api/**/*.json", "dist", - "index_data/*.json", + "apps/docs/index_data/*.json", "node_modules/", - "out/", - "public/playground-bundles/", - "public/blog/feed.xml", + "apps/docs/out/", + "apps/docs/public/playground-bundles/", + "apps/docs/public/blog/feed.xml", "_tempFile.cmi", "_tempFile.cmj", "_tempFile.cmt", @@ -28,15 +28,16 @@ ".bsb.lock", ".merlin", "lib/", + "apps/docs/lib/", ".vercel", - "src/**/*.mjs", - "scripts/gendocs.mjs", - "scripts/generate_*.mjs", - "public/llms/manual/**/llm*.txt", - "public/llms/react/**/llm*.txt", - "pages/docs/**/**/llms.mdx", - "markdown-pages/docs/manual/installation.mdx", + "apps/docs/src/**/*.mjs", + "apps/docs/scripts/gendocs.mjs", + "apps/docs/scripts/generate_*.mjs", + "apps/docs/public/llms/manual/**/llm*.txt", + "apps/docs/public/llms/react/**/llm*.txt", + "apps/docs/markdown-pages/docs/**/**/llms.mdx", + "apps/docs/markdown-pages/docs/manual/installation.mdx", ".yarn/releases/yarn-4.12.0.cjs", - "data/api/*" + "apps/docs/data/api/*" ] } From 9814ff604d278981d8fd1d986cc233c8d4045a83 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sun, 26 Apr 2026 12:25:37 -0400 Subject: [PATCH 11/32] fix: run docs formatter from repo root --- apps/docs/package.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index 7faf7b327..9a06a554a 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -13,7 +13,7 @@ "build:update-index": "yarn build:generate-llms && node _scripts/generate_feed.mjs > public/blog/feed.xml", "build:vite": "react-router build", "build": "yarn build:res && yarn build:scripts && yarn build:update-index && yarn build:vite", - "ci:format": "oxfmt --check", + "ci:format": "cd ../.. && oxfmt --check", "ci:test": "yarn vitest --run --browser.headless", "clean:res": "rescript clean", "convert-images": "auto-convert-images", @@ -21,7 +21,7 @@ "dev:vite": "react-router dev --host", "dev:wrangler": "yarn wrangler pages dev build/client", "dev": "yarn prepare && yarn dev:res & yarn dev:vite & yarn dev:wrangler", - "format": "oxfmt && rescript format", + "format": "cd ../.. && oxfmt && cd apps/docs && rescript format", "prepare": "yarn build:res && yarn build:scripts && yarn build:update-index", "preview": "yarn build && static-server build/client", "reanalyze": "rescript-tools reanalyze -all-cmt .", diff --git a/package.json b/package.json index c2e81c7e2..c59dbe421 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dev:vite": "yarn workspace @rescript-lang/docs dev:vite", "dev:wrangler": "yarn workspace @rescript-lang/docs dev:wrangler", "dev": "yarn workspace @rescript-lang/docs dev", - "format": "oxfmt && yarn workspace @rescript-lang/docs format", + "format": "yarn workspace @rescript-lang/docs format", "prepare": "yarn workspace @rescript-lang/docs prepare", "preview": "yarn workspace @rescript-lang/docs preview", "reanalyze": "yarn workspace @rescript-lang/docs reanalyze", From a444f75b2309e8ee1370a6408565e94d558510a3 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Sun, 26 Apr 2026 12:32:31 -0400 Subject: [PATCH 12/32] fix: correct users source link --- apps/docs/app/routes/LandingPage.res | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/app/routes/LandingPage.res b/apps/docs/app/routes/LandingPage.res index 36fa84650..f199313fd 100644 --- a/apps/docs/app/routes/LandingPage.res +++ b/apps/docs/app/routes/LandingPage.res @@ -523,7 +523,7 @@ module OtherSellingPoints = { module TrustedBy = { @react.component let make = () => { - let ourUsersSourcePath = "apps/docs/src/common/OurUsers.res" + let ourUsersSourcePath = "apps/docs/src/data/OurUsers.res"

From 0d8e8f8189072102acdddc7aedf196bd3f631cca Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 27 Apr 2026 10:50:45 -0400 Subject: [PATCH 13/32] fix: load docs env from workspace root --- apps/docs/vite.config.mjs | 1 + apps/docs/vitest.config.mjs | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/docs/vite.config.mjs b/apps/docs/vite.config.mjs index 47cb9e623..f3ec7a998 100644 --- a/apps/docs/vite.config.mjs +++ b/apps/docs/vite.config.mjs @@ -9,6 +9,7 @@ import pageReload from "vite-plugin-page-reload"; const excludedFiles = ["lib/**", "**/*.res", "**/*.resi"]; export default defineConfig({ + envDir: "../..", plugins: [ tailwindcss(), reactRouter(), diff --git a/apps/docs/vitest.config.mjs b/apps/docs/vitest.config.mjs index dbaeaa05f..cd0c3930e 100644 --- a/apps/docs/vitest.config.mjs +++ b/apps/docs/vitest.config.mjs @@ -4,6 +4,7 @@ import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ + envDir: "../..", plugins: [react(), tailwindcss()], test: { include: ["__tests__/*.jsx"], From 4222f0086078b1ab4c8f801c9008798a92e2708f Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 27 Apr 2026 11:00:45 -0400 Subject: [PATCH 14/32] fix: handle nested public hrefs in docs checks --- apps/docs/scripts/test-hrefs.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/scripts/test-hrefs.mjs b/apps/docs/scripts/test-hrefs.mjs index 4cab81c03..ea7ae6491 100644 --- a/apps/docs/scripts/test-hrefs.mjs +++ b/apps/docs/scripts/test-hrefs.mjs @@ -28,14 +28,14 @@ for (const file of files) { // Skip warnings about files that exist in public/ (served at root by Vite) const missingFileMatches = [ - ...warningMessage.matchAll(/`\.\.\/\.\.\/(.*?)`/g), + ...warningMessage.matchAll(/`((?:\.\.\/)+.*?)`/g), ]; let allMissingExistInPublic = false; if (missingFileMatches.length > 0) { allMissingExistInPublic = ( await Promise.all( missingFileMatches.map(([, p]) => - fs.access("public/" + p).then( + fs.access("public/" + p.replace(/^(?:\.\.\/)+/, "")).then( () => true, () => false, ), From d5b8df0e750476f8f4d56f9663366fda44d6ed6c Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 27 Apr 2026 11:43:35 -0400 Subject: [PATCH 15/32] fix: add root rescript monorepo config --- apps/docs/rescript.json | 9 --------- package.json | 6 +++--- rescript.json | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 rescript.json diff --git a/apps/docs/rescript.json b/apps/docs/rescript.json index ff18cc80b..9b0acd7f9 100644 --- a/apps/docs/rescript.json +++ b/apps/docs/rescript.json @@ -1,10 +1,6 @@ { "name": "@rescript-lang/docs", "namespace": false, - "jsx": { - "preserve": true, - "version": 4 - }, "dependencies": ["@rescript/react", "@rescript/webapi"], "compiler-flags": ["-open WebAPI.Global"], "sources": [ @@ -35,14 +31,9 @@ "subdirs": true } ], - "package-specs": { - "module": "esmodule", - "in-source": true - }, "warnings": { "error": "+8" }, - "suffix": ".jsx", "gentypeconfig": { "language": "untyped", "shims": [], diff --git a/package.json b/package.json index c59dbe421..e6c484677 100644 --- a/package.json +++ b/package.json @@ -12,16 +12,16 @@ "scripts": { "build:scripts": "yarn workspace @rescript-lang/docs build:scripts", "build:generate-llms": "yarn workspace @rescript-lang/docs build:generate-llms", - "build:res": "yarn workspace @rescript-lang/docs build:res", + "build:res": "rescript build --warn-error +3+8+11+12+26+27+31+32+33+34+35+39+44+45+110", "build:sync-bundles": "yarn workspace @rescript-lang/docs build:sync-bundles", "build:update-index": "yarn workspace @rescript-lang/docs build:update-index", "build:vite": "yarn workspace @rescript-lang/docs build:vite", "build": "yarn workspace @rescript-lang/docs build", "ci:format": "oxfmt --check", "ci:test": "yarn workspace @rescript-lang/docs ci:test", - "clean:res": "yarn workspace @rescript-lang/docs clean:res", + "clean:res": "rescript clean", "convert-images": "yarn workspace @rescript-lang/docs convert-images", - "dev:res": "yarn workspace @rescript-lang/docs dev:res", + "dev:res": "rescript watch", "dev:vite": "yarn workspace @rescript-lang/docs dev:vite", "dev:wrangler": "yarn workspace @rescript-lang/docs dev:wrangler", "dev": "yarn workspace @rescript-lang/docs dev", diff --git a/rescript.json b/rescript.json new file mode 100644 index 000000000..9babbd1df --- /dev/null +++ b/rescript.json @@ -0,0 +1,14 @@ +{ + "name": "rescript-lang.org-monorepo", + "dependencies": ["@rescript-lang/docs"], + "sources": [], + "jsx": { + "preserve": true, + "version": 4 + }, + "package-specs": { + "module": "esmodule", + "in-source": true + }, + "suffix": ".jsx" +} From 97880981625496073a81fdfbcdeccdd2e0e9ad70 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 27 Apr 2026 12:33:51 -0400 Subject: [PATCH 16/32] refactor: split playground into workspace packages Move shared editor/compiler helpers into @rescript-lang/shared and the playground implementation into @rescript-lang/playground. Wire the docs app to consume both workspace packages and keep playground browser storage access safe during prerendering. --- .github/workflows/deploy-fork-preview.yml | 2 +- .github/workflows/deploy.yml | 2 +- .gitignore | 3 + apps/docs/package.json | 16 +--- apps/docs/rescript.json | 7 +- .../playground}/ffi/loadScript.js | 0 packages/playground/package.json | 15 ++++ packages/playground/rescript.json | 19 ++++ .../playground/src}/CompilerManagerHook.res | 2 +- .../playground/src}/CompilerManagerHook.resi | 0 .../playground/src}/ConsolePanel.res | 0 .../playground/src}/EvalIFrame.res | 0 .../playground/src}/LzString.res | 0 .../playground/src}/OutputPanel.res | 0 .../playground/src}/Playground.res | 88 ++++++++++++------- .../playground/src}/Playground.resi | 0 .../playground/src}/PlaygroundLazy.res | 0 .../playground/src/PlaygroundReactRouter.res | 11 +++ .../playground/src}/RenderPanel.res | 0 .../playground/src}/RenderPanel.resi | 0 .../playground/src}/ToggleButton.res | 0 packages/shared/package.json | 26 ++++++ .../shared}/plugins/cm6-reason-mode.js | 0 packages/shared/rescript.json | 15 ++++ .../common => packages/shared/src}/Ansi.res | 0 .../common => packages/shared/src}/Ansi.resi | 0 .../shared/src}/AnsiPre.res | 0 .../shared/src}/AnsiPre.resi | 0 .../shared/src}/Babel.res | 0 .../shared/src}/CodeMirror.res | 2 +- .../shared/src}/CodeMirror.resi | 0 .../shared/src}/HighlightJs.res | 0 .../shared/src}/HighlightJs.resi | 0 .../shared/src}/Icon.res | 0 .../shared/src}/Icon.resi | 0 .../shared/src}/RescriptCompilerApi.res | 0 .../shared/src}/RescriptCompilerApi.resi | 0 .../shared/src}/ScrollLockContext.res | 0 .../common => packages/shared/src}/Semver.res | 0 .../shared/src}/Semver.resi | 0 .../shared/src}/Text.res | 0 .../shared/src}/Text.resi | 0 .../shared/src}/WarningFlagDescription.res | 0 .../shared/src}/WarningFlagDescription.resi | 0 rescript.json | 6 +- yarn.lock | 55 +++++++++--- 46 files changed, 205 insertions(+), 64 deletions(-) rename {apps/docs/src => packages/playground}/ffi/loadScript.js (100%) create mode 100644 packages/playground/package.json create mode 100644 packages/playground/rescript.json rename {apps/docs/src/playground => packages/playground/src}/CompilerManagerHook.res (99%) rename {apps/docs/src/playground => packages/playground/src}/CompilerManagerHook.resi (100%) rename {apps/docs/src/playground => packages/playground/src}/ConsolePanel.res (100%) rename {apps/docs/src/playground => packages/playground/src}/EvalIFrame.res (100%) rename {apps/docs/src/playground => packages/playground/src}/LzString.res (100%) rename {apps/docs/src/playground => packages/playground/src}/OutputPanel.res (100%) rename {apps/docs/src/playground => packages/playground/src}/Playground.res (97%) rename {apps/docs/src/playground => packages/playground/src}/Playground.resi (100%) rename {apps/docs/src/playground => packages/playground/src}/PlaygroundLazy.res (100%) create mode 100644 packages/playground/src/PlaygroundReactRouter.res rename {apps/docs/src/playground => packages/playground/src}/RenderPanel.res (100%) rename {apps/docs/src/playground => packages/playground/src}/RenderPanel.resi (100%) rename {apps/docs/src/components => packages/playground/src}/ToggleButton.res (100%) create mode 100644 packages/shared/package.json rename {apps/docs => packages/shared}/plugins/cm6-reason-mode.js (100%) create mode 100644 packages/shared/rescript.json rename {apps/docs/src/common => packages/shared/src}/Ansi.res (100%) rename {apps/docs/src/common => packages/shared/src}/Ansi.resi (100%) rename {apps/docs/src/components => packages/shared/src}/AnsiPre.res (100%) rename {apps/docs/src/components => packages/shared/src}/AnsiPre.resi (100%) rename {apps/docs/src/bindings => packages/shared/src}/Babel.res (100%) rename {apps/docs/src/components => packages/shared/src}/CodeMirror.res (99%) rename {apps/docs/src/components => packages/shared/src}/CodeMirror.resi (100%) rename {apps/docs/src/common => packages/shared/src}/HighlightJs.res (100%) rename {apps/docs/src/common => packages/shared/src}/HighlightJs.resi (100%) rename {apps/docs/src/components => packages/shared/src}/Icon.res (100%) rename {apps/docs/src/components => packages/shared/src}/Icon.resi (100%) rename {apps/docs/src/bindings => packages/shared/src}/RescriptCompilerApi.res (100%) rename {apps/docs/src/bindings => packages/shared/src}/RescriptCompilerApi.resi (100%) rename {apps/docs/src/common => packages/shared/src}/ScrollLockContext.res (100%) rename {apps/docs/src/common => packages/shared/src}/Semver.res (100%) rename {apps/docs/src/common => packages/shared/src}/Semver.resi (100%) rename {apps/docs/src/components => packages/shared/src}/Text.res (100%) rename {apps/docs/src/components => packages/shared/src}/Text.resi (100%) rename {apps/docs/src/common => packages/shared/src}/WarningFlagDescription.res (100%) rename {apps/docs/src/common => packages/shared/src}/WarningFlagDescription.resi (100%) diff --git a/.github/workflows/deploy-fork-preview.yml b/.github/workflows/deploy-fork-preview.yml index f2692a7b7..52a8d1372 100644 --- a/.github/workflows/deploy-fork-preview.yml +++ b/.github/workflows/deploy-fork-preview.yml @@ -81,7 +81,7 @@ jobs: workingDirectory: apps/docs command: pages deploy out --project-name=rescript-lang-org gitHubToken: ${{ secrets.GITHUB_TOKEN }} - wranglerVersion: 4.61.1 + wranglerVersion: 4.85.0 continue-on-error: true env: FORCE_COLOR: 0 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7b4be86a7..685f57808 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -64,7 +64,7 @@ jobs: workingDirectory: apps/docs command: pages deploy out --project-name=rescript-lang-org --branch=${{ env.SAFE_BRANCH }} gitHubToken: ${{ secrets.GITHUB_TOKEN }} - wranglerVersion: 4.63.0 + wranglerVersion: 4.85.0 env: FORCE_COLOR: 0 - name: Comment PR with deployment link diff --git a/.gitignore b/.gitignore index f9b1e0f1e..5bbdf6eb1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,11 +25,14 @@ apps/docs/temp-jsx-preserve .merlin lib/ apps/docs/lib/ +packages/*/lib/ .vercel apps/docs/src/**/*.mjs apps/docs/src/**/*.jsx +packages/*/src/**/*.mjs +packages/*/src/**/*.jsx apps/docs/scripts/gendocs.mjs apps/docs/scripts/generate_*.mjs apps/docs/scripts/gendocs.jsx diff --git a/apps/docs/package.json b/apps/docs/package.json index 9a06a554a..dbe1267c3 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -33,33 +33,21 @@ "vitest:update": "vitest --run --browser.headless --update" }, "dependencies": { - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.2", - "@babel/traverse": "^7.29.0", "@cloudflare/pages-plugin-vercel-og": "^0.1.2", - "@codemirror/commands": "^6.10.3", - "@codemirror/lang-javascript": "^6.2.5", - "@codemirror/language": "^6.12.3", - "@codemirror/lint": "^6.9.5", - "@codemirror/search": "^6.6.0", - "@codemirror/state": "^6.6.0", - "@codemirror/view": "^6.41.0", "@docsearch/react": "^4.6.2", "@headlessui/react": "^2.2.9", - "@lezer/highlight": "^1.2.3", "@mdx-js/mdx": "^3.1.1", "@node-cli/static-server": "^3.1.10", "@react-router/node": "^7.14.0", - "@replit/codemirror-vim": "^6.3.0", + "@rescript-lang/playground": "workspace:*", + "@rescript-lang/shared": "workspace:*", "@rescript/react": "^0.14.2", "@rescript/webapi": "0.1.0-experimental-29db5f4", - "@tsnobip/rescript-lezer": "^0.8.0", "docson": "^2.1.0", "fuse.js": "^6.6.2", "highlight.js": "^11.11.1", "highlightjs-rescript": "^0.2.2", "isbot": "^5.1.37", - "lz-string": "^1.5.0", "mdast-util-from-markdown": "^2.0.3", "mdast-util-to-string": "^4.0.0", "mdast-util-toc": "^7.1.0", diff --git a/apps/docs/rescript.json b/apps/docs/rescript.json index 9b0acd7f9..927e59552 100644 --- a/apps/docs/rescript.json +++ b/apps/docs/rescript.json @@ -1,7 +1,12 @@ { "name": "@rescript-lang/docs", "namespace": false, - "dependencies": ["@rescript/react", "@rescript/webapi"], + "dependencies": [ + "@rescript-lang/shared", + "@rescript-lang/playground", + "@rescript/react", + "@rescript/webapi" + ], "compiler-flags": ["-open WebAPI.Global"], "sources": [ { diff --git a/apps/docs/src/ffi/loadScript.js b/packages/playground/ffi/loadScript.js similarity index 100% rename from apps/docs/src/ffi/loadScript.js rename to packages/playground/ffi/loadScript.js diff --git a/packages/playground/package.json b/packages/playground/package.json new file mode 100644 index 000000000..23bb0d831 --- /dev/null +++ b/packages/playground/package.json @@ -0,0 +1,15 @@ +{ + "name": "@rescript-lang/playground", + "version": "1.0.0", + "private": true, + "type": "module", + "dependencies": { + "@rescript-lang/shared": "workspace:*", + "@rescript/react": "^0.14.2", + "@rescript/webapi": "0.1.0-experimental-29db5f4", + "lz-string": "^1.5.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-router": "^7.14.0" + } +} diff --git a/packages/playground/rescript.json b/packages/playground/rescript.json new file mode 100644 index 000000000..997cde23e --- /dev/null +++ b/packages/playground/rescript.json @@ -0,0 +1,19 @@ +{ + "name": "@rescript-lang/playground", + "namespace": false, + "dependencies": [ + "@rescript-lang/shared", + "@rescript/react", + "@rescript/webapi" + ], + "compiler-flags": ["-open WebAPI.Global"], + "sources": [ + { + "dir": "src", + "subdirs": true + } + ], + "warnings": { + "error": "+8" + } +} diff --git a/apps/docs/src/playground/CompilerManagerHook.res b/packages/playground/src/CompilerManagerHook.res similarity index 99% rename from apps/docs/src/playground/CompilerManagerHook.res rename to packages/playground/src/CompilerManagerHook.res index 363a5bd7d..8dd18c2a1 100644 --- a/apps/docs/src/playground/CompilerManagerHook.res +++ b/packages/playground/src/CompilerManagerHook.res @@ -251,7 +251,7 @@ let useCompilerManager = ( ~versions: array, ) => { let (state, setState) = React.useState(_ => Init) - let {pathname} = ReactRouter.useLocation() + let {pathname} = PlaygroundReactRouter.useLocation() // Dispatch method for the public interface let dispatch = React.useCallback((action: action): unit => { diff --git a/apps/docs/src/playground/CompilerManagerHook.resi b/packages/playground/src/CompilerManagerHook.resi similarity index 100% rename from apps/docs/src/playground/CompilerManagerHook.resi rename to packages/playground/src/CompilerManagerHook.resi diff --git a/apps/docs/src/playground/ConsolePanel.res b/packages/playground/src/ConsolePanel.res similarity index 100% rename from apps/docs/src/playground/ConsolePanel.res rename to packages/playground/src/ConsolePanel.res diff --git a/apps/docs/src/playground/EvalIFrame.res b/packages/playground/src/EvalIFrame.res similarity index 100% rename from apps/docs/src/playground/EvalIFrame.res rename to packages/playground/src/EvalIFrame.res diff --git a/apps/docs/src/playground/LzString.res b/packages/playground/src/LzString.res similarity index 100% rename from apps/docs/src/playground/LzString.res rename to packages/playground/src/LzString.res diff --git a/apps/docs/src/playground/OutputPanel.res b/packages/playground/src/OutputPanel.res similarity index 100% rename from apps/docs/src/playground/OutputPanel.res rename to packages/playground/src/OutputPanel.res diff --git a/apps/docs/src/playground/Playground.res b/packages/playground/src/Playground.res similarity index 97% rename from apps/docs/src/playground/Playground.res rename to packages/playground/src/Playground.res index a45a22b86..07f0e565f 100644 --- a/apps/docs/src/playground/Playground.res +++ b/packages/playground/src/Playground.res @@ -37,7 +37,42 @@ module ExperimentalFeatures = { let breakingPoint = 1024 let playgroundThemeStorageKey = "playgroundTheme" +let playgroundVersionStorageKey = "playground_version" let newLightModeToastSeenStorageKey = "playgroundLightModeToastSeen" +let dropdownLabelNext = "--- Next ---" +let dropdownLabelReleased = "--- Released ---" + +let getLocalStorageItem = key => { + try { + WebAPI.Storage.getItem(window.localStorage, key)->Null.toOption + } catch { + | JsExn(_) => None + } +} + +let setLocalStorageItem = (~key, ~value) => { + try { + WebAPI.Storage.setItem(window.localStorage, ~key, ~value) + } catch { + | JsExn(_) => () + } +} + +let getSessionStorageItem = key => { + try { + WebAPI.Storage.getItem(window.sessionStorage, key)->Null.toOption + } catch { + | JsExn(_) => None + } +} + +let setSessionStorageItem = (~key, ~value) => { + try { + WebAPI.Storage.setItem(window.sessionStorage, ~key, ~value) + } catch { + | JsExn(_) => () + } +} let isDarkTheme = (theme: CodeMirror.Theme.t): bool => switch theme { @@ -126,6 +161,12 @@ module ToggleSelection = { } } +module SelectSectionHeader = { + @react.component + let make = (~value) => + +} + module ResultPane = { module PreWrap = { @react.component @@ -317,9 +358,13 @@ module ResultPane = { {React.string( "The compiler bundle API returned a result that couldn't be interpreted. Please open an issue on our ", )} - + {React.string("issue tracker")} - + {React.string(".")}
@@ -960,7 +1005,7 @@ module Settings = { switch id->Semver.parse { | Some(v) => onCompilerSelect(v) - WebAPI.Storage.setItem(localStorage, ~key=(Url.Playground :> string), ~value=id) + setLocalStorageItem(~key=playgroundVersionStorageKey, ~value=id) | None => () } }} @@ -1009,7 +1054,7 @@ module Settings = { }->Float.fromInt }) <> - + {versionByOrder ->Array.map(version => { let version = Semver.toString(version) @@ -1018,7 +1063,7 @@ module Settings = { }) ->React.array} - + }} {switch stableVersions { @@ -1599,7 +1644,7 @@ let initialReContent = `Js.log("Hello Reason 3.6!");` @react.component let make = (~bundleBaseUrl: string, ~versions: array) => { - let (searchParams, _) = ReactRouter.useSearchParams() + let (searchParams, _) = PlaygroundReactRouter.useSearchParams() let containerRef = React.useRef(Nullable.null) let editorRef: React.ref> = React.useRef(None) let (_, setScrollLock) = ScrollLockContext.useScrollLock() @@ -1647,7 +1692,7 @@ let make = (~bundleBaseUrl: string, ~versions: array) => { ) { | Nullable.Value(version) => version->Semver.parse | _ => - switch Url.getVersionFromStorage(Playground) { + switch getLocalStorageItem(playgroundVersionStorageKey) { | Some(v) => v->Semver.parse | None => lastStableVersion } @@ -1711,8 +1756,7 @@ let make = (~bundleBaseUrl: string, ~versions: array) => { let (keyMap, setKeyMap) = React.useState(() => CodeMirror.KeyMap.Default) let (theme, setTheme) = React.useState(() => - WebAPI.Storage.getItem(window.localStorage, playgroundThemeStorageKey) - ->Null.toOption + getLocalStorageItem(playgroundThemeStorageKey) ->Option.map(CodeMirror.Theme.fromString) ->Option.getOr(CodeMirror.Theme.Dark) ) @@ -1722,8 +1766,7 @@ let make = (~bundleBaseUrl: string, ~versions: array) => { React.useEffect(() => { setKeyMap(_ => - WebAPI.Storage.getItem(window.localStorage, "vimMode") - ->Null.toOption + getLocalStorageItem("vimMode") ->Option.map(CodeMirror.KeyMap.fromString) ->Option.getOr(CodeMirror.KeyMap.Default) ) @@ -1731,10 +1774,7 @@ let make = (~bundleBaseUrl: string, ~versions: array) => { }, []) React.useEffect(() => { - let hasSeenToast = - WebAPI.Storage.getItem(window.sessionStorage, newLightModeToastSeenStorageKey) - ->Null.toOption - ->Option.isSome + let hasSeenToast = getSessionStorageItem(newLightModeToastSeenStorageKey)->Option.isSome if hasSeenToast { None @@ -1743,11 +1783,7 @@ let make = (~bundleBaseUrl: string, ~versions: array) => { let hideToast = () => { setShowNewLightModeToast(_ => false) - WebAPI.Storage.setItem( - window.sessionStorage, - ~key=newLightModeToastSeenStorageKey, - ~value="true", - ) + setSessionStorageItem(~key=newLightModeToastSeenStorageKey, ~value="true") } let timer = setTimeout(~handler=hideToast, ~timeout=10000) @@ -1796,21 +1832,13 @@ let make = (~bundleBaseUrl: string, ~versions: array) => { }, []) React.useEffect(() => { - WebAPI.Storage.setItem( - window.localStorage, - ~key="vimMode", - ~value=CodeMirror.KeyMap.toString(keyMap), - ) + setLocalStorageItem(~key="vimMode", ~value=CodeMirror.KeyMap.toString(keyMap)) editorRef.current->Option.forEach(CodeMirror.editorSetKeyMap(_, keyMap)) None }, [keyMap]) React.useEffect(() => { - WebAPI.Storage.setItem( - window.localStorage, - ~key=playgroundThemeStorageKey, - ~value=theme->CodeMirror.Theme.toString, - ) + setLocalStorageItem(~key=playgroundThemeStorageKey, ~value=theme->CodeMirror.Theme.toString) editorRef.current->Option.forEach(CodeMirror.editorSetTheme(_, theme)) None }, [theme]) diff --git a/apps/docs/src/playground/Playground.resi b/packages/playground/src/Playground.resi similarity index 100% rename from apps/docs/src/playground/Playground.resi rename to packages/playground/src/Playground.resi diff --git a/apps/docs/src/playground/PlaygroundLazy.res b/packages/playground/src/PlaygroundLazy.res similarity index 100% rename from apps/docs/src/playground/PlaygroundLazy.res rename to packages/playground/src/PlaygroundLazy.res diff --git a/packages/playground/src/PlaygroundReactRouter.res b/packages/playground/src/PlaygroundReactRouter.res new file mode 100644 index 000000000..0b8fce77d --- /dev/null +++ b/packages/playground/src/PlaygroundReactRouter.res @@ -0,0 +1,11 @@ +type location = { + pathname: string, + search?: string, + hash?: string, +} + +@module("react-router") +external useLocation: unit => location = "useLocation" + +@module("react-router") +external useSearchParams: unit => (WebAPI.URLAPI.urlSearchParams, {..} => unit) = "useSearchParams" diff --git a/apps/docs/src/playground/RenderPanel.res b/packages/playground/src/RenderPanel.res similarity index 100% rename from apps/docs/src/playground/RenderPanel.res rename to packages/playground/src/RenderPanel.res diff --git a/apps/docs/src/playground/RenderPanel.resi b/packages/playground/src/RenderPanel.resi similarity index 100% rename from apps/docs/src/playground/RenderPanel.resi rename to packages/playground/src/RenderPanel.resi diff --git a/apps/docs/src/components/ToggleButton.res b/packages/playground/src/ToggleButton.res similarity index 100% rename from apps/docs/src/components/ToggleButton.res rename to packages/playground/src/ToggleButton.res diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 000000000..1a7585dce --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,26 @@ +{ + "name": "@rescript-lang/shared", + "version": "1.0.0", + "private": true, + "type": "module", + "dependencies": { + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.2", + "@babel/traverse": "^7.29.0", + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/language": "^6.12.3", + "@codemirror/lint": "^6.9.5", + "@codemirror/search": "^6.6.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.41.0", + "@lezer/highlight": "^1.2.3", + "@replit/codemirror-vim": "^6.3.0", + "@rescript/react": "^0.14.2", + "@rescript/webapi": "0.1.0-experimental-29db5f4", + "@tsnobip/rescript-lezer": "^0.8.0", + "highlight.js": "^11.11.1", + "react": "^19.2.4", + "react-dom": "^19.2.4" + } +} diff --git a/apps/docs/plugins/cm6-reason-mode.js b/packages/shared/plugins/cm6-reason-mode.js similarity index 100% rename from apps/docs/plugins/cm6-reason-mode.js rename to packages/shared/plugins/cm6-reason-mode.js diff --git a/packages/shared/rescript.json b/packages/shared/rescript.json new file mode 100644 index 000000000..40328e643 --- /dev/null +++ b/packages/shared/rescript.json @@ -0,0 +1,15 @@ +{ + "name": "@rescript-lang/shared", + "namespace": false, + "dependencies": ["@rescript/react", "@rescript/webapi"], + "compiler-flags": ["-open WebAPI.Global"], + "sources": [ + { + "dir": "src", + "subdirs": true + } + ], + "warnings": { + "error": "+8" + } +} diff --git a/apps/docs/src/common/Ansi.res b/packages/shared/src/Ansi.res similarity index 100% rename from apps/docs/src/common/Ansi.res rename to packages/shared/src/Ansi.res diff --git a/apps/docs/src/common/Ansi.resi b/packages/shared/src/Ansi.resi similarity index 100% rename from apps/docs/src/common/Ansi.resi rename to packages/shared/src/Ansi.resi diff --git a/apps/docs/src/components/AnsiPre.res b/packages/shared/src/AnsiPre.res similarity index 100% rename from apps/docs/src/components/AnsiPre.res rename to packages/shared/src/AnsiPre.res diff --git a/apps/docs/src/components/AnsiPre.resi b/packages/shared/src/AnsiPre.resi similarity index 100% rename from apps/docs/src/components/AnsiPre.resi rename to packages/shared/src/AnsiPre.resi diff --git a/apps/docs/src/bindings/Babel.res b/packages/shared/src/Babel.res similarity index 100% rename from apps/docs/src/bindings/Babel.res rename to packages/shared/src/Babel.res diff --git a/apps/docs/src/components/CodeMirror.res b/packages/shared/src/CodeMirror.res similarity index 99% rename from apps/docs/src/components/CodeMirror.res rename to packages/shared/src/CodeMirror.res index 596e4be1a..15fb80401 100644 --- a/apps/docs/src/components/CodeMirror.res +++ b/packages/shared/src/CodeMirror.res @@ -572,7 +572,7 @@ module CM6 = { } module CustomLanguages = { - @module("../../plugins/cm6-reason-mode.js") @val + @module("../plugins/cm6-reason-mode.js") @val external reasonLanguage: extension = "reasonLanguage" } } diff --git a/apps/docs/src/components/CodeMirror.resi b/packages/shared/src/CodeMirror.resi similarity index 100% rename from apps/docs/src/components/CodeMirror.resi rename to packages/shared/src/CodeMirror.resi diff --git a/apps/docs/src/common/HighlightJs.res b/packages/shared/src/HighlightJs.res similarity index 100% rename from apps/docs/src/common/HighlightJs.res rename to packages/shared/src/HighlightJs.res diff --git a/apps/docs/src/common/HighlightJs.resi b/packages/shared/src/HighlightJs.resi similarity index 100% rename from apps/docs/src/common/HighlightJs.resi rename to packages/shared/src/HighlightJs.resi diff --git a/apps/docs/src/components/Icon.res b/packages/shared/src/Icon.res similarity index 100% rename from apps/docs/src/components/Icon.res rename to packages/shared/src/Icon.res diff --git a/apps/docs/src/components/Icon.resi b/packages/shared/src/Icon.resi similarity index 100% rename from apps/docs/src/components/Icon.resi rename to packages/shared/src/Icon.resi diff --git a/apps/docs/src/bindings/RescriptCompilerApi.res b/packages/shared/src/RescriptCompilerApi.res similarity index 100% rename from apps/docs/src/bindings/RescriptCompilerApi.res rename to packages/shared/src/RescriptCompilerApi.res diff --git a/apps/docs/src/bindings/RescriptCompilerApi.resi b/packages/shared/src/RescriptCompilerApi.resi similarity index 100% rename from apps/docs/src/bindings/RescriptCompilerApi.resi rename to packages/shared/src/RescriptCompilerApi.resi diff --git a/apps/docs/src/common/ScrollLockContext.res b/packages/shared/src/ScrollLockContext.res similarity index 100% rename from apps/docs/src/common/ScrollLockContext.res rename to packages/shared/src/ScrollLockContext.res diff --git a/apps/docs/src/common/Semver.res b/packages/shared/src/Semver.res similarity index 100% rename from apps/docs/src/common/Semver.res rename to packages/shared/src/Semver.res diff --git a/apps/docs/src/common/Semver.resi b/packages/shared/src/Semver.resi similarity index 100% rename from apps/docs/src/common/Semver.resi rename to packages/shared/src/Semver.resi diff --git a/apps/docs/src/components/Text.res b/packages/shared/src/Text.res similarity index 100% rename from apps/docs/src/components/Text.res rename to packages/shared/src/Text.res diff --git a/apps/docs/src/components/Text.resi b/packages/shared/src/Text.resi similarity index 100% rename from apps/docs/src/components/Text.resi rename to packages/shared/src/Text.resi diff --git a/apps/docs/src/common/WarningFlagDescription.res b/packages/shared/src/WarningFlagDescription.res similarity index 100% rename from apps/docs/src/common/WarningFlagDescription.res rename to packages/shared/src/WarningFlagDescription.res diff --git a/apps/docs/src/common/WarningFlagDescription.resi b/packages/shared/src/WarningFlagDescription.resi similarity index 100% rename from apps/docs/src/common/WarningFlagDescription.resi rename to packages/shared/src/WarningFlagDescription.resi diff --git a/rescript.json b/rescript.json index 9babbd1df..095144ecb 100644 --- a/rescript.json +++ b/rescript.json @@ -1,6 +1,10 @@ { "name": "rescript-lang.org-monorepo", - "dependencies": ["@rescript-lang/docs"], + "dependencies": [ + "@rescript-lang/shared", + "@rescript-lang/playground", + "@rescript-lang/docs" + ], "sources": [], "jsx": { "preserve": true, diff --git a/yarn.lock b/yarn.lock index c2e215e47..b77c3ab05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2234,29 +2234,18 @@ __metadata: version: 0.0.0-use.local resolution: "@rescript-lang/docs@workspace:apps/docs" dependencies: - "@babel/generator": "npm:^7.29.1" - "@babel/parser": "npm:^7.29.2" - "@babel/traverse": "npm:^7.29.0" "@cloudflare/pages-plugin-vercel-og": "npm:^0.1.2" - "@codemirror/commands": "npm:^6.10.3" - "@codemirror/lang-javascript": "npm:^6.2.5" - "@codemirror/language": "npm:^6.12.3" - "@codemirror/lint": "npm:^6.9.5" - "@codemirror/search": "npm:^6.6.0" - "@codemirror/state": "npm:^6.6.0" - "@codemirror/view": "npm:^6.41.0" "@docsearch/react": "npm:^4.6.2" "@headlessui/react": "npm:^2.2.9" - "@lezer/highlight": "npm:^1.2.3" "@mdx-js/mdx": "npm:^3.1.1" "@node-cli/static-server": "npm:^3.1.10" "@react-router/dev": "npm:^7.14.0" "@react-router/node": "npm:^7.14.0" - "@replit/codemirror-vim": "npm:^6.3.0" + "@rescript-lang/playground": "workspace:*" + "@rescript-lang/shared": "workspace:*" "@rescript/react": "npm:^0.14.2" "@rescript/webapi": "npm:0.1.0-experimental-29db5f4" "@tailwindcss/vite": "npm:^4.2.2" - "@tsnobip/rescript-lezer": "npm:^0.8.0" "@types/react": "npm:^19.2.14" "@vitejs/plugin-react": "npm:^6.0.1" "@vitest/browser-playwright": "npm:^4.1.2" @@ -2272,7 +2261,6 @@ __metadata: jsdom: "npm:^26.1.0" lefthook: "npm:^2.1.4" lightningcss: "npm:^1.32.0" - lz-string: "npm:^1.5.0" mdast-util-from-markdown: "npm:^2.0.3" mdast-util-to-string: "npm:^4.0.0" mdast-util-toc: "npm:^7.1.0" @@ -2309,6 +2297,45 @@ __metadata: languageName: unknown linkType: soft +"@rescript-lang/playground@workspace:*, @rescript-lang/playground@workspace:packages/playground": + version: 0.0.0-use.local + resolution: "@rescript-lang/playground@workspace:packages/playground" + dependencies: + "@rescript-lang/shared": "workspace:*" + "@rescript/react": "npm:^0.14.2" + "@rescript/webapi": "npm:0.1.0-experimental-29db5f4" + lz-string: "npm:^1.5.0" + react: "npm:^19.2.4" + react-dom: "npm:^19.2.4" + react-router: "npm:^7.14.0" + languageName: unknown + linkType: soft + +"@rescript-lang/shared@workspace:*, @rescript-lang/shared@workspace:packages/shared": + version: 0.0.0-use.local + resolution: "@rescript-lang/shared@workspace:packages/shared" + dependencies: + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.2" + "@babel/traverse": "npm:^7.29.0" + "@codemirror/commands": "npm:^6.10.3" + "@codemirror/lang-javascript": "npm:^6.2.5" + "@codemirror/language": "npm:^6.12.3" + "@codemirror/lint": "npm:^6.9.5" + "@codemirror/search": "npm:^6.6.0" + "@codemirror/state": "npm:^6.6.0" + "@codemirror/view": "npm:^6.41.0" + "@lezer/highlight": "npm:^1.2.3" + "@replit/codemirror-vim": "npm:^6.3.0" + "@rescript/react": "npm:^0.14.2" + "@rescript/webapi": "npm:0.1.0-experimental-29db5f4" + "@tsnobip/rescript-lezer": "npm:^0.8.0" + highlight.js: "npm:^11.11.1" + react: "npm:^19.2.4" + react-dom: "npm:^19.2.4" + languageName: unknown + linkType: soft + "@rescript/darwin-arm64@npm:12.2.0": version: 12.2.0 resolution: "@rescript/darwin-arm64@npm:12.2.0" From a7fee72cc9e8d938681b053015764c9dc8723655 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 4 May 2026 10:49:52 -0400 Subject: [PATCH 17/32] feat: add guide MVP --- .gitignore | 14 + apps/guide/__tests__/GuideHome_.test.res | 307 +++++++++++++++++ apps/guide/app/GuideCompilerBridge.res | 150 +++++++++ apps/guide/app/GuideCompilerData.res | 43 +++ apps/guide/app/GuideCompilerFeedback.res | 155 +++++++++ apps/guide/app/GuideCompilerSettings.res | 49 +++ apps/guide/app/GuideHome.res | 241 ++++++++++++++ apps/guide/app/GuideHomeRoute.res | 8 + apps/guide/app/GuideHomeRoute.resi | 4 + apps/guide/app/GuideLayout.res | 162 +++++++++ apps/guide/app/GuideReactRouter.res | 32 ++ apps/guide/app/GuideRoot.res | 21 ++ apps/guide/app/GuideRoot.resi | 2 + apps/guide/app/GuideRuntimeSource.res | 73 +++++ apps/guide/app/GuideRuntimeTransform.res | 165 ++++++++++ apps/guide/app/root.mjs | 1 + apps/guide/app/routes.mjs | 3 + apps/guide/package.json | 45 +++ apps/guide/react-router.config.mjs | 9 + apps/guide/rescript.json | 35 ++ apps/guide/src/bindings/GuideVitest.res | 50 +++ apps/guide/styles/main.css | 309 ++++++++++++++++++ apps/guide/vite.config.mjs | 62 ++++ apps/guide/vitest.config.mjs | 59 ++++ apps/guide/vitest.setup.mjs | 1 + package.json | 5 +- .../playground/src/CompilerManagerHook.res | 10 +- .../playground/src/CompilerManagerHook.resi | 1 + rescript.json | 1 + yarn.lock | 37 +++ 30 files changed, 2051 insertions(+), 3 deletions(-) create mode 100644 apps/guide/__tests__/GuideHome_.test.res create mode 100644 apps/guide/app/GuideCompilerBridge.res create mode 100644 apps/guide/app/GuideCompilerData.res create mode 100644 apps/guide/app/GuideCompilerFeedback.res create mode 100644 apps/guide/app/GuideCompilerSettings.res create mode 100644 apps/guide/app/GuideHome.res create mode 100644 apps/guide/app/GuideHomeRoute.res create mode 100644 apps/guide/app/GuideHomeRoute.resi create mode 100644 apps/guide/app/GuideLayout.res create mode 100644 apps/guide/app/GuideReactRouter.res create mode 100644 apps/guide/app/GuideRoot.res create mode 100644 apps/guide/app/GuideRoot.resi create mode 100644 apps/guide/app/GuideRuntimeSource.res create mode 100644 apps/guide/app/GuideRuntimeTransform.res create mode 100644 apps/guide/app/root.mjs create mode 100644 apps/guide/app/routes.mjs create mode 100644 apps/guide/package.json create mode 100644 apps/guide/react-router.config.mjs create mode 100644 apps/guide/rescript.json create mode 100644 apps/guide/src/bindings/GuideVitest.res create mode 100644 apps/guide/styles/main.css create mode 100644 apps/guide/vite.config.mjs create mode 100644 apps/guide/vitest.config.mjs create mode 100644 apps/guide/vitest.setup.mjs diff --git a/.gitignore b/.gitignore index 0677c6b6a..69feda068 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ node_modules/ .next/ out/ apps/docs/out/ +apps/guide/out/ index_data/*.json apps/docs/index_data/*.json @@ -25,12 +26,15 @@ apps/docs/temp-jsx-preserve .merlin lib/ apps/docs/lib/ +apps/guide/lib/ packages/*/lib/ .vercel apps/docs/src/**/*.mjs apps/docs/src/**/*.jsx +apps/guide/src/**/*.mjs +apps/guide/src/**/*.jsx packages/*/src/**/*.mjs packages/*/src/**/*.jsx apps/docs/scripts/gendocs.mjs @@ -51,17 +55,26 @@ dist build apps/docs/dist apps/docs/build +apps/guide/dist +apps/guide/build .react-router apps/docs/.react-router +apps/guide/.react-router mdx-manifest.json apps/docs/mdx-manifest.json apps/docs/app/**/*.mjs apps/docs/app/**/*.jsx +apps/guide/app/**/*.mjs +apps/guide/app/**/*.jsx +!apps/guide/app/root.mjs +!apps/guide/app/routes.mjs apps/docs/functions/**/*.mjs apps/docs/functions/**/*.jsx apps/docs/__tests__/**/*.mjs apps/docs/__tests__/**/*.jsx +apps/guide/__tests__/**/*.mjs +apps/guide/__tests__/**/*.jsx apps/docs/e2e/**/*.mjs apps/docs/e2e/**/*.jsx !_shims.mjs @@ -94,3 +107,4 @@ apps/docs/.env.local !apps/docs/__tests__/__screenshots__/**/* .vitest-attachments apps/docs/.vitest-attachments +apps/guide/.vitest-attachments diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res new file mode 100644 index 000000000..11229c98e --- /dev/null +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -0,0 +1,307 @@ +open GuideVitest + +test("selects the latest stable ReScript compiler with ESM output", async () => { + let version = + GuideCompilerSettings.latestStableVersion([ + "v12.1.0-alpha.1", + "v11.1.4", + "v12.0.0", + "v12.2.0-beta.1", + "v12.1.3", + ]) + ->Option.map(Semver.toString) + ->Option.getOr("") + + expect(version)->toBe("v12.1.3") + expect(GuideCompilerSettings.moduleSystem)->toBe("esmodule") + expect(GuideCompilerSettings.warnFlags->String.includes("-109"))->toBe(true) + expect(GuideCompilerSettings.initialCode)->toBe(`let greeting = "hello, world!"`) +}) + +test("maps compiler diagnostics into editor errors", async () => { + let locMsg: RescriptCompilerApi.LocMsg.t = { + fullMsg: "Full compiler error", + shortMsg: "Expected a string", + row: 2, + column: 7, + endRow: 2, + endColumn: 12, + } + + let editorError = GuideCompilerFeedback.locMsgToEditorError(~kind=#Error, locMsg) + + expect(editorError.row)->toBe(2) + expect(editorError.column)->toBe(7) + expect(editorError.endRow)->toBe(2) + expect(editorError.endColumn)->toBe(12) + expect(editorError.text)->toBe("Expected a string") + + let outputLine = + GuideCompilerFeedback.compileFailToOutputLines(TypecheckErr([locMsg])) + ->Array.get(0) + ->Option.getOr("") + expect(outputLine)->toBe("[E] Line 2, 7: Expected a string") +}) + +test("maps compiler type hints into hover hints", async () => { + let typeHint = RescriptCompilerApi.TypeHint.Binding({ + start: {line: 1, col: 4}, + end: {line: 1, col: 12}, + hint: "string", + }) + + let hoverHint = + GuideCompilerFeedback.typeHintsToHoverHints([typeHint])->Array.get(0)->Option.getOrThrow + + expect(hoverHint.start.line)->toBe(1) + expect(hoverHint.start.col)->toBe(4) + expect(hoverHint.end.line)->toBe(1) + expect(hoverHint.end.col)->toBe(12) + expect(hoverHint.hint)->toBe("string") +}) + +test("does not include compiler type hints in successful output", async () => { + let typeHint = RescriptCompilerApi.TypeHint.Binding({ + start: {line: 1, col: 4}, + end: {line: 1, col: 12}, + hint: "string", + }) + + let output = GuideCompilerFeedback.compilationResultToOutput( + ~compilerVersion="12.2.0", + Success({ + jsCode: "let value = 52;", + warnings: [], + typeHints: [typeHint], + time: 1.0, + }), + ) + + expect(output.typeHints->Array.length)->toBe(0) +}) + +test("renders output diagnostics as error lines", async () => { + let output = GuideCompilerFeedback.Output.make( + ~status="Compiler error", + ~diagnostics=["[E] Line 2, 7: Expected a string"], + ) + + let screen = await render() + let diagnostic = await screen->getByText("[E] Line 2, 7: Expected a string") + + await diagnostic->element->toHaveClass("guide-output-line-error") +}) + +test("rewrites the compiled final expression into a console log", async () => { + let result = GuideRuntimeTransform.transform(`let value = 52; +value; + +export { + value, +};`) + + switch result { + | Some({code, imports}) => + expect(code->String.includes("console.log(value)"))->toBe(true) + expect(code->String.includes("export"))->toBe(false) + expect(imports->Dict.keysToArray->Array.length)->toBe(0) + | None => expect("runtime transform")->toBe("a transformed program") + } +}) + +test("rewrites the guide runtime result binding into a console log", async () => { + let result = GuideRuntimeTransform.transform( + ~resultBindingName=GuideRuntimeSource.resultBindingName, + `let __rescriptGuideOutput = 52; + +export { + __rescriptGuideOutput, +};`, + ) + + switch result { + | Some({code}) => expect(code->String.includes("console.log(__rescriptGuideOutput)"))->toBe(true) + | None => expect("runtime transform")->toBe("a transformed program") + } +}) + +test("rewrites the last compiled binding into a console log", async () => { + let result = GuideRuntimeTransform.transform(`let greeting = "hello, world!"; + +export { + greeting, +};`) + + switch result { + | Some({code}) => expect(code->String.includes("console.log(greeting)"))->toBe(true) + | None => expect("runtime transform")->toBe("a transformed program") + } +}) + +test("skips runtime execution when compiled code has no expression or binding", async () => { + let result = GuideRuntimeTransform.transform(`export {};`) + + expect(result->Option.isNone)->toBe(true) +}) + +test("instruments the compiler-reported final expression range", async () => { + let code = `let greeting = "hello, world!" + +greeting` + let typeHints = [ + RescriptCompilerApi.TypeHint.Binding({ + start: {line: 1, col: 0}, + end: {line: 1, col: 30}, + hint: "string", + }), + RescriptCompilerApi.TypeHint.Expression({ + start: {line: 3, col: 0}, + end: {line: 3, col: 8}, + hint: "string", + }), + ] + + switch GuideRuntimeSource.instrument(~code, ~typeHints) { + | Some(instrumentedCode) => + expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (greeting)"))->toBe(true) + | None => expect("source instrumentation")->toBe("instrumented code") + } +}) + +test("prefers the outermost latest expression range", async () => { + let code = `let add = (a, b) => a + b + +add(10, 42)` + let typeHints = [ + RescriptCompilerApi.TypeHint.Expression({ + start: {line: 3, col: 8}, + end: {line: 3, col: 10}, + hint: "int", + }), + RescriptCompilerApi.TypeHint.Expression({ + start: {line: 3, col: 0}, + end: {line: 3, col: 11}, + hint: "int", + }), + RescriptCompilerApi.TypeHint.Binding({ + start: {line: 1, col: 0}, + end: {line: 1, col: 25}, + hint: "(int, int) => int", + }), + ] + + switch GuideRuntimeSource.instrument(~code, ~typeHints) { + | Some(instrumentedCode) => + expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (add(10, 42))"))->toBe( + true, + ) + | None => expect("source instrumentation")->toBe("instrumented code") + } +}) + +test("does not instrument expressions nested inside a binding", async () => { + let code = `let value = 52` + let typeHints = [ + RescriptCompilerApi.TypeHint.Expression({ + start: {line: 1, col: 12}, + end: {line: 1, col: 14}, + hint: "int", + }), + ] + + expect(GuideRuntimeSource.instrument(~code, ~typeHints)->Option.isNone)->toBe(true) +}) + +test("renders runtime logs in the output panel", async () => { + let output = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["52"]}], + ) + + let screen = await render() + + await (await screen->getByText("Result"))->element->toBeVisible + await (await screen->getByText("52"))->element->toBeVisible +}) + +test("does not render redundant output status inside the output panel", async () => { + let output = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["52"]}], + ) + + let screen = await render() + let outputText = screen->container->textContent->Nullable.toOption->Option.getOr("") + + expect(outputText->String.includes("Output"))->toBe(false) +}) + +test("clamps resized pane dimensions", async () => { + expect(GuideLayout.clampInstructionsWidth(~viewportWidth=1200.0, ~pointerX=120.0))->toBe(320.0) + expect(GuideLayout.clampInstructionsWidth(~viewportWidth=1200.0, ~pointerX=900.0))->toBe(720.0) + expect(GuideLayout.clampInstructionsWidth(~viewportWidth=1200.0, ~pointerX=520.0))->toBe(520.0) + + expect(GuideLayout.clampOutputHeight(~viewportHeight=900.0, ~pointerY=820.0))->toBe(160.0) + expect(GuideLayout.clampOutputHeight(~viewportHeight=900.0, ~pointerY=120.0))->toBe(660.0) + expect(GuideLayout.clampOutputHeight(~viewportHeight=900.0, ~pointerY=640.0))->toBe(260.0) +}) + +test("stores resized pane dimensions in local storage", async () => { + GuideLayout.clearPaneSizes() + + GuideLayout.savePaneSizes({ + instructionsWidth: Some(420.0), + outputHeight: 250.0, + }) + + let savedPaneSizes = GuideLayout.loadPaneSizes() + let savedInstructionsWidth = + savedPaneSizes.instructionsWidth->Option.map(width => width->Float.toString)->Option.getOr("") + + expect(savedInstructionsWidth)->toBe("420") + expect(savedPaneSizes.outputHeight)->toBe(250.0) + + GuideLayout.clearPaneSizes() +}) + +test("renders resize handles and toggles dark mode", async () => { + await viewport(1440, 900) + + let screen = await render() + let shell = await screen->getByTestId("guide-mvp") + let columnHandle = await screen->getByTestId("guide-column-resize") + let rowHandle = await screen->getByTestId("guide-row-resize") + let themeToggle = await screen->getByLabelText("Switch to dark mode") + + await columnHandle->element->toBeVisible + await rowHandle->element->toBeVisible + await shell->element->toHaveClass("guide-theme-light") + await themeToggle->click + await shell->element->toHaveClass("guide-theme-dark") +}) + +test("stretches the output surface to the full output panel", async () => { + await viewport(1440, 900) + + let screen = await render() + let output = await screen->getByTestId("guide-output") + + await output->element->toHaveClass("guide-output-frame") +}) + +test("renders the first guide MVP exercise and output", async () => { + await viewport(1440, 900) + + let screen = await render() + + await (await screen->getByText("Learn ReScript Guide"))->element->toBeVisible + await (await screen->getByText("Lorem ipsum dolor sit amet."))->element->toBeVisible + await (await screen->getByText("Next"))->element->toBeVisible + let editor = await screen->getByTestId("guide-code-editor") + await editor->element->toBeVisible + await editor->element->toHaveTextContent("let greeting = \"hello, world!\"") + let output = await screen->getByTestId("guide-output") + await output->element->toBeVisible + + await output->element->toHaveTextContent("hello, world!") +}) diff --git a/apps/guide/app/GuideCompilerBridge.res b/apps/guide/app/GuideCompilerBridge.res new file mode 100644 index 000000000..fdddeccb2 --- /dev/null +++ b/apps/guide/app/GuideCompilerBridge.res @@ -0,0 +1,150 @@ +let capitalizeFirstLetter = string => { + let firstLetter = string->String.charAt(0)->String.toUpperCase + `${firstLetter}${string->String.slice(~start=1)}` +} + +let runtimeImportUrl = (~bundleBaseUrl, ~compilerVersion: Semver.t, path) => { + let filename = path->String.slice(~start=9) + let filename = switch compilerVersion { + | {major: 12, minor: 0, patch: 0, preRelease: Some(Alpha(alpha))} if alpha < 8 => + let filename = if filename->String.startsWith("core__") { + filename->String.slice(~start=6) + } else { + filename + } + filename->capitalizeFirstLetter + | {major} if major < 12 && filename->String.startsWith("core__") => + filename->capitalizeFirstLetter + | _ => filename + } + let compilerVersion = switch compilerVersion { + | {major: 12, minor: 0, patch: 0, preRelease: Some(Alpha(alpha))} if alpha < 9 => { + Semver.major: 12, + minor: 0, + patch: 0, + preRelease: Some(Alpha(9)), + } + | {major, minor} if (major === 11 && minor < 2) || major < 11 => { + major: 11, + minor: 2, + patch: 0, + preRelease: Some(Beta(2)), + } + | version => version + } + + CompilerManagerHook.CdnMeta.getStdlibRuntimeUrl(bundleBaseUrl, compilerVersion, filename) +} + +@react.component +let make = ( + ~bundleBaseUrl, + ~versions: array, + ~code, + ~editorRef: React.ref>, + ~setOutput, +) => { + let compilerVersions = React.useMemo( + () => GuideCompilerSettings.supportedVersions(versions), + [versions], + ) + let initialVersion = React.useMemo( + () => compilerVersions->GuideCompilerSettings.latestStableParsedVersion, + [compilerVersions], + ) + + let (compilerState, compilerDispatch) = CompilerManagerHook.useCompilerManager( + ~bundleBaseUrl, + ~initialVersion?, + ~initialModuleSystem=GuideCompilerSettings.moduleSystem, + ~initialWarnFlags=GuideCompilerSettings.warnFlags, + ~versions=compilerVersions, + ) + + let lastCompiledCode = React.useRef("") + let lastExecutedJsCode = React.useRef("") + + React.useEffect(() => { + switch compilerState { + | Ready({targetLang}) if code !== lastCompiledCode.current => + let timer = setTimeout(~handler=() => { + lastCompiledCode.current = code + compilerDispatch(CompileCode(targetLang, code)) + }, ~timeout=150) + Some(() => clearTimeout(timer)) + | _ => None + } + }, (code, compilerState, compilerDispatch)) + + React.useEffect(() => { + let cb = event => { + let data = event["data"] + let appendLog = (level, content) => + setOutput(output => + output->GuideCompilerFeedback.Output.withRuntimeLog({ + level, + content, + }) + ) + + switch data["type"] { + | #log => appendLog(#log, data["args"]) + | #warn => appendLog(#warn, data["args"]) + | #error => appendLog(#error, data["args"]) + | _ => () + } + } + WebAPI.Window.addEventListener(window, Custom("message"), cb) + Some(() => WebAPI.Window.removeEventListener(window, Custom("message"), cb)) + }, [setOutput]) + + React.useEffect(() => { + let feedback = compilerState->GuideCompilerFeedback.editorFeedbackFromState + editorRef.current->Option.forEach(editor => { + CodeMirror.editorSetErrors(editor, feedback.errors) + CodeMirror.editorSetHoverHints(editor, feedback.hoverHints) + }) + setOutput(_ => compilerState->GuideCompilerFeedback.outputFromState) + None + }, (compilerState, setOutput)) + + React.useEffect(() => { + switch compilerState { + | Ready({selected, result: Comp(Success({jsCode, typeHints}))}) + if jsCode !== lastExecutedJsCode.current => + lastExecutedJsCode.current = jsCode + let runtimeJsCode = switch GuideRuntimeSource.instrument(~code, ~typeHints) { + | Some(runtimeCode) => + switch selected.instance->RescriptCompilerApi.Compiler.resCompile(runtimeCode) { + | Success({jsCode}) => jsCode + | Fail(_) | UnexpectedError(_) | Unknown(_, _) => jsCode + } + | None => jsCode + } + + switch runtimeJsCode->GuideRuntimeTransform.transform( + ~resultBindingName=GuideRuntimeSource.resultBindingName, + ) { + | Some({code: runtimeCode, imports}) => + let imports = + imports->Dict.mapValues(path => + path->runtimeImportUrl(~bundleBaseUrl, ~compilerVersion=selected.id) + ) + let timer = setTimeout( + ~handler=() => EvalIFrame.sendOutput(runtimeCode, imports), + ~timeout=50, + ) + Some(() => clearTimeout(timer)) + | None => None + } + | Compiling(_) => + lastExecutedJsCode.current = "" + None + | _ => None + } + }, (compilerState, bundleBaseUrl)) + +
+ +
+} diff --git a/apps/guide/app/GuideCompilerData.res b/apps/guide/app/GuideCompilerData.res new file mode 100644 index 000000000..d4a32be91 --- /dev/null +++ b/apps/guide/app/GuideCompilerData.res @@ -0,0 +1,43 @@ +type t = { + bundleBaseUrl: string, + versions: array, +} + +module Env = { + @scope(("process", "env")) external nodeEnv: string = "NODE_ENV" + @scope(("process", "env")) + external playgroundBundleEndpoint: option = "PLAYGROUND_BUNDLE_ENDPOINT" +} + +let fetchVersions = async versionsBaseUrl => { + let response = await fetch(versionsBaseUrl ++ "/playground-bundles/versions.json") + let json = await WebAPI.Response.json(response) + json + ->JSON.Decode.array + ->Option.getOrThrow + ->Array.map(json => json->JSON.Decode.string->Option.getOrThrow) +} + +let load = async () => { + let (bundleBaseUrl, versionsBaseUrl) = switch (Env.playgroundBundleEndpoint, Env.nodeEnv) { + | (Some(baseUrl), _) => (baseUrl, baseUrl) + | (None, "development") => + let baseUrl = "https://cdn.rescript-lang.org" + (baseUrl, baseUrl) + | (None, _) => + let baseUrl = "https://cdn.rescript-lang.org" + (baseUrl, baseUrl) + } + + try { + let versions = await fetchVersions(versionsBaseUrl) + Some({ + bundleBaseUrl, + versions, + }) + } catch { + | JsExn(error) => + Console.error2("error while fetching guide compiler versions", error) + None + } +} diff --git a/apps/guide/app/GuideCompilerFeedback.res b/apps/guide/app/GuideCompilerFeedback.res new file mode 100644 index 000000000..215d436ea --- /dev/null +++ b/apps/guide/app/GuideCompilerFeedback.res @@ -0,0 +1,155 @@ +module Api = RescriptCompilerApi + +module Output = { + type level = [ + | #log + | #warn + | #error + ] + type runtimeLog = {level: level, content: array} + + type t = { + status: string, + diagnostics: array, + typeHints: array, + runtimeLogs: array, + } + + let make = (~status, ~diagnostics=[], ~typeHints=[], ~runtimeLogs=[]) => { + status, + diagnostics, + typeHints, + runtimeLogs, + } + + let initial = make(~status="Output", ~runtimeLogs=[{level: #log, content: ["hello, world!"]}]) + + let withRuntimeLog = (output, runtimeLog) => { + ...output, + status: "Output", + runtimeLogs: output.runtimeLogs->Array.concat([runtimeLog]), + } +} + +type editorFeedback = { + errors: array, + hoverHints: array, +} + +let emptyEditorFeedback = {errors: [], hoverHints: []} + +let locMsgToEditorError = (~kind: CodeMirror.Error.kind, locMsg: Api.LocMsg.t) => { + let {Api.LocMsg.row: row, column, endColumn, endRow, shortMsg} = locMsg + { + CodeMirror.Error.row, + column, + endColumn, + endRow, + text: shortMsg, + kind, + } +} + +let warningToEditorError = (warning: Api.Warning.t) => + switch warning { + | Warn({details}) | WarnErr({details}) => locMsgToEditorError(~kind=#Warning, details) + } + +let plainText = text => text->Ansi.parse->Ansi.Printer.plainString + +let typeHintData = (typeHint: Api.TypeHint.t) => + switch typeHint { + | TypeDeclaration(data) | Expression(data) | Binding(data) | CoreType(data) => data + } + +let typeHintsToHoverHints = typeHints => + typeHints->Array.map(typeHint => { + let {Api.TypeHint.start: start, end, hint} = typeHint->typeHintData + { + CodeMirror.HoverHint.start: { + line: start.line, + col: start.col, + }, + end: { + line: end.line, + col: end.col, + }, + hint, + } + }) + +let compileFailToEditorErrors = (fail: Api.CompileFail.t) => + switch fail { + | SyntaxErr(locMsgs) | TypecheckErr(locMsgs) | OtherErr(locMsgs) => + locMsgs->Array.map(locMsg => locMsgToEditorError(~kind=#Error, locMsg)) + | WarningErr(warnings) => warnings->Array.map(warningToEditorError) + | WarningFlagErr({msg}) => [ + { + CodeMirror.Error.row: 1, + column: 0, + endRow: 1, + endColumn: 0, + text: msg, + kind: #Error, + }, + ] + } + +let compileFailToOutputLines = (fail: Api.CompileFail.t) => + switch fail { + | SyntaxErr(locMsgs) | TypecheckErr(locMsgs) | OtherErr(locMsgs) => + locMsgs->Array.map(locMsg => locMsg->Api.LocMsg.toCompactErrorLine(~prefix=#E)->plainText) + | WarningErr(warnings) => + warnings->Array.map(warning => warning->Api.Warning.toCompactErrorLine->plainText) + | WarningFlagErr({msg}) => [msg] + } + +let compilationResultToEditorFeedback = (result: Api.CompilationResult.t) => + switch result { + | Success({warnings, typeHints}) => { + errors: warnings->Array.map(warningToEditorError), + hoverHints: typeHints->typeHintsToHoverHints, + } + | Fail(fail) => { + errors: fail->compileFailToEditorErrors, + hoverHints: [], + } + | UnexpectedError(_) | Unknown(_, _) => emptyEditorFeedback + } + +let compilationResultToOutput = (~compilerVersion, result: Api.CompilationResult.t) => + switch result { + | Success({warnings}) => + Output.make( + ~status=`Compiled with ReScript ${compilerVersion}`, + ~diagnostics=warnings->Array.map(warning => + warning->Api.Warning.toCompactErrorLine->plainText + ), + ) + | Fail(fail) => Output.make(~status="Compiler error", ~diagnostics=fail->compileFailToOutputLines) + | UnexpectedError(message) => Output.make(~status="Compiler error", ~diagnostics=[message]) + | Unknown(message, _) => Output.make(~status="Unknown compiler result", ~diagnostics=[message]) + } + +let editorFeedbackFromState = (state: CompilerManagerHook.state) => + switch state { + | Ready({result: Comp(result)}) | Compiling({state: {result: Comp(result)}}) => + result->compilationResultToEditorFeedback + | _ => emptyEditorFeedback + } + +let outputFromState = (state: CompilerManagerHook.state) => + switch state { + | Init => Output.make(~status="Loading compiler...") + | SetupFailed(message) => Output.make(~status="Compiler setup failed", ~diagnostics=[message]) + | SwitchingCompiler(_, version) => + Output.make(~status=`Loading ReScript ${version->Semver.toString}...`) + | Compiling(_) => Output.make(~status="Compiling...") + | Executing(_) => Output.make(~status="Compiled") + | Ready({selected, result}) => + switch result { + | Nothing => Output.make(~status=`Compiler ready (${selected.compilerVersion})`) + | Comp(result) => result->compilationResultToOutput(~compilerVersion=selected.compilerVersion) + | Conv(_) => Output.make(~status="Compiler ready") + } + } diff --git a/apps/guide/app/GuideCompilerSettings.res b/apps/guide/app/GuideCompilerSettings.res new file mode 100644 index 000000000..e1dd911d3 --- /dev/null +++ b/apps/guide/app/GuideCompilerSettings.res @@ -0,0 +1,49 @@ +let moduleSystem = "esmodule" +let warnFlags = "+a-4-9-20-40-41-42-50-61-102-109" + +let initialCode = `let greeting = "hello, world!"` + +let isSupportedVersion = (version: Semver.t) => + switch version.major { + | 8 | 9 => false + | 10 => version.minor >= 1 + | 11 => version.minor >= 1 && version.preRelease->Option.isNone + | 12 => + switch version.preRelease { + | None => true + | Some(_) => version.minor > 1 + } + | _ => true + } + +let compareIntDescending = (a, b) => { + if a > b { + -1.0 + } else if a < b { + 1.0 + } else { + 0.0 + } +} + +let compareVersionDescending = (a: Semver.t, b: Semver.t) => { + switch compareIntDescending(a.major, b.major) { + | 0.0 => + switch compareIntDescending(a.minor, b.minor) { + | 0.0 => compareIntDescending(a.patch, b.patch) + | result => result + } + | result => result + } +} + +let supportedVersions = versions => + versions + ->Array.filterMap(Semver.parse) + ->Array.filter(isSupportedVersion) + ->Array.toSorted(compareVersionDescending) + +let latestStableParsedVersion = (versions: array) => + versions->Array.find(version => version.preRelease->Option.isNone) + +let latestStableVersion = versions => versions->supportedVersions->latestStableParsedVersion diff --git a/apps/guide/app/GuideHome.res b/apps/guide/app/GuideHome.res new file mode 100644 index 000000000..a21377b69 --- /dev/null +++ b/apps/guide/app/GuideHome.res @@ -0,0 +1,241 @@ +external domElementToWebElement: Dom.element => WebAPI.DOMAPI.element = "%identity" + +module OutputPanel = { + @react.component + let make = (~output: GuideCompilerFeedback.Output.t) => { +
+ {switch output.status { + | "Output" => React.null + | status =>
{React.string(status)}
+ }} + {switch output.diagnostics { + | [] => React.null + | diagnostics => +
+
{React.string("Diagnostics")}
+ {diagnostics + ->Array.mapWithIndex((diagnostic, index) => +
Int.toString} className="guide-output-line guide-output-line-error">
+              {React.string(diagnostic)}
+            
+ ) + ->React.array} +
+ }} + {switch output.runtimeLogs { + | [] => React.null + | runtimeLogs => +
+
{React.string("Result")}
+ {runtimeLogs + ->Array.mapWithIndex(({level, content}, index) => +
Int.toString}
+              className={switch level {
+              | #log => "guide-output-line"
+              | #warn => "guide-output-line guide-output-line-warning"
+              | #error => "guide-output-line guide-output-line-error"
+              }}
+            >
+              {React.string(content->Array.join(" "))}
+            
+ ) + ->React.array} +
+ }} +
+ } +} + +type dragTarget = + | NotDragging + | ResizingColumns + | ResizingRows + +@react.component +let make = (~compilerData: option=?) => { + let shellRef: React.ref> = React.useRef(Nullable.null) + let editorContainerRef: React.ref> = React.useRef(Nullable.null) + let editorRef: React.ref> = React.useRef(None) + let (code, setCode) = React.useState(() => GuideCompilerSettings.initialCode) + let (output, setOutput) = React.useState(() => GuideCompilerFeedback.Output.initial) + let (paneSizes, setPaneSizes) = React.useState(() => GuideLayout.defaultPaneSizes) + let (theme, setTheme) = React.useState(() => GuideLayout.Light) + let (themeLoaded, setThemeLoaded) = React.useState(() => false) + let (paneSizesLoaded, setPaneSizesLoaded) = React.useState(() => false) + let dragTarget = React.useRef(NotDragging) + + React.useEffect(() => { + setTheme(_ => GuideLayout.loadTheme()) + setPaneSizes(_ => + GuideLayout.loadPaneSizes()->GuideLayout.clampPaneSizes( + ~viewportWidth=window.innerWidth->Int.toFloat, + ~viewportHeight=window.innerHeight->Int.toFloat, + ) + ) + setThemeLoaded(_ => true) + setPaneSizesLoaded(_ => true) + None + }, []) + + React.useEffect(() => { + if themeLoaded { + theme->GuideLayout.saveTheme + } + editorRef.current->Option.forEach(editor => + CodeMirror.editorSetTheme(editor, theme->GuideLayout.themeToCodeMirror) + ) + None + }, (theme, themeLoaded)) + + React.useEffect(() => { + if paneSizesLoaded { + paneSizes->GuideLayout.savePaneSizes + } + None + }, (paneSizes, paneSizesLoaded)) + + React.useEffect(() => { + switch shellRef.current { + | Value(element) => + WebAPI.Element.setAttribute( + element->domElementToWebElement, + ~qualifiedName="style", + ~value=paneSizes->GuideLayout.paneSizesStyle, + ) + | Null | Undefined => () + } + None + }, [paneSizes]) + + React.useEffect(() => { + let stopDragging = _event => dragTarget.current = NotDragging + + let onMouseMove = event => + switch dragTarget.current { + | ResizingColumns => + let pointerX = event->ReactEvent.Mouse.clientX->Int.toFloat + let viewportWidth = window.innerWidth->Int.toFloat + let instructionsWidth = GuideLayout.clampInstructionsWidth(~viewportWidth, ~pointerX) + setPaneSizes(previous => {...previous, instructionsWidth: Some(instructionsWidth)}) + | ResizingRows => + let pointerY = event->ReactEvent.Mouse.clientY->Int.toFloat + let viewportHeight = window.innerHeight->Int.toFloat + let outputHeight = GuideLayout.clampOutputHeight(~viewportHeight, ~pointerY) + setPaneSizes(previous => {...previous, outputHeight}) + | NotDragging => () + } + + WebAPI.Window.addEventListener(window, Mousemove, onMouseMove) + WebAPI.Window.addEventListener(window, Mouseup, stopDragging) + + Some( + () => { + WebAPI.Window.removeEventListener(window, Mousemove, onMouseMove) + WebAPI.Window.removeEventListener(window, Mouseup, stopDragging) + }, + ) + }, []) + + let startDragging = (target, event) => { + ReactEvent.Mouse.preventDefault(event) + dragTarget.current = target + } + + React.useEffect(() => { + switch editorContainerRef.current { + | Value(parent) => + let config: CodeMirror.editorConfig = { + parent: parent->domElementToWebElement, + initialValue: GuideCompilerSettings.initialCode, + mode: "rescript", + readOnly: false, + lineNumbers: true, + lineWrapping: false, + theme: theme->GuideLayout.themeToCodeMirror, + keyMap: CodeMirror.KeyMap.Default, + onChange: value => setCode(_ => value), + errors: [], + hoverHints: [], + minHeight: "100%", + } + let editor = CodeMirror.createEditor(config) + editorRef.current = Some(editor) + Some(() => CodeMirror.editorDestroy(editor)) + | Null | Undefined => None + } + }, []) + +
GuideLayout.themeClass} + dataTestId="guide-mvp" + ref={ReactDOM.Ref.domRef(shellRef)} + > +
+
+
+

{React.string("Mission 01")}

+ +
+

{React.string("Learn ReScript Guide")}

+

+ {React.string( + "This interactive guide introduces ReScript through small examples, steady practice, and a live output log.", + )} +

+

{React.string("Lorem ipsum dolor sit amet.")}

+

+ {React.string( + "Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + )} +

+
+ +
+
startDragging(ResizingColumns, event)} + role="separator" + /> +
+
+
{React.string("Editor")}
+
+
+
startDragging(ResizingRows, event)} + role="separator" + /> +
+
{React.string("Output")}
+
+ +
+
+
+ {switch compilerData { + | Some({bundleBaseUrl, versions}) => + + | None => React.null + }} +
+} diff --git a/apps/guide/app/GuideHomeRoute.res b/apps/guide/app/GuideHomeRoute.res new file mode 100644 index 000000000..ad4768a72 --- /dev/null +++ b/apps/guide/app/GuideHomeRoute.res @@ -0,0 +1,8 @@ +let loader: GuideReactRouter.Loader.t> = async _ => + await GuideCompilerData.load() + +@react.component +let default = () => { + let compilerData = GuideReactRouter.useLoaderData() + +} diff --git a/apps/guide/app/GuideHomeRoute.resi b/apps/guide/app/GuideHomeRoute.resi new file mode 100644 index 000000000..9ea791fae --- /dev/null +++ b/apps/guide/app/GuideHomeRoute.resi @@ -0,0 +1,4 @@ +let loader: GuideReactRouter.Loader.t> + +@react.component +let default: unit => React.element diff --git a/apps/guide/app/GuideLayout.res b/apps/guide/app/GuideLayout.res new file mode 100644 index 000000000..f8a170dba --- /dev/null +++ b/apps/guide/app/GuideLayout.res @@ -0,0 +1,162 @@ +type theme = + | Light + | Dark + +type paneSizes = { + instructionsWidth: option, + outputHeight: float, +} + +let minInstructionsWidth = 320.0 +let minWorkspaceWidth = 480.0 +let minEditorHeight = 240.0 +let minOutputHeight = 160.0 +let defaultOutputHeight = 220.0 +let themeStorageKey = "guideTheme" +let instructionsWidthStorageKey = "guidePaneInstructionsWidth" +let outputHeightStorageKey = "guidePaneOutputHeight" + +let defaultPaneSizes = { + instructionsWidth: None, + outputHeight: defaultOutputHeight, +} + +let clamp = (~min, ~max, value) => { + if value < min { + min + } else if value > max { + max + } else { + value + } +} + +let clampInstructionsWidth = (~viewportWidth, ~pointerX) => + pointerX->clamp(~min=minInstructionsWidth, ~max=viewportWidth -. minWorkspaceWidth) + +let clampOutputHeight = (~viewportHeight, ~pointerY) => + (viewportHeight -. pointerY)->clamp(~min=minOutputHeight, ~max=viewportHeight -. minEditorHeight) + +let clampPaneSizes = (~viewportWidth, ~viewportHeight, paneSizes) => { + let instructionsWidth = + paneSizes.instructionsWidth->Option.map(width => + clampInstructionsWidth(~viewportWidth, ~pointerX=width) + ) + + { + instructionsWidth, + outputHeight: paneSizes.outputHeight->clamp( + ~min=minOutputHeight, + ~max=viewportHeight -. minEditorHeight, + ), + } +} + +let paneSizesStyle = paneSizes => { + let instructionsWidth = switch paneSizes.instructionsWidth { + | Some(width) => `${width->Float.toString}px` + | None => "50%" + } + + `--guide-instructions-width: ${instructionsWidth}; --guide-output-height: ${paneSizes.outputHeight->Float.toString}px;` +} + +let themeClass = theme => + switch theme { + | Light => "guide-theme-light" + | Dark => "guide-theme-dark" + } + +let toggleTheme = theme => + switch theme { + | Light => Dark + | Dark => Light + } + +let themeToCodeMirror = theme => + switch theme { + | Light => CodeMirror.Theme.Light + | Dark => CodeMirror.Theme.Dark + } + +let themeToString = theme => + switch theme { + | Light => "light" + | Dark => "dark" + } + +let themeFromString = value => + switch value { + | "dark" => Dark + | _ => Light + } + +let themeToggleLabel = theme => + switch theme { + | Light => "Switch to dark mode" + | Dark => "Switch to light mode" + } + +let themeToggleText = theme => + switch theme { + | Light => "Dark" + | Dark => "Light" + } + +let getLocalStorageItem = key => { + try { + WebAPI.Storage.getItem(window.localStorage, key)->Null.toOption + } catch { + | JsExn(_) => None + } +} + +let setLocalStorageItem = (~key, ~value) => { + try { + WebAPI.Storage.setItem(window.localStorage, ~key, ~value) + } catch { + | JsExn(_) => () + } +} + +let removeLocalStorageItem = key => { + try { + WebAPI.Storage.removeItem(window.localStorage, key) + } catch { + | JsExn(_) => () + } +} + +let loadTheme = () => + getLocalStorageItem(themeStorageKey) + ->Option.map(themeFromString) + ->Option.getOr(Light) + +let saveTheme = theme => setLocalStorageItem(~key=themeStorageKey, ~value=theme->themeToString) + +let parseStoredFloat = value => + switch value->Float.fromString { + | Some(value) if value > 0.0 => Some(value) + | _ => None + } + +let getStoredFloat = key => getLocalStorageItem(key)->Option.flatMap(parseStoredFloat) + +let loadPaneSizes = () => { + instructionsWidth: getStoredFloat(instructionsWidthStorageKey), + outputHeight: getStoredFloat(outputHeightStorageKey)->Option.getOr(defaultOutputHeight), +} + +let savePaneSizes = paneSizes => { + switch paneSizes.instructionsWidth { + | Some(width) => + setLocalStorageItem(~key=instructionsWidthStorageKey, ~value=width->Float.toString) + | None => removeLocalStorageItem(instructionsWidthStorageKey) + } + setLocalStorageItem(~key=outputHeightStorageKey, ~value=paneSizes.outputHeight->Float.toString) +} + +let clearPaneSizes = () => { + removeLocalStorageItem(instructionsWidthStorageKey) + removeLocalStorageItem(outputHeightStorageKey) +} diff --git a/apps/guide/app/GuideReactRouter.res b/apps/guide/app/GuideReactRouter.res new file mode 100644 index 000000000..499851bf9 --- /dev/null +++ b/apps/guide/app/GuideReactRouter.res @@ -0,0 +1,32 @@ +module Links = { + @module("react-router") @react.component + external make: unit => React.element = "Links" +} + +module Meta = { + @module("react-router") @react.component + external make: unit => React.element = "Meta" +} + +module Outlet = { + @module("react-router") @react.component + external make: unit => React.element = "Outlet" +} + +module Scripts = { + @module("react-router") @react.component + external make: unit => React.element = "Scripts" +} + +module ScrollRestoration = { + @module("react-router") @react.component + external make: unit => React.element = "ScrollRestoration" +} + +@module("react-router") +external useLoaderData: unit => 'a = "useLoaderData" + +module Loader = { + type loaderArgs = {request: WebAPI.FetchAPI.request} + type t<'a> = loaderArgs => promise<'a> +} diff --git a/apps/guide/app/GuideRoot.res b/apps/guide/app/GuideRoot.res new file mode 100644 index 000000000..3a743bd0a --- /dev/null +++ b/apps/guide/app/GuideRoot.res @@ -0,0 +1,21 @@ +@module("../styles/main.css?url") +external mainCss: string = "default" + +@react.component +let default = () => { + + + + + + + + {React.string("ReScript Guide")} + + + + + + + +} diff --git a/apps/guide/app/GuideRoot.resi b/apps/guide/app/GuideRoot.resi new file mode 100644 index 000000000..47fa1ac2e --- /dev/null +++ b/apps/guide/app/GuideRoot.resi @@ -0,0 +1,2 @@ +@react.component +let default: unit => React.element diff --git a/apps/guide/app/GuideRuntimeSource.res b/apps/guide/app/GuideRuntimeSource.res new file mode 100644 index 000000000..5a6400bc6 --- /dev/null +++ b/apps/guide/app/GuideRuntimeSource.res @@ -0,0 +1,73 @@ +module Api = RescriptCompilerApi + +let resultBindingName = "__rescriptGuideOutput" + +let expressionData = (typeHint: Api.TypeHint.t) => + switch typeHint { + | Expression(data) => Some(data) + | TypeDeclaration(_) | Binding(_) | CoreType(_) => None + } + +let offsetFromPosition = (position: Api.TypeHint.position, code) => { + let lines = code->String.split("\n") + let offset = ref(0) + + if position.line > 1 { + for i in 0 to position.line - 2 { + switch lines->Array.get(i) { + | Some(line) => offset.contents = offset.contents + line->String.length + 1 + | None => () + } + } + } + + offset.contents + position.col +} + +let startsAtLineBoundary = (position: Api.TypeHint.position, code) => { + let lines = code->String.split("\n") + + switch lines->Array.get(position.line - 1) { + | Some(line) => line->String.slice(~start=0, ~end=position.col)->String.trim === "" + | None => false + } +} + +let finalExpressionData = (~code, typeHints) => { + let best: ref> = ref(None) + + typeHints->Array.forEach(typeHint => + switch typeHint->expressionData { + | Some(data) if startsAtLineBoundary(data.start, code) => + let startOffset = data.start->offsetFromPosition(code) + let endOffset = data.end->offsetFromPosition(code) + switch best.contents { + | Some((bestEndOffset, bestStartOffset, _)) + if bestEndOffset > endOffset || + (bestEndOffset === endOffset && bestStartOffset <= startOffset) => () + | _ => best.contents = Some((endOffset, startOffset, data)) + } + | Some(_) | None => () + } + ) + + best.contents->Option.map(((_, _, data)) => data) +} + +let instrument = (~code, ~typeHints) => { + switch typeHints->finalExpressionData(~code) { + | Some({start, end}) => + let startOffset = start->offsetFromPosition(code) + let endOffset = end->offsetFromPosition(code) + let expressionSource = code->String.slice(~start=startOffset, ~end=endOffset)->String.trim + + if expressionSource === "" { + None + } else { + let prefix = code->String.slice(~start=0, ~end=startOffset) + let suffix = code->String.slice(~start=endOffset) + Some(`${prefix}let ${resultBindingName} = (${expressionSource})${suffix}`) + } + | None => None + } +} diff --git a/apps/guide/app/GuideRuntimeTransform.res b/apps/guide/app/GuideRuntimeTransform.res new file mode 100644 index 000000000..4bb9ca68a --- /dev/null +++ b/apps/guide/app/GuideRuntimeTransform.res @@ -0,0 +1,165 @@ +type expression +type statement + +type program = {mutable body: array} +type ast = {program: program} +type generatorResult = {code: string} +type source = {value: string} +type local = {name: string} +type specifier +type declarator + +type t = { + code: string, + imports: Dict.t, +} + +module Parser = { + type options = {sourceType?: string} + + @module("@babel/parser") + external parse: (string, options) => ast = "parse" +} + +module Generator = { + @module("@babel/generator") + external generate: ast => generatorResult = "generate" +} + +module Types = { + @module("@babel/types") + external identifier: string => expression = "identifier" + + @module("@babel/types") + external memberExpression: (expression, expression) => expression = "memberExpression" + + @module("@babel/types") + external callExpression: (expression, array) => expression = "callExpression" + + @module("@babel/types") + external expressionStatement: expression => statement = "expressionStatement" +} + +@get external nodeType: statement => string = "type" +@get external expression: statement => expression = "expression" +@get external source: statement => source = "source" +@get external specifiers: statement => array = "specifiers" +@get external specifierLocal: specifier => local = "local" +@get external declarations: statement => array = "declarations" +@get external declaratorId: declarator => local = "id" +@get external statementId: statement => local = "id" + +let isModuleBoundary = statement => { + switch statement->nodeType { + | "ImportDeclaration" | "ExportNamedDeclaration" => true + | _ => false + } +} + +let collectRuntimeImport = (~imports, statement) => { + switch statement->nodeType { + | "ImportDeclaration" => + let sourceValue = (statement->source).value + if sourceValue->String.startsWith("./stdlib") { + switch statement->specifiers { + | [specifier] => imports->Dict.set((specifier->specifierLocal).name, sourceValue) + | _ => () + } + } + | _ => () + } +} + +let consoleLogStatement = expression => { + let consoleLog = Types.memberExpression(Types.identifier("console"), Types.identifier("log")) + Types.expressionStatement(Types.callExpression(consoleLog, [expression])) +} + +let hasBinding = (~name, statement) => + switch statement->nodeType { + | "VariableDeclaration" => + statement->declarations->Array.some(declaration => (declaration->declaratorId).name === name) + | _ => false + } + +let appendResultBindingLog = (~resultBindingName, body) => + switch resultBindingName { + | Some(name) if body->Array.some(statement => statement->hasBinding(~name)) => + Some(body->Array.concat([Types.identifier(name)->consoleLogStatement])) + | _ => None + } + +let variableDeclarationBindingName = statement => { + let declarations = statement->declarations + + switch declarations->Array.length { + | 0 => None + | length => Some((declarations->Array.getUnsafe(length - 1)->declaratorId).name) + } +} + +let bindingName = statement => + switch statement->nodeType { + | "VariableDeclaration" => statement->variableDeclarationBindingName + | "FunctionDeclaration" => Some((statement->statementId).name) + | _ => None + } + +let lastBindingName = body => { + let lastFound = ref(None) + + body->Array.forEach(statement => + switch statement->bindingName { + | Some(name) => lastFound.contents = Some(name) + | None => () + } + ) + + lastFound.contents +} + +let appendLastBindingLog = body => + switch body->lastBindingName { + | Some(name) => Some(body->Array.concat([Types.identifier(name)->consoleLogStatement])) + | None => None + } + +let transform = (~resultBindingName=?, jsCode) => + try { + let ast = Parser.parse(jsCode, {sourceType: "module"}) + let imports = Dict.make() + ast.program.body->Array.forEach(statement => statement->collectRuntimeImport(~imports)) + + let executableBody = ast.program.body->Array.filter(statement => !isModuleBoundary(statement)) + + switch executableBody->Array.length { + | 0 => None + | length => + let lastIndex = length - 1 + let lastStatement = executableBody->Array.getUnsafe(lastIndex) + + switch lastStatement->nodeType { + | "ExpressionStatement" => + ast.program.body = + executableBody->Array.mapWithIndex((statement, index) => + index === lastIndex ? lastStatement->expression->consoleLogStatement : statement + ) + Some({code: Generator.generate(ast).code, imports}) + | _ => + switch executableBody->appendResultBindingLog(~resultBindingName) { + | Some(body) => + ast.program.body = body + Some({code: Generator.generate(ast).code, imports}) + | None => + switch executableBody->appendLastBindingLog { + | Some(body) => + ast.program.body = body + Some({code: Generator.generate(ast).code, imports}) + | None => None + } + } + } + } + } catch { + | _ => None + } diff --git a/apps/guide/app/root.mjs b/apps/guide/app/root.mjs new file mode 100644 index 000000000..11c4bd1e4 --- /dev/null +++ b/apps/guide/app/root.mjs @@ -0,0 +1 @@ +export { default } from "./GuideRoot.jsx"; diff --git a/apps/guide/app/routes.mjs b/apps/guide/app/routes.mjs new file mode 100644 index 000000000..2a84d0086 --- /dev/null +++ b/apps/guide/app/routes.mjs @@ -0,0 +1,3 @@ +import { index } from "@react-router/dev/routes"; + +export default [index("./GuideHomeRoute.jsx")]; diff --git a/apps/guide/package.json b/apps/guide/package.json new file mode 100644 index 000000000..070ab446c --- /dev/null +++ b/apps/guide/package.json @@ -0,0 +1,45 @@ +{ + "name": "@rescript-lang/guide", + "version": "1.0.0", + "private": true, + "license": "MIT", + "type": "module", + "scripts": { + "build:res": "rescript build --warn-error +3+8+11+12+26+27+31+32+33+34+35+39+44+45+110", + "build:vite": "react-router build", + "build": "yarn build:res && yarn build:vite", + "ci:test": "yarn build:res && yarn vitest --run --browser.headless", + "dev:vite": "yarn build:res && vite --host", + "vitest": "vitest" + }, + "dependencies": { + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "@react-router/node": "^7.14.0", + "@rescript-lang/playground": "workspace:*", + "@rescript-lang/shared": "workspace:*", + "@rescript/react": "^0.14.2", + "@rescript/webapi": "0.1.0-experimental-29db5f4", + "isbot": "^5", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-router": "^7.14.0", + "react-router-dom": "^7.14.0", + "rescript": "^12.2.0" + }, + "devDependencies": { + "@react-router/dev": "^7.14.0", + "@vitejs/plugin-react": "^6.0.1", + "@vitest/browser-playwright": "^4.1.2", + "lightningcss": "^1.32.0", + "playwright": "^1.59.1", + "vite": "^8.0.3", + "vite-plugin-env-compatible": "^2.0.1", + "vitest": "^4.1.2", + "vitest-browser-react": "^2.2.0" + }, + "engines": { + "node": ">=22" + } +} diff --git a/apps/guide/react-router.config.mjs b/apps/guide/react-router.config.mjs new file mode 100644 index 000000000..df41a8ad8 --- /dev/null +++ b/apps/guide/react-router.config.mjs @@ -0,0 +1,9 @@ +import * as fs from "node:fs"; + +export default { + ssr: false, + prerender: ["/"], + buildEnd: async () => { + fs.cpSync("./build/client", "./out", { recursive: true }); + }, +}; diff --git a/apps/guide/rescript.json b/apps/guide/rescript.json new file mode 100644 index 000000000..5739060e2 --- /dev/null +++ b/apps/guide/rescript.json @@ -0,0 +1,35 @@ +{ + "name": "@rescript-lang/guide", + "namespace": false, + "dependencies": [ + "@rescript-lang/playground", + "@rescript-lang/shared", + "@rescript/react", + "@rescript/webapi" + ], + "compiler-flags": ["-open WebAPI.Global"], + "sources": [ + { + "dir": "__tests__", + "subdirs": true, + "type": "dev" + }, + { + "dir": "src", + "subdirs": true, + "type": "dev" + }, + { + "dir": "app", + "subdirs": true + } + ], + "warnings": { + "error": "+8" + }, + "gentypeconfig": { + "language": "untyped", + "shims": [], + "module": "es6" + } +} diff --git a/apps/guide/src/bindings/GuideVitest.res b/apps/guide/src/bindings/GuideVitest.res new file mode 100644 index 000000000..a42639082 --- /dev/null +++ b/apps/guide/src/bindings/GuideVitest.res @@ -0,0 +1,50 @@ +type expect +type element + +@module("vitest") +external test: (string, unit => promise) => unit = "test" + +@module("vitest") +external expect: 'a => expect = "expect" + +@send +external toBe: (expect, 'a) => unit = "toBe" + +@module("vitest/browser") @scope("page") +external viewport: (int, int) => promise = "viewport" + +@module("vitest-browser-react") +external render: Jsx.element => promise = "render" + +@module("vitest") @scope("expect") +external element: 'a => element = "element" + +@send +external getByText: (element, string) => promise = "getByText" + +@send +external getByLabelText: (element, string) => promise = "getByLabelText" + +@send +external getByTestId: (element, string) => promise = "getByTestId" + +@send +external toBeVisible: element => promise = "toBeVisible" + +@send +external toHaveValue: (element, string) => promise = "toHaveValue" + +@send +external toHaveTextContent: (element, string) => promise = "toHaveTextContent" + +@send +external toHaveClass: (element, string) => promise = "toHaveClass" + +@send +external click: element => promise = "click" + +@get +external container: element => Dom.element = "container" + +@get +external textContent: Dom.element => Nullable.t = "textContent" diff --git a/apps/guide/styles/main.css b/apps/guide/styles/main.css new file mode 100644 index 000000000..8ce102d09 --- /dev/null +++ b/apps/guide/styles/main.css @@ -0,0 +1,309 @@ +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + min-height: 100%; + background: #f6f4ef; + color: #1f2933; + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; +} + +body { + min-width: 1024px; +} + +.guide-shell { + --guide-page-bg: #f6f4ef; + --guide-surface-bg: #ffffff; + --guide-panel-bg: #ffffff; + --guide-border: #d8d6cf; + --guide-border-soft: #e4e1d8; + --guide-heading: #102a43; + --guide-text: #1f2933; + --guide-muted: #52606d; + --guide-button-bg: #102a43; + --guide-button-border: #102a43; + --guide-button-hover-bg: #1f4e79; + --guide-button-hover-border: #1f4e79; + --guide-button-text: #ffffff; + --guide-toggle-bg: #ffffff; + --guide-toggle-text: #102a43; + --guide-toggle-border: #cbd5e1; + --guide-resize-bg: #edeae2; + --guide-resize-hover-bg: #93c5fd; + display: grid; + grid-template-columns: + minmax(320px, var(--guide-instructions-width, 50%)) + 8px minmax(480px, 1fr); + min-height: 100vh; + background: var(--guide-page-bg); + color: var(--guide-text); +} + +.guide-theme-dark { + --guide-page-bg: #0f172a; + --guide-surface-bg: #101827; + --guide-panel-bg: #0f172a; + --guide-border: #334155; + --guide-border-soft: #1f2937; + --guide-heading: #e5e7eb; + --guide-text: #cbd5e1; + --guide-muted: #94a3b8; + --guide-button-bg: #38bdf8; + --guide-button-border: #38bdf8; + --guide-button-hover-bg: #0ea5e9; + --guide-button-hover-border: #0ea5e9; + --guide-button-text: #082f49; + --guide-toggle-bg: #111827; + --guide-toggle-text: #e5e7eb; + --guide-toggle-border: #475569; + --guide-resize-bg: #1f2937; + --guide-resize-hover-bg: #38bdf8; +} + +.guide-instructions { + background: var(--guide-page-bg); + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 40px; + font-size: 18px; + line-height: 1.6; +} + +.guide-copy { + max-width: 620px; +} + +.guide-copy h1 { + color: var(--guide-heading); + font-size: 34px; + line-height: 1.15; + margin: 0 0 22px; +} + +.guide-copy p { + margin: 0 0 18px; +} + +.guide-kicker { + color: var(--guide-muted); + font-size: 12px; + font-weight: 700; + letter-spacing: 0; + margin: 0; + text-transform: uppercase; +} + +.guide-topbar { + align-items: center; + display: flex; + gap: 16px; + justify-content: space-between; + margin-bottom: 22px; +} + +.guide-theme-toggle { + background: var(--guide-toggle-bg); + border: 1px solid var(--guide-toggle-border); + color: var(--guide-toggle-text); + cursor: pointer; + font: inherit; + font-size: 13px; + font-weight: 700; + line-height: 1; + padding: 8px 10px; +} + +.guide-theme-toggle:hover { + border-color: var(--guide-resize-hover-bg); +} + +.guide-next-button { + align-self: flex-start; + background: var(--guide-button-bg); + border: 1px solid var(--guide-button-border); + color: var(--guide-button-text); + cursor: pointer; + font: inherit; + font-size: 15px; + font-weight: 700; + line-height: 1; + padding: 12px 18px; +} + +.guide-next-button:hover { + background: var(--guide-button-hover-bg); + border-color: var(--guide-button-hover-border); +} + +.guide-resize-handle { + background: var(--guide-resize-bg); + min-height: 0; + min-width: 0; + transition: background-color 120ms ease; +} + +.guide-resize-handle:hover { + background: var(--guide-resize-hover-bg); +} + +.guide-resize-handle-columns { + cursor: col-resize; +} + +.guide-resize-handle-rows { + cursor: row-resize; +} + +.guide-workspace { + display: grid; + grid-template-rows: minmax(180px, 1fr) 8px var(--guide-output-height, 220px); + min-height: 100vh; + background: var(--guide-panel-bg); +} + +.guide-editor-panel, +.guide-output-panel { + display: flex; + flex-direction: column; + min-height: 0; +} + +.guide-editor-panel { + background: var(--guide-surface-bg); +} + +.guide-label { + background: var(--guide-surface-bg); + border-bottom: 1px solid var(--guide-border-soft); + color: var(--guide-muted); + font-size: 12px; + font-weight: 700; + letter-spacing: 0; + padding: 10px 14px; + text-transform: uppercase; +} + +.guide-editor { + flex: 1; + font-family: "Roboto Mono", "SFMono-Regular", Consolas, monospace; + font-size: 15px; + line-height: 1.6; + min-height: 0; + --playground-editor-bg: #ffffff; + --playground-editor-text: #102a43; + --playground-editor-cursor: #1f4e79; + --playground-editor-active-line: rgba(31, 78, 121, 0.07); + --playground-editor-gutter-bg: #f8fafc; + --playground-editor-gutter-text: #829ab1; + --playground-editor-gutter-border: #d8d6cf; + --playground-editor-active-gutter-bg: #eef2f6; + --playground-editor-active-gutter-text: #102a43; + --playground-editor-selection: rgba(31, 78, 121, 0.2); + --playground-editor-selection-match: rgba(45, 125, 210, 0.16); + --playground-editor-syntax-keyword: #7b2cbf; + --playground-editor-syntax-variable: #102a43; + --playground-editor-syntax-type: #b45309; + --playground-editor-syntax-string: #0f766e; + --playground-editor-syntax-comment: #6b7280; + --playground-editor-syntax-namespace-def: #b45309; + --playground-editor-syntax-namespace: #2563eb; + --playground-editor-syntax-property: #1f4e79; + --playground-editor-syntax-attribute: #475569; +} + +.guide-theme-dark .guide-editor { + --playground-editor-bg: #101827; + --playground-editor-text: #dbeafe; + --playground-editor-cursor: #38bdf8; + --playground-editor-active-line: rgba(56, 189, 248, 0.12); + --playground-editor-gutter-bg: #0f172a; + --playground-editor-gutter-text: #64748b; + --playground-editor-gutter-border: #334155; + --playground-editor-active-gutter-bg: #172033; + --playground-editor-active-gutter-text: #e5e7eb; + --playground-editor-selection: rgba(56, 189, 248, 0.28); + --playground-editor-selection-match: rgba(56, 189, 248, 0.18); + --playground-editor-syntax-keyword: #c084fc; + --playground-editor-syntax-variable: #dbeafe; + --playground-editor-syntax-type: #fbbf24; + --playground-editor-syntax-string: #5eead4; + --playground-editor-syntax-comment: #94a3b8; + --playground-editor-syntax-namespace-def: #fbbf24; + --playground-editor-syntax-namespace: #7dd3fc; + --playground-editor-syntax-property: #93c5fd; + --playground-editor-syntax-attribute: #cbd5e1; +} + +.guide-editor .cm-editor { + height: 100%; +} + +.guide-editor .cm-scroller { + font-family: inherit; +} + +.guide-output-frame { + display: flex; + flex: 1; + min-height: 0; +} + +.guide-output { + background: #111827; + color: #d1fae5; + flex: 1; + font-family: "Roboto Mono", "SFMono-Regular", Consolas, monospace; + font-size: 15px; + line-height: 1.6; + margin: 0; + overflow: auto; + padding: 18px; +} + +.guide-output-status { + color: #e5e7eb; + margin-bottom: 14px; +} + +.guide-output-group { + margin-top: 18px; +} + +.guide-output-heading { + color: #93c5fd; + font-size: 12px; + font-weight: 700; + margin-bottom: 8px; + text-transform: uppercase; +} + +.guide-output-line { + color: inherit; + font: inherit; + margin: 0 0 8px; + white-space: pre-wrap; +} + +.guide-output-line-error { + color: #fca5a5; +} + +.guide-output-line-warning { + color: #fde68a; +} + +.guide-runtime-frame { + display: none; +} diff --git a/apps/guide/vite.config.mjs b/apps/guide/vite.config.mjs new file mode 100644 index 000000000..f54f1a5da --- /dev/null +++ b/apps/guide/vite.config.mjs @@ -0,0 +1,62 @@ +import { reactRouter } from "@react-router/dev/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import env from "vite-plugin-env-compatible"; + +const excludedFiles = ["lib/**", "**/*.res", "**/*.resi"]; +const sharedEditorDeps = [ + "@babel/generator", + "@babel/parser", + "@babel/traverse", + "@babel/types", + "@codemirror/commands", + "@codemirror/lang-javascript", + "@codemirror/language", + "@codemirror/lint", + "@codemirror/search", + "@codemirror/state", + "@codemirror/view", + "@lezer/highlight", + "@replit/codemirror-vim", + "@rescript/runtime/lib/es6/Belt_Array.js", + "@rescript/runtime/lib/es6/Primitive_object.js", + "@rescript/runtime/lib/es6/Primitive_option.js", + "@rescript/runtime/lib/es6/Stdlib_Array.js", + "@rescript/runtime/lib/es6/Stdlib_Dict.js", + "@rescript/runtime/lib/es6/Stdlib_Int.js", + "@rescript/runtime/lib/es6/Stdlib_JsExn.js", + "@rescript/runtime/lib/es6/Stdlib_List.js", + "@rescript/runtime/lib/es6/Stdlib_Option.js", + "@tsnobip/rescript-lezer", + "lz-string", + "react-router", +]; + +export default defineConfig({ + envDir: "../..", + plugins: [ + env({ prefix: "PUBLIC_" }), + reactRouter(), + react({ + include: ["**/*.mjs"], + exclude: excludedFiles, + }), + ], + server: { + watch: { + ignored: excludedFiles, + }, + }, + build: { + sourcemap: process.env.NODE_ENV !== "production", + }, + css: { + transformer: "lightningcss", + }, + optimizeDeps: { + include: sharedEditorDeps, + }, + legacy: { + inconsistentCjsInterop: true, + }, +}); diff --git a/apps/guide/vitest.config.mjs b/apps/guide/vitest.config.mjs new file mode 100644 index 000000000..7632b395f --- /dev/null +++ b/apps/guide/vitest.config.mjs @@ -0,0 +1,59 @@ +import { defineConfig } from "vitest/config"; +import { playwright } from "@vitest/browser-playwright"; +import react from "@vitejs/plugin-react"; +import env from "vite-plugin-env-compatible"; + +const sharedEditorDeps = [ + "@babel/generator", + "@babel/parser", + "@babel/traverse", + "@babel/types", + "@codemirror/commands", + "@codemirror/lang-javascript", + "@codemirror/language", + "@codemirror/lint", + "@codemirror/search", + "@codemirror/state", + "@codemirror/view", + "@lezer/highlight", + "@replit/codemirror-vim", + "@rescript/runtime/lib/es6/Belt_Array.js", + "@rescript/runtime/lib/es6/Primitive_object.js", + "@rescript/runtime/lib/es6/Primitive_option.js", + "@rescript/runtime/lib/es6/Stdlib_Array.js", + "@rescript/runtime/lib/es6/Stdlib_Dict.js", + "@rescript/runtime/lib/es6/Stdlib_Int.js", + "@rescript/runtime/lib/es6/Stdlib_JsExn.js", + "@rescript/runtime/lib/es6/Stdlib_List.js", + "@rescript/runtime/lib/es6/Stdlib_Option.js", + "@tsnobip/rescript-lezer", + "lz-string", + "react-router", +]; + +export default defineConfig({ + envDir: "../..", + plugins: [env({ prefix: "PUBLIC_" }), react()], + optimizeDeps: { + include: sharedEditorDeps, + }, + test: { + include: ["__tests__/*.jsx"], + setupFiles: ["./vitest.setup.mjs"], + browser: { + enabled: true, + provider: playwright({ + contextOptions: { + deviceScaleFactor: 1, + }, + }), + ui: false, + instances: [ + { + browser: "chromium", + viewport: { width: 1440, height: 900 }, + }, + ], + }, + }, +}); diff --git a/apps/guide/vitest.setup.mjs b/apps/guide/vitest.setup.mjs new file mode 100644 index 000000000..9dc01a105 --- /dev/null +++ b/apps/guide/vitest.setup.mjs @@ -0,0 +1 @@ +import "./styles/main.css"; diff --git a/package.json b/package.json index e6c484677..8f92badd0 100644 --- a/package.json +++ b/package.json @@ -12,16 +12,18 @@ "scripts": { "build:scripts": "yarn workspace @rescript-lang/docs build:scripts", "build:generate-llms": "yarn workspace @rescript-lang/docs build:generate-llms", + "build:guide": "yarn workspace @rescript-lang/guide build", "build:res": "rescript build --warn-error +3+8+11+12+26+27+31+32+33+34+35+39+44+45+110", "build:sync-bundles": "yarn workspace @rescript-lang/docs build:sync-bundles", "build:update-index": "yarn workspace @rescript-lang/docs build:update-index", "build:vite": "yarn workspace @rescript-lang/docs build:vite", "build": "yarn workspace @rescript-lang/docs build", "ci:format": "oxfmt --check", - "ci:test": "yarn workspace @rescript-lang/docs ci:test", + "ci:test": "yarn workspace @rescript-lang/docs ci:test && yarn workspace @rescript-lang/guide ci:test", "clean:res": "rescript clean", "convert-images": "yarn workspace @rescript-lang/docs convert-images", "dev:res": "rescript watch", + "dev:guide": "yarn workspace @rescript-lang/guide dev:vite", "dev:vite": "yarn workspace @rescript-lang/docs dev:vite", "dev:wrangler": "yarn workspace @rescript-lang/docs dev:wrangler", "dev": "yarn workspace @rescript-lang/docs dev", @@ -34,6 +36,7 @@ "cy:run": "yarn workspace @rescript-lang/docs cy:run", "cy:e2e": "yarn workspace @rescript-lang/docs cy:e2e", "vitest": "yarn workspace @rescript-lang/docs vitest", + "vitest:guide": "yarn workspace @rescript-lang/guide vitest", "vitest:update": "yarn workspace @rescript-lang/docs vitest:update" }, "devDependencies": { diff --git a/packages/playground/src/CompilerManagerHook.res b/packages/playground/src/CompilerManagerHook.res index 8dd18c2a1..c0c269ce0 100644 --- a/packages/playground/src/CompilerManagerHook.res +++ b/packages/playground/src/CompilerManagerHook.res @@ -244,6 +244,7 @@ let useCompilerManager = ( ~bundleBaseUrl: string, ~initialVersion: option=?, ~initialModuleSystem=defaultModuleSystem, + ~initialWarnFlags=?, ~initialJsxPreserveMode=false, ~initialExperimentalFeatures=[], ~initialLang: Lang.t=Res, @@ -444,9 +445,11 @@ let useCompilerManager = ( // should default to esmodule. So we override the config // and use the `setConfig` function to sync up the // internal compiler state with our playground state. + let defaultConfig = instance->Compiler.getConfig let config = { - ...instance->Compiler.getConfig, + ...defaultConfig, moduleSystem: initialModuleSystem, + warnFlags: initialWarnFlags->Option.getOr(defaultConfig.warnFlags), experimentalFeatures: initialExperimentalFeatures, jsxPreserveMode: initialJsxPreserveMode, ?openModules, @@ -507,9 +510,11 @@ let useCompilerManager = ( let apiVersion = apiVersion->Version.fromString let openModules = getOpenModules(~apiVersion, ~libraries) + let defaultConfig = instance->Compiler.getConfig let config = { - ...instance->Compiler.getConfig, + ...defaultConfig, moduleSystem: defaultModuleSystem, + warnFlags: initialWarnFlags->Option.getOr(defaultConfig.warnFlags), ?openModules, } @@ -632,6 +637,7 @@ let useCompilerManager = ( dispatchError, initialVersion, initialModuleSystem, + initialWarnFlags, initialJsxPreserveMode, initialExperimentalFeatures, initialLang, diff --git a/packages/playground/src/CompilerManagerHook.resi b/packages/playground/src/CompilerManagerHook.resi index 7f2238b16..fec4dc63b 100644 --- a/packages/playground/src/CompilerManagerHook.resi +++ b/packages/playground/src/CompilerManagerHook.resi @@ -64,6 +64,7 @@ let useCompilerManager: ( ~bundleBaseUrl: string, ~initialVersion: Semver.t=?, ~initialModuleSystem: string=?, + ~initialWarnFlags: string=?, ~initialJsxPreserveMode: bool=?, ~initialExperimentalFeatures: array=?, ~initialLang: Lang.t=?, diff --git a/rescript.json b/rescript.json index 095144ecb..a7323f7d2 100644 --- a/rescript.json +++ b/rescript.json @@ -3,6 +3,7 @@ "dependencies": [ "@rescript-lang/shared", "@rescript-lang/playground", + "@rescript-lang/guide", "@rescript-lang/docs" ], "sources": [], diff --git a/yarn.lock b/yarn.lock index 584876d8c..344b8930e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2453,6 +2453,36 @@ __metadata: languageName: unknown linkType: soft +"@rescript-lang/guide@workspace:apps/guide": + version: 0.0.0-use.local + resolution: "@rescript-lang/guide@workspace:apps/guide" + dependencies: + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.2" + "@babel/types": "npm:^7.29.0" + "@react-router/dev": "npm:^7.14.0" + "@react-router/node": "npm:^7.14.0" + "@rescript-lang/playground": "workspace:*" + "@rescript-lang/shared": "workspace:*" + "@rescript/react": "npm:^0.14.2" + "@rescript/webapi": "npm:0.1.0-experimental-29db5f4" + "@vitejs/plugin-react": "npm:^6.0.1" + "@vitest/browser-playwright": "npm:^4.1.2" + isbot: "npm:^5" + lightningcss: "npm:^1.32.0" + playwright: "npm:^1.59.1" + react: "npm:^19.2.4" + react-dom: "npm:^19.2.4" + react-router: "npm:^7.14.0" + react-router-dom: "npm:^7.14.0" + rescript: "npm:^12.2.0" + vite: "npm:^8.0.3" + vite-plugin-env-compatible: "npm:^2.0.1" + vitest: "npm:^4.1.2" + vitest-browser-react: "npm:^2.2.0" + languageName: unknown + linkType: soft + "@rescript-lang/playground@workspace:*, @rescript-lang/playground@workspace:packages/playground": version: 0.0.0-use.local resolution: "@rescript-lang/playground@workspace:packages/playground" @@ -6846,6 +6876,13 @@ __metadata: languageName: node linkType: hard +"isbot@npm:^5": + version: 5.1.39 + resolution: "isbot@npm:5.1.39" + checksum: 10c0/b6cfd4fa59662e7f660cefb7396629ab8d3142c2c4d4240fc4e8ecd08942f8c1c5f5b7b17861231466bdfb6e03ea924381470908b1d7aef5a9632fff9403e692 + languageName: node + linkType: hard + "isbot@npm:^5.1.11, isbot@npm:^5.1.37": version: 5.1.37 resolution: "isbot@npm:5.1.37" From 83a0f0003745369a7d84fd7705e0407a4b738a17 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 4 May 2026 10:56:30 -0400 Subject: [PATCH 18/32] feat: persist guide editor state Persist guide editor code in local storage and show a desktop-only message on narrow viewports, with browser coverage for both states. --- apps/guide/__tests__/GuideHome_.test.res | 32 +++++ apps/guide/app/GuideHome.res | 149 ++++++++++++----------- apps/guide/app/GuideLayout.res | 7 ++ apps/guide/styles/main.css | 37 +++++- 4 files changed, 152 insertions(+), 73 deletions(-) diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index 11229c98e..a55ae45f2 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -264,6 +264,29 @@ test("stores resized pane dimensions in local storage", async () => { GuideLayout.clearPaneSizes() }) +test("stores guide editor code in local storage", async () => { + GuideLayout.clearCode() + + GuideLayout.saveCode("let voyager = 1701") + + expect(GuideLayout.loadCode()->Option.getOr(""))->toBe("let voyager = 1701") + + GuideLayout.clearCode() +}) + +test("loads saved guide editor code into the editor", async () => { + await viewport(1440, 900) + GuideLayout.clearCode() + GuideLayout.saveCode("let sisko = \"emissary\"") + + let screen = await render() + let editor = await screen->getByTestId("guide-code-editor") + + await editor->element->toHaveTextContent("let sisko = \"emissary\"") + + GuideLayout.clearCode() +}) + test("renders resize handles and toggles dark mode", async () => { await viewport(1440, 900) @@ -280,6 +303,15 @@ test("renders resize handles and toggles dark mode", async () => { await shell->element->toHaveClass("guide-theme-dark") }) +test("shows a minimum screen size message on narrow viewports", async () => { + await viewport(800, 900) + + let screen = await render() + let message = await screen->getByText("This guide needs a wider screen.") + + await message->element->toBeVisible +}) + test("stretches the output surface to the full output panel", async () => { await viewport(1440, 900) diff --git a/apps/guide/app/GuideHome.res b/apps/guide/app/GuideHome.res index a21377b69..4d0294d2a 100644 --- a/apps/guide/app/GuideHome.res +++ b/apps/guide/app/GuideHome.res @@ -145,16 +145,21 @@ let make = (~compilerData: option=?) => { React.useEffect(() => { switch editorContainerRef.current { | Value(parent) => + let initialCode = GuideLayout.loadCode()->Option.getOr(GuideCompilerSettings.initialCode) + setCode(_ => initialCode) let config: CodeMirror.editorConfig = { parent: parent->domElementToWebElement, - initialValue: GuideCompilerSettings.initialCode, + initialValue: initialCode, mode: "rescript", readOnly: false, lineNumbers: true, lineWrapping: false, theme: theme->GuideLayout.themeToCodeMirror, keyMap: CodeMirror.KeyMap.Default, - onChange: value => setCode(_ => value), + onChange: value => { + GuideLayout.saveCode(value) + setCode(_ => value) + }, errors: [], hoverHints: [], minHeight: "100%", @@ -166,76 +171,82 @@ let make = (~compilerData: option=?) => { } }, []) -
GuideLayout.themeClass} - dataTestId="guide-mvp" - ref={ReactDOM.Ref.domRef(shellRef)} - > -
-
-
-

{React.string("Mission 01")}

- + <> +
+

{React.string("This guide needs a wider screen.")}

+

{React.string("Use a desktop browser or resize this window to continue.")}

+
+
GuideLayout.themeClass} + dataTestId="guide-mvp" + ref={ReactDOM.Ref.domRef(shellRef)} + > +
+
+
+

{React.string("Mission 01")}

+ +
+

{React.string("Learn ReScript Guide")}

+

+ {React.string( + "This interactive guide introduces ReScript through small examples, steady practice, and a live output log.", + )} +

+

{React.string("Lorem ipsum dolor sit amet.")}

+

+ {React.string( + "Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + )} +

-

{React.string("Learn ReScript Guide")}

-

- {React.string( - "This interactive guide introduces ReScript through small examples, steady practice, and a live output log.", - )} -

-

{React.string("Lorem ipsum dolor sit amet.")}

-

- {React.string( - "Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - )} -

-
- -
-
startDragging(ResizingColumns, event)} - role="separator" - /> -
-
-
{React.string("Editor")}
-
-
+ +
startDragging(ResizingRows, event)} + ariaLabel="Resize instructions pane" + className="guide-resize-handle guide-resize-handle-columns" + dataTestId="guide-column-resize" + onMouseDown={event => startDragging(ResizingColumns, event)} role="separator" /> -
-
{React.string("Output")}
-
- +
+
+
{React.string("Editor")}
+
-
-
- {switch compilerData { - | Some({bundleBaseUrl, versions}) => - - | None => React.null - }} -
+
startDragging(ResizingRows, event)} + role="separator" + /> +
+
{React.string("Output")}
+
+ +
+
+

+ {switch compilerData { + | Some({bundleBaseUrl, versions}) => + + | None => React.null + }} + + } diff --git a/apps/guide/app/GuideLayout.res b/apps/guide/app/GuideLayout.res index f8a170dba..31e80e8c1 100644 --- a/apps/guide/app/GuideLayout.res +++ b/apps/guide/app/GuideLayout.res @@ -15,6 +15,7 @@ let defaultOutputHeight = 220.0 let themeStorageKey = "guideTheme" let instructionsWidthStorageKey = "guidePaneInstructionsWidth" let outputHeightStorageKey = "guidePaneOutputHeight" +let codeStorageKey = "guideCode" let defaultPaneSizes = { instructionsWidth: None, @@ -160,3 +161,9 @@ let clearPaneSizes = () => { removeLocalStorageItem(instructionsWidthStorageKey) removeLocalStorageItem(outputHeightStorageKey) } + +let loadCode = () => getLocalStorageItem(codeStorageKey) + +let saveCode = code => setLocalStorageItem(~key=codeStorageKey, ~value=code) + +let clearCode = () => removeLocalStorageItem(codeStorageKey) diff --git a/apps/guide/styles/main.css b/apps/guide/styles/main.css index 8ce102d09..30a64fb2d 100644 --- a/apps/guide/styles/main.css +++ b/apps/guide/styles/main.css @@ -18,10 +18,6 @@ body { sans-serif; } -body { - min-width: 1024px; -} - .guide-shell { --guide-page-bg: #f6f4ef; --guide-surface-bg: #ffffff; @@ -50,6 +46,29 @@ body { color: var(--guide-text); } +.guide-screen-size-message { + background: #0f172a; + color: #cbd5e1; + display: none; + min-height: 100vh; + padding: 32px; + place-content: center; + text-align: center; +} + +.guide-screen-size-message h1 { + color: #f8fafc; + font-size: 30px; + line-height: 1.2; + margin: 0 0 12px; +} + +.guide-screen-size-message p { + font-size: 16px; + line-height: 1.5; + margin: 0; +} + .guide-theme-dark { --guide-page-bg: #0f172a; --guide-surface-bg: #101827; @@ -307,3 +326,13 @@ body { .guide-runtime-frame { display: none; } + +@media (max-width: 1023px) { + .guide-screen-size-message { + display: grid; + } + + .guide-shell { + display: none; + } +} From 2b6fb6123922812aa062b0f530315325fdb191eb Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 4 May 2026 11:04:06 -0400 Subject: [PATCH 19/32] feat: add guide checkpoint progress Introduce the first guide lesson model, expected-output checkpoint evaluation, and localStorage progress/code storage in the rescript-guide:v1 namespace. --- apps/guide/__tests__/GuideHome_.test.res | 69 ++++++++++++++++++++---- apps/guide/app/GuideHome.res | 58 ++++++++++++++------ apps/guide/app/GuideLayout.res | 67 ++++++++++++++++++++--- apps/guide/app/GuideLesson.res | 48 +++++++++++++++++ apps/guide/styles/main.css | 25 +++++++++ 5 files changed, 235 insertions(+), 32 deletions(-) create mode 100644 apps/guide/app/GuideLesson.res diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index a55ae45f2..5c08d092c 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -80,6 +80,25 @@ test("does not include compiler type hints in successful output", async () => { expect(output.typeHints->Array.length)->toBe(0) }) +test("checks expected output for the first lesson exercise", async () => { + let matchingOutput = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["hello, world!"]}], + ) + let nonMatchingOutput = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["goodbye"]}], + ) + + expect(GuideLesson.first.exercise.initialCode)->toBe(`let greeting = "hello, world!"`) + expect( + GuideLesson.isExerciseComplete(~exercise=GuideLesson.first.exercise, ~output=matchingOutput), + )->toBe(true) + expect( + GuideLesson.isExerciseComplete(~exercise=GuideLesson.first.exercise, ~output=nonMatchingOutput), + )->toBe(false) +}) + test("renders output diagnostics as error lines", async () => { let output = GuideCompilerFeedback.Output.make( ~status="Compiler error", @@ -264,27 +283,44 @@ test("stores resized pane dimensions in local storage", async () => { GuideLayout.clearPaneSizes() }) -test("stores guide editor code in local storage", async () => { - GuideLayout.clearCode() +test("stores guide exercise code in local storage", async () => { + let exerciseId = GuideLesson.first.exercise.id + GuideLayout.clearExerciseCode(exerciseId) - GuideLayout.saveCode("let voyager = 1701") + GuideLayout.saveExerciseCode(~exerciseId, ~code="let voyager = 1701") - expect(GuideLayout.loadCode()->Option.getOr(""))->toBe("let voyager = 1701") + expect(GuideLayout.loadExerciseCode(exerciseId)->Option.getOr(""))->toBe("let voyager = 1701") - GuideLayout.clearCode() + GuideLayout.clearExerciseCode(exerciseId) +}) + +test("stores completed guide exercises in local storage", async () => { + let exerciseId = GuideLesson.first.exercise.id + GuideLayout.clearCompletedExercises() + + GuideLayout.saveCompletedExercise(exerciseId) + GuideLayout.saveCompletedExercise(exerciseId) + + let completedExerciseIds = GuideLayout.loadCompletedExerciseIds() + + expect(completedExerciseIds->Array.includes(exerciseId))->toBe(true) + expect(completedExerciseIds->Array.length)->toBe(1) + + GuideLayout.clearCompletedExercises() }) test("loads saved guide editor code into the editor", async () => { await viewport(1440, 900) - GuideLayout.clearCode() - GuideLayout.saveCode("let sisko = \"emissary\"") + let exerciseId = GuideLesson.first.exercise.id + GuideLayout.clearExerciseCode(exerciseId) + GuideLayout.saveExerciseCode(~exerciseId, ~code="let sisko = \"emissary\"") let screen = await render() let editor = await screen->getByTestId("guide-code-editor") await editor->element->toHaveTextContent("let sisko = \"emissary\"") - GuideLayout.clearCode() + GuideLayout.clearExerciseCode(exerciseId) }) test("renders resize handles and toggles dark mode", async () => { @@ -312,6 +348,15 @@ test("shows a minimum screen size message on narrow viewports", async () => { await message->element->toBeVisible }) +test("shows the first checkpoint as complete when output matches", async () => { + await viewport(1440, 900) + + let screen = await render() + let checkpoint = await screen->getByTestId("guide-check-status") + + await checkpoint->element->toHaveTextContent("Checkpoint complete") +}) + test("stretches the output surface to the full output panel", async () => { await viewport(1440, 900) @@ -327,7 +372,13 @@ test("renders the first guide MVP exercise and output", async () => { let screen = await render() await (await screen->getByText("Learn ReScript Guide"))->element->toBeVisible - await (await screen->getByText("Lorem ipsum dolor sit amet."))->element->toBeVisible + await ( + await screen->getByText( + "The editor runs automatically. For this first checkpoint, the final value should print hello, world! in the output log.", + ) + ) + ->element + ->toBeVisible await (await screen->getByText("Next"))->element->toBeVisible let editor = await screen->getByTestId("guide-code-editor") await editor->element->toBeVisible diff --git a/apps/guide/app/GuideHome.res b/apps/guide/app/GuideHome.res index 4d0294d2a..2d38cf861 100644 --- a/apps/guide/app/GuideHome.res +++ b/apps/guide/app/GuideHome.res @@ -57,7 +57,9 @@ let make = (~compilerData: option=?) => { let shellRef: React.ref> = React.useRef(Nullable.null) let editorContainerRef: React.ref> = React.useRef(Nullable.null) let editorRef: React.ref> = React.useRef(None) - let (code, setCode) = React.useState(() => GuideCompilerSettings.initialCode) + let lesson = GuideLesson.first + let exercise = lesson.exercise + let (code, setCode) = React.useState(() => exercise.initialCode) let (output, setOutput) = React.useState(() => GuideCompilerFeedback.Output.initial) let (paneSizes, setPaneSizes) = React.useState(() => GuideLayout.defaultPaneSizes) let (theme, setTheme) = React.useState(() => GuideLayout.Light) @@ -142,10 +144,21 @@ let make = (~compilerData: option=?) => { dragTarget.current = target } + let exercisePassed = GuideLesson.isExerciseComplete(~exercise, ~output) + let checkpointComplete = exercisePassed || GuideLayout.isExerciseCompleted(exercise.id) + + React.useEffect(() => { + if exercisePassed { + GuideLayout.saveCompletedExercise(exercise.id) + } + None + }, (exercisePassed, exercise.id)) + React.useEffect(() => { switch editorContainerRef.current { | Value(parent) => - let initialCode = GuideLayout.loadCode()->Option.getOr(GuideCompilerSettings.initialCode) + let initialCode = + GuideLayout.loadExerciseCode(exercise.id)->Option.getOr(exercise.initialCode) setCode(_ => initialCode) let config: CodeMirror.editorConfig = { parent: parent->domElementToWebElement, @@ -157,7 +170,7 @@ let make = (~compilerData: option=?) => { theme: theme->GuideLayout.themeToCodeMirror, keyMap: CodeMirror.KeyMap.Default, onChange: value => { - GuideLayout.saveCode(value) + GuideLayout.saveExerciseCode(~exerciseId=exercise.id, ~code=value) setCode(_ => value) }, errors: [], @@ -184,7 +197,7 @@ let make = (~compilerData: option=?) => {
-

{React.string("Mission 01")}

+

{React.string(lesson.missionLabel)}

-

{React.string("Learn ReScript Guide")}

-

- {React.string( - "This interactive guide introduces ReScript through small examples, steady practice, and a live output log.", - )} -

-

{React.string("Lorem ipsum dolor sit amet.")}

-

- {React.string( - "Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - )} -

+

{React.string(lesson.title)}

+ {lesson.paragraphs + ->Array.mapWithIndex((paragraph, index) => +

Int.toString}> {React.string(paragraph)}

+ ) + ->React.array} +
+ {React.string("Checkpoint")} + + {React.string( + if checkpointComplete { + "Checkpoint complete" + } else { + "Waiting for matching output" + }, + )} + +
diff --git a/apps/guide/app/GuideLayout.res b/apps/guide/app/GuideLayout.res index 31e80e8c1..b60225422 100644 --- a/apps/guide/app/GuideLayout.res +++ b/apps/guide/app/GuideLayout.res @@ -12,10 +12,12 @@ let minWorkspaceWidth = 480.0 let minEditorHeight = 240.0 let minOutputHeight = 160.0 let defaultOutputHeight = 220.0 -let themeStorageKey = "guideTheme" -let instructionsWidthStorageKey = "guidePaneInstructionsWidth" -let outputHeightStorageKey = "guidePaneOutputHeight" -let codeStorageKey = "guideCode" +let storageKey = key => `rescript-guide:v1:${key}` +let themeStorageKey = storageKey("theme") +let instructionsWidthStorageKey = storageKey("pane:instructionsWidth") +let outputHeightStorageKey = storageKey("pane:outputHeight") +let progressStorageKey = storageKey("progress") +let exerciseCodeStorageKey = exerciseId => storageKey(`exercise:${exerciseId}`) let defaultPaneSizes = { instructionsWidth: None, @@ -162,8 +164,59 @@ let clearPaneSizes = () => { removeLocalStorageItem(outputHeightStorageKey) } -let loadCode = () => getLocalStorageItem(codeStorageKey) +let parseCompletedExerciseIds = value => { + open JSON -let saveCode = code => setLocalStorageItem(~key=codeStorageKey, ~value=code) + try { + switch value->JSON.parseOrThrow { + | Object(dict{"completedExerciseIds": Array(ids)}) => + ids->Array.filterMap(id => + switch id { + | String(id) => Some(id) + | _ => None + } + ) + | _ => [] + } + } catch { + | JsExn(_) => [] + } +} + +let stringifyCompletedExerciseIds = completedExerciseIds => { + let dict = Dict.make() + dict->Dict.set( + "completedExerciseIds", + JSON.Array(completedExerciseIds->Array.map(id => JSON.String(id))), + ) + JSON.Object(dict)->JSON.stringify +} + +let loadCompletedExerciseIds = () => + getLocalStorageItem(progressStorageKey) + ->Option.map(parseCompletedExerciseIds) + ->Option.getOr([]) + +let saveCompletedExerciseIds = completedExerciseIds => + setLocalStorageItem( + ~key=progressStorageKey, + ~value=completedExerciseIds->stringifyCompletedExerciseIds, + ) + +let saveCompletedExercise = exerciseId => { + let completedExerciseIds = loadCompletedExerciseIds() + if !(completedExerciseIds->Array.includes(exerciseId)) { + saveCompletedExerciseIds(completedExerciseIds->Array.concat([exerciseId])) + } +} + +let isExerciseCompleted = exerciseId => loadCompletedExerciseIds()->Array.includes(exerciseId) + +let clearCompletedExercises = () => removeLocalStorageItem(progressStorageKey) + +let loadExerciseCode = exerciseId => getLocalStorageItem(exerciseId->exerciseCodeStorageKey) + +let saveExerciseCode = (~exerciseId, ~code) => + setLocalStorageItem(~key=exerciseId->exerciseCodeStorageKey, ~value=code) -let clearCode = () => removeLocalStorageItem(codeStorageKey) +let clearExerciseCode = exerciseId => removeLocalStorageItem(exerciseId->exerciseCodeStorageKey) diff --git a/apps/guide/app/GuideLesson.res b/apps/guide/app/GuideLesson.res new file mode 100644 index 000000000..823a50cce --- /dev/null +++ b/apps/guide/app/GuideLesson.res @@ -0,0 +1,48 @@ +type exerciseCheck = + | ExpectedOutput(string) + | Manual + +type exercise = { + id: string, + title: string, + initialCode: string, + check: exerciseCheck, +} + +type t = { + id: string, + missionLabel: string, + title: string, + description: string, + paragraphs: array, + exercise: exercise, +} + +let firstExercise = { + id: "first-contact/greeting", + title: "Send a greeting", + initialCode: `let greeting = "hello, world!"`, + check: ExpectedOutput("hello, world!"), +} + +let first = { + id: "first-contact", + missionLabel: "Mission 01", + title: "Learn ReScript Guide", + description: "Run a small ReScript program and inspect its output.", + paragraphs: [ + "This interactive guide introduces ReScript through small examples, steady practice, and a live output log.", + "The editor runs automatically. For this first checkpoint, the final value should print hello, world! in the output log.", + ], + exercise: firstExercise, +} + +let runtimeLogText = (runtimeLog: GuideCompilerFeedback.Output.runtimeLog) => + runtimeLog.content->Array.join(" ") + +let isExerciseComplete = (~exercise, ~output: GuideCompilerFeedback.Output.t) => + switch exercise.check { + | ExpectedOutput(expected) => + output.runtimeLogs->Array.some(runtimeLog => runtimeLog->runtimeLogText === expected) + | Manual => false + } diff --git a/apps/guide/styles/main.css b/apps/guide/styles/main.css index 30a64fb2d..e05f4654f 100644 --- a/apps/guide/styles/main.css +++ b/apps/guide/styles/main.css @@ -115,6 +115,31 @@ body { margin: 0 0 18px; } +.guide-check-status { + border-left: 3px solid var(--guide-border); + color: var(--guide-muted); + display: flex; + flex-direction: column; + font-size: 14px; + gap: 5px; + line-height: 1.35; + margin-top: 28px; + padding-left: 14px; +} + +.guide-check-status-complete { + border-color: #16a34a; + color: var(--guide-text); +} + +.guide-check-label { + color: var(--guide-muted); + font-size: 11px; + font-weight: 700; + letter-spacing: 0; + text-transform: uppercase; +} + .guide-kicker { color: var(--guide-muted); font-size: 12px; From 42d6c6d04cf4d35ebd7c1e91fff22cfb2ea34ffe Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 4 May 2026 11:30:49 -0400 Subject: [PATCH 20/32] feat: add second guide lesson Add a function-call lesson that asks learners to change greet("ReScript") to greet("Spock") and completes the checkpoint when the output is Hello, Spock!. --- apps/guide/__tests__/GuideHome_.test.res | 49 ++++++++++++++++++++++++ apps/guide/app/GuideHome.res | 35 +++++++++++++++-- apps/guide/app/GuideLesson.res | 27 +++++++++++++ 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index 5c08d092c..1021edad5 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -99,6 +99,32 @@ test("checks expected output for the first lesson exercise", async () => { )->toBe(false) }) +test("checks expected output for the function argument exercise", async () => { + let matchingOutput = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["Hello, Spock!"]}], + ) + let nonMatchingOutput = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["Hello, ReScript!"]}], + ) + + expect( + GuideLesson.second.exercise.initialCode, + )->toBe(`let greet = name => "Hello, " ++ name ++ "!" + +let greeting = greet("ReScript")`) + expect( + GuideLesson.isExerciseComplete(~exercise=GuideLesson.second.exercise, ~output=matchingOutput), + )->toBe(true) + expect( + GuideLesson.isExerciseComplete( + ~exercise=GuideLesson.second.exercise, + ~output=nonMatchingOutput, + ), + )->toBe(false) +}) + test("renders output diagnostics as error lines", async () => { let output = GuideCompilerFeedback.Output.make( ~status="Compiler error", @@ -357,6 +383,29 @@ test("shows the first checkpoint as complete when output matches", async () => { await checkpoint->element->toHaveTextContent("Checkpoint complete") }) +test("navigates to the function argument page", async () => { + await viewport(1440, 900) + GuideLayout.clearCompletedExercises() + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) + + let screen = await render() + let nextButton = await screen->getByText("Next") + + await nextButton->click + + await (await screen->getByText("Call A Function"))->element->toBeVisible + await (await screen->getByText("Change the argument passed to greet from ReScript to Spock.")) + ->element + ->toBeVisible + let editor = await screen->getByTestId("guide-code-editor") + await editor->element->toHaveTextContent(`let greet = name => "Hello, " ++ name ++ "!"`) + await editor->element->toHaveTextContent(`let greeting = greet("ReScript")`) + let checkpoint = await screen->getByTestId("guide-check-status") + await checkpoint->element->toHaveTextContent("Waiting for matching output") + + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) +}) + test("stretches the output surface to the full output panel", async () => { await viewport(1440, 900) diff --git a/apps/guide/app/GuideHome.res b/apps/guide/app/GuideHome.res index 2d38cf861..b6ca12fbc 100644 --- a/apps/guide/app/GuideHome.res +++ b/apps/guide/app/GuideHome.res @@ -57,7 +57,8 @@ let make = (~compilerData: option=?) => { let shellRef: React.ref> = React.useRef(Nullable.null) let editorContainerRef: React.ref> = React.useRef(Nullable.null) let editorRef: React.ref> = React.useRef(None) - let lesson = GuideLesson.first + let (lessonIndex, setLessonIndex) = React.useState(() => 0) + let lesson = GuideLesson.lessonAt(lessonIndex) let exercise = lesson.exercise let (code, setCode) = React.useState(() => exercise.initialCode) let (output, setOutput) = React.useState(() => GuideCompilerFeedback.Output.initial) @@ -144,6 +145,14 @@ let make = (~compilerData: option=?) => { dragTarget.current = target } + let hasNextLesson = GuideLesson.hasNextLesson(lessonIndex) + let goToNextLesson = _event => { + if hasNextLesson { + setLessonIndex(index => index + 1) + setOutput(_ => GuideCompilerFeedback.Output.make(~status="Output")) + } + } + let exercisePassed = GuideLesson.isExerciseComplete(~exercise, ~output) let checkpointComplete = exercisePassed || GuideLayout.isExerciseCompleted(exercise.id) @@ -179,10 +188,15 @@ let make = (~compilerData: option=?) => { } let editor = CodeMirror.createEditor(config) editorRef.current = Some(editor) - Some(() => CodeMirror.editorDestroy(editor)) + Some( + () => { + editorRef.current = None + CodeMirror.editorDestroy(editor) + }, + ) | Null | Undefined => None } - }, []) + }, (exercise.id, exercise.initialCode)) <>
@@ -234,7 +248,20 @@ let make = (~compilerData: option=?) => {
- +

"Hello, " ++ name ++ "!" + +let greeting = greet("ReScript")`, + check: ExpectedOutput("Hello, Spock!"), +} + +let second = { + id: "functions", + missionLabel: "Mission 02", + title: "Call A Function", + description: "Change a function call and inspect the result.", + paragraphs: [ + "Functions take values as input and return a new value.", + "Change the argument passed to greet from ReScript to Spock.", + ], + exercise: secondExercise, +} + +let all = [first, second] + +let lessonAt = index => all->Array.get(index)->Option.getOr(first) + +let hasNextLesson = index => index < all->Array.length - 1 + let runtimeLogText = (runtimeLog: GuideCompilerFeedback.Output.runtimeLog) => runtimeLog.content->Array.join(" ") From 7101509994b13b7e6d751d6dfa0af42a01d30b6d Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 4 May 2026 11:56:51 -0400 Subject: [PATCH 21/32] fix: support guide lesson back navigation Route guide lessons through React Router hash navigation so browser Back returns to the previous lesson. Disable playground URL syncing in the guide compiler bridge so compiler state does not overwrite lesson hashes. --- apps/guide/__tests__/GuideHome_.test.res | 59 ++++++++++++++++--- apps/guide/app/GuideCompilerBridge.res | 1 + apps/guide/app/GuideHome.res | 34 ++++++++++- apps/guide/app/GuideLesson.res | 16 +++++ apps/guide/app/GuideReactRouter.res | 18 ++++++ .../playground/src/CompilerManagerHook.res | 5 +- .../playground/src/CompilerManagerHook.resi | 1 + 7 files changed, 124 insertions(+), 10 deletions(-) diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index 1021edad5..0a38f62e0 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -1,5 +1,12 @@ open GuideVitest +let renderGuideHome = () => + render( + + + , + ) + test("selects the latest stable ReScript compiler with ESM output", async () => { let version = GuideCompilerSettings.latestStableVersion([ @@ -341,7 +348,7 @@ test("loads saved guide editor code into the editor", async () => { GuideLayout.clearExerciseCode(exerciseId) GuideLayout.saveExerciseCode(~exerciseId, ~code="let sisko = \"emissary\"") - let screen = await render() + let screen = await renderGuideHome() let editor = await screen->getByTestId("guide-code-editor") await editor->element->toHaveTextContent("let sisko = \"emissary\"") @@ -352,7 +359,7 @@ test("loads saved guide editor code into the editor", async () => { test("renders resize handles and toggles dark mode", async () => { await viewport(1440, 900) - let screen = await render() + let screen = await renderGuideHome() let shell = await screen->getByTestId("guide-mvp") let columnHandle = await screen->getByTestId("guide-column-resize") let rowHandle = await screen->getByTestId("guide-row-resize") @@ -368,7 +375,7 @@ test("renders resize handles and toggles dark mode", async () => { test("shows a minimum screen size message on narrow viewports", async () => { await viewport(800, 900) - let screen = await render() + let screen = await renderGuideHome() let message = await screen->getByText("This guide needs a wider screen.") await message->element->toBeVisible @@ -377,7 +384,7 @@ test("shows a minimum screen size message on narrow viewports", async () => { test("shows the first checkpoint as complete when output matches", async () => { await viewport(1440, 900) - let screen = await render() + let screen = await renderGuideHome() let checkpoint = await screen->getByTestId("guide-check-status") await checkpoint->element->toHaveTextContent("Checkpoint complete") @@ -388,7 +395,7 @@ test("navigates to the function argument page", async () => { GuideLayout.clearCompletedExercises() GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) - let screen = await render() + let screen = await renderGuideHome() let nextButton = await screen->getByText("Next") await nextButton->click @@ -406,10 +413,48 @@ test("navigates to the function argument page", async () => { GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) }) +let guideTestUrl = hash => window.location.pathname ++ window.location.search ++ hash + +let resetGuideTestUrl = () => + WebAPI.History.replaceState(window.history, ~data=JSON.Null, ~unused="", ~url=guideTestUrl("")) + +test("browser back returns to the previous guide lesson", async () => { + await viewport(1440, 900) + GuideLayout.clearCompletedExercises() + GuideLayout.clearExerciseCode(GuideLesson.first.exercise.id) + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) + WebAPI.History.replaceState( + window.history, + ~data=JSON.Null, + ~unused="", + ~url=guideTestUrl("#guide-test-start"), + ) + WebAPI.History.pushState( + window.history, + ~data=JSON.Null, + ~unused="", + ~url=guideTestUrl("#first-contact"), + ) + + let screen = await renderGuideHome() + + await (await screen->getByText("Learn ReScript Guide"))->element->toBeVisible + await (await screen->getByText("Next"))->click + await (await screen->getByText("Call A Function"))->element->toBeVisible + + WebAPI.History.back(window.history) + + await (await screen->getByText("Learn ReScript Guide"))->element->toBeVisible + + GuideLayout.clearExerciseCode(GuideLesson.first.exercise.id) + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) + resetGuideTestUrl() +}) + test("stretches the output surface to the full output panel", async () => { await viewport(1440, 900) - let screen = await render() + let screen = await renderGuideHome() let output = await screen->getByTestId("guide-output") await output->element->toHaveClass("guide-output-frame") @@ -418,7 +463,7 @@ test("stretches the output surface to the full output panel", async () => { test("renders the first guide MVP exercise and output", async () => { await viewport(1440, 900) - let screen = await render() + let screen = await renderGuideHome() await (await screen->getByText("Learn ReScript Guide"))->element->toBeVisible await ( diff --git a/apps/guide/app/GuideCompilerBridge.res b/apps/guide/app/GuideCompilerBridge.res index fdddeccb2..0c3308835 100644 --- a/apps/guide/app/GuideCompilerBridge.res +++ b/apps/guide/app/GuideCompilerBridge.res @@ -58,6 +58,7 @@ let make = ( ~initialVersion?, ~initialModuleSystem=GuideCompilerSettings.moduleSystem, ~initialWarnFlags=GuideCompilerSettings.warnFlags, + ~syncUrl=false, ~versions=compilerVersions, ) diff --git a/apps/guide/app/GuideHome.res b/apps/guide/app/GuideHome.res index b6ca12fbc..ea09d86ea 100644 --- a/apps/guide/app/GuideHome.res +++ b/apps/guide/app/GuideHome.res @@ -52,8 +52,19 @@ type dragTarget = | ResizingColumns | ResizingRows +let emptyOutput = () => GuideCompilerFeedback.Output.make(~status="Output") + +let outputForLessonIndex = index => + if index === 0 { + GuideCompilerFeedback.Output.initial + } else { + emptyOutput() + } + @react.component let make = (~compilerData: option=?) => { + let location = GuideReactRouter.useLocation() + let navigate = GuideReactRouter.useNavigate() let shellRef: React.ref> = React.useRef(Nullable.null) let editorContainerRef: React.ref> = React.useRef(Nullable.null) let editorRef: React.ref> = React.useRef(None) @@ -68,6 +79,22 @@ let make = (~compilerData: option=?) => { let (paneSizesLoaded, setPaneSizesLoaded) = React.useState(() => false) let dragTarget = React.useRef(NotDragging) + React.useEffect(() => { + let currentHash = window.location.hash + let nextLessonIndex = GuideLesson.indexForHash(currentHash) + let nextLesson = GuideLesson.lessonAt(nextLessonIndex) + let nextLessonHash = nextLesson->GuideLesson.hashForLesson + + setLessonIndex(_ => nextLessonIndex) + setOutput(_ => nextLessonIndex->outputForLessonIndex) + + if currentHash !== nextLessonHash { + navigate(nextLessonHash, ~options={replace: true}) + } + + None + }, (location.hash, navigate)) + React.useEffect(() => { setTheme(_ => GuideLayout.loadTheme()) setPaneSizes(_ => @@ -148,8 +175,11 @@ let make = (~compilerData: option=?) => { let hasNextLesson = GuideLesson.hasNextLesson(lessonIndex) let goToNextLesson = _event => { if hasNextLesson { - setLessonIndex(index => index + 1) - setOutput(_ => GuideCompilerFeedback.Output.make(~status="Output")) + let nextLessonIndex = lessonIndex + 1 + let nextLesson = GuideLesson.lessonAt(nextLessonIndex) + navigate(nextLesson->GuideLesson.hashForLesson) + setLessonIndex(_ => nextLessonIndex) + setOutput(_ => nextLessonIndex->outputForLessonIndex) } } diff --git a/apps/guide/app/GuideLesson.res b/apps/guide/app/GuideLesson.res index a9ce5375d..59ddabe96 100644 --- a/apps/guide/app/GuideLesson.res +++ b/apps/guide/app/GuideLesson.res @@ -62,6 +62,22 @@ let all = [first, second] let lessonAt = index => all->Array.get(index)->Option.getOr(first) +let hashForLesson = lesson => "#" ++ lesson.id + +let lessonIdFromHash = hash => + if hash->String.startsWith("#") { + hash->String.slice(~start=1) + } else { + hash + } + +let indexForId = lessonId => { + let index = all->Array.findIndex(lesson => lesson.id === lessonId) + index < 0 ? 0 : index +} + +let indexForHash = hash => hash->lessonIdFromHash->indexForId + let hasNextLesson = index => index < all->Array.length - 1 let runtimeLogText = (runtimeLog: GuideCompilerFeedback.Output.runtimeLog) => diff --git a/apps/guide/app/GuideReactRouter.res b/apps/guide/app/GuideReactRouter.res index 499851bf9..c3939e47c 100644 --- a/apps/guide/app/GuideReactRouter.res +++ b/apps/guide/app/GuideReactRouter.res @@ -23,9 +23,27 @@ module ScrollRestoration = { external make: unit => React.element = "ScrollRestoration" } +type location = { + hash: string, +} + +type navigateOptions = {replace?: bool} +type navigate = (string, ~options: navigateOptions=?) => unit + @module("react-router") external useLoaderData: unit => 'a = "useLoaderData" +@module("react-router") +external useLocation: unit => location = "useLocation" + +@module("react-router") +external useNavigate: unit => navigate = "useNavigate" + +module BrowserRouter = { + @module("react-router") @react.component + external make: (~children: React.element) => React.element = "BrowserRouter" +} + module Loader = { type loaderArgs = {request: WebAPI.FetchAPI.request} type t<'a> = loaderArgs => promise<'a> diff --git a/packages/playground/src/CompilerManagerHook.res b/packages/playground/src/CompilerManagerHook.res index c0c269ce0..e5b8fc4f2 100644 --- a/packages/playground/src/CompilerManagerHook.res +++ b/packages/playground/src/CompilerManagerHook.res @@ -249,6 +249,7 @@ let useCompilerManager = ( ~initialExperimentalFeatures=[], ~initialLang: Lang.t=Res, ~onAction: option unit>=?, + ~syncUrl=true, ~versions: array, ) => { let (state, setState) = React.useState(_ => Init) @@ -624,9 +625,10 @@ let useCompilerManager = ( : EvalIFrame.sendOutput(code, imports) setState(_ => Ready({...state, logs: [], validReactCode: entryPointExists})) | SetupFailed(_) => () - | Ready(ready) => + | Ready(ready) if syncUrl => let url = createUrl((pathname :> string), ready) WebAPI.History.replaceState(history, ~data=JSON.Null, ~unused="", ~url) + | Ready(_) => () } } @@ -643,6 +645,7 @@ let useCompilerManager = ( initialLang, versions, pathname, + syncUrl, )) (state, dispatch) diff --git a/packages/playground/src/CompilerManagerHook.resi b/packages/playground/src/CompilerManagerHook.resi index fec4dc63b..47c13ef66 100644 --- a/packages/playground/src/CompilerManagerHook.resi +++ b/packages/playground/src/CompilerManagerHook.resi @@ -69,6 +69,7 @@ let useCompilerManager: ( ~initialExperimentalFeatures: array=?, ~initialLang: Lang.t=?, ~onAction: action => unit=?, + ~syncUrl: bool=?, ~versions: array, ) => (state, action => unit) From e8cc5a40a5efcf40ea5a04547c0fff4e12ddde31 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 4 May 2026 13:43:40 -0400 Subject: [PATCH 22/32] fix: keep guide output during compilation Do not replace the result panel with intermediate compiler statuses for successful compiles. Keep the previous runtime result visible until the first log from a new run arrives, while still replacing the panel immediately for compiler errors. --- apps/guide/__tests__/GuideHome_.test.res | 27 ++++++++++++--- apps/guide/app/GuideCompilerBridge.res | 32 +++++++++++++----- apps/guide/app/GuideCompilerFeedback.res | 43 +++++++++++------------- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index 0a38f62e0..8af5b8406 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -67,15 +67,14 @@ test("maps compiler type hints into hover hints", async () => { expect(hoverHint.hint)->toBe("string") }) -test("does not include compiler type hints in successful output", async () => { +test("keeps the previous output while a successful compile waits for runtime logs", async () => { let typeHint = RescriptCompilerApi.TypeHint.Binding({ start: {line: 1, col: 4}, end: {line: 1, col: 12}, hint: "string", }) - let output = GuideCompilerFeedback.compilationResultToOutput( - ~compilerVersion="12.2.0", + let outputUpdate = GuideCompilerFeedback.compilationResultToOutputUpdate( Success({ jsCode: "let value = 52;", warnings: [], @@ -84,7 +83,27 @@ test("does not include compiler type hints in successful output", async () => { }), ) - expect(output.typeHints->Array.length)->toBe(0) + expect(outputUpdate->Option.isNone)->toBe(true) +}) + +test("shows compiler errors as output updates", async () => { + let locMsg: RescriptCompilerApi.LocMsg.t = { + fullMsg: "Full compiler error", + shortMsg: "Expected a string", + row: 2, + column: 7, + endRow: 2, + endColumn: 12, + } + + switch GuideCompilerFeedback.compilationResultToOutputUpdate(Fail(TypecheckErr([locMsg]))) { + | Some(output) => + expect(output.status)->toBe("Compiler error") + expect(output.diagnostics->Array.get(0)->Option.getOr(""))->toBe( + "[E] Line 2, 7: Expected a string", + ) + | None => expect("compiler error update")->toBe("an output update") + } }) test("checks expected output for the first lesson exercise", async () => { diff --git a/apps/guide/app/GuideCompilerBridge.res b/apps/guide/app/GuideCompilerBridge.res index 0c3308835..15d6e5114 100644 --- a/apps/guide/app/GuideCompilerBridge.res +++ b/apps/guide/app/GuideCompilerBridge.res @@ -64,6 +64,7 @@ let make = ( let lastCompiledCode = React.useRef("") let lastExecutedJsCode = React.useRef("") + let isWaitingForRuntimeOutput = React.useRef(false) React.useEffect(() => { switch compilerState { @@ -80,13 +81,15 @@ let make = ( React.useEffect(() => { let cb = event => { let data = event["data"] - let appendLog = (level, content) => - setOutput(output => - output->GuideCompilerFeedback.Output.withRuntimeLog({ - level, - content, - }) - ) + let appendLog = (level, content) => { + let runtimeLog = {GuideCompilerFeedback.Output.level, content} + if isWaitingForRuntimeOutput.current { + isWaitingForRuntimeOutput.current = false + setOutput(_ => runtimeLog->GuideCompilerFeedback.Output.fromRuntimeLog) + } else { + setOutput(output => output->GuideCompilerFeedback.Output.withRuntimeLog(runtimeLog)) + } + } switch data["type"] { | #log => appendLog(#log, data["args"]) @@ -105,7 +108,12 @@ let make = ( CodeMirror.editorSetErrors(editor, feedback.errors) CodeMirror.editorSetHoverHints(editor, feedback.hoverHints) }) - setOutput(_ => compilerState->GuideCompilerFeedback.outputFromState) + switch compilerState->GuideCompilerFeedback.outputUpdateFromState { + | Some(output) => + isWaitingForRuntimeOutput.current = false + setOutput(_ => output) + | None => () + } None }, (compilerState, setOutput)) @@ -127,6 +135,7 @@ let make = ( ~resultBindingName=GuideRuntimeSource.resultBindingName, ) { | Some({code: runtimeCode, imports}) => + isWaitingForRuntimeOutput.current = true let imports = imports->Dict.mapValues(path => path->runtimeImportUrl(~bundleBaseUrl, ~compilerVersion=selected.id) @@ -135,7 +144,12 @@ let make = ( ~handler=() => EvalIFrame.sendOutput(runtimeCode, imports), ~timeout=50, ) - Some(() => clearTimeout(timer)) + Some( + () => { + isWaitingForRuntimeOutput.current = false + clearTimeout(timer) + }, + ) | None => None } | Compiling(_) => diff --git a/apps/guide/app/GuideCompilerFeedback.res b/apps/guide/app/GuideCompilerFeedback.res index 215d436ea..2fd329729 100644 --- a/apps/guide/app/GuideCompilerFeedback.res +++ b/apps/guide/app/GuideCompilerFeedback.res @@ -29,6 +29,8 @@ module Output = { status: "Output", runtimeLogs: output.runtimeLogs->Array.concat([runtimeLog]), } + + let fromRuntimeLog = runtimeLog => make(~status="Output", ~runtimeLogs=[runtimeLog]) } type editorFeedback = { @@ -117,18 +119,14 @@ let compilationResultToEditorFeedback = (result: Api.CompilationResult.t) => | UnexpectedError(_) | Unknown(_, _) => emptyEditorFeedback } -let compilationResultToOutput = (~compilerVersion, result: Api.CompilationResult.t) => +let compilationResultToOutputUpdate = (result: Api.CompilationResult.t) => switch result { - | Success({warnings}) => - Output.make( - ~status=`Compiled with ReScript ${compilerVersion}`, - ~diagnostics=warnings->Array.map(warning => - warning->Api.Warning.toCompactErrorLine->plainText - ), - ) - | Fail(fail) => Output.make(~status="Compiler error", ~diagnostics=fail->compileFailToOutputLines) - | UnexpectedError(message) => Output.make(~status="Compiler error", ~diagnostics=[message]) - | Unknown(message, _) => Output.make(~status="Unknown compiler result", ~diagnostics=[message]) + | Success(_) => None + | Fail(fail) => + Some(Output.make(~status="Compiler error", ~diagnostics=fail->compileFailToOutputLines)) + | UnexpectedError(message) => Some(Output.make(~status="Compiler error", ~diagnostics=[message])) + | Unknown(message, _) => + Some(Output.make(~status="Unknown compiler result", ~diagnostics=[message])) } let editorFeedbackFromState = (state: CompilerManagerHook.state) => @@ -138,18 +136,15 @@ let editorFeedbackFromState = (state: CompilerManagerHook.state) => | _ => emptyEditorFeedback } -let outputFromState = (state: CompilerManagerHook.state) => +let outputUpdateFromState = (state: CompilerManagerHook.state) => switch state { - | Init => Output.make(~status="Loading compiler...") - | SetupFailed(message) => Output.make(~status="Compiler setup failed", ~diagnostics=[message]) - | SwitchingCompiler(_, version) => - Output.make(~status=`Loading ReScript ${version->Semver.toString}...`) - | Compiling(_) => Output.make(~status="Compiling...") - | Executing(_) => Output.make(~status="Compiled") - | Ready({selected, result}) => - switch result { - | Nothing => Output.make(~status=`Compiler ready (${selected.compilerVersion})`) - | Comp(result) => result->compilationResultToOutput(~compilerVersion=selected.compilerVersion) - | Conv(_) => Output.make(~status="Compiler ready") - } + | SetupFailed(message) => + Some(Output.make(~status="Compiler setup failed", ~diagnostics=[message])) + | Ready({result: Comp(result)}) => result->compilationResultToOutputUpdate + | Init + | SwitchingCompiler(_, _) + | Compiling(_) + | Executing(_) + | Ready({result: Nothing | Conv(_)}) => + None } From a511db20bde2bf10403a9f081ca28fb07615ec3c Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 4 May 2026 13:54:45 -0400 Subject: [PATCH 23/32] feat: add guide lesson back button Render a Back action before the forward lesson action and route it through the same hash-based lesson navigation. Keep the first lesson Back action disabled and cover the placement plus backward navigation in the guide browser tests. --- apps/guide/__tests__/GuideHome_.test.res | 34 +++++++++++++++++ apps/guide/app/GuideHome.res | 48 +++++++++++++++++------- apps/guide/app/GuideLesson.res | 2 + apps/guide/styles/main.css | 22 ++++++++++- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index 8af5b8406..5ffe309ce 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -437,6 +437,40 @@ let guideTestUrl = hash => window.location.pathname ++ window.location.search ++ let resetGuideTestUrl = () => WebAPI.History.replaceState(window.history, ~data=JSON.Null, ~unused="", ~url=guideTestUrl("")) +test("shows Back before lesson forward actions and returns to the previous lesson", async () => { + await viewport(1440, 900) + GuideLayout.clearCompletedExercises() + GuideLayout.clearExerciseCode(GuideLesson.first.exercise.id) + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) + WebAPI.History.replaceState( + window.history, + ~data=JSON.Null, + ~unused="", + ~url=guideTestUrl("#first-contact"), + ) + + let screen = await renderGuideHome() + let firstLessonText = screen->container->textContent->Nullable.toOption->Option.getOr("") + let beforeNext = firstLessonText->String.split("Next")->Array.get(0)->Option.getOr("") + + expect(beforeNext->String.includes("Back"))->toBe(true) + await (await screen->getByText("Back"))->element->toBeVisible + await (await screen->getByText("Next"))->click + + await (await screen->getByText("Call A Function"))->element->toBeVisible + + let secondLessonText = screen->container->textContent->Nullable.toOption->Option.getOr("") + let beforeDone = secondLessonText->String.split("Done")->Array.get(0)->Option.getOr("") + + expect(beforeDone->String.includes("Back"))->toBe(true) + await (await screen->getByText("Back"))->click + await (await screen->getByText("Learn ReScript Guide"))->element->toBeVisible + + GuideLayout.clearExerciseCode(GuideLesson.first.exercise.id) + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) + resetGuideTestUrl() +}) + test("browser back returns to the previous guide lesson", async () => { await viewport(1440, 900) GuideLayout.clearCompletedExercises() diff --git a/apps/guide/app/GuideHome.res b/apps/guide/app/GuideHome.res index ea09d86ea..56559e85f 100644 --- a/apps/guide/app/GuideHome.res +++ b/apps/guide/app/GuideHome.res @@ -172,7 +172,17 @@ let make = (~compilerData: option=?) => { dragTarget.current = target } + let hasPreviousLesson = GuideLesson.hasPreviousLesson(lessonIndex) let hasNextLesson = GuideLesson.hasNextLesson(lessonIndex) + let goToPreviousLesson = _event => { + if hasPreviousLesson { + let previousLessonIndex = lessonIndex - 1 + let previousLesson = GuideLesson.lessonAt(previousLessonIndex) + navigate(previousLesson->GuideLesson.hashForLesson) + setLessonIndex(_ => previousLessonIndex) + setOutput(_ => previousLessonIndex->outputForLessonIndex) + } + } let goToNextLesson = _event => { if hasNextLesson { let nextLessonIndex = lessonIndex + 1 @@ -278,20 +288,30 @@ let make = (~compilerData: option=?) => {
- +
+ + +
{ let indexForHash = hash => hash->lessonIdFromHash->indexForId +let hasPreviousLesson = index => index > 0 + let hasNextLesson = index => index < all->Array.length - 1 let runtimeLogText = (runtimeLog: GuideCompilerFeedback.Output.runtimeLog) => diff --git a/apps/guide/styles/main.css b/apps/guide/styles/main.css index e05f4654f..c5aabace2 100644 --- a/apps/guide/styles/main.css +++ b/apps/guide/styles/main.css @@ -173,8 +173,14 @@ body { border-color: var(--guide-resize-hover-bg); } -.guide-next-button { +.guide-lesson-actions { align-self: flex-start; + display: flex; + gap: 12px; +} + +.guide-back-button, +.guide-next-button { background: var(--guide-button-bg); border: 1px solid var(--guide-button-border); color: var(--guide-button-text); @@ -186,11 +192,23 @@ body { padding: 12px 18px; } -.guide-next-button:hover { +.guide-back-button { + background: transparent; + color: var(--guide-text); +} + +.guide-back-button:hover:not(:disabled), +.guide-next-button:hover:not(:disabled) { background: var(--guide-button-hover-bg); border-color: var(--guide-button-hover-border); } +.guide-back-button:disabled, +.guide-next-button:disabled { + cursor: not-allowed; + opacity: 0.5; +} + .guide-resize-handle { background: var(--guide-resize-bg); min-height: 0; From 9636370d4807f1dcb40be2a11f47c052d522d308 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Mon, 4 May 2026 14:43:55 -0400 Subject: [PATCH 24/32] fix: enable completed guide done action Enable the final guide Done action once the checkpoint is complete and route it to the ReScript manual introduction. Cover the completed final lesson state and docs navigation target in guide browser tests. --- apps/guide/__tests__/GuideHome_.test.res | 53 ++++++++++++++++++++++++ apps/guide/app/GuideHome.res | 16 ++++--- apps/guide/src/bindings/GuideVitest.res | 6 +++ 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index 5ffe309ce..3c8e3caf2 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -7,6 +7,13 @@ let renderGuideHome = () => , ) +let renderGuideHomeWithDocsIntroNavigation = goToDocsIntro => + render( + + + , + ) + test("selects the latest stable ReScript compiler with ESM output", async () => { let version = GuideCompilerSettings.latestStableVersion([ @@ -471,6 +478,52 @@ test("shows Back before lesson forward actions and returns to the previous lesso resetGuideTestUrl() }) +test("enables Done on a completed final lesson", async () => { + await viewport(1440, 900) + GuideLayout.clearCompletedExercises() + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) + GuideLayout.saveCompletedExercise(GuideLesson.second.exercise.id) + WebAPI.History.replaceState( + window.history, + ~data=JSON.Null, + ~unused="", + ~url=guideTestUrl("#functions"), + ) + + let screen = await renderGuideHome() + let doneButton = await screen->getByText("Done") + + await doneButton->element->notToBeDisabled + + GuideLayout.clearCompletedExercises() + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) + resetGuideTestUrl() +}) + +test("Done on a completed final lesson opens the ReScript docs intro", async () => { + await viewport(1440, 900) + GuideLayout.clearCompletedExercises() + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) + GuideLayout.saveCompletedExercise(GuideLesson.second.exercise.id) + WebAPI.History.replaceState( + window.history, + ~data=JSON.Null, + ~unused="", + ~url=guideTestUrl("#functions"), + ) + let openedUrl = ref("") + + let screen = await renderGuideHomeWithDocsIntroNavigation(url => openedUrl.contents = url) + + await (await screen->getByText("Done"))->click + + expect(openedUrl.contents)->toBe(GuideHome.docsIntroUrl) + + GuideLayout.clearCompletedExercises() + GuideLayout.clearExerciseCode(GuideLesson.second.exercise.id) + resetGuideTestUrl() +}) + test("browser back returns to the previous guide lesson", async () => { await viewport(1440, 900) GuideLayout.clearCompletedExercises() diff --git a/apps/guide/app/GuideHome.res b/apps/guide/app/GuideHome.res index 56559e85f..919e178f9 100644 --- a/apps/guide/app/GuideHome.res +++ b/apps/guide/app/GuideHome.res @@ -61,8 +61,12 @@ let outputForLessonIndex = index => emptyOutput() } +let docsIntroUrl = "https://rescript-lang.org/docs/manual/introduction" + +let navigateToDocsIntro = url => window.location->WebAPI.Location.assign(url) + @react.component -let make = (~compilerData: option=?) => { +let make = (~compilerData: option=?, ~goToDocsIntro=navigateToDocsIntro) => { let location = GuideReactRouter.useLocation() let navigate = GuideReactRouter.useNavigate() let shellRef: React.ref> = React.useRef(Nullable.null) @@ -174,6 +178,9 @@ let make = (~compilerData: option=?) => { let hasPreviousLesson = GuideLesson.hasPreviousLesson(lessonIndex) let hasNextLesson = GuideLesson.hasNextLesson(lessonIndex) + let exercisePassed = GuideLesson.isExerciseComplete(~exercise, ~output) + let checkpointComplete = exercisePassed || GuideLayout.isExerciseCompleted(exercise.id) + let forwardActionEnabled = hasNextLesson || checkpointComplete let goToPreviousLesson = _event => { if hasPreviousLesson { let previousLessonIndex = lessonIndex - 1 @@ -190,12 +197,11 @@ let make = (~compilerData: option=?) => { navigate(nextLesson->GuideLesson.hashForLesson) setLessonIndex(_ => nextLessonIndex) setOutput(_ => nextLessonIndex->outputForLessonIndex) + } else if checkpointComplete { + goToDocsIntro(docsIntroUrl) } } - let exercisePassed = GuideLesson.isExerciseComplete(~exercise, ~output) - let checkpointComplete = exercisePassed || GuideLayout.isExerciseCompleted(exercise.id) - React.useEffect(() => { if exercisePassed { GuideLayout.saveCompletedExercise(exercise.id) @@ -299,7 +305,7 @@ let make = (~compilerData: option=?) => {

{React.string(lesson.title)}

- {lesson.paragraphs - ->Array.mapWithIndex((paragraph, index) => -

Int.toString}> {React.string(paragraph)}

- ) - ->React.array} + lesson.content
> = async _ => - await GuideCompilerData.load() +type loaderData = { + compilerData: option, + lessons: array, +} + +let loader: GuideReactRouter.Loader.t = async _ => { + let compilerData = await GuideCompilerData.load() + let lessons = GuideLessonContent.load() + + {compilerData, lessons} +} @react.component let default = () => { - let compilerData = GuideReactRouter.useLoaderData() - + let {compilerData, lessons}: loaderData = GuideReactRouter.useLoaderData() + } diff --git a/apps/guide/app/GuideHomeRoute.resi b/apps/guide/app/GuideHomeRoute.resi index 9ea791fae..8d5641473 100644 --- a/apps/guide/app/GuideHomeRoute.resi +++ b/apps/guide/app/GuideHomeRoute.resi @@ -1,4 +1,9 @@ -let loader: GuideReactRouter.Loader.t> +type loaderData = { + compilerData: option, + lessons: array, +} + +let loader: GuideReactRouter.Loader.t @react.component let default: unit => React.element diff --git a/apps/guide/app/GuideLesson.res b/apps/guide/app/GuideLesson.res index 1f40a8b25..e6630824e 100644 --- a/apps/guide/app/GuideLesson.res +++ b/apps/guide/app/GuideLesson.res @@ -11,58 +11,28 @@ type exercise = { type t = { id: string, + position: int, + sourcePath: string, missionLabel: string, title: string, description: string, - paragraphs: array, + content: string, exercise: exercise, } -let firstExercise = { - id: "first-contact/greeting", - title: "Send a greeting", - initialCode: `let greeting = "hello, world!"`, - check: ExpectedOutput("hello, world!"), -} - -let first = { - id: "first-contact", - missionLabel: "Mission 01", - title: "Learn ReScript Guide", - description: "Run a small ReScript program and inspect its output.", - paragraphs: [ - "This interactive guide introduces ReScript through small examples, steady practice, and a live output log.", - "The editor runs automatically. For this first checkpoint, the final value should print hello, world! in the output log.", - ], - exercise: firstExercise, -} - -let secondExercise = { - id: "functions/greet-spock", - title: "Greet Spock", - initialCode: `let greet = name => "Hello, " ++ name ++ "!" - -let greeting = greet("ReScript")`, - check: ExpectedOutput("Hello, Spock!"), -} - -let second = { - id: "functions", - missionLabel: "Mission 02", - title: "Call A Function", - description: "Change a function call and inspect the result.", - paragraphs: [ - "Functions take values as input and return a new value.", - "Change the argument passed to greet from ReScript to Spock.", - ], - exercise: secondExercise, -} +let sort = lessons => + lessons->Array.toSorted((a, b) => + switch Int.compare(a.position, b.position) { + | 0. => String.compare(a.id, b.id) + | result => result + } + ) -let all = [first, second] +let firstLesson = lessons => lessons->Array.get(0)->Option.getOrThrow -let lessonAt = index => all->Array.get(index)->Option.getOr(first) +let lessonAt = (~lessons, index) => lessons->Array.get(index)->Option.getOr(lessons->firstLesson) -let hashForLesson = lesson => "#" ++ lesson.id +let hashForLesson = (lesson: t) => "#" ++ lesson.id let lessonIdFromHash = hash => if hash->String.startsWith("#") { @@ -71,16 +41,16 @@ let lessonIdFromHash = hash => hash } -let indexForId = lessonId => { - let index = all->Array.findIndex(lesson => lesson.id === lessonId) +let indexForId = (~lessons, lessonId) => { + let index = lessons->Array.findIndex(lesson => lesson.id === lessonId) index < 0 ? 0 : index } -let indexForHash = hash => hash->lessonIdFromHash->indexForId +let indexForHash = (~lessons, hash) => hash->lessonIdFromHash->indexForId(~lessons) let hasPreviousLesson = index => index > 0 -let hasNextLesson = index => index < all->Array.length - 1 +let hasNextLesson = (~lessons, index) => index < lessons->Array.length - 1 let runtimeLogText = (runtimeLog: GuideCompilerFeedback.Output.runtimeLog) => runtimeLog.content->Array.join(" ") diff --git a/apps/guide/app/GuideLessonContent.res b/apps/guide/app/GuideLessonContent.res new file mode 100644 index 000000000..ebc7ed7b4 --- /dev/null +++ b/apps/guide/app/GuideLessonContent.res @@ -0,0 +1,83 @@ +let fail = message => JsExn.throw(Error(message)) + +let fieldLabel = (~sourcePath, ~key) => `Guide lesson ${sourcePath} frontmatter "${key}"` + +let readString = (~dict, ~sourcePath, ~key) => + switch dict->Dict.get(key) { + | Some(JSON.String(value)) if value->String.trim !== "" => value + | _ => fail(`${fieldLabel(~sourcePath, ~key)} must be a non-empty string.`) + } + +let readOptionalString = (~dict, ~sourcePath, ~key) => + switch dict->Dict.get(key) { + | Some(JSON.String(value)) => Some(value) + | Some(_) => fail(`${fieldLabel(~sourcePath, ~key)} must be a string when present.`) + | None => None + } + +let readInt = (~dict, ~sourcePath, ~key) => + switch dict->Dict.get(key) { + | Some(JSON.Number(value)) => value->Float.toInt + | _ => fail(`${fieldLabel(~sourcePath, ~key)} must be a number.`) + } + +let readObject = (~dict, ~sourcePath, ~key) => + switch dict->Dict.get(key) { + | Some(JSON.Object(value)) => value + | _ => fail(`${fieldLabel(~sourcePath, ~key)} must be an object.`) + } + +let frontmatterObject = (~frontmatter, ~sourcePath) => + switch frontmatter { + | JSON.Object(dict) => dict + | _ => fail(`Guide lesson ${sourcePath} must use object frontmatter.`) + } + +let exerciseFromFrontmatter = (~dict, ~sourcePath): GuideLesson.exercise => { + let check = switch readOptionalString(~dict, ~sourcePath, ~key="expectedOutput") { + | Some(expectedOutput) => GuideLesson.ExpectedOutput(expectedOutput) + | None => GuideLesson.Manual + } + + { + id: readString(~dict, ~sourcePath, ~key="id"), + title: readString(~dict, ~sourcePath, ~key="title"), + initialCode: readString(~dict, ~sourcePath, ~key="initialCode")->String.trimEnd, + check, + } +} + +let lessonFromFile = sourcePath => { + let raw = GuideNode.Fs.readFileSync(sourcePath) + let {frontmatter, content}: GuideMarkdownParser.result = GuideMarkdownParser.parseSync(raw) + let dict = frontmatterObject(~frontmatter, ~sourcePath) + let exerciseDict = readObject(~dict, ~sourcePath, ~key="exercise") + + { + GuideLesson.id: readString(~dict, ~sourcePath, ~key="id"), + position: readInt(~dict, ~sourcePath, ~key="position"), + sourcePath, + missionLabel: readString(~dict, ~sourcePath, ~key="missionLabel"), + title: readString(~dict, ~sourcePath, ~key="title"), + description: readString(~dict, ~sourcePath, ~key="description"), + content: content->String.trim, + exercise: exerciseFromFrontmatter(~dict=exerciseDict, ~sourcePath), + } +} + +let rec scanDir = currentDir => + GuideNode.Fs.readdirSync(currentDir)->Array.flatMap(entry => { + let fullPath = GuideNode.Path.join2(currentDir, entry) + + if GuideNode.Fs.statSync(fullPath)["isDirectory"]() { + scanDir(fullPath) + } else if GuideNode.Path.extname(entry) === ".mdx" { + [fullPath] + } else { + [] + } + }) + +let lessonsDir = () => GuideNode.Path.join2(GuideNode.Process.cwd(), "app/lessons") + +let load = (~dir=lessonsDir()) => scanDir(dir)->Array.map(lessonFromFile)->GuideLesson.sort diff --git a/apps/guide/app/GuideMarkdown.res b/apps/guide/app/GuideMarkdown.res new file mode 100644 index 000000000..39e148af4 --- /dev/null +++ b/apps/guide/app/GuideMarkdown.res @@ -0,0 +1,2 @@ +@module("react-markdown") @react.component +external make: (~children: string) => React.element = "default" diff --git a/apps/guide/app/GuideMarkdownParser.res b/apps/guide/app/GuideMarkdownParser.res new file mode 100644 index 000000000..1974c75a5 --- /dev/null +++ b/apps/guide/app/GuideMarkdownParser.res @@ -0,0 +1,55 @@ +type t +type plugin +type vfile<'a> = {..} as 'a + +external makePlugin: 'a => plugin = "%identity" + +@module("unified") external make: unit => t = "unified" + +@module("remark-parse") external remarkParse: plugin = "default" +@module("remark-gfm") external remarkGfm: plugin = "default" +@module("remark-comment") external remarkComment: plugin = "default" +@module("remark-frontmatter") external remarkFrontmatter: plugin = "default" +@module("remark-stringify") external remarkStringify: plugin = "default" + +@send external use: (t, plugin) => t = "use" +@send external useOptions: (t, plugin, array<{..}>) => t = "use" + +@send external processSync: (t, string) => vfile<'a> = "processSync" +@send external toString: vfile<'a> => string = "toString" + +@module("vfile-matter") external vfileMatter: vfile<'a> => unit = "matter" + +type result = { + frontmatter: JSON.t, + content: string, +} + +let vfileMatterPlugin = makePlugin(_options => (_tree, vfile) => vfileMatter(vfile)) + +type remarkNode = {@as("type") type_: string} +type remarkTree = {mutable children: array} + +let stripFrontmatterPlugin = makePlugin(_options => + (tree, _vfile) => { + tree.children = tree.children->Array.filter(node => node.type_ !== "yaml") + } +) + +let parser = + make() + ->use(remarkParse) + ->use(remarkStringify) + ->use(remarkGfm) + ->use(remarkComment) + ->useOptions(remarkFrontmatter, [{"type": "yaml", "marker": "-"}]) + ->use(vfileMatterPlugin) + ->use(stripFrontmatterPlugin) + +let parseSync = content => { + let vfile = parser->processSync(content) + let frontmatter = (vfile["data"]["matter"] :> option) + let frontmatter = frontmatter->Option.getOr(JSON.Object(Dict.make())) + let content = vfile->toString + {frontmatter, content} +} diff --git a/apps/guide/app/GuideNode.res b/apps/guide/app/GuideNode.res new file mode 100644 index 000000000..69e3272d4 --- /dev/null +++ b/apps/guide/app/GuideNode.res @@ -0,0 +1,14 @@ +module Path = { + @module("path") external join2: (string, string) => string = "join" + @module("path") external extname: string => string = "extname" +} + +module Process = { + @scope("process") external cwd: unit => string = "cwd" +} + +module Fs = { + @module("fs") external readFileSync: string => string = "readFileSync" + @module("fs") external readdirSync: string => array = "readdirSync" + @module("fs") external statSync: string => {.."isDirectory": unit => bool} = "statSync" +} diff --git a/apps/guide/app/lessons/01-first-contact.mdx b/apps/guide/app/lessons/01-first-contact.mdx new file mode 100644 index 000000000..3f85329e9 --- /dev/null +++ b/apps/guide/app/lessons/01-first-contact.mdx @@ -0,0 +1,17 @@ +--- +position: 1 +id: first-contact +missionLabel: Mission 01 +title: Learn ReScript Guide +description: Run a small ReScript program and inspect its output. +exercise: + id: first-contact/greeting + title: Send a greeting + initialCode: | + let greeting = "hello, world!" + expectedOutput: hello, world! +--- + +This interactive guide introduces ReScript through small examples, steady practice, and a live output log. + +The editor runs automatically. For this first checkpoint, the final value should print `hello, world!` in the output log. diff --git a/apps/guide/app/lessons/02-functions.mdx b/apps/guide/app/lessons/02-functions.mdx new file mode 100644 index 000000000..46504a93a --- /dev/null +++ b/apps/guide/app/lessons/02-functions.mdx @@ -0,0 +1,19 @@ +--- +position: 2 +id: functions +missionLabel: Mission 02 +title: Call A Function +description: Change a function call and inspect the result. +exercise: + id: functions/greet-spock + title: Greet Spock + initialCode: | + let greet = name => "Hello, " ++ name ++ "!" + + let greeting = greet("ReScript") + expectedOutput: Hello, Spock! +--- + +Functions take values as input and return a new value. + +Change the argument passed to `greet` from `ReScript` to `Spock`. diff --git a/apps/guide/package.json b/apps/guide/package.json index 070ab446c..45456a783 100644 --- a/apps/guide/package.json +++ b/apps/guide/package.json @@ -24,9 +24,17 @@ "isbot": "^5", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-markdown": "^10.1.0", "react-router": "^7.14.0", "react-router-dom": "^7.14.0", - "rescript": "^12.2.0" + "remark-comment": "^1.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "rescript": "^12.2.0", + "unified": "^11.0.5", + "vfile-matter": "^5.0.1" }, "devDependencies": { "@react-router/dev": "^7.14.0", diff --git a/apps/guide/vite.config.mjs b/apps/guide/vite.config.mjs index f54f1a5da..1cc83fd1c 100644 --- a/apps/guide/vite.config.mjs +++ b/apps/guide/vite.config.mjs @@ -19,8 +19,10 @@ const sharedEditorDeps = [ "@lezer/highlight", "@replit/codemirror-vim", "@rescript/runtime/lib/es6/Belt_Array.js", + "@rescript/runtime/lib/es6/Primitive_int.js", "@rescript/runtime/lib/es6/Primitive_object.js", "@rescript/runtime/lib/es6/Primitive_option.js", + "@rescript/runtime/lib/es6/Primitive_string.js", "@rescript/runtime/lib/es6/Stdlib_Array.js", "@rescript/runtime/lib/es6/Stdlib_Dict.js", "@rescript/runtime/lib/es6/Stdlib_Int.js", @@ -29,7 +31,9 @@ const sharedEditorDeps = [ "@rescript/runtime/lib/es6/Stdlib_Option.js", "@tsnobip/rescript-lezer", "lz-string", + "react-markdown", "react-router", + "vfile-matter", ]; export default defineConfig({ diff --git a/yarn.lock b/yarn.lock index 344b8930e..1b22e74fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2473,9 +2473,17 @@ __metadata: playwright: "npm:^1.59.1" react: "npm:^19.2.4" react-dom: "npm:^19.2.4" + react-markdown: "npm:^10.1.0" react-router: "npm:^7.14.0" react-router-dom: "npm:^7.14.0" + remark-comment: "npm:^1.0.0" + remark-frontmatter: "npm:^5.0.0" + remark-gfm: "npm:^4.0.1" + remark-parse: "npm:^11.0.0" + remark-stringify: "npm:^11.0.0" rescript: "npm:^12.2.0" + unified: "npm:^11.0.5" + vfile-matter: "npm:^5.0.1" vite: "npm:^8.0.3" vite-plugin-env-compatible: "npm:^2.0.1" vitest: "npm:^4.1.2" From 078657d3785d4c663e9bff447d20e37cf950f897 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Tue, 5 May 2026 18:00:24 -0400 Subject: [PATCH 26/32] fix: address guide review feedback Move duplicated guide and playground bindings into shared modules, update guide tests to use MemoryRouter and unsafe test accessors, and replace guide-only helpers with shared ReScript APIs. Also routes DocSearch navigation through useNavigate so the shared React Router binding no longer points at a missing react-router-dom export. --- apps/docs/src/components/Search.res | 68 +++++---- apps/guide/__tests__/GuideHome_.test.res | 133 ++++++++---------- apps/guide/app/GuideCompilerSettings.res | 2 - apps/guide/app/GuideHome.res | 6 +- apps/guide/app/GuideHomeRoute.res | 4 +- apps/guide/app/GuideHomeRoute.resi | 2 +- apps/guide/app/GuideLayout.res | 19 +-- apps/guide/app/GuideLessonContent.res | 14 +- apps/guide/app/GuideMarkdownParser.res | 55 -------- apps/guide/app/GuideNode.res | 14 -- apps/guide/app/GuideReactRouter.res | 50 ------- apps/guide/app/GuideRoot.res | 10 +- apps/guide/src/bindings/GuideVitest.res | 56 -------- .../playground/src/CompilerManagerHook.res | 2 +- packages/playground/src/Playground.res | 2 +- .../playground/src/PlaygroundReactRouter.res | 11 -- packages/shared/package.json | 9 +- .../shared/src}/MarkdownParser.res | 0 .../shared/src}/MarkdownParser.resi | 0 .../bindings => packages/shared/src}/Node.res | 0 .../common => packages/shared/src}/Path.res | 0 .../shared/src}/ReactRouter.res | 6 +- .../shared/src}/Vitest.res | 21 +++ yarn.lock | 7 + 24 files changed, 160 insertions(+), 331 deletions(-) delete mode 100644 apps/guide/app/GuideMarkdownParser.res delete mode 100644 apps/guide/app/GuideNode.res delete mode 100644 apps/guide/app/GuideReactRouter.res delete mode 100644 apps/guide/src/bindings/GuideVitest.res delete mode 100644 packages/playground/src/PlaygroundReactRouter.res rename {apps/docs/src/markdown => packages/shared/src}/MarkdownParser.res (100%) rename {apps/docs/src/markdown => packages/shared/src}/MarkdownParser.resi (100%) rename {apps/docs/src/bindings => packages/shared/src}/Node.res (100%) rename {apps/docs/src/common => packages/shared/src}/Path.res (100%) rename {apps/docs/src/bindings => packages/shared/src}/ReactRouter.res (95%) rename {apps/docs/src/bindings => packages/shared/src}/Vitest.res (81%) diff --git a/apps/docs/src/components/Search.res b/apps/docs/src/components/Search.res index 42c5e7c41..4a0b213f1 100644 --- a/apps/docs/src/components/Search.res +++ b/apps/docs/src/components/Search.res @@ -68,9 +68,9 @@ let normalizeHitUrls = (items: array, ~siteUrl: string) {...hit, url, url_without_anchor, hierarchy} }) -let navigator = (~siteUrl: string): DocSearch.navigator => { +let navigator = (~siteUrl: string, ~navigate: ReactRouter.navigate): DocSearch.navigator => { navigate: ({itemUrl}) => { - ReactRouter.navigate(toRelativeSiteUrl(itemUrl, ~siteUrl)) + navigate(toRelativeSiteUrl(itemUrl, ~siteUrl)) }, } @@ -301,6 +301,44 @@ module ErrorBoundary = { } } +module ActiveDocSearch = { + @react.component + let make = ( + ~apiKey, + ~appId, + ~indexName, + ~deactivateSearch: unit => unit, + ~onClose: unit => unit, + ) => { + let navigate = ReactRouter.useNavigate() + + switch ReactDOM.querySelector("body") { + | Some(element) => + ReactDOM.createPortal( + + normalizeHitUrls(items, ~siteUrl=Env.root_url)} + hitComponent + onClose + initialScrollY={window.scrollY->Float.toInt} + searchParameters={ + distinct: 3, + hitsPerPage: 20, + attributesToSnippet: ["content:9999"], + } + /> + , + element, + ) + | None => React.null + } + } +} + @react.component let make = () => { let (state, setState) = React.useState(_ => Inactive) @@ -392,31 +430,7 @@ let make = () => { {switch state { - | Active => - switch ReactDOM.querySelector("body") { - | Some(element) => - ReactDOM.createPortal( - - normalizeHitUrls(items, ~siteUrl=Env.root_url)} - hitComponent - onClose - initialScrollY={window.scrollY->Float.toInt} - searchParameters={ - distinct: 3, - hitsPerPage: 20, - attributesToSnippet: ["content:9999"], - } - /> - , - element, - ) - | None => React.null - } + | Active => | Inactive => React.null }} diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index cb178f908..e9733dbb1 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -1,4 +1,4 @@ -open GuideVitest +open Vitest let firstLesson: GuideLesson.t = { id: "first-contact", @@ -40,18 +40,25 @@ let greeting = greet("ReScript")`, let guideLessons = [firstLesson, secondLesson] -let renderGuideHome = () => +let renderGuideHome = (~initialEntries=["/"], ()) => render( - + - , + , ) -let renderGuideHomeWithDocsIntroNavigation = goToDocsIntro => +let renderGuideHomeWithDocsIntroNavigation = (goToDocsIntro, ~initialEntries=["/"]) => render( - + - , + , + ) + +let renderGuideHomeInBrowser = () => + render( + + + , ) test("selects the latest stable ReScript compiler with ESM output", async () => { @@ -63,13 +70,12 @@ test("selects the latest stable ReScript compiler with ESM output", async () => "v12.2.0-beta.1", "v12.1.3", ]) - ->Option.map(Semver.toString) - ->Option.getOr("") + ->Option.getOrThrow + ->Semver.toString expect(version)->toBe("v12.1.3") expect(GuideCompilerSettings.moduleSystem)->toBe("esmodule") expect(GuideCompilerSettings.warnFlags->String.includes("-109"))->toBe(true) - expect(GuideCompilerSettings.initialCode)->toBe(`let greeting = "hello, world!"`) }) test("maps compiler diagnostics into editor errors", async () => { @@ -93,7 +99,7 @@ test("maps compiler diagnostics into editor errors", async () => { let outputLine = GuideCompilerFeedback.compileFailToOutputLines(TypecheckErr([locMsg])) ->Array.get(0) - ->Option.getOr("") + ->Option.getOrThrow expect(outputLine)->toBe("[E] Line 2, 7: Expected a string") }) @@ -143,14 +149,15 @@ test("shows compiler errors as output updates", async () => { endColumn: 12, } - switch GuideCompilerFeedback.compilationResultToOutputUpdate(Fail(TypecheckErr([locMsg]))) { - | Some(output) => - expect(output.status)->toBe("Compiler error") - expect(output.diagnostics->Array.get(0)->Option.getOr(""))->toBe( - "[E] Line 2, 7: Expected a string", - ) - | None => expect("compiler error update")->toBe("an output update") - } + let output = + GuideCompilerFeedback.compilationResultToOutputUpdate( + Fail(TypecheckErr([locMsg])), + )->Option.getOrThrow + + expect(output.status)->toBe("Compiler error") + expect(output.diagnostics->Array.get(0)->Option.getOrThrow)->toBe( + "[E] Line 2, 7: Expected a string", + ) }) test("checks expected output for the first lesson exercise", async () => { @@ -220,13 +227,11 @@ export { value, };`) - switch result { - | Some({code, imports}) => - expect(code->String.includes("console.log(value)"))->toBe(true) - expect(code->String.includes("export"))->toBe(false) - expect(imports->Dict.keysToArray->Array.length)->toBe(0) - | None => expect("runtime transform")->toBe("a transformed program") - } + let {code, imports} = result->Option.getOrThrow + + expect(code->String.includes("console.log(value)"))->toBe(true) + expect(code->String.includes("export"))->toBe(false) + expect(imports->Dict.keysToArray->Array.length)->toBe(0) }) test("rewrites the guide runtime result binding into a console log", async () => { @@ -239,10 +244,8 @@ export { };`, ) - switch result { - | Some({code}) => expect(code->String.includes("console.log(__rescriptGuideOutput)"))->toBe(true) - | None => expect("runtime transform")->toBe("a transformed program") - } + let {code} = result->Option.getOrThrow + expect(code->String.includes("console.log(__rescriptGuideOutput)"))->toBe(true) }) test("rewrites the last compiled binding into a console log", async () => { @@ -252,10 +255,8 @@ export { greeting, };`) - switch result { - | Some({code}) => expect(code->String.includes("console.log(greeting)"))->toBe(true) - | None => expect("runtime transform")->toBe("a transformed program") - } + let {code} = result->Option.getOrThrow + expect(code->String.includes("console.log(greeting)"))->toBe(true) }) test("skips runtime execution when compiled code has no expression or binding", async () => { @@ -281,11 +282,9 @@ greeting` }), ] - switch GuideRuntimeSource.instrument(~code, ~typeHints) { - | Some(instrumentedCode) => - expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (greeting)"))->toBe(true) - | None => expect("source instrumentation")->toBe("instrumented code") - } + let instrumentedCode = GuideRuntimeSource.instrument(~code, ~typeHints)->Option.getOrThrow + + expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (greeting)"))->toBe(true) }) test("prefers the outermost latest expression range", async () => { @@ -310,13 +309,9 @@ add(10, 42)` }), ] - switch GuideRuntimeSource.instrument(~code, ~typeHints) { - | Some(instrumentedCode) => - expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (add(10, 42))"))->toBe( - true, - ) - | None => expect("source instrumentation")->toBe("instrumented code") - } + let instrumentedCode = GuideRuntimeSource.instrument(~code, ~typeHints)->Option.getOrThrow + + expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (add(10, 42))"))->toBe(true) }) test("does not instrument expressions nested inside a binding", async () => { @@ -351,7 +346,7 @@ test("does not render redundant output status inside the output panel", async () ) let screen = await render() - let outputText = screen->container->textContent->Nullable.toOption->Option.getOr("") + let outputText = screen->container->textContent->Nullable.toOption->Option.getOrThrow expect(outputText->String.includes("Output"))->toBe(false) }) @@ -376,7 +371,7 @@ test("stores resized pane dimensions in local storage", async () => { let savedPaneSizes = GuideLayout.loadPaneSizes() let savedInstructionsWidth = - savedPaneSizes.instructionsWidth->Option.map(width => width->Float.toString)->Option.getOr("") + savedPaneSizes.instructionsWidth->Option.map(width => width->Float.toString)->Option.getOrThrow expect(savedInstructionsWidth)->toBe("420") expect(savedPaneSizes.outputHeight)->toBe(250.0) @@ -390,7 +385,7 @@ test("stores guide exercise code in local storage", async () => { GuideLayout.saveExerciseCode(~exerciseId, ~code="let voyager = 1701") - expect(GuideLayout.loadExerciseCode(exerciseId)->Option.getOr(""))->toBe("let voyager = 1701") + expect(GuideLayout.loadExerciseCode(exerciseId)->Option.getOrThrow)->toBe("let voyager = 1701") GuideLayout.clearExerciseCode(exerciseId) }) @@ -491,16 +486,10 @@ test("shows Back before lesson forward actions and returns to the previous lesso GuideLayout.clearCompletedExercises() GuideLayout.clearExerciseCode(firstLesson.exercise.id) GuideLayout.clearExerciseCode(secondLesson.exercise.id) - WebAPI.History.replaceState( - window.history, - ~data=JSON.Null, - ~unused="", - ~url=guideTestUrl("#first-contact"), - ) - let screen = await renderGuideHome() - let firstLessonText = screen->container->textContent->Nullable.toOption->Option.getOr("") - let beforeNext = firstLessonText->String.split("Next")->Array.get(0)->Option.getOr("") + let screen = await renderGuideHome(~initialEntries=["/#first-contact"], ()) + let firstLessonText = screen->container->textContent->Nullable.toOption->Option.getOrThrow + let beforeNext = firstLessonText->String.split("Next")->Array.get(0)->Option.getOrThrow expect(beforeNext->String.includes("Back"))->toBe(true) await (await screen->getByText("Back"))->element->toBeVisible @@ -508,8 +497,8 @@ test("shows Back before lesson forward actions and returns to the previous lesso await (await screen->getByText("Call A Function"))->element->toBeVisible - let secondLessonText = screen->container->textContent->Nullable.toOption->Option.getOr("") - let beforeDone = secondLessonText->String.split("Done")->Array.get(0)->Option.getOr("") + let secondLessonText = screen->container->textContent->Nullable.toOption->Option.getOrThrow + let beforeDone = secondLessonText->String.split("Done")->Array.get(0)->Option.getOrThrow expect(beforeDone->String.includes("Back"))->toBe(true) await (await screen->getByText("Back"))->click @@ -517,7 +506,6 @@ test("shows Back before lesson forward actions and returns to the previous lesso GuideLayout.clearExerciseCode(firstLesson.exercise.id) GuideLayout.clearExerciseCode(secondLesson.exercise.id) - resetGuideTestUrl() }) test("enables Done on a completed final lesson", async () => { @@ -525,21 +513,14 @@ test("enables Done on a completed final lesson", async () => { GuideLayout.clearCompletedExercises() GuideLayout.clearExerciseCode(secondLesson.exercise.id) GuideLayout.saveCompletedExercise(secondLesson.exercise.id) - WebAPI.History.replaceState( - window.history, - ~data=JSON.Null, - ~unused="", - ~url=guideTestUrl("#functions"), - ) - let screen = await renderGuideHome() + let screen = await renderGuideHome(~initialEntries=["/#functions"], ()) let doneButton = await screen->getByText("Done") await doneButton->element->notToBeDisabled GuideLayout.clearCompletedExercises() GuideLayout.clearExerciseCode(secondLesson.exercise.id) - resetGuideTestUrl() }) test("Done on a completed final lesson opens the ReScript docs intro", async () => { @@ -547,15 +528,12 @@ test("Done on a completed final lesson opens the ReScript docs intro", async () GuideLayout.clearCompletedExercises() GuideLayout.clearExerciseCode(secondLesson.exercise.id) GuideLayout.saveCompletedExercise(secondLesson.exercise.id) - WebAPI.History.replaceState( - window.history, - ~data=JSON.Null, - ~unused="", - ~url=guideTestUrl("#functions"), - ) let openedUrl = ref("") - let screen = await renderGuideHomeWithDocsIntroNavigation(url => openedUrl.contents = url) + let screen = await renderGuideHomeWithDocsIntroNavigation( + url => openedUrl.contents = url, + ~initialEntries=["/#functions"], + ) await (await screen->getByText("Done"))->click @@ -563,7 +541,6 @@ test("Done on a completed final lesson opens the ReScript docs intro", async () GuideLayout.clearCompletedExercises() GuideLayout.clearExerciseCode(secondLesson.exercise.id) - resetGuideTestUrl() }) test("browser back returns to the previous guide lesson", async () => { @@ -584,7 +561,7 @@ test("browser back returns to the previous guide lesson", async () => { ~url=guideTestUrl("#first-contact"), ) - let screen = await renderGuideHome() + let screen = await renderGuideHomeInBrowser() await (await screen->getByText("Learn ReScript Guide"))->element->toBeVisible await (await screen->getByText("Next"))->click diff --git a/apps/guide/app/GuideCompilerSettings.res b/apps/guide/app/GuideCompilerSettings.res index e1dd911d3..843d36835 100644 --- a/apps/guide/app/GuideCompilerSettings.res +++ b/apps/guide/app/GuideCompilerSettings.res @@ -1,8 +1,6 @@ let moduleSystem = "esmodule" let warnFlags = "+a-4-9-20-40-41-42-50-61-102-109" -let initialCode = `let greeting = "hello, world!"` - let isSupportedVersion = (version: Semver.t) => switch version.major { | 8 | 9 => false diff --git a/apps/guide/app/GuideHome.res b/apps/guide/app/GuideHome.res index 1f9325924..52411e353 100644 --- a/apps/guide/app/GuideHome.res +++ b/apps/guide/app/GuideHome.res @@ -71,8 +71,8 @@ let make = ( ~compilerData: option=?, ~goToDocsIntro=navigateToDocsIntro, ) => { - let location = GuideReactRouter.useLocation() - let navigate = GuideReactRouter.useNavigate() + let location = ReactRouter.useLocation() + let navigate = ReactRouter.useNavigate() let shellRef: React.ref> = React.useRef(Nullable.null) let editorContainerRef: React.ref> = React.useRef(Nullable.null) let editorRef: React.ref> = React.useRef(None) @@ -88,7 +88,7 @@ let make = ( let dragTarget = React.useRef(NotDragging) React.useEffect(() => { - let currentHash = window.location.hash + let currentHash = location.hash->Option.getOr("") let nextLessonIndex = GuideLesson.indexForHash(~lessons, currentHash) let nextLesson = GuideLesson.lessonAt(~lessons, nextLessonIndex) let nextLessonHash = nextLesson->GuideLesson.hashForLesson diff --git a/apps/guide/app/GuideHomeRoute.res b/apps/guide/app/GuideHomeRoute.res index dda56355a..203547e4c 100644 --- a/apps/guide/app/GuideHomeRoute.res +++ b/apps/guide/app/GuideHomeRoute.res @@ -3,7 +3,7 @@ type loaderData = { lessons: array, } -let loader: GuideReactRouter.Loader.t = async _ => { +let loader: ReactRouter.Loader.t = async _ => { let compilerData = await GuideCompilerData.load() let lessons = GuideLessonContent.load() @@ -12,6 +12,6 @@ let loader: GuideReactRouter.Loader.t = async _ => { @react.component let default = () => { - let {compilerData, lessons}: loaderData = GuideReactRouter.useLoaderData() + let {compilerData, lessons}: loaderData = ReactRouter.useLoaderData() } diff --git a/apps/guide/app/GuideHomeRoute.resi b/apps/guide/app/GuideHomeRoute.resi index 8d5641473..380c80ddc 100644 --- a/apps/guide/app/GuideHomeRoute.resi +++ b/apps/guide/app/GuideHomeRoute.resi @@ -3,7 +3,7 @@ type loaderData = { lessons: array, } -let loader: GuideReactRouter.Loader.t +let loader: ReactRouter.Loader.t @react.component let default: unit => React.element diff --git a/apps/guide/app/GuideLayout.res b/apps/guide/app/GuideLayout.res index b60225422..254d60598 100644 --- a/apps/guide/app/GuideLayout.res +++ b/apps/guide/app/GuideLayout.res @@ -24,21 +24,12 @@ let defaultPaneSizes = { outputHeight: defaultOutputHeight, } -let clamp = (~min, ~max, value) => { - if value < min { - min - } else if value > max { - max - } else { - value - } -} - let clampInstructionsWidth = (~viewportWidth, ~pointerX) => - pointerX->clamp(~min=minInstructionsWidth, ~max=viewportWidth -. minWorkspaceWidth) + pointerX->Float.clamp(~min=minInstructionsWidth, ~max=viewportWidth -. minWorkspaceWidth) let clampOutputHeight = (~viewportHeight, ~pointerY) => - (viewportHeight -. pointerY)->clamp(~min=minOutputHeight, ~max=viewportHeight -. minEditorHeight) + (viewportHeight -. pointerY) + ->Float.clamp(~min=minOutputHeight, ~max=viewportHeight -. minEditorHeight) let clampPaneSizes = (~viewportWidth, ~viewportHeight, paneSizes) => { let instructionsWidth = @@ -48,7 +39,7 @@ let clampPaneSizes = (~viewportWidth, ~viewportHeight, paneSizes) => { { instructionsWidth, - outputHeight: paneSizes.outputHeight->clamp( + outputHeight: paneSizes.outputHeight->Float.clamp( ~min=minOutputHeight, ~max=viewportHeight -. minEditorHeight, ), @@ -106,6 +97,8 @@ let themeToggleText = theme => | Dark => "Light" } +// localStorage can throw in restricted browser contexts. Persistence is optional +// for the guide, so storage failures fall back to the current UI state. let getLocalStorageItem = key => { try { WebAPI.Storage.getItem(window.localStorage, key)->Null.toOption diff --git a/apps/guide/app/GuideLessonContent.res b/apps/guide/app/GuideLessonContent.res index ebc7ed7b4..1e5d60e7c 100644 --- a/apps/guide/app/GuideLessonContent.res +++ b/apps/guide/app/GuideLessonContent.res @@ -48,8 +48,8 @@ let exerciseFromFrontmatter = (~dict, ~sourcePath): GuideLesson.exercise => { } let lessonFromFile = sourcePath => { - let raw = GuideNode.Fs.readFileSync(sourcePath) - let {frontmatter, content}: GuideMarkdownParser.result = GuideMarkdownParser.parseSync(raw) + let raw = Node.Fs.readFileSync(sourcePath) + let {frontmatter, content}: MarkdownParser.result = MarkdownParser.parseSync(raw) let dict = frontmatterObject(~frontmatter, ~sourcePath) let exerciseDict = readObject(~dict, ~sourcePath, ~key="exercise") @@ -66,18 +66,18 @@ let lessonFromFile = sourcePath => { } let rec scanDir = currentDir => - GuideNode.Fs.readdirSync(currentDir)->Array.flatMap(entry => { - let fullPath = GuideNode.Path.join2(currentDir, entry) + Node.Fs.readdirSync(currentDir)->Array.flatMap(entry => { + let fullPath = Node.Path.join2(currentDir, entry) - if GuideNode.Fs.statSync(fullPath)["isDirectory"]() { + if Node.Fs.statSync(fullPath)["isDirectory"]() { scanDir(fullPath) - } else if GuideNode.Path.extname(entry) === ".mdx" { + } else if Node.Path.extname(entry) === ".mdx" { [fullPath] } else { [] } }) -let lessonsDir = () => GuideNode.Path.join2(GuideNode.Process.cwd(), "app/lessons") +let lessonsDir = () => Node.Path.join2(Node.Process.cwd(), "app/lessons") let load = (~dir=lessonsDir()) => scanDir(dir)->Array.map(lessonFromFile)->GuideLesson.sort diff --git a/apps/guide/app/GuideMarkdownParser.res b/apps/guide/app/GuideMarkdownParser.res deleted file mode 100644 index 1974c75a5..000000000 --- a/apps/guide/app/GuideMarkdownParser.res +++ /dev/null @@ -1,55 +0,0 @@ -type t -type plugin -type vfile<'a> = {..} as 'a - -external makePlugin: 'a => plugin = "%identity" - -@module("unified") external make: unit => t = "unified" - -@module("remark-parse") external remarkParse: plugin = "default" -@module("remark-gfm") external remarkGfm: plugin = "default" -@module("remark-comment") external remarkComment: plugin = "default" -@module("remark-frontmatter") external remarkFrontmatter: plugin = "default" -@module("remark-stringify") external remarkStringify: plugin = "default" - -@send external use: (t, plugin) => t = "use" -@send external useOptions: (t, plugin, array<{..}>) => t = "use" - -@send external processSync: (t, string) => vfile<'a> = "processSync" -@send external toString: vfile<'a> => string = "toString" - -@module("vfile-matter") external vfileMatter: vfile<'a> => unit = "matter" - -type result = { - frontmatter: JSON.t, - content: string, -} - -let vfileMatterPlugin = makePlugin(_options => (_tree, vfile) => vfileMatter(vfile)) - -type remarkNode = {@as("type") type_: string} -type remarkTree = {mutable children: array} - -let stripFrontmatterPlugin = makePlugin(_options => - (tree, _vfile) => { - tree.children = tree.children->Array.filter(node => node.type_ !== "yaml") - } -) - -let parser = - make() - ->use(remarkParse) - ->use(remarkStringify) - ->use(remarkGfm) - ->use(remarkComment) - ->useOptions(remarkFrontmatter, [{"type": "yaml", "marker": "-"}]) - ->use(vfileMatterPlugin) - ->use(stripFrontmatterPlugin) - -let parseSync = content => { - let vfile = parser->processSync(content) - let frontmatter = (vfile["data"]["matter"] :> option) - let frontmatter = frontmatter->Option.getOr(JSON.Object(Dict.make())) - let content = vfile->toString - {frontmatter, content} -} diff --git a/apps/guide/app/GuideNode.res b/apps/guide/app/GuideNode.res deleted file mode 100644 index 69e3272d4..000000000 --- a/apps/guide/app/GuideNode.res +++ /dev/null @@ -1,14 +0,0 @@ -module Path = { - @module("path") external join2: (string, string) => string = "join" - @module("path") external extname: string => string = "extname" -} - -module Process = { - @scope("process") external cwd: unit => string = "cwd" -} - -module Fs = { - @module("fs") external readFileSync: string => string = "readFileSync" - @module("fs") external readdirSync: string => array = "readdirSync" - @module("fs") external statSync: string => {.."isDirectory": unit => bool} = "statSync" -} diff --git a/apps/guide/app/GuideReactRouter.res b/apps/guide/app/GuideReactRouter.res deleted file mode 100644 index c3939e47c..000000000 --- a/apps/guide/app/GuideReactRouter.res +++ /dev/null @@ -1,50 +0,0 @@ -module Links = { - @module("react-router") @react.component - external make: unit => React.element = "Links" -} - -module Meta = { - @module("react-router") @react.component - external make: unit => React.element = "Meta" -} - -module Outlet = { - @module("react-router") @react.component - external make: unit => React.element = "Outlet" -} - -module Scripts = { - @module("react-router") @react.component - external make: unit => React.element = "Scripts" -} - -module ScrollRestoration = { - @module("react-router") @react.component - external make: unit => React.element = "ScrollRestoration" -} - -type location = { - hash: string, -} - -type navigateOptions = {replace?: bool} -type navigate = (string, ~options: navigateOptions=?) => unit - -@module("react-router") -external useLoaderData: unit => 'a = "useLoaderData" - -@module("react-router") -external useLocation: unit => location = "useLocation" - -@module("react-router") -external useNavigate: unit => navigate = "useNavigate" - -module BrowserRouter = { - @module("react-router") @react.component - external make: (~children: React.element) => React.element = "BrowserRouter" -} - -module Loader = { - type loaderArgs = {request: WebAPI.FetchAPI.request} - type t<'a> = loaderArgs => promise<'a> -} diff --git a/apps/guide/app/GuideRoot.res b/apps/guide/app/GuideRoot.res index 3a743bd0a..6251b792c 100644 --- a/apps/guide/app/GuideRoot.res +++ b/apps/guide/app/GuideRoot.res @@ -6,16 +6,16 @@ let default = () => { - - + + {React.string("ReScript Guide")} - - - + + + } diff --git a/apps/guide/src/bindings/GuideVitest.res b/apps/guide/src/bindings/GuideVitest.res deleted file mode 100644 index 9481a8243..000000000 --- a/apps/guide/src/bindings/GuideVitest.res +++ /dev/null @@ -1,56 +0,0 @@ -type expect -type element - -@module("vitest") -external test: (string, unit => promise) => unit = "test" - -@module("vitest") -external expect: 'a => expect = "expect" - -@send -external toBe: (expect, 'a) => unit = "toBe" - -@module("vitest/browser") @scope("page") -external viewport: (int, int) => promise = "viewport" - -@module("vitest-browser-react") -external render: Jsx.element => promise = "render" - -@module("vitest") @scope("expect") -external element: 'a => element = "element" - -@send -external getByText: (element, string) => promise = "getByText" - -@send -external getByLabelText: (element, string) => promise = "getByLabelText" - -@send -external getByTestId: (element, string) => promise = "getByTestId" - -@send -external toBeVisible: element => promise = "toBeVisible" - -@send -external toBeDisabled: element => promise = "toBeDisabled" - -@send @scope("not") -external notToBeDisabled: element => promise = "toBeDisabled" - -@send -external toHaveValue: (element, string) => promise = "toHaveValue" - -@send -external toHaveTextContent: (element, string) => promise = "toHaveTextContent" - -@send -external toHaveClass: (element, string) => promise = "toHaveClass" - -@send -external click: element => promise = "click" - -@get -external container: element => Dom.element = "container" - -@get -external textContent: Dom.element => Nullable.t = "textContent" diff --git a/packages/playground/src/CompilerManagerHook.res b/packages/playground/src/CompilerManagerHook.res index e5b8fc4f2..3e82102e8 100644 --- a/packages/playground/src/CompilerManagerHook.res +++ b/packages/playground/src/CompilerManagerHook.res @@ -253,7 +253,7 @@ let useCompilerManager = ( ~versions: array, ) => { let (state, setState) = React.useState(_ => Init) - let {pathname} = PlaygroundReactRouter.useLocation() + let {pathname} = ReactRouter.useLocation() // Dispatch method for the public interface let dispatch = React.useCallback((action: action): unit => { diff --git a/packages/playground/src/Playground.res b/packages/playground/src/Playground.res index ae1a2bb83..37f210e68 100644 --- a/packages/playground/src/Playground.res +++ b/packages/playground/src/Playground.res @@ -1644,7 +1644,7 @@ let initialReContent = `Js.log("Hello Reason 3.6!");` @react.component let make = (~bundleBaseUrl: string, ~versions: array) => { - let (searchParams, _) = PlaygroundReactRouter.useSearchParams() + let (searchParams, _) = ReactRouter.useSearchParams() let containerRef = React.useRef(Nullable.null) let editorRef: React.ref> = React.useRef(None) let (_, setScrollLock) = ScrollLockContext.useScrollLock() diff --git a/packages/playground/src/PlaygroundReactRouter.res b/packages/playground/src/PlaygroundReactRouter.res deleted file mode 100644 index 0b8fce77d..000000000 --- a/packages/playground/src/PlaygroundReactRouter.res +++ /dev/null @@ -1,11 +0,0 @@ -type location = { - pathname: string, - search?: string, - hash?: string, -} - -@module("react-router") -external useLocation: unit => location = "useLocation" - -@module("react-router") -external useSearchParams: unit => (WebAPI.URLAPI.urlSearchParams, {..} => unit) = "useSearchParams" diff --git a/packages/shared/package.json b/packages/shared/package.json index 1a7585dce..158e2fc6a 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -21,6 +21,13 @@ "@tsnobip/rescript-lezer": "^0.8.0", "highlight.js": "^11.11.1", "react": "^19.2.4", - "react-dom": "^19.2.4" + "react-dom": "^19.2.4", + "remark-comment": "^1.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.5", + "vfile-matter": "^5.0.1" } } diff --git a/apps/docs/src/markdown/MarkdownParser.res b/packages/shared/src/MarkdownParser.res similarity index 100% rename from apps/docs/src/markdown/MarkdownParser.res rename to packages/shared/src/MarkdownParser.res diff --git a/apps/docs/src/markdown/MarkdownParser.resi b/packages/shared/src/MarkdownParser.resi similarity index 100% rename from apps/docs/src/markdown/MarkdownParser.resi rename to packages/shared/src/MarkdownParser.resi diff --git a/apps/docs/src/bindings/Node.res b/packages/shared/src/Node.res similarity index 100% rename from apps/docs/src/bindings/Node.res rename to packages/shared/src/Node.res diff --git a/apps/docs/src/common/Path.res b/packages/shared/src/Path.res similarity index 100% rename from apps/docs/src/common/Path.res rename to packages/shared/src/Path.res diff --git a/apps/docs/src/bindings/ReactRouter.res b/packages/shared/src/ReactRouter.res similarity index 95% rename from apps/docs/src/bindings/ReactRouter.res rename to packages/shared/src/ReactRouter.res index 194d31343..a6d4ade12 100644 --- a/apps/docs/src/bindings/ReactRouter.res +++ b/packages/shared/src/ReactRouter.res @@ -1,11 +1,9 @@ type navigateOptions = {replace?: bool} - -@module("react-router-dom") -external navigate: (string, ~options: navigateOptions=?) => unit = "navigate" +type navigate = (string, ~options: navigateOptions=?) => unit // https://api.reactrouter.com/v7/functions/react_router.useNavigate.html @module("react-router") -external useNavigate: unit => string => unit = "useNavigate" +external useNavigate: unit => navigate = "useNavigate" @module("react-router") external useSearchParams: unit => (WebAPI.URLAPI.urlSearchParams, {..} => unit) = "useSearchParams" diff --git a/apps/docs/src/bindings/Vitest.res b/packages/shared/src/Vitest.res similarity index 81% rename from apps/docs/src/bindings/Vitest.res rename to packages/shared/src/Vitest.res index c2428c72d..18fb832e4 100644 --- a/apps/docs/src/bindings/Vitest.res +++ b/packages/shared/src/Vitest.res @@ -101,5 +101,26 @@ external toBeVisible: element => promise = "toBeVisible" @send @scope("not") external notToBeVisible: element => promise = "toBeVisible" +@send +external toBeDisabled: element => promise = "toBeDisabled" + +@send @scope("not") +external notToBeDisabled: element => promise = "toBeDisabled" + +@send +external toHaveValue: (element, string) => promise = "toHaveValue" + +@send +external toHaveTextContent: (element, string) => promise = "toHaveTextContent" + +@send +external toHaveClass: (element, string) => promise = "toHaveClass" + @send external toMatchScreenshot: (element, string) => promise = "toMatchScreenshot" + +@get +external container: element => Dom.element = "container" + +@get +external textContent: Dom.element => Nullable.t = "textContent" diff --git a/yarn.lock b/yarn.lock index 1b22e74fc..b00916c92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2527,6 +2527,13 @@ __metadata: highlight.js: "npm:^11.11.1" react: "npm:^19.2.4" react-dom: "npm:^19.2.4" + remark-comment: "npm:^1.0.0" + remark-frontmatter: "npm:^5.0.0" + remark-gfm: "npm:^4.0.1" + remark-parse: "npm:^11.0.0" + remark-stringify: "npm:^11.0.0" + unified: "npm:^11.0.5" + vfile-matter: "npm:^5.0.1" languageName: unknown linkType: soft From 89abdace4b62e88c3f5ea74aba830ad89505ed5a Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Tue, 5 May 2026 18:33:19 -0400 Subject: [PATCH 27/32] refactor: group guide state hooks Move guide navigation, layout resizing, editor lifecycle, output rendering, and compiler bridge effects into focused modules. Add brief comments around the storage, hash, editor, and runtime import edge cases. --- apps/guide/__tests__/GuideHome_.test.res | 8 +- apps/guide/app/GuideCompilerBridge.res | 155 +--------- apps/guide/app/GuideCompilerBridgeHook.res | 123 ++++++++ apps/guide/app/GuideDom.res | 3 + apps/guide/app/GuideEditorHook.res | 57 ++++ apps/guide/app/GuideHome.res | 286 ++----------------- apps/guide/app/GuideLayoutHook.res | 104 +++++++ apps/guide/app/GuideLessonNavigationHook.res | 97 +++++++ apps/guide/app/GuideOutputPanel.res | 44 +++ apps/guide/app/GuideRuntimeImport.res | 39 +++ 10 files changed, 504 insertions(+), 412 deletions(-) create mode 100644 apps/guide/app/GuideCompilerBridgeHook.res create mode 100644 apps/guide/app/GuideDom.res create mode 100644 apps/guide/app/GuideEditorHook.res create mode 100644 apps/guide/app/GuideLayoutHook.res create mode 100644 apps/guide/app/GuideLessonNavigationHook.res create mode 100644 apps/guide/app/GuideOutputPanel.res create mode 100644 apps/guide/app/GuideRuntimeImport.res diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index e9733dbb1..8ab23ee0f 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -213,7 +213,7 @@ test("renders output diagnostics as error lines", async () => { ~diagnostics=["[E] Line 2, 7: Expected a string"], ) - let screen = await render() + let screen = await render() let diagnostic = await screen->getByText("[E] Line 2, 7: Expected a string") await diagnostic->element->toHaveClass("guide-output-line-error") @@ -333,7 +333,7 @@ test("renders runtime logs in the output panel", async () => { ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["52"]}], ) - let screen = await render() + let screen = await render() await (await screen->getByText("Result"))->element->toBeVisible await (await screen->getByText("52"))->element->toBeVisible @@ -345,7 +345,7 @@ test("does not render redundant output status inside the output panel", async () ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["52"]}], ) - let screen = await render() + let screen = await render() let outputText = screen->container->textContent->Nullable.toOption->Option.getOrThrow expect(outputText->String.includes("Output"))->toBe(false) @@ -537,7 +537,7 @@ test("Done on a completed final lesson opens the ReScript docs intro", async () await (await screen->getByText("Done"))->click - expect(openedUrl.contents)->toBe(GuideHome.docsIntroUrl) + expect(openedUrl.contents)->toBe(GuideLessonNavigationHook.docsIntroUrl) GuideLayout.clearCompletedExercises() GuideLayout.clearExerciseCode(secondLesson.exercise.id) diff --git a/apps/guide/app/GuideCompilerBridge.res b/apps/guide/app/GuideCompilerBridge.res index 15d6e5114..18072bd76 100644 --- a/apps/guide/app/GuideCompilerBridge.res +++ b/apps/guide/app/GuideCompilerBridge.res @@ -1,41 +1,3 @@ -let capitalizeFirstLetter = string => { - let firstLetter = string->String.charAt(0)->String.toUpperCase - `${firstLetter}${string->String.slice(~start=1)}` -} - -let runtimeImportUrl = (~bundleBaseUrl, ~compilerVersion: Semver.t, path) => { - let filename = path->String.slice(~start=9) - let filename = switch compilerVersion { - | {major: 12, minor: 0, patch: 0, preRelease: Some(Alpha(alpha))} if alpha < 8 => - let filename = if filename->String.startsWith("core__") { - filename->String.slice(~start=6) - } else { - filename - } - filename->capitalizeFirstLetter - | {major} if major < 12 && filename->String.startsWith("core__") => - filename->capitalizeFirstLetter - | _ => filename - } - let compilerVersion = switch compilerVersion { - | {major: 12, minor: 0, patch: 0, preRelease: Some(Alpha(alpha))} if alpha < 9 => { - Semver.major: 12, - minor: 0, - patch: 0, - preRelease: Some(Alpha(9)), - } - | {major, minor} if (major === 11 && minor < 2) || major < 11 => { - major: 11, - minor: 2, - patch: 0, - preRelease: Some(Beta(2)), - } - | version => version - } - - CompilerManagerHook.CdnMeta.getStdlibRuntimeUrl(bundleBaseUrl, compilerVersion, filename) -} - @react.component let make = ( ~bundleBaseUrl, @@ -44,121 +6,14 @@ let make = ( ~editorRef: React.ref>, ~setOutput, ) => { - let compilerVersions = React.useMemo( - () => GuideCompilerSettings.supportedVersions(versions), - [versions], - ) - let initialVersion = React.useMemo( - () => compilerVersions->GuideCompilerSettings.latestStableParsedVersion, - [compilerVersions], - ) - - let (compilerState, compilerDispatch) = CompilerManagerHook.useCompilerManager( + GuideCompilerBridgeHook.useCompilerBridge( ~bundleBaseUrl, - ~initialVersion?, - ~initialModuleSystem=GuideCompilerSettings.moduleSystem, - ~initialWarnFlags=GuideCompilerSettings.warnFlags, - ~syncUrl=false, - ~versions=compilerVersions, + ~versions, + ~code, + ~editorRef, + ~setOutput, ) - let lastCompiledCode = React.useRef("") - let lastExecutedJsCode = React.useRef("") - let isWaitingForRuntimeOutput = React.useRef(false) - - React.useEffect(() => { - switch compilerState { - | Ready({targetLang}) if code !== lastCompiledCode.current => - let timer = setTimeout(~handler=() => { - lastCompiledCode.current = code - compilerDispatch(CompileCode(targetLang, code)) - }, ~timeout=150) - Some(() => clearTimeout(timer)) - | _ => None - } - }, (code, compilerState, compilerDispatch)) - - React.useEffect(() => { - let cb = event => { - let data = event["data"] - let appendLog = (level, content) => { - let runtimeLog = {GuideCompilerFeedback.Output.level, content} - if isWaitingForRuntimeOutput.current { - isWaitingForRuntimeOutput.current = false - setOutput(_ => runtimeLog->GuideCompilerFeedback.Output.fromRuntimeLog) - } else { - setOutput(output => output->GuideCompilerFeedback.Output.withRuntimeLog(runtimeLog)) - } - } - - switch data["type"] { - | #log => appendLog(#log, data["args"]) - | #warn => appendLog(#warn, data["args"]) - | #error => appendLog(#error, data["args"]) - | _ => () - } - } - WebAPI.Window.addEventListener(window, Custom("message"), cb) - Some(() => WebAPI.Window.removeEventListener(window, Custom("message"), cb)) - }, [setOutput]) - - React.useEffect(() => { - let feedback = compilerState->GuideCompilerFeedback.editorFeedbackFromState - editorRef.current->Option.forEach(editor => { - CodeMirror.editorSetErrors(editor, feedback.errors) - CodeMirror.editorSetHoverHints(editor, feedback.hoverHints) - }) - switch compilerState->GuideCompilerFeedback.outputUpdateFromState { - | Some(output) => - isWaitingForRuntimeOutput.current = false - setOutput(_ => output) - | None => () - } - None - }, (compilerState, setOutput)) - - React.useEffect(() => { - switch compilerState { - | Ready({selected, result: Comp(Success({jsCode, typeHints}))}) - if jsCode !== lastExecutedJsCode.current => - lastExecutedJsCode.current = jsCode - let runtimeJsCode = switch GuideRuntimeSource.instrument(~code, ~typeHints) { - | Some(runtimeCode) => - switch selected.instance->RescriptCompilerApi.Compiler.resCompile(runtimeCode) { - | Success({jsCode}) => jsCode - | Fail(_) | UnexpectedError(_) | Unknown(_, _) => jsCode - } - | None => jsCode - } - - switch runtimeJsCode->GuideRuntimeTransform.transform( - ~resultBindingName=GuideRuntimeSource.resultBindingName, - ) { - | Some({code: runtimeCode, imports}) => - isWaitingForRuntimeOutput.current = true - let imports = - imports->Dict.mapValues(path => - path->runtimeImportUrl(~bundleBaseUrl, ~compilerVersion=selected.id) - ) - let timer = setTimeout( - ~handler=() => EvalIFrame.sendOutput(runtimeCode, imports), - ~timeout=50, - ) - Some( - () => { - isWaitingForRuntimeOutput.current = false - clearTimeout(timer) - }, - ) - | None => None - } - | Compiling(_) => - lastExecutedJsCode.current = "" - None - | _ => None - } - }, (compilerState, bundleBaseUrl)) -
diff --git a/apps/guide/app/GuideCompilerBridgeHook.res b/apps/guide/app/GuideCompilerBridgeHook.res new file mode 100644 index 000000000..2b19d1a1b --- /dev/null +++ b/apps/guide/app/GuideCompilerBridgeHook.res @@ -0,0 +1,123 @@ +let useCompilerBridge = ( + ~bundleBaseUrl, + ~versions, + ~code, + ~editorRef: React.ref>, + ~setOutput, +) => { + let compilerVersions = React.useMemo( + () => GuideCompilerSettings.supportedVersions(versions), + [versions], + ) + let initialVersion = React.useMemo( + () => compilerVersions->GuideCompilerSettings.latestStableParsedVersion, + [compilerVersions], + ) + + let (compilerState, compilerDispatch) = CompilerManagerHook.useCompilerManager( + ~bundleBaseUrl, + ~initialVersion?, + ~initialModuleSystem=GuideCompilerSettings.moduleSystem, + ~initialWarnFlags=GuideCompilerSettings.warnFlags, + ~syncUrl=false, + ~versions=compilerVersions, + ) + + let lastCompiledCode = React.useRef("") + let lastExecutedJsCode = React.useRef("") + let isWaitingForRuntimeOutput = React.useRef(false) + + React.useEffect(() => { + switch compilerState { + | Ready({targetLang}) if code !== lastCompiledCode.current => + let timer = setTimeout(~handler=() => { + lastCompiledCode.current = code + compilerDispatch(CompileCode(targetLang, code)) + }, ~timeout=150) + Some(() => clearTimeout(timer)) + | _ => None + } + }, (code, compilerState, compilerDispatch)) + + React.useEffect(() => { + let cb = event => { + let data = event["data"] + let appendLog = (level, content) => { + let runtimeLog = {GuideCompilerFeedback.Output.level, content} + if isWaitingForRuntimeOutput.current { + isWaitingForRuntimeOutput.current = false + setOutput(_ => runtimeLog->GuideCompilerFeedback.Output.fromRuntimeLog) + } else { + setOutput(output => output->GuideCompilerFeedback.Output.withRuntimeLog(runtimeLog)) + } + } + + switch data["type"] { + | #log => appendLog(#log, data["args"]) + | #warn => appendLog(#warn, data["args"]) + | #error => appendLog(#error, data["args"]) + | _ => () + } + } + WebAPI.Window.addEventListener(window, Custom("message"), cb) + Some(() => WebAPI.Window.removeEventListener(window, Custom("message"), cb)) + }, [setOutput]) + + React.useEffect(() => { + let feedback = compilerState->GuideCompilerFeedback.editorFeedbackFromState + editorRef.current->Option.forEach(editor => { + CodeMirror.editorSetErrors(editor, feedback.errors) + CodeMirror.editorSetHoverHints(editor, feedback.hoverHints) + }) + switch compilerState->GuideCompilerFeedback.outputUpdateFromState { + | Some(output) => + isWaitingForRuntimeOutput.current = false + setOutput(_ => output) + | None => () + } + None + }, (compilerState, setOutput)) + + React.useEffect(() => { + switch compilerState { + | Ready({selected, result: Comp(Success({jsCode, typeHints}))}) + if jsCode !== lastExecutedJsCode.current => + lastExecutedJsCode.current = jsCode + let runtimeJsCode = switch GuideRuntimeSource.instrument(~code, ~typeHints) { + | Some(runtimeCode) => + // The instrumented source may fail on compiler internals; fall back to user JS in that case. + switch selected.instance->RescriptCompilerApi.Compiler.resCompile(runtimeCode) { + | Success({jsCode}) => jsCode + | Fail(_) | UnexpectedError(_) | Unknown(_, _) => jsCode + } + | None => jsCode + } + + switch runtimeJsCode->GuideRuntimeTransform.transform( + ~resultBindingName=GuideRuntimeSource.resultBindingName, + ) { + | Some({code: runtimeCode, imports}) => + isWaitingForRuntimeOutput.current = true + let imports = + imports->Dict.mapValues(path => + path->GuideRuntimeImport.url(~bundleBaseUrl, ~compilerVersion=selected.id) + ) + let timer = setTimeout( + ~handler=() => EvalIFrame.sendOutput(runtimeCode, imports), + ~timeout=50, + ) + Some( + () => { + isWaitingForRuntimeOutput.current = false + clearTimeout(timer) + }, + ) + | None => None + } + | Compiling(_) => + lastExecutedJsCode.current = "" + None + | _ => None + } + }, (compilerState, bundleBaseUrl)) +} diff --git a/apps/guide/app/GuideDom.res b/apps/guide/app/GuideDom.res new file mode 100644 index 000000000..406bf1246 --- /dev/null +++ b/apps/guide/app/GuideDom.res @@ -0,0 +1,3 @@ +external domElementToWebElement: Dom.element => WebAPI.DOMAPI.element = "%identity" + +let toWebElement = domElementToWebElement diff --git a/apps/guide/app/GuideEditorHook.res b/apps/guide/app/GuideEditorHook.res new file mode 100644 index 000000000..ef4a4d58f --- /dev/null +++ b/apps/guide/app/GuideEditorHook.res @@ -0,0 +1,57 @@ +type t = { + code: string, + containerRef: React.ref>, + editorRef: React.ref>, +} + +let useEditor = (~exercise: GuideLesson.exercise, ~theme): t => { + let containerRef: React.ref> = React.useRef(Nullable.null) + let editorRef: React.ref> = React.useRef(None) + let (code, setCode) = React.useState(() => exercise.initialCode) + + React.useEffect(() => { + editorRef.current->Option.forEach(editor => + CodeMirror.editorSetTheme(editor, theme->GuideLayout.themeToCodeMirror) + ) + None + }, [theme]) + + React.useEffect(() => { + switch containerRef.current { + | Value(parent) => + let initialCode = + GuideLayout.loadExerciseCode(exercise.id)->Option.getOr(exercise.initialCode) + setCode(_ => initialCode) + + // Recreate CodeMirror on lesson changes so persisted drafts replace the editor doc and history. + let config: CodeMirror.editorConfig = { + parent: parent->GuideDom.toWebElement, + initialValue: initialCode, + mode: "rescript", + readOnly: false, + lineNumbers: true, + lineWrapping: false, + theme: theme->GuideLayout.themeToCodeMirror, + keyMap: CodeMirror.KeyMap.Default, + onChange: value => { + GuideLayout.saveExerciseCode(~exerciseId=exercise.id, ~code=value) + setCode(_ => value) + }, + errors: [], + hoverHints: [], + minHeight: "100%", + } + let editor = CodeMirror.createEditor(config) + editorRef.current = Some(editor) + Some( + () => { + editorRef.current = None + CodeMirror.editorDestroy(editor) + }, + ) + | Null | Undefined => None + } + }, (exercise.id, exercise.initialCode)) + + {code, containerRef, editorRef} +} diff --git a/apps/guide/app/GuideHome.res b/apps/guide/app/GuideHome.res index 52411e353..d6fad32f1 100644 --- a/apps/guide/app/GuideHome.res +++ b/apps/guide/app/GuideHome.res @@ -1,68 +1,3 @@ -external domElementToWebElement: Dom.element => WebAPI.DOMAPI.element = "%identity" - -module OutputPanel = { - @react.component - let make = (~output: GuideCompilerFeedback.Output.t) => { -
- {switch output.status { - | "Output" => React.null - | status =>
{React.string(status)}
- }} - {switch output.diagnostics { - | [] => React.null - | diagnostics => -
-
{React.string("Diagnostics")}
- {diagnostics - ->Array.mapWithIndex((diagnostic, index) => -
Int.toString} className="guide-output-line guide-output-line-error">
-              {React.string(diagnostic)}
-            
- ) - ->React.array} -
- }} - {switch output.runtimeLogs { - | [] => React.null - | runtimeLogs => -
-
{React.string("Result")}
- {runtimeLogs - ->Array.mapWithIndex(({level, content}, index) => -
Int.toString}
-              className={switch level {
-              | #log => "guide-output-line"
-              | #warn => "guide-output-line guide-output-line-warning"
-              | #error => "guide-output-line guide-output-line-error"
-              }}
-            >
-              {React.string(content->Array.join(" "))}
-            
- ) - ->React.array} -
- }} -
- } -} - -type dragTarget = - | NotDragging - | ResizingColumns - | ResizingRows - -let emptyOutput = () => GuideCompilerFeedback.Output.make(~status="Output") - -let outputForLessonIndex = index => - if index === 0 { - GuideCompilerFeedback.Output.initial - } else { - emptyOutput() - } - -let docsIntroUrl = "https://rescript-lang.org/docs/manual/introduction" - let navigateToDocsIntro = url => window.location->WebAPI.Location.assign(url) @react.component @@ -71,182 +6,11 @@ let make = ( ~compilerData: option=?, ~goToDocsIntro=navigateToDocsIntro, ) => { - let location = ReactRouter.useLocation() - let navigate = ReactRouter.useNavigate() - let shellRef: React.ref> = React.useRef(Nullable.null) - let editorContainerRef: React.ref> = React.useRef(Nullable.null) - let editorRef: React.ref> = React.useRef(None) - let (lessonIndex, setLessonIndex) = React.useState(() => 0) - let lesson = GuideLesson.lessonAt(~lessons, lessonIndex) + let layout = GuideLayoutHook.useLayout() + let navigation = GuideLessonNavigationHook.useLessonNavigation(~lessons, ~goToDocsIntro) + let lesson = navigation.lesson let exercise = lesson.exercise - let (code, setCode) = React.useState(() => exercise.initialCode) - let (output, setOutput) = React.useState(() => GuideCompilerFeedback.Output.initial) - let (paneSizes, setPaneSizes) = React.useState(() => GuideLayout.defaultPaneSizes) - let (theme, setTheme) = React.useState(() => GuideLayout.Light) - let (themeLoaded, setThemeLoaded) = React.useState(() => false) - let (paneSizesLoaded, setPaneSizesLoaded) = React.useState(() => false) - let dragTarget = React.useRef(NotDragging) - - React.useEffect(() => { - let currentHash = location.hash->Option.getOr("") - let nextLessonIndex = GuideLesson.indexForHash(~lessons, currentHash) - let nextLesson = GuideLesson.lessonAt(~lessons, nextLessonIndex) - let nextLessonHash = nextLesson->GuideLesson.hashForLesson - - setLessonIndex(_ => nextLessonIndex) - setOutput(_ => nextLessonIndex->outputForLessonIndex) - - if currentHash !== nextLessonHash { - navigate(nextLessonHash, ~options={replace: true}) - } - - None - }, (location.hash, navigate, lessons)) - - React.useEffect(() => { - setTheme(_ => GuideLayout.loadTheme()) - setPaneSizes(_ => - GuideLayout.loadPaneSizes()->GuideLayout.clampPaneSizes( - ~viewportWidth=window.innerWidth->Int.toFloat, - ~viewportHeight=window.innerHeight->Int.toFloat, - ) - ) - setThemeLoaded(_ => true) - setPaneSizesLoaded(_ => true) - None - }, []) - - React.useEffect(() => { - if themeLoaded { - theme->GuideLayout.saveTheme - } - editorRef.current->Option.forEach(editor => - CodeMirror.editorSetTheme(editor, theme->GuideLayout.themeToCodeMirror) - ) - None - }, (theme, themeLoaded)) - - React.useEffect(() => { - if paneSizesLoaded { - paneSizes->GuideLayout.savePaneSizes - } - None - }, (paneSizes, paneSizesLoaded)) - - React.useEffect(() => { - switch shellRef.current { - | Value(element) => - WebAPI.Element.setAttribute( - element->domElementToWebElement, - ~qualifiedName="style", - ~value=paneSizes->GuideLayout.paneSizesStyle, - ) - | Null | Undefined => () - } - None - }, [paneSizes]) - - React.useEffect(() => { - let stopDragging = _event => dragTarget.current = NotDragging - - let onMouseMove = event => - switch dragTarget.current { - | ResizingColumns => - let pointerX = event->ReactEvent.Mouse.clientX->Int.toFloat - let viewportWidth = window.innerWidth->Int.toFloat - let instructionsWidth = GuideLayout.clampInstructionsWidth(~viewportWidth, ~pointerX) - setPaneSizes(previous => {...previous, instructionsWidth: Some(instructionsWidth)}) - | ResizingRows => - let pointerY = event->ReactEvent.Mouse.clientY->Int.toFloat - let viewportHeight = window.innerHeight->Int.toFloat - let outputHeight = GuideLayout.clampOutputHeight(~viewportHeight, ~pointerY) - setPaneSizes(previous => {...previous, outputHeight}) - | NotDragging => () - } - - WebAPI.Window.addEventListener(window, Mousemove, onMouseMove) - WebAPI.Window.addEventListener(window, Mouseup, stopDragging) - - Some( - () => { - WebAPI.Window.removeEventListener(window, Mousemove, onMouseMove) - WebAPI.Window.removeEventListener(window, Mouseup, stopDragging) - }, - ) - }, []) - - let startDragging = (target, event) => { - ReactEvent.Mouse.preventDefault(event) - dragTarget.current = target - } - - let hasPreviousLesson = GuideLesson.hasPreviousLesson(lessonIndex) - let hasNextLesson = GuideLesson.hasNextLesson(~lessons, lessonIndex) - let exercisePassed = GuideLesson.isExerciseComplete(~exercise, ~output) - let checkpointComplete = exercisePassed || GuideLayout.isExerciseCompleted(exercise.id) - let forwardActionEnabled = hasNextLesson || checkpointComplete - let goToPreviousLesson = _event => { - if hasPreviousLesson { - let previousLessonIndex = lessonIndex - 1 - let previousLesson = GuideLesson.lessonAt(~lessons, previousLessonIndex) - navigate(previousLesson->GuideLesson.hashForLesson) - setLessonIndex(_ => previousLessonIndex) - setOutput(_ => previousLessonIndex->outputForLessonIndex) - } - } - let goToNextLesson = _event => { - if hasNextLesson { - let nextLessonIndex = lessonIndex + 1 - let nextLesson = GuideLesson.lessonAt(~lessons, nextLessonIndex) - navigate(nextLesson->GuideLesson.hashForLesson) - setLessonIndex(_ => nextLessonIndex) - setOutput(_ => nextLessonIndex->outputForLessonIndex) - } else if checkpointComplete { - goToDocsIntro(docsIntroUrl) - } - } - - React.useEffect(() => { - if exercisePassed { - GuideLayout.saveCompletedExercise(exercise.id) - } - None - }, (exercisePassed, exercise.id)) - - React.useEffect(() => { - switch editorContainerRef.current { - | Value(parent) => - let initialCode = - GuideLayout.loadExerciseCode(exercise.id)->Option.getOr(exercise.initialCode) - setCode(_ => initialCode) - let config: CodeMirror.editorConfig = { - parent: parent->domElementToWebElement, - initialValue: initialCode, - mode: "rescript", - readOnly: false, - lineNumbers: true, - lineWrapping: false, - theme: theme->GuideLayout.themeToCodeMirror, - keyMap: CodeMirror.KeyMap.Default, - onChange: value => { - GuideLayout.saveExerciseCode(~exerciseId=exercise.id, ~code=value) - setCode(_ => value) - }, - errors: [], - hoverHints: [], - minHeight: "100%", - } - let editor = CodeMirror.createEditor(config) - editorRef.current = Some(editor) - Some( - () => { - editorRef.current = None - CodeMirror.editorDestroy(editor) - }, - ) - | Null | Undefined => None - } - }, (exercise.id, exercise.initialCode)) + let editor = GuideEditorHook.useEditor(~exercise, ~theme=layout.theme) <>
@@ -254,28 +18,28 @@ let make = (

{React.string("Use a desktop browser or resize this window to continue.")}

GuideLayout.themeClass} + className={"guide-shell " ++ layout.theme->GuideLayout.themeClass} dataTestId="guide-mvp" - ref={ReactDOM.Ref.domRef(shellRef)} + ref={ReactDOM.Ref.domRef(layout.shellRef)} >

{React.string(lesson.missionLabel)}

{React.string(lesson.title)}

lesson.content
{React.string("Checkpoint")} {React.string( - if checkpointComplete { + if navigation.checkpointComplete { "Checkpoint complete" } else { "Waiting for matching output" @@ -297,20 +61,20 @@ let make = (
@@ -341,19 +105,25 @@ let make = ( ariaLabel="Resize output pane" className="guide-resize-handle guide-resize-handle-rows" dataTestId="guide-row-resize" - onMouseDown={event => startDragging(ResizingRows, event)} + onMouseDown={layout.startRowResize} role="separator" />
{React.string("Output")}
- +
{switch compilerData { | Some({bundleBaseUrl, versions}) => - + | None => React.null }}
diff --git a/apps/guide/app/GuideLayoutHook.res b/apps/guide/app/GuideLayoutHook.res new file mode 100644 index 000000000..4ee7df26c --- /dev/null +++ b/apps/guide/app/GuideLayoutHook.res @@ -0,0 +1,104 @@ +type dragTarget = + | NotDragging + | ResizingColumns + | ResizingRows + +type t = { + shellRef: React.ref>, + theme: GuideLayout.theme, + toggleTheme: ReactEvent.Mouse.t => unit, + startColumnResize: ReactEvent.Mouse.t => unit, + startRowResize: ReactEvent.Mouse.t => unit, +} + +let useLayout = (): t => { + let shellRef: React.ref> = React.useRef(Nullable.null) + let (paneSizes, setPaneSizes) = React.useState(() => GuideLayout.defaultPaneSizes) + let (theme, setTheme) = React.useState(() => GuideLayout.Light) + let (themeLoaded, setThemeLoaded) = React.useState(() => false) + let (paneSizesLoaded, setPaneSizesLoaded) = React.useState(() => false) + let dragTarget = React.useRef(NotDragging) + + React.useEffect(() => { + setTheme(_ => GuideLayout.loadTheme()) + setPaneSizes(_ => + GuideLayout.loadPaneSizes()->GuideLayout.clampPaneSizes( + ~viewportWidth=window.innerWidth->Int.toFloat, + ~viewportHeight=window.innerHeight->Int.toFloat, + ) + ) + setThemeLoaded(_ => true) + setPaneSizesLoaded(_ => true) + None + }, []) + + React.useEffect(() => { + if themeLoaded { + theme->GuideLayout.saveTheme + } + None + }, (theme, themeLoaded)) + + React.useEffect(() => { + if paneSizesLoaded { + paneSizes->GuideLayout.savePaneSizes + } + None + }, (paneSizes, paneSizesLoaded)) + + React.useEffect(() => { + switch shellRef.current { + | Value(element) => + // CSS variables keep the two resizable axes in one place for layout and tests. + WebAPI.Element.setAttribute( + element->GuideDom.toWebElement, + ~qualifiedName="style", + ~value=paneSizes->GuideLayout.paneSizesStyle, + ) + | Null | Undefined => () + } + None + }, [paneSizes]) + + React.useEffect(() => { + let stopDragging = _event => dragTarget.current = NotDragging + + let onMouseMove = event => + switch dragTarget.current { + | ResizingColumns => + let pointerX = event->ReactEvent.Mouse.clientX->Int.toFloat + let viewportWidth = window.innerWidth->Int.toFloat + let instructionsWidth = GuideLayout.clampInstructionsWidth(~viewportWidth, ~pointerX) + setPaneSizes(previous => {...previous, instructionsWidth: Some(instructionsWidth)}) + | ResizingRows => + let pointerY = event->ReactEvent.Mouse.clientY->Int.toFloat + let viewportHeight = window.innerHeight->Int.toFloat + let outputHeight = GuideLayout.clampOutputHeight(~viewportHeight, ~pointerY) + setPaneSizes(previous => {...previous, outputHeight}) + | NotDragging => () + } + + WebAPI.Window.addEventListener(window, Mousemove, onMouseMove) + WebAPI.Window.addEventListener(window, Mouseup, stopDragging) + + Some( + () => { + WebAPI.Window.removeEventListener(window, Mousemove, onMouseMove) + WebAPI.Window.removeEventListener(window, Mouseup, stopDragging) + }, + ) + }, []) + + let startDragging = (target, event) => { + ReactEvent.Mouse.preventDefault(event) + dragTarget.current = target + } + + { + shellRef, + theme, + toggleTheme: _event => setTheme(previous => previous->GuideLayout.toggleTheme), + startColumnResize: event => startDragging(ResizingColumns, event), + startRowResize: event => startDragging(ResizingRows, event), + } +} diff --git a/apps/guide/app/GuideLessonNavigationHook.res b/apps/guide/app/GuideLessonNavigationHook.res new file mode 100644 index 000000000..b3ee6447f --- /dev/null +++ b/apps/guide/app/GuideLessonNavigationHook.res @@ -0,0 +1,97 @@ +type t = { + lesson: GuideLesson.t, + output: GuideCompilerFeedback.Output.t, + setOutput: (GuideCompilerFeedback.Output.t => GuideCompilerFeedback.Output.t) => unit, + hasPreviousLesson: bool, + hasNextLesson: bool, + checkpointComplete: bool, + forwardActionEnabled: bool, + goToPreviousLesson: ReactEvent.Mouse.t => unit, + goToNextLesson: ReactEvent.Mouse.t => unit, +} + +let emptyOutput = () => GuideCompilerFeedback.Output.make(~status="Output") + +let outputForLessonIndex = index => + if index === 0 { + GuideCompilerFeedback.Output.initial + } else { + emptyOutput() + } + +let docsIntroUrl = "https://rescript-lang.org/docs/manual/introduction" + +let navigateToLesson = (~goToHash, ~setLessonIndex, ~setOutput, ~lessons, index) => { + let lesson = GuideLesson.lessonAt(~lessons, index) + goToHash(lesson->GuideLesson.hashForLesson) + setLessonIndex(_ => index) + setOutput(_ => index->outputForLessonIndex) +} + +let useLessonNavigation = (~lessons, ~goToDocsIntro): t => { + let location = ReactRouter.useLocation() + let navigate = ReactRouter.useNavigate() + let goToHash = hash => navigate(hash) + let (lessonIndex, setLessonIndex) = React.useState(() => 0) + let (output, setOutput) = React.useState(() => GuideCompilerFeedback.Output.initial) + + React.useEffect(() => { + let currentHash = location.hash->Option.getOr("") + let nextLessonIndex = GuideLesson.indexForHash(~lessons, currentHash) + let nextLesson = GuideLesson.lessonAt(~lessons, nextLessonIndex) + let nextLessonHash = nextLesson->GuideLesson.hashForLesson + + setLessonIndex(_ => nextLessonIndex) + setOutput(_ => nextLessonIndex->outputForLessonIndex) + + // Keep the hash canonical so direct links, browser back, and MemoryRouter tests share one path. + if currentHash !== nextLessonHash { + navigate(nextLessonHash, ~options={replace: true}) + } + + None + }, (location.hash, navigate, lessons)) + + let lesson = GuideLesson.lessonAt(~lessons, lessonIndex) + let exercise = lesson.exercise + let hasPreviousLesson = GuideLesson.hasPreviousLesson(lessonIndex) + let hasNextLesson = GuideLesson.hasNextLesson(~lessons, lessonIndex) + let exercisePassed = GuideLesson.isExerciseComplete(~exercise, ~output) + let checkpointComplete = exercisePassed || GuideLayout.isExerciseCompleted(exercise.id) + let forwardActionEnabled = hasNextLesson || checkpointComplete + + React.useEffect(() => { + if exercisePassed { + GuideLayout.saveCompletedExercise(exercise.id) + } + None + }, (exercisePassed, exercise.id)) + + let goToPreviousLesson = _event => { + if hasPreviousLesson { + let previousLessonIndex = lessonIndex - 1 + navigateToLesson(~goToHash, ~setLessonIndex, ~setOutput, ~lessons, previousLessonIndex) + } + } + + let goToNextLesson = _event => { + if hasNextLesson { + let nextLessonIndex = lessonIndex + 1 + navigateToLesson(~goToHash, ~setLessonIndex, ~setOutput, ~lessons, nextLessonIndex) + } else if checkpointComplete { + goToDocsIntro(docsIntroUrl) + } + } + + { + lesson, + output, + setOutput, + hasPreviousLesson, + hasNextLesson, + checkpointComplete, + forwardActionEnabled, + goToPreviousLesson, + goToNextLesson, + } +} diff --git a/apps/guide/app/GuideOutputPanel.res b/apps/guide/app/GuideOutputPanel.res new file mode 100644 index 000000000..7197f2a11 --- /dev/null +++ b/apps/guide/app/GuideOutputPanel.res @@ -0,0 +1,44 @@ +@react.component +let make = (~output: GuideCompilerFeedback.Output.t) => { +
+ {switch output.status { + | "Output" => React.null + | status =>
{React.string(status)}
+ }} + {switch output.diagnostics { + | [] => React.null + | diagnostics => +
+
{React.string("Diagnostics")}
+ {diagnostics + ->Array.mapWithIndex((diagnostic, index) => +
Int.toString} className="guide-output-line guide-output-line-error">
+            {React.string(diagnostic)}
+          
+ ) + ->React.array} +
+ }} + {switch output.runtimeLogs { + | [] => React.null + | runtimeLogs => +
+
{React.string("Result")}
+ {runtimeLogs + ->Array.mapWithIndex(({level, content}, index) => +
Int.toString}
+            className={switch level {
+            | #log => "guide-output-line"
+            | #warn => "guide-output-line guide-output-line-warning"
+            | #error => "guide-output-line guide-output-line-error"
+            }}
+          >
+            {React.string(content->Array.join(" "))}
+          
+ ) + ->React.array} +
+ }} +
+} diff --git a/apps/guide/app/GuideRuntimeImport.res b/apps/guide/app/GuideRuntimeImport.res new file mode 100644 index 000000000..f54782a6d --- /dev/null +++ b/apps/guide/app/GuideRuntimeImport.res @@ -0,0 +1,39 @@ +let capitalizeFirstLetter = string => { + let firstLetter = string->String.charAt(0)->String.toUpperCase + `${firstLetter}${string->String.slice(~start=1)}` +} + +let url = (~bundleBaseUrl, ~compilerVersion: Semver.t, path) => { + let filename = path->String.slice(~start=9) + let filename = switch compilerVersion { + | {major: 12, minor: 0, patch: 0, preRelease: Some(Alpha(alpha))} if alpha < 8 => + let filename = if filename->String.startsWith("core__") { + filename->String.slice(~start=6) + } else { + filename + } + filename->capitalizeFirstLetter + | {major} if major < 12 && filename->String.startsWith("core__") => + filename->capitalizeFirstLetter + | _ => filename + } + + // Older compiler builds emitted stdlib import paths that no longer match the CDN layout. + let compilerVersion = switch compilerVersion { + | {major: 12, minor: 0, patch: 0, preRelease: Some(Alpha(alpha))} if alpha < 9 => { + Semver.major: 12, + minor: 0, + patch: 0, + preRelease: Some(Alpha(9)), + } + | {major, minor} if (major === 11 && minor < 2) || major < 11 => { + major: 11, + minor: 2, + patch: 0, + preRelease: Some(Beta(2)), + } + | version => version + } + + CompilerManagerHook.CdnMeta.getStdlibRuntimeUrl(bundleBaseUrl, compilerVersion, filename) +} From d46e5eb0f572040b2e09ba87b47e51457552a776 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Tue, 5 May 2026 19:17:07 -0400 Subject: [PATCH 28/32] test: split guide coverage Break the guide browser tests into focused suites for compiler feedback, compiler settings, lessons, layout persistence, output rendering, and runtime transforms. Add runtime import helper coverage and keep GuideHome tests focused on integrated browser behavior. --- .../__tests__/GuideCompilerFeedback_.test.res | 83 ++++ .../__tests__/GuideCompilerSettings_.test.res | 18 + apps/guide/__tests__/GuideHome_.test.res | 409 +----------------- apps/guide/__tests__/GuideLayout_.test.res | 65 +++ apps/guide/__tests__/GuideLesson_.test.res | 59 +++ .../__tests__/GuideOutputPanel_.test.res | 37 ++ apps/guide/__tests__/GuideRuntime_.test.res | 141 ++++++ apps/guide/app/GuideRuntimeImport.res | 12 +- apps/guide/src/test/GuideTestFixtures.res | 62 +++ 9 files changed, 479 insertions(+), 407 deletions(-) create mode 100644 apps/guide/__tests__/GuideCompilerFeedback_.test.res create mode 100644 apps/guide/__tests__/GuideCompilerSettings_.test.res create mode 100644 apps/guide/__tests__/GuideLayout_.test.res create mode 100644 apps/guide/__tests__/GuideLesson_.test.res create mode 100644 apps/guide/__tests__/GuideOutputPanel_.test.res create mode 100644 apps/guide/__tests__/GuideRuntime_.test.res create mode 100644 apps/guide/src/test/GuideTestFixtures.res diff --git a/apps/guide/__tests__/GuideCompilerFeedback_.test.res b/apps/guide/__tests__/GuideCompilerFeedback_.test.res new file mode 100644 index 000000000..c426035bb --- /dev/null +++ b/apps/guide/__tests__/GuideCompilerFeedback_.test.res @@ -0,0 +1,83 @@ +open Vitest + +test("maps compiler diagnostics into editor errors", async () => { + let locMsg: RescriptCompilerApi.LocMsg.t = { + fullMsg: "Full compiler error", + shortMsg: "Expected a string", + row: 2, + column: 7, + endRow: 2, + endColumn: 12, + } + + let editorError = GuideCompilerFeedback.locMsgToEditorError(~kind=#Error, locMsg) + + expect(editorError.row)->toBe(2) + expect(editorError.column)->toBe(7) + expect(editorError.endRow)->toBe(2) + expect(editorError.endColumn)->toBe(12) + expect(editorError.text)->toBe("Expected a string") + + let outputLine = + GuideCompilerFeedback.compileFailToOutputLines(TypecheckErr([locMsg])) + ->Array.get(0) + ->Option.getOrThrow + expect(outputLine)->toBe("[E] Line 2, 7: Expected a string") +}) + +test("maps compiler type hints into hover hints", async () => { + let typeHint = RescriptCompilerApi.TypeHint.Binding({ + start: {line: 1, col: 4}, + end: {line: 1, col: 12}, + hint: "string", + }) + + let hoverHint = + GuideCompilerFeedback.typeHintsToHoverHints([typeHint])->Array.get(0)->Option.getOrThrow + + expect(hoverHint.start.line)->toBe(1) + expect(hoverHint.start.col)->toBe(4) + expect(hoverHint.end.line)->toBe(1) + expect(hoverHint.end.col)->toBe(12) + expect(hoverHint.hint)->toBe("string") +}) + +test("keeps the previous output while a successful compile waits for runtime logs", async () => { + let typeHint = RescriptCompilerApi.TypeHint.Binding({ + start: {line: 1, col: 4}, + end: {line: 1, col: 12}, + hint: "string", + }) + + let outputUpdate = GuideCompilerFeedback.compilationResultToOutputUpdate( + Success({ + jsCode: "let value = 52;", + warnings: [], + typeHints: [typeHint], + time: 1.0, + }), + ) + + expect(outputUpdate->Option.isNone)->toBe(true) +}) + +test("shows compiler errors as output updates", async () => { + let locMsg: RescriptCompilerApi.LocMsg.t = { + fullMsg: "Full compiler error", + shortMsg: "Expected a string", + row: 2, + column: 7, + endRow: 2, + endColumn: 12, + } + + let output = + GuideCompilerFeedback.compilationResultToOutputUpdate( + Fail(TypecheckErr([locMsg])), + )->Option.getOrThrow + + expect(output.status)->toBe("Compiler error") + expect(output.diagnostics->Array.get(0)->Option.getOrThrow)->toBe( + "[E] Line 2, 7: Expected a string", + ) +}) diff --git a/apps/guide/__tests__/GuideCompilerSettings_.test.res b/apps/guide/__tests__/GuideCompilerSettings_.test.res new file mode 100644 index 000000000..853643400 --- /dev/null +++ b/apps/guide/__tests__/GuideCompilerSettings_.test.res @@ -0,0 +1,18 @@ +open Vitest + +test("selects the latest stable ReScript compiler with ESM output", async () => { + let version = + GuideCompilerSettings.latestStableVersion([ + "v12.1.0-alpha.1", + "v11.1.4", + "v12.0.0", + "v12.2.0-beta.1", + "v12.1.3", + ]) + ->Option.getOrThrow + ->Semver.toString + + expect(version)->toBe("v12.1.3") + expect(GuideCompilerSettings.moduleSystem)->toBe("esmodule") + expect(GuideCompilerSettings.warnFlags->String.includes("-109"))->toBe(true) +}) diff --git a/apps/guide/__tests__/GuideHome_.test.res b/apps/guide/__tests__/GuideHome_.test.res index 8ab23ee0f..21fade5f9 100644 --- a/apps/guide/__tests__/GuideHome_.test.res +++ b/apps/guide/__tests__/GuideHome_.test.res @@ -1,409 +1,10 @@ open Vitest -let firstLesson: GuideLesson.t = { - id: "first-contact", - position: 1, - sourcePath: "app/lessons/01-first-contact.mdx", - missionLabel: "Mission 01", - title: "Learn ReScript Guide", - description: "Run a small ReScript program and inspect its output.", - content: `This interactive guide introduces ReScript through small examples, steady practice, and a live output log. - -The editor runs automatically. For this first checkpoint, the final value should print \`hello, world!\` in the output log.`, - exercise: { - id: "first-contact/greeting", - title: "Send a greeting", - initialCode: `let greeting = "hello, world!"`, - check: ExpectedOutput("hello, world!"), - }, -} - -let secondLesson: GuideLesson.t = { - id: "functions", - position: 2, - sourcePath: "app/lessons/02-functions.mdx", - missionLabel: "Mission 02", - title: "Call A Function", - description: "Change a function call and inspect the result.", - content: `Functions take values as input and return a new value. - -Change the argument passed to \`greet\` from \`ReScript\` to \`Spock\`.`, - exercise: { - id: "functions/greet-spock", - title: "Greet Spock", - initialCode: `let greet = name => "Hello, " ++ name ++ "!" - -let greeting = greet("ReScript")`, - check: ExpectedOutput("Hello, Spock!"), - }, -} - -let guideLessons = [firstLesson, secondLesson] - -let renderGuideHome = (~initialEntries=["/"], ()) => - render( - - - , - ) - -let renderGuideHomeWithDocsIntroNavigation = (goToDocsIntro, ~initialEntries=["/"]) => - render( - - - , - ) - -let renderGuideHomeInBrowser = () => - render( - - - , - ) - -test("selects the latest stable ReScript compiler with ESM output", async () => { - let version = - GuideCompilerSettings.latestStableVersion([ - "v12.1.0-alpha.1", - "v11.1.4", - "v12.0.0", - "v12.2.0-beta.1", - "v12.1.3", - ]) - ->Option.getOrThrow - ->Semver.toString - - expect(version)->toBe("v12.1.3") - expect(GuideCompilerSettings.moduleSystem)->toBe("esmodule") - expect(GuideCompilerSettings.warnFlags->String.includes("-109"))->toBe(true) -}) - -test("maps compiler diagnostics into editor errors", async () => { - let locMsg: RescriptCompilerApi.LocMsg.t = { - fullMsg: "Full compiler error", - shortMsg: "Expected a string", - row: 2, - column: 7, - endRow: 2, - endColumn: 12, - } - - let editorError = GuideCompilerFeedback.locMsgToEditorError(~kind=#Error, locMsg) - - expect(editorError.row)->toBe(2) - expect(editorError.column)->toBe(7) - expect(editorError.endRow)->toBe(2) - expect(editorError.endColumn)->toBe(12) - expect(editorError.text)->toBe("Expected a string") - - let outputLine = - GuideCompilerFeedback.compileFailToOutputLines(TypecheckErr([locMsg])) - ->Array.get(0) - ->Option.getOrThrow - expect(outputLine)->toBe("[E] Line 2, 7: Expected a string") -}) - -test("maps compiler type hints into hover hints", async () => { - let typeHint = RescriptCompilerApi.TypeHint.Binding({ - start: {line: 1, col: 4}, - end: {line: 1, col: 12}, - hint: "string", - }) - - let hoverHint = - GuideCompilerFeedback.typeHintsToHoverHints([typeHint])->Array.get(0)->Option.getOrThrow - - expect(hoverHint.start.line)->toBe(1) - expect(hoverHint.start.col)->toBe(4) - expect(hoverHint.end.line)->toBe(1) - expect(hoverHint.end.col)->toBe(12) - expect(hoverHint.hint)->toBe("string") -}) - -test("keeps the previous output while a successful compile waits for runtime logs", async () => { - let typeHint = RescriptCompilerApi.TypeHint.Binding({ - start: {line: 1, col: 4}, - end: {line: 1, col: 12}, - hint: "string", - }) - - let outputUpdate = GuideCompilerFeedback.compilationResultToOutputUpdate( - Success({ - jsCode: "let value = 52;", - warnings: [], - typeHints: [typeHint], - time: 1.0, - }), - ) - - expect(outputUpdate->Option.isNone)->toBe(true) -}) - -test("shows compiler errors as output updates", async () => { - let locMsg: RescriptCompilerApi.LocMsg.t = { - fullMsg: "Full compiler error", - shortMsg: "Expected a string", - row: 2, - column: 7, - endRow: 2, - endColumn: 12, - } - - let output = - GuideCompilerFeedback.compilationResultToOutputUpdate( - Fail(TypecheckErr([locMsg])), - )->Option.getOrThrow - - expect(output.status)->toBe("Compiler error") - expect(output.diagnostics->Array.get(0)->Option.getOrThrow)->toBe( - "[E] Line 2, 7: Expected a string", - ) -}) - -test("checks expected output for the first lesson exercise", async () => { - let matchingOutput = GuideCompilerFeedback.Output.make( - ~status="Output", - ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["hello, world!"]}], - ) - let nonMatchingOutput = GuideCompilerFeedback.Output.make( - ~status="Output", - ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["goodbye"]}], - ) - - expect(firstLesson.exercise.initialCode)->toBe(`let greeting = "hello, world!"`) - expect( - GuideLesson.isExerciseComplete(~exercise=firstLesson.exercise, ~output=matchingOutput), - )->toBe(true) - expect( - GuideLesson.isExerciseComplete(~exercise=firstLesson.exercise, ~output=nonMatchingOutput), - )->toBe(false) -}) - -test("checks expected output for the function argument exercise", async () => { - let matchingOutput = GuideCompilerFeedback.Output.make( - ~status="Output", - ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["Hello, Spock!"]}], - ) - let nonMatchingOutput = GuideCompilerFeedback.Output.make( - ~status="Output", - ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["Hello, ReScript!"]}], - ) - - expect(secondLesson.exercise.initialCode)->toBe(`let greet = name => "Hello, " ++ name ++ "!" - -let greeting = greet("ReScript")`) - expect( - GuideLesson.isExerciseComplete(~exercise=secondLesson.exercise, ~output=matchingOutput), - )->toBe(true) - expect( - GuideLesson.isExerciseComplete(~exercise=secondLesson.exercise, ~output=nonMatchingOutput), - )->toBe(false) -}) - -test("orders guide lessons by position", async () => { - let ordered = [secondLesson, firstLesson]->GuideLesson.sort - - expect(ordered->Array.get(0)->Option.getOrThrow)->toBe(firstLesson) - expect(ordered->Array.get(1)->Option.getOrThrow)->toBe(secondLesson) -}) - -test("renders output diagnostics as error lines", async () => { - let output = GuideCompilerFeedback.Output.make( - ~status="Compiler error", - ~diagnostics=["[E] Line 2, 7: Expected a string"], - ) - - let screen = await render() - let diagnostic = await screen->getByText("[E] Line 2, 7: Expected a string") - - await diagnostic->element->toHaveClass("guide-output-line-error") -}) - -test("rewrites the compiled final expression into a console log", async () => { - let result = GuideRuntimeTransform.transform(`let value = 52; -value; - -export { - value, -};`) - - let {code, imports} = result->Option.getOrThrow - - expect(code->String.includes("console.log(value)"))->toBe(true) - expect(code->String.includes("export"))->toBe(false) - expect(imports->Dict.keysToArray->Array.length)->toBe(0) -}) - -test("rewrites the guide runtime result binding into a console log", async () => { - let result = GuideRuntimeTransform.transform( - ~resultBindingName=GuideRuntimeSource.resultBindingName, - `let __rescriptGuideOutput = 52; - -export { - __rescriptGuideOutput, -};`, - ) - - let {code} = result->Option.getOrThrow - expect(code->String.includes("console.log(__rescriptGuideOutput)"))->toBe(true) -}) - -test("rewrites the last compiled binding into a console log", async () => { - let result = GuideRuntimeTransform.transform(`let greeting = "hello, world!"; - -export { - greeting, -};`) - - let {code} = result->Option.getOrThrow - expect(code->String.includes("console.log(greeting)"))->toBe(true) -}) - -test("skips runtime execution when compiled code has no expression or binding", async () => { - let result = GuideRuntimeTransform.transform(`export {};`) - - expect(result->Option.isNone)->toBe(true) -}) - -test("instruments the compiler-reported final expression range", async () => { - let code = `let greeting = "hello, world!" - -greeting` - let typeHints = [ - RescriptCompilerApi.TypeHint.Binding({ - start: {line: 1, col: 0}, - end: {line: 1, col: 30}, - hint: "string", - }), - RescriptCompilerApi.TypeHint.Expression({ - start: {line: 3, col: 0}, - end: {line: 3, col: 8}, - hint: "string", - }), - ] - - let instrumentedCode = GuideRuntimeSource.instrument(~code, ~typeHints)->Option.getOrThrow - - expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (greeting)"))->toBe(true) -}) - -test("prefers the outermost latest expression range", async () => { - let code = `let add = (a, b) => a + b - -add(10, 42)` - let typeHints = [ - RescriptCompilerApi.TypeHint.Expression({ - start: {line: 3, col: 8}, - end: {line: 3, col: 10}, - hint: "int", - }), - RescriptCompilerApi.TypeHint.Expression({ - start: {line: 3, col: 0}, - end: {line: 3, col: 11}, - hint: "int", - }), - RescriptCompilerApi.TypeHint.Binding({ - start: {line: 1, col: 0}, - end: {line: 1, col: 25}, - hint: "(int, int) => int", - }), - ] - - let instrumentedCode = GuideRuntimeSource.instrument(~code, ~typeHints)->Option.getOrThrow - - expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (add(10, 42))"))->toBe(true) -}) - -test("does not instrument expressions nested inside a binding", async () => { - let code = `let value = 52` - let typeHints = [ - RescriptCompilerApi.TypeHint.Expression({ - start: {line: 1, col: 12}, - end: {line: 1, col: 14}, - hint: "int", - }), - ] - - expect(GuideRuntimeSource.instrument(~code, ~typeHints)->Option.isNone)->toBe(true) -}) - -test("renders runtime logs in the output panel", async () => { - let output = GuideCompilerFeedback.Output.make( - ~status="Output", - ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["52"]}], - ) - - let screen = await render() - - await (await screen->getByText("Result"))->element->toBeVisible - await (await screen->getByText("52"))->element->toBeVisible -}) - -test("does not render redundant output status inside the output panel", async () => { - let output = GuideCompilerFeedback.Output.make( - ~status="Output", - ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["52"]}], - ) - - let screen = await render() - let outputText = screen->container->textContent->Nullable.toOption->Option.getOrThrow - - expect(outputText->String.includes("Output"))->toBe(false) -}) - -test("clamps resized pane dimensions", async () => { - expect(GuideLayout.clampInstructionsWidth(~viewportWidth=1200.0, ~pointerX=120.0))->toBe(320.0) - expect(GuideLayout.clampInstructionsWidth(~viewportWidth=1200.0, ~pointerX=900.0))->toBe(720.0) - expect(GuideLayout.clampInstructionsWidth(~viewportWidth=1200.0, ~pointerX=520.0))->toBe(520.0) - - expect(GuideLayout.clampOutputHeight(~viewportHeight=900.0, ~pointerY=820.0))->toBe(160.0) - expect(GuideLayout.clampOutputHeight(~viewportHeight=900.0, ~pointerY=120.0))->toBe(660.0) - expect(GuideLayout.clampOutputHeight(~viewportHeight=900.0, ~pointerY=640.0))->toBe(260.0) -}) - -test("stores resized pane dimensions in local storage", async () => { - GuideLayout.clearPaneSizes() - - GuideLayout.savePaneSizes({ - instructionsWidth: Some(420.0), - outputHeight: 250.0, - }) - - let savedPaneSizes = GuideLayout.loadPaneSizes() - let savedInstructionsWidth = - savedPaneSizes.instructionsWidth->Option.map(width => width->Float.toString)->Option.getOrThrow - - expect(savedInstructionsWidth)->toBe("420") - expect(savedPaneSizes.outputHeight)->toBe(250.0) - - GuideLayout.clearPaneSizes() -}) - -test("stores guide exercise code in local storage", async () => { - let exerciseId = firstLesson.exercise.id - GuideLayout.clearExerciseCode(exerciseId) - - GuideLayout.saveExerciseCode(~exerciseId, ~code="let voyager = 1701") - - expect(GuideLayout.loadExerciseCode(exerciseId)->Option.getOrThrow)->toBe("let voyager = 1701") - - GuideLayout.clearExerciseCode(exerciseId) -}) - -test("stores completed guide exercises in local storage", async () => { - let exerciseId = firstLesson.exercise.id - GuideLayout.clearCompletedExercises() - - GuideLayout.saveCompletedExercise(exerciseId) - GuideLayout.saveCompletedExercise(exerciseId) - - let completedExerciseIds = GuideLayout.loadCompletedExerciseIds() - - expect(completedExerciseIds->Array.includes(exerciseId))->toBe(true) - expect(completedExerciseIds->Array.length)->toBe(1) - - GuideLayout.clearCompletedExercises() -}) +let firstLesson = GuideTestFixtures.firstLesson +let secondLesson = GuideTestFixtures.secondLesson +let renderGuideHome = GuideTestFixtures.renderGuideHome +let renderGuideHomeWithDocsIntroNavigation = GuideTestFixtures.renderGuideHomeWithDocsIntroNavigation +let renderGuideHomeInBrowser = GuideTestFixtures.renderGuideHomeInBrowser test("loads saved guide editor code into the editor", async () => { await viewport(1440, 900) diff --git a/apps/guide/__tests__/GuideLayout_.test.res b/apps/guide/__tests__/GuideLayout_.test.res new file mode 100644 index 000000000..11e1672c7 --- /dev/null +++ b/apps/guide/__tests__/GuideLayout_.test.res @@ -0,0 +1,65 @@ +open Vitest + +test("clamps resized pane dimensions", async () => { + expect(GuideLayout.clampInstructionsWidth(~viewportWidth=1200.0, ~pointerX=120.0))->toBe(320.0) + expect(GuideLayout.clampInstructionsWidth(~viewportWidth=1200.0, ~pointerX=900.0))->toBe(720.0) + expect(GuideLayout.clampInstructionsWidth(~viewportWidth=1200.0, ~pointerX=520.0))->toBe(520.0) + + expect(GuideLayout.clampOutputHeight(~viewportHeight=900.0, ~pointerY=820.0))->toBe(160.0) + expect(GuideLayout.clampOutputHeight(~viewportHeight=900.0, ~pointerY=120.0))->toBe(660.0) + expect(GuideLayout.clampOutputHeight(~viewportHeight=900.0, ~pointerY=640.0))->toBe(260.0) +}) + +test("serializes pane sizes as guide CSS variables", async () => { + let style = GuideLayout.paneSizesStyle({ + instructionsWidth: Some(420.0), + outputHeight: 250.0, + }) + + expect(style->String.includes("--guide-instructions-width: 420px"))->toBe(true) + expect(style->String.includes("--guide-output-height: 250px"))->toBe(true) +}) + +test("stores resized pane dimensions in local storage", async () => { + GuideLayout.clearPaneSizes() + + GuideLayout.savePaneSizes({ + instructionsWidth: Some(420.0), + outputHeight: 250.0, + }) + + let savedPaneSizes = GuideLayout.loadPaneSizes() + let savedInstructionsWidth = + savedPaneSizes.instructionsWidth->Option.map(width => width->Float.toString)->Option.getOrThrow + + expect(savedInstructionsWidth)->toBe("420") + expect(savedPaneSizes.outputHeight)->toBe(250.0) + + GuideLayout.clearPaneSizes() +}) + +test("stores guide exercise code in local storage", async () => { + let exerciseId = GuideTestFixtures.firstLesson.exercise.id + GuideLayout.clearExerciseCode(exerciseId) + + GuideLayout.saveExerciseCode(~exerciseId, ~code="let voyager = 1701") + + expect(GuideLayout.loadExerciseCode(exerciseId)->Option.getOrThrow)->toBe("let voyager = 1701") + + GuideLayout.clearExerciseCode(exerciseId) +}) + +test("stores completed guide exercises in local storage", async () => { + let exerciseId = GuideTestFixtures.firstLesson.exercise.id + GuideLayout.clearCompletedExercises() + + GuideLayout.saveCompletedExercise(exerciseId) + GuideLayout.saveCompletedExercise(exerciseId) + + let completedExerciseIds = GuideLayout.loadCompletedExerciseIds() + + expect(completedExerciseIds->Array.includes(exerciseId))->toBe(true) + expect(completedExerciseIds->Array.length)->toBe(1) + + GuideLayout.clearCompletedExercises() +}) diff --git a/apps/guide/__tests__/GuideLesson_.test.res b/apps/guide/__tests__/GuideLesson_.test.res new file mode 100644 index 000000000..47dbea768 --- /dev/null +++ b/apps/guide/__tests__/GuideLesson_.test.res @@ -0,0 +1,59 @@ +open Vitest + +let firstLesson = GuideTestFixtures.firstLesson +let secondLesson = GuideTestFixtures.secondLesson +let guideLessons = GuideTestFixtures.guideLessons + +test("checks expected output for the first lesson exercise", async () => { + let matchingOutput = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["hello, world!"]}], + ) + let nonMatchingOutput = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["goodbye"]}], + ) + + expect(firstLesson.exercise.initialCode)->toBe(`let greeting = "hello, world!"`) + expect( + GuideLesson.isExerciseComplete(~exercise=firstLesson.exercise, ~output=matchingOutput), + )->toBe(true) + expect( + GuideLesson.isExerciseComplete(~exercise=firstLesson.exercise, ~output=nonMatchingOutput), + )->toBe(false) +}) + +test("checks expected output for the function argument exercise", async () => { + let matchingOutput = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["Hello, Spock!"]}], + ) + let nonMatchingOutput = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["Hello, ReScript!"]}], + ) + + expect(secondLesson.exercise.initialCode)->toBe(`let greet = name => "Hello, " ++ name ++ "!" + +let greeting = greet("ReScript")`) + expect( + GuideLesson.isExerciseComplete(~exercise=secondLesson.exercise, ~output=matchingOutput), + )->toBe(true) + expect( + GuideLesson.isExerciseComplete(~exercise=secondLesson.exercise, ~output=nonMatchingOutput), + )->toBe(false) +}) + +test("orders guide lessons by position", async () => { + let ordered = [secondLesson, firstLesson]->GuideLesson.sort + + expect(ordered->Array.get(0)->Option.getOrThrow)->toBe(firstLesson) + expect(ordered->Array.get(1)->Option.getOrThrow)->toBe(secondLesson) +}) + +test("resolves lesson hashes and falls back to the first lesson", async () => { + expect(GuideLesson.indexForHash(~lessons=guideLessons, "#functions"))->toBe(1) + expect(GuideLesson.indexForHash(~lessons=guideLessons, "functions"))->toBe(1) + expect(GuideLesson.indexForHash(~lessons=guideLessons, "#missing"))->toBe(0) + expect(GuideLesson.lessonAt(~lessons=guideLessons, 99))->toBe(firstLesson) +}) diff --git a/apps/guide/__tests__/GuideOutputPanel_.test.res b/apps/guide/__tests__/GuideOutputPanel_.test.res new file mode 100644 index 000000000..0cf2003de --- /dev/null +++ b/apps/guide/__tests__/GuideOutputPanel_.test.res @@ -0,0 +1,37 @@ +open Vitest + +test("renders output diagnostics as error lines", async () => { + let output = GuideCompilerFeedback.Output.make( + ~status="Compiler error", + ~diagnostics=["[E] Line 2, 7: Expected a string"], + ) + + let screen = await render() + let diagnostic = await screen->getByText("[E] Line 2, 7: Expected a string") + + await diagnostic->element->toHaveClass("guide-output-line-error") +}) + +test("renders runtime logs in the output panel", async () => { + let output = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["52"]}], + ) + + let screen = await render() + + await (await screen->getByText("Result"))->element->toBeVisible + await (await screen->getByText("52"))->element->toBeVisible +}) + +test("does not render redundant output status inside the output panel", async () => { + let output = GuideCompilerFeedback.Output.make( + ~status="Output", + ~runtimeLogs=[{GuideCompilerFeedback.Output.level: #log, content: ["52"]}], + ) + + let screen = await render() + let outputText = screen->container->textContent->Nullable.toOption->Option.getOrThrow + + expect(outputText->String.includes("Output"))->toBe(false) +}) diff --git a/apps/guide/__tests__/GuideRuntime_.test.res b/apps/guide/__tests__/GuideRuntime_.test.res new file mode 100644 index 000000000..8c708ea54 --- /dev/null +++ b/apps/guide/__tests__/GuideRuntime_.test.res @@ -0,0 +1,141 @@ +open Vitest + +test("rewrites the compiled final expression into a console log", async () => { + let result = GuideRuntimeTransform.transform(`let value = 52; +value; + +export { + value, +};`) + + let {code, imports} = result->Option.getOrThrow + + expect(code->String.includes("console.log(value)"))->toBe(true) + expect(code->String.includes("export"))->toBe(false) + expect(imports->Dict.keysToArray->Array.length)->toBe(0) +}) + +test("rewrites the guide runtime result binding into a console log", async () => { + let result = GuideRuntimeTransform.transform( + ~resultBindingName=GuideRuntimeSource.resultBindingName, + `let __rescriptGuideOutput = 52; + +export { + __rescriptGuideOutput, +};`, + ) + + let {code} = result->Option.getOrThrow + expect(code->String.includes("console.log(__rescriptGuideOutput)"))->toBe(true) +}) + +test("rewrites the last compiled binding into a console log", async () => { + let result = GuideRuntimeTransform.transform(`let greeting = "hello, world!"; + +export { + greeting, +};`) + + let {code} = result->Option.getOrThrow + expect(code->String.includes("console.log(greeting)"))->toBe(true) +}) + +test("skips runtime execution when compiled code has no expression or binding", async () => { + let result = GuideRuntimeTransform.transform(`export {};`) + + expect(result->Option.isNone)->toBe(true) +}) + +test("instruments the compiler-reported final expression range", async () => { + let code = `let greeting = "hello, world!" + +greeting` + let typeHints = [ + RescriptCompilerApi.TypeHint.Binding({ + start: {line: 1, col: 0}, + end: {line: 1, col: 30}, + hint: "string", + }), + RescriptCompilerApi.TypeHint.Expression({ + start: {line: 3, col: 0}, + end: {line: 3, col: 8}, + hint: "string", + }), + ] + + let instrumentedCode = GuideRuntimeSource.instrument(~code, ~typeHints)->Option.getOrThrow + + expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (greeting)"))->toBe(true) +}) + +test("prefers the outermost latest expression range", async () => { + let code = `let add = (a, b) => a + b + +add(10, 42)` + let typeHints = [ + RescriptCompilerApi.TypeHint.Expression({ + start: {line: 3, col: 8}, + end: {line: 3, col: 10}, + hint: "int", + }), + RescriptCompilerApi.TypeHint.Expression({ + start: {line: 3, col: 0}, + end: {line: 3, col: 11}, + hint: "int", + }), + RescriptCompilerApi.TypeHint.Binding({ + start: {line: 1, col: 0}, + end: {line: 1, col: 25}, + hint: "(int, int) => int", + }), + ] + + let instrumentedCode = GuideRuntimeSource.instrument(~code, ~typeHints)->Option.getOrThrow + + expect(instrumentedCode->String.includes("let __rescriptGuideOutput = (add(10, 42))"))->toBe(true) +}) + +test("does not instrument expressions nested inside a binding", async () => { + let code = `let value = 52` + let typeHints = [ + RescriptCompilerApi.TypeHint.Expression({ + start: {line: 1, col: 12}, + end: {line: 1, col: 14}, + hint: "int", + }), + ] + + expect(GuideRuntimeSource.instrument(~code, ~typeHints)->Option.isNone)->toBe(true) +}) + +test("normalizes old compiler runtime import filenames", async () => { + let alpha7 = Semver.parse("v12.0.0-alpha.7")->Option.getOrThrow + let oldV11 = Semver.parse("v11.1.4")->Option.getOrThrow + let stableV12 = Semver.parse("v12.0.0")->Option.getOrThrow + + expect( + GuideRuntimeImport.filenameForCompiler(~compilerVersion=alpha7, "./stdlib/core__array"), + )->toBe("Array") + expect( + GuideRuntimeImport.filenameForCompiler(~compilerVersion=oldV11, "./stdlib/core__array"), + )->toBe("Core__array") + expect( + GuideRuntimeImport.filenameForCompiler(~compilerVersion=stableV12, "./stdlib/core__array"), + )->toBe("core__array") +}) + +test("normalizes old compiler versions for runtime imports", async () => { + let alpha7 = Semver.parse("v12.0.0-alpha.7")->Option.getOrThrow + let alpha9 = Semver.parse("v12.0.0-alpha.9")->Option.getOrThrow + let oldV11 = Semver.parse("v11.1.4")->Option.getOrThrow + + expect(GuideRuntimeImport.compilerVersionForRuntimeImport(alpha7)->Semver.toString)->toBe( + "v12.0.0-alpha.9", + ) + expect(GuideRuntimeImport.compilerVersionForRuntimeImport(alpha9)->Semver.toString)->toBe( + "v12.0.0-alpha.9", + ) + expect(GuideRuntimeImport.compilerVersionForRuntimeImport(oldV11)->Semver.toString)->toBe( + "v11.2.0-beta.2", + ) +}) diff --git a/apps/guide/app/GuideRuntimeImport.res b/apps/guide/app/GuideRuntimeImport.res index f54782a6d..dac45a148 100644 --- a/apps/guide/app/GuideRuntimeImport.res +++ b/apps/guide/app/GuideRuntimeImport.res @@ -3,9 +3,9 @@ let capitalizeFirstLetter = string => { `${firstLetter}${string->String.slice(~start=1)}` } -let url = (~bundleBaseUrl, ~compilerVersion: Semver.t, path) => { +let filenameForCompiler = (~compilerVersion: Semver.t, path) => { let filename = path->String.slice(~start=9) - let filename = switch compilerVersion { + switch compilerVersion { | {major: 12, minor: 0, patch: 0, preRelease: Some(Alpha(alpha))} if alpha < 8 => let filename = if filename->String.startsWith("core__") { filename->String.slice(~start=6) @@ -17,9 +17,11 @@ let url = (~bundleBaseUrl, ~compilerVersion: Semver.t, path) => { filename->capitalizeFirstLetter | _ => filename } +} +let compilerVersionForRuntimeImport = (compilerVersion: Semver.t) => // Older compiler builds emitted stdlib import paths that no longer match the CDN layout. - let compilerVersion = switch compilerVersion { + switch compilerVersion { | {major: 12, minor: 0, patch: 0, preRelease: Some(Alpha(alpha))} if alpha < 9 => { Semver.major: 12, minor: 0, @@ -35,5 +37,9 @@ let url = (~bundleBaseUrl, ~compilerVersion: Semver.t, path) => { | version => version } +let url = (~bundleBaseUrl, ~compilerVersion: Semver.t, path) => { + let filename = path->filenameForCompiler(~compilerVersion) + let compilerVersion = compilerVersion->compilerVersionForRuntimeImport + CompilerManagerHook.CdnMeta.getStdlibRuntimeUrl(bundleBaseUrl, compilerVersion, filename) } diff --git a/apps/guide/src/test/GuideTestFixtures.res b/apps/guide/src/test/GuideTestFixtures.res new file mode 100644 index 000000000..214b9d179 --- /dev/null +++ b/apps/guide/src/test/GuideTestFixtures.res @@ -0,0 +1,62 @@ +open Vitest + +let firstLesson: GuideLesson.t = { + id: "first-contact", + position: 1, + sourcePath: "app/lessons/01-first-contact.mdx", + missionLabel: "Mission 01", + title: "Learn ReScript Guide", + description: "Run a small ReScript program and inspect its output.", + content: `This interactive guide introduces ReScript through small examples, steady practice, and a live output log. + +The editor runs automatically. For this first checkpoint, the final value should print \`hello, world!\` in the output log.`, + exercise: { + id: "first-contact/greeting", + title: "Send a greeting", + initialCode: `let greeting = "hello, world!"`, + check: ExpectedOutput("hello, world!"), + }, +} + +let secondLesson: GuideLesson.t = { + id: "functions", + position: 2, + sourcePath: "app/lessons/02-functions.mdx", + missionLabel: "Mission 02", + title: "Call A Function", + description: "Change a function call and inspect the result.", + content: `Functions take values as input and return a new value. + +Change the argument passed to \`greet\` from \`ReScript\` to \`Spock\`.`, + exercise: { + id: "functions/greet-spock", + title: "Greet Spock", + initialCode: `let greet = name => "Hello, " ++ name ++ "!" + +let greeting = greet("ReScript")`, + check: ExpectedOutput("Hello, Spock!"), + }, +} + +let guideLessons = [firstLesson, secondLesson] + +let renderGuideHome = (~initialEntries=["/"], ()) => + render( + + + , + ) + +let renderGuideHomeWithDocsIntroNavigation = (goToDocsIntro, ~initialEntries=["/"]) => + render( + + + , + ) + +let renderGuideHomeInBrowser = () => + render( + + + , + ) From 941277b2cde9544c198bca406792cbd2fcf93ff8 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Tue, 5 May 2026 19:39:27 -0400 Subject: [PATCH 29/32] refactor: share Babel bindings with playground Move guide runtime transformation onto the shared Babel bindings already used by playground code. Keep Babel package ownership in the shared workspace so guide code imports the shared API instead of declaring its own bindings. --- apps/guide/app/GuideRuntimeTransform.res | 104 ++++++++--------------- apps/guide/package.json | 3 - packages/shared/package.json | 1 + packages/shared/src/Babel.res | 77 ++++++++++++++++- yarn.lock | 4 +- 5 files changed, 112 insertions(+), 77 deletions(-) diff --git a/apps/guide/app/GuideRuntimeTransform.res b/apps/guide/app/GuideRuntimeTransform.res index 4bb9ca68a..3163f6b00 100644 --- a/apps/guide/app/GuideRuntimeTransform.res +++ b/apps/guide/app/GuideRuntimeTransform.res @@ -1,68 +1,23 @@ -type expression -type statement - -type program = {mutable body: array} -type ast = {program: program} -type generatorResult = {code: string} -type source = {value: string} -type local = {name: string} -type specifier -type declarator - type t = { code: string, imports: Dict.t, } -module Parser = { - type options = {sourceType?: string} - - @module("@babel/parser") - external parse: (string, options) => ast = "parse" -} - -module Generator = { - @module("@babel/generator") - external generate: ast => generatorResult = "generate" -} - -module Types = { - @module("@babel/types") - external identifier: string => expression = "identifier" - - @module("@babel/types") - external memberExpression: (expression, expression) => expression = "memberExpression" - - @module("@babel/types") - external callExpression: (expression, array) => expression = "callExpression" - - @module("@babel/types") - external expressionStatement: expression => statement = "expressionStatement" -} - -@get external nodeType: statement => string = "type" -@get external expression: statement => expression = "expression" -@get external source: statement => source = "source" -@get external specifiers: statement => array = "specifiers" -@get external specifierLocal: specifier => local = "local" -@get external declarations: statement => array = "declarations" -@get external declaratorId: declarator => local = "id" -@get external statementId: statement => local = "id" - let isModuleBoundary = statement => { - switch statement->nodeType { + switch statement->Babel.Ast.nodeType { | "ImportDeclaration" | "ExportNamedDeclaration" => true | _ => false } } let collectRuntimeImport = (~imports, statement) => { - switch statement->nodeType { + switch statement->Babel.Ast.nodeType { | "ImportDeclaration" => - let sourceValue = (statement->source).value + let sourceValue = statement->Babel.Ast.source->Babel.Ast.stringLiteralValue if sourceValue->String.startsWith("./stdlib") { - switch statement->specifiers { - | [specifier] => imports->Dict.set((specifier->specifierLocal).name, sourceValue) + switch statement->Babel.Ast.specifiers { + | [specifier] => + imports->Dict.set(specifier->Babel.Ast.specifierLocal->Babel.Ast.lvalName, sourceValue) | _ => () } } @@ -71,37 +26,50 @@ let collectRuntimeImport = (~imports, statement) => { } let consoleLogStatement = expression => { - let consoleLog = Types.memberExpression(Types.identifier("console"), Types.identifier("log")) - Types.expressionStatement(Types.callExpression(consoleLog, [expression])) + let consoleLog = Babel.Types.memberExpression( + Babel.Types.identifier("console"), + Babel.Types.identifier("log"), + ) + Babel.Types.expressionStatement(Babel.Types.callExpression(consoleLog, [expression])) } let hasBinding = (~name, statement) => - switch statement->nodeType { + switch statement->Babel.Ast.nodeType { | "VariableDeclaration" => - statement->declarations->Array.some(declaration => (declaration->declaratorId).name === name) + statement + ->Babel.Ast.declarations + ->Array.some(declaration => + declaration->Babel.Ast.variableDeclaratorId->Babel.Ast.lvalName === name + ) | _ => false } let appendResultBindingLog = (~resultBindingName, body) => switch resultBindingName { | Some(name) if body->Array.some(statement => statement->hasBinding(~name)) => - Some(body->Array.concat([Types.identifier(name)->consoleLogStatement])) + Some(body->Array.concat([Babel.Types.identifier(name)->consoleLogStatement])) | _ => None } let variableDeclarationBindingName = statement => { - let declarations = statement->declarations + let declarations = statement->Babel.Ast.declarations switch declarations->Array.length { | 0 => None - | length => Some((declarations->Array.getUnsafe(length - 1)->declaratorId).name) + | length => + Some( + declarations + ->Array.getUnsafe(length - 1) + ->Babel.Ast.variableDeclaratorId + ->Babel.Ast.lvalName, + ) } } let bindingName = statement => - switch statement->nodeType { + switch statement->Babel.Ast.nodeType { | "VariableDeclaration" => statement->variableDeclarationBindingName - | "FunctionDeclaration" => Some((statement->statementId).name) + | "FunctionDeclaration" => Some(statement->Babel.Ast.statementId->Babel.Ast.lvalName) | _ => None } @@ -120,13 +88,13 @@ let lastBindingName = body => { let appendLastBindingLog = body => switch body->lastBindingName { - | Some(name) => Some(body->Array.concat([Types.identifier(name)->consoleLogStatement])) + | Some(name) => Some(body->Array.concat([Babel.Types.identifier(name)->consoleLogStatement])) | None => None } let transform = (~resultBindingName=?, jsCode) => try { - let ast = Parser.parse(jsCode, {sourceType: "module"}) + let ast = Babel.Parser.parse(jsCode, {sourceType: "module"}) let imports = Dict.make() ast.program.body->Array.forEach(statement => statement->collectRuntimeImport(~imports)) @@ -138,23 +106,25 @@ let transform = (~resultBindingName=?, jsCode) => let lastIndex = length - 1 let lastStatement = executableBody->Array.getUnsafe(lastIndex) - switch lastStatement->nodeType { + switch lastStatement->Babel.Ast.nodeType { | "ExpressionStatement" => ast.program.body = executableBody->Array.mapWithIndex((statement, index) => - index === lastIndex ? lastStatement->expression->consoleLogStatement : statement + index === lastIndex + ? lastStatement->Babel.Ast.expression->consoleLogStatement + : statement ) - Some({code: Generator.generate(ast).code, imports}) + Some({code: Babel.Generator.generator(ast).code, imports}) | _ => switch executableBody->appendResultBindingLog(~resultBindingName) { | Some(body) => ast.program.body = body - Some({code: Generator.generate(ast).code, imports}) + Some({code: Babel.Generator.generator(ast).code, imports}) | None => switch executableBody->appendLastBindingLog { | Some(body) => ast.program.body = body - Some({code: Generator.generate(ast).code, imports}) + Some({code: Babel.Generator.generator(ast).code, imports}) | None => None } } diff --git a/apps/guide/package.json b/apps/guide/package.json index 45456a783..68772c430 100644 --- a/apps/guide/package.json +++ b/apps/guide/package.json @@ -13,9 +13,6 @@ "vitest": "vitest" }, "dependencies": { - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.2", - "@babel/types": "^7.29.0", "@react-router/node": "^7.14.0", "@rescript-lang/playground": "workspace:*", "@rescript-lang/shared": "workspace:*", diff --git a/packages/shared/package.json b/packages/shared/package.json index 158e2fc6a..a74811bef 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -7,6 +7,7 @@ "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.2", "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@codemirror/commands": "^6.10.3", "@codemirror/lang-javascript": "^6.2.5", "@codemirror/language": "^6.12.3", diff --git a/packages/shared/src/Babel.res b/packages/shared/src/Babel.res index 93f120809..334e8b65e 100644 --- a/packages/shared/src/Babel.res +++ b/packages/shared/src/Babel.res @@ -1,5 +1,7 @@ module Ast = { - type t + type statement + type program = {mutable body: array} + type t = {program: program} @tag("type") type lval = Identifier({name: string}) @@ -37,6 +39,16 @@ module Ast = { type t = ImportDeclaration({specifiers: array, source: StringLiteral.t}) } + module ExpressionStatement = { + @tag("type") + type t = ExpressionStatement({expression: expression}) + } + + module FunctionDeclaration = { + @tag("type") + type t = FunctionDeclaration({id: lval}) + } + module Identifier = { @tag("type") type t = Identifier({mutable name: string}) @@ -49,25 +61,82 @@ module Ast = { | ...VariableDeclarator.t | ...VariableDeclaration.t | ...ImportDeclaration.t + | ...ExpressionStatement.t + | ...FunctionDeclaration.t | ...Identifier.t type nodePath<'nodeType> = {node: 'nodeType} + + @get external nodeType: statement => string = "type" + @get external expression: statement => expression = "expression" + @get external source: statement => StringLiteral.t = "source" + @get external specifiers: statement => array = "specifiers" + @get external declarations: statement => array = "declarations" + @get external statementId: statement => lval = "id" + + let lvalName = (lval: lval) => + switch lval { + | Identifier({name}) => name + } + + let stringLiteralValue = (stringLiteral: StringLiteral.t) => + switch stringLiteral { + | StringLiteral({value}) => value + } + + let specifierLocal = (specifier: Specifier.t) => + switch specifier { + | ImportSpecifier({local}) + | ImportDefaultSpecifier({local}) + | ImportNamespaceSpecifier({local}) => local + } + + let variableDeclaratorId = (variableDeclarator: VariableDeclarator.t) => + switch variableDeclarator { + | VariableDeclarator({id}) => id + } } module Parser = { type options = {sourceType?: string} - @module("@babel/parser") external parse: (string, options) => Ast.t = "parse" + @module("@babel/parser") external parse_: (string, options) => Ast.t = "parse" + + let parse = (code, options) => parse_(code, options) } module Traverse = { - @module("@babel/traverse") external traverse: (Ast.t, {..}) => unit = "default" + @module("@babel/traverse") external traverse_: (Ast.t, {..}) => unit = "default" + + let traverse = (ast, visitors) => traverse_(ast, visitors) } module Generator = { @send external remove: Ast.nodePath<'nodeType> => unit = "remove" type t = {code: string} - @module("@babel/generator") external generator: Ast.t => t = "default" + @module("@babel/generator") external generate: Ast.t => t = "generate" + + let generator = ast => generate(ast) +} + +module Types = { + @module("@babel/types") external identifier_: string => Ast.expression = "identifier" + + @module("@babel/types") + external memberExpression_: (Ast.expression, Ast.expression) => Ast.expression = + "memberExpression" + + @module("@babel/types") + external callExpression_: (Ast.expression, array) => Ast.expression = + "callExpression" + + @module("@babel/types") + external expressionStatement_: Ast.expression => Ast.statement = "expressionStatement" + + let identifier = name => identifier_(name) + let memberExpression = (object_, property) => memberExpression_(object_, property) + let callExpression = (callee, arguments) => callExpression_(callee, arguments) + let expressionStatement = expression => expressionStatement_(expression) } module PlaygroundValidator = { diff --git a/yarn.lock b/yarn.lock index b00916c92..fc8522e50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2457,9 +2457,6 @@ __metadata: version: 0.0.0-use.local resolution: "@rescript-lang/guide@workspace:apps/guide" dependencies: - "@babel/generator": "npm:^7.29.1" - "@babel/parser": "npm:^7.29.2" - "@babel/types": "npm:^7.29.0" "@react-router/dev": "npm:^7.14.0" "@react-router/node": "npm:^7.14.0" "@rescript-lang/playground": "workspace:*" @@ -2512,6 +2509,7 @@ __metadata: "@babel/generator": "npm:^7.29.1" "@babel/parser": "npm:^7.29.2" "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" "@codemirror/commands": "npm:^6.10.3" "@codemirror/lang-javascript": "npm:^6.2.5" "@codemirror/language": "npm:^6.12.3" From b662772f25c16ee01761b29c42687bc9a8a8a5d0 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Tue, 5 May 2026 19:41:14 -0400 Subject: [PATCH 30/32] docs: explain shared Babel bindings Add short comments describing why the shared Babel surface stays narrow, why externals are wrapped, and why the generator binding uses Babel's named export. --- packages/shared/src/Babel.res | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/shared/src/Babel.res b/packages/shared/src/Babel.res index 334e8b65e..101998272 100644 --- a/packages/shared/src/Babel.res +++ b/packages/shared/src/Babel.res @@ -1,4 +1,6 @@ module Ast = { + // Keep this as a narrow structural AST surface for the playground validator + // and guide runtime transform instead of mirroring all Babel node shapes. type statement type program = {mutable body: array} type t = {program: program} @@ -99,6 +101,9 @@ module Ast = { module Parser = { type options = {sourceType?: string} + + // Wrap externals so app code imports this shared module instead of re-emitting + // direct @babel/* imports from every call site. @module("@babel/parser") external parse_: (string, options) => Ast.t = "parse" let parse = (code, options) => parse_(code, options) @@ -114,6 +119,8 @@ module Generator = { @send external remove: Ast.nodePath<'nodeType> => unit = "remove" type t = {code: string} + + // The ESM path exposes the callable generator as the named export. @module("@babel/generator") external generate: Ast.t => t = "generate" let generator = ast => generate(ast) From d086372e9e3e0e2c850300c716f67cf4b1894ea5 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Tue, 5 May 2026 20:03:11 -0400 Subject: [PATCH 31/32] refactor: simplify Babel bindings Return the shared Babel module to direct externals and declare Babel dependencies on workspaces whose generated JavaScript imports them directly. --- apps/guide/package.json | 3 +++ packages/playground/package.json | 1 + packages/shared/src/Babel.res | 33 +++++++------------------------- yarn.lock | 4 ++++ 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/apps/guide/package.json b/apps/guide/package.json index 68772c430..45456a783 100644 --- a/apps/guide/package.json +++ b/apps/guide/package.json @@ -13,6 +13,9 @@ "vitest": "vitest" }, "dependencies": { + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", "@react-router/node": "^7.14.0", "@rescript-lang/playground": "workspace:*", "@rescript-lang/shared": "workspace:*", diff --git a/packages/playground/package.json b/packages/playground/package.json index 23bb0d831..7ff7ebd3d 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -4,6 +4,7 @@ "private": true, "type": "module", "dependencies": { + "@babel/parser": "^7.29.2", "@rescript-lang/shared": "workspace:*", "@rescript/react": "^0.14.2", "@rescript/webapi": "0.1.0-experimental-29db5f4", diff --git a/packages/shared/src/Babel.res b/packages/shared/src/Babel.res index 101998272..561cd4eec 100644 --- a/packages/shared/src/Babel.res +++ b/packages/shared/src/Babel.res @@ -1,6 +1,4 @@ module Ast = { - // Keep this as a narrow structural AST surface for the playground validator - // and guide runtime transform instead of mirroring all Babel node shapes. type statement type program = {mutable body: array} type t = {program: program} @@ -101,49 +99,32 @@ module Ast = { module Parser = { type options = {sourceType?: string} - - // Wrap externals so app code imports this shared module instead of re-emitting - // direct @babel/* imports from every call site. - @module("@babel/parser") external parse_: (string, options) => Ast.t = "parse" - - let parse = (code, options) => parse_(code, options) + @module("@babel/parser") external parse: (string, options) => Ast.t = "parse" } module Traverse = { - @module("@babel/traverse") external traverse_: (Ast.t, {..}) => unit = "default" - - let traverse = (ast, visitors) => traverse_(ast, visitors) + @module("@babel/traverse") external traverse: (Ast.t, {..}) => unit = "default" } module Generator = { @send external remove: Ast.nodePath<'nodeType> => unit = "remove" type t = {code: string} - - // The ESM path exposes the callable generator as the named export. - @module("@babel/generator") external generate: Ast.t => t = "generate" - - let generator = ast => generate(ast) + @module("@babel/generator") external generator: Ast.t => t = "generate" } module Types = { - @module("@babel/types") external identifier_: string => Ast.expression = "identifier" + @module("@babel/types") external identifier: string => Ast.expression = "identifier" @module("@babel/types") - external memberExpression_: (Ast.expression, Ast.expression) => Ast.expression = - "memberExpression" + external memberExpression: (Ast.expression, Ast.expression) => Ast.expression = "memberExpression" @module("@babel/types") - external callExpression_: (Ast.expression, array) => Ast.expression = + external callExpression: (Ast.expression, array) => Ast.expression = "callExpression" @module("@babel/types") - external expressionStatement_: Ast.expression => Ast.statement = "expressionStatement" - - let identifier = name => identifier_(name) - let memberExpression = (object_, property) => memberExpression_(object_, property) - let callExpression = (callee, arguments) => callExpression_(callee, arguments) - let expressionStatement = expression => expressionStatement_(expression) + external expressionStatement: Ast.expression => Ast.statement = "expressionStatement" } module PlaygroundValidator = { diff --git a/yarn.lock b/yarn.lock index fc8522e50..b6ea162b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2457,6 +2457,9 @@ __metadata: version: 0.0.0-use.local resolution: "@rescript-lang/guide@workspace:apps/guide" dependencies: + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.2" + "@babel/types": "npm:^7.29.0" "@react-router/dev": "npm:^7.14.0" "@react-router/node": "npm:^7.14.0" "@rescript-lang/playground": "workspace:*" @@ -2492,6 +2495,7 @@ __metadata: version: 0.0.0-use.local resolution: "@rescript-lang/playground@workspace:packages/playground" dependencies: + "@babel/parser": "npm:^7.29.2" "@rescript-lang/shared": "workspace:*" "@rescript/react": "npm:^0.14.2" "@rescript/webapi": "npm:0.1.0-experimental-29db5f4" From 62620f5cc8d70cfc3e3b45890c1f194a47cfce88 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Tue, 5 May 2026 20:58:11 -0400 Subject: [PATCH 32/32] chore: align guide app config --- .gitignore | 4 +- .../test => __tests__}/GuideTestFixtures.res | 0 apps/guide/app/root.jsx | 44 +++++++++++ apps/guide/app/root.mjs | 1 - apps/guide/app/{GuideRoot.res => root.res} | 0 apps/guide/app/{GuideRoot.resi => root.resi} | 0 apps/guide/app/routes.jsx | 10 +++ apps/guide/app/routes.mjs | 3 - apps/guide/app/routes.res | 3 + apps/guide/app/routes.resi | 1 + apps/guide/rescript.json | 10 --- apps/guide/vite.config.mjs | 78 ++++++++++++------- apps/guide/vitest.config.mjs | 59 -------------- 13 files changed, 112 insertions(+), 101 deletions(-) rename apps/guide/{src/test => __tests__}/GuideTestFixtures.res (100%) create mode 100644 apps/guide/app/root.jsx delete mode 100644 apps/guide/app/root.mjs rename apps/guide/app/{GuideRoot.res => root.res} (100%) rename apps/guide/app/{GuideRoot.resi => root.resi} (100%) create mode 100644 apps/guide/app/routes.jsx delete mode 100644 apps/guide/app/routes.mjs create mode 100644 apps/guide/app/routes.res create mode 100644 apps/guide/app/routes.resi delete mode 100644 apps/guide/vitest.config.mjs diff --git a/.gitignore b/.gitignore index 69feda068..1594fa311 100644 --- a/.gitignore +++ b/.gitignore @@ -67,8 +67,8 @@ apps/docs/app/**/*.mjs apps/docs/app/**/*.jsx apps/guide/app/**/*.mjs apps/guide/app/**/*.jsx -!apps/guide/app/root.mjs -!apps/guide/app/routes.mjs +!apps/guide/app/root.jsx +!apps/guide/app/routes.jsx apps/docs/functions/**/*.mjs apps/docs/functions/**/*.jsx apps/docs/__tests__/**/*.mjs diff --git a/apps/guide/src/test/GuideTestFixtures.res b/apps/guide/__tests__/GuideTestFixtures.res similarity index 100% rename from apps/guide/src/test/GuideTestFixtures.res rename to apps/guide/__tests__/GuideTestFixtures.res diff --git a/apps/guide/app/root.jsx b/apps/guide/app/root.jsx new file mode 100644 index 000000000..ee50dca94 --- /dev/null +++ b/apps/guide/app/root.jsx @@ -0,0 +1,44 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as ReactRouter from "react-router"; +import * as JsxRuntime from "react/jsx-runtime"; +import MainCssurl from "../styles/main.css?url"; + +let mainCss = MainCssurl; + +function Root$default(props) { + return + + + + + + + + {"ReScript Guide"} + + + + + + + + ; +} + +let $$default = Root$default; + +export { + $$default as default, +} +/* mainCss Not a pure module */ diff --git a/apps/guide/app/root.mjs b/apps/guide/app/root.mjs deleted file mode 100644 index 11c4bd1e4..000000000 --- a/apps/guide/app/root.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./GuideRoot.jsx"; diff --git a/apps/guide/app/GuideRoot.res b/apps/guide/app/root.res similarity index 100% rename from apps/guide/app/GuideRoot.res rename to apps/guide/app/root.res diff --git a/apps/guide/app/GuideRoot.resi b/apps/guide/app/root.resi similarity index 100% rename from apps/guide/app/GuideRoot.resi rename to apps/guide/app/root.resi diff --git a/apps/guide/app/routes.jsx b/apps/guide/app/routes.jsx new file mode 100644 index 000000000..e793a07bf --- /dev/null +++ b/apps/guide/app/routes.jsx @@ -0,0 +1,10 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Routes from "@react-router/dev/routes"; + +let $$default = [Routes.index("./GuideHomeRoute.jsx")]; + +export { + $$default as default, +} +/* default Not a pure module */ diff --git a/apps/guide/app/routes.mjs b/apps/guide/app/routes.mjs deleted file mode 100644 index 2a84d0086..000000000 --- a/apps/guide/app/routes.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { index } from "@react-router/dev/routes"; - -export default [index("./GuideHomeRoute.jsx")]; diff --git a/apps/guide/app/routes.res b/apps/guide/app/routes.res new file mode 100644 index 000000000..9d0187936 --- /dev/null +++ b/apps/guide/app/routes.res @@ -0,0 +1,3 @@ +open ReactRouter.Routes + +let default = [index("./GuideHomeRoute.jsx")] diff --git a/apps/guide/app/routes.resi b/apps/guide/app/routes.resi new file mode 100644 index 000000000..b09c4fb75 --- /dev/null +++ b/apps/guide/app/routes.resi @@ -0,0 +1 @@ +let default: array diff --git a/apps/guide/rescript.json b/apps/guide/rescript.json index 5739060e2..3c89889a8 100644 --- a/apps/guide/rescript.json +++ b/apps/guide/rescript.json @@ -14,11 +14,6 @@ "subdirs": true, "type": "dev" }, - { - "dir": "src", - "subdirs": true, - "type": "dev" - }, { "dir": "app", "subdirs": true @@ -26,10 +21,5 @@ ], "warnings": { "error": "+8" - }, - "gentypeconfig": { - "language": "untyped", - "shims": [], - "module": "es6" } } diff --git a/apps/guide/vite.config.mjs b/apps/guide/vite.config.mjs index 1cc83fd1c..b6e1594e1 100644 --- a/apps/guide/vite.config.mjs +++ b/apps/guide/vite.config.mjs @@ -1,4 +1,5 @@ import { reactRouter } from "@react-router/dev/vite"; +import { playwright } from "@vitest/browser-playwright"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; import env from "vite-plugin-env-compatible"; @@ -36,31 +37,56 @@ const sharedEditorDeps = [ "vfile-matter", ]; -export default defineConfig({ - envDir: "../..", - plugins: [ - env({ prefix: "PUBLIC_" }), - reactRouter(), - react({ - include: ["**/*.mjs"], - exclude: excludedFiles, - }), - ], - server: { - watch: { - ignored: excludedFiles, +export default defineConfig(({ mode }) => { + const isTest = mode === "test"; + + return { + envDir: "../..", + plugins: [ + env({ prefix: "PUBLIC_" }), + ...(isTest ? [] : [reactRouter()]), + isTest + ? react() + : react({ + include: ["**/*.mjs"], + exclude: excludedFiles, + }), + ], + server: { + watch: { + ignored: excludedFiles, + }, + }, + build: { + sourcemap: process.env.NODE_ENV !== "production", + }, + css: { + transformer: "lightningcss", + }, + optimizeDeps: { + include: sharedEditorDeps, + }, + legacy: { + inconsistentCjsInterop: true, + }, + test: { + include: ["__tests__/*_.test.jsx"], + setupFiles: ["./vitest.setup.mjs"], + browser: { + enabled: true, + provider: playwright({ + contextOptions: { + deviceScaleFactor: 1, + }, + }), + ui: false, + instances: [ + { + browser: "chromium", + viewport: { width: 1440, height: 900 }, + }, + ], + }, }, - }, - build: { - sourcemap: process.env.NODE_ENV !== "production", - }, - css: { - transformer: "lightningcss", - }, - optimizeDeps: { - include: sharedEditorDeps, - }, - legacy: { - inconsistentCjsInterop: true, - }, + }; }); diff --git a/apps/guide/vitest.config.mjs b/apps/guide/vitest.config.mjs deleted file mode 100644 index 7632b395f..000000000 --- a/apps/guide/vitest.config.mjs +++ /dev/null @@ -1,59 +0,0 @@ -import { defineConfig } from "vitest/config"; -import { playwright } from "@vitest/browser-playwright"; -import react from "@vitejs/plugin-react"; -import env from "vite-plugin-env-compatible"; - -const sharedEditorDeps = [ - "@babel/generator", - "@babel/parser", - "@babel/traverse", - "@babel/types", - "@codemirror/commands", - "@codemirror/lang-javascript", - "@codemirror/language", - "@codemirror/lint", - "@codemirror/search", - "@codemirror/state", - "@codemirror/view", - "@lezer/highlight", - "@replit/codemirror-vim", - "@rescript/runtime/lib/es6/Belt_Array.js", - "@rescript/runtime/lib/es6/Primitive_object.js", - "@rescript/runtime/lib/es6/Primitive_option.js", - "@rescript/runtime/lib/es6/Stdlib_Array.js", - "@rescript/runtime/lib/es6/Stdlib_Dict.js", - "@rescript/runtime/lib/es6/Stdlib_Int.js", - "@rescript/runtime/lib/es6/Stdlib_JsExn.js", - "@rescript/runtime/lib/es6/Stdlib_List.js", - "@rescript/runtime/lib/es6/Stdlib_Option.js", - "@tsnobip/rescript-lezer", - "lz-string", - "react-router", -]; - -export default defineConfig({ - envDir: "../..", - plugins: [env({ prefix: "PUBLIC_" }), react()], - optimizeDeps: { - include: sharedEditorDeps, - }, - test: { - include: ["__tests__/*.jsx"], - setupFiles: ["./vitest.setup.mjs"], - browser: { - enabled: true, - provider: playwright({ - contextOptions: { - deviceScaleFactor: 1, - }, - }), - ui: false, - instances: [ - { - browser: "chromium", - viewport: { width: 1440, height: 900 }, - }, - ], - }, - }, -});