COORD: 44.21.90
OFFSET: +12.5°
SYS.READY
BUFFER: 99%
FOCAL_PT
BACK TO DEVLOG
BRUBKR

Design System Chorus: Content Blocks, Layouts, and Charts

Expanded design system from ~120 to 224 specimens, adding content blocks, full-page layouts, and Recharts data visualizations.

2026-01-23 // RAW LEARNING CAPTURE
PROJECTBRUBKR

Design System Chorus: Content Blocks, Layouts, and Charts

Date: 2026-01-23 Context: Continuing from the previous session that ported the design system canvas into a Next.js 16 app. Today's session expanded the library from ~120 specimens (primitives: packets, grids, backdrops, marks, icons, dividers, frames, status, hovers, textures, loading, connectors, headlines, gauges, ambient) to 224 total lab components, adding 61 content block specimens, 30 full-page layout compositions, and 25 Recharts data visualizations.

What We Did

Phase 1: Content Block Specimens (61 components)

Wired 61 new content block specimens into the design system canvas across 13 categories:

  • Cards (6): card-project, card-post, card-stat, card-tool, card-link, card-empty
  • Panels (5): panel-system, panel-activity, panel-specs, panel-comparison, panel-terminal
  • Entries (5): entry-timeline, entry-log, entry-changelog, entry-activity, entry-directory
  • Banners (5): banner-status, banner-announcement, banner-metrics, banner-cta, banner-alert
  • Quotes (5): quote-pull, quote-block, quote-code, quote-testimonial, quote-highlight
  • Navigation (5): nav-breadcrumb, nav-pagination, nav-tabs, nav-section-index, nav-prev-next
  • Steps (5): steps-vertical, steps-horizontal, steps-branch, steps-progress, steps-annotated
  • Tables (5): table-specs, table-comparison, table-data, table-mini, table-matrix
  • Previews (5): preview-article, preview-project, preview-link, preview-embed, preview-repo
  • Footers (5): footer-columns, footer-mini, footer-colophon, footer-newsletter, footer-social
  • Sections (5): section-feature, section-logos, section-faq, section-pricing, section-about
  • Separators (5): separator-numbered, separator-pause, separator-ornament, separator-stat, separator-context

Build fixes during this phase:

  1. steps-progress.tsx — Biome lint error: unused variable. Removed dead code.
  2. banner-cta.tsx — Export name mismatch. The file exported BannerCta but the page imported BannerCTA. Fixed the component to export BannerCTA.
  3. backdrop-wind.tsx and backdrop.tsx — Pre-existing TypeScript error: optional chaining on left side of assignment (canvas?.style.width = ...). This is invalid TS — you can't assign to an optional chain. Fixed by wrapping in if (canvas) guard:
// Before (invalid):
canvas?.style.width = `${width}px`;

// After:
if (canvas) {
  canvas.style.width = `${width}px`;
}

Phase 2: Layout Compositions (30 components)

Brainstormed 6 categories of full-page layout compositions themed "Design Engineer Showcase":

CategoryComponentsExamples
Heroes (5)layout-hero-split, layout-hero-statement, layout-hero-terminal, layout-hero-metrics, layout-hero-minimalName/title with generative SVG art, terminal-style with typing effect
Case Studies (5)layout-case-header, layout-case-breakdown, layout-case-process, layout-case-results, layout-case-galleryBefore/after metrics, timeline process diagrams
Work Grids (5)layout-grid-bento, layout-grid-masonry, layout-grid-featured, layout-grid-timeline, layout-grid-categorizedAsymmetric bento boxes, filtered category views
About (5)layout-about-split, layout-about-skills, layout-about-philosophy, layout-about-experience, layout-about-nowRadar chart skills, chronological experience
Lab/Experiments (5)layout-lab-interactive, layout-lab-code, layout-lab-comparison, layout-lab-diagram, layout-lab-metricsLive code editors, architecture diagrams
Contact (5)layout-contact-minimal, layout-contact-form, layout-contact-social, layout-contact-collab, layout-contact-statusTerminal-style forms, availability calendars

Orchestration pattern: Launched 6 parallel Opus agents, each responsible for one category (5 components). All 30 components built concurrently. Each agent received the design language spec (dark-first, monospace, white/10 borders, corner marks, emerald/amber accents) and built self-contained components.

New ReactFlow node type — LayoutNode:

function LayoutNode({ data }: { data: LayoutData }) {
  return (
    <div className="relative border border-white/10 rounded-lg bg-[#0a0a0a] overflow-hidden">
      <div className="relative">
        {data.children}  {/* Renders at natural size, no fixed width/height */}
      </div>
      <div className="absolute top-2 right-3">
        <div className="text-[10px] font-mono text-white/40">{data.title}</div>
      </div>
    </div>
  );
}

Unlike SpecimenNode (fixed 300x300) or PreviewNode (fixed 600x380), LayoutNode renders at natural content size. Layout specimens set their own dimensions (typically width: 800, height: 460-600). Placed on the canvas in a 2-column grid with 860px horizontal and 480-660px vertical spacing depending on category.

Scoped CSS animations with React 19 <style> hoisting:

<style href="layout-hero-split-scroll" precedence="low">
  {`@keyframes layout-hero-split-scroll {
    0%, 100% { transform: translateY(0); opacity: 0.4; }
    50% { transform: translateY(4px); opacity: 0.7; }
  }`}
</style>

The href acts as a deduplication key — React 19 hoists these to <head> and ensures only one instance per href value exists in the document. precedence="low" places them before utility CSS. This avoids global CSS pollution while keeping animations co-located with their components.

Phase 3: Recharts Visualizations (25 components)

