# WorshipKit Slides — Importing, Editing, and Exporting

A guide to `/slides` — the SPA that turns Word / Pages / PowerPoint
documents into ProPresenter slides, lets a church team edit them in
the browser, and exports them as `.proBundle` (or `.pro`, `.pptx`,
`.key`).

**Public URL:** https://worshipkit.com/slides-editor
**Also served as raw Markdown at:** https://worshipkit.com/slides-editor.md

If you're an AI agent helping a WorshipKit user prepare slides, read
this whole page — it covers the two import paths, how the editor
represents styling, and the export formats.

---

## The two import paths

Every document that ends up in `/slides/editor/:id` came through one
of two paths. Both apply the user's active Design (from
`/slides/designs`) — the same Design decides slide chunking,
reference extraction, and per-run styling on either path.

### 1. Quick Import — one shot from source to editor

Drop a `.docx` / `.pages` / `.pptx` on the Documents dropzone (or
click **Quick Import**). The server:

1. Runs the file through `pp7gen`'s DocumentProcessor with the
   Design's rules payload — the rules engine chunks paragraphs,
   extracts Bible references, splits long passages at sentence
   boundaries, interleaves blank slides, and applies per-run
   styling.
2. Hands the resulting slides to `pp7gen`'s Generator, which emits
   a ProPresenter `.pro` protobuf.
3. Persists it as a `SlideDocument` (id-scoped to the user's
   organization).
4. Redirects the browser to `/slides/editor/:id`.

Use Quick Import when the Design is trusted and the source doc is
well-formatted — no review step, straight to the editable slides.

### 2. Draft Review — human-in-the-loop before slide generation

Drop the same file on the dropzone and pick either **Continue
without AI** (legacy path — the rules engine returns raw slides for
review) or one of the AI prompts. The SPA:

1. Posts to `POST /documents/legacy_draft` (or `ai_draft`) — server
   runs the rules engine but returns the parsed slide list as JSON
   instead of generating a `.pro`.
2. Renders each slide as a thumbnail in `DraftReviewPage`; the user
   can edit body text, add / remove slides, and toggle "blank
   slides between chunks."
3. On **Generate**, the SPA posts the reviewed slides to `POST
   /documents/generate_from_draft` — the server runs the Generator
   and creates the `SlideDocument`.

Use Draft Review when the source is a rough transcript, the design
rules are new, or when the user wants to fix chunking before
generating slides.

### Both paths share

- The active Design's `add_blank_slides` and `split_long_text`
  options.
- Media (images embedded in the source doc) is base64'd into the
  draft round-trip and stored as `SlideDocumentMediaItem` rows.
- The generated `.pro` carries every slide's `type` (from
  `set_slide_type` rules) into the ProPresenter cue so downstream
  style rules and themes can key on it.

---

## The Slides editor (EditorPage)

Open a document at `/slides/editor/:id`. The layout is three panes:

- **Left (thumbnails):** every cue in the presentation, drag-to-
  reorder, right-click menu for duplicate / hide / delete / theme.
- **Center (SlideCanvas):** the selected slide scaled to fit the
  viewport. Click an element to select it; double-click a text
  element to edit inline.
- **Right (EditorPanel):** per-slide + per-element controls (text
  color, font size, alignment, timer, theme override, hot key,
  notes).

### Inline text editing

Double-click a text element. The element becomes a contentEditable
with per-run styling preserved:

- Bold / italic / underline stay visible during edit and round-trip
  back through the RTF on blur.
- Per-run **color** and **font_family** (Rules::StylePass emphasis
  like Black Diamond Fixed gold on a bold+underline word) are
  wrapped in inline-style spans during edit. Typing inside an
  emphasized run inherits its styling; typing outside it inherits
  the surrounding element font.
- Enter inserts a newline. Escape exits without saving. Blur saves.
- A no-op edit (double-click and click away without changing text
  or modifiers) does NOT trigger a save — per-run styling is
  preserved exactly.

### FloatingFormatBar

While editing, a small toolbar floats above the element with B / I
/ U buttons. They wrap the current selection in `<b>` / `<i>` /
`<u>` and round-trip back through `\b` / `\i` / `\ul` toggles in
the RTF.

### Per-slide edits (not text)

The right-hand EditorPanel handles:

- **Slide-level:** background color, notes, hot key, "advance
  after N seconds" timer, workspace theme override.
- **Element-level:** move / resize (drag corners in the canvas),
  text color picker, font size, alignment, delete.
- **Cue metadata** (hot key, timer, notes, theme override) is
  stored server-side in `CueMeta` rows and re-woven into the
  protobuf on export by `DownloadPresentationBuilder`.

Every change goes through `handlePresentationChange`, which
debounces a save at 500ms. The last-saved state is what a re-open
loads.

---

## Exporting a document

Three formats are exposed from `/slides/editor/:id`:

| Format       | Endpoint                                                      | Contents                                                             |
|--------------|---------------------------------------------------------------|----------------------------------------------------------------------|
| `.proBundle` | `GET /api/v2/documents/:id/download?format=probundle`  | Zip of the `.pro` protobuf + `Media/Assets/` — what ProPresenter opens |
| `.pro`       | `GET /api/v2/documents/:id/download?format=pro`        | Just the `.pro` protobuf (no bundled media)                          |
| `.pptx`      | `GET /api/v2/documents/:id/download?format=pptx`       | PowerPoint (best-effort text extraction)                             |
| `.key`       | `GET /api/v2/documents/:id/download?format=key`        | Keynote (best-effort text extraction)                                |

All formats route through `DocumentExportDispatcher`, which:

