Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# www

The site at `lab.variable.land`: the Index of Variable Lab's Projects and a Project page for each. [README.md](./README.md) is for people and holds the commands; this file is for agents working in this repo.
The site at `lab.variable.land`: the Index of Variable Lab's Projects and a Project page for each. [README.md](./README.md) is for people, [docs/development.md](./docs/development.md) holds the commands, and this file is for agents working in this repo.

The site is static. `astro build` renders the pages in `src/pages` and the content in `src/content` into `dist/`, with `public/robots.txt` copied as is. The Worker has no script: [wrangler.jsonc](./wrangler.jsonc) serves `dist/` as assets, so how a path resolves, redirects or 404s is set there. The pages are designed and specified in `docs/`.

Expand All @@ -17,8 +17,8 @@ The site is static. `astro build` renders the pages in `src/pages` and the conte
- Stored content is in `src/content/`, typed and checked by `src/content.config.ts`. Derived fields are computed in `src/lib/content.ts`, never stored.
- The fixed words that `docs/design.md` names are in `src/lib/labels.ts`; [design.md](./docs/design.md#decisions) lists them.
- Brand values come from `@variablelab/brand`, never typed by hand. CSS uses the `--vlab-*` custom properties of its `tokens.css`, which `src/styles/site.css` imports with Fontsource's IBM Plex. TypeScript (the Logo, the Class stamps, `theme-color` and the `og:image`s) reads them through `src/lib/brand.ts`. Code gets its colors from `src/lib/code.ts`, which the Project page and the `og:image`s share.
- The tools run by name through mise's PATH; the commands are in [README.md](./README.md#local-development). In a non-interactive shell mise's hook may not run: use `mise exec -- <command>`.
- For the visual check in [design.md](./docs/design.md#verifying), serve the build as the Worker does: `astro build`, then `wrangler dev` ([README.md](./README.md#local-development)).
- The tools run by name through mise's PATH; the commands are in [docs/development.md](./docs/development.md#tasks). In a non-interactive shell mise's hook may not run: use `mise exec -- <command>`.
- For the visual check in [design.md](./docs/design.md#verifying), serve the build as the Worker does: `astro build`, then `wrangler dev` ([docs/development.md](./docs/development.md#tasks)).

## Brand

Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 rcrd <ricardo@variable.land>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
80 changes: 15 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,79 +1,29 @@
# www

The site at [lab.variable.land](https://lab.variable.land): the Index of Variable Lab's apps and libraries, and a page for each one.
A public index of Variable Lab's apps and libraries. It is the site at [lab.variable.land](https://lab.variable.land): the Index, and a Project page for each Project.

The site is static: [Astro](https://astro.build) builds it into `dist/`, and a Worker with no script serves `dist/` as [static assets](https://developers.cloudflare.com/workers/static-assets/). Each page is served without a trailing slash: `/<project>/` and `/<project>.html` redirect to `/<project>`, and an unknown path gets a 404. `/robots.txt` is `public/robots.txt`, copied as is. The site is designed and specified in [docs/](./docs):
The site is static: [Astro](https://astro.build) builds it into `dist/`, and a Worker with no script serves `dist/` as [static assets](https://developers.cloudflare.com/workers/static-assets/). Each page is served without a trailing slash: `/<project>/` and `/<project>.html` redirect to `/<project>`, and an unknown path gets a 404. `/robots.txt` is `public/robots.txt`, copied as is. The site is designed and specified in [docs/](docs):

| Doc | What it holds |
| --- | --- |
| [design.md](./docs/design.md) | The pages, their head and `og:image`, the rules they render by, and every measurement. |
| [content-model.md](./docs/content-model.md) | The entities and fields behind the pages. |
| [content.md](./docs/content.md) | The approved content: the Index and each Project. |
| [glossary.md](./docs/glossary.md) | The terms used in code, content and copy. |
| [add-a-project.md](./docs/add-a-project.md) | How a Project gets into the Index. |
- [design.md](docs/design.md): the pages, their head and `og:image`, the rules they render by, and every measurement.
- [content-model.md](docs/content-model.md): the entities and fields behind the pages.
- [content.md](docs/content.md): the approved content of the Index and of each Project.
- [glossary.md](docs/glossary.md): the terms used in code, content and copy.
- [add-a-project.md](docs/add-a-project.md): how a Project gets into the Index.

The brand comes from [withvariablelab/brand](https://github.com/withvariablelab/brand).

## How `lab.variable.land` is split

This Worker owns `lab.variable.land` as a [Custom Domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/), which also creates the DNS record. It serves the Index, every Project page at `/<project>`, and the `og:image`s at `/og.png` and `/og/<project>.png`.

A Project that publishes its own docs deploys its own Worker, with a route for everything under `/<project>/`. Routes on the same hostname [take precedence](https://developers.cloudflare.com/workers/configuration/routing/routes/), so those paths never reach this Worker. In the Project's `wrangler.jsonc`:

```jsonc
"routes": [
{ "pattern": "lab.variable.land/<project>/*", "zone_name": "variable.land" }
]
```

The pattern ends in `/*`, so `/<project>` itself, the Project page, still reaches this Worker. `lab.variable.land/<project>*` would take it too.

`/robots.txt` lives here because it must sit at the root. It also lists the sitemaps of the Projects' docs.

This Worker serves `/<project>` with no redirect, and redirects `/<project>/` to `/<project>`. A Project's Worker may redirect `/<project>/` to `/<project>` too, as env's does. So www must never redirect the other way, from `/<project>` to `/<project>/`, or the two would loop: keep `build.format: 'file'` in `astro.config.mjs` and `html_handling: "drop-trailing-slash"` in `wrangler.jsonc`.

## Adding a Project

Follow [docs/add-a-project.md](./docs/add-a-project.md).

## Deploying

Deployed with [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/) (Git integration). Its build settings, under the Worker's Settings → Build, have two sets: Production, for `main`, and Previews Base, for every other branch.

| Setting | Production | Previews Base |
| --- | --- | --- |
| Root directory | `/` | `/` |
| Build command | `pnpm exec astro build` | `pnpm exec astro build` |
| Build variable | `NODE_VERSION` = `24` | `NODE_VERSION` = `24` |
| Deploy command | `pnpm exec wrangler deploy` | `npx wrangler preview` |

- The Build command is set in Workers Builds, not as `build.command` in `wrangler.jsonc`. Both `wrangler deploy` and `wrangler preview` would run `build.command` after Workers Builds' own Build command, so every build would run Astro twice.
- `NODE_VERSION` is there because Workers Builds doesn't read `mise.toml` or `mise.lock`. Without it, a build gets the build image's default Node.
- Each branch copies the Previews Base into its own trigger the first time it's pushed, so a change to the base applies only to branches pushed after it. A branch pushed before the change keeps its old settings until its own trigger is edited. Deleting the branch deletes its trigger and its Preview.
- The API returns both sets as `production_settings` and `previews_base_config` from `GET /accounts/{account_id}/builds/workers/{worker_tag}`.
- The Previews Base under Settings → Build is not the one under Variables and secrets or Bindings. That other one is the Previews' runtime configuration, which this Worker doesn't need.
- `previews: {}` in `wrangler.jsonc` is required for the preview command.
- `preview_urls: true` in `wrangler.jsonc`, together with Preview turned on under the Worker's Domains → Worker URL, gives each branch its own Preview URL: `https://<branch>-variablelab-www.<subdomain>.workers.dev`, with each `/` in the branch name turned into `-`. The branch `ci/foo` gets `https://ci-foo-variablelab-www.<subdomain>.workers.dev`.

## Local development

[mise](https://mise.jdx.dev) pins Node and pnpm (`mise.toml`, `mise.lock`) and puts `node_modules/.bin` on the PATH, so the tools run by name:
Follow [docs/add-a-project.md](docs/add-a-project.md).

```sh
mise install
pnpm install
astro dev # the site, rebuilt as you edit
rr check # format and lint with Biome, then types with astro check
rr jsc --fix # applies Biome's fixes
astro build # the site, into dist/
wrangler dev # dist/, served as the Worker serves it
wrangler deploy --dry-run
```
## Contributing

In a shell where mise isn't activated, run them as `mise exec -- <command>`.
The [contributing guide](docs/contributing.md) covers issues and pull requests, and the [development guide](docs/development.md) covers setting up the repo and running its checks.

`rr check` runs Biome, then `astro check` through `@rrlab/astro-plugin`, which type-checks `.astro` files, `.ts` files and the content schema. `rr tsc` runs only the type check.
## License

CI runs `pnpm rr check` and `pnpm astro build` on each pull request to `main`. It calls the tools through pnpm because mise's GitHub Action puts only mise's shims on the PATH, not `mise.toml`'s `[env]`.
[MIT](LICENSE).

A weekly workflow runs `pnpm audit` on `pnpm-lock.yaml` and keeps an issue labeled `dependency-audit` open while it finds known vulnerabilities. Dependabot opens pull requests for security updates only, never for version updates.
<p align="center">
<a href="https://lab.variable.land"><img src="https://cdn.jsdelivr.net/gh/withvariablelab/brand@v0.3.1/.github/readme/logo.svg" width="24" alt="" align="absmiddle">&nbsp;Variable Land</a>
</p>
4 changes: 2 additions & 2 deletions docs/add-a-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ How a Project gets into the Index and gets its Project page. Each step ends when
Check: its title, description and `og:image` follow [design.md](design.md#head).
9. **Its docs, if it publishes its own.** The Project's own Worker takes everything under `lab.variable.land/<project>/`, with the route `{ "pattern": "lab.variable.land/<project>/*", "zone_name": "variable.land" }`. The pattern ends in `/*`, so the Project page at `lab.variable.land/<project>` stays with www; `lab.variable.land/<project>*` would take it too. A landing the Project had before redirects to its Project page. www's `/robots.txt` (`public/robots.txt`) lists the docs' sitemap, and any crawl rule they need, such as disallowing their API.
Check: `lab.variable.land/<project>` shows the Project page from www, a path under `lab.variable.land/<project>/` shows the Project's docs, and `/robots.txt` names that sitemap.
10. **Its README.** The Project's README copies its Tagline, word for word.
Check: the Tagline in the README matches `tagline`.
10. **Its README.** The Project's README follows the `vlab-brand` skill's `readme.md`, and copies its Tagline and Install command word for word. Its title links to the Project page.
Check: the Tagline and the install command in the README match `tagline` and `installCommand`, and the README's `<h1>` links to `lab.variable.land/<project>`.
11. **Visual review.** As in [design.md](design.md#verifying).
Check: every item in [design.md](design.md#verifying) holds for its page and its `og:image`.
18 changes: 18 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Contributing

www takes fixes to the site and its docs, and new Projects for the Index. To set up the repo and run its checks, see the [development guide](development.md).

## Issues

- A bug report names the page, its URL and the browser, and says what you expected and what happened.
- A new page, a change to [design.md](design.md), or new copy starts as an issue before any pull request. [content.md](content.md) holds the approved copy, and new copy needs the Keeper's approval.
- A new Project follows [add-a-project.md](add-a-project.md).

## Pull requests

1. Branch from `main` with a prefix: `feat/`, `fix/`, `docs/`, `refactor/`, `ci/` or `chore/`.
2. Write commits as [Conventional Commits](https://www.conventionalcommits.org), such as `docs: give the Previews Base its own build settings in Deploying`. The pull request title takes the same form, because it becomes the squash commit.
3. Run `rr check` and `astro build` before you push. A page is done after the visual check in [design.md](design.md#verifying).
4. Workers Builds deploys each pushed branch as a Preview, with its own URL: see [Deploying](development.md#deploying).

CI has to pass, and a maintainer squash-merges. A merge to `main` deploys the site.
82 changes: 82 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Development

How to work on www in a clone. The [contributing guide](contributing.md) covers how a change gets in.

## Layout

- `src/pages/`: the Index, the Project pages, the `og:image`s and the icons.
- `src/content/`: the stored content, typed and checked by `src/content.config.ts`.
- `src/lib/`: the derived fields, the fixed words, the brand values, the code colors and the `og:image` drawing.
- `src/components/`, `src/layouts/` and `src/styles/`: the parts the pages are built from.
- `public/`: files copied as is: `robots.txt` and `_headers`.
- `docs/`: the site's design, content model, content and glossary, the steps to add a Project, and these two guides.
- `wrangler.jsonc`: how the Worker serves `dist/`.

## Setup

[mise](https://mise.jdx.dev) pins Node and pnpm (`mise.toml`, `mise.lock`) and puts `node_modules/.bin` on the PATH, so the tools run by name:

```sh
mise install
pnpm install
```

In a shell where mise isn't activated, run them as `mise exec -- <command>`.

## Tasks

```sh
astro dev # the site, rebuilt as you edit
rr check # format and lint with Biome, then types with astro check
rr jsc --fix # apply Biome's fixes
astro build # the site, into dist/
wrangler dev # dist/, served as the Worker serves it
wrangler deploy --dry-run # the deploy, without deploying
```

`rr check` runs Biome, then `astro check` through `@rrlab/astro-plugin`, which type-checks `.astro` files, `.ts` files and the content schema. `rr tsc` runs only the type check.

## CI

- **On each pull request to `main`:** CI runs `pnpm rr check` and `pnpm astro build`, and Workflow audit runs zizmor on the workflows. Both are required checks.
- **On each push:** Workers Builds builds the branch. `main` deploys the site, and every other branch deploys its own Preview, as [Deploying](#deploying) says.
- **Every Monday:** a workflow runs `pnpm audit` on `pnpm-lock.yaml`, and keeps an issue labeled `dependency-audit` open while it finds known vulnerabilities (Dependency audit). Dependabot opens pull requests for security updates only, never for version updates.

The repo has no git hooks.

## How `lab.variable.land` is split

This Worker owns `lab.variable.land` as a [Custom Domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/), which also creates the DNS record. It serves the Index, every Project page at `/<project>`, and the `og:image`s at `/og.png` and `/og/<project>.png`.

A Project that publishes its own docs deploys its own Worker, with a route for everything under `/<project>/`. Routes on the same hostname [take precedence](https://developers.cloudflare.com/workers/configuration/routing/routes/), so those paths never reach this Worker. In the Project's `wrangler.jsonc`:

```jsonc
"routes": [
{ "pattern": "lab.variable.land/<project>/*", "zone_name": "variable.land" }
]
```

The pattern ends in `/*`, so `/<project>` itself, the Project page, still reaches this Worker. `lab.variable.land/<project>*` would take it too.

`/robots.txt` lives here because it must sit at the root. It also lists the sitemaps of the Projects' docs.

This Worker serves `/<project>` with no redirect, and redirects `/<project>/` to `/<project>`. A Project's Worker may redirect `/<project>/` to `/<project>` too, as env's does. So www must never redirect the other way, from `/<project>` to `/<project>/`, or the two would loop: keep `build.format: 'file'` in `astro.config.mjs` and `html_handling: "drop-trailing-slash"` in `wrangler.jsonc`.

## Deploying

Deployed with [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/) (Git integration). Its build settings, under the Worker's Settings → Build, have two sets: Production, for `main`, and Previews Base, for every other branch.

| Setting | Production | Previews Base |
| --- | --- | --- |
| Root directory | `/` | `/` |
| Build command | `pnpm exec astro build` | `pnpm exec astro build` |
| Build variable | `NODE_VERSION` = `24` | `NODE_VERSION` = `24` |
| Deploy command | `pnpm exec wrangler deploy` | `npx wrangler preview` |

- The Build command is set in Workers Builds, not as `build.command` in `wrangler.jsonc`. Both `wrangler deploy` and `wrangler preview` would run `build.command` after Workers Builds' own Build command, so every build would run Astro twice.
- `NODE_VERSION` is there because Workers Builds doesn't read `mise.toml` or `mise.lock`. Without it, a build gets the build image's default Node.
- Each branch copies the Previews Base into its own trigger the first time it's pushed, so a change to the base applies only to branches pushed after it. A branch pushed before the change keeps its old settings until its own trigger is edited. Deleting the branch deletes its trigger and its Preview.
- The API returns both sets as `production_settings` and `previews_base_config` from `GET /accounts/{account_id}/builds/workers/{worker_tag}`.
- The Previews Base under Settings → Build is not the one under Variables and secrets or Bindings. That other one is the Previews' runtime configuration, which this Worker doesn't need.
- `previews: {}` in `wrangler.jsonc` is required for the preview command.
- `preview_urls: true` in `wrangler.jsonc`, together with Preview turned on under the Worker's Domains → Worker URL, gives each branch its own Preview URL: `https://<branch>-variablelab-www.<subdomain>.workers.dev`, with each `/` in the branch name turned into `-`. The branch `ci/foo` gets `https://ci-foo-variablelab-www.<subdomain>.workers.dev`.
Loading