docs/architecture/demo-app-and-tier-architecture.md

Demo App and Tier Architecture

Last verified: 2026-03-07 Target: apps/demo Companion: packages/app-library, packages/demo-flow

See also: demo-app-architecture.md for the full demo app architecture with Mermaid diagrams, route reference, and DemoStateProvider documentation.

This document describes the standalone demo app (apps/demo) and the tier-based package structure for @repo/app-library.

Overview

The demo app provides a unified demonstration experience that runs without environment variables or authentication. It consolidates demo pages from both the hello and library apps into a single, standalone application.

Architecture Diagram

Demo App (apps/demo)

Purpose

  • Standalone demos: Run without Clerk authentication or environment variables
  • Unified experience: Single app for all demo flows across the product
  • Development tool: Quick iteration on UI without full stack setup
  • Sales/Marketing: Shareable demo links without account requirements

Structure

apps/demo/
├── app/
│   ├── layout.tsx               # Plus Jakarta Sans font, DemoProviders
│   ├── page.tsx                 # Landing with categorized demo cards
│   ├── providers.tsx            # DemoStateProvider + DemoControlPanel
│   ├── hello/                   # Hello onboarding wizard demo
│   ├── library/                 # Library app demos (redesign + deprecated)
│   │   ├── interviews/          # Unified interviews view (active redesign)
│   │   ├── interviews-empty/    # Empty state + onboarding nudges
│   │   ├── recordings/          # Video detail
│   │   ├── dashboard/[flowId]/  # Flow dashboard
│   │   ├── free/                # Free tier (pre-redesign, collapsible)
│   │   └── paid/                # Paid tier (pre-redesign, collapsible)
│   ├── interview/[flowId]/      # BVF interview experience (real components)
│   ├── bvf-screens/             # Permission screen design exploration
│   ├── review/                  # Collaborative video review
│   ├── video-intelligence/      # AI analysis dashboard
│   ├── video-player/            # Video player showcase
│   ├── question-video/          # Animated question cards (Pancarte)
│   ├── architecture/            # Architecture docs browser (Mermaid + Markdown)
│   └── nightly-maintenance/     # Nightly pipeline architecture overview
├── package.json
└── next.config.mjs

Key Characteristics

  1. No Authentication: No Clerk, no auth middleware
  2. Mostly No Environment Variables: Uses mock data throughout. Exception: Sentry (@sentry/nextjs) — silently disabled when DSN is absent.
  3. Shared Components: Imports from @repo/app-library, @repo/app-hello, @repo/app-video-flow, @repo/app-admin-library, @repo/video-review, @repo/video-player, @repo/question-video, @repo/demo-flow
  4. Mock Data: Inline mocks or package-scoped mocks (e.g. @repo/video-review/mocks)
  5. DemoStateContext: Global mock state (auth, tier, user name) with localStorage persistence, toggled via DemoControlPanel floating cog button

Dependencies

{
  "@repo/app-admin-library": "workspace:*",
  "@repo/app-hello": "workspace:*",
  "@repo/app-library": "workspace:*",
  "@repo/app-video-flow": "workspace:*",
  "@repo/core": "workspace:*",
  "@repo/demo-flow": "workspace:*",
  "@repo/design-system": "workspace:*",
  "@repo/organization-extraction": "workspace:*",
  "@repo/question-video": "workspace:*",
  "@repo/registries": "workspace:*",
  "@repo/video-player": "workspace:*",
  "@repo/video-review": "workspace:*",
  "@sentry/nextjs": "^10.x",
  "mermaid": "^11.x",
  "react-markdown": "^9.x"
}

Package Structure: @repo/app-library

The @repo/app-library package uses a tier-based organization to cleanly separate free and paid functionality while sharing common code.

Folder Structure

