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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
"husky": "^9.1.7",
"lighthouse": "^13.5.0",
"lint-staged": "^17.6.0",
"playwright-layout-guard": "0.3.0",
"playwright-privacy-guard": "0.3.0",
"playwright-layout-guard": "0.4.0",
"playwright-privacy-guard": "0.4.0",
"prettier": "^3.9.10",
"prettier-plugin-astro": "^1.1.2",
"prettier-plugin-tailwindcss": "^0.8.1",
Expand Down
22 changes: 12 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
allowBuilds:
esbuild: true
workerd: true
# pnpm does not install a version that is younger than its minimum release age. playwright-privacy-guard
# is my own package and 0.3.0 was published on 2026-10-28: delete this entry once pnpm installs it
# without it (a few days after the release).
# pnpm does not install a version that is younger than its minimum release age. playwright-layout-guard
# and playwright-privacy-guard are my own packages and 0.4.0 of both was published on 2026-10-29:
# delete these entries once pnpm installs them without (a few days after the release).
minimumReleaseAgeExclude:
- playwright-privacy-guard@0.3.0
- playwright-layout-guard@0.4.0
- playwright-privacy-guard@0.4.0
# miniflare (inside wrangler) pins undici 7.29.1 exactly, and 7.29.1 has known denial of service
# findings (patched in 7.30.1; found by pnpm audit on 2026-10-24). Written as a range of the bad
# versions, so it does nothing once miniflare asks for a patched version itself: then delete it.
Expand Down
4 changes: 2 additions & 2 deletions src/content/notes/how-this-site-is-built.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ tag: 'Site · Overview'
code: .github/workflows
group: site
order: 1
updated: '2026-10-28'
updated: '2026-10-29'
---

import ExternalLink from '@/components/ExternalLink.astro';
Expand All @@ -18,7 +18,7 @@ The shape is simple. The site is **static**: it is built once, and the result is

