diff --git a/ROADMAP.md b/ROADMAP.md index b653af403..09e986b84 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -149,6 +149,7 @@ app's JSON control channel (`deckcraft --control PORT`). | Date | What landed | |---|---| +| 2026-11-02 | Deleting slides removes their IDs from every custom show; the cleanup is undoable and survives native save/reopen | | 2026-11-02 | File › About replaces the unused Account page, with version, project links, contributor/model credits and appearance preferences | | 2026-11-02 | Tiled picture fills in groups: a tile (`a:blipFill` with `a:tile`) keeps its size, offset and anchor in slide points whatever the group's child-coordinate scale, uniform or stretched unevenly, nested or flipped (also a group's own tile fill passed to its members, and a picture shape's own tile, with its shadow and reflection), and tiles with `rotWithShape="0"` keep their size in points at any zoom, as PowerPoint draws them (measured on its own PNG export of a synthetic probe deck: every grouped case within ~3/255 of the ungrouped control's own difference from PowerPoint, which is ~13/255 from resampling). Before, a group whose child space was ~1800 times smaller than its box showed one flat texel of the texture (a Venn diagram with paper-textured circles and a hatched intersection) | | 2026-10-31 | No Fill shape interiors allow selection and editing of underlying objects, even when a quick style remains; hit-testing uses the renderer’s fill resolution. Outlines, rotation, text targets and actual fills keep their hit behaviour (#1621) | diff --git a/crates/engine/src/cmd/slide.rs b/crates/engine/src/cmd/slide.rs index e150a2659..198f1bc6c 100644 --- a/crates/engine/src/cmd/slide.rs +++ b/crates/engine/src/cmd/slide.rs @@ -267,6 +267,9 @@ fn delete(s: &mut Session, p: &Value) -> Result { for sec in &mut doc.sections { sec.slides.retain(|x| !ids.contains(x)); } + for show in &mut doc.custom_shows { + show.slides.retain(|x| !ids.contains(x)); + } sel.slide = first.min(doc.slides.len().saturating_sub(1)); sel.slides = doc.slides.get(sel.slide).map(|x| vec![x.id]).unwrap_or_default(); sel.shapes.clear(); @@ -777,6 +780,45 @@ mod tests { (sel.slide, sel.slides.iter().map(|x| x.0).collect()) } + #[test] + fn deleting_slide_prunes_every_custom_show_in_one_undo_step() { + let (mut s, ids) = deck(3); + let shows = vec![ + deckcraft_model::CustomShow { + name: "First".into(), + slides: vec![deckcraft_model::SlideId(ids[0]), deckcraft_model::SlideId(ids[1]), deckcraft_model::SlideId(ids[1])], + }, + deckcraft_model::CustomShow { name: "Second".into(), slides: vec![deckcraft_model::SlideId(ids[1]), deckcraft_model::SlideId(ids[2])] }, + deckcraft_model::CustomShow { name: "Empty after delete".into(), slides: vec![deckcraft_model::SlideId(ids[1])] }, + ]; + s.edit(|doc, _| { + doc.custom_shows = shows; + Ok(()) + }) + .expect("set custom shows"); + + s.execute("slide.delete", &json!({"index": 1})).expect("delete slide"); + let shows = &s.doc().expect("doc").doc.custom_shows; + assert_eq!(shows[0].slides, [deckcraft_model::SlideId(ids[0])]); + assert_eq!(shows[1].slides, [deckcraft_model::SlideId(ids[2])]); + assert!(shows[2].slides.is_empty()); + + s.execute("edit.undo", &json!({})).expect("undo deletion"); + assert_eq!(order(&s), ids); + assert_eq!( + s.doc().expect("doc").doc.custom_shows[0].slides, + [deckcraft_model::SlideId(ids[0]), deckcraft_model::SlideId(ids[1]), deckcraft_model::SlideId(ids[1])] + ); + assert_eq!(s.doc().expect("doc").doc.custom_shows[1].slides, [deckcraft_model::SlideId(ids[1]), deckcraft_model::SlideId(ids[2])]); + + s.execute("edit.redo", &json!({})).expect("redo deletion"); + let bytes = deckcraft_format::save(&s.doc().expect("doc").doc).expect("save deck"); + let reopened = deckcraft_format::load(&bytes).expect("reopen deck"); + assert_eq!(reopened.custom_shows[0].slides, [deckcraft_model::SlideId(ids[0])]); + assert_eq!(reopened.custom_shows[1].slides, [deckcraft_model::SlideId(ids[2])]); + assert!(reopened.custom_shows[2].slides.is_empty()); + } + #[test] fn move_from_to_moves_one_slide() { let (mut s, ids) = deck(4); diff --git a/docs/slideshow-parity.md b/docs/slideshow-parity.md index 5b10c2028..137960531 100644 --- a/docs/slideshow-parity.md +++ b/docs/slideshow-parity.md @@ -1,6 +1,6 @@ # Slide show and presenter view: parity with PowerPoint -> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-27 · **Change:** minor (Set Up Slide Show ▸ Keep mouse cursor visible, #1445; previous: minor, Ctrl+wheel zooms the show around the pointer, #1286; previous: major, community PR wave 2026-10-14: interactive decks, laser pointer, See All Slides and zoom, go-to-slide number, custom show playback, presenter timer, clock, notes size and keyboard, Show Media Controls, swipe; breadth 68%; duplicated rows and headers folded; previous: major, created) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) +> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-11-02 · **Change:** minor (deleting slides removes them from custom shows; previous: minor, Set Up Slide Show ▸ Keep mouse cursor visible, #1445; previous: minor, Ctrl+wheel zooms the show around the pointer, #1286; previous: major, community PR wave 2026-10-14: interactive decks, laser pointer, See All Slides and zoom, go-to-slide number, custom show playback, presenter timer, clock, notes size and keyboard, Show Media Controls, swipe; breadth 68%; duplicated rows and headers folded; previous: major, created) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) Presenting is the job PowerPoint is hired for. This doc covers the show window, presenter view, rehearsal and recording. Transition and animation playback are in @@ -33,7 +33,7 @@ ready** (estimated), **10–16 h** to parity. | Pen and highlighter ink | yes | pen only (Cmd+P pen, Cmd+A arrow, E erases all ink) | No highlighter, per-stroke eraser, ink colours, or keep/discard annotations prompt | | Laser pointer | yes | yes in the show | Cmd/Ctrl+L and the on-screen button draw the show's laser colour and leave no ink. The presenter window does not move that dot | | Subtitles and live captions | yes (cloud speech) | no | AI dimension | -| Custom shows | yes | yes: Show plays that list in order and does not change Set Up; Set Up Slide Show can keep one for Play from Start | Hidden slides in the list are skipped | +| Custom shows | yes | yes: Show plays that list in order and does not change Set Up; Set Up Slide Show can keep one for Play from Start | Hidden slides in the list are skipped; deleting a slide removes its ID from every custom show | | Set Up Show: loop until Esc, kiosk (browsed at a kiosk), show without animation/narration, pen colour, slides range | yes | yes, most options | Keep mouse cursor visible (DeckCraft's own, #1445) stops the show hiding a still pointer after 2.5 s, for decks with clickable choices; it is a user preference (`ui.json`, `showKeepCursor`), since PPTX has no place for it | | Use rehearsed timings | yes | yes | | @@ -90,6 +90,7 @@ Reading View in a window with navigation: present (checklist row done). | 2026-10-14 | trivial | Show Media Controls hides the show's playback bar and GIF button | | 2026-10-14 | minor | Typing a slide number during the show shows it, and Enter goes there. Esc clears the number before it leaves the show. Checklist breadth stays 66% | | 2026-10-14 | minor | A custom show plays only its slides, from the Custom Shows dialog, from Set Up Slide Show, or from a click action. Checklist breadth stays 66% | +| 2026-11-02 | minor | Deleting a slide removes it from every custom show; custom-show cleanup is undoable and survives native save/reopen | | 2026-10-14 | minor | Presenter view pauses and resets its timer (T, R) and changes notes size (A-/A+, brackets). The clock shows local time. Second-display placement and swap stay with #433. Checklist breadth stays 66% | | 2026-10-14 | minor | See All Slides (G) and click-to-zoom (Z) during the show. Presenter view opens that grid and sets black, white and zoom. Checklist breadth stays 66% | | 2026-10-24 | minor | Ctrl+wheel (Cmd+wheel on a Mac) zooms the show in and out around the pointer, up to 4×; the plain wheel still turns the page (#1286) |