feat: SVG composition animation — real-time slide preview with diff-based animation#54
Open
ShotaroKataoka wants to merge 24 commits intomainfrom
Open
feat: SVG composition animation — real-time slide preview with diff-based animation#54ShotaroKataoka wants to merge 24 commits intomainfrom
ShotaroKataoka wants to merge 24 commits intomainfrom
Conversation
…eview SPEC: 20260413-0806_svg-composition-animation Phase 1-3: compose module, API integration, WebUI animation - mcp-server/tools/compose.py: extract_optimized_defs + split_slide_components (font strip, PNG→WebP, component split with bbox/class/text metadata) - server.py: compose after _run_measure (sync, ~300ms for 8 slides) - api/index.py: defsUrl + composeUrl presigned URLs in deck detail - AnimatedSlidePreview.tsx: SVG build + diff animation (class+bbox key) cursor fly-in → wireframe drag → materialize + typewriter - SlideCarousel: composeUrl → animated preview, fallback to WebP thumbnail - DOMPurify sanitization, prefers-reduced-motion, version check fallback - useWorkspace: presigned URL stabilization for compose/defs URLs
…build cache - Disable DOMPurify sanitization (strips SVG clip-path, namespaces, fills) - Fix slidesWithPreview filter to include composeUrl-only slides - Fix hasSlides to check composeUrl in addition to previewUrl SPEC: 20260413-0806_svg-composition-animation
… webp separation - Fix compose.py: use slide-specific SlideBackground over master page - Epoch-keyed compose S3 keys for proper update detection - API: _latest_compose_key() to resolve latest epoch - AnimatedSlidePreview: skip animation on initial load - Remove WebP generation from run_python (kept in generate_pptx) - Generate compose for all slides (frontend diff handles animation) - count_slides() helper in compose.py SPEC: 20260413-0806_svg-composition-animation
…ose on save-only - Compose runs on save=True even without measure_slides - SVG export shared between measure and compose (single conversion) - Remove WebP from run_python (kept in generate_pptx) - Auto-scroll to first changed slide before animation - AnimatedSlidePreview accepts slideId for scroll targeting SPEC: 20260413-0806_svg-composition-animation
…ration - Extract _export_svg() from _run_measure for reuse by compose - Compose generates SVG when measure_slides not present (save-only) - Add Dockerfile cache-bust for forced image rebuild SPEC: 20260413-0806_svg-composition-animation
…sition fix - Add initialLoad prop: page-load compose = instant, session-first = animate all - Scroll to top of changed slide with 24px padding instead of center SPEC: 20260413-0806_svg-composition-animation
…a onAnimate callback SPEC: 20260413-0806_svg-composition-animation
…ewriter fast SPEC: 20260413-0806_svg-composition-animation
…hange detected SPEC: 20260413-0806_svg-composition-animation
…p by composeUrl - Diff uses text+class instead of raw SVG (stable across LibreOffice re-renders) - Skip re-render when composeUrl base path unchanged (ignore defs-only changes) SPEC: 20260413-0806_svg-composition-animation
…_1 matched slide_10/11) SPEC: 20260413-0806_svg-composition-animation
SPEC: 20260413-0806_svg-composition-animation
…pose cleanup - server.py: sourceHash (slide JSON md5) for cross-slide matching - server.py: 2-level diff (slide-level sourceHash + component-level class+bbox) - server.py: cleanup old epoch compose files after upload - AnimatedSlidePreview: simplified to use backend changed flag only - SlideCarousel: removed initialComposeIds, kept scroll via onAnimate SPEC: 20260413-0806_svg-composition-animation
…nimation - server.py: fallback to slot-number diff when sourceHash mismatches - AnimatedSlidePreview: first render = instant, defer composeUrl changes during animation SPEC: 20260413-0806_svg-composition-animation
…ction + defer animation - AnimatedSlidePreview: interval-based check (no useEffect dep on composeUrl), skipAnimation prop for instant render on page load - SlideCarousel: deckReadyRef tracks initial load, detects new slides for scroll - Remove debug logs SPEC: 20260413-0806_svg-composition-animation
10f0a36 to
06932b6
Compare
…t B303 SPEC: 20260413-0806_svg-composition-animation
- AnimatedSlidePreview: reset lastComposeUrlRef when skipAnimation transitions true→false - generate.py: fallback template blank-dark instead of non-existent default SPEC: 20260413-0806_svg-composition-animation
…ecision - Existing deck (slides on mount) → first compose instant, subsequent animate - New deck (no slides on mount) → all composes animate - No localStorage, no complex state — just mount-time fact SPEC: 20260413-0806_svg-composition-animation
- Lint/bias block: 2-space → 4-space indent consistency - Move 'import re' outside for-loop SPEC: 20260413-0806_svg-composition-animation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
LibreOffice SVG → optimized component split → S3 → WebUI animated preview with diff-based animation.
When the agent edits slides via
run_python(save=True), the compose pipeline splits the SVG into individual components and uploads them as JSON to S3. The frontend fetches these, diffs against the previous state, and animates only the changed/new components with agent cursor fly-in, wireframe drag, and typewriter effects.Demo
How it works
Backend: Compose pipeline (
mcp-server/tools/compose.py)extract_optimized_defs()— shared SVG defs (gradients, clip-paths)split_slide_components()— per-slide component extraction with bbox, class, text, svgcount_slides()— slide count from SVG structureSlideBackground→ master page fallbackdefs_{epoch}.json,slide_{N}_{epoch}.json) for cache-bustingBackend: Integration (
mcp-server/server.py)_export_svg()— extracted from_run_measurefor reuserun_pythonpost-processing: SVG export → compose for all slides onsave=Truemeasure_slidesAPI (
api/index.py)_latest_compose_key()— resolves highest epoch from S3 key listingdefsUrland per-slidecomposeUrlvia CloudFront signed URLsFrontend
AnimatedSlidePreview— fetches compose JSON, builds SVG, diff-based animationprevCompRefnull +initialLoad=true→ instant (page load)prevCompRefnull +initialLoad=false→ animate all (first agent write)prevCompRefexists → animate only changed/new componentsprefers-reduced-motionrespectedSlideCarousel— auto-scroll to first changed slide on compose updateuseWorkspace— URL stabilization forcomposeUrl/defsUrlS3 key structure
decks/{deck_id}/compose/defs_{epoch}.json
decks/{deck_id}/compose/slide_{N}_{epoch}.json
Files changed (12 files, +741 -37)
mcp-server/tools/compose.pymcp-server/server.py_export_svgextractionapi/index.py_latest_compose_key, defsUrl/composeUrl in responseweb-ui/.../AnimatedSlidePreview.tsxweb-ui/.../SlideCarousel.tsxweb-ui/src/hooks/useWorkspace.tsweb-ui/src/services/deckService.ts