<Stats
items={[
{ value: '2,900+', label: 'automated tests' },
{ value: '3,000+', label: 'automated tests' },
{ value: '8', label: 'jobs in the gate' },
{ value: '95+', label: 'Lighthouse floor' },
{ value: '0', label: 'third-party requests' },
Expand Down
8 changes: 4 additions & 4 deletions src/content/projects/layout-guard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ stats:
- value: '440'
label: faults injected to compare it with the guard it replaced (same result)
highlights:
- Any element that moves or changes its size after the page has loaded fails the test, and the message names the element, how far it moved and when. It measures layout in the test itself, so it also sees what Lighthouse's layout shift score leaves out, such as shifts after a click or far below the first screen.
- Extracted from the browser tests of this site, which have run with it since the first version of the [layout guard](/notes/a-page-that-never-moves/). Before it replaced the original, both ran side by side on every page of the site against built-in faults, and running the whole suite with the package found three bugs that none of its own tests had found.
- Tried on 23 public sites next to the browser's own layout shift measurement. The false alarms of the first versions (one cause reported fifty times, sticky headers, a blind spot after a history call) are rules with tests now.
- Released like a product, with semantic versioning and a changelog from changesets, CI on three browsers and three Node versions including the oldest supported Playwright, and publishing from GitHub Actions with npm provenance and trusted publishing. A release is staged and only goes live when I approve it with two-factor authentication, so no publish token exists anywhere.
- Fails a test when an element moves or changes size after the page has loaded, and names the element, how far it moved and when. It also sees what the layout shift score of Lighthouse leaves out, such as shifts after a click.
- Extracted from the browser tests of this site, and tried on 23 public sites next to the layout shift measurement of the browser. The false alarms of the first versions are rules with tests now.
- Works without a test as well. A command line, `npx playwright-layout-guard scan <address>`, lists what moved next to the browser's own score, saves a picture of the moved boxes and ends with an exit code for a pipeline.
- Released like a product, with semantic versioning, a changelog, CI on three browsers including the oldest supported Playwright, and publishing from GitHub Actions with npm provenance that only goes live when I approve it with two-factor authentication.
technologies: [typescript, playwright, vitest, cicd, npm, git, eslint, prettier]
code:
status: public
Expand Down
8 changes: 4 additions & 4 deletions src/content/projects/privacy-guard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ stats:
- value: '0'
label: runtime dependencies
highlights:
- Any request to another origin, cookie, entry in the storage of the device, violation of the content security policy or error on the page fails the test. The message names the host, the cookie or the script, says which company is behind it, and ends with the exact setting that would allow it if it is meant.
- Extracted from the browser tests of this site, which promise no cookies, nothing stored and no other origin. It replaced the hand-written privacy and error tests there, and now watches every one of the browser tests instead of one loop over the pages. Canary tests make a page misbehave on purpose and expect the guard to fail them.
- Goes beyond a privacy test. It also rates security headers and the content security policy, finds scripts without an integrity attribute, forms that send elsewhere, sensitive browser APIs, canvas and audio fingerprinting, and values that must not leave. Results come as a readable message, an HTML and JSON report per test, a report for the whole run and a baseline for projects that leak today. Everything a test allowed on purpose is listed with its reason, so an exception cannot hide.
- Released like a product, with semantic versioning and a changelog from changesets, CI on three browsers and three Node versions including the oldest supported Playwright, which found a bug that the newest one hides, and publishing from GitHub Actions with npm provenance. A release is staged and only goes live when I approve it with two-factor authentication.
- Fails a test on a request to another origin, a cookie, anything stored on the device, a policy violation or an error on the page. The message names the host, the cookie or the script, the company behind it, and the setting that would allow it if it is meant.
- Extracted from the browser tests of this site, where it now watches every test. Canary tests make a page misbehave on purpose, and everything a test allows is listed with its reason.
- Tried on 29 public pages in three browsers next to a record that Playwright keeps without the package. Nothing the browser did was missing from the report, and the four faults the trial found are fixed. The same scan runs without a test as `npx playwright-privacy-guard scan <address>`.
- Released like a product, with semantic versioning, a changelog, CI on three browsers including the oldest supported Playwright, and publishing from GitHub Actions with npm provenance that only goes live when I approve it with two-factor authentication.
technologies: [typescript, playwright, vitest, cicd, npm, git, eslint, prettier]
code:
status: public
Expand Down
9 changes: 4 additions & 5 deletions src/content/projects/website.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ stats:
label: security headers grade (securityheaders.com)
- value: '95+'
label: Lighthouse score in every category (mobile, enforced in CI)
- value: '2,900+'
- value: '3,000+'
label: automated tests (unit, build output, browser, accessibility)
highlights:
- Static portfolio built with Astro, TypeScript, React islands and Tailwind CSS, hosted on Cloudflare, with a command palette (Ctrl K) and a small terminal that load only when they are opened. Its content is typed YAML that is validated at build time, so a typo fails the build instead of shipping.
- Every change goes through a pull request and automated gates before it goes live, from linting, type checks and unit tests to build-output checks (security headers, CSP, legal pages), end-to-end tests with accessibility scans and two guards (my own open source packages) in every test, one that fails when anything on any page moves or changes size and one that fails when a page talks to another origin, sets a cookie or stores anything, Lighthouse budgets, CodeQL, dependency review and OpenSSF Scorecard. After the deployment a live check of the published site (headers, redirects, legal pages, contact form, certificate, domain, mail DNS) runs every few hours, plus a weekly link check.
- A contact form without any third-party service. A Cloudflare Function validates the message with the same rules as the browser, checks origin, a signed time token, a honeypot and a rate limit, and hands it to my own mailbox over SMTP.
- Privacy and security by design with a strict content security policy, HSTS, no cookies of its own, self-hosted fonts and obfuscated contact data.
- Static portfolio built with Astro, TypeScript, React islands and Tailwind CSS on Cloudflare, with a command palette (Ctrl K) and a small terminal that load only when they are opened. Its content is typed YAML that is validated at build time, so a typo fails the build instead of shipping.
- Every change goes through a pull request and automated gates before it goes live, from linting, type checks, unit, build-output and end-to-end tests with accessibility scans, Lighthouse budgets, CodeQL and dependency review. Two of my own open source packages guard every browser test, one against anything that moves and one against leaks to other origins. A live check of the published site runs every few hours.
- A contact form without any third-party service. A Cloudflare Function validates the message, checks a signed time token, a honeypot and a rate limit, and hands it to my own mailbox. Strict content security policy, HSTS, no cookies of its own and self-hosted fonts.
- Five browser games in the [Playground](/#playground) further down this page, written as tested pure functions, playable with the keyboard, with nothing stored on your device. How they and this site are built is written up in [Behind the scenes](/notes/).
technologies:
[
Expand Down
Loading