Skip to content

The Deckly Developer Bible ​

Welcome to the internal source of truth. This document provides a high-fidelity guide to the technical architecture, service patterns, and development workflows that power the Deckly "Founder Data Room" experience.

Fast Map ​

TaskStart here
Change routing or app boot logicsrc/App.tsx, src/main.tsx
Change upload or rendering flowsrc/pages/ManageDeck.tsx, src/workflows/deckProcessing.ts
Change auth or session behaviorsrc/contexts/AuthContext.tsx, src/services/authSession.ts
Change room behaviorsrc/pages/DataRoomDetail.tsx, src/services/dataRoomService.ts
Change shared navigationsrc/components/layout/Sidebar.tsx, src/components/layout/BottomNav.tsx

Tech Stack Core ​

LayerTechnologyPurpose
FrameworkReact 19 + ViteHigh-performance SPA with route-level code splitting.
LogicTypeScriptStrict type safety for complex data shapes.
Auth/DBSupabaseIdentity, PostgreSQL, and namespaced URL routing logic.
ProcessingPDF.js (Browser)Privacy-first, zero-cost client-side rendering.
AnalyticsPostHog + CustomCombined deep UX events with aggregated Room visitor signals.
StylingTailwind CSS + shadcn/uiUtility-first classes + accessible component primitives. Custom design tokens in App.css.

Project Architecture ​

1. File Structure ​

text
src/
├── components/
│   ├── common/        # Atomic UI atoms (Buttons, Toggles, Inputs)
│   ├── dashboard/     # Dashboard cards, charts, tables, and split feature sections
│   │   ├── manage-deck/           # Upload/details/access/actions sections
│   │   ├── AnalyticsChart.tsx       # Responsive flex-1 bar chart
│   │   ├── AnalyticsDashboard.tsx   # Tabbed stats overview
│   │   ├── ContentStatsCard.tsx     # Inline stats row
│   │   └── DecksTable.tsx           # Desktop table + Mobile card-list
│   ├── layout/        # Shell components
│   │   ├── DashboardLayout.tsx      # Root layout: sidebar/header/FAB/BottomNav
│   │   ├── Sidebar.tsx              # Desktop sidebar (md+)
│   │   └── BottomNav.tsx            # Mobile bottom nav (< md)
│   ├── AccessGate     # Interceptor for password/email gating
│   └── AnalyticsModal # Real-time charts and drop-off analytics
├── contexts/
│   └── AuthContext    # Session watchdog and Pro-tier gating
├── pages/
│   └── DeckAnalytics  # Per-deck engagement deep-dive
├── services/
│   ├── deckService      # Composed facade over deck-focused modules
│   ├── deckStorageService
│   ├── deckLibraryService
│   ├── deckBrandingService
│   ├── authSession.ts   # Shared auth/user-id resolution helpers
│   ├── analyticsService # Heartbeat tracking and analytics queries
│   ├── dataRoomService  # Room management & document ordering
│   ├── interestSignalService # Visitor engagement & Room-level signal aggregation
│   ├── userService      # Identity and user handle management
│   └── url.ts           # Centralized share-link & routing logic
├── workflows/
│   └── deckProcessing.ts # Shared PDF rendering/image generation workflow
└── pages/
    ├── ManageDeck     # Composition layer for upload/edit flow
    ├── DataRoomDetail # Internal room view with aggregated signals
    └── Viewer         # High-fidelity viewer with heartbeat tracking

2. Database Schema (PostgreSQL + RLS) ​