Built 25 chart specimens across 5 categories:

  • Time Series (5): chart-line-deploys, chart-line-performance, chart-area-commits, chart-line-uptime, chart-area-response
  • Distributions (5): chart-bar-languages, chart-bar-bundle, chart-bar-coverage, chart-bar-activity, chart-bar-stacked
  • Comparisons (5): chart-comparison-frameworks, chart-comparison-versions, chart-scatter-correlation, chart-comparison-before-after, chart-composed-overview
  • Radial (5): chart-radar-skills, chart-pie-stack, chart-radialbar-progress, chart-radar-comparison, chart-pie-minimal
  • Widgets (5): chart-sparkline-grid, chart-kpi-trend, chart-heatmap-grid, chart-gauge-ring, chart-waterfall

Recharts dark theme pattern:

const tickStyle = {
  fontSize: 10,
  fontFamily: 'monospace',
  fill: 'rgba(255,255,255,0.4)',
};

<CartesianGrid stroke="rgba(255,255,255,0.06)" strokeDasharray="none" />
<XAxis
  dataKey="month"
  tick={tickStyle}
  axisLine={{ stroke: 'rgba(255,255,255,0.15)', strokeWidth: 1 }}
  tickLine={false}
/>

Key elements: 6% opacity grid (barely visible, not distracting), 15% opacity axis lines, 40% opacity tick labels, all monospace. Accent colors: emerald (#10b981), amber (#f59e0b), white/40 for tertiary data.

TypeScript issues with Recharts:

  1. chart-line-performance.tsx — The Legend component does not accept a payload prop in its type definitions (even though the docs show custom legends receiving it). Removed the prop and let Recharts auto-derive legend items from the chart children.

  2. chart-bar-activity.tsx — Tooltip formatter type mismatch. The formatter callback receives value typed as ValueType (union of string | number | readonly (string | number)[]). Had to type the parameter as unknown to avoid strict mode errors:

// Before (type error):
formatter={(value) => [`${value} commits`, 'Activity']}

// After:
formatter={(value: unknown) => [`${value} commits`, 'Activity']}
  1. Various Cell deprecation warnings from recharts (component vs children pattern) — not blocking errors, left as-is.

Phase 4: Nav Restructuring

Between content phases, the navigation was restructured from a flat section list to categorized groups. Each category has:

interface Category {
  label: string;        // "Canvas", "Decorators", "Feedback", etc.
  description: string;  // Short description of the category
  tag: string;          // Hex tag like "0x01"
  feeds?: string[];     // Dependencies (used for edge generation)
  sections: { id: SectionId; label: string }[];
}

Added an IndexView that uses @dagrejs/dagre for automatic graph layout. Categories become nodes with feeds relationships becoming directed edges. The dagre algorithm computes positions with rankdir: 'TB', nodesep: 80, ranksep: 120. This produces a dependency DAG showing how Canvas feeds Decorators+Feedback, which feeds Typography+Components, which feeds Layout, which feeds Pages.

Technical Insights

Parallel Agent Orchestration

Pattern for building 30+ components simultaneously:

  1. Define the design language spec once (color palette, spacing, typography, visual motifs)
  2. Assign decoupled categories to parallel agents (each agent owns 5 files, no shared state)
  3. Each agent creates self-contained components — no cross-component imports needed
  4. Orchestrator handles wiring (the page.tsx imports and specimen arrays)
  5. Post-build: fix lint/type errors that only surface when everything is composed

Trade-off: agents can't see each other's work, so some visual consistency drift is inevitable. But the design spec constrains this enough that results are cohesive.

Linter Race Condition

The Biome linter (running in watch mode or via editor integration) would remove "unused" imports from page.tsx between the time components were imported and the specimen arrays were added. This happened because:

  1. Agent adds imports at top of file
  2. Linter runs, sees no usage of those imports yet, removes them
  3. Agent adds specimen arrays that reference the imports
  4. Build fails: missing imports

Fix: Add the specimen arrays first (which reference not-yet-imported symbols), then add imports. The linter won't remove imports that are actually used. Alternatively, add both in a single atomic edit.

ReactFlow Node Type Strategy

Four node types handle different specimen sizes:

TypeSizeUse Case
SpecimenNodeFixed 300x300Small atomic components (packets, marks, icons)
FrameNodeGrid of 300x300 cellsGrouped specimens in a single frame
PreviewNodeFixed 600x380Full-width backdrops
LayoutNodeNatural content sizeFull-page compositions (800px+ wide)

All share the same visual treatment: border-white/10, bg-[#0a0a0a], monospace title label at top-right.

File Summary

  • /Users/joel/Code/brubkr/src/app/(standalone)/design-system/page.tsx — 1372 lines, main canvas orchestrator
  • /Users/joel/Code/brubkr/src/components/lab/ — 224 component files total
    • 30 layout compositions (layout-*.tsx)
    • 25 chart specimens (chart-*.tsx)
    • 61 content block specimens (cards, panels, entries, banners, quotes, navs, steps, tables, previews, footers, sections, separators)
    • ~108 primitive specimens (packets, grids, backdrops, icons, marks, dividers, frames, status, hovers, textures, loading, connectors, headlines, gauges, ambient)

Open Questions

  • The page.tsx is 1372 lines — should the specimen arrays be split into separate files? Currently works because it's a single client component and tree-shaking handles the rest.
  • Layout specimens each define their own width/height. Should there be a shared LAYOUT_W constant or is per-component flexibility better?
  • Chart specimens are 400px wide for the container. Some (radar, pie) look better square. May need a ChartNode type that's 400x300 fixed.
  • The dagre index view is nice but doesn't show specimen count per category. Could add that as a badge on each category node.
LOG.ENTRY_END
ref:brubkr
RAW