Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ace5010
fix: generate route types from the live route configuration
fhammerschmidt Oct 9, 2026
c704e16
fix: align local test commands with their supported workflows
fhammerschmidt Oct 11, 2026
63e9254
fix: retain localhost in local Cypress URLs
fhammerschmidt Oct 11, 2026
4ea200d
docs: align contributor guidance with the current monorepo
fhammerschmidt Oct 11, 2026
19daf80
fix: align workspace commands with contributor guidance
fhammerschmidt Oct 11, 2026
398ef95
chore: prune redundant package script aliases
fhammerschmidt Oct 12, 2026
291ce19
chore: align preparation alias with the package audit
fhammerschmidt Oct 12, 2026
6a7be0c
chore: incorporate the package-script cleanup
fhammerschmidt Oct 12, 2026
23aca83
docs: remove redundant package command guide
fhammerschmidt Oct 12, 2026
b01df23
docs: clarify agent rules and interop examples
fhammerschmidt Oct 12, 2026
faea63a
docs: resolve local test command conflicts with master
fhammerschmidt Oct 14, 2026
66676b6
chore: reconcile route generation with current master
fhammerschmidt Oct 20, 2026
78e0b12
Merge remote-tracking branch 'fork/master' into HEAD
fhammerschmidt Oct 20, 2026
b7fca47
docs: reconcile local testing guidance with current master
fhammerschmidt Oct 20, 2026
8aceef3
docs: reconcile agent rules with the merged contributor guidance
fhammerschmidt Oct 20, 2026
5c79ae2
test: check RSS links against the shared route configuration
fhammerschmidt Oct 20, 2026
a37d338
Merge commit '5c79ae28' into HEAD
fhammerschmidt Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ use the installed `rescript` package, not a separate compiler installation.
- Prefer small functions with a single purpose.
- Use a functional approach but don't make it too hardcore or difficult to understand.
- Keep files and modules small and focused.
- `.res` files must always be capitalized (PascalCase), matching ReScript module conventions.
- Use PascalCase for new `.res` modules. Follow the existing filenames for framework entrypoints such as `root.res` and `routes.res`, and for script entrypoints.
- Use the pipe-first operator (`->`) for chaining, which is idiomatic ReScript.
- Resolve all warnings and treat them as errors. The project has `"error": "+8"` in `rescript.json`.
- Resolve all warnings before committing. `yarn build:res` applies the repository's strict warning checks.
- Never directly edit generated `.jsx` files; edit the corresponding `.res` file and rebuild. Most compiler output is ignored by Git; the guide's `apps/guide/app/root.jsx` and `apps/guide/app/routes.jsx` are tracked and must be regenerated with their sources.

## ReScript Rules
Expand All @@ -83,7 +83,7 @@ use the installed `rescript` package, not a separate compiler installation.
- Reference the abridged documentation for clarification on how ReScript's APIs work: https://rescript-lang.org/llms/manual/llm-small.txt
- If you need more information you can access the full documentation, but do this only when needed as the docs are very large: https://rescript-lang.org/llms/manual/llm-full.txt
- Never use `%raw` unless you are specifically asked to
- Never use `Object.magic`
- Never use `Obj.magic`
- Don't add type annotations unless necessary for clarity or to resolve an error. ReScript's type inference is powerful, and often explicit annotations are not needed.

### ReScript Dependencies
Expand All @@ -96,13 +96,18 @@ use the installed `rescript` package, not a separate compiler installation.
The project uses several patterns for JavaScript interop. Follow the existing conventions:

- **`@module` externals** for binding to npm packages (see `apps/docs/src/bindings/` and `packages/shared/src/` for examples):

```
@module("react-router") external useNavigate: unit => string => unit = "useNavigate"
type navigateOptions = {replace?: bool}
type navigate = (string, ~options: navigateOptions=?) => unit

@module("react-router") external useNavigate: unit => navigate = "useNavigate"
```

