From cdb461c0a899b537081690c8524b5208b8a7061f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 15:03:50 +0000 Subject: [PATCH 1/6] Update owner references from obartra/quentin to qafears/website The repo moved to a new owner. Point the Keystatic GitHub storage repo, the GitHub Pages URLs, and the docs at qafears/website so the hosted admin commits to the right place and the docs reflect the new location. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EpKy9WNMQdEfmyxxFJpagd --- CLAUDE.md | 4 ++-- README.md | 2 +- docs/astro-keystatic-migration.md | 2 +- docs/hosted-admin.md | 8 ++++---- keystatic.config.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index aae12d5..3b6898f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -141,7 +141,7 @@ Everything is keyed to `https://quentinfears.com` as the single source of truth. [src/lib/content.ts](src/lib/content.ts), `BASE` in [tools/seo_check.py](tools/seo_check.py), and find-and-replace the domain in `public/robots.txt` and `public/sitemap.xml`. The site is *served* pre-launch from -GitHub Pages at `https://obartra.github.io/quentin/`; the canonical/`og:` URLs +GitHub Pages at `https://qafears.github.io/website/`; the canonical/`og:` URLs intentionally point at the launch domain `quentinfears.com`. ### Indexing @@ -180,7 +180,7 @@ home page, then re-optimize (JPEG, ~2400px wide). - Preview locally: `npm run dev` (site at http://localhost:4321, admin at `/keystatic`). Or `npm run build && npm run preview` for the production build. - Deploy: pushing to `main` builds and publishes `dist/` to GitHub Pages via - `.github/workflows/deploy.yml`. Live at https://obartra.github.io/quentin/. + `.github/workflows/deploy.yml`. Live at https://qafears.github.io/website/. ## Weekly routine: curate the site from new @mrqfears Instagram content diff --git a/README.md b/README.md index cf7490d..dbbb669 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ editing tool and is not part of the deployed site. This repo is wired for **GitHub Pages**: [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml) builds and publishes `dist/` on every push to `main` (currently at -`https://obartra.github.io/quentin/`). +`https://qafears.github.io/website/`). ## Before launch — checklist diff --git a/docs/astro-keystatic-migration.md b/docs/astro-keystatic-migration.md index e9c6373..22e908b 100644 --- a/docs/astro-keystatic-migration.md +++ b/docs/astro-keystatic-migration.md @@ -73,7 +73,7 @@ separate `build:admin` output on Netlify. extensionless form (`/work`); the static host serves `work.html` for it and the `.html` URLs still resolve. - **Relative links.** Every internal link and asset ref stays relative, so the site - works both under the GitHub Pages subpath (`obartra.github.io/quentin/`) and at + works both under the GitHub Pages subpath (`qafears.github.io/website/`) and at the apex domain. `base` stays `/`. - **Gate <-> indexing coupling.** The password gate has been removed and pages ship `robots: index, follow`. `tools/seo_check.py` still enforces the coupling, so a diff --git a/docs/hosted-admin.md b/docs/hosted-admin.md index 13ad2f4..ce175b5 100644 --- a/docs/hosted-admin.md +++ b/docs/hosted-admin.md @@ -10,7 +10,7 @@ repo, which triggers the GitHub Pages deploy of the public site. Editor's browser ──▶ Netlify (SSR) ──▶ /keystatic admin │ Save ▼ - commit to github.com/obartra/quentin (main) + commit to github.com/qafears/website (main) │ ▼ .github/workflows/deploy.yml builds + publishes @@ -39,7 +39,7 @@ Two builds, one project: ### 1. Create the Netlify site 1. Netlify → **Add new site → Import an existing project** → pick - `obartra/quentin`. + `qafears/website`. 2. Netlify reads `netlify.toml`, so the build command (`npm run build:admin`), publish dir (`dist`), and Node version are already set. Deploy once. It will build but the admin won't authenticate yet — that's expected until step 2. @@ -53,7 +53,7 @@ Keystatic can create the GitHub App for you: App"** prompt. It sends you to GitHub with the correct permissions and callback pre-filled. 2. On GitHub, set the callback / homepage to your Netlify URL, create the app, then - **Install** it on the `obartra/quentin` repository. + **Install** it on the `qafears/website` repository. 3. GitHub shows the app's **Client ID**, a generated **Client secret**, and the app **slug**. Keystatic hands these back to paste into Netlify (next step). @@ -88,7 +88,7 @@ variables above. ## Notes - **The config already points at the repo.** `keystatic.config.ts` uses - `repo: 'obartra/quentin'` for GitHub storage. Change it if the repo moves. + `repo: 'qafears/website'` for GitHub storage. Change it if the repo moves. - **Local editing still works** with no setup: `npm run dev` → `/keystatic` uses local file storage and never touches GitHub. Contributors who have the repo checked out can use that instead of the hosted admin. diff --git a/keystatic.config.ts b/keystatic.config.ts index 6b6edb9..0cc01ad 100644 --- a/keystatic.config.ts +++ b/keystatic.config.ts @@ -20,7 +20,7 @@ import { config, fields, singleton } from '@keystatic/core'; */ const storage = import.meta.env.DEV ? ({ kind: 'local' } as const) - : ({ kind: 'github', repo: 'obartra/quentin' } as const); + : ({ kind: 'github', repo: 'qafears/website' } as const); // --- Reusable field groups --------------------------------------------------- From e2c92f4523fb19ff993f32d02031dc86ad546634 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 15:19:06 +0000 Subject: [PATCH 2/6] Document the owner-driven autonomous workflow in CLAUDE.md Codify the operating model for a non-technical owner: they request and approve changes in plain language; the agent handles branch, validation, PR, and merge, and drives every change through to the live deploy. Also reconcile the weekly-routine guidance with main's branch protection (changes land via PR + self-merge, never a direct push to main). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EpKy9WNMQdEfmyxxFJpagd --- CLAUDE.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3b6898f..4bb2dca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,6 +18,43 @@ content into the same markup the site shipped by hand and reuses the same self-contained CSS/JS, so the output, the SEO layer, and the galleries are unchanged. The build is a static export deployed to GitHub Pages. +## How you work: owner-driven and autonomous + +The site owner is non-technical and runs everything by plain-language request. They +should never have to know or say the words "branch", "commit", "PR", or "merge" — +that plumbing is your job. Their part is to say what they want and to approve the +result; your part is everything else, end to end, including getting it live. + +Follow this loop for every change, without being asked for each step: + +1. **Work on a branch.** Never push to `main` directly: it is protected and rejects + direct pushes. Changes reach it only through a pull request. +2. **Validate thoroughly before showing it.** `npm run build`, then + `python3 tools/validate_site.py dist` and `python3 tools/seo_check.py dist`; both + must pass clean. Re-run until green. For anything visible, look at the built page, + not just the YAML. +3. **Show the owner and get approval.** Describe the change in plain terms and, when + it helps, point them at the local preview (`npm run dev` or `npm run preview`). + They approve *what* changed, not the mechanics. Skip this only when they already + approved the change in the same request. +4. **Open a pull request** and let CI run both validators on it. +5. **Merge it yourself once CI is green and the owner has approved.** Merging into + `main` is what deploys the site to GitHub Pages, so a change is not done until it + is merged and the deploy succeeds. Never leave an approved, green PR sitting + unmerged waiting for the owner to click a button: they may not know to. Then + report back with the live result. + +If anything blocks the loop (missing access, a check you cannot fix, a genuinely +ambiguous request), stop and say so in one plain sentence with one direct question. +Do not narrate the plumbing; report outcomes. + +This depends on two things staying set up (see [docs/hosted-admin.md](docs/hosted-admin.md)): + +- The **Claude GitHub App** installed on `qafears/website` with **Contents: Read & + write**. Without it, every push and merge fails with a 403. +- `main` protected so changes must go through a PR. That is deliberate; keep it. It is + also why the flow above is always PR-then-merge, never a direct push. + ## Stack & conventions - **Astro** (static output, `build.format: 'file'` → flat `about.html`, `work.html`, @@ -190,8 +227,11 @@ The weekly run is an editorial pass over the whole site with the new material in hand — not an ingest job. Nothing gets in just because it is new; new work competes with what is already there, and the run is as much about replacing and trimming as adding. Ship mode is auto-merge to live for content edits: content lives in -Keystatic collections under `content/*.yaml`, and pushing to `main` builds and -deploys, so the routine edits the YAML directly and commits to `main` — no PR. +Keystatic collections under `content/*.yaml`, and merging to `main` builds and +deploys, so the routine edits the YAML directly and lands it through a pull request it +validates and merges itself. ("Auto-merge" means you open the PR and merge your own +green PR without a human approval step, not that you push straight to `main`: `main` +is protected and takes changes only through a PR.) `instagram-ledger.json` (repo root) is the source of truth for what has already been considered; never process the same post twice. @@ -280,8 +320,8 @@ Instagram tooling in `tools/` (stdlib-only except the authed one): step 3 that are not tied to a post go under `trimmed` with the same shape. Advance `reviewed_through`. 7. **Publish:** `npm run build`, then `python3 tools/validate_site.py dist` and - `python3 tools/seo_check.py dist` (same as CI); commit to `main` and confirm the - Pages deploy succeeds. The commit message summarizes adds, replacements, and + `python3 tools/seo_check.py dist` (same as CI); open a PR, let CI run both + validators, then merge it to `main` and confirm the Pages deploy succeeds. The commit message summarizes adds, replacements, and trims so the week's editorial decisions are auditable at a glance. ### Section budgets @@ -300,8 +340,10 @@ for unattended growth, not targets to fill: - **≤ 3 content changes per run** (adds + replacements + trims combined). The bias is toward doing less: a run that changes nothing is a valid outcome, not a failure. -- **Content only auto-merges.** YAML edits and optimized images commit straight to - `main`. Anything structural — a new page or section, removing a whole case study, +- **Content only auto-merges.** YAML edits and optimized images go in through a PR you + validate and merge yourself (no human approval needed for on-brand content); `main` + is protected, so there is never a direct push. Anything structural — a new page or + section, removing a whole case study, layout/code/CSS changes, reworking a page's story — is out of scope for the unattended run: open a PR describing the proposal instead, and say why, so a human decides. From b4c7d170e0e178446ba49b31fbfa9a6aa75dc802 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 15:22:12 +0000 Subject: [PATCH 3/6] Preview changes for the owner instead of sending them to a terminal The non-technical owner should never open a command line. The approval step now says to build and open the site in the pre-installed browser, screenshot the affected pages, and send those (or share the PR's Netlify deploy-preview link) rather than pointing them at npm run dev/preview. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EpKy9WNMQdEfmyxxFJpagd --- CLAUDE.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4bb2dca..57652dd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,9 +21,10 @@ unchanged. The build is a static export deployed to GitHub Pages. ## How you work: owner-driven and autonomous The site owner is non-technical and runs everything by plain-language request. They -should never have to know or say the words "branch", "commit", "PR", or "merge" — -that plumbing is your job. Their part is to say what they want and to approve the -result; your part is everything else, end to end, including getting it live. +should never have to know or say the words "branch", "commit", "PR", or "merge", and +should never have to open a terminal or know one exists — that plumbing is your job. +Their part is to say what they want and to approve the result; your part is everything +else, end to end, including getting it live. Follow this loop for every change, without being asked for each step: @@ -33,10 +34,13 @@ Follow this loop for every change, without being asked for each step: `python3 tools/validate_site.py dist` and `python3 tools/seo_check.py dist`; both must pass clean. Re-run until green. For anything visible, look at the built page, not just the YAML. -3. **Show the owner and get approval.** Describe the change in plain terms and, when - it helps, point them at the local preview (`npm run dev` or `npm run preview`). - They approve *what* changed, not the mechanics. Skip this only when they already - approved the change in the same request. +3. **Show the owner and get approval, without ever sending them to a terminal.** They + should not have to run anything or know the command line exists. Preview the change + *for* them: build and open the site in the pre-installed browser (Chromium via + Playwright), capture screenshots of the affected pages, and send those; or point + them at the PR's live Netlify deploy-preview link, which they can just click. + Describe in plain terms what changed. They approve *what* changed, not the + mechanics. Skip this only when they already approved the change in the same request. 4. **Open a pull request** and let CI run both validators on it. 5. **Merge it yourself once CI is green and the owner has approved.** Merging into `main` is what deploys the site to GitHub Pages, so a change is not done until it From f36271b28266222c118bcdbdf8a93ef12a3b74e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 15:23:43 +0000 Subject: [PATCH 4/6] Add a self-maintenance rule for CLAUDE.md and trim redundancy Record that owner feedback about how the agent works gets folded into CLAUDE.md as it comes in, and that the file is kept lean as it grows. Condense the duplicative "What not to do" recap to terse one-liners since the reasoning already lives in the sections above. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EpKy9WNMQdEfmyxxFJpagd --- CLAUDE.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 57652dd..6a00d76 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,6 +52,12 @@ If anything blocks the loop (missing access, a check you cannot fix, a genuinely ambiguous request), stop and say so in one plain sentence with one direct question. Do not narrate the plumbing; report outcomes. +**Maintain this file as you go.** When the owner gives feedback about how you should +work, fold it into CLAUDE.md immediately, in the section it belongs to. Keep the file +lean: group related guidance, trim redundancy, and delete anything no longer true. +Treat length as a cost, and prefer editing an existing line over adding a new one; a +short, current file beats a growing one. + This depends on two things staying set up (see [docs/hosted-admin.md](docs/hosted-admin.md)): - The **Claude GitHub App** installed on `qafears/website` with **Contents: Read & @@ -363,19 +369,12 @@ for unattended growth, not targets to fill: ## What not to do -- Do not add external fonts, scripts, or CDNs to the site. It breaks the offline / - CSP-safe guarantee. (Astro/Keystatic build-time dependencies are fine; they do not - ship to the browser.) -- Do not switch internal links/assets to root-absolute paths; keep them relative so - the subpath and apex domain both work. -- Do not use em dashes in copy or in anything that ships to `dist/`; restructure the - sentence instead. CI (`tools/validate_site.py`) fails on them. -- Do not hand-write or desync the ``, JSON-LD, or sitemap when adding pages; the - head is generated by `BaseLayout` and keyed to the canonical origin. -- Do not present Quentin as speaking for an employer in metadata or structured data, - or put an employer name in any `seo.*` field. -- Keep the **public** build (`npm run build`) static and adapter-free — that is what - ships to GitHub Pages. The Netlify adapter is intentional but belongs only to the - separate hosted-admin build (`npm run build:admin`); do not merge the two. -- Rename/move the crawl files or validators only alongside updates to - `tools/seo_check.py`, `tools/validate_site.py`, and `.github/workflows/`. +Quick guardrail recap; the reasoning is in the sections above. + +- No external fonts, scripts, or CDNs (breaks the offline / CSP guarantee). Build-time deps are fine. +- No root-absolute internal links or assets; keep them relative. +- No em dashes in anything shipped to `dist/`; restructure instead. CI fails on them. +- Do not hand-write or desync the ``, JSON-LD, or sitemap; `BaseLayout` generates them. +- No employer claim in metadata or any `seo.*` field. +- Keep the public `npm run build` static and adapter-free; the Netlify adapter belongs only to `build:admin`. +- Move or rename the crawl files or validators only alongside `tools/*.py` and `.github/workflows/` updates. From b211fd974542ab1f2f56d180ead7adf9c73ab610 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 15:27:06 +0000 Subject: [PATCH 5/6] Clarify quentinfears.com as the public site vs the Pages publish URL Name https://quentinfears.com as the front door in the deploy and domain-change notes; qafears.github.io/website is only where GitHub Pages publishes the build, not the canonical address. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EpKy9WNMQdEfmyxxFJpagd --- CLAUDE.md | 9 +++++---- README.md | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6a00d76..da216af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -187,9 +187,9 @@ Everything is keyed to `https://quentinfears.com` as the single source of truth. `https://quentinfears.com` is the single source of truth. Update `SITE_ORIGIN` in [src/lib/content.ts](src/lib/content.ts), `BASE` in [tools/seo_check.py](tools/seo_check.py), and find-and-replace the domain in -`public/robots.txt` and `public/sitemap.xml`. The site is *served* pre-launch from -GitHub Pages at `https://qafears.github.io/website/`; the canonical/`og:` URLs -intentionally point at the launch domain `quentinfears.com`. +`public/robots.txt` and `public/sitemap.xml`. The public site is `https://quentinfears.com`; +GitHub Pages publishes it at `https://qafears.github.io/website/` (the publish URL), and +the canonical/`og:` URLs intentionally point at `quentinfears.com`. ### Indexing @@ -227,7 +227,8 @@ home page, then re-optimize (JPEG, ~2400px wide). - Preview locally: `npm run dev` (site at http://localhost:4321, admin at `/keystatic`). Or `npm run build && npm run preview` for the production build. - Deploy: pushing to `main` builds and publishes `dist/` to GitHub Pages via - `.github/workflows/deploy.yml`. Live at https://qafears.github.io/website/. + `.github/workflows/deploy.yml`. The public site is https://quentinfears.com; GitHub + Pages serves it at https://qafears.github.io/website/ (the publish URL, not the front door). ## Weekly routine: curate the site from new @mrqfears Instagram content diff --git a/README.md b/README.md index dbbb669..4f3bb2a 100644 --- a/README.md +++ b/README.md @@ -104,8 +104,8 @@ both on every push and pull request (see [`.github/workflows/ci.yml`](.github/wo editing tool and is not part of the deployed site. This repo is wired for **GitHub Pages**: [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml) -builds and publishes `dist/` on every push to `main` (currently at -`https://qafears.github.io/website/`). +builds and publishes `dist/` on every push to `main`. The public site is +`https://quentinfears.com`; GitHub Pages publishes it at `https://qafears.github.io/website/`. ## Before launch — checklist From dc919d690dcb88544da1726a80ed4a7a398db232 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 15:28:18 +0000 Subject: [PATCH 6/6] Pin quentinfears.com as the GitHub Pages custom domain Add public/CNAME (served verbatim as dist/CNAME) so the custom domain persists across artifact-based Pages deploys, and add it to the domain-change update list in CLAUDE.md. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EpKy9WNMQdEfmyxxFJpagd --- CLAUDE.md | 3 ++- public/CNAME | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 public/CNAME diff --git a/CLAUDE.md b/CLAUDE.md index da216af..ac1c4cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -187,7 +187,8 @@ Everything is keyed to `https://quentinfears.com` as the single source of truth. `https://quentinfears.com` is the single source of truth. Update `SITE_ORIGIN` in [src/lib/content.ts](src/lib/content.ts), `BASE` in [tools/seo_check.py](tools/seo_check.py), and find-and-replace the domain in -`public/robots.txt` and `public/sitemap.xml`. The public site is `https://quentinfears.com`; +`public/robots.txt`, `public/sitemap.xml`, and `public/CNAME` (the Pages custom-domain +pin, served verbatim as `dist/CNAME`). The public site is `https://quentinfears.com`; GitHub Pages publishes it at `https://qafears.github.io/website/` (the publish URL), and the canonical/`og:` URLs intentionally point at `quentinfears.com`. diff --git a/public/CNAME b/public/CNAME new file mode 100644 index 0000000..334f505 --- /dev/null +++ b/public/CNAME @@ -0,0 +1 @@ +quentinfears.com