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 .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ jobs:
with:
node-version: 24
cache: npm
- name: Guard protected sources and published snapshots
- name: Guard protected sources
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
FORCED_PUSH: ${{ github.event.forced }}
AUTHORIZED_ROOT_RESET_SHA: ${{ vars.AUTHORIZED_ROOT_RESET_SHA }}
run: npm run check:published-copy
run: npm run check:protected-sources
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Validate and build
Expand Down
50 changes: 5 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,7 @@
# Theodore Ouyang

This repository contains the source for [theodoreoy.com](https://www.theodoreoy.com/),
a selective personal archive built around four public sections:

- **Home** — a particle-first opening stage followed by Theodore's profile,
biography, and contact coordinates.
- **Education** — Theodore's academic record and selected coursework.
- **Past Experience** — a five-domain archive generated from a validated source
record.
- **Current Chapter** — a concise account of Theodore's exploration of practical
AI use cases in everyday life.

The Home page gives visual priority to an interactive Three.js particle field.
A same-shape particle fallback makes the first visible frame a stable word;
the canvas then takes over without flashing solid text. The complete identity
and biographical content remains available as static HTML below it.

## Architecture and publishing

The site uses the Next.js App Router, React, and TypeScript, and ships as a
Next.js static export. There is no application server, database, or request-time
API: GitHub Pages serves the generated HTML, CSS, JavaScript, fonts, and images.
Pull requests validate the complete export; pushes to `main` deploy it.
[![Theodore Ouyang — Exploring practical AI use cases](public/assets/brand/og-1774.jpg)](https://www.theodoreoy.com/)

## Development and validation

```sh
npm ci --ignore-scripts
npm run check
npm audit --omit=dev
npm run preview:static
```

`npm run check` runs linting, TypeScript, a production build, and final-artifact
tests. Generated `.next/` and `out/` directories are intentionally not committed.
See [`website-maintenance/`](website-maintenance/) for the source map, design
boundaries, particle-system notes, and release checklist.

## Rollback

Publish changes through a pull request and keep `main` history intact. To undo a
release, create a new `codex/` branch from the current `main`, revert the relevant
merge commit, run the full validation suite, and merge that rollback through a
new pull request. Do not rewrite the published branch.
# Theodore Ouyang

[Visit the website →](https://www.theodoreoy.com/)
[theodoreoy.com](https://www.theodoreoy.com/) is a living archive of the
questions Theodore pursues, the work he chooses to preserve, and the ways his
thinking changes over time. It is selective and honest.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"check:published-copy": "node scripts/check-published-copy-fixtures.mjs",
"check:protected-sources": "node scripts/check-protected-sources.mjs",
"preview:static": "node scripts/preview-static.mjs",
"typecheck": "tsc --noEmit",
"test": "npm run build && npm run test:artifact",
Expand Down
4 changes: 2 additions & 2 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
- `optimize-images.mjs` creates reproducible web derivatives while preserving
the original image files.

Whenever a public route is added, update the content registry and add its
append-only published-copy snapshot before release.
Whenever a public route is added, update the route registry, sitemap
expectations, and final-artifact tests in the same change.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { execFileSync, spawnSync } from "node:child_process";

const baseSha = process.env.BASE_SHA;
if (!baseSha) {
throw new Error("BASE_SHA is required to verify append-only published copy fixtures.");
throw new Error("BASE_SHA is required to verify protected sources.");
}

const forcedPush = process.env.FORCED_PUSH === "true";
Expand All @@ -25,7 +25,6 @@ const protectedPathspecs = [
":(literal)app/past-experience/components/ExperienceDomainPage.tsx",
":(literal)app/lib/content/experience.ts",
":(literal)content/past-experience/archive-through-2026-06-30.md",
":(glob)tests/fixtures/published-copy/*.json",
];

const mergeBaseCheck = spawnSync(
Expand All @@ -46,12 +45,12 @@ let comparisonMessage;
if (mergeBaseCheck.status === 0) {
revisions = [`${baseSha}...HEAD`];
comparisonMessage =
"Protected Education, Past Experience, and Current Chapter sources are unchanged; published-copy snapshots contain additions only.";
"Protected Education, Past Experience, and Current Chapter sources are unchanged.";
} else if (isAuthorizedRootReset) {
if (mergeBaseCheck.status === 1) {
revisions = [baseSha, "HEAD"];
comparisonMessage =
"Authorized root reset detected; direct tree comparison confirms protected Education, Past Experience, and Current Chapter sources are unchanged and published-copy snapshots contain additions only.";
"Authorized root reset detected; direct tree comparison confirms protected Education, Past Experience, and Current Chapter sources are unchanged.";
} else {
const emptyTreeSha = execFileSync(
"git",
Expand All @@ -60,7 +59,7 @@ if (mergeBaseCheck.status === 0) {
).trim();
revisions = [emptyTreeSha, "HEAD"];
comparisonMessage =
"Authorized root reset detected without the previous object; protected Education, Past Experience, and Current Chapter sources and preservation snapshots are introduced as additions.";
"Authorized root reset detected without the previous object; protected Education, Past Experience, and Current Chapter sources are introduced as additions.";
}
} else {
throw new Error(
Expand Down Expand Up @@ -89,7 +88,7 @@ const forbidden = changes

if (forbidden.length > 0) {
throw new Error(
"Protected Education, Past Experience, and Current Chapter sources are immutable, and published-copy snapshots are append-only; modification, deletion, and rename are forbidden:\n" +
"Protected Education, Past Experience, and Current Chapter sources are immutable; modification, deletion, and rename are forbidden:\n" +
forbidden.join("\n"),
);
}
Expand Down
32 changes: 0 additions & 32 deletions tests/fixtures/published-copy/README.md

This file was deleted.

26 changes: 11 additions & 15 deletions tests/rendered-html.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -477,39 +477,35 @@ test("Education retains all 31 selected courses", async () => {
);
});

test("README mirrors the current four-page site and its operating model", async () => {
test("README stays a concise public introduction", async () => {
const readme = await readFile(path.join(ROOT, "README.md"), "utf8");
assert.match(readme, /^# Theodore Ouyang$/mu);
for (const page of ["Home", "Education", "Past Experience", "Current Chapter"]) {
assert.match(readme, new RegExp(`\\b${page}\\b`, "u"), `README is missing ${page}`);
}
assert.match(readme, /particle[- ]first/iu);
assert.match(readme, /(?:same-shape|matching)\s+(?:particle\s+)?fallback/iu);
assert.match(readme, /Next(?:\.js)?\s+static export/iu);
assert.match(readme, /GitHub Pages/u);
assert.match(readme, /npm run check/u);
assert.match(readme, /npm run preview:static/u);
assert.match(
readme,
/\[Visit (?:the )?website[^\]]*\]\(https:\/\/www\.theodoreoy\.com\/\)/iu,
/\[!\[Theodore Ouyang[^\]]*\]\(public\/assets\/brand\/og-1774\.jpg\)\]\(https:\/\/www\.theodoreoy\.com\/\)/u,
);
assert.match(readme, /\[theodoreoy\.com\]\(https:\/\/www\.theodoreoy\.com\/\) is a living archive/u);
assert.match(readme, /It is selective and honest\./u);
assert.doesNotMatch(
readme,
/^##\s|<!--|<details|The archive|A note on the design|Repository note|deliberately\s+unfinished|npm run|Next(?:\.js)?\s+static export|GitHub Pages/imu,
);
assert.doesNotMatch(readme, /Quis ego sum\?/iu);

const normalized = readme.toLowerCase();
for (const marker of RETIRED_MARKERS) {
assert.ok(!normalized.includes(marker), `Retired marker remains in README: ${marker}`);
}
});

test("the published-copy guard covers every protected source, including Current Chapter", async () => {
test("the protected-source guard covers every protected source, including Current Chapter", async () => {
const guard = await readFile(
path.join(ROOT, "scripts", "check-published-copy-fixtures.mjs"),
path.join(ROOT, "scripts", "check-protected-sources.mjs"),
"utf8",
);
for (const relative of PROTECTED_SOURCE_HASHES.keys()) {
assert.ok(
guard.includes(`:(literal)${relative}`),
`Published-copy guard is missing protected source: ${relative}`,
`Protected-source guard is missing protected source: ${relative}`,
);
}
});
Expand Down
6 changes: 0 additions & 6 deletions types/README.md

This file was deleted.

2 changes: 1 addition & 1 deletion website-maintenance/02-repository-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ there automatically.

`preview-static.mjs` serves the exact `out/` artifact locally.
`optimize-images.mjs` regenerates responsive image derivatives.
`check-published-copy-fixtures.mjs` enforces protected-source and snapshot rules.
`check-protected-sources.mjs` enforces protected-source immutability.

### `tests/`

Expand Down
3 changes: 1 addition & 2 deletions website-maintenance/04-release-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ transparency, and high contrast.
- Never commit generated or local-state directories.
- Push a `codex/` branch and open a pull request against `main`.
- Require the pull-request build to pass before merge.
- Confirm the preservation gate reports protected sources unchanged and
published-copy snapshot additions only.
- Confirm the preservation gate reports protected sources unchanged.
- GitHub Pages deploys only after the validated change reaches `main`.
- After deployment, verify the Home page, one dynamic experience route,
`/robots.txt`, and `/sitemap.xml`.
5 changes: 2 additions & 3 deletions website-maintenance/05-architecture-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ runtime listeners rather than allowing a later ready state to overwrite it.

CI protects the Education, Past Experience, and Current Chapter source files
directly, while the artifact suite verifies their normalized SHA-256 hashes and
approved structural counts. Future published-copy snapshots are append-only.
This lets an explicitly approved site change retire unrelated content without
weakening the protected biographical record.
approved structural counts. This lets an explicitly approved site change
retire unrelated content without weakening the protected biographical record.

## Image policy

Expand Down
5 changes: 2 additions & 3 deletions website-maintenance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ assistants.
## Non-negotiable guardrail

Education, Past Experience, and Current Chapter source files are protected from
modification. Published-copy snapshots are append-only. Any other removal or
rewrite requires Theodore's explicit approval and must update routes, tests,
and documentation in the same change.
modification. Any other removal or rewrite requires Theodore's explicit
approval and must update routes, tests, and documentation in the same change.

The visual system is intentionally restrained. Preserve its typography,
material hierarchy, responsive behavior, reduced-motion support, and immediate
Expand Down
Loading