Deckly uses Row Level Security (RLS) to ensure data isolation.

  • decks: Registry of documents, status, and slide URLs.
  • profiles: User metadata and tier levels (FREE/PRO).
  • deck_stats: Aggregate table for rapid dashboard rendering.
  • deck_page_views: Registry for unique view deduplication (24h window).
  • library_folders: Localized "Collections" for asset organization, including a color identifier (Default: #666666).
  • global_tags: Canonical user-defined labels reused across library decks, saved rooms, room folders, room documents, and deck tags.
  • global_tag_aliases: Legacy ID/name compatibility layer used during the migration from room-local and library-local tag rows.

System Deep-Dives ​

1. The PDF processing Warehouse ​

Processing happens entirely in the browser to eliminate server costs and maximize privacy.

mermaid
sequenceDiagram
    participant User
    participant ManageDeck
    participant Workflow
    participant PDFjs
    participant Storage
    participant DB

    User->>ManageDeck: Uploads PDF
    ManageDeck->>Workflow: submitDeck / processPdfFile
    Workflow->>PDFjs: Loads ArrayBuffer
    loop Every Page
        PDFjs->>Workflow: Render page to canvas
        Workflow->>Workflow: Convert to WebP and collect links
    end
    Workflow->>Storage: Parallel Upload (Concurrency: 3)
    Storage-->>Workflow: Return URLs
    Workflow->>DB: Upsert Deck Pages & Status
    DB-->>User: Navigate to Dashboard

2. The Auth Watchdog Sequence ​

Prevents app hangs caused by Supabase free-tier "cold starts."

mermaid
flowchart TD
    A[App Initialization] --> B{auth.getSession}
    B -->|Success < 10s| C[Profile Sync]
    B -->|Timeout > 10s| D[Show 'Waking up DB' UI]
    C --> E[Enable Dashboard]
    D --> F[Wait 2s more]
    F --> G{Still Loading?}
    G -->|Yes| H[Force Clear Loader]
    G -->|No| E

3. Analytics Heartbeat & Deduplication ​

  • Heartbeat: On slide change or tab close, Viewer.tsx calls analyticsService.syncSlideStats() to perform an Atomic Upsert (total_views + 1).
  • Uniqueness: Uses a visitorId in localStorage. A view is only "unique" if the ID hasn't touched the slide in 24 hours.
  • Aggregation: getRoomVisitorSignals aggregates engagement data from all decks in a room to provide a unified "Room Interest" score.

To prevent broken links during routing schema updates, we centralize all public and internal URL generation in src/utils/url.ts. All copy-link actions and internal navigation must use these helpers to ensure a single source of truth for handle-based paths.

5. Current refactor direction ​

  • ManageDeck.tsx now delegates orchestration to useManageDeckWorkflow.ts
  • the screen UI is split into ManageDeckSections.tsx
  • shared PDF logic lives in src/workflows/deckProcessing.ts
  • auth/session lookups are being standardized via authSession.ts
  • deckService.ts is now a facade over narrower services instead of one large implementation file

6. Bundle Optimization (Performance) ​

To maintain a premium feel, we use route-level code splitting via React.lazy and Suspense. Vendor libraries are isolated into a separate bundle via Vite's manualChunks to ensure minimal initial payload and effective caching of stable dependencies.

Source Anchors ​

When this guide references a behavior, these are the code paths to inspect first:

  • src/components/layout/DashboardLayout.tsx for global shell changes.
  • src/components/layout/Sidebar.tsx and src/components/layout/BottomNav.tsx for navigation changes.
  • src/services/deckService.ts for the shared deck facade.
  • src/services/analyticsService.ts and src/services/interestSignalService.ts for tracking changes.
  • src/utils/url.ts for share-link and route generation.

Developer Workflows ​

1. Setup & Standards ​

  1. npm install && cp .env.example .env.local
  2. Always use Tailwind utility classes for layout and spacing, and CSS Variables from App.css (e.g., --accent-primary) for brand colours. Avoid ad-hoc inline styles.
  3. Prefer Custom Hooks (useAuth, useDecks) over direct Supabase client calls.
  4. Run npm run type-check, npm run lint, and npm test before closing substantial work. The Vitest pipeline is healthy again.

2. The "New Feature" Recipe ​

  1. Type: Define interfaces in src/types/index.ts.
  2. Service: Create logic in src/services/.
  3. Component: Build in src/components/ using design tokens.
  4. Analytics: Register temporary events in analyticsService.ts.

3. Mobile-First UI Rule ​

All dashboard pages use a single md breakpoint (768px) to split mobile and desktop layouts. Key conventions every dev must follow:

  • Navigation: BottomNav on mobile, Sidebar on desktop. Never show both simultaneously.
  • Content padding: Always p-4 md:p-8 — never hard-code large padding without a mobile fallback.
  • Bottom clearance: Add pb-20 md:pb-0 to any page's scroll container so content doesn't hide behind the bottom nav bar (80px tall).
  • Tables on mobile: Use the dual-render pattern — md:hidden card-list above + hidden md:block table below. Reference: DecksTable.tsx.
  • Grids: grid-cols-2 md:grid-cols-4 for summary cards.
  • Fonts: text-2xl md:text-5xl for large stats. Always include a smaller mobile size.
  • Charts: Custom charts use flex-1 max-w-12 per bar so they naturally fit any container width without overflow.

Troubleshooting & FAQ ​

ProblemSolution
Upload stuck at 0%Ensure the decks bucket is Public and RLS allows INSERT.
Icons are missingCheck if the path in markdown matches /icons/[name].svg.
Stats not updatingWait 30s for the memory cache to expire or use the manual refresh.
Auth hangsCheck VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY (ensure VITE_ prefix).
Content behind navAdd pb-20 md:pb-0 to the page's scroll container. The bottom nav is 80px tall.
Chart bars too wideChart uses flex-1 max-w-12 — if you increase the number of data points beyond 14, reduce max-w-12 to max-w-8.

This guide is the living constitution of the Deckly codebase. Last Updated: April 2026.

Built with ❤️ for Founders