packages/app-library/
├── src/
│   ├── index.ts              # Barrel for shared only
│   ├── shared/               # Layout, navigation, types, helpers — used by all tiers
│   │   ├── index.ts
│   │   ├── types.ts              # Shared types (VideoInfo, FlowInfo, etc.)
│   │   ├── transformers.ts       # toFlowInfo(), toVideoInfo(), toRespondentViewData()
│   │   ├── mocks.ts              # Shared mock data
│   │   ├── LibraryLayoutShell.tsx  # Main layout shell (collapsible sidebar + content)
│   │   ├── AppSidebar.tsx          # Collapsible sidebar navigation
│   │   ├── MobileNav.tsx           # Mobile bottom tab navigation
│   │   ├── ProfileMenu.tsx         # User profile/org menu (Clerk-free UI component)
│   │   ├── InterviewsView.tsx      # Unified interviews card grid (all tiers)
│   │   ├── InterviewsSkeleton.tsx  # Loading skeleton for interviews view
│   │   ├── ShareFlowDialog.tsx     # Share interview link dialog
│   │   ├── OnboardingNudges.tsx    # Empty-state nudge cards
│   │   ├── InviteTemplatesDialog.tsx # Invite email template picker
│   │   ├── AnimatedThumbnail.tsx   # Static JPEG → GIF crossfade thumbnail
│   │   ├── extract-preload-thumbnails.ts # Server-side image preloading
│   │   ├── video-helpers.ts        # getThumbnailUrl, getBestVideoSource, etc.
│   │   └── csv-export.ts           # CSV generation and download
│   ├── free/                 # Free tier specific
│   │   ├── index.ts          # Exports free components + re-exports shared
│   │   ├── FlowsListView.tsx
│   │   ├── FlowManageUI.tsx
│   │   └── OrganizationSelectorUI.tsx
│   └── paid/                 # Paid tier specific
│       ├── index.ts          # Exports paid components + re-exports shared
│       ├── RespondentViewUI.tsx
│       ├── UserRecentVideos.tsx
│       ├── VideosDashboardWithTabs.tsx
│       ├── VideoDashboardGrid.tsx
│       ├── FinalVideosSection.tsx
│       ├── FinalVideosEmptyState.tsx
│       ├── FlowSidebar.tsx
│       ├── MainNavUI.tsx
│       ├── MainNavLinksClient.tsx
│       ├── PaidLayoutHeader.tsx
│       └── useFinalVideoSelection.ts
└── package.json

Import Patterns

// Free tier pages - gets free components + shared
import { FlowsListView, VideoInfo } from "@repo/app-library/free";

// Paid tier pages - gets paid components + shared
import { VideosDashboard, VideoInfo } from "@repo/app-library/paid";

// Just shared stuff (optional convenience)
import { VideoInfo, toVideoInfo } from "@repo/app-library";
import { transformers } from "@repo/app-library/shared";

Package Exports

{
  "exports": {
    ".": {
      "types": "./src/index.ts",
      "import": "./dist/index.js"
    },
    "./shared": {
      "types": "./src/shared/index.ts",
      "import": "./dist/shared/index.js"
    },
    "./free": {
      "types": "./src/free/index.ts",
      "import": "./dist/free/index.js"
    },
    "./paid": {
      "types": "./src/paid/index.ts",
      "import": "./dist/paid/index.js"
    }
  }
}

What Goes Where

LocationContents
/sharedLayout (LibraryLayoutShell, AppSidebar, MobileNav), navigation (ProfileMenu), unified views (InterviewsView), types, transformers, mocks, helpers
/freeFlowsListView, FlowManageUI, OrganizationSelectorUI
/paidRespondentViewUI, VideosDashboardWithTabs, VideoDashboardGrid, FinalVideosSection, FlowSidebar, nav components, hooks

When a component is needed by both tiers, move it to /shared.

Note (2026-02): The shared tier has grown significantly beyond types/transformers to include the full layout shell and navigation. The root library page (/) now uses InterviewsView from /shared for all users — plan-gating has moved to the respondent detail level (/dashboard/[flowId]/[respondentId]), not the root.

Component Patterns

User-Facing vs Admin Components

Some library app components mix admin features with user features. The demo architecture uses this separation strategy:

  1. Package contains user-facing components only:

    • No server actions requiring admin permissions
    • No delete/edit operations needing auth checks
    • Pure UI that works with props/mock data
  2. Admin components stay in apps/library:

    • Components with server actions remain in the app
    • Admin-specific functionality not extracted to packages
    • Will be separated when admin app is created

Component Naming Convention

  • *UI suffix indicates a presentation component (e.g., RespondentViewUI)
  • These components accept data via props
  • No direct data fetching or server actions
  • Can be used in both production and demo contexts

Example: RespondentViewUI

// packages/app-library/src/paid/RespondentViewUI.tsx
// User-facing component - no admin actions