- **`@module` with `@react.component`** for binding to external React components:
```
@module("react-router") @react.component
external make: (~children: React.element) => React.element = "Outlet"
external make: unit => React.element = "Outlet"
```
- `%raw` and `%%raw` embed JavaScript; use them only when specifically requested.
- **`@scope`** for binding to object properties like `process.env`.
Expand All @@ -115,12 +120,12 @@ The project uses several patterns for JavaScript interop. Follow the existing co
- The site uses hybrid rendering: most routes are pre-rendered during the build, while `/try` is server-rendered by Cloudflare Pages Functions at runtime.
- Loaders for pre-rendered routes can access the filesystem during the build. Runtime SSR loaders, including shared loader code reachable from `/try`, execute in Cloudflare and must not depend on Node-only filesystem APIs or other build-time-only operations.
- Docs route modules live in `apps/docs/app/routes/`; the guide's route modules live in `apps/guide/app/`. Routes can export a `loader`, `links`, and a `default` component as needed.
- Route modules **require** both a `.res` and a `.resi` (interface) file for Vite HMR to work.
- Only a single React component can be exposed from a module's JS output.
- Give route modules a `.resi` interface that exposes their route exports and keeps helper components private.
- Export a route's page component as `default`. Reusable component modules may export multiple components, as `Markdown.resi` does.

### Route Module Pattern

Every route follows this pattern:
Example of a route with a loader. Routes without loader data can omit the loader and its type:

```rescript
// SomeRoute.res
Expand Down Expand Up @@ -197,7 +202,7 @@ let default: unit => React.element
```

- Components requiring React Router context must be wrapped in `<MemoryRouter>`.
- Do not use `<BrowserRouter>` in tests.
- Do not use `<BrowserRouter>` in component tests. End-to-end tests exercise the app's normal routing and browser history.
- Run `yarn build:res` first so the tests use current compiler output. Run `yarn vitest --browser.headless --run` for docs tests or `yarn vitest:guide --browser.headless --run` for guide tests. Run `yarn ci:test` to check both suites.
- Do not update snapshots without asking for confirmation. Approved docs visual baseline changes use the `Update Visual Regression Screenshots` GitHub Actions workflow against the feature branch; pull and review the generated commit. Local screenshot updates are blocked by the Vitest configuration.

Expand Down Expand Up @@ -229,4 +234,4 @@ let default: unit => React.element
- Do **not** modify generated `.jsx` / `.mjs` files directly — they are ReScript compiler output.
- Do **not** use `@genType` — the project does not use it.
- The `apps/docs/src/ffi/` directory is legacy; prefer bindings in `apps/docs/src/bindings/` or `packages/shared/src/` for new JS interop.
- When editing route files, always update both the `.res` and `.resi` files.
- Keep route `.resi` files in sync when public exports or their types change. Implementation-only edits need only change the `.res` file.
66 changes: 47 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,24 @@ Run these commands from the repository root. To target a workspace, use
`yarn workspace <name> <command>`; the workspace names are `@rescript-lang/docs`,
`@rescript-lang/guide`, `@rescript-lang/playground`, and `@rescript-lang/shared`.

