diff --git a/CLAUDE.md b/CLAUDE.md index aae12d5..ac1c4cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,6 +18,53 @@ 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", 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: + +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, 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 + 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. + +**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 & + 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`, @@ -140,9 +187,10 @@ 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://obartra.github.io/quentin/`; the canonical/`og:` URLs -intentionally point at the launch domain `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`. ### Indexing @@ -180,7 +228,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://obartra.github.io/quentin/. + `.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 @@ -190,8 +239,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 +332,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 +352,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. @@ -317,19 +371,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. diff --git a/README.md b/README.md index cf7490d..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://obartra.github.io/quentin/`). +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 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 --------------------------------------------------- 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