Unified Post Header — Design

Goal

Hoist ContentHeader rendering into page.tsx so all post types share one header call site. Renderers become body-only components.

Current State

All three post types (Observable, DataPackage, Story) already use ContentHeader, but each renderer calls it internally with slightly different props. Observable returns early in page.tsx before shared data (postConfig, stats) is fetched.

Design

page.tsx restructuring

  1. Fetch post (already done)
  2. Fetch postConfig + stats for ALL post types (move before the type branching)
  3. Resolve authors with unified fallback chain
  4. Resolve title/description/date/image based on post type and available metadata
  5. Render <ContentHeader /> once
  6. Render type-specific body:
    • OBSERVABLE: <ObservableEmbed /> (iframe only)
    • datapackage metadata present: <DataPackageBody /> (tables, previews, etc.)
    • story/default: <article className="prose ...">{mdxContent}</article>

For Observable posts, blob/pageMetadata are not needed — skip that fetch and go straight to the iframe body.

Author resolution (unified in page.tsx)

Fallback chain:

  1. post.authors from DB
  2. postConfig.authors (string-based, from config file)
  3. DataPackage contributors (from blob metadata)
  4. post.publication.owner.ghUsername (last resort)

ContentHeader props per type

PropObservableDataPackageStory
titlepost.title or projectNamepost.title or projectNamepageMetadata.title or projectName
descriptionpost.descriptionpost.descriptionpageMetadata.description or post.description
datepost.modifiedAtpost.modifiedAtpageMetadata.date or post.modifiedAt
dateFormatrelativerelativehuman
imagenonenoneresolved from pageMetadata.image
showDownloadsfalsetruefalse
showImagefalsefalsetrue

Renderer simplification

  • ObservablePage becomes body-only: just the iframe. No header, no postConfig fetch.
  • DataPackageLayout drops ContentHeader. Keeps metadata table, data files, previews, premium warning, JSON-LD.
  • StoryLayout reduces to the prose <section> wrapper (or inlined in page.tsx).

Files affected

  • app/[publication]/[post]/[[...slug]]/page.tsx — main changes
  • components/post-renderers/ObservablePage.tsx — remove header, simplify props
  • components/layouts/datapackage.tsx — remove header
  • components/story-layout.tsx — remove header (possibly inline)