| Command | Purpose |
| -------------------- | ---------------------------------------------------------------- |
| `yarn dev` | Prepare generated files and run the docs development environment |
| `yarn build` | Build and pre-render the docs site |
| `yarn build:guide` | Build and pre-render the interactive guide |
| `yarn preview` | Build and serve the generated docs client locally |
| `yarn build:res` | Generate route types and compile all four workspaces |
| `yarn build:routes` | Regenerate the shared ReScript route type |
| `yarn check:routes` | Check that the shared route type matches the current routes |
| `yarn dev:res` | Run the ReScript compiler in watch mode |
| `yarn format` | Run Oxfmt and format ReScript across all four workspaces |
| `yarn test` | Run docs markdown example, href, and redirect validation |
| `yarn ci:test` | Run docs and guide Vitest browser tests headlessly |
| `yarn vitest` | Run docs Vitest browser tests in watch mode |
| `yarn vitest:guide` | Run guide Vitest browser tests in watch mode |
| `yarn vitest:update` | Update screenshot baselines headlessly |
| Command | Purpose |
| ------------------- | ---------------------------------------------------------------- |
| `yarn dev` | Prepare generated files and run the docs development environment |
| `yarn build` | Build and pre-render the docs site |
| `yarn build:guide` | Build and pre-render the interactive guide |
| `yarn preview` | Build and serve the generated docs client locally |
| `yarn build:res` | Generate route types and compile all four workspaces |
| `yarn build:routes` | Regenerate the shared ReScript route type |
| `yarn check:routes` | Check that the shared route type matches the current routes |
| `yarn dev:res` | Run the ReScript compiler in watch mode |
| `yarn format` | Run Oxfmt and format ReScript across all four workspaces |
| `yarn test` | Run docs markdown example, href, and redirect validation |
| `yarn ci:test` | Run docs and guide Vitest browser tests headlessly |
| `yarn vitest` | Run docs Vitest browser tests in watch mode |
| `yarn vitest:guide` | Run guide Vitest browser tests in watch mode |
| `yarn cy:open` | Open the full-site Cypress suite against the running dev server |
| `yarn cy:e2e` | Run the full-site Cypress suite headlessly in Chrome |
| `yarn test:e2e` | Build and run homepage Cypress tests with a temporary server |

## Testing

Expand Down Expand Up @@ -161,13 +163,39 @@ yarn ci:test
For a headless run of a single suite, use `yarn vitest --browser.headless --run`
for docs or `yarn vitest:guide --browser.headless --run` for the guide.

To update screenshot baselines, run:
Visual screenshot baselines are generated in CI's pinned Linux browser environment. Local snapshot updates are blocked. Push your feature branch, then open [Update Visual Regression Screenshots](https://github.com/rescript-lang/rescript-lang.org/actions/workflows/update-visual-screenshots.yml), select that branch, and choose **Run workflow**. The workflow refuses `master` and commits any changed PNGs back to the selected branch.

You can also dispatch the workflow with the GitHub CLI, replacing `<feature-branch>` with your pushed branch:

```sh
gh workflow run update-visual-screenshots.yml --repo rescript-lang/rescript-lang.org --ref <feature-branch>
```

After the workflow finishes, pull its commit and review the screenshot diff. Only accept screenshots intentionally affected by your change.

### Cypress E2E Tests

The full-site suite covers documentation, navigation, and the playground. Start the local development server in one terminal:

```sh
yarn dev
```

Wait for Vite to report its local URL. In another terminal, run one of these commands:

```sh
yarn cy:open # Interactive Cypress runner (Electron)
yarn cy:run # Headless run (Electron)
yarn cy:e2e # Headless run (Chrome must be installed)
```

These commands compile the ReScript specs and default to `http://localhost:5173`. If Vite reports another port, or you want to test a deployed site, pass its URL explicitly:

```sh
yarn vitest:update
yarn cy:e2e --config baseUrl=http://localhost:5174
```

Only update screenshots that are intentionally affected by your change.
The homepage suite uses the production build and its own configuration. `yarn test:e2e` builds the site, starts a temporary server at `http://127.0.0.1:4173`, runs the homepage specs in Chrome, and stops the server when the tests finish. It does not require `yarn dev`.

