// Project Plan page — modules, sprints, principles, roadmap
const Plan = ({ setPage }) => {
const [tab, setTab] = React.useState('overview');
return (
{/* Sticky tab bar */}
{[
['overview','Overview'],
['modules','Modules'],
['sprints','Sprints'],
['principles','Principles'],
['stack','Stack'],
].map(([id,l]) => (
setTab(id)} style={{
padding:'8px 14px', borderRadius:8, fontSize:13,
color: tab===id ? '#0a0a0a' : 'var(--ink-2)',
background: tab===id ? 'var(--accent)' : 'transparent',
fontWeight: tab===id ? 500 : 400,
transition:'all .15s ease'
}}>{l}
))}
setPage('home')} className="mono" style={{fontSize:11,color:'var(--ink-3)',letterSpacing:'0.06em',padding:'8px 12px'}}>
← BACK TO SITE
{tab === 'overview' &&
}
{tab === 'modules' &&
}
{tab === 'sprints' &&
}
{tab === 'principles' &&
}
{tab === 'stack' &&
}
);
};
const PlanHero = () => (
Internal · v0.4
LAST UPDATED MAY 13, 2026
SolidAtoms Build Plan
The full module breakdown, sprint roadmap, and engineering principles
for the SolidAtoms website. Living document. Updates after every retro.
{[
['Modules done', '13 of 14', '93% complete'],
['Sprints shipped', '13 of 14', '336 of 362 pts'],
['Active sprint', 'SP12', 'W44–W45 · Analytics'],
['Target launch', 'W45', 'Nov 2026'],
].map(([l,v,sub],i) => (
0 ? '1px solid var(--line)' : 'none', background:'var(--bg-2)'}}>
{l.toUpperCase()}
{v}
{sub.toUpperCase()}
))}
);
// OVERVIEW
const PlanOverview = () => (
How we'll actually build this.
The SolidAtoms site is split into fourteen modules , sequenced across fourteen two-week sprints . Each module is scoped, documented, and reviewed before any code is written.
We don't ship monoliths. Every feature is its own folder — components, data, and tests — with hard limits: no file over 300 lines, no component doing more than one job, and no copy-paste duplication of logic.
Progress is tracked sprint-by-sprint with velocity, burndown, and a retro after every closing Friday. New ideas don't derail the current sprint — they go into the backlog and get prioritised at the next planning session.
● THIS WEEK · SPRINT 12 · ANALYTICS STUDIO
Analytics — event pipeline, dashboards, cohorts, alerts
IN PROGRESS
{['Cohort retention heatmap', 'Funnel drop-off labels', 'Alert routing to Slack', 'Scheduled PDF reports'].map(i => (
{Icon.dot()} {i}
))}
DONE THIS SPRINT
{['Event pipeline scaffolding', 'Realtime visitor stream', 'Overview dashboard + sparklines', 'Events catalog + tracking SDK'].map(i => (
{Icon.check()} {i}
))}
{/* Side panel */}
);
// MODULES
const PlanModules = () => {
const StatusBadge = ({ s }) => {
const map = {
'in-progress': ['var(--accent)', '#0a0a0a', 'In progress'],
'planned': ['var(--line-2)', 'var(--ink-2)', 'Planned'],
'future': ['rgba(255,255,255,0.06)', 'var(--ink-3)', 'Future'],
'done': ['var(--ok)', '#0a0a0a', 'Done'],
};
const [bg, c, label] = map[s] || map.planned;
return
{label} ;
};
return (
Modules
14 modules · 13 shipped, 1 active
{MODULES.map(m => (
{m.id}
{m.name}
{m.scope.map(s => {s} )}
{m.priority} priority
{Icon.arrow()}
))}
);
};
// SPRINTS
const PlanSprints = () => {
return (
Sprints
2-week cadence · 362 total points · W18 → W45
{/* Gantt-ish timeline */}
{Array.from({length:28}, (_,i)=>(
W{18+i}
))}
{SPRINTS.map(sp => {
const startW = parseInt(sp.weeks.split('–')[0].slice(1));
const endW = parseInt(sp.weeks.split('–')[1].slice(1));
const startCol = startW - 18 + 2;
const span = endW - startW + 1;
const colorMap = {done:'var(--ink-3)', active:'var(--accent)', next:'#7c9bff', planned:'var(--line-2)'};
return (
);
})}
{SPRINTS.map(sp => (
{sp.id} · {sp.weeks}
{sp.status}
{sp.name}
{sp.items.map(it => (
{it}
))}
{sp.points} STORY POINTS
{sp.items.length} STORIES
))}
);
};
// PRINCIPLES
const PlanPrinciples = () => (
Engineering principles
5 rules we don't bend
{PRINCIPLES.map(p => (
))}
FOLDER CONVENTION
{`src/
modules/
marketing/ ← M02 marketing site
components/ ← max 300 lines each
data/
hooks/
index.tsx
portfolio/ ← M03
team/ ← M04
blog/ ← M05
tools/ ← M06
cms/ ← M07
payments/ ← M08
portal/ ← M09
shared/ ← cross-module primitives
components/
tokens/
utils/
app/ ← routing only`}
);
// STACK
const PlanStack = () => {
const stack = [
{layer:'Frontend', items:[['Next.js 15','Framework'],['React 19','UI'],['TypeScript','Type safety'],['Tailwind v4','Styling'],['Framer Motion','Animation']]},
{layer:'Backend', items:[['Next.js Route Handlers','API'],['tRPC','Type-safe RPC'],['Drizzle ORM','Database'],['Postgres (Neon)','DB hosting'],['Upstash Redis','Cache + queue']]},
{layer:'CMS', items:[['Payload CMS 3','Headless CMS'],['MDX','Long-form posts'],['UploadThing','Media'],['Resend','Email']]},
{layer:'AI', items:[['Vercel AI SDK','LLM client'],['OpenAI / Anthropic','Models'],['Pinecone','Vector DB']]},
{layer:'Payments', items:[['Stripe','Payments + Connect'],['Stripe Tax','Tax handling'],['Webhooks via Inngest','Reliable events']]},
{layer:'Infra', items:[['Vercel','Hosting'],['Sentry','Errors'],['PostHog','Product analytics'],['GitHub Actions','CI/CD'],['Linear','Project management']]},
];
return (
The stack
Boring where it matters, sharp where it counts
{stack.map(s => (
● {s.layer.toUpperCase()}
{s.items.map(([n,r],i) => (
0 ? '1px solid var(--line)' : 'none'}}>
{n}
{r}
))}
))}
);
};
window.Plan = Plan;