Draft slide-deck & presenter-mode feature plan

- docs/SLIDE_DECK_FEATURE.md: phased spec for Slide Designer (16:9
  canvas + speaker notes), Deck Builder (embedded ordered slides,
  slide-sorter view, deck theme), and Presenter Mode (single-window
  v1, dual-window deferred to Phase E). Phases A-D with ship gates.
- Select tool icon changed to a rotated arrowhead so it reads as a
  cursor, not a play button (distinct from the planned Present icon).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.7 committed 2026-05-21 14:30:59 -04:00
1 parent c872b5210d
commit 06c5ab6768
2 files changed
+437 -1

No files matched your search

+436
View File
@@ -0,0 +1,436 @@
# Slide Decks & Presenter Mode
## Goals
Add a presentation system to Artstr Studio: design individual **slides**,
assemble them into an ordered **deck**, and run a **presenter mode** to show
the deck — all Nostr-native, forkable, and driven entirely off JSON data.
### Product goals
1. A **Slide Designer** — a 16:9 design surface (a flat canvas, like Custom
Art) with an added **speaker-notes** field. A slide is a first-class
Nostr design: publishable, forkable, loadable.
2. A **Deck Builder** — a container that holds an ordered, variable-length
list of slides. The deck embeds its slides' full data inline, so it is a
single self-contained file. Slides can be imported from published
designs and extracted back out as standalone designs.
3. A **deck-level theme** that overrides per-slide styling (fonts,
background) across the whole deck — with a per-slide opt-out so an
individual slide can keep its own look.
4. A **Presenter Mode** that plays the deck full-screen with speaker notes
and slide navigation.
### Non-goals (v1)
- **Dual-window presenter view** (audience window + a separate presenter
window with notes on a second screen). This is the real presenting
experience and is the explicit Phase E follow-up — v1 is single-window.
- Slide **transitions / animations / builds** (step-by-step reveals).
- A presenter **timer / rehearsal stats / laser pointer**. Phase E+.
- **Rich-text** speaker notes — plain text for v1.
- Importing PowerPoint / Keynote / PDF decks.
- **Exporting** a deck to PDF or slideshow video. A natural future ask, but
not v1 — Presenter Mode is the v1 way to show a deck. (Individual slides
still export as images like any Custom Art design.)
- A per-property theme matrix — v1 theme overrides a small fixed set
(font family, background); per-property locking is future polish.
---
## Why this fits the repo
This is the **disc pattern generalized**. A disc-label *sheet*
(`templateMode: 'disc'`) embeds the disc designs assigned to its slots and
is self-contained for printing; disc *designs* (`appMode: 'designer'`) are
also independently publishable and importable. A deck/slide pair works the
same way:
- **Slide** ⇄ disc design — a standalone, publishable design.
- **Deck** ⇄ disc-label sheet — a Template-tab container that embeds the
designs it holds.
Everything a deck needs already exists in some form:
- The **canvas editor** edits a slide unchanged — a slide is mechanically a
Custom Art canvas locked to 16:9. Layers, text, shapes, the pen tool,
undo/redo, fit-to-view all apply for free.
- The **persist/load-on-switch** pattern (`persistCurrentDiscDesign` /
`loadCurrentDiscDesignLayers`) is exactly how editing one slide inside a
deck will work — swap the editor's `state.layers` for the active slide,
write back on switch.
- The **import-a-design-into-a-slot** flow (disc design → disc slot) is the
model for "add a published design as a slide."
- **Publish / fork / community browser / Lightning** all reuse as-is.
The genuinely new surfaces are the **slide sorter** (the Deck Builder view)
and the **presenter runtime**.
### Current-state facts the plan is built on
- `appMode`: `'template' | 'designer'`. `templateMode`:
`'cover' | 'disc' | 'jewel' | 'customart'`.
- The **Designer tab** is shown when `appMode === 'designer'` *or*
`templateMode === 'customart'`. The **Template tab** otherwise.
- Each tab has a Layout panel: `layoutFieldset` (Template — Case cover /
Disc labels) and `designerLayoutFieldset` (Designer — Custom Art / Disc
Design). `applyLayout()` maps a layout choice to the right
`appMode`/`templateMode` and crosses app modes when needed.
- Custom Art canvas state: `state.customArt = { width, height, background,
preset }`, rendered on `#sheet`; payload via `customArtPayload()`.
- Designs publish as kind-30078 parameterized replaceable events tagged
`casewrap-<mode>`, with the `d`-tag mode-prefixed.
- Save discriminator (`templateMode` in payloads): `cover`, `disc`,
`jewel`, `customart`, `disc-design`.
- All image references are remote URLs — never embedded binary. A deck is
therefore N slides of *layer JSON*, not megabytes of image data.
---
## Framing within Template / Designer
Per the decision to keep the existing tab framing (even though a deck is a
*sequence*, not a print artboard — the distinction stays intentionally
muddy for now):
- **Slide Designer** — a new layout option in the **Designer tab** Layout
panel, beside Custom Art and Disc Design. Runs as
`appMode: 'template', templateMode: 'slide'`. The Designer-tab visibility
test extends to `… || templateMode === 'slide'`.
- **Deck Builder** — a new layout option in the **Template tab** Layout
panel, beside Case cover and Disc labels. Runs as
`appMode: 'template', templateMode: 'deck'`.
This mirrors disc design (Designer) vs disc-label sheet (Template): the
standalone authored thing lives on the Designer side, the container that
holds many of them lives on the Template side.
Two new `templateMode` values — `slide` and `deck` — are added to
`applyLayout()`, `WORKFLOW_ORDER`, the layout panels, the body-class /
visibility logic, and the save discriminator set.
---
## Data model
### Slide design (standalone — `templateMode: 'slide'`)
```js
{
version: SCHEMA_VERSION,
templateMode: 'slide',
templateType: 'slide',
meta: { … }, // title / category / language, as usual
slide: {
width: 1920, height: 1080, // 16:9, fixed aspect
background: '#ffffff',
notes: '' // plain-text speaker notes
},
layers: [ … ] // standard layer array
}
```
A slide is a Custom Art canvas with a `slide` object instead of `customArt`,
the addition being `notes`. Rendered on `#sheet`. The aspect is locked 16:9
(width/height adjustable for resolution, but the ratio holds).
### Deck (`templateMode: 'deck'`)
```js
{
version: SCHEMA_VERSION,
templateMode: 'deck',
templateType: 'deck',
meta: { … }, // deck title / category / language
deck: {
theme: {
fontFamily: '', // '' = no override
background: '', // '' = no override (color or image URL)
// small fixed set for v1; extensible
},
slides: [
{
name: 'Intro', // optional label shown in the sorter
slide: { width, height, background, notes },
layers: [ … ],
ignoreDeckTheme: false // true = this slide keeps its own look
},
… // ordered, variable length
]
}
}
```
- The deck **embeds each slide's full data inline** — it is one
self-contained file. Presenter mode and previews never depend on N
external events resolving.
- A slide object inside `deck.slides[]` is the same shape as a standalone
slide design's `{ slide, layers }`, plus an optional `name` and
`ignoreDeckTheme`. Embedded slides carry **no `meta`** of their own — the
deck's `meta` covers the whole deck.
- **Import** copies a published design's `{ slide?/customArt, layers }` into
a new `deck.slides[]` entry (see surface #2 for cross-aspect handling).
**Extract** lifts one entry back out into a standalone slide design for
separate publishing.
### Nostr
- Slide: kind-30078, tag `casewrap-slide`, `d`-tag `slide:<title|id>`.
- Deck: kind-30078, tag `casewrap-deck`, `d`-tag `deck:<title|id>`.
- Both are forkable and editable-in-place via the existing NIP-33 flow.
- Schema version bumps; `slide` / `deck` / `notes` / `ignoreDeckTheme` are
additive — old clients ignore unknown payloads.
---
## The surfaces
### 1. Slide editor
The existing canvas editor, opened on a 16:9 slide. The Designer-tab Layout
panel's "Slide Designer" option gives a Canvas-style panel (resolution
presets — 1920×1080, 1280×720 — background) **plus a "Speaker notes"
textarea**. Notes save into `slide.notes`. Nothing else changes — it is the
Custom Art editor with a locked aspect and one extra field.
When a slide is opened **from within a deck**, the panel shows a small
notice that the deck theme will restyle fonts/background in the deck and
presenter views — so the un-themed editor view isn't a surprise.
### 2. Deck Builder (the slide sorter)
A new view for `templateMode === 'deck'`. The canvas area shows a
**reorderable grid of slide thumbnails**; the sidebar holds deck title and
theme controls.
- **Add slide** — new blank slide · import from a published design · import
from the community browser. Imports land on a 16:9 slide: a 16:9 source
(slide / Custom Art) fills it; any other aspect (cover, square, …) is
scaled to fit and centered, letterboxed.
- **Reorder** — drag thumbnails.
- **Duplicate / Delete** a slide.
- **Edit a slide** — clicking a thumbnail opens it in the canvas editor
(surface #1). The deck tracks `state.deck.editingIndex`; entering edit
loads that slide into editor state, and a "Back to deck" control persists
it back — the `persistCurrentDiscDesign`/`loadCurrentDiscDesignLayers`
pattern. The active slide is persisted before *any* save, publish,
reorder, or present.
- **Thumbnails** are mini-renders via the existing slide preview-DOM
renderer + `fitMiniPreview`. Only the edited slide's thumbnail re-renders
on write-back — not the whole grid.
- **Canvas chrome** — in the sorter view the bottom zoom bar and the right
tool-options panel are hidden (nothing to zoom or configure); the left
tool palette shows just the **Present** (▶) action button (see surface
#3). Full chrome returns when a slide is opened in the canvas editor.
- **Undo** uses the app's single history stack: sorter operations
(add / delete / reorder) and slide edits are ordinary entries; undoing an
entry made in the other view switches to that view first.
- The sorter is its own view; a Keynote-style always-visible filmstrip
alongside the canvas is deferred polish.
### 3. Presenter Mode (v1 — single window)
**Entry points.** Presenter Mode launches from a **Present** button — a play
glyph (`▶`) — used consistently in two places:
- **In the editor** — a button in the left **tool palette**, in the slot
where the drawing tools normally sit. It is shown whenever a deck is
active (the Deck Builder sorter, and while editing one of the deck's
slides), so you can jump straight to presenting. It is an *action*, not a
tool mode.
- **In the template browser** — a **Start Presentation** button on a deck's
full preview page (`openDesignPreview`), beside Use/Fork · Share · Save
JSON, using the same `▶` icon. This lets anyone present a published deck
straight from the browser without forking it first.
Both entry points run the same full-screen runtime (Fullscreen API):
- The current slide is drawn with the **existing slide / Custom-Art
preview-DOM renderer**, scaled to fill the viewport (letterboxed to 16:9)
— no new slide renderer is needed.
- **Speaker notes** for the current slide in a side/bottom panel, with a
toggle to hide them (e.g. when projecting this same window).
- A **next-slide thumbnail** and a **slide counter** (`3 / 12`).
- Navigation: on-screen prev/next, arrow keys / space, `Esc` to exit.
Presenter mode **owns the keyboard** while active — the editor's tool and
layer shortcuts (V/P/B, arrows, Delete, …) are suppressed until it exits.
- The deck theme is composed in (see below) — presenter mode shows the
fully themed slide.
v1 is single-window: good for rehearsal, review, and casual presenting.
The true projector experience (audience window + separate presenter window
with notes) is **Phase E**.
### 4. Deck in the community browser
A published deck is a normal kind-30078 event, so it appears in the feed
like any design — which means it needs preview support:
- **Feed-card thumbnail** — the first slide's mini-render, with a
slide-count badge (e.g. `12 slides`) so a deck reads as a deck at a
glance.
- **Publish-confirm preview** — likewise the first slide.
- **Full preview page** (`openDesignPreview`) — carries the **Start
Presentation** button (surface #3) alongside Use/Fork · Share · Save JSON.
These hook into the existing per-mode preview renderers and feed-card
builder; `deck` joins `MODE_LABELS` and the community mode filters.
---
## Theme composition
The deck theme is applied at **render time**, non-destructively — the slide
JSON is never mutated.
- `deck.theme` carries a small fixed set for v1: `fontFamily`, `background`.
- When a slide is rendered **in a deck context** (deck thumbnails, presenter
mode), each themed property resolves as:
```
effective = slide.ignoreDeckTheme ? slideValue
: (deck.theme.<prop> || slideValue)
```
e.g. a text layer's font = the deck theme font unless the slide opts out
or the theme leaves it blank.
- The **slide editor shows the raw slide** (no theme) — the theme only
exists in the deck. This keeps the editor simple and the slide reusable
across decks with different themes. When the slide is opened from a deck,
the editor shows a notice that the theme will restyle it elsewhere
(surface #1) so the editor/presenter mismatch isn't surprising.
- `ignoreDeckTheme` is a per-slide boolean for v1 (whole-slide opt-out).
Per-property locking is a future refinement.
Because it is pure JSON composition, the same logic serves deck thumbnails,
presenter mode, and any future export.
---
## Phased delivery
### Phase A — Slide format
- `templateMode: 'slide'`; 16:9 canvas; `slide` object with `notes`.
- Slide Designer option in the Designer-tab Layout panel.
- Canvas panel with resolution presets + the Speaker notes textarea.
- Save / load / publish / fork a standalone slide design (`casewrap-slide`).
**Ship gate:** create a 16:9 slide with notes, publish it, load it back —
notes round-trip; it shows in the community browser.
> Note: a standalone slide is thin value on its own — a 16:9 graphic whose
> notes don't pay off until Presenter Mode (Phase D). Phase A is
> groundwork; **A + B together** are the minimum genuinely-useful unit.
### Phase B — Deck Builder
- `templateMode: 'deck'`; the `deck` data model (embedded ordered slides).
- Deck Builder option in the Template-tab Layout panel.
- The slide-sorter view: add (blank / import, with cross-aspect
letterboxing) · reorder · duplicate · delete; open a slide in the canvas
editor and write back on switch; per-slide thumbnails.
- Sorter-view canvas chrome (hidden zoom bar / options panel).
- Save / load / publish / fork a deck (`casewrap-deck`), self-contained.
- Deck feed-card thumbnail (first slide + slide-count badge) and
publish-confirm preview; `deck` added to `MODE_LABELS` / mode filters.
**Ship gate:** build a 5-slide deck, reorder it, edit slide 3, save the
deck, reload it — order and edits persist; the deck shows in the feed with
its slide count.
### Phase C — Deck theme
- `deck.theme` (font family, background) + theme controls in the sidebar.
- Per-slide `ignoreDeckTheme` toggle.
- Render-time theme composition for deck thumbnails.
**Ship gate:** set a deck font; all slides restyle in the thumbnails;
toggle one slide's opt-out and it reverts to its own font.
### Phase D — Presenter Mode (single-window)
- Full-screen runtime: current slide + notes + next thumbnail + counter.
- Keyboard / on-screen navigation; notes toggle; `Esc` to exit.
- Theme composed in.
- Two `▶` Present entry points: a tool-palette button in the Deck Builder
editor, and a "Start Presentation" button on a deck's template-browser
preview page.
**Ship gate:** present a deck start to finish with the keyboard from both
entry points; notes track the current slide.
### Phase E — Dual-window presenter (future)
- Popped-out audience window (slide only) synced to a presenter window
(current + next slide, notes, timer). Not built in this pass.
---
## Risks & tradeoffs
| Risk | Mitigation |
|---|---|
| A deck embeds N slides → large JSON / large Nostr event; relays may cap event size | Images are URLs, not binary, so size is bounded to layer JSON. Warn when the deck payload exceeds ~100 KB or ~25 slides. Escape hatch if this proves limiting: a future mode where the deck references slide `naddr`s instead of embedding them (Phase E). |
| Slide-edit ⇄ deck round-trip can drop edits if write-back is missed | Reuse the proven `persist…/load…` pattern; persist on every view switch and before publish/save/reorder/present. |
| Undo crossing the sorter ⇄ slide-editor boundary (one shared history stack) | Sorter ops and slide edits are ordinary stack entries; undoing an entry made in the other view switches to that view first, so undo always lands somewhere coherent. |
| Template/Designer framing is conceptually muddy (a deck is a sequence, not a print artboard) | Accepted explicitly; documented. Deck = Template-tab by analogy to the disc sheet; revisit if the IA is ever reworked. |
| Theme composition must not corrupt the slide's own data | Theme is applied purely at render time; slide JSON is never mutated; opt-out is a slide flag. |
| Presenter fullscreen behaviour varies across browsers | Use the standard Fullscreen API with a graceful non-fullscreen fallback. |
| The slide sorter is a brand-new editor surface | Isolated as Phase B with its own ship gate; the slide *editor* itself is the existing canvas, unchanged. |
| Old clients loading a `deck`/`slide` payload | Additive schema; unknown `templateMode` falls back gracefully (the community browser already filters by mode). |
---
## Open questions
1. **Slide default resolution** — DECIDED: 1920×1080 default (matches the
existing Custom Art 16:9 preset), 1280×720 offered as an alternate
preset. Aspect locked 16:9.
2. **Presenter scope** — DECIDED: start single-window (Phase D); dual-window
presenter view is Phase E.
2a. **Presenter entry points** — DECIDED: a `▶` Present button in the Deck
Builder's tool palette, and a matching "Start Presentation" button (same
icon) on a deck's preview page in the template browser.
3. **Slide editing** — DECIDED: the existing canvas editor, unchanged — a
slide is just a 16:9 canvas.
4. **Deck theme** — DECIDED: a deck-level theme overrides per-slide styling
(fonts, background); each slide has an opt-out (`ignoreDeckTheme`) so it
can keep its own look. Render-time composition, JSON-driven.
5. **Speaker notes** — plain text for v1; rich text is future.
6. **Deck Builder canvas UI** — sorter-grid view that opens slides in the
canvas editor for v1; an always-on filmstrip is deferred polish.
7. **Schema bump** — increment `schemaVersion` when Phase A lands; `slide`,
`deck`, `notes`, `ignoreDeckTheme`, slide `name` are additive.
8. **Importing a non-16:9 design as a slide** — DECIDED: scale-to-fit,
centered and letterboxed on the 16:9 slide; no cropping.
9. **Deck in the community feed** — DECIDED: feed-card and publish-confirm
previews show the first slide, with a slide-count badge on the card.
10. **Deck → PDF / video export** — DECIDED: non-goal for v1; Presenter
Mode is how you show a deck. A natural future phase.
11. **Deck size limit** — soft warning past ~100 KB / ~25 slides; hard
architectural fallback (reference slides by `naddr`) deferred to Phase E
if it proves necessary.
---
## Acceptance summary
### Phase A
- [ ] Slide Designer is a Designer-tab layout; canvas is 16:9.
- [ ] Speaker-notes field saves into `slide.notes` and round-trips.
- [ ] A slide publishes/loads/forks as a `casewrap-slide` design.
### Phase B
- [ ] Deck Builder is a Template-tab layout with a working slide sorter.
- [ ] Add / reorder / duplicate / delete slides; import a published design
(a non-16:9 import lands letterboxed on the slide).
- [ ] Editing a slide writes back into the deck; deck saves self-contained.
- [ ] A published deck shows in the community feed with a slide-count badge.
### Phase C
- [ ] A deck theme restyles every slide in the deck.
- [ ] A slide's `ignoreDeckTheme` toggle reverts it to its own styling.
### Phase D
- [ ] Presenter mode plays a deck full-screen with notes + navigation.
- [ ] Notes follow the current slide; the theme is applied.
- [ ] Presenter mode owns the keyboard — editor shortcuts don't fire.
- [ ] A `▶` Present button launches it from the Deck Builder tool palette.
- [ ] A "Start Presentation" button launches it from a deck's template-
browser preview page.
+1 -1
View File
@@ -2908,7 +2908,7 @@
</div>
<div class="toolPalette" id="toolPalette">
<button type="button" class="toolPaletteBtn active" id="tool-select" data-tool="select" title="Select tool (V)" aria-label="Select tool">▸</button>
<button type="button" class="toolPaletteBtn active" id="tool-select" data-tool="select" title="Select tool (V)" aria-label="Select tool"><span style="display:inline-block;transform:rotate(-135deg)">➤</span></button>
<button type="button" class="toolPaletteBtn" id="tool-hand" data-tool="hand" title="Hand / pan tool (H)" aria-label="Hand tool">✋</button>
<div class="toolPaletteDivider" aria-hidden="true"></div>
<button type="button" class="toolPaletteBtn" id="tool-pen" data-tool="pen" title="Pen tool (P)" aria-label="Pen tool">✒</button>