### Markdown Example and Link Checks

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/cypress.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default defineConfig({
openMode: 0,
},
e2e: {
baseUrl: "http://localhost:8080",
baseUrl: "http://localhost:5173",
specPattern: "e2e/**/*.cy.jsx",
excludeSpecPattern: "e2e/homepage/**",
supportFile: "cypress/support/e2e.js",
Expand Down
3 changes: 1 addition & 2 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
"cy:run": "yarn build:res && cypress run",
"cy:e2e": "yarn build:res && cypress run --browser chrome",
"vitest": "vitest",
"vitest:visual": "VISUAL_TESTS=1 vitest",
"vitest:update": "VISUAL_TESTS=1 VISUAL_BASELINE_UPDATE=1 vitest --run --browser.headless --update"
"vitest:visual": "VISUAL_TESTS=1 vitest"
},
"dependencies": {
"@cloudflare/pages-plugin-vercel-og": "^0.1.2",
Expand Down
17 changes: 13 additions & 4 deletions apps/docs/scripts/__tests__/BlogFeed_.test.res
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ let blogDirectory = async () => {
let directory = await temporaryDirectory("blog-feed-")
let blog = join([directory, "markdown-pages", "blog"])
await mkdir(join([blog, "archived"]), {recursive: true})
let _ = await Promise.all(
["community", "docs/manual", "docs/guides", "docs/react", "syntax-lookup"]->Array.map(prefix =>
mkdir(join([directory, "markdown-pages", prefix]), {recursive: true})
),
)
await write(join([blog, "z-old.mdx"]), fixture("Old post", "2019-11-28"))
await write(join([blog, "a-new.mdx"]), fixture("New post", "2026-04-28"))
await write(join([blog, "archived", "middle.mdx"]), fixture("Archived post", "2020-01-01"))
Expand Down Expand Up @@ -50,9 +55,13 @@ test("RSS links match article routes, including archived posts", async () => {
"https://rescript-lang.org/blog/archived/middle",
"https://rescript-lang.org/blog/z-old",
])
let routes = MdxFile.scanPaths(~dir=join([directory, "markdown-pages", "blog"]), ~alias="blog")
let routes = DocsRouteConfig.loadRoutes(
~options={
contentDirectory: join([directory, "markdown-pages"]),
},
).routes->DocsRouteConfig.concretePaths
items->Array.forEach(item => {
let route = item.href->String.replace("https://rescript-lang.org/", "")
let route = item.href->String.replace("https://rescript-lang.org", "")
expect(routes->Array.includes(route))->toBe(true)
})
})
Expand Down Expand Up @@ -80,11 +89,11 @@ test("RSS dates use midnight UTC and XML serialization leaves dates unchanged",

test("the current blog feed contains ten recent posts with existing routes", async () => {
let items = BlogApi.RssFeed.getLatest()
let routes = MdxFile.scanPaths(~dir="markdown-pages/blog", ~alias="blog")
let routes = DocsRouteConfig.loadRoutes().routes->DocsRouteConfig.concretePaths
expect(items->Array.length)->toBe(10)
items->Array.forEach(item => {
expect(
routes->Array.includes(item.href->String.replace("https://rescript-lang.org/", "")),
routes->Array.includes(item.href->String.replace("https://rescript-lang.org", "")),
)->toBe(true)
})
})
9 changes: 9 additions & 0 deletions apps/docs/src/bindings/DocsRouteConfig.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type route
type config = {routes: array<route>}
type options = {contentDirectory?: string}

@module("../../route-config.mjs")
external loadRoutes: (~options: options=?) => config = "loadRoutes"

@module("../../generate-route-types.mjs")
external concretePaths: array<route> => array<string> = "concretePaths"
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
"cy:e2e": "yarn workspace @rescript-lang/docs cy:e2e",
"vitest": "yarn workspace @rescript-lang/docs vitest",
"vitest:visual": "yarn workspace @rescript-lang/docs vitest:visual",
"vitest:guide": "yarn workspace @rescript-lang/guide vitest",
"vitest:update": "yarn workspace @rescript-lang/docs vitest:update"
"vitest:guide": "yarn workspace @rescript-lang/guide vitest"
},
"devDependencies": {
"lefthook": "^2.1.4",
Expand Down