Add a PRD and three prototype directions (Gallery, Command Bar, Guided) behind the prototype picker harness for the lesson Slide embed flow. Covers the top 10 embeddable slide platforms with per-platform detection, link/embed-code normalization, live preview, and How-to-embed docs links. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
10 KiB
PRD — Slide Embed Picker
Feature area: Course → Lesson → Materials → Slide tab
Status: Draft for review
Author: Design exploration (cloud agent)
Related prototype: index.html — three interaction directions behind the prototype picker
1. Problem
The Slide tab is a single unlabeled URL input with one line of helper text: "You can embed Google Slides or Canva Presentation." (apps/dashboard/src/lib/features/course/components/lesson/slide/slide.svelte).
This creates three problems for the teacher building a lesson:
- It isn't clear what to paste. A raw edit URL (
.../edit#slide=id.p) does not embed. Google Slides needs a published/embedlink; Canva needs?embed; PowerPoint, Pitch, Gamma and SlideShare hand you a full<iframe>embed code, not a link. The field looks identical for all of them and silently fails when the wrong string is pasted. - It isn't clear which platforms are supported. The copy names two (Google Slides, Canva). In reality the same
<iframe>mechanism works for at least ten major tools, but a teacher has no way to know that. - There's no help path. When the embed shows a blank frame or a "sign in" screen, the teacher is stuck — there is nowhere to click for guidance.
The only transform the current code does is append ?embed for www.canva.com URLs. Everything else is passed through verbatim.
2. Goals
- Make embedding a deck obvious and low-error: the teacher always knows what to paste and can see it working before saving.
- Surface the breadth of support with a visual, icon-forward platform picker.
- Give every platform a one-click path to step-by-step docs.
- Normalize input into a valid embed
src(accept a share link or a pasted<iframe>embed code) so a correct render is the default, not the exception. - Preserve the existing data contract: the lesson still persists a single
slideUrl. (Per repo guidance, the persisted column stores the resolved embed URL/data, not presentational markup.)
3. Non-goals
- Uploading raw
.pptx/.keyfiles for hosting (these platforms host the deck; we embed it). - Building a slide editor. We embed decks authored elsewhere.
- Server-side scraping of private decks or bypassing a platform's own auth wall.
- Analytics on embedded decks (several platforms explicitly don't expose this).
4. Top 10 supported platforms
Ranked by prevalence in education/SaaS plus reliability of the public embed path. Each row lists what the teacher pastes and how we turn it into an embed src. All mechanics were verified against each vendor's current help docs (Aug 2026).
| # | Platform | Teacher pastes | We produce | Notes |
|---|---|---|---|---|
| 1 | Google Slides | Publish-to-web link | https://docs.google.com/presentation/d/e/<ID>/embed?start=false&loop=false&delayms=3000 |
File → Share → Publish to web → Embed. We rewrite /pub · /edit · /present → /embed. |
| 2 | Canva | Design view link or embed code | https://www.canva.com/design/<ID>/view?embed |
Share → More → Embed. We append ?embed (current behavior, kept). |
| 3 | Microsoft PowerPoint (OneDrive / Office for the web) | <iframe> embed code |
src extracted from the pasted iframe (onedrive.live.com/embed?...) |
File → Share → Embed in PowerPoint for the web. Personal OneDrive avoids the sign-in wall. |
| 4 | Apple Keynote (iCloud) | iCloud share link | https://www.icloud.com/keynote/<id>?embed=true |
Collaborate → Copy Link (Anyone / View only). We strip #name and append ?embed=true. Collaboration must stay on. |
| 5 | Figma Slides | figma.com/slides/… or /deck/… link |
https://embed.figma.com/slides/<KEY>?embed-host=classroomio |
Embed Kit 2.0: swap www → embed, keep slides/deck, add embed-host. |
| 6 | Prezi | View link or embed code | https://prezi.com/view/<ID>/embed |
Share → Embed. We append /embed to the view link. |
| 7 | Pitch | <iframe> embed code (public link) |
src extracted (pitch.com/embed/<id>) |
Share externally → link → Copy embed code. Disabling the link breaks the embed. |
| 8 | Gamma | Embed code or public deck link | src extracted / gamma.app/embed/<id> |
Share → enable public access → copy iframe. |
| 9 | SlideShare | <iframe> embed code |
src extracted (slideshare.net/slideshow/embed_code/key/<KEY>) |
Public decks only; LinkedIn-owned. |
| 10 | Beautiful.ai | Public link or embed code | src extracted / player link |
Sharing Settings → Public → Get Embed Code (iframe) or oEmbed link. |
Two input shapes. Six platforms give the teacher a link we transform (Google Slides, Canva, Keynote, Figma, Prezi, Beautiful.ai); four hand over a full <iframe> embed code (PowerPoint, Pitch, Gamma, SlideShare). The picker therefore accepts either — if the pasted value contains an <iframe>, we lift its src; otherwise we apply the platform's link transform.
5. Proposed feature — the Slide Embed Picker
Replace the bare input with a small, guided flow:
5.1 Feature list
- Visual platform picker. A gallery of the 10 platforms, each as a branded icon tile, so the teacher sees at a glance what's supported.
- Per-platform tailored input. Selecting a platform reveals the right field for it — a single link input or an embed-code area — with placeholder + one-line "where to find it" microcopy specific to that platform.
- Paste normalization. Accepts a share link or a full
<iframe>embed code and resolves it to a valid embedsrc(extract-src or link-transform per §4). - Live preview. A 16:9 preview renders the resolved embed before saving, with an explicit "Embed ready" confirmation, so failures are caught at author time, not by students.
- Inline validation. If the pasted value doesn't match the chosen platform (e.g. an
/editlink that was never published), show a specific, fixable message ("This looks like an edit link — publish to the web first"). - "How to embed" docs button. Every platform has a deep link to
classroomio.com/docs/guides/embed-slides#<platform>for step-by-step help. - Auto-detect (accelerator). When a teacher pastes first, detect the platform from the URL/host and pre-select it, so an expert can skip the gallery.
- Change / clear. Easy path back to the gallery to switch platforms or remove the deck.
- Accessibility & i18n. Keyboard-navigable tiles, labelled controls, all copy in
en.json(no hardcoded strings), reduced-motion respected. - Backward compatible. An already-saved
slideUrlre-opens in the matching platform's editor state; the stored value stays a single embed URL.
5.2 Interaction directions (see prototype)
The prototype explores three genuinely different ways to structure the same flow. They are meant to be compared, not combined:
| Direction | Interaction model | Best when | Cost |
|---|---|---|---|
| Gallery | Browse a grid of platform tiles → focus into a per-platform input + preview | Discovery matters; teacher may not know what's supported | One extra click before typing |
| Command Bar | Paste first into one smart field → platform auto-detects → preview + contextual docs | Fast, repeat authors who already have the link | Less discoverable; leans on detection |
| Guided | Searchable platform rail + step-by-step instructions beside the input | First-timers who need hand-holding to get a valid link | Densest; most vertical space |
6. UX flow (happy path)
- Teacher opens the Slide tab (empty).
- Picks Google Slides from the gallery (or pastes a link and it auto-selects).
- Field shows the Google-specific placeholder + "File → Share → Publish to web → Embed."
- Teacher pastes the published link. We rewrite it to the
/embedform. - Live preview renders; a green Embed ready badge appears.
- Autosave persists the resolved
slideUrl(existing save behavior). - In view mode, students see the embedded deck (existing
<iframe>render).
Unhappy path: pasted an unpublished /edit link → inline message + How to embed button → docs.
7. Data & implementation notes
- No schema change. Continue to persist
slideUrl(resolved embedsrc). Do not store the raw<iframe>markup in the content column — extract and store only thesrc(repo rule: persisted columns store data, not presentation). - Where the logic lives. Platform configs (id, brand, input kind, placeholder, docs link,
test,resolve) belong in a pure util, e.g.apps/dashboard/src/lib/features/course/utils/slide-embed.ts, so both detection and transform are unit-testable and reused by the view-modegetUrl()normalizer that today only special-cases Canva. - Docs. Add
apps/docs/content/docs/guides/embed-slides.mdxwith a section per platform and stable#<platform>anchors matching thedocslinks. - Copy. New keys under
course.navItem.lessons.materials.tabs.slide.*inen.json; translate to the other locales before commit. - Component. Keep the component thin; put resolve/validate logic in the util and (if any request is added) an API class — no business logic in the
.sveltefile.
8. Success metrics
- ↓ rate of saved
slideUrlvalues that render a blank/invalid frame (measured by view-mode load failures). - ↑ share of lessons that include a working embedded deck.
- ↓ support questions tagged "slides won't embed".
- Time-to-first-working-embed for a new teacher.
9. Open questions
- Do we allow a raw pasted
<iframe>for any host (generic passthrough), or only the 10 allow-listed platforms (safer, sanitized)? - Should the preview load the real remote iframe at author time, or a lightweight resolved-URL confirmation (avoids third-party auth walls during editing)?
- Per-platform embed options (autoplay, loop, start slide for Google Slides) — expose now or defer?
- Which direction from §5.2 do we ship? (This is the decision the prototype picker exists to make.)