Skip to content
Draft
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
13 changes: 11 additions & 2 deletions features/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
{"@type": "ListItem", "position": 3, "name": "Obsidian wiki for token-efficient context"},
{"@type": "ListItem", "position": 4, "name": "Durable state on disk"},
{"@type": "ListItem", "position": 5, "name": "Automated CI maintenance"},
{"@type": "ListItem", "position": 6, "name": "Modern React stack"}
{"@type": "ListItem", "position": 6, "name": "Modern React stack"},
{"@type": "ListItem", "position": 7, "name": "React render-performance diagnostics"}
]
},
{
Expand Down Expand Up @@ -113,12 +114,20 @@
"text": "Run /gaia-fitness. It audits your entire Claude integration, including hooks, rules, skills, CLAUDE.md, settings, and GAIA install fitness, then grades each area A+ to F and heals what it can on a branch. You review the diff and commit when satisfied. The check protocol survives /update-gaia."
}
},
{
"@type": "Question",
"name": "How does GAIA diagnose slow React renders?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Run /gaia-react-perf. It drives a micro-interaction you point it at, captures real per-render attribution with a bippy harness, and reduces the raw capture to a small ranked summary so it never enters Claude's context. It flags a React.memo that re-renders anyway because a prop or context value is a new object or callback every render, plus any component whose render crosses a 16 ms frame budget. It ranks findings by how costly each wasted render is and how widely it ripples, then names the structural fix. v1 is measure-only: GAIA diagnoses, Claude applies the fix you approve."
}
},
{
"@type": "Question",
"name": "How does GAIA automate project maintenance?",
"acceptedAnswer": {
"@type": "Answer",
"text": "GAIA CI handles dependency updates (patch and minor auto-merge with codemods, major routes to a review PR), daily security audits with pnpm audit, wiki sync whenever app code changes, and monthly stale branch cleanup. Claude handles the safe cases automatically. Humans only see what it cannot safely recover from, with a hard $5 cost cap per run."
"text": "GAIA CI handles dependency updates (patch and minor auto-merge with codemods, major routes to a review PR), daily security audits with pnpm audit, wiki sync whenever app code changes, and monthly stale branch cleanup. Claude handles the safe cases automatically. Humans only see what it cannot safely recover from. GAIA CI runs against your Claude Code Pro/Max subscription or your Anthropic API key, and a fixed turn limit caps every scheduled run, so no run loops without end."
}
},
{
Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"Obsidian wiki for token-efficient context management",
"Automated GAIA CI with code-review audit",
"Token economics controls and trust scaffolding",
"React render-performance diagnostics via /gaia-react-perf",
"One-command React 19 project scaffolding via create-gaia"
],
"offers": {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/consulting/sections/Consulting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ const Consulting = () => {
{SKUS.map((sku) => (
<section
key={sku.anchor}
className="border-line-soft scroll-mt-20 border-t py-20 sm:py-28"
className="border-line-soft scroll-mt-16 border-t py-20 sm:py-28"
id={sku.anchor}
>
<div className="mx-auto max-w-5xl">
Expand Down
2 changes: 2 additions & 0 deletions src/pages/features/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Hero from './sections/Hero';
import LoadOnDemand from './sections/LoadOnDemand';
import ObsidianWikiDetail from './sections/ObsidianWikiDetail';
import Principles from './sections/Principles';
import ReactPerf from './sections/ReactPerf';
import Stack from './sections/Stack';
import StateOnDisk from './sections/StateOnDisk';
import Trust from './sections/Trust';
Expand All @@ -23,6 +24,7 @@ const App = () => {
<Layout>
<Hero />
<Trust />
<ReactPerf />
<LoadOnDemand />
<StateOnDisk />
<AgenticDesignDetail />
Expand Down
2 changes: 1 addition & 1 deletion src/pages/features/sections/AgenticDesignDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const pad = (n: number) => String(n + 1).padStart(2, '0');

const AgenticDesignDetail = () => (
<section
className="border-line-soft scroll-mt-20 border-b py-20"
className="border-line-soft scroll-mt-16 border-b py-20"
id="agentic-design"
>
<div className="mx-auto max-w-6xl px-[clamp(1rem,4vw,2rem)]">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/features/sections/Fitness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const gradeTone = (grade: string) => {

const Fitness = () => (
<section
className="border-line-soft bg-tint scroll-mt-20 border-b py-20"
className="border-line-soft bg-tint scroll-mt-16 border-b py-20"
id="fitness"
>
<div className="mx-auto max-w-6xl px-[clamp(1rem,4vw,2rem)]">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/features/sections/Forensics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ForensicsIssue from './figures/ForensicsIssue';

const Forensics = () => (
<section
className="border-line-soft scroll-mt-20 border-b py-20"
className="border-line-soft scroll-mt-16 border-b py-20"
id="forensics"
>
<div className="mx-auto max-w-6xl px-[clamp(1rem,4vw,2rem)]">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/features/sections/FxSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const FxSection = ({
title,
}: Properties) => (
<section
className={`border-line-soft scroll-mt-20 border-b py-20 ${isCool ? 'bg-tint' : ''}`}
className={`border-line-soft scroll-mt-16 border-b py-20 ${isCool ? 'bg-tint' : ''}`}
id={id}
>
<div className="mx-auto max-w-6xl px-[clamp(1rem,4vw,2rem)]">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/features/sections/GaiaCi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const TONE: Record<LedgerRow['action'], {dot: string; text: string}> = {

const GaiaCi = () => (
<section
className="border-line-soft bg-tint scroll-mt-20 border-b py-20"
className="border-line-soft bg-tint scroll-mt-16 border-b py-20"
id="gaia-ci"
>
<div className="mx-auto max-w-6xl px-[clamp(1rem,4vw,2rem)]">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/features/sections/LoadOnDemand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const codeStyles =

const LoadOnDemand = () => (
<section
className="border-line-soft scroll-mt-20 border-b py-20"
className="border-line-soft scroll-mt-16 border-b py-20"
id="context"
>
<div className="mx-auto max-w-6xl px-[clamp(1rem,4vw,2rem)]">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/features/sections/Principles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const PRINCIPLES = [

const Principles = () => (
<section
className="border-line-soft relative scroll-mt-20 overflow-hidden border-b py-24 sm:py-32"
className="border-line-soft relative scroll-mt-16 overflow-hidden border-b py-24 sm:py-32"
id="discipline"
>
<div
Expand Down
257 changes: 257 additions & 0 deletions src/pages/features/sections/ReactPerf.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
type Finding = {
cause: null | string;
component: string;
exceedsBudget: boolean;
inputs: string[];
isMemo: boolean;
maxTotalMs: number;
memoDefeated: number;
renderCount: number;
source: 'context' | 'props' | null;
};

const FRAME_BUDGET = '16 ms';

const TOTALS = [
{label: 'renders', value: '213'},
{label: 'framework-filtered', value: '46'},
{label: 'app components', value: '9'},
{label: 'memo-defeated', value: '17'},
];

const FINDINGS: Finding[] = [
{
cause: 'jsx-no-new-object-as-prop',
component: 'OrdersTable',
exceedsBudget: true,
inputs: ['columns', 'onRowSelect'],
isMemo: true,
maxTotalMs: 22.4,
memoDefeated: 11,
renderCount: 11,
source: 'props',
},
{
cause: 'jsx-no-constructed-context-values',
component: 'Sidebar',
exceedsBudget: false,
inputs: ['DashboardContext'],
isMemo: true,
maxTotalMs: 4.2,
memoDefeated: 6,
renderCount: 6,
source: 'context',
},
{
cause: null,
component: 'RevenueChart',
exceedsBudget: true,
inputs: [],
isMemo: false,
maxTotalMs: 19.8,
memoDefeated: 0,
renderCount: 8,
source: null,
},
];

const META = [
{
desc: 'It emits a ranked diagnosis and stops. Claude applies the fix you approve in conversation, never an automatic rewrite, so the call on what changes stays yours.',
name: 'Measure-only by design',
},
{
desc: (
<>
{'A '}
<a
className="text-accent hover:text-accent-soft transition-colors duration-150"
href="https://github.com/aidenybai/bippy"
rel="noopener noreferrer"
target="_blank"
>
bippy
</a>
{
' harness records every render to disk, then a deterministic CLI reduces tens of thousands of tokens of raw capture to the small ranked summary the model reads. The raw dump never enters context.'
}
</>
),
name: 'The raw capture stays on disk',
},
{
desc: 'It flags a memoized component that re-renders anyway when its props or context arrive as a new object or callback every render, and any component whose render crosses the frame budget, memo or not. A cheap re-render with real changes is left alone, so nothing pushes you toward memoizing everything.',
name: 'Two signals, not noise',
},
{
desc: 'Findings lead with the memoized components that re-render anyway, then rank by wasted cost: how often each one re-renders times how long a render takes. A barely perceptible render-count win never outranks a memo that keeps re-rendering an expensive subtree on every parent update.',
name: 'Ranked by what users feel',
},
];

const ReactPerf = () => (
<section
className="border-line-soft bg-tint scroll-mt-16 border-b py-20"
id="react-perf"
>
<div className="mx-auto max-w-6xl px-[clamp(1rem,4vw,2rem)]">
<div className="mb-12 grid gap-8 md:grid-cols-[minmax(0,0.85fr)_minmax(0,1.15fr)] md:gap-16">
<div>
<h2 className="group font-display text-ink max-w-[18ch] text-[clamp(2rem,4vw,2.85rem)] leading-[1.1] font-normal tracking-[-0.02em]">
<a className="text-inherit no-underline" href="#react-perf">
App performance, optimized
<span
aria-hidden={true}
className="ml-[0.4em] text-[0.6em] opacity-0 transition-opacity duration-150 select-none group-hover:opacity-40"
>
#
</span>
</a>
</h2>
</div>
<div className="text-ink-dim space-y-4 text-[1.05rem] leading-[1.65] text-pretty">
<p>
A React app slows down one wasted render at a time. From the outside
the app just feels slow. From the inside, you are guessing which
component, and why. The profiler shows you every render and ranks
none of them.
</p>
<p>
<code className="bg-surface text-ink rounded-sm px-1.5 text-[0.88em]">
/gaia-react-perf
</code>
{
' drives the interaction you point it at and captures real per-render attribution. The capture runs on the same React instrumentation that powers '
}
<a
className="text-accent hover:text-accent-soft transition-colors duration-150"
href="https://github.com/aidenybai/react-scan"
rel="noopener noreferrer"
target="_blank"
>
react-scan
</a>
, wired into Claude, not a browser overlay. It surfaces two failure
modes: a memoized component that re-renders anyway because a prop or
context value it depends on is a new object or callback every
render, and any component whose render crosses a 16 ms frame budget,
memo or not. It skips the cheap re-renders that push you toward
memoizing everything, then names the structural fix for what is
left.
</p>
</div>
</div>

{/* Reduced summary · centerpiece */}
<div className="bg-surface border-line-soft overflow-hidden rounded-lg border font-mono">
<div className="border-line-soft grid grid-cols-[1fr_auto] items-baseline gap-x-4 border-b bg-black/15 px-5 py-[0.7rem]">
<span className="text-ink-dim text-[0.8rem]">/gaia-react-perf</span>
<span className="flex items-baseline gap-x-3">
<span className="text-muted text-[0.6rem] tracking-[0.18em] uppercase">
Over budget
</span>
<span className="text-warn-soft text-[0.95rem]">2</span>
</span>
</div>

<div className="border-line-soft text-muted flex flex-wrap gap-x-4 gap-y-1 border-b px-5 py-[0.7rem] text-[0.72rem]">
{TOTALS.map((total) => (
<span key={total.label}>
<span className="text-ink-dim">{total.value}</span>
{` ${total.label}`}
</span>
))}
</div>

<div className="border-line-soft space-y-4 border-b px-5 py-[0.9rem]">
<p className="text-muted text-[0.6rem] tracking-[0.18em] uppercase">
Ranked findings
</p>
{FINDINGS.map((finding, index) => (
<div
key={finding.component}
className="grid grid-cols-[1.25rem_1fr] gap-x-3"
>
<span className="text-muted text-[0.8rem]">{index + 1}</span>
<div className="space-y-1.5">
<div className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
<span className="text-[0.85rem]">
<span className="text-ink">{finding.component}</span>
{finding.isMemo ?
<span className="text-secondary-soft ml-2 text-[0.62rem] tracking-[0.14em] uppercase">
memo
</span>
: null}
</span>
<span
className={`text-[0.8rem] ${finding.exceedsBudget ? 'text-warn-soft' : 'text-ink-dim'}`}
>
{`${finding.maxTotalMs} ms max`}
{finding.exceedsBudget ?
<span className="text-warn-soft">{` · over ${FRAME_BUDGET}`}</span>
: null}
</span>
</div>
<p className="text-muted text-[0.72rem]">
{finding.memoDefeated > 0 ?
`${finding.renderCount} renders · ${finding.memoDefeated} memo-defeated`
: `${finding.renderCount} renders`}
</p>
{finding.inputs.length > 0 ?
<p className="text-[0.78rem]">
<span className="text-muted">
{finding.source === 'context' ?
'unstable context: '
: 'unstable props: '}
</span>
<span className="text-accent-soft">
{finding.inputs.join(', ')}
</span>
</p>
: null}
{finding.cause ?
<p className="text-muted text-[0.72rem]">
<span>{'react-doctor · '}</span>
<span className="text-secondary-soft">{finding.cause}</span>
</p>
: null}
</div>
</div>
))}
</div>

<div className="space-y-2 bg-black/15 px-5 py-[0.9rem]">
<p className="text-muted text-[0.6rem] tracking-[0.18em] uppercase">
Diagnosis
</p>
<p className="text-ink-dim font-sans text-[0.85rem] leading-[1.6]">
Two findings cross the 16 ms frame budget, and the fixes differ.
Give OrdersTable stable props so its memo holds, then cut the
per-render work in RevenueChart, which has no memo at all.
</p>
</div>
</div>

<p className="text-muted mt-3 font-mono text-[0.72rem] leading-normal">
Measure-only: GAIA diagnoses, Claude applies the fix you approve, then
re-runs /gaia-react-perf to confirm the finding drops to zero.
</p>

{/* Meta strip */}
<dl className="mt-10 grid gap-x-12 gap-y-6 md:grid-cols-2">
{META.map(({desc, name}) => (
<div key={name}>
<dt className="text-ink mb-1.5 text-[0.98rem] font-medium tracking-[-0.005em]">
{name}
</dt>
<dd className="text-ink-dim text-[0.92rem] leading-[1.6]">
{desc}
</dd>
</div>
))}
</dl>
</div>
</section>
);

export default ReactPerf;
2 changes: 1 addition & 1 deletion src/pages/features/sections/StateOnDisk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import TokenArtifacts from './figures/TokenArtifacts';

const StateOnDisk = () => (
<section
className="border-line-soft bg-tint scroll-mt-20 border-b py-20"
className="border-line-soft bg-tint scroll-mt-16 border-b py-20"
id="tokens"
>
<div className="mx-auto max-w-6xl px-[clamp(1rem,4vw,2rem)]">
Expand Down
Loading