Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e8a9fc8
restructure: "metadata" -> "seo"; some "app" -> "ui"; some "utils" ->…
ooloth Dec 26, 2025
da94047
restructure: "app" -> "ui" for almost every presentational component
ooloth Dec 26, 2025
67a2344
restructure: move jsonLd generation from app/ to seo/
ooloth Dec 26, 2025
70dd1f2
home: rephrase summary
ooloth Dec 26, 2025
880deda
seo: generate json-ld for home and blog index pages too
ooloth Dec 26, 2025
e7e204f
home: rephrase summary to signal teamwork
ooloth Dec 26, 2025
1822373
html: move main element into shared page layout component
ooloth Dec 26, 2025
dded999
seo: create json-ld script component to capture stringification and e…
ooloth Dec 26, 2025
9943359
seo: let json-ld component handle generating the data it needs
ooloth Dec 26, 2025
486d4f4
likes: make data fetching steps more explicit
ooloth Dec 26, 2025
0740b71
itunes: simplify args required to fetch items
ooloth Dec 26, 2025
eb2903d
likes: cache tmdb and itunes api responses in local dev
ooloth Dec 26, 2025
0e92855
restructure: io/logging -> utils/logging
ooloth Dec 26, 2025
a96534b
seo: extract SITE_LOCALE constant
ooloth Dec 26, 2025
e85008d
restructure: organize ui/ by elements, layout and page sections
ooloth Dec 27, 2025
149aa36
post: inline wrapping logic on app/ page
ooloth Dec 27, 2025
18e3d0a
restructure: consolidate ui/ files
ooloth Dec 27, 2025
37bdd70
io: move retry module to io/utils
ooloth Dec 27, 2025
b0c9b5d
cloudflare: move redirects file tests to io/cloudflare
ooloth Dec 27, 2025
4fe9eb9
home: nest routes under (home) folder
ooloth Dec 27, 2025
e150872
next: fix typo
ooloth Dec 27, 2025
2963f06
git: tidy up gitignore
ooloth Dec 27, 2025
254f6dc
ci: inline constants
ooloth Dec 27, 2025
af627cc
notion: remove unused getPage helper
ooloth Dec 27, 2025
e78fdbc
tmdb: remove unused constants
ooloth Dec 27, 2025
3c8bdf3
post: redirect to not-found page if no matching post found while tryi…
ooloth Dec 27, 2025
6ec219b
tests: better comments
ooloth Dec 27, 2025
560a7b0
seo: extract all page metadata to seo/pages
ooloth Dec 27, 2025
f98fc70
seo: dry up descriptions
ooloth Dec 27, 2025
19404f5
seo: extract TWITTER_CARD constant
ooloth Dec 27, 2025
468d94e
seo: delete low-value tests for constants
ooloth Dec 27, 2025
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
44 changes: 44 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# michaeluloth.com - Claude Code Rules

## TypeScript Type Safety

### Never use `any`

**Rule:** Never use `any`, `as any`, or `@typescript-eslint/no-explicit-any` to bypass type errors.

**Why:** Type errors indicate a mismatch between what TypeScript expects and what you're providing. The solution is always to communicate the real type properly, not to suppress the error.

**How to fix type errors properly:**

1. **Understand what TypeScript expects** - Read the error message carefully to understand the expected type
2. **Provide the correct type** - Use proper type annotations, interfaces, or type assertions with the actual type
3. **Use the right syntax** - If a library expects JSX/ReactNode, use JSX syntax (`.tsx` files) instead of plain objects
4. **Import necessary types** - Ensure you have the correct imports (e.g., `React` for JSX)

**Example: Satori type error**

```typescript
// ❌ WRONG - bypassing the type error
const svg = await satori(
{
type: 'div',
props: { /* ... */ }
} as any, // Never do this!
options
)

// ✅ CORRECT - using proper JSX syntax that TypeScript understands
// 1. Rename file from .ts to .tsx
// 2. Import React
import React from 'react'

// 3. Use JSX syntax instead of plain objects
const svg = await satori(
<div>
{/* ... */}
</div>,
options
)
```

This rule ensures the codebase maintains full type safety and prevents runtime errors that `any` would hide.
7 changes: 3 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
# Theirs
node_modules/
.netlify/
.next/
.playwright-mcp/
.vercel/
build/
node_modules/
out/
.DS_Store
*.log
*.tsbuildinfo
next-env.d.ts
.lighthouseci/
ci/lighthouse/urls.json

# Mine
**/cloudinary/*.json
.env
.env.local
.lighthouseci/
.local-cache/
ci/lighthouse/urls.json
15 changes: 12 additions & 3 deletions app/(prose)/page.test.tsx → app/(home)/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import { Ok } from '@/utils/errors/result'

// Mock dependencies
vi.mock('@/io/notion/getPosts')
vi.mock('@/ui/image', () => ({
vi.mock('@/ui/elements/image', () => ({
// eslint-disable-next-line @next/next/no-img-element -- Using img in test mock is acceptable
default: ({ url }: { url: string }) => <img src={url} alt="Michael Uloth" />,
}))

// Mock PostList to avoid async server component complexity in tests
// The actual PostList behavior is tested in ui/post-list.test.tsx
vi.mock('@/ui/post-list', () => ({
// The actual PostList behavior is tested in ui/sections/blog-post-list.test.tsx
vi.mock('@/ui/sections/blog-post-list', () => ({
default: ({ limit }: { limit?: number }) => {
// Call getPosts to verify it's called with correct params
// This ensures the mock is called during render which we verify in tests
Expand All @@ -29,6 +29,15 @@ vi.mock('@/ui/post-list', () => ({
},
}))

// Mock PageLayout to avoid Next.js usePathname() in Header component
vi.mock('@/ui/layout/page-layout', () => ({
default: ({ children }: { children: React.ReactNode }) => (
<main id="main" className="flex-auto flex flex-col">
{children}
</main>
),
}))

describe('Home page', () => {
beforeEach(() => {
vi.clearAllMocks()
Expand Down
15 changes: 15 additions & 0 deletions app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type ReactElement } from 'react'
import PageLayout from '@/ui/layout/page-layout'
import Summary from '@/ui/sections/home-summary'
import RecentWriting from '@/ui/sections/home-recent-writing'
import JsonLdScript from '@/seo/json-ld/script'

export default async function Home(): Promise<ReactElement> {
return (
<PageLayout>
<Summary />
<RecentWriting />
<JsonLdScript type="person" />
</PageLayout>
)
}
143 changes: 0 additions & 143 deletions app/(prose)/[slug]/page.tsx

This file was deleted.

28 changes: 0 additions & 28 deletions app/(prose)/[slug]/ui/post.tsx

This file was deleted.

33 changes: 0 additions & 33 deletions app/(prose)/blog/page.tsx

This file was deleted.

54 changes: 0 additions & 54 deletions app/(prose)/layout.test.tsx

This file was deleted.

Loading
Loading