FeaturesDynamic SectionsPhase 3: Display Registry

Phase 3: Display Registry

Goal: Extract existing display components into a registry pattern, enabling dynamic section rendering in Phase 4.

Status: Design complete, ready for implementation


Design Decisions

DecisionChoiceRationale
Registry locationmodules/registry/Dedicated module, discoverable
Data flowProps-basedKeeps displays pure/testable, SSR in pages
Content componentsExtract StoryCard, ProjectCardClean abstraction
Gallery displaySkip for nowYAGNI - different beast
Loading/empty statesParent handlesDisplays stay focused
Type safetyMinimal interfacesContent registry typed, displays flexible

Registry Module Structure

frontend/src/modules/registry/
├── index.ts                    # Public exports
├── types.ts                    # TypeScript interfaces
├── contentRegistry.ts          # Content type registry
├── displayRegistry.ts          # Display type registry
└── displays/
    ├── FeedDisplay.tsx
    ├── CardGridDisplay.tsx
    ├── StaticPageDisplay.tsx
    └── index.ts

Type Definitions

// types.ts
export type DisplayType = 'feed' | 'card-grid' | 'static-page';
export type ContentType = 'story' | 'project' | 'page';
 
export interface ContentEntry<T> {
  listItem: React.ComponentType<{ item: T }> | null;
  detail: React.ComponentType<{ item: T }> | null;
}

Content Registry

// contentRegistry.ts
import { StoryCard, StoryDetail } from '@/modules/stories';
import { ProjectCard, ProjectDetail } from '@/modules/projects';
 
export const contentRegistry = {
  story: {
    listItem: StoryCard,
    detail: StoryDetail,
  },
  project: {
    listItem: ProjectCard,
    detail: ProjectDetail,
  },
  page: {
    listItem: null,
    detail: null,
  },
};

Display Registry

// displayRegistry.ts
import { FeedDisplay, CardGridDisplay, StaticPageDisplay } from './displays';
 
export const displayRegistry = {
  'feed': FeedDisplay,
  'card-grid': CardGridDisplay,
  'static-page': StaticPageDisplay,
};

Display Components

FeedDisplay

Infinite scroll list, extracted from StoryList.

interface FeedDisplayProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  onLoadMore: () => void;
  hasMore: boolean;
}

CardGridDisplay

Responsive grid, extracted from projects.tsx.

interface CardGridDisplayProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

StaticPageDisplay

Single content block for static pages.

interface StaticPageDisplayProps {
  content: string;
  title?: string;
}

Component Extraction

ComponentCurrent LocationExtract To
Story list itemInline in StoryList.tsxStoryCard.tsx
Story detailpages/stories/[slug].tsxStoryDetail.tsx
Project cardInline in pages/projects.tsxProjectCard.tsx
Project detailpages/projects/[slug].tsxProjectDetail.tsx

Components stay in their domain modules. Registry imports and maps them.


Success Criteria

  1. All existing pages render identically
  2. modules/registry/ exports both registries
  3. modules/projects/ module exists with extracted components
  4. StoryList uses StoryCard internally
  5. projects.tsx uses ProjectCard internally
  6. All existing tests pass
  7. Guide exists at /guides/creating-content-types

What Phase 3 Does NOT Do

  • Change any URLs
  • Fetch sections from database
  • Use registries dynamically

Phase 4 wires registries to dynamic routing.