Changelog System Refactor: Layouts, Components, and Client-Side View Toggling

Extracted changelog page variants into reusable layouts with client-side view toggling. Created Button--Default component, ChangelogViewToggler, and three PageHeader variants for the design system.

Claude
Claude Claude

Changelog - 2025-12-25 (#6)

Dark Matter Site: Changelog System Refactor

Overview

Refactored the three changelog page variants (Timeline, Cards, Releases) into a unified system with reusable layouts and client-side view toggling. The previous implementation had three separate pages (index.astro, variant-1.astro, variant-3.astro) with duplicated code. Now all three views live on a single page with instant client-side switching.

Key insight: By rendering all three layouts on initial page load (hidden via CSS), we achieve instant view switching without any server round-trips or flash of unstyled content.

Key deliverables:

  • Three Changelog Layouts: Timeline, CardGrid, and Releases as standalone layout components
  • Button--Default Component: Exact replica of the variant-tab styling for reuse
  • ChangelogViewToggler: View switching with share URL functionality
  • Three PageHeader Variants: Centered, SplitNav, and Compact header components
  • No-Flash View Switching: CSS data-initial-view pattern for SSR-friendly toggling

Why This Refactor?

Before After
3 separate pages with duplicated rendering logic 1 page with 3 reusable layouts
URL changes on view switch (full page load) Instant client-side toggle, URL stays or updates
Copy-paste button styling across pages Single Button--Default component
Hardcoded headers per page Reusable PageHeader--* components

Architecture

The Layout System

src/layouts/changelog/
├── TimelineLayout.astro    # Vertical timeline with month grouping
├── CardGridLayout.astro    # Grid cards with category filtering
└── ReleasesLayout.astro    # GitHub-style collapsible releases

Each layout receives the same props and renders its own visualization:

interface Props {
  entries: CollectionEntry<'changelog'>[];
  groupedByMonth?: Map<string, CollectionEntry<'changelog'>[]>;
}

Client-Side View Toggling Pattern

The key innovation is a CSS-first approach that avoids flash:

<!-- Initial state set via CSS data attribute (SSR-safe) -->
<div class="views-container" data-initial-view="timeline">
  <TimelineLayout ... />   <!-- shown via CSS when data-initial-view="timeline" -->
  <CardGridLayout ... />   <!-- hidden via CSS -->
  <ReleasesLayout ... />   <!-- hidden via CSS -->
</div>

<style>
  /* CSS handles initial state - no JS needed for first paint */
  [data-initial-view="timeline"] .timeline-view { display: block; }
  [data-initial-view="timeline"] .cards-view { display: none; }
  [data-initial-view="timeline"] .releases-view { display: none; }
</style>

<script>
  // JS takes over after hydration - removes data-initial-view
  container.removeAttribute('data-initial-view');
  // Now JS controls visibility via inline styles
</script>

Why this matters: The CSS ensures correct initial render without waiting for JavaScript. Once JS hydrates, it removes the attribute and takes full control.


Components Created

Button--Default

A foundational button component matching the exact styling of the changelog variant tabs:

<ButtonDefault href="/changelog" active={isActive}>
  <svg slot="icon">...</svg>
  Timeline
</ButtonDefault>

Features:

  • Works as both <a> (with href) and <button> (without href)
  • Active state styling with violet glow
  • Icon slot for inline SVGs
  • Data attribute pass-through for JS interaction

ChangelogViewToggler

Renders the three view buttons with share functionality:

<ChangelogViewToggler initialView="timeline" />

Features:

  • Three toggle buttons (Timeline, Cards, Releases)
  • Share button that copies URL with ?view= parameter
  • Toast notification on copy
  • URL update on view change (without page reload)

PageHeader Variants

Three header components for different page contexts:

Component Use Case Features
PageHeader--Centered Landing pages, announcements Gradient title, badge, centered layout
PageHeader--SplitNav List pages, dashboards Split layout, counter badge, filter slot
PageHeader--Compact Simple content pages Minimal, GitHub-style

Technical Details

Sorting Fix

Fixed incorrect ordering of same-day changelog entries:

// Before: Only sorted by date
entries.sort((a, b) => b.data.date - a.data.date);

// After: Secondary sort by entry ID (descending) for same-day entries
entries.sort((a, b) => {
  const dateCompare = b.data.date.getTime() - a.data.date.getTime();
  if (dateCompare !== 0) return dateCompare;
  return b.id.localeCompare(a.id); // _05 comes before _04 before _03...
});

Releases View Filter

The Releases layout now properly filters to only show entries from the releases/ folder:

const releaseEntries = entries.filter(entry =>
  entry.id.startsWith('releases/')
);

Header Text Clipping Fix

Fixed the "g" descender being cut off in gradient titles caused by bg-clip-text:

<!-- Added pb-1 for descender clearance -->
<h1 class="... bg-clip-text pb-1">Changelog</h1>

File Summary

New Files (8)

File Lines Purpose
src/layouts/changelog/TimelineLayout.astro 180 Timeline view with month grouping
src/layouts/changelog/CardGridLayout.astro 280 Card grid with category filters
src/layouts/changelog/ReleasesLayout.astro 190 GitHub-style releases
src/components/basics/buttons/Button--Default.astro 120 Reusable styled button
src/components/ui/ChangelogViewToggler.astro 180 View toggle with share
src/components/page-headers/PageHeader--Centered.astro 110 Centered hero header
src/components/page-headers/PageHeader--SplitNav.astro 70 Split nav header
src/components/page-headers/PageHeader--Compact.astro 50 Minimal header

Modified Files (1)

File Changes
src/pages/changelog/index.astro Now uses layouts, toggler, client-side switching

Usage

Using the Layouts

---
import TimelineLayout from '@layouts/changelog/TimelineLayout.astro';
import { getCollection } from 'astro:content';

const entries = await getCollection('changelog');
---

<TimelineLayout entries={entries} groupedByMonth={groupedByMonth} />

Using Page Headers

---
import PageHeaderCentered from '@components/page-headers/PageHeader--Centered.astro';
---

<PageHeaderCentered
  title="Changelog"
  subtitle="Track the evolution of Dark Matter."
  badgeText="Development Timeline"
  badgeIcon="clock"
>
  <div slot="actions">
    <!-- Optional action buttons -->
  </div>
</PageHeaderCentered>

Why This Architecture?

  1. Single Page, Multiple Views: Users can toggle views instantly without losing scroll position or reloading
  2. SSR Compatible: CSS data attributes ensure correct first paint before JS hydrates
  3. Shareable URLs: View preference persists via URL parameter
  4. DRY Components: Button and header components reusable across the site
  5. Separation of Concerns: Layouts handle data display, togglers handle interaction

Summary

This refactor transforms the changelog from three separate pages into a unified, client-interactive experience. The pattern of "render all views, toggle visibility" trades slightly larger initial HTML for instant interactivity and better UX.

Code reduction: ~400 lines of duplicated code eliminated Components created: 8 reusable components User experience: Instant view switching, shareable URLs, consistent styling