# The Browser UI: Next.js Frontend

> The Next.js app is the face the user sees: a dashboard to manage decks, an outline editor, a live slide editor with Tiptap rich text, and an export flow (PDF, PPTX, Google Slides). Routes map directly to stages of the generation workflow.

- Repository: presenton/presenton
- GitHub: https://github.com/presenton/presenton
- Human wiki: https://grok-wiki.com/public/wiki/presenton-presenton-f6685dc028cc
- Complete Markdown: https://grok-wiki.com/public/wiki/presenton-presenton-f6685dc028cc/llms-full.txt

## Source Files

- `servers/nextjs/app/(presentation-generator)/outline/page.tsx`
- `servers/nextjs/app/(presentation-generator)/presentation/page.tsx`
- `servers/nextjs/app/(presentation-generator)/components/PresentationRender.tsx`
- `servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx`
- `servers/nextjs/app/(export)/pdf-maker/page.tsx`
- `servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/page.tsx`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/page.tsx](servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/page.tsx)
- [servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/DashboardPage.tsx](servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/DashboardPage.tsx)
- [servers/nextjs/app/(presentation-generator)/outline/page.tsx](servers/nextjs/app/(presentation-generator)/outline/page.tsx)
- [servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx](servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx)
- [servers/nextjs/app/(presentation-generator)/presentation/page.tsx](servers/nextjs/app/(presentation-generator)/presentation/page.tsx)
- [servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx](servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx)
- [servers/nextjs/app/(presentation-generator)/presentation/components/PresentationHeader.tsx](servers/nextjs/app/(presentation-generator)/presentation/components/PresentationHeader.tsx)
- [servers/nextjs/app/(presentation-generator)/presentation/components/Chat.tsx](servers/nextjs/app/(presentation-generator)/presentation/components/Chat.tsx)
- [servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx](servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx)
- [servers/nextjs/app/(presentation-generator)/components/PresentationRender.tsx](servers/nextjs/app/(presentation-generator)/components/PresentationRender.tsx)
- [servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx](servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx)
- [servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx](servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx)
- [servers/nextjs/app/(export)/pdf-maker/page.tsx](servers/nextjs/app/(export)/pdf-maker/page.tsx)
- [servers/nextjs/app/(export)/pdf-maker/PdfMakerPage.tsx](servers/nextjs/app/(export)/pdf-maker/PdfMakerPage.tsx)
</details>

# The Browser UI: Next.js Frontend

The Next.js application is the complete visual interface for Presenton. It covers every stage a user moves through: starting at a dashboard that lists existing decks, moving through an outline editor where slide topics are arranged, then to a live slide editor where content can be refined with direct text and image editing, and finally to an export flow that produces a PDF or PPTX file. Each stage maps to a distinct URL, and the routes are organized in a way that makes the workflow easy to follow in the file system.

This page describes how those routes are structured, what each screen does, and how the UI components work together—including the rich-text editor, the image/icon editing layer, the AI chat panel, and the export renderer.

---

## Route Map

The Next.js app uses the App Router and groups routes by concern inside parenthesized layout groups. The main generation workflow lives under `(presentation-generator)`, and a separate `(export)` group hosts the headless PDF renderer.

```text
app/
├── (presentation-generator)/
│   ├── (dashboard)/
│   │   ├── dashboard/          → /dashboard   (deck list)
│   │   ├── settings/           → /settings
│   │   ├── templates/          → /templates
│   │   └── theme/              → /theme
│   ├── outline/                → /outline     (slide outline editor)
│   ├── presentation/           → /presentation?id=<id>  (live slide editor)
│   └── documents-preview/      → /documents-preview
└── (export)/
    └── pdf-maker/              → /pdf-maker?id=<id>     (headless print renderer)
```

Each route group can share its own layout file without polluting sibling groups. The `(dashboard)` sub-group provides the sidebar navigation and shared header used by settings, templates, and themes, while the `outline` and `presentation` pages use a narrower layout focused on the editing canvas.

Sources: [servers/nextjs/app/(presentation-generator)/outline/page.tsx:1-42](), [servers/nextjs/app/(presentation-generator)/presentation/page.tsx:1-27]()

---

## Stage 1: Dashboard (`/dashboard`)

The dashboard is the home screen. It fetches every saved presentation from the backend via `DashboardApi.getPresentations()`, sorts the list by `updated_at` descending by default, and renders each deck as a card in `PresentationGrid`. Users can toggle between ascending and descending sort order with a single button.

The page also exposes a prominent "Create Presentation" card that links to `/upload`, the entry point for creating a new deck. Usage events (page viewed, new presentation clicked) are sent to Mixpanel on load and on interaction.

```tsx
// DashboardPage.tsx – fetching and sorting decks
const data = await DashboardApi.getPresentations();
data.sort(
  (a, b) =>
    new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
);
setPresentations(data);
```

Sources: [servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/DashboardPage.tsx:65-90]()

---

## Stage 2: Outline Editor (`/outline`)

