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
11 changes: 11 additions & 0 deletions app/(main)/blog/agent-coordination/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { OG_SIZE, ogCard } from '../../../../lib/og-image'
import { agentCoordinationPost as post } from '../posts'

export const dynamic = 'force-static'
export const alt = post.title
export const size = OG_SIZE
export const contentType = 'image/png'

export default function OpengraphImage() {
return ogCard(post.title, 'Bidwell Consulting')
}
59 changes: 27 additions & 32 deletions app/(main)/blog/agent-coordination/page.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,39 @@
import { baseUrl } from 'app/sitemap'
import { siteConfig } from 'lib/site-config'
import { blogPostingSchema } from 'lib/structured-data'
import type { Metadata } from 'next'
import Breadcrumb from '../../../components/breadcrumb'
import JsonLd from '../../../components/structured-data'
import { formatPostDate, agentCoordinationPost as post } from '../posts'

export const metadata = {
title: 'Agent Coordination Structure',
description: 'A framework for managing AI agents within the Bidwell ecosystem.',
publishedAt: new Date().toISOString(),
image: '/og?title=Agent%20Coordination%20Structure',
export const metadata: Metadata = {
title: post.title,
description: post.description,
alternates: { canonical: `/blog/${post.slug}` },
openGraph: {
title: post.title,
description: post.description,
type: 'article',
publishedTime: post.publishedAt,
authors: [siteConfig.founder.name],
url: `/blog/${post.slug}`,
},
}

export default function AgentCoordinationPost() {
return (
<section>
<script
type='application/ld+json'
suppressHydrationWarning
// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires this
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: metadata.title,
datePublished: metadata.publishedAt,
dateModified: metadata.publishedAt,
description: metadata.description,
image: `${baseUrl}${metadata.image}`,
url: `${baseUrl}/blog/agent-coordination`,
author: {
'@type': 'Person',
name: 'Bidwell Consulting',
},
}),
}}
<JsonLd data={blogPostingSchema(post)} />
<Breadcrumb
items={[
{ name: 'Home', href: '/' },
{ name: 'Blog', href: '/blog' },
{ name: post.title, href: `/blog/${post.slug}` },
]}
/>
<h1 className='title font-semibold text-2xl tracking-tighter'>{metadata.title}</h1>
<h1 className='title font-semibold text-2xl tracking-tighter'>{post.title}</h1>
<div className='flex justify-between items-center mt-2 mb-8 text-sm'>
<p className='text-sm text-neutral-600 dark:text-neutral-400'>
{new Date(metadata.publishedAt).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
{formatPostDate(post.publishedAt)}
</p>
</div>
<article className='prose'>
Expand Down
34 changes: 19 additions & 15 deletions app/(main)/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import type { Metadata } from 'next'
import Link from 'next/link'

export const metadata = {
import { formatPostDate, posts } from './posts'

export const metadata: Metadata = {
title: 'Blog',
description: 'Updates and insights from the Bidwell Consulting team.',
alternates: { canonical: '/blog' },
}

export default function BlogPage() {
return (
<section>
<h1 className='font-semibold text-2xl mb-8 tracking-tighter'>Blog</h1>
<div>
<Link href='/blog/agent-coordination' className='flex flex-col space-y-1 mb-4'>
<div className='w-full flex flex-col md:flex-row space-x-0 md:space-x-2'>
<p className='text-neutral-600 dark:text-neutral-400 w-[100px] tabular-nums'>
{new Date().toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
})}
</p>
<p className='text-neutral-900 dark:text-neutral-100 tracking-tight'>
Agent Coordination Structure
</p>
</div>
</Link>
{posts.map(post => (
<Link
key={post.slug}
href={`/blog/${post.slug}`}
className='flex flex-col space-y-1 mb-4'
>
<div className='w-full flex flex-col md:flex-row space-x-0 md:space-x-2'>
<p className='text-neutral-600 dark:text-neutral-400 w-[100px] tabular-nums'>
{formatPostDate(post.publishedAt, 'short')}
</p>
<p className='text-neutral-900 dark:text-neutral-100 tracking-tight'>{post.title}</p>
</div>
</Link>
))}
</div>
</section>
)
Expand Down
30 changes: 30 additions & 0 deletions app/(main)/blog/posts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Blog post registry — the single source of truth for post metadata.
* Pure data with no imports: app/sitemap.ts consumes this and must stay
* executable by tsx (health-check generation) outside the Next bundler.
*/
export interface Post {
slug: string
title: string
description: string
/** ISO date (YYYY-MM-DD) the post was first published. Never derived from build time. */
publishedAt: string
}

export const agentCoordinationPost: Post = {
slug: 'agent-coordination',
title: 'Agent Coordination Structure',
description: 'A framework for managing AI agents within the Bidwell ecosystem.',
publishedAt: '2025-11-27',
}

export const posts: Post[] = [agentCoordinationPost]

export function formatPostDate(publishedAt: string, month: 'short' | 'long' = 'long'): string {
// Anchor to local midnight so the rendered date can't slip a day via UTC parsing.
return new Date(`${publishedAt}T00:00:00`).toLocaleDateString('en-US', {
year: 'numeric',
month,
day: 'numeric',
})
}
10 changes: 10 additions & 0 deletions app/(main)/career-guidance/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { OG_SIZE, ogCard } from '../../../lib/og-image'

export const dynamic = 'force-static'
export const alt = 'Career Guidance'
export const size = OG_SIZE
export const contentType = 'image/png'

export default function OpengraphImage() {
return ogCard('Career Guidance', 'Bidwell Consulting')
}
12 changes: 4 additions & 8 deletions app/(main)/career-guidance/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { faqSchema } from 'lib/structured-data'
import type { Metadata } from 'next'
import type React from 'react'
import JsonLd from '../../components/structured-data'

export const metadata: Metadata = {
title: 'Career Guidance',
Expand All @@ -15,18 +17,11 @@ export const metadata: Metadata = {
'interview preparation',
'leadership development',
],
alternates: { canonical: '/career-guidance' },
openGraph: {
title: 'Career Guidance | Bidwell Consulting',
description: 'We provide career coaching and guidance for tech professionals.',
type: 'website',
images: [
{
url: '/og?title=Career%20Guidance',
width: 1200,
height: 630,
alt: 'Career Guidance',
},
],
},
}

Expand Down Expand Up @@ -100,6 +95,7 @@ const engagementOptions = [
export default function CareerGuidancePage(): React.JSX.Element {
return (
<section>
<JsonLd data={faqSchema(faqs)} />
{/* Hero Section */}
<div className='mb-12'>
<h1 className='mb-4 text-2xl font-semibold tracking-tighter'>Career Guidance</h1>
Expand Down
24 changes: 24 additions & 0 deletions app/(main)/experiments/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Metadata } from 'next'

export interface Experiment {
slug: string
title: string
Expand Down Expand Up @@ -120,3 +122,25 @@ export function getActiveExperiments(): Experiment[] {
export function getExperimentBySlug(slug: string): Experiment | undefined {
return experiments.find(exp => exp.slug === slug)
}

/**
* Metadata for experiment pages driven by the registry: unique title and
* description per page, self-canonical, and noindex until an experiment
* ships (`status: 'active'`). Active experiments with bespoke pages may
* export richer metadata of their own instead.
*/
export function experimentMetadata(slug: string): Metadata {
const experiment = getExperimentBySlug(slug)
if (!experiment) {
throw new Error(`Unknown experiment slug: ${slug}`)
}
const metadata: Metadata = {
title: experiment.title,
description: experiment.description,
alternates: { canonical: `/experiments/${experiment.slug}` },
}
if (experiment.status !== 'active') {
metadata.robots = { index: false, follow: false }
}
return metadata
}
1 change: 1 addition & 0 deletions app/(main)/experiments/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type Experiment, experiments } from './config'
export const metadata: Metadata = {
title: 'Experiments',
description: 'Explore interactive experiments and prototypes built by Bidwell Consulting.',
alternates: { canonical: '/experiments' },
}

function ExperimentCard({ experiment }: { experiment: Experiment }): React.JSX.Element {
Expand Down
16 changes: 16 additions & 0 deletions app/(main)/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { OG_SIZE, ogCard } from '../../lib/og-image'

export const dynamic = 'force-static'
export const alt = 'Bidwell Consulting'
export const size = OG_SIZE
export const contentType = 'image/png'

/**
* Card for the home page (and any (main) route without its own image).
* Required here: the home page defines metadata.openGraph, which replaces
* the inherited root openGraph — including the root segment's file image —
* so the image file must be colocated in this segment.
*/
export default function OpengraphImage() {
return ogCard('Bidwell Consulting', 'Software Engineering & Organizational Consulting')
}
9 changes: 1 addition & 8 deletions app/(main)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,12 @@ export const metadata: Metadata = {
'business optimization',
'full-stack development',
],
alternates: { canonical: '/' },
openGraph: {
title: 'Bidwell Consulting',
description:
'Expert software engineer and organizational consultant, specializing in problem solving, system design, and process optimization. This portfolio site showcases innovative technical solutions and demonstrates our approach to complex problem solving.',
type: 'website',
images: [
{
url: '/og?title=Bidwell%20Consulting',
width: 1200,
height: 630,
alt: 'Bidwell Consulting',
},
],
},
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { OG_SIZE, ogCard } from '../../../../lib/og-image'

export const dynamic = 'force-static'
export const alt = 'Claude Agentic Framework'
export const size = OG_SIZE
export const contentType = 'image/png'

export default function OpengraphImage() {
return ogCard('Claude Agentic Framework', 'The Governed Swarm — Bidwell Consulting')
}
9 changes: 1 addition & 8 deletions app/(wide)/experiments/claude-agentic-framework/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,12 @@ export const metadata: Metadata = {
'ai coding agents',
'quality gates',
],
alternates: { canonical: '/experiments/claude-agentic-framework' },
openGraph: {
title: 'Claude Agentic Framework',
description:
'A drop-in template for Claude Code: coordinated multi-agent swarms, workflow skills, layered rules, and safety hooks.',
type: 'website',
images: [
{
url: '/og?title=Claude%20Agentic%20Framework',
width: 1200,
height: 630,
alt: 'Claude Agentic Framework',
},
],
},
}

Expand Down
4 changes: 4 additions & 0 deletions app/(wide)/experiments/data-music-generator/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { experimentMetadata } from '../../../(main)/experiments/config'

export const metadata = experimentMetadata('data-music-generator')

export default function DataMusicGeneratorPage() {
return (
<div className='container mx-auto px-4 py-8'>
Expand Down
4 changes: 4 additions & 0 deletions app/(wide)/experiments/developer-diaspora/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { experimentMetadata } from '../../../(main)/experiments/config'

export const metadata = experimentMetadata('developer-diaspora')

export default function DeveloperDiasporaPage() {
return (
<div className='container mx-auto px-4 py-8'>
Expand Down
4 changes: 4 additions & 0 deletions app/(wide)/experiments/devops-roi-monitor/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { experimentMetadata } from '../../../(main)/experiments/config'

export const metadata = experimentMetadata('devops-roi-monitor')

export default function DevOpsROIMonitorPage() {
return (
<div className='container mx-auto px-4 py-8'>
Expand Down
4 changes: 4 additions & 0 deletions app/(wide)/experiments/economic-sentiment/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { experimentMetadata } from '../../../(main)/experiments/config'

export const metadata = experimentMetadata('economic-sentiment')

export default function EconomicSentimentPage() {
return (
<div className='container mx-auto px-4 py-8'>
Expand Down
4 changes: 4 additions & 0 deletions app/(wide)/experiments/geological-rhythms/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { experimentMetadata } from '../../../(main)/experiments/config'

export const metadata = experimentMetadata('geological-rhythms')

export default function GeologicalRhythmsPage() {
return (
<div className='container mx-auto px-4 py-8'>
Expand Down
4 changes: 4 additions & 0 deletions app/(wide)/experiments/global-anxiety-map/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { experimentMetadata } from '../../../(main)/experiments/config'

export const metadata = experimentMetadata('global-anxiety-map')

export default function GlobalAnxietyMapPage() {
return (
<div className='container mx-auto px-4 py-8'>
Expand Down
4 changes: 4 additions & 0 deletions app/(wide)/experiments/harmonic-deck/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { experimentMetadata } from '../../../(main)/experiments/config'

export const metadata = experimentMetadata('harmonic-deck')

export default function HarmonicDeckPage() {
return (
<div className='container mx-auto px-4 py-8'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { experimentMetadata } from '../../../(main)/experiments/config'

export const metadata = experimentMetadata('infrastructure-weather-topology')

export default function InfrastructureWeatherTopologyPage() {
return (
<div className='container mx-auto px-4 py-8'>
Expand Down
4 changes: 4 additions & 0 deletions app/(wide)/experiments/live-order-book/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { experimentMetadata } from '../../../(main)/experiments/config'

export const metadata = experimentMetadata('live-order-book')

export default function LiveOrderBookPage() {
return (
<div className='container mx-auto px-4 py-8'>
Expand Down
Loading