From b33bdb5356f65b71675237c26e54db9bdb6aa10d Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Tue, 9 Jun 2026 19:02:16 -0500 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20portfolio-first=20site=20=E2=80=94?= =?UTF-8?q?=20drop=20Work=20and=20Skills=20sections,=20r=C3=A9sum=C3=A9=20?= =?UTF-8?q?carries=20career=20history?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The site is a lobby, not a brochure: hero (identity, proof, action) → Open Source tri-panel (what a résumé can't show) → contact. Work and Skills duplicated the résumé, which is one click away in HTML and DOCX. External research on exemplar staff-engineer sites (Chiang, Hashimoto, Brandur, Evans, Luu, McKinley, Robinson, matklad, Majors, swyx) independently flagged the skills listing as the strongest template/junior tell — no staff-level site has one — and confirmed the lobby model for hire-me sites when career detail lives in the résumé. Also: ClassPass work entry deleted — everything pre-Symbiont (2017) lives in the Earlier Career paragraph (its cost win, fleet scale, and early-IaC claim were already quoted there). Symbiont, the long-term salaried role, is the start of the listed history. Nav reduces to Open Source · Contact · Résumé. Decision: site shows only what the résumé cannot; career history starts at Symbiont Why: user directive (redundancy + pre-2017 one-year roles read better as prose); research-validated Resolves: 'do we NEED a work section' / 'Symbiont should be the start of actual roles' Co-Authored-By: Claude Fable 5 --- .agent-state/decisions.ndjson | 1 + public/Jon_Bogaty_Resume.docx | Bin 78696 -> 78696 bytes src/App.tsx | 22 +----- src/components/SiteNav.tsx | 2 - src/components/sections/JobList.tsx | 100 ------------------------- src/components/sections/SkillSheet.tsx | 28 ------- src/content/resume.ts | 19 +---- tests/e2e/navigation.spec.ts | 15 +--- tests/e2e/resume.spec.ts | 19 +---- 9 files changed, 15 insertions(+), 191 deletions(-) delete mode 100644 src/components/sections/JobList.tsx delete mode 100644 src/components/sections/SkillSheet.tsx diff --git a/.agent-state/decisions.ndjson b/.agent-state/decisions.ndjson index 2ebe6836..172489fa 100644 --- a/.agent-state/decisions.ndjson +++ b/.agent-state/decisions.ndjson @@ -1,2 +1,3 @@ {"ts":"2026-06-09T23:13:09.916Z","sha":"2c9a43adaccebc7bda8227d07a70e0df990c4356","subject":"feat: DOCX-first resume pipeline with visual QC and recruiter-driven restructure","decision":"DOCX compiled from typed TS data via turbodocx, not pandoc or raw docx lib","why":"pandoc drops CSS; docx lib broke Apple Pages previously (fdcd220); turbodocx was the validated path and QC now guards it","resolves":["user directive to make DOCX the sole","properly-styled distributable"],"overrides":[]} {"ts":"2026-06-09T23:31:36.852Z","sha":"c0b9051bfacb93637f99b9d9744051e0552ca15a","subject":"feat: DOCX-first resume pipeline with visual QC and recruiter-driven restructure (#152)","decision":"DOCX compiled from typed TS data via turbodocx, not pandoc or raw docx lib","why":"pandoc drops CSS; docx lib broke Apple Pages previously (fdcd220); turbodocx was the validated path and QC now guards it","resolves":["user directive to make DOCX the sole","properly-styled distributable"],"overrides":[],"source":"gh-pr-merge"} +{"ts":"2026-06-09T23:54:29.544Z","sha":"583aec472f6ec4490e7817385fdccc22ad2f28ed","subject":"chore(main): release 1.5.0 (#153)","decision":null,"why":null,"resolves":[],"overrides":[],"source":"gh-pr-merge"} diff --git a/public/Jon_Bogaty_Resume.docx b/public/Jon_Bogaty_Resume.docx index 2dca6cee9e9cd644f67ee015712c44559f168861..2d5f2e7c92c342cb46ec5ddfb236e7297a0b1c71 100644 GIT binary patch delta 357 zcmaFyjOE2M7Ty4FW)?065YS~fHIY}58Axq3e8C8!C(AIYZ~*C;FOyQ%Z}wn1$;5AH z5Mp3pWngG!WUgmwY%p1Y)eA$clGPZjdp)ZHn7+iS2BJ6rW_>RPX1tYoyAsT}du%BO zn9+Lv10R@CaWB9Q#MpjXhB1K&%+ix*MGej3#jBPd7JUGzSY88Za7w8FP&orKj&VV3Y>)?iw&UfEfygj5c6K rydk44m@(gw(F)AC0~3)kf~tu&VzdWK0S$xLdd-N@9W0_?%;*6C4WMcp delta 357 zcmaFyjOE2M7Ty4FW)?065LmePs~3h?C95%5_j*K`8BO5MpKflzXbu)GG+;CUGv*pGN>AT!z$gvo-8Eoz05cQ}8EwFf rctb{8Fk`+UqZOEO2PPt81XUAl#Apwe0vZOf^_mf*J6J@)n9&0OEE|tM diff --git a/src/App.tsx b/src/App.tsx index 80b05a39..1db1f218 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,14 @@ import { HeroSection } from '@/components/HeroSection' import { SiteFooter } from '@/components/SiteFooter' import { SiteNav } from '@/components/SiteNav' -import { JobList } from '@/components/sections/JobList' import { OpenSource } from '@/components/sections/OpenSource' -import { SkillSheet } from '@/components/sections/SkillSheet' import resume from '@/content/resume' /** - * One opinionated scroll, not tabs: who + proof + action above the fold, - * the Work receipts at depth 1, the open-source package detail at depth 2. - * Each section gets its own layout — no shared shell. + * A lobby, not a brochure: identity + proof + action in the hero, then the + * one thing a résumé can't show — the live open-source portfolio. Career + * history and the skills matrix live in the résumé (one click away, HTML + * and DOCX); duplicating them here was noise. */ export default function App() { return ( @@ -27,11 +26,6 @@ export default function App() { />
-
-

Work

- -
-
Open Source
- -
-

Skills

- -
diff --git a/src/components/SiteNav.tsx b/src/components/SiteNav.tsx index fceb1e7d..d1e3f562 100644 --- a/src/components/SiteNav.tsx +++ b/src/components/SiteNav.tsx @@ -4,9 +4,7 @@ import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' const ANCHORS = [ - { id: 'work', label: 'Work' }, { id: 'open-source', label: 'Open Source' }, - { id: 'skills', label: 'Skills' }, { id: 'contact', label: 'Contact' }, ] as const diff --git a/src/components/sections/JobList.tsx b/src/components/sections/JobList.tsx deleted file mode 100644 index 1224037f..00000000 --- a/src/components/sections/JobList.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useState } from 'react' -import { Badge } from '@/components/ui/badge' -import type { Resume, WorkEntry } from '@/content/resume' -import { formatDateRange } from '@/lib/dates' -import { cn } from '@/lib/utils' - -const CLOUD_TECH = new Set(['AWS', 'GCP', 'Azure']) - -export function JobList({ - jobs, - earlierCareer, -}: { - jobs: WorkEntry[] - earlierCareer: Resume['earlierCareer'] -}) { - const [selected, setSelected] = useState(0) - const active = jobs[selected] - const cloud = (active.tech ?? []).filter((t) => CLOUD_TECH.has(t)) - const rest = (active.tech ?? []).filter((t) => !CLOUD_TECH.has(t)) - - return ( -
-
- - -
-

{active.name}

-

- {active.position} · {formatDateRange(active.startDate, active.endDate)} -

- - {(cloud.length > 0 || rest.length > 0) && ( -
- {cloud.map((t) => ( - - {t} - - ))} - {rest.length > 0 && ( - - {rest.join(', ').toLowerCase()} - - )} -
- )} - - {active.summary && ( -

{active.summary}

- )} - - {active.highlights.length > 0 && ( -
    - {active.highlights.map((h, i) => ( -
  • - {i < 2 ? '▸' : '•'} - {h} -
  • - ))} -
- )} -
-
- -
-

- Before 2017 -

-

{earlierCareer.summary}

-
-
- ) -} diff --git a/src/components/sections/SkillSheet.tsx b/src/components/sections/SkillSheet.tsx deleted file mode 100644 index 6bef7c36..00000000 --- a/src/components/sections/SkillSheet.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { SkillCategory } from '@/content/resume' - -const LEAD = - 'Day to day: AWS and GCP, Terraform and Terragrunt, Kubernetes across EKS/GKE/AKS, Python and Go, and the CI/CD and secrets plumbing that ties them together.' - -/** Spec-sheet rows, not a chip cloud — every skill in plain prose. */ -export function SkillSheet({ categories }: { categories: SkillCategory[] }) { - return ( -
-

{LEAD}

-
- {categories.map((cat) => ( -
-
- {cat.name} -
-
- {cat.keywords.join(', ')} -
-
- ))} -
-
- ) -} diff --git a/src/content/resume.ts b/src/content/resume.ts index d17b53db..f1518798 100644 --- a/src/content/resume.ts +++ b/src/content/resume.ts @@ -194,23 +194,10 @@ const resume: Resume = { 'Built Terraform-based repeatable customer deployment workflows for secure installs of DLT infrastructure into enterprise cloud environments on all three major providers', ], }, - { - // Site-only: on the DOCX this is covered by the Earlier Career - // paragraph (its cost win and early-IaC claim are quoted there). - name: 'ClassPass', - position: 'Senior Systems Operations Engineer', - startDate: '2015-04', - endDate: '2016-04', - onResume: false, - tech: ['AWS', 'Docker', 'Terraform', 'Packer', 'Vagrant'], - summary: null, - highlights: [ - 'Containerized services with Docker and adopted Terraform, Packer, Atlas, and Vagrant in 2015 — infrastructure as code before it was standard practice', - 'Managed 200–300 production AWS instances powering the ClassPass desktop and mobile experience for a major international fitness subscription platform', - 'Reduced cloud costs $20K/month by deploying Netflix OSS Janitor Monkey for automated resource lifecycle management', - ], - }, ], + // ClassPass (2015–16) is intentionally NOT a work entry: everything before + // Symbiont (2017) lives in the Earlier Career paragraph below, which + // already carries its cost win, fleet scale, and early-IaC claim. // Everything before Symbiont is compressed into one prose paragraph: // a decade of short roles reads as a consulting-style arc in prose, but as diff --git a/tests/e2e/navigation.spec.ts b/tests/e2e/navigation.spec.ts index 6543efe2..ec662f07 100644 --- a/tests/e2e/navigation.spec.ts +++ b/tests/e2e/navigation.spec.ts @@ -19,9 +19,7 @@ test.describe('Site navigation', () => { test('anchor nav scrolls to sections', async ({ page }) => { await page.goto('/') for (const [label, id] of [ - ['Work', 'work'], ['Open Source', 'open-source'], - ['Skills', 'skills'], ['Contact', 'contact'], ] as const) { const anchor = page.locator(`header a[href="#${id}"]`, { hasText: label }) @@ -31,10 +29,10 @@ test.describe('Site navigation', () => { } }) - test('work section shows master-detail with Flipside', async ({ page }) => { + test('no Work or Skills sections — the résumé carries career history', async ({ page }) => { await page.goto('/') - await page.getByRole('button', { name: /Flipside Crypto/ }).click() - await expect(page.getByText(/Cut AWS spend from ~\$150K/)).toBeVisible() + await expect(page.locator('#work')).toHaveCount(0) + await expect(page.locator('#skills')).toHaveCount(0) }) test('open source tri-panel shows the three flagships with package tables', async ({ page }) => { @@ -46,13 +44,6 @@ test.describe('Site navigation', () => { await expect(oss.getByText('paranoid-passwd-gui')).toBeVisible() }) - test('no badge chip-walls in skills (spec-sheet rows instead)', async ({ page }) => { - await page.goto('/') - const skills = page.locator('#skills') - await expect(skills.getByText('Platform & Reliability')).toBeVisible() - await expect(skills.locator('[data-slot="badge"]')).toHaveCount(0) - }) - test('GitHub link opens in new tab', async ({ page }) => { await page.goto('/') const githubLink = page.getByRole('link', { name: 'GitHub' }).first() diff --git a/tests/e2e/resume.spec.ts b/tests/e2e/resume.spec.ts index 586dc886..5f37f840 100644 --- a/tests/e2e/resume.spec.ts +++ b/tests/e2e/resume.spec.ts @@ -5,21 +5,12 @@ test.describe('Resume content', () => { await page.goto('/') }) - test('Flipside Crypto appears in the Work section', async ({ page }) => { - await expect(page.getByText('Flipside Crypto').first()).toBeVisible() - }) - - test('hero proof line is visible', async ({ page }) => { + test('hero proof line names Flipside', async ({ page }) => { await expect( page.getByText(/sole infrastructure engineer at Flipside Crypto/).first() ).toBeVisible() }) - test('skills section has multiple categories', async ({ page }) => { - await expect(page.getByText('Cloud Platforms').first()).toBeVisible() - await expect(page.getByText('Infrastructure as Code').first()).toBeVisible() - }) - test('education appears in the footer', async ({ page }) => { await expect(page.getByText(/Ivy Tech Community College/).first()).toBeVisible() }) @@ -45,15 +36,13 @@ test.describe('Resume page (print-optimized)', () => { await expect(page.getByText('Education')).toBeVisible() }) - test('resume page has correct job entries', async ({ page }) => { + test('resume page has correct job entries, starting at Symbiont', async ({ page }) => { await page.goto('/resume') await expect(page.getByText('Flipside Crypto').first()).toBeVisible() await expect(page.getByText('GoHealth').first()).toBeVisible() await expect(page.getByText('Symbiont').first()).toBeVisible() - }) - - test('site-only roles stay off the resume page', async ({ page }) => { - await page.goto('/resume') + // Pre-2017 roles live in the Earlier Career paragraph, never as entries await expect(page.getByText('Senior Systems Operations Engineer')).toHaveCount(0) + await expect(page.getByText('Cloud Platforms').first()).toBeVisible() // skills matrix lives here }) }) From b9a94a1ec9784f966ee913355fabe384b0f6ebef Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Tue, 9 Jun 2026 19:04:56 -0500 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20inline=20brand=20SVGs=20=E2=80=94=20?= =?UTF-8?q?lucide-react=20v1=20removes=20deprecated=20Github/Linkedin=20ic?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unblocks the npm-major dependabot bump (#129) whose lucide upgrade turned the long-standing deprecation warnings into type errors. Co-Authored-By: Claude Fable 5 --- .agent-state/decisions.ndjson | 1 + src/components/BrandIcons.tsx | 36 ++++++++++++++++++++++++++++++++++ src/components/HeroSection.tsx | 7 ++++--- 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 src/components/BrandIcons.tsx diff --git a/.agent-state/decisions.ndjson b/.agent-state/decisions.ndjson index 172489fa..4dc89c67 100644 --- a/.agent-state/decisions.ndjson +++ b/.agent-state/decisions.ndjson @@ -1,3 +1,4 @@ {"ts":"2026-06-09T23:13:09.916Z","sha":"2c9a43adaccebc7bda8227d07a70e0df990c4356","subject":"feat: DOCX-first resume pipeline with visual QC and recruiter-driven restructure","decision":"DOCX compiled from typed TS data via turbodocx, not pandoc or raw docx lib","why":"pandoc drops CSS; docx lib broke Apple Pages previously (fdcd220); turbodocx was the validated path and QC now guards it","resolves":["user directive to make DOCX the sole","properly-styled distributable"],"overrides":[]} {"ts":"2026-06-09T23:31:36.852Z","sha":"c0b9051bfacb93637f99b9d9744051e0552ca15a","subject":"feat: DOCX-first resume pipeline with visual QC and recruiter-driven restructure (#152)","decision":"DOCX compiled from typed TS data via turbodocx, not pandoc or raw docx lib","why":"pandoc drops CSS; docx lib broke Apple Pages previously (fdcd220); turbodocx was the validated path and QC now guards it","resolves":["user directive to make DOCX the sole","properly-styled distributable"],"overrides":[],"source":"gh-pr-merge"} {"ts":"2026-06-09T23:54:29.544Z","sha":"583aec472f6ec4490e7817385fdccc22ad2f28ed","subject":"chore(main): release 1.5.0 (#153)","decision":null,"why":null,"resolves":[],"overrides":[],"source":"gh-pr-merge"} +{"ts":"2026-06-10T00:02:18.730Z","sha":"b33bdb5356f65b71675237c26e54db9bdb6aa10d","subject":"feat: portfolio-first site — drop Work and Skills sections, résumé carries career history","decision":"site shows only what the résumé cannot; career history starts at Symbiont","why":"user directive (redundancy + pre-2017 one-year roles read better as prose); research-validated","resolves":["'do we NEED a work section' / 'Symbiont should be the start of actual roles'"],"overrides":[]} diff --git a/src/components/BrandIcons.tsx b/src/components/BrandIcons.tsx new file mode 100644 index 00000000..cf75a4ab --- /dev/null +++ b/src/components/BrandIcons.tsx @@ -0,0 +1,36 @@ +/** + * Brand icons inlined as SVGs — lucide-react removed its deprecated brand + * icons in v1; these paths are from simple-icons (CC0). + */ + +interface IconProps { + className?: string +} + +export function GithubIcon({ className }: IconProps) { + return ( + + ) +} + +export function LinkedinIcon({ className }: IconProps) { + return ( + + ) +} diff --git a/src/components/HeroSection.tsx b/src/components/HeroSection.tsx index 400ea701..61f9b75b 100644 --- a/src/components/HeroSection.tsx +++ b/src/components/HeroSection.tsx @@ -1,5 +1,6 @@ -import { Download, FileText, Github, Linkedin, MessageCircle } from 'lucide-react' +import { Download, FileText, MessageCircle } from 'lucide-react' import { motion } from 'motion/react' +import { GithubIcon, LinkedinIcon } from '@/components/BrandIcons' import { Button } from '@/components/ui/button' interface Stat { @@ -81,7 +82,7 @@ export function HeroSection({ name, label, heroLine, status, stats }: HeroProps) asChild > - +