# Presentation Editor Stage 1 Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Deliver a usable presentation editor with separate «Презентация» and «Интерактив» tabs, a selectable audience-screen mode, and controlled aggregate statistics during a live question.

**Architecture:** Keep the existing `slides` array, material endpoints, and live-session snapshot. Extend validated slide data and the shared renderer, reorganize the current editor UI around two tabs, and add one per-question live visibility state plus an aggregate-only projector response. Fullscreen is controlled locally by the projector page; it does not require a database field.

**Tech Stack:** Node.js 22+, native `node:sqlite`, vanilla HTML/CSS/JavaScript, Node built-in test runner; no new runtime dependency.

**Spec:** `docs/superpowers/specs/2026-10-21-presentation-editor-stage-1-design.md`

## Global Constraints

- Existing material types and HTTP material routes remain valid; old rows are not rewritten in bulk.
- Imported PPTX/PDF still uses static slide images in stage 1; editable PPTX objects belong to stage 2, and video/animation to stage 3.
- The projector must receive no correct-answer keys, presenter notes, participant names, or individual answers in its statistics payload.
- Rating and word-cloud publication remain independent of the new statistics toggle.
- The directory currently has no `.git`; omit commit steps until it becomes a Git repository. Keep each task testable and reviewable independently.

## Review Focus

- An old material with no new fields opens, saves, and projects unchanged (Tasks 1 and 2 regression checks).
- A user cannot inject an HTML element through slide text or statistical labels (Tasks 1 and 5 browser check).
- One host cannot change another host's statistics visibility (Task 4 API test).
- A closed question with zero answers produces zero counts, not a broken chart (Task 4 API test).
- A denied fullscreen request leaves the projector usable in a window (Task 5 browser check).

---

### Task 1: Validated slide properties and shared rendering

**Files:**
- Modify: `apps/api/src/material-store.mjs`
- Modify: `apps/web/src/slide-elements.js`
- Modify: `apps/web/src/styles/slide-elements.css`
- Test: `apps/api/tests/materials.test.mjs`

**Interfaces:**
- Consumes: existing `validateMaterial(input)` and `window.renderSlideElements(slide, options)`.
- Produces: optional material `aspectRatio: '16:9' | '4:3'` (default `16:9`); optional own-slide `background: '#rrggbb'`; optional shape `shape: 'rectangle' | 'ellipse'`; optional text `italic: boolean`; optional element `id: string` (UUID format, generated by `crypto.randomUUID()` for new elements, preserved if valid). Old data retains legacy rendering.

- [ ] **Step 1: Add failing tests** in `materials.test.mjs`: accepted new properties survive create/get/update; invalid ratio, color, shape, ID, geometry, and another owner's image return 400/403; old slide without these properties remains valid.
- [ ] **Step 2: Run** `npm test --workspace @aag/api`; confirm the new assertions fail for missing validation/preservation.
- [ ] **Step 3: Implement validation** in `validateMaterial(input)` and update `renderSlideElements(slide, options)` to paint the validated properties without inserting user text as HTML.
- [ ] **Step 4: Run** `npm test --workspace @aag/api`; expect all API tests to pass. Open an old and a new material in the browser and verify backgrounds, aspect ratio, shape, and italic text render in editor and projector.

### Task 2: Presentation workspace and slide tools

**Files:**
- Modify: `apps/web/editor.html`
- Modify: `apps/web/src/editor.js`
- Create: `apps/web/src/editor-slide-tools.js`
- Modify: `apps/web/src/styles/editor.css`
- Test: `apps/web/tests/editor-slide-tools.test.mjs`

**Interfaces:**
- Consumes: Task 1 slide properties and renderer.
- Produces: `window.editorSlideTools` with pure `duplicateSlide(slide)`, `moveElement(elements, index, direction)` where `direction` is `'forward' | 'backward'`, and `createElement(type, assetId?)` where `type` is `'text' | 'shape' | 'image'`; editor binds these to toolbar commands and its existing `changed()/save()/undoStack` flow.

- [ ] **Step 1: Add failing tests** for duplicate without shared element IDs, moving a selected element exactly one layer, and default element bounds for text/shape/image.
- [ ] **Step 2: Run** `npm test --workspace @aag/web`; confirm only these new tests fail.
- [ ] **Step 3: Build the «Презентация» workspace** in `editor.html`/`editor.css`: toolbar groups, slide thumbnails, 16:9 or 4:3 canvas, selected-element inspector, material title and save status. Move current slide controls into the matching toolbar groups; keep imported-slide warning visible.
- [ ] **Step 4: Wire operations** in `editor.js`: add/duplicate/delete/reorder slide, add/select/move/resize/duplicate/delete element, background, shape type, text size/bold/italic/alignment/color, layer order, slide title and note, undo/redo and autosave. Disable unsupported tools for imported and question slides. Preserve the existing version-conflict block.
- [ ] **Step 5: Run** `npm test --workspace @aag/web`; expect all web tests to pass. In browser, edit a new slide, reload it, and check the same appearance in editor, host, and projector. Verify keyboard buttons can reorder slides.

### Task 3: Interactive tab and per-question default