1. Decodes the stored protobuf (or the in-progress JSON if the SPA
   sends `presentation_json`).
2. Applies workspace themes + per-cue metadata via
   `DownloadPresentationBuilder`.
3. Runs `PresentationHealer.heal!` — UUID normalization, RTF
   Cocoa-shape validation, colortbl / expandedcolortbl slot
   alignment, notes wrapping, opacity / geometry defaults.
4. Hands the final `Rv::Data::Presentation` to the format
   strategy's `write` method.

### Getting the download from the SPA

The editor's overflow menu → **Download** picks the format. The SPA
posts the current in-memory presentation as `presentation_json` so
even unsaved edits ship with the download.

### Automated exports (agents + scripts)

Every `/api/v2/documents/*` endpoint requires a Bearer token in the
`Authorization` header. Two token types work — same as the Billboard
API:

- **Personal Access Token (PAT)** — created by a signed-in user in
  the browser at [/settings/api-tokens](https://worshipkit.com/settings/api-tokens)
  (accessible from the user menu → **Your Profile** → *"Manage API
  tokens →"*). Give the token a descriptive name (e.g. the agent
  it's for), copy the raw string on the reveal panel (this is the
  only time it will be visible), and paste it into your agent's
  config as the `Authorization: Bearer <token>` value. Scope:
  everything the user can do — read + write every document their
  organization owns. Revoke from the same page when you rotate.
  Scripted alternative: `POST /api_tokens { "name": "…" }` returns
  the raw token in the response envelope, but the browser flow is
  the recommended path for humans setting up an agent.
- **Doorkeeper OAuth token** — issued by the standard OAuth2 flow at
  `/oauth/authorize`. Scoped.

**Missing / invalid auth** returns `HTTP 401` with `Content-Type:
application/json`, body `{"error":"Unauthorized"}`, and a
`WWW-Authenticate: Bearer realm="worshipkit"` header — an agent can
detect the auth failure programmatically from any of those signals.
A `HTTP 404` means the document doesn't exist (or was soft-deleted,
or belongs to another organization).

**Common endpoints:**

```
# List every document in the user's organization.
GET /api/v2/documents

# Fetch one document (JSON metadata + presentation_json payload).
GET /api/v2/documents/:id

# Rename it.
PUT /api/v2/documents/:id
    { "name": "Renamed" }

# Download the .proBundle for ProPresenter.
GET /api/v2/documents/:id/download?format=probundle
GET /api/v2/documents/:id/download?format=pro
GET /api/v2/documents/:id/download?format=pptx
GET /api/v2/documents/:id/download?format=key

# Import a source doc (Quick Import path).
POST /api/v2/documents/import
     Content-Type: multipart/form-data
     file=@sermon.docx
     design_id=42        # optional; picks a specific Design

# Draft-review path (returns slides for review before generation).
POST /api/v2/documents/legacy_draft
     Content-Type: multipart/form-data
     file=@sermon.docx
     design_id=42

# Generate a document from an approved draft (SPA + agent both use this).
POST /api/v2/documents/generate_from_draft
     Content-Type: multipart/form-data
     draft={"name":"…","slides":[…]}
     images={"filename.jpg":"<base64>", …}
     design_id=42
     add_blank_slides=true

# Soft-delete a document.
DELETE /api/v2/documents/:id
```

**Concrete example — export the current .proBundle:**

```bash
curl -H 'Authorization: Bearer wk_pat_abc123…' \
     -o slides.proBundle \
     'https://worshipkit.com/api/v2/documents/123/download?format=probundle'
```

**Concrete example — import a Word doc using Design #42, get the new
document id back:**

```bash
curl -H 'Authorization: Bearer wk_pat_abc123…' \
     -F 'file=@sermon.docx' \
     -F 'design_id=42' \
     'https://worshipkit.com/api/v2/documents/import'
```

The response is JSON: `{"id": 456, "name": "sermon", "updated_at": "…", …}`.

---

## Importing a ProPresenter file

`/slides/import` accepts `.pptx`, `.key`, `.pro`, and `.proBundle`.
Non-source formats (`.pro`, `.proBundle`) go through
`::Slides::PresentationImporter` — the presentation lands in
`/slides/editor/:id` without a rules-engine pass, so it opens
exactly as ProPresenter would render it (text, colors, fonts,
per-run styling from the source `.pro` all preserved).

Use this path when a user already has slides they want to edit in
the browser rather than in ProPresenter.

---

## Notes for AI agents

- The Design (from `/slides/designs`) is the authority on how a
  document becomes slides. Point users there first if their import
  chunks or styles look wrong.
- Editor edits change the `.pro` in place — they never re-run the
  rules engine. Re-importing the source doc is the only way to
  re-apply Design changes to an existing document.
- Per-run styling (color, font, bold/italic/underline) survives
  inline edits, but per-run **color** and **font_family** can only
  be set by a Design's Text rule at import time (there's no editor
  UI for a color / font picker on a text sub-selection today —
  users pick element-level colors from EditorPanel).
- ProPresenter's slide-list thumbnail sometimes falls back to the
  element's `text_solid_fill` and paints per-run color runs white,
  even though the same slide plays back correctly. That's a PP
  render cache; not a WK export bug. Suggest closing and reopening
  the presentation in PP if the thumbnail is misleading.

---

## Where this lives

- **Source of truth:** `docs/slides_editor.md` in the WorshipKit
  web repo.
- **Served publicly at:** `/slides-editor` (HTML) and
  `/slides-editor.md` (raw markdown).
- **Discovered by AI agents via:** `/llms.txt` at the site root.
