Purpose: This document serves as a persistent memory for Claude Code sessions, providing context about the project, recent work, and important patterns to follow.
Maintenance: When completing significant work, update the "Recent Updates" section with a dated entry. Archive entries older than 3 months to the bottom of the file.
Primary audience: Engineering peers and the technical community—fellow engineers, CTOs, and technical leaders who appreciate craft and thoughtful problem-solving. Visitors arrive to evaluate Brennan's work, learn from his writing on engineering leadership and value-based care, or explore his photography. They expect a site that reflects the same care and attention to detail they'd expect in well-architected software.
Voice: Direct but warm. Explains complex ideas clearly without being condescending. Shares openly about challenges and learnings.
Tone: Reflective and substantive—prioritizes depth over polish, insight over self-promotion.
Three words: Thoughtful, Builder, Curious
Emotional goals: Visitors should feel that Brennan is approachable and thoughtful ("I'd like to work with or learn from this person") and creative/distinctive ("This person thinks differently and has taste").
Visual tone: Sophisticated minimalism with warmth. The current "Slate Executive" palette (cool mist backgrounds, steel blue and dusty teal accents, warm copper CTAs) establishes a calm, professional foundation while the serif typography (EB Garamond) and subtle texture add personality.
Current design language:
- Full-bleed sections with semi-transparent backgrounds reveal floating particles beneath
- Work cards with hover states create tactile, interactive feel
- Section labels in small caps + elegant serif italic headings establish hierarchy
- Generous whitespace and 680px content width prioritize readability
Theme: Light mode only (current implementation)
Colors in use:
| Token | Value | Usage |
|---|---|---|
| Background | #f0f2f5 |
Cool Mist - page background |
| Foreground | #2c333a |
Deep Charcoal Blue - body text |
| Primary | #5a7684 |
Steel Blue - headers, branding |
| Accent | #749ca8 |
Dusty Teal - links, borders |
| Accent Bold | #c17f59 |
Warm Copper - CTAs, emphasis |
| Muted | #e8eaed / #5a6570 |
Subtle backgrounds, secondary text |
-
Clarity over cleverness: Every design choice should make content easier to consume, not harder. Typography, spacing, and visual hierarchy serve readability first.
-
Warmth within professionalism: Balance the cool, sophisticated palette with warm touches—the copper accent, the serif italics, the subtle texture—that make the site feel human and inviting.
-
Craft reflects craft: The site should demonstrate the same attention to detail and quality that Brennan brings to engineering work. Small refinements matter.
-
Content breathes: Generous whitespace, constrained content width, and clear section boundaries let the writing and photography take center stage.
-
Accessible by default: WCAG 2.1 AA compliance, reduced motion support, semantic markup. Good design works for everyone.
| Component | Version | Notes |
|---|---|---|
| Next.js | 16.1.1 | Turbopack default, async route params |
| React | 19.2.3 | Using automatic JSX runtime |
| TypeScript | 5.9.3 | Strict mode enabled |
| Notion Client | 5.6.0 | Uses dataSources API (not databases) |
| Tailwind CSS | 4.1.18 | v4 with Lightning CSS |
| Vitest | 4.0.16 | Test runner with Istanbul coverage |
| ESLint | 9.39.2 | Flat config format (eslint.config.mjs) |
- Tests: 132 tests (2 skipped)
- Build Time: ~21s (production static build)
- Pages: 34 static pages
- Node Version: 24.x
- Type: Static site generation with Astro
- CMS: Notion as headless CMS via Content Collections
- Deployment: Cloudflare Pages
- Sections: "All" (general posts) and "VBC" (Value-Based Care series)
- Images: Local images in public/images/
- Testing First: Run tests before any changes (
npm test) - Type Safety: Verify TypeScript compilation (
npm run typecheck) - Build Verification: Test production builds after major changes (
npm run build) - Feature Branches: Use descriptive branch names (e.g.,
upgrade/nextjs-16,feature/json-feed)
// ✅ Correct (v5.x API)
await notion.dataSources.query({
data_source_id: NOTION_DATABASE_ID,
// ...
});
// ❌ Incorrect (old v4.x API)
await notion.databases.query({
database_id: NOTION_DATABASE_ID,
// ...
});// ✅ Correct (Next.js 16 requires Promise)
interface PageProps {
params: Promise<{ slug: string }>;
}
export default async function Page({ params }: PageProps) {
const { slug } = await params;
// ...
}// Required in next.config.ts for custom quality values
images: {
qualities: [75, 85], // Must list all quality values used
formats: ['image/webp', 'image/avif'],
// ...
}// eslint.config.mjs - ESLint v9 uses flat config format
import nextPlugin from 'eslint-config-next';
const eslintConfig = [
{
ignores: ['.next/**', 'node_modules/**', 'coverage/**'],
},
...nextPlugin,
{
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},
{
files: ['**/__tests__/**/*.ts', '**/__tests__/**/*.tsx'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
},
];
export default eslintConfig;Note: ESLint v9 requires flat config format. Legacy .eslintrc.json files are no longer supported.
- Vitest: Test runner with global imports configured
- React Testing Library: Behavioral testing, avoid implementation details
- Mocking: Use
vi.mocked()for type-safe mocks - Snapshots: Update with
npm test -- -uafter intentional changes - Timezone Handling: Use regex for date assertions (e.g.,
/Aug (19|20), 2023/)
| Purpose | Location | Example |
|---|---|---|
| Components | src/components/ |
post-card.tsx |
| API Routes | src/app/*/route.ts |
feed.json/route.ts |
| Utilities | src/lib/ |
notion.ts, config.ts |
| Tests | __tests__/ |
__tests__/lib/notion.test.ts |
| Scripts | scripts/ |
cache-posts.ts |
| Documentation | docs/ |
*.md files |
| Config | Root directory | eslint.config.mjs, next.config.ts |
# Development
npm run dev # Start dev server (Turbopack HMR)
npm run build # Production build
npm run cache:posts # Fetch & cache posts from Notion
# Testing
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
npm run test:ui # Vitest UI
# Quality Checks
npm run typecheck # TypeScript validation
npm run lint # ESLintNote: Keep high-level summaries here. Link to detailed docs in
/docsfor full context.Archive Policy: Move entries older than 3 months to the "Archive" section at the bottom.
Visual Theme Overhaul (PR #872):
- ✅ Redesigned homepage layout with improved visual hierarchy
- ✅ Updated footer design with better spacing and styling
- ✅ Enhanced particle animation system with theme-aware colors
- ✅ Improved table of contents component styling
- ✅ Refreshed post layout and VBC footer components
- ✅ Added 190+ lines of new CSS utilities in
globals.css
Floating Particles Optimization:
- ✅ Disabled particle background on mobile browsers (viewport < 768px)
- ✅ Uses existing
useIsMobilehook for responsive detection - ✅ Improves mobile performance by skipping animation entirely
Infrastructure:
- ✅ Added GitHub Actions workflow for Claude Code (
.github/workflows/claude.yml) - ✅ Updated all dependencies to latest versions
- ✅ Node.js 24.x support confirmed
Key Metrics:
- Tests: 132 passing
- All dependencies current
- 4 low severity vulnerabilities (acceptable)
January 2026 - Astro Migration:
- ✅ Migrated from Next.js to Astro 5
- ✅ Deployed to Cloudflare Pages (was Vercel)
- ✅ Content Collections with custom Notion loader
- ✅ React islands for interactive components
Notion API Errors
- Ensure using
dataSources.query()notdatabases.query()(v5+ API) - Verify
data_source_idis correct in environment variables - Check API version is compatible (default 2025-09-03 works)
Test Failures
- Date assertions: Use regex for timezone variations (e.g.,
/Aug (19|20), 2023/) - Snapshot mismatches: Review changes, update with
npm test -- -uif intentional - Mock errors: Use
vi.mocked()for type-safe mocking
Detailed Update History (Expand for full details)
Major Accomplishments:
- ✅ Next.js 16 Upgrade: 15.5.6 → 16.0.0, Turbopack default, 19% faster builds
- ✅ ESLint v9 Migration: 8.57.1 → 9.38.0, flat config format, fixed React hooks pattern
- ✅ Test Coverage: 59.79% → 75.79% (+95 tests across 7 new test files)
- ✅ Image Optimization: Responsive sizing, WebP/AVIF support, 40-60% bandwidth reduction
- ✅ JSON Feed: Added JSON Feed 1.1 route with Schema.org enhancements
- ✅ Code Quality: Centralized config, standardized error handling, API route tests
Documentation:
Date: 2025-10-24
Successfully upgraded ESLint to v9 and updated all outdated dependencies for Next.js 16 compatibility.
Packages Updated:
- @vitejs/plugin-react: 5.0.4 → 5.1.0 (minor update)
- ESLint: 8.57.1 → 9.38.0 (major upgrade)
- Migrated to flat config format (
eslint.config.mjs) - Removed legacy
.eslintrc.jsonfile - Added
coverage/to ignore patterns
- Migrated to flat config format (
- eslint-config-next: 15.5.6 → 16.0.0 (Next.js 16 compatibility)
- react-syntax-highlighter: Evaluated v16 upgrade
- v16.0.0 has refractor v5 compatibility issues
- Staying on v15.6.6 until patches released
- Security fixes in v16 only affect refractor (we use Prism)
Code Fixes:
- Fixed
use-mobile.tsReact hook (src/hooks/use-mobile.ts:6-13)- Resolved ESLint error: "Calling setState synchronously within an effect"
- Used lazy initializer pattern for
useState - Added SSR check with
typeof window !== 'undefined'
ESLint v9 Migration:
- Created new
eslint.config.mjswith flat config format - Native import of
eslint-config-next(no FlatCompat needed) - Updated ignore patterns to include
coverage/** - Preserved all custom rules for tests and project
Files Modified:
eslint.config.mjs(created).eslintrc.json(removed)src/hooks/use-mobile.ts(fixed React pattern)package.json&package-lock.json(dependency updates)
Verification:
- ✅ All 221 tests passing
- ✅ ESLint v9 running without errors or warnings
- ✅ TypeScript compilation successful
- ✅ Production build successful (1.7s)
- ✅ 0 security vulnerabilities (down from 3)
Key Learnings:
- ESLint v9 requires flat config format (
.mjsfile) eslint-config-next@16works natively with ESLint v9- React hooks should initialize state with lazy initializers, not in effects
- Coverage directory should be ignored in ESLint config
Date: 2025-10-23
Implemented Phase 1 and Phase 2 of the test coverage improvement plan from the code quality audit.
Coverage Improvement:
- Coverage: 59.79% → 75.79% (+16%)
- Tests: 126 → 221 (+95 tests)
- Test Files: 10 → 17 (+7 files)
- Grade: A- (88/100) → A (93/100)
Phase 1: Error Handling Tests (29 tests)
__tests__/lib/config.test.ts- 11 tests for environment validation__tests__/lib/errors.test.ts- 18 tests for error utilities
Phase 2: UI Component Tests (66 tests)
__tests__/components/post-card.test.tsx- 9 tests__tests__/components/photo-card.test.tsx- 11 tests__tests__/components/series-post-card.test.tsx- 16 tests__tests__/components/vbc-footer.test.tsx- 13 tests__tests__/components/ui/table.test.tsx- 19 tests
Test Improvements:
- Fixed timezone issues with date formatting tests
- Fixed reading time calculation tests (250 words = 2 min read at 225 wpm)
- Improved VBC Footer mocking with
vi.mocked()pattern - Added behavioral assertions for user interactions
Verification:
- ✅ All 221 tests passing
- ✅ 75.79% coverage (exceeded 70% target by 5.79%)
- ✅ TypeScript compilation successful
- ✅ Production build successful
See docs/CODE_QUALITY_AUDIT_2025-10-23.md (updated) for remaining work.
Date: 2025-10-23
Implemented Phase 1 quick wins from the image optimization plan to reduce bandwidth usage.
Optimizations:
-
Responsive Image Sizing: Added
sizesprop to PhotoCard"(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"- Prevents loading oversized images on mobile devices
-
Quality Settings: Added
quality={85}for high-quality photos- Better balance between quality and file size
-
Modern Formats: Configured WebP and AVIF in
next.config.tsformats: ['image/webp', 'image/avif']- Automatic format selection based on browser support
-
Priority Loading: Added
priorityprop support to PhotoCard- Used on homepage hero image for better LCP
- Prevents lazy loading for above-the-fold images
-
MDX Image Improvements: Better aspect ratio (1200x800 vs 1000x1000)
- Responsive sizes for blog post images
- Consistent quality settings
Expected Results:
- 40-60% bandwidth reduction overall
- 70-75% reduction on mobile devices
- Improved Largest Contentful Paint (LCP)
- Better Core Web Vitals scores
Files Modified:
src/components/photo-card.tsx- Added sizes, quality, priority propssrc/components/mdx-component.tsx- Improved dimensions and sizesnext.config.ts- Added formats, deviceSizes, imageSizes, qualitiessrc/app/page.tsx- Added priority to homepage hero image
See docs/IMAGE_OPTIMIZATION_IMPLEMENTATION.md for detailed metrics.
Date: 2025-10-23
Successfully added JSON Feed 1.1 support and enhanced Schema.org JSON-LD structured data across the blog.
JSON Feed Implementation:
- Added JSON Feed 1.1 route at
/feed.json - Auto-discovery link in HTML head for feed readers
- Full content included in feed items (better UX than RSS summaries)
- Custom
_word_countextension for analytics - Section-based filtering (matches RSS behavior - "All" section only)
- 16 comprehensive tests covering structure, content, and edge cases
Schema.org JSON-LD Enhancements:
- Homepage now includes Person + Blog schema with
@graph - Blog posts use
BlogPostingtype with enhanced properties - Photos use
Photographtype (more specific than generic BlogPosting) - Added
wordCountproperty for writing posts (SEO benefit) - Added
articleSectionfor VBC posts ("Value-Based Care") - Added
keywordsarray based on post section - Enhanced image objects with
ImageObjecttype and captions - Absolute image URLs for better search engine compatibility
- Added author and publisher URLs for entity recognition
- 13 new tests for JSON-LD validation
Files Created:
src/app/feed.json/route.ts- JSON Feed route handler__tests__/app/feed.test.ts- 16 tests for JSON Feeddocs/JSON_FEED_AND_SCHEMA_IMPLEMENTATION_PLAN.md- Implementation plan
Files Modified:
src/app/layout.tsx- Added feed auto-discovery linkssrc/app/page.tsx- Added homepage Person + Blog JSON-LDsrc/lib/page-utils.ts- EnhancedgenerateJsonLd()function__tests__/lib/page-utils.test.ts- Added 13 new JSON-LD tests
Test Coverage Improvement:
- Tests: 97 → 126 (+29 tests)
- Test files: 9 → 10 (+1 file)
- All 126 tests passing
Verification:
- ✅ All 126 tests passing
- ✅ TypeScript compilation successful
- ✅ Production build successful
- ✅ JSON Feed 1.1 spec compliant
- ✅ Schema.org valid (Person, Blog, BlogPosting, Photograph types)
- ✅ Auto-discovery working (RSS + JSON Feed)
Benefits:
- Modern JSON-based feed format (easier to parse than XML)
- Improved SEO with enhanced structured data
- Rich snippets potential in search results
- Better entity recognition for search engines
- Word count and article sections for content categorization
See docs/JSON_FEED_AND_SCHEMA_IMPLEMENTATION_PLAN.md for full implementation details.
Date: 2025-10-19
Successfully completed all high-priority items from the code quality audit:
Improvements Made:
-
Added API Route Tests (26 new tests)
- RSS feed route: 13 tests covering XML structure, CDATA escaping, section filtering
- Sitemap route: 13 tests covering URL generation, priorities, change frequencies
-
Centralized Configuration (
src/lib/config.ts)- Type-safe configuration object
- Environment variable validation with
validateNotionConfig() - Single source of truth for all config values
- Updated 8 files to use centralized config
-
Standardized Error Handling (
src/lib/errors.ts)- Custom error classes:
NotionApiError,ValidationError - Consistent
logError()utility for structured error logging - Graceful error handling in cache functions (log and return empty array)
- Better error context with metadata
- Custom error classes:
-
Test Coverage Improvement
- Coverage: 57.69% → 64.48% statements (+6.79%)
- Tests: 71 → 97 (+26 tests)
- Test files: 7 → 9 (+2 files)
Files Created:
__tests__/app/rss.test.ts__tests__/app/sitemap.test.tssrc/lib/config.tssrc/lib/errors.tsdocs/HIGH_PRIORITY_IMPROVEMENTS_COMPLETED.md
Verification:
- ✅ All 97 tests passing
- ✅ TypeScript compilation successful
- ✅ ESLint passing (no warnings)
- ✅ Production build successful
Date: 2025-09-28
Successfully migrated from @notion/client v4.0.2 to v5.1.0 without breaking functionality.
Key Changes Made:
- API Method Change:
notion.databases.query()→notion.dataSources.query() - Parameter Change:
database_id→data_source_id - Import Path Fix:
ImageBlockObjectResponsenow imported from main module - API Version: Using default API version (2025-09-03) - older versions had compatibility issues
- Test Updates: Updated mocks to use
dataSourcesinstead ofdatabases
Important Notes:
- Switched to use notion data source ids instead of Database IDs
Files Modified:
src/lib/notion.ts- Core Notion API interactionsscripts/cache-posts.ts- Post caching script__tests__/lib/notion.test.ts- Unit tests
Verification:
- ✅ All 71 tests pass
- ✅ TypeScript compilation successful
- ✅ Build completes without errors
- ✅ Static generation works correctly
NOTION_TOKEN=secret_xxx # Notion API integration token
NOTION_POSTS_DATABASE_ID=xxx # Posts database/datasource ID
NOTION_PHOTOS_DATABASE_ID=xxx # Photos database/datasource ID
SITE_URL=https://brennanmoore.com # Site URL for absolute linkshomepage-notion-nextjs/
├── src/
│ ├── app/ # Next.js 16 App Router
│ │ ├── (routes)/ # Page routes
│ │ ├── feed.json/ # JSON Feed route
│ │ ├── rss.xml/ # RSS feed route
│ │ └── sitemap.xml/ # Dynamic sitemap
│ ├── components/ # React components
│ ├── lib/ # Utilities (notion, config, errors)
│ └── styles/ # Tailwind CSS
├── __tests__/ # Vitest tests
├── scripts/ # Build scripts (cache-posts.ts)
├── docs/ # Project documentation
├── posts-cache.json # Cached posts from Notion
├── photos-cache.json # Cached photos from Notion
└── public/images/ # Optimized images
Last Updated: 2025-12-28 Document Version: 2.2 (December 2025 visual theme + mobile particle optimization)