diff --git a/AGENTS.md b/AGENTS.md index 9b2ee43..20b73f0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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/`. @@ -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 -- `. -- 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 -- `. +- 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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f65a6b1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 rcrd + +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. diff --git a/README.md b/README.md index 2a43bc1..7469dee 100644 --- a/README.md +++ b/README.md @@ -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: `//` and `/.html` redirect to `/`, 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: `//` and `/.html` redirect to `/`, 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 `/`, and the `og:image`s at `/og.png` and `/og/.png`. - -A Project that publishes its own docs deploys its own Worker, with a route for everything under `//`. 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//*", "zone_name": "variable.land" } -] -``` - -The pattern ends in `/*`, so `/` itself, the Project page, still reaches this Worker. `lab.variable.land/*` 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 `/` with no redirect, and redirects `//` to `/`. A Project's Worker may redirect `//` to `/` too, as env's does. So www must never redirect the other way, from `/` to `//`, 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://-variablelab-www..workers.dev`, with each `/` in the branch name turned into `-`. The branch `ci/foo` gets `https://ci-foo-variablelab-www..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 -- `. +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. +

+  Variable Land +

diff --git a/docs/add-a-project.md b/docs/add-a-project.md index 8637874..3c54ba4 100644 --- a/docs/add-a-project.md +++ b/docs/add-a-project.md @@ -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//`, with the route `{ "pattern": "lab.variable.land//*", "zone_name": "variable.land" }`. The pattern ends in `/*`, so the Project page at `lab.variable.land/` stays with www; `lab.variable.land/*` 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/` shows the Project page from www, a path under `lab.variable.land//` 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 `

` links to `lab.variable.land/`. 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`. diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..4b55cb9 --- /dev/null +++ b/docs/contributing.md @@ -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. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..c948c82 --- /dev/null +++ b/docs/development.md @@ -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 -- `. + +## 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 `/`, and the `og:image`s at `/og.png` and `/og/.png`. + +A Project that publishes its own docs deploys its own Worker, with a route for everything under `//`. 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//*", "zone_name": "variable.land" } +] +``` + +The pattern ends in `/*`, so `/` itself, the Project page, still reaches this Worker. `lab.variable.land/*` 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 `/` with no redirect, and redirects `//` to `/`. A Project's Worker may redirect `//` to `/` too, as env's does. So www must never redirect the other way, from `/` to `//`, 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://-variablelab-www..workers.dev`, with each `/` in the branch name turned into `-`. The branch `ci/foo` gets `https://ci-foo-variablelab-www..workers.dev`.