From 6699dd6b8d47dd629ef121ca1d4cbce7e9b494a0 Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Mon, 7 Sep 2026 06:54:20 -0400 Subject: [PATCH 1/6] copy: replace em dashes with colons/parens/sentence breaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ran site copy through no-ai-slop — one line also cut a binary-contrast pattern ("not X — it is Y") on the Claude Code Placemat card. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Cq3MWjyFGDwwtHqPNJ5oBs --- app/layout.tsx | 4 ++-- components/landing/Hero.tsx | 2 +- components/landing/Resume.tsx | 2 +- lib/content/projects.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index d7da38c..934f657 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -50,7 +50,7 @@ export const metadata: Metadata = { template: "%s | Dom Mangonon", }, description: - "Dom Mangonon builds software with AI — SousIQ, Bracketeer, the Claude Code Placemat, and more. Projects, writing, and a travel map.", + "Dom Mangonon builds software with AI: SousIQ, Bracketeer, the Claude Code Placemat, and more. Projects, writing, and a travel map.", openGraph: { title: "Dom Mangonon", description: @@ -64,7 +64,7 @@ export const metadata: Metadata = { url: "/og.png", width: 1200, height: 630, - alt: "Dom Mangonon — builds software with AI", + alt: "Dom Mangonon: builds software with AI", }, ], }, diff --git a/components/landing/Hero.tsx b/components/landing/Hero.tsx index d6dbf84..568e318 100644 --- a/components/landing/Hero.tsx +++ b/components/landing/Hero.tsx @@ -37,7 +37,7 @@ export function Hero() {
About

- Seventeen years in financial services — operations at BNP Paribas through 2008, an MBA + Seventeen years in financial services: operations at BNP Paribas through 2008, an MBA at CMU Tepper, consulting at PwC, and now SVP at Citi.

diff --git a/components/landing/Resume.tsx b/components/landing/Resume.tsx index a8b69b4..d9bbdb7 100644 --- a/components/landing/Resume.tsx +++ b/components/landing/Resume.tsx @@ -6,7 +6,7 @@ const TIMELINE = [ { y: '2025 →', t: 'Building with AI', - d: 'Shipping software nights and weekends with Claude Code — see above', + d: 'Shipping software nights and weekends with Claude Code (see above)', }, { y: '2021 →', diff --git a/lib/content/projects.ts b/lib/content/projects.ts index b22a743..b7532ff 100644 --- a/lib/content/projects.ts +++ b/lib/content/projects.ts @@ -44,7 +44,7 @@ export const PROJECTS: Project[] = [ impact: 'Live · ran a real World Cup pool', points: [ 'Create a pool, invite friends, make bracket picks, watch a leaderboard update from live results', - 'Knockout seeding follows FIFA Annex C — the tiebreak rules are genuinely gnarly', + 'Knockout seeding follows FIFA Annex C. The tiebreak rules are genuinely gnarly', 'Started as a pool for friends, grew into a multi-tenant platform', ], href: 'https://fifawc26.up.railway.app', @@ -59,7 +59,7 @@ export const PROJECTS: Project[] = [ points: [ 'A one-page reference for Claude Code: shortcuts, slash commands, flags, hooks, MCP', 'A scheduled agent re-reads the latest release every day and opens a PR when anything drifts', - 'The interesting part is not the page — it is that nobody updates it by hand', + 'Nobody updates it by hand', ], href: 'https://dommango.github.io/claude-code-placemat/', hrefKind: 'live', From 3be8f0168018bc272ebc7ed463a3a88ff19b7bdd Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Mon, 7 Sep 2026 14:19:40 -0400 Subject: [PATCH 2/6] fix: correct Bracketeer status, reorder Placemat to first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit World Cup ended, so the app is no longer live in any active sense — swapped the "Live" claim for a concrete fact (player count) and dropped the ongoing-work arrow from the year. Moved Claude Code Placemat to the front of the list since it's the one still actively live and self-maintaining; catalog IDs renumbered to match. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Cq3MWjyFGDwwtHqPNJ5oBs --- lib/content/projects.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/content/projects.ts b/lib/content/projects.ts index b7532ff..4d01630 100644 --- a/lib/content/projects.ts +++ b/lib/content/projects.ts @@ -23,7 +23,21 @@ export interface Project { export const PROJECTS: Project[] = [ { - id: '#sous-0001/05', + id: '#plcm-0001/05', + name: 'Claude Code Placemat', + stack: 'Static HTML · GitHub Actions · scheduled agent', + year: '2026 →', + impact: 'Maintains itself · MIT', + points: [ + 'A one-page reference for Claude Code: shortcuts, slash commands, flags, hooks, MCP', + 'A scheduled agent re-reads the latest release every day and opens a PR when anything drifts', + 'Nobody updates it by hand', + ], + href: 'https://dommango.github.io/claude-code-placemat/', + hrefKind: 'live', + }, + { + id: '#sous-0002/05', name: 'SousIQ', stack: 'Express · React 19 · Postgres + pgvector · Claude', year: '2025 →', @@ -37,11 +51,11 @@ export const PROJECTS: Project[] = [ hrefKind: 'live', }, { - id: '#brkt-0002/05', + id: '#brkt-0003/05', name: 'Bracketeer', stack: 'Next 16 · Prisma 7 · Auth.js · Railway', - year: '2026 →', - impact: 'Live · ran a real World Cup pool', + year: '2026', + impact: 'Ran a real World Cup pool · 40+ players', points: [ 'Create a pool, invite friends, make bracket picks, watch a leaderboard update from live results', 'Knockout seeding follows FIFA Annex C. The tiebreak rules are genuinely gnarly', @@ -50,20 +64,6 @@ export const PROJECTS: Project[] = [ href: 'https://fifawc26.up.railway.app', hrefKind: 'live', }, - { - id: '#plcm-0003/05', - name: 'Claude Code Placemat', - stack: 'Static HTML · GitHub Actions · scheduled agent', - year: '2026 →', - impact: 'Maintains itself · MIT', - points: [ - 'A one-page reference for Claude Code: shortcuts, slash commands, flags, hooks, MCP', - 'A scheduled agent re-reads the latest release every day and opens a PR when anything drifts', - 'Nobody updates it by hand', - ], - href: 'https://dommango.github.io/claude-code-placemat/', - hrefKind: 'live', - }, { id: '#modm-0004/05', name: 'modular-mind', From 208ee83b968d714d1d42b249b94d25676297ba14 Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:26:19 -0400 Subject: [PATCH 3/6] chore: session-end auto-commit on feat/social-preview-and-a11y-pass (2026-09-09 12:26) 1 file changed, 4 insertions(+), 4 deletions(-) Files: projects.ts --- lib/content/projects.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/content/projects.ts b/lib/content/projects.ts index 4d01630..d293ccc 100644 --- a/lib/content/projects.ts +++ b/lib/content/projects.ts @@ -33,8 +33,8 @@ export const PROJECTS: Project[] = [ 'A scheduled agent re-reads the latest release every day and opens a PR when anything drifts', 'Nobody updates it by hand', ], - href: 'https://dommango.github.io/claude-code-placemat/', - hrefKind: 'live', + href: 'https://github.com/dommango/claude-code-placemat', + hrefKind: 'repo', }, { id: '#sous-0002/05', @@ -61,8 +61,8 @@ export const PROJECTS: Project[] = [ 'Knockout seeding follows FIFA Annex C. The tiebreak rules are genuinely gnarly', 'Started as a pool for friends, grew into a multi-tenant platform', ], - href: 'https://fifawc26.up.railway.app', - hrefKind: 'live', + href: 'https://github.com/dommango/bracketeer', + hrefKind: 'repo', }, { id: '#modm-0004/05', From 21c9643c2bfc962f3f3f3eb5a52c67ffe39ae6bb Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:30:35 -0400 Subject: [PATCH 4/6] fix: point Bracketeer card at its repo, not the sign-in-walled app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tournament's over, so the live link is a dead end for visitors without an account. The repo is actually public (github.com/dommango/ bracketeer) despite the case-study draft's stale "Source: Private" note — fixed that too. Placemat keeps its live link since the self-updating page is the actual point of that project. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Cq3MWjyFGDwwtHqPNJ5oBs --- docs/plans/case-study-copy.md | 129 ++++++++++++++++++++++++++++++++++ lib/content/projects.ts | 4 +- 2 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 docs/plans/case-study-copy.md diff --git a/docs/plans/case-study-copy.md b/docs/plans/case-study-copy.md new file mode 100644 index 0000000..59d207d --- /dev/null +++ b/docs/plans/case-study-copy.md @@ -0,0 +1,129 @@ +# Case study copy — working draft + +Scratch file for iterating on Plan 02 content before it goes into `lib/content/projects.ts`. +Not shipped, not linked from the site. Edit freely; I'll re-read this file to pick up changes. + +## Standard (for reference — edit here too if it should change) + +- Direct, evidence-led, active voice, concrete verbs, short paragraphs (~60 words max) +- No generic pleasantries, corporate filler, inflated praise, unsupported certainty, hidden ownership, or em dashes +- Own it explicitly — "I built," "I decided," not passive constructions +- No superlative without a fact under it +- Sections from: Problem → What I built → What broke → Outcome. 2–4 per project, skip one if there's nothing real to say +- Facts strip: only what's confirmed right now — unknowns get omitted, never estimated + +--- + +## 1. Bracketeer — PILOT + +**Headline** +Made an app for the 2026 FIFA World Cup...three days before kickoff...with everyone's picks already made. + +**Problem** +A buddy's pool started as one HTML file: fill in a bracket, export picks as a CSV, email it to the commissioner. Forty-some friends and family did exactly that before the opening match. The plan was to re-enter results by hand after each round. + +Three days before kickoff, I decided that wasn't good enough. That set the first constraint before the first commit: every pick already existed, made in a tool I now had to treat as law. + +**What I built** +A multi-tenant pool platform on Next 16, Prisma 7, and Auth.js, deployed to Railway. Create a pool, invite by link, make picks, watch a leaderboard update from live results. Knockout seeding implements FIFA Annex C. + +The first piece I built wasn't a feature. It was a test: I kept the original scoring function verbatim as an oracle and ran the new engine against it across two thousand randomized brackets. + +**What broke** + +The hard problem wasn't building fast. It was building fast without ever changing an answer. One point off on one bracket, and someone's standing changes under them. + +The oracle test never left the codebase. Every refactor for six weeks had to walk past it. + +**Outcome** +The pool ran on the app from the round of 32 through the final. Nobody's score moved during the migration. + +**Facts** +- Status: Concluded · account required to view +- Players: 40+ in one pool +- Built in: 3 days to launch +- Source: Public — github.com/dommango/bracketeer + +**Links** +- Read the build story ↗ — https://dommangonon.substack.com/p/the-game-had-already-started +- Open live ↗ — https://fifawc26.up.railway.app + +**Image** +- BLOCKED on you: real screenshot of the leaderboard mid-tournament (not the pick sheet the old mock-up used) + +--- + +## 2. SousIQ — LIVE + +**Headline** +A pilot bakery's real invoices, and a bug that looked exactly like a broken AI parser. + +**Problem** +Restaurant operators buy from a handful of vendors on prices that shift often, tracked mostly in spreadsheets and inboxes. Catching a price hike, or a vendor billing above its own quoted price, means matching every invoice line against what's already on file. It's tedious enough that most operators don't do it. + +**What I built** +A parsing and matching pipeline on Express, Postgres with pgvector, and Claude, deployed on Railway. Upload an invoice or bid sheet: OCR and a Claude vision pass read it, then a tiered matcher checks SKU, exact name, fuzzy name, vector embedding, and purchase history against the restaurant's own catalog. Above 85% confidence it auto-approves; below 70%, a person reviews it. + +I decided the match had to be provably right, not just plausible. Get it wrong, and the tool's whole reason for existing (showing an operator where they're overpaying) stops working. + +**What broke** +Migration 041 added the table that stores agent-parse jobs but missed a permission grant for the tenant role. Every image-based upload started failing "permission denied" at the database, and from the user's side it looked like a network error. The fix landed two weeks later. The bug had nothing to do with the parser, and looked exactly like it did. + +Delete a vendor, a recipe, a bid sheet, and it kept reappearing in the list. The mutation hooks invalidated the cache on success instead of updating it right away, so a fast refetch could still catch the old row before the delete landed. I rewrote every delete hook to update state immediately and roll back on failure. + +**Outcome** +The pipeline ran against a real bakery's invoices for two months. Coverage was the real constraint: of about 700 purchase lines in one month, fewer than 40 had a competing quote to check against. A same-vendor check instead, catching a vendor billing above its own listed price, needed no competitor data and held up as the steadier signal. + +**Facts** +- Status: Live · field-tested in a working bakery +- Pilot: Real vendor invoices, two months, Jul-Aug 2026 +- Built in: Active development since March 2026 +- Source: Private + +**Links** +- Open live ↗ — https://sousiq-production.up.railway.app + +**Image** +- BLOCKED on you: real screenshot of the matching review screen or the price-gap view + +--- + +## 3. Claude Code Placemat — LIVE + +**Headline** +Claude Code ships most days. The reference page has to keep up without me. + +**Problem** +Claude Code's feature set changes fast enough that a hand-maintained reference goes stale within days. I started from reference material curated by AI Edge and built a one-page HTML sheet: shortcuts, slash commands, flags, hooks, MCP. A static page is only accurate on the day you write it. + +**What I built** +A scheduled Claude Code agent, not a GitHub Actions cron, runs daily at 9am UTC. It reads the version documented on the page, fetches the official changelog, and exits silently if nothing changed. When something changed, it categorizes the update, edits the page, tags new entries, demotes anything three releases old, and opens a PR. + +104 of the repo's 116 pull requests are these automated syncs, and the agent merges every one of them itself: no review step, no human in the loop for the routine case. + +**What broke** +The automation had a rule to demote entries after three releases, but no matching rule to promote them once confirmed. Nothing catches that before it ships: /plan and /fast sat marked "unverified" live on the page for roughly twenty-five releases before I noticed. The fix added a check that runs the comparison on every pass, not just when an item is first added. + +Two days after the first commit, the automation also bumped the wrong version: its own template number, not the Claude Code release it was tracking. I noticed it live on the page and reverted it before it compounded. A separate attempt to poll an X account for faster updates got built out in full and never shipped; daily turned out to be fast enough. + +**Outcome** +It has run since March 2026: about five months of daily checks, 116 pull requests total, 104 of them opened and merged by the agent with no human step in between. The reference page has never gone stale long enough for anyone to notice. + +**Facts** +- Status: Live · self-updating · MIT +- Cadence: Daily checks since March 2026 +- Pull requests: 116 total, 104 auto-generated +- Source: Public + +**Links** +- Open live ↗ — https://dommango.github.io/claude-code-placemat/ +- Source ↗ — https://github.com/dommango/claude-code-placemat + +**Image** +- BLOCKED on you: screenshot of the reference page itself + +--- + +## 4. modular-mind — TODO (blocked on facts beyond `points`) + +## 5. PRIAL Pipeline — TODO (blocked on facts beyond `points`, no image — data tile only) diff --git a/lib/content/projects.ts b/lib/content/projects.ts index d293ccc..19d2a62 100644 --- a/lib/content/projects.ts +++ b/lib/content/projects.ts @@ -33,8 +33,8 @@ export const PROJECTS: Project[] = [ 'A scheduled agent re-reads the latest release every day and opens a PR when anything drifts', 'Nobody updates it by hand', ], - href: 'https://github.com/dommango/claude-code-placemat', - hrefKind: 'repo', + href: 'https://dommango.github.io/claude-code-placemat/', + hrefKind: 'live', }, { id: '#sous-0002/05', From b7533b579111eb55e16475faf474b8cf6fb20fcf Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:30:49 -0400 Subject: [PATCH 5/6] chore: untrack case-study-copy.md, it's a scratch file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accidentally staged it alongside the Bracketeer link fix. Its own header says "not shipped, not linked from the site" — keeping it untracked matches that and the rest of this session's handling of it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Cq3MWjyFGDwwtHqPNJ5oBs --- docs/plans/case-study-copy.md | 129 ---------------------------------- 1 file changed, 129 deletions(-) delete mode 100644 docs/plans/case-study-copy.md diff --git a/docs/plans/case-study-copy.md b/docs/plans/case-study-copy.md deleted file mode 100644 index 59d207d..0000000 --- a/docs/plans/case-study-copy.md +++ /dev/null @@ -1,129 +0,0 @@ -# Case study copy — working draft - -Scratch file for iterating on Plan 02 content before it goes into `lib/content/projects.ts`. -Not shipped, not linked from the site. Edit freely; I'll re-read this file to pick up changes. - -## Standard (for reference — edit here too if it should change) - -- Direct, evidence-led, active voice, concrete verbs, short paragraphs (~60 words max) -- No generic pleasantries, corporate filler, inflated praise, unsupported certainty, hidden ownership, or em dashes -- Own it explicitly — "I built," "I decided," not passive constructions -- No superlative without a fact under it -- Sections from: Problem → What I built → What broke → Outcome. 2–4 per project, skip one if there's nothing real to say -- Facts strip: only what's confirmed right now — unknowns get omitted, never estimated - ---- - -## 1. Bracketeer — PILOT - -**Headline** -Made an app for the 2026 FIFA World Cup...three days before kickoff...with everyone's picks already made. - -**Problem** -A buddy's pool started as one HTML file: fill in a bracket, export picks as a CSV, email it to the commissioner. Forty-some friends and family did exactly that before the opening match. The plan was to re-enter results by hand after each round. - -Three days before kickoff, I decided that wasn't good enough. That set the first constraint before the first commit: every pick already existed, made in a tool I now had to treat as law. - -**What I built** -A multi-tenant pool platform on Next 16, Prisma 7, and Auth.js, deployed to Railway. Create a pool, invite by link, make picks, watch a leaderboard update from live results. Knockout seeding implements FIFA Annex C. - -The first piece I built wasn't a feature. It was a test: I kept the original scoring function verbatim as an oracle and ran the new engine against it across two thousand randomized brackets. - -**What broke** - -The hard problem wasn't building fast. It was building fast without ever changing an answer. One point off on one bracket, and someone's standing changes under them. - -The oracle test never left the codebase. Every refactor for six weeks had to walk past it. - -**Outcome** -The pool ran on the app from the round of 32 through the final. Nobody's score moved during the migration. - -**Facts** -- Status: Concluded · account required to view -- Players: 40+ in one pool -- Built in: 3 days to launch -- Source: Public — github.com/dommango/bracketeer - -**Links** -- Read the build story ↗ — https://dommangonon.substack.com/p/the-game-had-already-started -- Open live ↗ — https://fifawc26.up.railway.app - -**Image** -- BLOCKED on you: real screenshot of the leaderboard mid-tournament (not the pick sheet the old mock-up used) - ---- - -## 2. SousIQ — LIVE - -**Headline** -A pilot bakery's real invoices, and a bug that looked exactly like a broken AI parser. - -**Problem** -Restaurant operators buy from a handful of vendors on prices that shift often, tracked mostly in spreadsheets and inboxes. Catching a price hike, or a vendor billing above its own quoted price, means matching every invoice line against what's already on file. It's tedious enough that most operators don't do it. - -**What I built** -A parsing and matching pipeline on Express, Postgres with pgvector, and Claude, deployed on Railway. Upload an invoice or bid sheet: OCR and a Claude vision pass read it, then a tiered matcher checks SKU, exact name, fuzzy name, vector embedding, and purchase history against the restaurant's own catalog. Above 85% confidence it auto-approves; below 70%, a person reviews it. - -I decided the match had to be provably right, not just plausible. Get it wrong, and the tool's whole reason for existing (showing an operator where they're overpaying) stops working. - -**What broke** -Migration 041 added the table that stores agent-parse jobs but missed a permission grant for the tenant role. Every image-based upload started failing "permission denied" at the database, and from the user's side it looked like a network error. The fix landed two weeks later. The bug had nothing to do with the parser, and looked exactly like it did. - -Delete a vendor, a recipe, a bid sheet, and it kept reappearing in the list. The mutation hooks invalidated the cache on success instead of updating it right away, so a fast refetch could still catch the old row before the delete landed. I rewrote every delete hook to update state immediately and roll back on failure. - -**Outcome** -The pipeline ran against a real bakery's invoices for two months. Coverage was the real constraint: of about 700 purchase lines in one month, fewer than 40 had a competing quote to check against. A same-vendor check instead, catching a vendor billing above its own listed price, needed no competitor data and held up as the steadier signal. - -**Facts** -- Status: Live · field-tested in a working bakery -- Pilot: Real vendor invoices, two months, Jul-Aug 2026 -- Built in: Active development since March 2026 -- Source: Private - -**Links** -- Open live ↗ — https://sousiq-production.up.railway.app - -**Image** -- BLOCKED on you: real screenshot of the matching review screen or the price-gap view - ---- - -## 3. Claude Code Placemat — LIVE - -**Headline** -Claude Code ships most days. The reference page has to keep up without me. - -**Problem** -Claude Code's feature set changes fast enough that a hand-maintained reference goes stale within days. I started from reference material curated by AI Edge and built a one-page HTML sheet: shortcuts, slash commands, flags, hooks, MCP. A static page is only accurate on the day you write it. - -**What I built** -A scheduled Claude Code agent, not a GitHub Actions cron, runs daily at 9am UTC. It reads the version documented on the page, fetches the official changelog, and exits silently if nothing changed. When something changed, it categorizes the update, edits the page, tags new entries, demotes anything three releases old, and opens a PR. - -104 of the repo's 116 pull requests are these automated syncs, and the agent merges every one of them itself: no review step, no human in the loop for the routine case. - -**What broke** -The automation had a rule to demote entries after three releases, but no matching rule to promote them once confirmed. Nothing catches that before it ships: /plan and /fast sat marked "unverified" live on the page for roughly twenty-five releases before I noticed. The fix added a check that runs the comparison on every pass, not just when an item is first added. - -Two days after the first commit, the automation also bumped the wrong version: its own template number, not the Claude Code release it was tracking. I noticed it live on the page and reverted it before it compounded. A separate attempt to poll an X account for faster updates got built out in full and never shipped; daily turned out to be fast enough. - -**Outcome** -It has run since March 2026: about five months of daily checks, 116 pull requests total, 104 of them opened and merged by the agent with no human step in between. The reference page has never gone stale long enough for anyone to notice. - -**Facts** -- Status: Live · self-updating · MIT -- Cadence: Daily checks since March 2026 -- Pull requests: 116 total, 104 auto-generated -- Source: Public - -**Links** -- Open live ↗ — https://dommango.github.io/claude-code-placemat/ -- Source ↗ — https://github.com/dommango/claude-code-placemat - -**Image** -- BLOCKED on you: screenshot of the reference page itself - ---- - -## 4. modular-mind — TODO (blocked on facts beyond `points`) - -## 5. PRIAL Pipeline — TODO (blocked on facts beyond `points`, no image — data tile only) From 1ee9c15c95343aa9e37ef4abd16f85c5e8ba6059 Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Sat, 12 Sep 2026 14:11:40 -0400 Subject: [PATCH 6/6] docs: add AGENTS.md, move guidance from CLAUDE.md CLAUDE.md now just imports AGENTS.md via @AGENTS.md, matching the convention used elsewhere (see /home/dom/CLAUDE.md). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_018t33aELnMh7R4CQjDFLWP9 --- AGENTS.md | 55 +++++++++++++++++++++++ CLAUDE.md | 130 +----------------------------------------------------- 2 files changed, 56 insertions(+), 129 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..272f94c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,55 @@ +# personal-website — Agent Guide + +Dom Mangonon's single-page portfolio, deployed to https://dommango.github.io. Accessibility +guidelines: `ACCESSIBILITY.md`. + +## Stack + +Next.js 16 App Router · React 19 · TS strict · Tailwind 4 · static export (no server runtime, +all data loaded at build time) · GitHub Pages deploy via `.github/workflows/deploy.yml`. + +## Commands + +- `npm install --legacy-peer-deps` — react-simple-maps declares a React <19 peer; CI uses it too +- `npm run dev` / `build` (→ `out/`) / `lint` · `npx tsc --noEmit` +- `npm test -- --run` (vitest; drop `-- --run` for watch mode) · `npx playwright test` (e2e) + +## Hard rules + +- **Never commit Citi/employer-confidential content.** History was purged once already + (2026-08-28: filter-rewrite + force-push across `main`/feature branches) after regulator- + confidential material (Consent Order text, MRA reporting detail) leaked into commits on this + public repo. Career content is gitignored by design — source of record is `~/personal/career`, + not this repo; don't re-add it or reintroduce a sync step. `'Citi · SVP, Transformation'` + (LinkedIn-grade) is fine; verbatim regulatory/internal detail is not. A dangling old SHA + (`cf65730`) may still be GitHub-support-GC pending — don't assume force-push alone is enough. +- Four places must stay in sync when adding/changing a landing section, or the scroll-spy breaks + silently: the component in `components/landing/`, the `SectionId` union in `Nav.tsx`, + `SECTION_IDS` in `BrutalistLanding.tsx` (must match DOM order — spy takes the last element with + `offsetTop <= scrollY`), and the `link()` calls in `Nav.tsx`. A conditional section (e.g. + Writing) needs its nav link gated by the same predicate as the section. +- Build sections from semantic tokens only (`var(--accent)`, `var(--fg)`, `var(--fg-muted)`, + `var(--rule)`, `var(--s-N)`, `var(--font-*)`) in `app/globals.css` — that's what makes all three + themes (Gold/Oxblood/High Contrast) work via `[data-accent]`/`[data-contrast]`. The landing + (`.brutalist-root`) is dark-only by design and shadows the legacy `:root` light block. One + responsive breakpoint, `@media (max-width: 900px)` at the bottom of globals.css — new grids + aren't automatic. Structure convention: `.section` > `` > `.*-head`, order matters + (adjacent-sibling rule supplies top margin). `BinaryRule`'s `seed` prop drives a deterministic + PRNG for hydration safety, not aesthetics — give new sections an unused seed. +- Content is hand-authored TS in `lib/content/`, not markdown/JSON (`resolveJsonModule` + + `strict` infers `never[]` from an empty JSON array, which fails typecheck). Travel data is the + exception — script-generated JSON via `scripts/process-travel-data.js` / + `scripts/fetch-flights.js` (both manual, not automated). +- `github-actions[bot]` pushes with the default `GITHUB_TOKEN` don't trigger workflows, so a cron + that commits data can't make the site rebuild. Anything needing fresh data at deploy time must + fetch during the build, not commit-then-rebuild. + +## Env (`.env.local`, copy from `.env.example`) + +EmailJS (`NEXT_PUBLIC_EMAILJS_SERVICE_ID`/`_TEMPLATE_ID`/`_PUBLIC_KEY`, +`NEXT_PUBLIC_EMAILJS_CONTACT_TEMPLATE_ID`) — without these the contact form renders but won't +send. `NEXT_PUBLIC_RECAPTCHA_SITE_KEY`. `NOTION_API_KEY` (flight-data sync). +`NEXT_PUBLIC_SITE_URL` / `SITE_URL`. `NEXT_PUBLIC_CHAT_API_URL` (ChatBot widget only renders when +set). GoatCounter analytics: `GOATCOUNTER_API_KEY`, `GOATCOUNTER_SITE`, +`NEXT_PUBLIC_GOATCOUNTER_SITE`. `UPTIMEROBOT_API_KEY` (dashboard). `RESUME_SOURCE` (optional, +defaults to `~/personal/career/Mangonon_Dominic_Resume.html`). diff --git a/CLAUDE.md b/CLAUDE.md index a7d5d88..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,129 +1 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -Dom Mangonon's personal site: a **single-page** portfolio built with Next.js 16, React 19, -TypeScript, and Tailwind CSS 4. Static export (no server runtime), deployed to GitHub Pages -at https://dommango.github.io. - -The page leads with the **project portfolio**, then writing, with career compressed to context. - -## Commands - -```bash -npm run dev # Start dev server (localhost:3000) -npm run build # Build static site (output: out/) -npm test # Run Vitest tests (watch mode) -npm test -- --run # Run tests once -npm run lint # ESLint check -npx tsc --noEmit # Type check -npx playwright test # E2E -``` - -Note: `npm install` needs `--legacy-peer-deps` (react-simple-maps declares a React <19 peer). -CI uses it too. - -## Architecture - -The whole site is **one page**. `app/page.tsx` is a server component that loads data at build -time and hands it to `components/landing/BrutalistLanding.tsx`, a client shell holding theme + -scroll-spy state and composing every section. - -``` -app/ -├── layout.tsx # Root layout, fonts, metadata/JSON-LD, ChatBot (only if NEXT_PUBLIC_CHAT_API_URL is set) -├── page.tsx # Loads projects/writing/travel data -> BrutalistLanding -├── globals.css # All styles (see Design System below) -└── dashboard-m7x9k2/ # Private-ish analytics dashboard (obscure URL, not linked) - -components/landing/ # The page, in DOM order: -├── Nav.tsx # Sticky nav + theme cycler -├── Availability.tsx # Status strip under nav -├── Hero.tsx # Headline, portrait, bio -├── Projects.tsx # THE MAIN SECTION — project cards -├── Writing.tsx # Substack posts; renders only when posts exist -├── Resume.tsx # Compressed career timeline -├── Travel.tsx # Continent bars + globe -├── Contact.tsx # EmailJS-wired form -├── Footer.tsx -└── BinaryRule.tsx # Decorative divider (seeded PRNG — see below) - -components/travel/TravelMap.tsx # Heavy globe, dynamically imported by landing/Travel -components/chat/ChatBot.tsx # Assistant widget -components/ui/ # Card, Skeleton, ProgressBar — dashboard only - -lib/content/ # Build-time data (no fs, no runtime fetch) -├── projects.ts # Hand-authored PROJECTS array -├── writing.ts # Substack posts -└── travel.ts # Transforms content/travel/*.json -lib/services/ # emailjs.ts, chat.ts -``` - -## Adding or changing a section - -Four places must stay in sync or the scroll-spy breaks silently: - -1. `components/landing/

.tsx` — the component -2. `SectionId` union in `Nav.tsx` -3. `SECTION_IDS` in `BrutalistLanding.tsx` — **must match DOM order**; the spy takes the last - element with `offsetTop <= scrollY`, so wrong order = wrong active link -4. The `link()` calls in `Nav.tsx` - -Conditional sections (Writing) need the nav link gated by the same predicate as the section, -or the link scrolls to nothing. - -## Design System - -All styles live in `app/globals.css` (~1130 lines). Two disjoint token systems: - -- **`:root` (lines 1-121)** — legacy tokens + Tailwind bridge. Used by the dashboard/ChatBot only. -- **`.brutalist-root` (128-239)** — the landing. Everything is scoped here. - -**Build new sections from semantic tokens only** — `var(--accent)`, `var(--fg)`, `var(--fg-muted)`, -`var(--fg-low)`, `var(--rule)`, `var(--s-N)` spacing, `var(--font-display|sans|mono)`. Do that and -all three themes (Gold / Oxblood / High Contrast) work for free, since the theme cycler only swaps -token values via `[data-accent]` / `[data-contrast]` attributes. - -The landing is **dark-only by design** — the `prefers-color-scheme: light` block only touches -`:root`, which `.brutalist-root` shadows. - -**Responsive: one breakpoint**, `@media (max-width: 900px)` at the bottom. Any new multi-column -grid must be added there manually; nothing is automatic. - -Structure convention: `.section` > `` > `.*-head`. The adjacent-sibling rule -`.section > .binary-rule + *` supplies the top margin, so keep that order. - -`BinaryRule`'s `seed` prop drives a deterministic sine-hash PRNG — it exists for **hydration -safety** (server and client must render identical digits), not aesthetics. Give new sections an -arbitrary unused seed. - -## Content - -Hand-authored TypeScript in `lib/content/`, not markdown. Prefer typed TS modules over JSON: -`resolveJsonModule` is on, and importing an empty `[]` JSON file infers `never[]`, which fails -typecheck under `strict`. - -Travel data is the exception — script-generated JSON in `content/travel/`, produced by -`scripts/process-travel-data.js` and `scripts/fetch-flights.js` (both manual). - -**Career content is deliberately NOT in this repo** — it's gitignored. This repo is public; the -source of record is `~/personal/career`. Don't re-add it or reintroduce a sync step. - -## Deployment - -`.github/workflows/deploy.yml` — on push to `main` (+ manual), builds and pushes `out/` to Pages. - -**Gotcha:** pushes made by `github-actions[bot]` with the default `GITHUB_TOKEN` do **not** -trigger workflows. So a cron that commits data cannot make the site rebuild. Anything needing -fresh data at deploy time must fetch **during the build**, not commit-then-rebuild. - -`update-dashboard-data.yml` crons analytics/uptime/performance JSON into `public/data/`. - -## Testing - -Vitest + jsdom for units (`__tests__/`), Playwright for E2E (`e2e/`). `vitest.setup.ts` mocks -`matchMedia` and `IntersectionObserver`. - -Test nontrivial logic (parsers, transforms, conditional-render invariants). Skip ceremony tests. +@AGENTS.md