After uploading a document or entering a topic, the user lands on the outline page. This is where the AI-generated slide outline is presented, reviewed, and adjusted before full slide rendering begins.

`OutlinePage` is the top-level client component. It has two tabs—**Outline & Content** and **Select Template**—rendered as a sticky tab bar fixed at the top of the viewport. Switching tabs is blocked while the outline is still streaming from the server.

Three custom hooks coordinate the page's logic:

| Hook | Responsibility |
|---|---|
| `useOutlineStreaming` | Tracks whether the AI outline is still being streamed and which slide index is active |
| `useOutlineManagement` | Handles drag-and-drop reordering (`handleDragEnd`) and adding new slides (`handleAddSlide`) |
| `usePresentationGeneration` | Owns the "Generate" button state and submits the finalized outline to produce slides |

A floating "Generate" button in the bottom-right corner kicks off slide generation. An `OverlayLoader` with a progress message appears while generation runs.

```tsx
// OutlinePage.tsx – tab switch is blocked during streaming
const handleTabChange = (tab: string) => {
  if (streamState.isStreaming) {
    return;
  }
  setActiveTab(tab);
};
```

Sources: [servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx:29-123]()

---

## Stage 3: Slide Editor (`/presentation?id=<id>`)

This is the most complex screen in the application. It renders all slides in a scrollable column, provides a thumbnail side panel for navigation, and houses a chat panel for AI-assisted edits.

### Layout

The editor is a full-height (`h-screen overflow-hidden`) three-column layout:

```text
┌──────────┬──────────────────────────────┬──────────────┐
│  120px   │     Slide canvas (flex-1)    │   370px max  │
│ Side     │     SlideContent list        │   Chat       │
│ Panel    │     (scrollable)             │   Panel      │
│ (thumbs) │                              │              │
└──────────┴──────────────────────────────┴──────────────┘
```

The slide canvas scrolls automatically during streaming: as each new slide arrives, the container scrolls to keep the latest slide in view using `requestAnimationFrame` and `scrollTo({ behavior: "smooth" })`.

Sources: [servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx:299-365]()

### Slide Scaling (`SlideScale` / `PresentationRender`)

Every slide is rendered at a fixed design resolution of **1280 × 720 px** and then scaled down to fit its container using a `ResizeObserver`. The math is straightforward: the scale factor is `(containerWidth / 1280) * 0.98`, capped at 1.0 so slides never enlarge beyond their design size. In presentation mode, both axes are considered and the minimum of `sx` and `sy` is used to fill the viewport without cropping.

```tsx
// PresentationRender.tsx – responsive scale calculation
const scale = useMemo(() => {
  if (fixedSize) return 1;
  if (presentMode) {
    const sx = (w / BASE_WIDTH) * 0.995;
    const sy = (h / BASE_HEIGHT) * 0.995;
    return Math.min(sx, sy);
  }
  const safeWidth = Math.max(0, box.w + 20);
  return Math.min((safeWidth / BASE_WIDTH) * 0.98, 1);
}, [fixedSize, presentMode, box.w, box.h]);
```

The actual slide content is rendered by `V1ContentRender` inside the scaled `div`, which keeps all layout and font sizes pixel-perfect regardless of screen size.

Sources: [servers/nextjs/app/(presentation-generator)/components/PresentationRender.tsx:31-113]()

### Rich Text Editing with Tiptap

Text blocks inside slides use Tiptap, a headless rich-text editor built on ProseMirror. The `TiptapText` component configures three extensions—`StarterKit`, `tiptap-markdown`, and `Underline`—giving every text field support for bold, italic, underline, strikethrough, and inline code formatting. A floating `BubbleMenu` toolbar appears when text is selected.

Changes are flushed to the Redux store on blur, not on every keystroke, which prevents excessive re-renders. The stored format is Markdown, converted back from the editor's internal state via `editor.storage.markdown.getMarkdown()`.

```tsx
// TiptapText.tsx – save Markdown on blur
onBlur: ({ editor }) => {
  const markdown = editor?.storage.markdown.getMarkdown();
  if (onContentChange) {
    onContentChange(markdown);
  }
},
```

Sources: [servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx:32-50]()

### Image and Icon Editing (`EditableLayoutWrapper`)

`EditableLayoutWrapper` is a transparent overlay that wraps each rendered slide and makes images and SVG icons clickable. On mount (with a 400ms delay to wait for lazy-loaded assets), it queries the DOM for all `<img>` and `<svg>` elements that have not yet been processed, maps each one back to its corresponding data path in the slide's JSON structure, and attaches click and hover handlers.

Clicking an image opens an `ImageEditor` modal. Clicking an SVG icon opens an `IconsEditor` modal. Both dispatch Redux actions (`updateSlideImage`, `updateSlideIcon`, `updateImageProperties`) to keep the store in sync.

The path-matching logic uses a multi-strategy URL comparison: exact match, protocol-stripped match, `/app_data/` filename match, and general filename match—falling back through each until a confident pairing is found.

A `MutationObserver` watches the slide's DOM subtree so that images arriving after the initial render (from lazy loading or streaming) are also picked up.