**Files:**
- Modify: `apps/web/editor.html`
- Modify: `apps/web/src/editor.js`
- Modify: `apps/web/src/styles/editor.css`
- Modify: `apps/api/src/material-store.mjs`
- Test: `apps/api/tests/materials.test.mjs`

**Interfaces:**
- Consumes: Task 2 editor state and existing ordered `material.slides`.
- Produces: optional question `showStatistics: boolean`, default `false`; tab-switch and question-position controls modify the same `material.slides`, never a second list.

- [ ] **Step 1: Add failing tests**: `showStatistics: true` survives material save; omitted value acts as false; string/null values are rejected; non-question slides cannot use the field.
- [ ] **Step 2: Run** `npm test --workspace @aag/api`; confirm new tests fail.
- [ ] **Step 3: Add server validation** and build the «Интерактив» tab with question cards, existing answer/time/score fields, `showStatistics` checkbox, and insertion/move-after-slide controls. Tab switching must not mutate the material or trigger autosave.
- [ ] **Step 4: Run** `npm test --workspace @aag/api` and `npm test --workspace @aag/web`; expect pass. In browser, open a mixed old material, add all supported question types, change a question's position, save/reload, and verify show order is unchanged except for intended moves.

### Task 4: Live statistics state and safe aggregates

**Files:**
- Modify: `apps/api/src/live-store.mjs`
- Modify: `apps/api/src/server.mjs`
- Test: `apps/api/tests/live.test.mjs`

**Interfaces:**
- Consumes: Task 3 `showStatistics` in the material snapshot.
- Produces: `liveStore.setStatisticsVisibility(eventId, hostId, show: boolean)`; `POST /api/live/:id/statistics` body `{show: boolean}`; host response `statisticsVisible`; projector response `statistics: null | { total: number, counts: number[] }`. Counts follow displayed option order; for scale, index 0 represents value 1; free text and word cloud return `counts: []`.

- [ ] **Step 1: Add failing integration tests**: default hidden; host can toggle current question after opening it, including before answer closure; nonboolean input and other host rejected; projector hides aggregates while open or disabled; after close it returns exact counts, including zero-answer case and multi-quiz selection counts; advancing clears the visible statistics. Assert JSON contains no participant names, notes, correctIndex or raw answers.
- [ ] **Step 2: Run** `npm test --workspace @aag/api`; confirm new tests fail.
- [ ] **Step 3: Add per-question visibility** to `live_questions` with a safe migration and initial value from the material snapshot; implement `setStatisticsVisibility()` and an aggregate SQL query bounded by the current event/question. Set previous question's visibility to false when moving away from it. Add authenticated, CSRF-protected route to `server.mjs`.
- [ ] **Step 4: Run** `npm test --workspace @aag/api`; expect all API tests to pass, including existing rating, cloud moderation, and reports checks.

### Task 5: Launch choice, projector fullscreen, and statistics display

**Files:**
- Modify: `apps/web/host.html`
- Modify: `apps/web/src/host.js`
- Modify: `apps/web/projector.html`
- Modify: `apps/web/src/projector.js`
- Modify: `apps/web/src/styles/live.css`
- Modify: `apps/web/src/styles/projector.css`

**Interfaces:**
- Consumes: Task 4 `statisticsVisible` and aggregate projector payload.
- Produces: launch choice `window | fullscreen` passed to projector as a URL setting; local projector `requestFullscreen()` and `exitFullscreen()` controls; host button calling `POST /api/live/:id/statistics`.

- [ ] **Step 1: Add launch choice** to `host.html`: «В окне» and «На весь экран». After session starts, open the separate projector view with the chosen setting; fullscreen choice shows a prominent user-click command on that view.
- [ ] **Step 2: Add fullscreen controls** in `projector.js`: enter/exit without ending the session, update button from `fullscreenchange`, and show a readable fallback when fullscreen is denied. Never depend on automatic fullscreen after page load.
- [ ] **Step 3: Add statistics controls and rendering**: host sees aggregate counts irrespective of projector visibility; host toggles the current opened question before or after closure; projector renders only `statistics` from Task 4 beside the question, with text-node labels and bars after closure; hide the panel on another slide. Keep rating and word cloud visually separate.
- [ ] **Step 4: Run** `npm test`; expect all tests to pass. With the local server, conduct a session in window mode and fullscreen mode, close a question with responses, toggle statistics, navigate away and back, exit fullscreen, and deny fullscreen once to verify the fallback.

### Task 6: Full acceptance and documentation

**Files:**
- Modify: `README.md`

**Interfaces:** No new API; this task validates the integrated workflow.

- [ ] **Step 1: Run** `npm test`; expect all suites to pass.
- [ ] **Step 2: Run an end-to-end manual check** with an old material and a new material: save/reload, mixed slide order, join by phone, answers, hidden/shown statistics, QR, projector modes, rating, cloud, reports, and a second host denied access to the first host's session.
- [ ] **Step 3: Update operator instructions** with the two tabs, projector mode selector, browser fullscreen button, statistics toggle, and current stage-1 PPTX limitation.
- [ ] **Step 4: Review changed files and record test results and any remaining stage-2/3 work.** Do not claim PPTX object editing, video, or animations are implemented by this stage.