interface RespondentViewUIProps {
  respondent: PersonData;
  videos: VideoInfo[];
  onVideoSelect?: (video: VideoInfo) => void;
}

export function RespondentViewUI({ 
  respondent, 
  videos, 
  onVideoSelect 
}: RespondentViewUIProps) {
  // Pure presentation logic
}
// apps/library - admin version with server actions
// Wraps RespondentViewUI with admin capabilities

import { RespondentViewUI } from "@repo/app-library/paid";

export function RespondentDashboard({ respondentId }) {
  // Data fetching, auth checks, admin actions
  return (
    <RespondentViewUI 
      respondent={data}
      videos={videos}
      onVideoSelect={handleAdminVideoAction}
    />
  );
}

Transformer Pattern

Transformers convert backend data models to frontend-friendly types:

// packages/app-library/src/shared/transformers.ts

export function toVideoInfo(video: Video): VideoInfo {
  return {
    id: video.id,
    thumbnailUrl: video.thumbnail_url,
    createdAt: new Date(video.created_at),
    // ... simplified, frontend-friendly structure
  };
}

export function toFlowInfo(flow: VideoFlow): FlowInfo {
  return {
    id: flow.id,
    name: flow.name,
    videoCount: flow.videos?.length ?? 0,
    // ... derived properties for UI
  };
}

Usage in Apps

// Server component in apps/library
import { toFlowInfo } from "@repo/app-library/shared";

export async function FlowsPage() {
  const flows = await fetchFlows();
  const flowInfos = flows.map(toFlowInfo);
  return <FlowsListView flows={flowInfos} />;
}
// Demo page - uses mock data directly
import { mockFlowInfos } from "@repo/app-library/shared";

export function DemoFlowsPage() {
  return <FlowsListView flows={mockFlowInfos} />;
}

Mock Data Strategy

Location

Mock data lives in the shared layer for reuse:

// packages/app-library/src/shared/mocks.ts

export const mockVideoInfo: VideoInfo = {
  id: "demo-video-1",
  thumbnailUrl: "/demo/thumbnail.jpg",
  // ...
};

export const mockFlowInfos: FlowInfo[] = [
  { id: "demo-flow-1", name: "Customer Testimonials", videoCount: 5 },
  // ...
];

Usage Patterns

  1. Demo App: Import mocks directly for demo pages
  2. Tests: Use same mocks for component testing
  3. Storybook: Consistent mock data across stories

Relationship to Production Apps

apps/library

The Library app imports heavily from @repo/app-library for layout and UI components:

// apps/library/app/(main)/layout.tsx — uses shared layout shell
import { LibraryLayoutShell } from "@repo/app-library/shared";

// apps/library/app/(main)/page.tsx — unified root for ALL tiers
import { InterviewsView, InterviewsSkeleton } from "@repo/app-library/shared";

// apps/library/app/(main)/dashboard/[videoFlowId]/[respondentId]/page.tsx
import { toRespondentViewData } from "@repo/app-library/paid";

Architecture note: As of 2026-02, the library root page (/) serves a unified InterviewsView for all users. The legacy (free)/onboarding route redirects to root and is kept only for backwards-compatible URL compatibility. Plan-gating has shifted from the root page to the respondent detail page, where unpaid users who don't own a video are redirected back to the flow dashboard.

apps/hello

The Hello app remains unchanged — it uses @repo/app-hello for its components.

apps/demo

The Demo app imports from both packages with mock data:

// apps/demo/app/library/free/page.tsx
import { FlowsListView } from "@repo/app-library/free";
import { mockFlowInfos } from "@repo/app-library/shared";

export default function DemoFreeTierPage() {
  return <FlowsListView flows={mockFlowInfos} />;
}

Benefits

  1. Clean Separation: Free/paid tiers clearly organized
  2. Reusable Components: Same components work in prod and demo
  3. Type Safety: Shared types ensure consistency
  4. No Auth Overhead: Demo runs standalone
  5. Transformer Pattern: Backend complexity hidden from UI
  6. Mock Consistency: Same mock data for demos and tests

Migration Notes

When adding new components:

  1. Determine tier: Free, paid, or shared?
  2. Create UI component: Props-driven, no server actions
  3. Add transformer: If backend model differs from UI needs
  4. Add mocks: For demo and testing
  5. Export from index: Add to appropriate tier's barrel file