Sources: [servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx:42-372]()

### Auto-Save

While the editor is open and slides are not streaming, changes to `presentationData` in the Redux store trigger a debounced auto-save (default 2000 ms). The `useAutoSave` hook compares the current serialized data against what was last saved and only calls `PresentationGenerationApi.updatePresentationContent` when the content has actually changed. Each save also appends to the undo/redo history via the `addToHistory` action.

Sources: [servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx:13-88]()

### Header Controls

The `PresentationHeader` bar (sticky, always on top) provides:

| Control | Behavior |
|---|---|
| Title (click to edit) | Inline input; commits on Enter or blur, cancels on Escape |
| Theme selector | Loads custom + default themes; applies CSS variables to the slide wrapper |
| Undo / Redo | Powered by a separate `undoRedoSlice` in Redux |
| Regenerate | Clears slide data and history, re-routes to `?stream=true` |
| Present | Switches to presentation mode via `?mode=present&slide=N` |
| Export | Popover with PDF and PPTX options |

Export in a browser context POSTs to `/api/export-presentation` with the format and presentation ID. In the Electron desktop app, it calls `window.electron.exportPresentation` via IPC instead.

```tsx
// PresentationHeader.tsx – dual export path (web vs Electron)
if (window.electron?.exportPresentation) {
  await exportViaIpc("pdf", safePdfTitle);
} else {
  const response = await fetch("/api/export-presentation", {
    method: "POST",
    body: JSON.stringify({ format: "pdf", id: presentation_id, title: safePdfTitle }),
  });
}
```

Sources: [servers/nextjs/app/(presentation-generator)/presentation/components/PresentationHeader.tsx:187-307]()

### AI Chat Panel

A `Chat` component occupies the right column. It calls `PresentationChatApi` with the user's message and the current slide index. The API streams back responses including `ChatStreamTrace` events that identify which slide the AI agent is currently working on. The `PresentationPage` uses those events to:

1. **Follow mode** – automatically scroll the canvas to the slide being edited by the agent.
2. **Glow effect** – apply a visual highlight (`isChatEditing`) to the active slide and a softer highlight (`isChatTargeted`) to other slides touched in the same request.
3. **Clear on finish** – remove all highlights 900 ms after the agent stops sending.

Sources: [servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx:159-249]()

### Presentation Mode

When the user clicks "Present", the page re-renders as `PresentationMode` (no editor chrome, slides fill the viewport). The mode is encoded in the URL query string (`?mode=present`) so it survives a browser refresh. `toggleFullscreen` uses the Fullscreen API.

Sources: [servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx:253-265]()

---

## Stage 4: PDF Export Renderer (`/pdf-maker?id=<id>`)

The `(export)` route group contains a special headless page used only for generating PDFs. The application (or Electron shell) opens this page in a hidden browser window, waits for all slides to render, and then invokes the browser's print function.

`PdfMakerPage` loads the presentation data from the API, applies theme CSS variables and fonts to a wrapper element, then renders every slide using the same `SlideScale` component used in the editor—but with `fixedSize={true}` and `isEditMode={false}` so slides render at exactly 1280 × 720 px with no scaling math and no edit overlays.

A `<style>` block injected via `jsx global` configures print-specific CSS: each slide becomes a separate printed page (`break-after: page`), the page size is set to `1280px 720px`, and all margins are zero.

```tsx
// PdfMakerPage.tsx – print page size matches slide canvas
@page {
  size: 1280px 720px;
  margin: 0;
}
```

If the export is triggered from Electron, an `exportCookie` header is passed and the slide data is fetched from `/api/export-presentation-data/<id>` with that cookie, so authentication is preserved across the headless render context.

Sources: [servers/nextjs/app/(export)/pdf-maker/PdfMakerPage.tsx:20-89](), [servers/nextjs/app/(export)/pdf-maker/PdfMakerPage.tsx:158-211]()

---

## State Management

The frontend uses Redux (with a `presentationGeneration` slice) as the single source of truth for the current presentation. Both the editor canvas and the chat panel read from and write to the same slice, which is what makes features like auto-save, undo/redo, and follow mode work without prop-drilling through deeply nested component trees. Mixpanel events are fired at each major user action (page viewed, export started, title updated, regenerate triggered) to capture usage telemetry.

---

## Summary

The Next.js frontend is organized as a four-stage funnel—dashboard → outline → editor → export—where each URL corresponds to a concrete user action. The slide canvas always renders at 1280 × 720 px and uses `ResizeObserver`-driven scaling to fit any screen. Tiptap provides rich-text editing directly inside slide content; `EditableLayoutWrapper` adds click-to-edit behavior for images and icons through DOM introspection; and a streaming chat panel lets the AI agent modify slides while the user watches in real time. The `(export)` route group keeps the print renderer cleanly separated from the interactive editor, reusing the same `SlideScale` component but without edit overlays or scaling.

Sources: [servers/nextjs/app/(presentation-generator)/components/PresentationRender.tsx:6-116]()
