From 729ad409dd3d712fd2660f00e4b10ca8908d8ca3 Mon Sep 17 00:00:00 2001 From: KeyInfo Bot Date: Sat, 29 Aug 2026 00:01:56 +0800 Subject: [PATCH] Sync third-party and MCP marketplace plugins Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources. Confidence: high Scope-risk: narrow Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history. Tested: Marketplace validation passed. --- config/external-sources.lock.json | 8 +- .../plugins/mcp-playwright/MCP_SOURCE.json | 2 +- .../next-skills/THIRD_PARTY_SOURCE.json | 4 +- .../rig-template.md | 10 +- .../SKILL.md | 128 +- .../rig-template.md | 172 ++ plugins/codex/plugins/ppt-master/README.md | 2 +- .../ppt-master/THIRD_PARTY_SOURCE.json | 4 +- .../ppt-master/skills/ppt-master/SKILL.md | 5 +- .../references/artifact-ownership.md | 14 +- .../ppt-master/references/executor-base.md | 80 +- .../ppt-master/references/executor-chart.md | 2 +- .../ppt-master/references/executor-image.md | 4 +- .../ppt-master/references/executor-notes.md | 2 +- .../references/executor-structure.md | 9 +- .../references/executor-structured.md | 10 +- .../ppt-master/references/executor-table.md | 1 + .../ppt-master/references/image-base.md | 2 +- .../ppt-master/references/image-generator.md | 2 +- .../ppt-master/references/image-searcher.md | 8 + .../references/native-data-interface.md | 160 +- .../references/native-hyperlinks.md | 14 +- .../references/native-shape-authoring.md | 11 +- .../references/pptx-structure-interface.md | 49 +- .../ppt-master/references/semantic-svg.md | 6 +- .../references/shared-standards-core.md | 95 +- .../ppt-master/references/shared-standards.md | 2 +- .../ppt-master/references/strategist-image.md | 38 +- .../references/strategist-template.md | 27 +- .../ppt-master/references/strategist.md | 202 +- .../ppt-master/references/svg-effects.md | 47 +- .../references/template-designer.md | 136 +- .../references/visual-styles/_index.md | 56 +- .../skills/ppt-master/scripts/README.md | 174 +- .../ppt-master/scripts/attribution_guard.py | 4 - .../ppt-master/scripts/authoring_roundtrip.py | 1939 ++++++++++++ .../ppt-master/scripts/beautify_inventory.py | 2 +- .../scripts/compact_svg_coordinates.py | 13 +- .../ppt-master/scripts/compact_svg_styles.py | 585 ++++ .../ppt-master/scripts/confirm_ui/server.py | 71 +- .../scripts/confirm_ui/static/app.js | 111 +- .../scripts/confirm_ui/static/catalogs.json | 26 + .../docs/advanced-image-motion-smoke.md | 4 +- .../ppt-master/scripts/docs/confirm_ui.md | 38 +- .../ppt-master/scripts/docs/conversion.md | 34 +- .../scripts/docs/multilingual-text-smoke.md | 1 + .../scripts/docs/pptx-animations.md | 7 - .../scripts/docs/pptx-transitions.md | 15 +- .../skills/ppt-master/scripts/docs/project.md | 12 +- .../ppt-master/scripts/docs/svg-pipeline.md | 369 ++- .../ppt-master/scripts/extract_svg_assets.py | 365 ++- .../skills/ppt-master/scripts/finalize_svg.py | 7 +- .../skills/ppt-master/scripts/icon_sync.py | 29 +- .../skills/ppt-master/scripts/image_search.py | 35 +- .../scripts/mirror_template_materialize.py | 640 +++- .../ppt-master/scripts/native_enhance_pptx.py | 42 - .../scripts/native_enhance_pptx_core.py | 2587 ----------------- .../scripts/native_narration_pptx.py | 37 - .../ppt-master/scripts/notes_to_audio.py | 48 +- .../ppt-master/scripts/pptx_animations.py | 7 +- .../skills/ppt-master/scripts/pptx_intake.py | 8 +- .../ppt-master/scripts/pptx_ooxml/__init__.py | 20 + .../analyzer.py | 46 +- .../chart_read.py | 7 +- .../ppt-master/scripts/pptx_ooxml/clone.py | 817 ++++++ .../diagram_read.py | 0 .../edit_safety.py | 26 +- .../ooxml.py | 12 +- .../package.py | 69 +- .../scripts/pptx_template_import.py | 195 +- .../skills/ppt-master/scripts/pptx_to_svg.py | 62 +- .../scripts/pptx_to_svg/converter.py | 574 +++- .../scripts/pptx_to_svg/notes_import.py | 114 + .../scripts/pptx_to_svg/pic_to_svg.py | 25 +- .../scripts/pptx_to_svg/shape_walker.py | 9 + .../scripts/pptx_to_svg/slide_to_svg.py | 79 +- .../scripts/pptx_to_svg/tbl_to_svg.py | 293 +- .../scripts/pptx_to_svg/transition_import.py | 19 +- .../scripts/pptx_to_svg/txbody_to_svg.py | 5 +- .../ppt-master/scripts/pptx_transitions.py | 10 +- .../ppt-master/scripts/pptx_workspace.py | 978 +++++++ .../project_management/project_specs.py | 24 +- .../ppt-master/scripts/project_utils.py | 4 +- .../scripts/prompt_audit_manifest.json | 233 +- .../ppt-master/scripts/register_template.py | 25 +- .../ppt-master/scripts/resource_paths.py | 112 +- .../ppt-master/scripts/semantic_table.py | 522 ++++ .../skills/ppt-master/scripts/slice_images.py | 79 +- .../scripts/source_to_md/ppt_to_md.py | 2 +- .../scripts/stamp_native_fallbacks.py | 346 +++ .../scripts/svg_authoring_contract.py | 399 +++ .../ppt-master/scripts/svg_authoring_view.py | 1804 +++++++++++- .../ppt-master/scripts/svg_compatibility.py | 59 + .../ppt-master/scripts/svg_editor/server.py | 47 +- .../scripts/svg_finalize/embed_icons.py | 181 +- .../scripts/svg_finalize/flatten_tspan.py | 71 +- .../ppt-master/scripts/svg_quality/checker.py | 1464 +++++++++- .../ppt-master/scripts/svg_quality/cli.py | 130 +- .../ppt-master/scripts/svg_quality_checker.py | 2 + .../skills/ppt-master/scripts/svg_to_pptx.py | 8 + .../scripts/svg_to_pptx/drawingml/context.py | 4 + .../svg_to_pptx/drawingml/converter.py | 278 +- .../scripts/svg_to_pptx/drawingml/elements.py | 39 +- .../svg_to_pptx/drawingml/theme_fonts.py | 85 + .../scripts/svg_to_pptx/drawingml/utils.py | 12 +- .../svg_to_pptx/native_objects/__init__.py | 78 +- .../svg_to_pptx/native_objects/chart_style.py | 366 ++- .../svg_to_pptx/native_objects/chartex.py | 48 +- .../native_objects/fallback_hash.py | 27 +- .../native_objects/marker_attributes.py | 7 + .../native_objects/marker_common.py | 200 ++ .../native_objects/marker_status.py | 17 + .../svg_to_pptx/native_objects/table.py | 965 +++++- .../svg_to_pptx/pptx_package/builder.py | 2145 ++++++++++++-- .../scripts/svg_to_pptx/pptx_package/cli.py | 1619 ++++++++++- .../scripts/svg_to_pptx/pptx_package/notes.py | 9 +- .../pptx_package/template_structure.py | 291 +- .../scripts/svg_to_pptx/tspan_flattener.py | 20 + .../scripts/svg_to_pptx/use_expander.py | 68 +- .../ppt-master/scripts/template_fill_pptx.py | 32 - .../scripts/template_fill_pptx/__init__.py | 29 - .../scripts/template_fill_pptx/applier.py | 489 ---- .../scripts/template_fill_pptx/chart_fill.py | 401 --- .../scripts/template_fill_pptx/checker.py | 719 ----- .../scripts/template_fill_pptx/cli.py | 227 -- .../scripts/template_fill_pptx/clone.py | 176 -- .../scripts/template_fill_pptx/notes.py | 114 - .../scripts/template_fill_pptx/scaffolder.py | 82 - .../scripts/template_fill_pptx/selectors.py | 65 - .../scripts/template_fill_pptx/table_fill.py | 71 - .../scripts/template_fill_pptx/text_fill.py | 117 - .../scripts/template_fill_pptx/transitions.py | 150 - .../scripts/template_fill_pptx/validator.py | 491 ---- .../scripts/template_import/manifest.py | 52 +- .../template_import/native_structure.py | 13 +- .../scripts/template_preview_pptx.py | 204 +- .../native_projection/06_1145_completion.svg | 188 ++ .../native_projection/07_segment_mix.svg | 75 + .../native_projection/10_competitiveness.svg | 71 + .../native_projection/11_market_space.svg | 61 + .../native_projection/15_signing_target.svg | 228 ++ .../native_projection/27_business_models.svg | 77 + .../native_projection/30_risk_matrix.svg | 111 + .../native_projection/31_power_market.svg | 95 + .../test_confirm_ui_design_spec_depth.py | 105 + .../scripts/tests/test_image_search.py | 93 + .../tests/test_native_chart_table_parity.py | 427 +++ .../scripts/tests/test_slice_images.py | 60 + .../scripts/tests/test_svg_quality_checker.py | 290 ++ .../scripts/tests/test_text_measure.py | 203 ++ .../scripts/tests/test_update_spec.py | 169 ++ .../skills/ppt-master/scripts/text_measure.py | 357 +++ .../skills/ppt-master/scripts/update_spec.py | 79 +- .../VISUALIZATION_TEMPLATE_AUTHORING.md | 13 +- .../ppt-master/templates/decks/README.md | 6 +- .../templates/design_spec_reference.md | 17 +- .../ppt-master/templates/icons/README.md | 7 +- .../ppt-master/templates/layouts/README.md | 6 +- .../templates/19_chart_insight.svg | 2 +- .../templates/20_table_summary.svg | 4 +- .../templates/15_chart_insight.svg | 2 +- .../templates/16_table_summary.svg | 4 +- .../templates/08_chart_insight.svg | 2 +- .../templates/09_table_summary.svg | 4 +- .../templates/schemas/spec_lock.schema.json | 3 +- .../templates/spec_lock_reference.md | 6 +- .../templates/tables/hierarchical_table.svg | 1 + .../templates/tables/record_table.svg | 1 + .../ppt-master/workflows/create-template.md | 332 ++- .../workflows/create-template/create-brand.md | 2 +- .../workflows/create-template/create-deck.md | 7 +- .../create-template/create-layout.md | 9 +- .../workflows/create-template/create-style.md | 2 +- .../ppt-master/workflows/edit-native-pptx.md | 282 ++ .../ppt-master/workflows/generate-pptx.md | 75 +- .../workflows/governance/failure-recovery.md | 4 +- .../skills/ppt-master/workflows/index.md | 5 +- .../workflows/native-enhance-pptx.md | 409 --- .../workflows/profiles/beautify-pptx.md | 43 +- .../workflows/profiles/quick-generate.md | 134 +- .../skills/ppt-master/workflows/routing.md | 39 +- .../stages/apply-template-workspace.md | 47 +- .../workflows/stages/customize-animations.md | 2 +- .../workflows/stages/generate-audio.md | 31 +- .../workflows/stages/live-preview.md | 6 +- .../workflows/stages/verify-charts.md | 4 +- .../workflows/template-fill-pptx.md | 379 --- 187 files changed, 22335 insertions(+), 9348 deletions(-) create mode 100644 plugins/codex/plugins/next-skills/skills/next-partial-prefetching-adoption/rig-template.md create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/authoring_roundtrip.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/compact_svg_styles.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/native_enhance_pptx.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/native_enhance_pptx_core.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/native_narration_pptx.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pptx_ooxml/__init__.py rename plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/{template_fill_pptx => pptx_ooxml}/analyzer.py (86%) rename plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/{template_fill_pptx => pptx_ooxml}/chart_read.py (92%) create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pptx_ooxml/clone.py rename plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/{template_fill_pptx => pptx_ooxml}/diagram_read.py (100%) rename plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/{template_fill_pptx => pptx_ooxml}/edit_safety.py (90%) rename plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/{template_fill_pptx => pptx_ooxml}/ooxml.py (95%) rename plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/{template_fill_pptx => pptx_ooxml}/package.py (74%) create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pptx_to_svg/notes_import.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pptx_workspace.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/semantic_table.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/stamp_native_fallbacks.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_authoring_contract.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_compatibility.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/__init__.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/applier.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/chart_fill.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/checker.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/cli.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/clone.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/notes.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/scaffolder.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/selectors.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/table_fill.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/text_fill.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/transitions.py delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/validator.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/06_1145_completion.svg create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/07_segment_mix.svg create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/10_competitiveness.svg create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/11_market_space.svg create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/15_signing_target.svg create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/27_business_models.svg create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/30_risk_matrix.svg create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/31_power_market.svg create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_confirm_ui_design_spec_depth.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_image_search.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_native_chart_table_parity.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_slice_images.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_svg_quality_checker.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_text_measure.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_update_spec.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/text_measure.py create mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/edit-native-pptx.md delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/native-enhance-pptx.md delete mode 100644 plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/template-fill-pptx.md diff --git a/config/external-sources.lock.json b/config/external-sources.lock.json index 3f82ea35..6cef16c1 100644 --- a/config/external-sources.lock.json +++ b/config/external-sources.lock.json @@ -96,8 +96,8 @@ "repo": "https://github.com/hugohe3/ppt-master.git", "ref": "main", "adapter": "claude-skill", - "commit": "ebd74d1f1d61a686f0f80e10abde5029fc4beeca", - "syncedAt": "2026-08-25T16:00:00Z" + "commit": "d6bcaf96b7946667f4a8871b0688b903181db527", + "syncedAt": "2026-08-28T16:00:00Z" }, { "id": "grill-me", @@ -114,8 +114,8 @@ "repo": "https://github.com/vercel/next.js.git", "ref": "canary", "adapter": "skill-collection", - "commit": "0a2bd1d531595b1d6d115c2c912f897e9053d3b8", - "syncedAt": "2026-08-27T16:00:00Z" + "commit": "1cb20a1d1431f9216f44230a1e5a698c23ba9f70", + "syncedAt": "2026-08-28T16:00:00Z" } ] } diff --git a/plugins/codex/plugins/mcp-playwright/MCP_SOURCE.json b/plugins/codex/plugins/mcp-playwright/MCP_SOURCE.json index 68d0763c..06ccf318 100644 --- a/plugins/codex/plugins/mcp-playwright/MCP_SOURCE.json +++ b/plugins/codex/plugins/mcp-playwright/MCP_SOURCE.json @@ -3,5 +3,5 @@ "name": "playwright浏览器自动化操作", "version": "20260605", "keySource": "none", - "syncedAt": "2026-08-27T16:01:49Z" + "syncedAt": "2026-08-28T16:01:55Z" } diff --git a/plugins/codex/plugins/next-skills/THIRD_PARTY_SOURCE.json b/plugins/codex/plugins/next-skills/THIRD_PARTY_SOURCE.json index 673372f2..fd8ff4da 100644 --- a/plugins/codex/plugins/next-skills/THIRD_PARTY_SOURCE.json +++ b/plugins/codex/plugins/next-skills/THIRD_PARTY_SOURCE.json @@ -2,8 +2,8 @@ "sourceId": "next-skills", "repo": "https://github.com/vercel/next.js.git", "ref": "canary", - "commit": "0a2bd1d531595b1d6d115c2c912f897e9053d3b8", + "commit": "1cb20a1d1431f9216f44230a1e5a698c23ba9f70", "adapter": "skill-collection", "sourcePath": "skills", - "syncedAt": "2026-08-27T16:00:00Z" + "syncedAt": "2026-08-28T16:00:00Z" } diff --git a/plugins/codex/plugins/next-skills/skills/next-cache-components-optimizer/rig-template.md b/plugins/codex/plugins/next-skills/skills/next-cache-components-optimizer/rig-template.md index d0f29230..c19c0ba5 100644 --- a/plugins/codex/plugins/next-skills/skills/next-cache-components-optimizer/rig-template.md +++ b/plugins/codex/plugins/next-skills/skills/next-cache-components-optimizer/rig-template.md @@ -39,7 +39,10 @@ build/run obstacles, accumulated as you first hit them). and never for real production? Spellings: an explicit `EXPOSE_TESTING_API=1` for local production builds; `process.env.DEPLOY_ENV === 'staging'` for a generic CI/staging env var; `process.env.VERCEL_ENV === -'preview'` on Vercel. +'preview'` on Vercel. Set the condition during `next build`, not only + `next start`. Otherwise `instant()` may not acquire the testing cookie + before the test times out; rebuild the artifact before debugging the + assertion. 3. **RUN**: how is the Playwright suite invoked, and against which `BASE_URL`? 4. **TEST USER**: which account does the suite run as, and how does login @@ -64,7 +67,10 @@ build/run obstacles, accumulated as you first hit them). chosen mechanism. For a local `build && start` rig the artifact is the one freshly built, so no SHA probe is needed. Record the port, stop the previous server before starting, fail the loop on `EADDRINUSE`, and verify the newly - started process owns the port before running the test. + started process owns the port before running the test. `next start` can fork + a `next-server` child, so the launcher process ID may not own the port. Start + the server in a process group that the rig can stop as a unit, or discover + and stop the process listening on the recorded port before the next build. ## The file: copy, fill, commit as `instant-nav.rig.md` diff --git a/plugins/codex/plugins/next-skills/skills/next-partial-prefetching-adoption/SKILL.md b/plugins/codex/plugins/next-skills/skills/next-partial-prefetching-adoption/SKILL.md index f09895b6..aa11cd07 100644 --- a/plugins/codex/plugins/next-skills/skills/next-partial-prefetching-adoption/SKILL.md +++ b/plugins/codex/plugins/next-skills/skills/next-partial-prefetching-adoption/SKILL.md @@ -5,7 +5,8 @@ description: > insights it surfaces. Use when the user wants to enable or adopt Partial Prefetching, flip the `partialPrefetching` flag, opt routes in with `export const prefetch = 'partial'`, audit - `` calls, or resolve the + `Link prefetch={true}` behavior, preserve existing prefetched UI + with `instant()` tests, or resolve the instant-link-prefetch-partial and instant-shell-url-data insights. --- @@ -13,19 +14,23 @@ description: > Enable Partial Prefetching and walk the app until every link reuses a shared App Shell. This skill sequences the work; per-insight recipes live in the dev overlay fix cards and their docs pages. The [Adopting Partial Prefetching guide](https://nextjs.org/docs/app/guides/adopting-partial-prefetching) is the canonical reference for the concepts this skill applies. -The one thing that shapes everything below: **these insights surface only in `next dev`, in the dev overlay's Insights tab.** Nothing fails the build. There is no build-only fallback loop — confirming an insight is _cleared_ means driving the running app in a browser. But a missing browser gates that verification, not the whole skill: the adoption work is static and runs from the guide, so do the static pass anyway and hand off the live shell check. +The development insights and the preservation tests are two different paths. Insights surface only in `next dev`, in the dev overlay's Insights tab. Test-backed preservation runs against a production-like build with `instant()` and does not need a development server. After the flag is enabled, the separate URL-data insight sweep still uses `next dev`. -Talk to the user in terms of what they'll see — PRs, features, and how the app behaves after — never the insight slugs or step labels. Before you start, tell them briefly what Partial Prefetching changes: a `` loads a shared App Shell, and `prefetch={true}` no longer prefetches everything the old full prefetch did. +## preservation gate + +When using test-backed preservation, the first implementation milestone is a passing flag-off `instant()` suite. Set up the production test rig, write the selected assertions, run them with `partialPrefetching` disabled, and record the command and exit status. Test-only configuration required by the rig is allowed, but until that baseline passes, do not enable `partialPrefetching` or edit the destination, cache boundaries, or Link props. Installing missing test dependencies is part of reaching the baseline, not a reason to adopt first. Use the manual path only when `rig-template.md` identifies a concrete blocker the repository cannot resolve, and record the blocker and deferred test coverage. + +Talk to the user in terms of what they'll see — PRs, features, and how the app behaves after — never the insight slugs or step labels. Before you start, tell them briefly what Partial Prefetching changes: links to a route prefetch one shared App Shell, and `prefetch={true}` can also resolve cached URL-specific content. The audit determines which UI from the legacy full prefetch to preserve. ## requires -- **Cache Components on (`cacheComponents: true`).** This is the only hard requirement; `partialPrefetching` depends on it. Full Cache Components adoption is the ideal starting point but not a gate. Nothing in this skill blocks the build, and neither do the prerender insights an unadopted route surfaces, like a leftover `unstable_noStore` or a `cookies()` read outside ``: they are non-blocking dev signals, expected on any fresh branch off `main`, not a reason to stop. They replace the URL-data insight only on their own route in the [step 3](#step-3-sweep-for-url-data-insights-after-enabling) sweep; the flag-off step 1 audit and its static adoption run regardless. The only thing that actually stops this skill is a build-blocking failure, and anything build-blocking would have been resolved before you reached here. Otherwise fix the prerender insights you hit as inline [`next-cache-components-adoption`](https://github.com/vercel/next.js/tree/canary/skills/next-cache-components-adoption) work, or hand them off, and keep going. +- **Cache Components adopted (`cacheComponents: true`) with a passing build.** Both `partialPrefetching` and the route-level `prefetch` export require Cache Components. If it is off, use [`next-cache-components-adoption`](https://github.com/vercel/next.js/tree/canary/skills/next-cache-components-adoption) first and return after its build-blocking prerender errors are resolved. Those errors can fail `next build`; only the Partial Prefetching insights handled by this skill are non-blocking development signals. - **Next.js 16.3 or later.** `partialPrefetching`, the `prefetch` route segment config, and the prefetch insights all land there. -- **A browser you can drive.** Install [`next-dev-loop`](https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop) before starting, unless it is already available — it ships alongside this skill (`npx skills add https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop`). Install it without asking — it's a tool, not a product change — and don't assume it's blocked: verify a real blocker (no network, no npm, read-only filesystem) before falling back, and name it in your report. Link prefetches fire when a link renders and enters the viewport, and shell validation fires on navigation — neither is reachable from `curl` or the build. If the app is webpack-pinned, drive a browser directly (`agent-browser`, Playwright) — you lose the framework cross-checks, not the insights; they're still in the overlay and the dev log. +- **A browser you can drive.** Test-backed preservation uses an existing or minimal production-mode Playwright suite; manual preservation and the final demonstration use the running production app. The development insight path and the post-flag URL-data sweep use [`next-dev-loop`](https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop); install it before either development pass unless it is already available (`npx skills add https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop`). If the app is webpack-pinned, drive a browser directly (`agent-browser`, Playwright) — you lose the framework cross-checks, not the insights; they're still in the overlay and the dev log. -- **A runnable app.** Verification runs against `next dev` for the insight sweep and a production `next build`/`next start` for prefetching (prefetching is prod-only), so the app has to boot in both. If it reads a database or required env at import (e.g. an `env.ts` that throws on a missing `DATABASE_URL`), confirm it starts — with the real environment, or local data you stand up — before step 1. An app that won't run can't be swept or verified. +- **A runnable app.** Preservation and the final demonstration need a production-like build because automatic prefetching runs only in production. The development server is required only when using the insight path or running the post-flag URL-data sweep; do not start it merely to confirm a test-backed preservation case. If the app reads a database or required environment at import, confirm the environment used by the chosen path can start before step 1. ### notes @@ -35,75 +40,104 @@ Talk to the user in terms of what they'll see — PRs, features, and how the app ## background -Adopting Partial Prefetching means every route still delivers what its links prefetched before, now split between the shared App Shell and any extra per-link data a link explicitly asks for. The [guide](https://nextjs.org/docs/app/guides/adopting-partial-prefetching) is the canonical reference for what a prefetch contains and how to decide each case; this skill sequences that work against a running app. +Adopting Partial Prefetching means every route preserves the prefetched UI that matters, now split between the shared App Shell and any extra per-link data a link explicitly asks for. The [guide](https://nextjs.org/docs/app/guides/adopting-partial-prefetching) is the canonical reference for what a prefetch contains and how to decide each case; this skill sequences that work against a running app. The catch that decides most of the sweep: a default link warms only the shared App Shell. A route keyed by `params` or `searchParams` can prefetch more only after it has adopted Partial Prefetching and a specific link uses [``](https://nextjs.org/docs/app/api-reference/components/link#prefetch); then Next.js resolves the URL data and any cached content behind it before the click (the guide's [URL data](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#url-data) section). ## working surfaces -- **The dev server terminal — your primary record.** Each validated route's insights are logged as `Error: Route "...": Next.js encountered ...` lines with the `https://nextjs.org/docs/messages/` link. Tail the dev log during the sweep; it's the greppable record of what fired where, and it works the same on Turbopack and webpack. +- **The production-mode `instant()` suite — the primary record for test-backed preservation.** Reuse the app's production build, test context, and Playwright setup. Read an existing `instant-nav.rig.md` first; if the project has no rig, create it from **`rig-template.md`**. The same tests define the legacy target before adoption and become the work queue after each destination opts into Partial Prefetching. Development can help investigate a failure, but only this suite decides whether the prefetched UI was preserved. +- **The dev server terminal — the primary record for the insight path.** Each validated route's insights are logged as `Error: Route "...": Next.js encountered ...` lines with the `https://nextjs.org/docs/messages/` link. Tail the dev log during the sweep; it's the greppable record of what fired where, and it works the same on Turbopack and webpack. - **The dev overlay Insights tab.** Insights are the amber, non-blocking tab. It appears only once an insight has fired, so a route that surfaces nothing shows no tab at all — that's the clean state, not a missing feature. Don't hunt for the tab on a quiet route; confirm clean from the dev log above, which is the reliable signal. The precondition is no blocking-prerender errors — those replace the insight on their route (see requires). An unrelated Issue (a hydration error, a console error) doesn't block the sweep; don't stall on it. When the tab is present, the overlay pill shows the count and each insight has fix cards linking its docs page. The overlay renders inside a shadow root (`nextjs-portal`), so accessibility-tree snapshots don't see it — evaluate into `shadowRoot` when you need to read or click it programmatically. - **`next-dev-loop`** to drive navigations and read the overlay. Prefer it over hand-rolled browser automation for the same reasons as in the Cache Components skill (webpack apps: see requires). When browsing its `/_next/mcp` tools, the prefetch insights surface through `get_errors` and the overlay, not the similarly-named `get_request_insights`. That one is the span and performance recorder (gated behind `experimental.requestInsights`) and reports nothing about prefetching. Every insight has a docs page — open it. Fetch the linked page for every distinct insight you encounter; the inline message is a summary, the page is the recipe. -## step 1: audit `` (before enabling) +## step 1: audit `` navigations (before enabling) -If `partialPrefetching: true` is already set in `next.config.ts`, the app is adopted — skip to [step 3](#step-3-sweep-for-url-data-insights-after-enabling). Otherwise work the audit with the global flag **off**, adopting each destination with `export const prefetch = 'partial'` — enabling the flag first would mark every route adopted and silence the [`instant-link-prefetch-partial`](https://nextjs.org/docs/messages/instant-link-prefetch-partial) insight this audit runs on. Ask the user how to ship it, in the language of PRs: +Keep the global flag **off** through this audit and the legacy baseline in step 2. Enabling it earlier would remove the legacy behavior the migration needs to measure. If the flag is already on in unshipped work, use the pre-flag commit for the audit and baseline. When the user is available, ask how to ship it in the language of PRs: -- **One branch** — the whole audit in one change, with the flag enabled and the codemod run at the end (step 2). -- **Route by route** — each adopted destination ships as its own PR. The insight still fires for the destinations you haven't reached, a live worklist, and step 2 comes after the last one. +- **One branch** — the whole audit in one change, with the flag enabled and the codemod run at the end (step 4). +- **Route by route** — each adopted destination ships as its own PR. The insight still fires for the destinations you haven't reached, a live worklist, and step 4 comes after the last one. -The work is identical either way — only the commit boundaries differ. Default by app size: one branch for a handful of links, route by route when the audit is big enough that reviewers need smaller diffs. Note the choice in your report. +The work and its order are identical either way — only the commit boundaries differ. When no user is available, default by app size: one branch for a handful of links, route by route when the audit is big enough that reviewers need smaller diffs. Note the choice in your report. -Enumerate the prefetch sites across the whole source tree, not only `app/` — they often live in `src/components` or shared UI packages: `rg -n '\bprefetch\b|router\.prefetch' -g '*.tsx' -g '*.jsx' .`. Keep the `` and bare-prop matches (a bare prop is `true`) as the over-prefetching links this audit adopts destinations for, and drop `prefetch={false}` and other values. Also audit existing imperative [`router.prefetch()`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter) call sites with the same table, because they can be preserving the same "fetch before navigation" behavior and have no dev insight. For new navigation prefetching, prefer [``](https://nextjs.org/docs/app/api-reference/components/link), which the docs call the primary navigation API; use [`router.prefetch()`](https://nextjs.org/docs/app/guides/prefetching#manual-prefetch) only for manual prefetching. If the app already passes an internal `kind` option, treat that as existing implementation detail, not a pattern to spread. Always inspect custom Link wrappers and trace their consumers: a wrapper can call `router.prefetch()` on hover or touch while a consumer omits `prefetch` or passes `prefetch={false}`. Record the declarative and imperative behavior separately, and use keyboard activation when verifying the declarative path so hover prefetching does not mask it. If nothing matches, say so in your report and move on to [step 2](#step-2-enable-the-flag). +Enumerate explicit prefetch and manual prefetch sites across the whole source tree, not only `app/` — they often live in `src/components` or shared UI packages. Start from `next/link` imports and re-exports, then follow custom wrappers to their consumers. Use `rg -n '\bprefetch\b|router\.prefetch' -g '*.tsx' -g '*.jsx' .` as a candidate list, not as the complete audit; inspect conditional props and forwarded `LinkProps` to determine the effective production value. Include every audited navigation whose effective production Link value is `prefetch={true}`: explicit `true`, a bare `prefetch` prop, and expressions that resolve to `true`. Exclude the default value, `prefetch="auto"`, and `prefetch={false}` from the preservation suite because they do not request the legacy full prefetch. Audit existing [`router.prefetch()`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter) calls separately because they have no Link insight. For new manual prefetching, follow the [Prefetching guide](https://nextjs.org/docs/app/guides/prefetching#manual-prefetch). If no Link resolves to `prefetch={true}`, say so and move on to [step 4](#step-4-enable-the-flag). -Then, for each one: +### Choose what to preserve and how to verify it -1. **Click each `` in `next dev`.** The insight fires at navigation time, not when the link prefetches, so a link sitting in the viewport won't trip it — you have to navigate through it. This click is _verification_: it confirms the insight fires before you adopt and clears after. Imperative `router.prefetch()` sites have no equivalent insight, so audit them from source and verify them in production ([step 4](#step-4-verify)). Without a browser, skip the click and adopt from [`instant-link-prefetch-partial`](https://nextjs.org/docs/messages/instant-link-prefetch-partial) and the audit table below — the destination's structure tells you the row, and type-check gates the edit — then leave the live confirmation for the hand-off. -2. **Adopt the destination.** Add the temporary route config with a link to the migration guide. That clears the insight for every link pointing at it: +Before writing tests or editing destinations, follow the guide's [migration guidance](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#migrate-existing-full-prefetches) to propose the UI worth preserving. Present the result in one concise table: - ```tsx - // See: https://nextjs.org/docs/app/guides/adopting-partial-prefetching - export const prefetch = 'partial' - ``` +| Navigation | Proposed result | +| ---------- | --------------- | - If the route reads URL data (`params`, `searchParams`), the default link still warms only its skeleton (the guide's [URL data](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#url-data) section), so it's a per-link-prefetch candidate for step 5, not a finished adoption. Keep `prefetch={true}` on its links and mark the route: +Group equivalent navigations. Summarize what will be ready immediately and what will stream. When a proposal is ambiguous, show the navigation in the running app and ask the user to confirm it. If they are unavailable, follow the guide and record the assumption. - ```tsx - // TODO(per-link-prefetch): assess with the user whether URL data should resolve before click. - // See: https://nextjs.org/docs/app/guides/optimizing-prefetching - export const prefetch = 'partial' - ``` +After the target UI is settled, inspect the existing test setup. The `instant()` helper comes from the separate [`@next/playwright`](https://nextjs.org/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) package, not `next/experimental/testmode/playwright`. - Use that exact prefix so step 5 can grep them back. Don't cache or decide anything for these routes now. +- **Applicable production-mode suite:** use test-backed preservation by default. Reuse the project's `@next/playwright` tests, production scripts, authentication, and existing `instant-nav.rig.md`. Follow the guide's [prefetched UI test workflow](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#verify-prefetched-ui-with-tests) and make the complete flag-off suite green before adoption. The unchanged assertions drive the migration and stay as regression coverage. +- **No applicable production-mode suite:** set up the production-mode rig in **`rig-template.md`** using the project's package manager and test conventions. This is part of test-backed adoption and does not require a user to be present. +- **Rig cannot run reliably:** work through **`rig-template.md`** setup and liveness checks. Fall back to manual preservation only for a concrete blocker the repository cannot resolve, such as unavailable credentials or an inaccessible production environment. Record the blocker and the deferred test coverage; do not claim test-backed verification. -3. **Preserve what that prefetch delivered.** The guide's [audit table](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#auditing-link-prefetchtrue-calls) is the canonical decision — fetch it and apply the matching row. Caching uncached content is the judgment call in that table: trace where the data comes from and what freshness and revalidation it needs, per the [`use cache`](https://nextjs.org/docs/app/api-reference/directives/use-cache) docs, and ask the user when the answer isn't clear-cut. The URL-data routes you marked in the previous item wait for step 5. +No user input is required to reuse an existing suite or create the rig. Ask only when the repository cannot answer an environment question or when the target UI itself is a product decision. If no user is available, use the guide's safe product default and reserve manual verification for a concrete rig blocker. Treat new prefetched UI as step 7 work; verify any deliberate removal separately after adoption. - If the repository already has `instant()` e2e coverage for a destination, run it before editing and preserve its assertion as the contract. A successful build or completed navigation does not prove that the same UI was prefetched. If caching the primary data loader still leaves only a fallback inside `instant()`, inspect rendered descendants and providers for dynamic work, then expand the cache only to the smallest coherent rendered subtree that restores the contract. +This workflow is specific to a clicked ``. A direct call such as `router.prefetch('/dashboard')` is a manual prefetch, not a Link prefetch; keep it in the source audit and verify it separately in step 6. + +## step 2: capture the legacy baseline + +Do not enable `partialPrefetching` or edit route behavior, Link props, or cache boundaries during this step. Test-only configuration required to run `instant()` is allowed. + +For test-backed preservation, complete the [preservation gate](#preservation-gate): write the complete `instant()` suite and **run it** against the production-like rig with Partial Prefetching disabled. A test file, build, completed navigation, or command printed for the user is not a baseline. Do not continue to step 3 until the suite has actually passed. + +For manual preservation, finish the before/target inventory before editing any destination. Fall back to this path only for a concrete rig blocker identified through `rig-template.md`, and record the blocker and deferred tests. + +## step 3: adopt destinations and restore the target + +Adopt every audited destination with the temporary route config. The route export is enough for the unchanged tests to exercise Partial Prefetching on that destination while the global flag remains off: + +```tsx +// See: https://nextjs.org/docs/app/guides/adopting-partial-prefetching +export const prefetch = 'partial' +``` + +If other URL-specific UI might be worth prefetching but was not part of the legacy contract, keep `prefetch={true}` on its links and mark the route for step 7: + +```tsx +// TODO(per-link-prefetch): assess with the user whether URL data should resolve before click. +// See: https://nextjs.org/docs/app/guides/optimizing-prefetching +export const prefetch = 'partial' +``` + +Use that exact prefix so step 7 can grep them back. Do not select new target UI now; restore only the target chosen from the legacy behavior. + +For test-backed preservation, rerun the affected **unchanged** tests after each destination changes and treat failures as the work queue. Run the complete suite and record its passing exit status before enabling the global flag. For manual preservation, compare the adopted production navigation with the selected target and document anything not yet restored. Apply the guide's matching preservation pattern for caching and Link-prop changes, and ask the user before making an unclear freshness or caching decision. New URL-data candidates marked above wait for step 7. + +When restoring the target changes caching or invalidation, follow the project's existing verification approach. Reuse or extend an applicable suite for the affected lifecycle, such as freshness after mutations, cache scope, or generated values. If the project doesn't test this type of behavior, do not introduce new test infrastructure during adoption; verify it manually in production and record the expected and observed results. A green `instant()` test proves readiness, not cache correctness. Ask the user only when the intended behavior is unclear. > **If you add `use cache`, verify under `next start`, not only the build.** A `cookies()`/`headers()`/session read anywhere in the cached call tree throws at request time while `next build` passes clean. See [`use cache`](https://nextjs.org/docs/app/api-reference/directives/use-cache). -## step 2: enable the flag +## step 4: enable the flag Once every audited destination has `prefetch = 'partial'`, finish in two moves. 1. **Enable the flag globally.** Set `partialPrefetching: true` in `next.config.ts` (alongside `cacheComponents: true`). Every route is adopted now, so every link is good. -2. **Strip the redundant `prefetch = 'partial'` exports.** Run the first-party `remove-partial-prefetch` codemod rather than a text find-and-replace. It removes only `export const prefetch = 'partial'` and its generated Partial Prefetching guide comment. It leaves other values such as `prefetch = 'force-disabled'` in place, along with your `TODO(per-link-prefetch)` markers and their Optimizing prefetching guide links, which wait for step 5. +2. **Strip the redundant `prefetch = 'partial'` exports.** Run the first-party `remove-partial-prefetch` codemod rather than a text find-and-replace. It removes every `export const prefetch = 'partial'`, including exports below a `TODO(per-link-prefetch)` marker, and removes its generated Partial Prefetching guide comment. The TODO marker and its Optimizing prefetching guide link stay for step 7. Other values such as `prefetch = 'force-disabled'` stay in place. ```bash - npx @next/codemod@latest remove-partial-prefetch ./app + npx @next/codemod@canary remove-partial-prefetch ./app ``` - The codemod refuses to run on a dirty working tree. Commit or stash unrelated work first, or pass `--force` to let its edits land alongside your WIP. If the codemod isn't available (older `@next/codemod`, sandboxed environment, offline run), reproduce it by hand by removing `export const prefetch = 'partial'` and its generated Partial Prefetching guide comment from every `app/**/{page,layout}.{js,jsx,ts,tsx}` — leave other `prefetch` values in place, and leave the `TODO(per-link-prefetch)` markers and Optimizing prefetching guide links where they are. Don't hand-edit when the codemod can run. + Use `./src/app` in a `src/` project and check the reported file count. The codemod refuses to run on a dirty working tree. Commit or stash unrelated work first, or pass `--force` to let its edits land alongside your WIP. If the codemod isn't available (older `@next/codemod`, sandboxed environment, offline run), reproduce it by hand by removing `export const prefetch = 'partial'` and its generated Partial Prefetching guide comment from every `app/**/{page,layout}.{js,jsx,ts,tsx}` — leave other `prefetch` values in place, and leave the `TODO(per-link-prefetch)` markers and Optimizing prefetching guide links where they are. Don't hand-edit when the codemod can run. -## step 3: sweep for URL-data insights (after enabling) +After the flag and codemod land together, rerun the locked preservation suite when using the test-backed path. Otherwise repeat the documented production comparisons under the final global configuration. -This is a dev-only second pass. The shell check runs only with the flag on, fires at navigation time, and never blocks the build, so it can happen any time after step 2. Build the route queue from a concrete source (the last `next build` route table, or the `app/` tree) and keep it as a todo list. +## step 5: sweep for URL-data insights (after enabling) + +This is a dev-only second pass. The shell check runs only with the flag on, fires at navigation time, and never blocks the build, so it can happen any time after step 4. Build the route queue from a concrete source (the last `next build` route table, or the `app/` tree) and keep it as a todo list. Sweep feature by feature. A feature is a single product surface — `app/settings/**`, `app/posts/[slug]/**` — not a whole top-level area. Finish one end-to-end before starting the next: load its routes in `next dev` and resolve their insights. The insight never blocks the build and each route is independent, so a partial sweep leaves a working app, and each feature is a self-contained change the user can review or ship on its own. -If the environment can't finish the whole sweep (slow first compiles, a dev server that falls over under load, no browser at all), take the browser-free work as far as it goes before handing off. Adopt every route you can statically: apply the fix from [`URL data`](https://nextjs.org/docs/messages/instant-shell-url-data) (up to a new `` boundary) and opt the route into `prefetch = 'partial'`, gating on type-check. Work the whole queue in one pass — a larger refactor isn't a reason to defer, and asking whether to continue to the next route or tier isn't a checkpoint; keep going. Stop only for a genuine judgment call, and batch those into the single hand-off report: the routes you statically adopted, the ones still needing a live shell check, and the queue. +If the environment can't finish the whole sweep (slow first compiles, a dev server that falls over under load, no browser at all), take the browser-free work as far as it goes before handing off. Adopt every route you can statically: apply the fix from [`URL data`](https://nextjs.org/docs/messages/instant-shell-url-data) up to a new `` boundary, gating on type-check. Work the whole queue in one pass — a larger refactor isn't a reason to defer, and asking whether to continue to the next route or tier isn't a checkpoint; keep going. Stop only for a genuine judgment call, and batch those into the single hand-off report: the routes you statically adopted, the ones still needing a live shell check, and the queue. Watch the Insights tab and the dev log for `Next.js encountered … data` lines. The signal this step adds is [`URL data`](https://nextjs.org/docs/messages/instant-shell-url-data): a `params` or `searchParams` read too high in the suspended subtree ties the shared shell to one URL. This insight is narrow; it most reliably appears on a `generateStaticParams` route where `params` is already under ``, but still awaited before the URL-specific leaf boundary. If a `blocking-prerender-*` error fires instead, apply the same structural fix. @@ -111,13 +145,15 @@ Loading a route with the flag on prerenders its App Shell, which validates more These fixes rarely involve the user — each insight names the offending read and its docs page has the fix, so apply it and keep sweeping. Collect the rare exceptions for one batched question at the end: a page that is entirely one URL-dependent region (wrapping it all leaves an empty shell), or a route that should arguably stay opted out. Don't narrate the refactor with comments — the `` boundaries speak for themselves. -## step 4: verify +## step 6: verify Checklist before checking in with the user: - **An empty sweep is expected when Cache Components adoption finished cleanly.** A quiet log is success, not a missing signal. If you deliberately probe the validation path, use a `generateStaticParams` route with `params` read inside `` but before the URL-specific leaf boundary; other shapes may surface `blocking-prerender-*` instead. - The App Shells are real: for each route you changed, confirm the first paint after a navigation shows the intended shared content, not an empty shell or a stuck fallback. A `` around the whole page body passes validation with an empty shell, which defeats the point. -- The insights validate shell _structure_, not that a prefetch actually happened. Confirm on the production run (prefetching is prod-only) that navigating a changed link lands on the shared shell instantly. +- The insights validate shell _structure_, not that a prefetch actually happened. Confirm on the production run (automatic prefetching runs only in production) that navigating a changed link lands on the shared shell instantly. +- For test-backed preservation, every locked `instant()` test for an audited `` passes against the production run. For manual preservation, the before/after inventory and any deferred test follow-ups are recorded. +- Any caching or invalidation changed to preserve the target is verified through an applicable existing test suite or a recorded manual check when the project has no such coverage. - **If the app prefetches imperatively**, the insight sweep does not cover it, so an empty sweep is not proof the prefetch survived the flag. Verify the call under `next start`: compare the `_rsc` prefetch response or resource timing before/after, and make sure any intentionally preserved full prefetch still carries the data the old call was warming. If it now returns only the App Shell, migrate that call site using the same decision as the nearest `` destination — cache the data, or move per-link-prefetch behavior to a docs-supported ``. - **Before blaming a broken route on the flag, reproduce it with `partialPrefetching` off** (or on the pre-flag branch). The flag surfaces existing issues — a fragile request-time auth gate, a rewrite, deployment skew — earlier and more visibly, but rarely causes them. If it breaks flag-off too, it isn't a Partial Prefetching problem; fix it there, not here. - `next build` still passes. @@ -126,19 +162,23 @@ Then check in with the user. Speak their language — no insight slugs or step l - What you did: which links you audited, which destinations you adopted, and what each link now prefetches. - What changed: dropped props, `use cache` boundaries added, and which routes carry a `TODO(per-link-prefetch)` marker for later. -- Demo against a production run. Prefetching is limited in development, so `next dev` won't show the result — run `next build` and `next start`, and hand the user that URL. That run needs the app's real environment (database, auth, secrets), and a partial or stale install or leftover generated artifacts can fail the build for reasons unrelated to the adoption. Set the expectation up front that verification is a complete, credentialed production run, not a quick check. +- Demo against a production run. Automatic prefetching runs only in production, so `next dev` won't show the result — run `next build` and `next start`, and hand the user that URL. That run needs the app's real environment (database, auth, secrets), and a partial or stale install or leftover generated artifacts can fail the build for reasons unrelated to the adoption. Set the expectation up front that verification is a complete, credentialed production run, not a quick check. - Show, don't tell: drive one link live in the headed browser against the production server, so they see the shared App Shell paint instantly and the URL-specific region stream in. Attach before/after screenshots only when a live browser isn't possible. - Give them the click-through: a table of each changed route — the link to click, and what to expect after the click (what paints instantly, what streams in) — so they can verify each result themselves. - The question: "Want to commit this (or open the PR) before we look at which routes should also prefetch their URL-specific content?" Wait for the answer — adoption and per-link prefetching read best as their own changes. -## step 5: per-link prefetching (optional) +## step 7: per-link prefetching (optional) -The audit marked the candidates instead of deciding them. Grep for `TODO(per-link-prefetch)` and walk the list with the user in one conversation. The question per route is whether they want the URL-dependent content prefetched ahead of the click, or streaming in after navigation is fine. A per-link prefetch costs a server invocation per prefetchable link — the guide's [trade-offs](https://nextjs.org/docs/app/guides/optimizing-prefetching#trade-offs) section is the checklist. Don't make these calls alone. +The audit marked candidates beyond the already-preserved legacy contract instead of deciding them. Grep for `TODO(per-link-prefetch)` and walk the list with the user in one conversation. The question per route is whether they want the additional URL-dependent content prefetched ahead of the click, or streaming in after navigation is fine. A per-link prefetch costs a server invocation per prefetchable link — the guide's [trade-offs](https://nextjs.org/docs/app/guides/optimizing-prefetching#trade-offs) section is the checklist. Don't make these calls alone. -Where the answer is yes, follow the [Optimizing prefetching guide](https://nextjs.org/docs/app/guides/optimizing-prefetching): keep [``](https://nextjs.org/docs/app/api-reference/components/link#prefetch) on the links that should resolve more than the App Shell, and cache the content behind the URL-data read using the guide's patterns (`use cache` with the runtime value passed in, or `use cache: private` for per-user data). Each per-link prefetch is a server render when the destination needs non-static data, so use the guide's [per-link trade-offs](https://nextjs.org/docs/app/guides/optimizing-prefetching#trade-offs) to decide when viewport prefetching is worth it and when [hover-triggered prefetch](https://nextjs.org/docs/app/guides/prefetching#hover-triggered-prefetch) is a better fit. Where it's no, delete the marker and leave the route on the App Shell default. Either way no `TODO(per-link-prefetch)` marker survives this step. Confirm the opted-in links against a production run (`next build` and `next start` — the per-link prefetch runs there, not in `next dev`), give the user the same click-through for them, and keep this as its own commit or PR. +Where the answer is no, delete the marker and leave the route on the App Shell default. Where the answer is yes, follow the [Optimizing prefetching guide](https://nextjs.org/docs/app/guides/optimizing-prefetching), confirm the opted-in link against a production run, and delete the marker when the selected result is verified. + +No `TODO(per-link-prefetch)` marker survives the finished step. Per-link optimization remains a separate commit or PR from adoption. + +Finally, show any effective `prefetch={false}` links in a concise `Navigation | Why it may no longer be needed` table. Explain that `false` disables all prefetching, while Partial Prefetching's default `auto` behavior prefetches only the shared App Shell, so opt-outs added to avoid legacy full-route prefetching may now be unnecessary. Invite the user to revisit them separately. ## further reading - [Instant navigation](https://nextjs.org/docs/app/guides/instant-navigation) — the broader validation model and loading-state tooling. -- [Prevent regressions with e2e tests](https://nextjs.org/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) — the `@next/playwright` `instant()` helper locks in what a navigation shows immediately; recommend it once the sweep is clean, since nothing else guards these in CI. +- [Prevent regressions with e2e tests](https://nextjs.org/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) — use the `@next/playwright` `instant()` helper to build the flag-off baseline suite, then keep it as the CI regression guard. - [`next-cache-components-optimizer`](https://github.com/vercel/next.js/tree/canary/skills/next-cache-components-optimizer) — grows each route's static shell so the App Shell carries more. diff --git a/plugins/codex/plugins/next-skills/skills/next-partial-prefetching-adoption/rig-template.md b/plugins/codex/plugins/next-skills/skills/next-partial-prefetching-adoption/rig-template.md new file mode 100644 index 00000000..bbfa4c7e --- /dev/null +++ b/plugins/codex/plugins/next-skills/skills/next-partial-prefetching-adoption/rig-template.md @@ -0,0 +1,172 @@ +# Production `instant()` rig + +The preservation suite needs a production build that exposes the Next.js +testing API, a stable URL for that build, and a Playwright command that can +drive the audited Links. Discover this setup once, record it in +`instant-nav.rig.md`, and reuse it throughout adoption. + +Read an existing `instant-nav.rig.md` before creating one. Inspect the +repository before asking the user: + +- `package.json` scripts for build, start, and end-to-end tests +- `playwright.config.*` for `baseURL`, `webServer`, projects, and authentication +- `next.config.*` for existing `experimental` options +- CI, preview deployment, container, and hosting configuration +- test helpers for login, `storageState`, fixtures, flags, and seeded data + +Ask only for details the repository cannot answer, such as unavailable +credentials or which remote environment may expose the testing API. + +## What the rig must define + +### Production build and server + +Use `next build` followed by `next start`, or a remote artifact produced by the +same production build. Automatic prefetching does not run in `next dev`, so a +development server cannot verify preservation. + +Record separate build and start commands. For a local rig, record the port, +stop any previous server before starting, fail on `EADDRINUSE`, and confirm the +new process owns the port before running Playwright. `next start` can fork a +`next-server` child, so the launcher process ID may not own the port. Start the +server in a process group that the rig can stop as a unit, or discover and stop +the process listening on the recorded port before the next build. + +### Testing API + +An `instant()` test against a production build requires +`experimental.exposeTestingApiInProductionBuild`. Gate it so real production +builds do not expose the API: + +```ts filename="next.config.ts" highlight={3,8-10} +import type { NextConfig } from 'next' + +const exposeTestingApi = process.env.EXPOSE_TESTING_API === '1' + +const nextConfig: NextConfig = { + cacheComponents: true, + experimental: { + exposeTestingApiInProductionBuild: exposeTestingApi, + }, +} + +export default nextConfig +``` + +Merge the option into an existing `experimental` object instead of replacing +the project's other experimental options. + +Set the condition while running `next build`. Setting it only for `next start` +is too late because the testing API is compiled into the production artifact. +When the artifact was built without it, Next.js does not activate the +navigation lock, so the test cannot distinguish prefetched UI from streamed +dynamic content. Rebuild with the condition enabled before interpreting the +results. Use the project's existing environment naming when it already +distinguishes test, staging, preview, and production builds. + +### Test command and base URL + +Record the exact Playwright command and how it receives the measured build's +URL. Reuse the project's package manager, Playwright configuration, projects, +and reporters. The suite must import `instant()` from `@next/playwright`. If +the dependencies are absent, install `@next/playwright` on the same release +line as the project's `next`, alongside `@playwright/test`. + +For a local rig, a typical sequence is: + +```bash filename="Terminal" +EXPOSE_TESTING_API=1 pnpm build +pnpm start --port 3000 +BASE_URL=http://localhost:3000 pnpm playwright test tests/prefetch-preservation.spec.ts +``` + +Adapt the script names and port to the project. Keep the production server +running while the test command executes. Follow the public +[client-navigation test](https://nextjs.org/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests): load the source route, confirm the real +Link is visible, then enter `instant()`, click, wait for the destination URL, +and assert the prefetched UI. + +### Test context + +Record the state required to reach the audited Links and destination UI: + +- Use `public; no authentication` when the navigation is public. +- Otherwise record the test account and login mechanism, including a fixture, + `storageState`, API login, or seeded session. +- Record flags, plan, role, locale, seeded data, and other state that can change + which UI the test sees. + +A test user is not required. The field exists to make authenticated and +state-dependent tests reproducible when the app needs one. + +### Drift + +List differences between the state used to choose the preservation target and +the state used by Playwright. Feature flags, permissions, empty test data, and +locale differences can make an assertion fail because the target is +unreachable, not because Partial Prefetching removed it. Write `none known` +only after checking the test context. + +### Iteration loop + +Record the complete loop the agent can repeat without rediscovering commands: + +- Local: build with the testing API, start the new artifact, run the focused + suite, stop the server, edit, and repeat. +- Remote: push, wait for the measured artifact, verify it matches `HEAD`, run + the focused suite against its URL, edit, and repeat. + +Note any step the agent cannot perform without the user, including deployment +approval, protected branches, secrets, or multi-factor authentication. + +### Artifact liveness + +For a remote rig, record how the test proves the deployment matches `HEAD`. +Prefer an endpoint or response header that exposes the deployed commit SHA. If +the app has neither, use the deployment provider's API to select the artifact +whose commit SHA matches `HEAD`. + +A freshly completed local `build` followed by `start` does not need a SHA +probe. Record `n/a; local build and start`. + +### Walls + +Record build and run obstacles with their working resolution, such as required +environment variables, server-only imports that fail during prerendering, +unavailable credentials, or a process that keeps reclaiming the test port. +Reuse these notes on the next iteration. + +## Write `instant-nav.rig.md` + +Place this file at the repository root or next to the end-to-end configuration: + +```md +# instant-nav rig: + +- BUILD: +- EXPOSE: +- RUN: +- TEST USER: ; state: +- DRIFT: +- LOOP: ; agent limits: <...> +- LIVENESS: +- WALLS: +``` + +Every field needs a concrete value. `n/a` is valid only with a reason, such as +`TEST USER: public; no authentication` or `LIVENESS: n/a; local build and +start`. + +## Check the rig before writing the baseline + +Before recording the legacy prefetched UI: + +1. Build with the testing API condition enabled. +2. Start or locate that exact artifact and confirm the base URL responds. +3. Run one focused `instant()` smoke test through a real `` navigation. +4. Confirm the test can reach its source Link and eventual destination UI in + the recorded test context. + +Fix the rig before interpreting a preservation failure. A missing testing API, +stale deployment, unreachable target, or wrong test state is an environment +failure rather than evidence that the migration changed the prefetch. diff --git a/plugins/codex/plugins/ppt-master/README.md b/plugins/codex/plugins/ppt-master/README.md index f319b948..bb9dc089 100644 --- a/plugins/codex/plugins/ppt-master/README.md +++ b/plugins/codex/plugins/ppt-master/README.md @@ -334,7 +334,7 @@ Whatever you state explicitly is followed; whatever you leave unspecified the ag > **Output:** The SVG pipeline has one PPTX converter: it reads `svg_output/` and writes a directly editable native DrawingML deck to `exports/_.pptx`. The default Generate flow runs `finalize_svg.py` and produces self-contained previews in `svg_final/`; PowerPoint's manual **Convert to Shape** command is outside the supported contract. Explicit [quick generation](./skills/ppt-master/workflows/profiles/quick-generate.md) skips Strategist, confirmation, `design_spec.md`, `spec_lock.md`, and `finalize_svg.py`: whatever you state explicitly is followed, and whatever you leave unspecified the agent decides directly in one active context. It still converts sources, researches factual gaps, applies shared mode/style/aesthetic guidance, prepares required images/icons, authors formulas as native inline or block markers, considers native shapes and data visualizations, hand-authors SVG, passes the lockless Quick final quality check, and exports the final PPTX. It writes no substitute plan and cannot resume after context loss. Formula markers compile their LaTeX payload to editable OMML for PowerPoint 2010+; block groups and inline `` runs keep ordinary SVG previews that are replaced during export. Formula rendering and editability in Keynote, WPS, LibreOffice, and other non-PowerPoint clients are not part of this contract. Ordinary export capabilities remain available as needed, including native chart/table replacement, notes, motion, narration, and diagnostics; notes, custom object animation, and narration start off, and the agent may enable them when the request or deck needs them. A default-path Quick export writes the normal postflight report and snapshots `svg_output/` to `backup//svg_output/`; an explicit output path keeps the ordinary no-backup behavior. By default charts and tables export as individually editable SVG-derived DrawingML shapes, which prioritize cross-app visual consistency. Pass `--native-charts-and-tables` to replace eligible groups with PowerPoint-native Chart/Table objects backed by data, which provide **Edit Data** and object-specific controls but may render differently across apps; this variant is saved as `exports/__native_charts_tables.pptx`. Both chart/table export variants are editable—the distinction is the PowerPoint object model, not editability itself. -> **Already have a `.pptx` you want to reuse?** Hand the AI that deck plus your material and ask it to "fill this deck with the new content" — it fills text, table, and chart data into your existing design and exports only the pages you pick, staying natively editable. See the [FAQ](./docs/faq.md) and [template-fill workflow](./skills/ppt-master/workflows/template-fill-pptx.md). +> **Already have a `.pptx` you want to reuse?** Give the AI the deck and material and ask it to "fill this deck with the new content" — Edit Native PPTX keeps the design and unchanged pages byte-for-byte, edits chosen pages, supports selection/reordering, and can add notes or narration. See the [FAQ](./docs/faq.md) and [workflow](./skills/ppt-master/workflows/edit-native-pptx.md). > **Something went wrong?** If the AI loses context, ask it to read `skills/ppt-master/SKILL.md`; for everything else, check the **[FAQ](./docs/faq.md)** — it covers model selection, layout issues, export problems, and more. Continuously updated from real user reports. diff --git a/plugins/codex/plugins/ppt-master/THIRD_PARTY_SOURCE.json b/plugins/codex/plugins/ppt-master/THIRD_PARTY_SOURCE.json index 82140efa..72606c21 100644 --- a/plugins/codex/plugins/ppt-master/THIRD_PARTY_SOURCE.json +++ b/plugins/codex/plugins/ppt-master/THIRD_PARTY_SOURCE.json @@ -2,8 +2,8 @@ "sourceId": "ppt-master", "repo": "https://github.com/hugohe3/ppt-master.git", "ref": "main", - "commit": "ebd74d1f1d61a686f0f80e10abde5029fc4beeca", + "commit": "d6bcaf96b7946667f4a8871b0688b903181db527", "adapter": "claude-skill", "sourcePath": "skills/ppt-master", - "syncedAt": "2026-08-25T16:00:00Z" + "syncedAt": "2026-08-28T16:00:00Z" } diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/SKILL.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/SKILL.md index 50d761ef..6cff44c7 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/SKILL.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/SKILL.md @@ -2,7 +2,7 @@ name: ppt-master description: "多格式源文档到高质量 SVG 页面再导出 PPTX 的多阶段演示文稿生成工作流。" metadata: - version: "5.0.0" + version: "5.1.0" copyright: "Copyright (c) 2025-2026 Hugo He" license: "MIT" official_repository: "https://github.com/hugohe3/ppt-master" @@ -40,8 +40,7 @@ use CWD, or assume a repo checkout. If unavailable, ask; never search or guess. | Generate PPTX — ordinary Default | [`workflows/generate-pptx.md`](workflows/generate-pptx.md) | | Generate PPTX — ordinary explicit Quick | [`workflows/profiles/quick-generate.md`](workflows/profiles/quick-generate.md) | | Create Template | [`workflows/create-template.md`](workflows/create-template.md) | -| Fill Native PPTX | [`workflows/template-fill-pptx.md`](workflows/template-fill-pptx.md) | -| Enhance Native PPTX | [`workflows/native-enhance-pptx.md`](workflows/native-enhance-pptx.md) | +| Edit Native PPTX | [`workflows/edit-native-pptx.md`](workflows/edit-native-pptx.md) | **Hard rule — selected authority only**: Do not load another top-level route's procedure after routing. Image to PPTX and Beautify are mutually exclusive; diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/artifact-ownership.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/artifact-ownership.md index de38385e..16d7d52d 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/artifact-ownership.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/artifact-ownership.md @@ -41,13 +41,13 @@ history, or resumable planning state. Context loss restarts the Quick run. | `analysis/page-context/P.usage.json` | Derived optional context telemetry | Measured on-demand page-context size plus hashes of owning inputs/references | `page-context --record-usage` deterministically replaces only the invoked page's snapshot; `page-context-report` summarizes existing snapshots. Telemetry may be partial. Use token data to evaluate context cost, never as content or an execution contract. | | `images/` | Runtime image pool | User, extracted, AI, web, slice, and EMF/WMF assets | Default Step 5 or Quick Generate resource preparation writes here; `analysis/image_analysis.csv` derives from current contents | | `images/image_prompts.json`, `image_queries.json`, `image_sources.json` | Conditional resource contracts | AI/web execution status and provenance | Create only for a triggered path, including Quick. They guide preparation/attribution, never page design. | -| `icons/` | Prepared project icon pool | Bundled icons copied by `icon_sync.py` plus user-provided, template, imported, or custom icon SVGs | SVG authoring may choose any icon in this project-local pool per page; `spec_lock.icons.inventory` indexes the default plan's curated synced bundled pool rather than assigning page usage or defining an exhaustive whitelist. Exporter global fallback is legacy compatibility only. | +| `icons/` | Prepared project icon pool | Bundled icons copied by `icon_sync.py` plus user-provided, template, imported, or custom icon SVGs | SVG authoring may choose any icon in this project-local pool per page; `spec_lock.icons.inventory` indexes the default plan's curated synced bundled pool rather than assigning page usage or defining an exhaustive whitelist. Preview, finalization, checking, and export resolve only complete `library/name` references under this root. | | `${SKILL_DIR}/templates/{brands,styles,layouts,decks}/*_index.json` | Library discovery indexes | The complete registered option source for Default Stage-1 template selection and chat listing | The UI server or chat branch reads these indexes only to populate the Stage-1 choice, after the communication recommendation is authored. Never scan kind directories to add options or use index summaries as Stage-1 planning evidence. Derive a library root from kind + entry id. Exact unregistered roots remain explicit inputs. Quick does not read the catalog. | | `templates/` | Project template reference | Stage-1-confirmed non-free selection or Quick direct-input installed specs, one file per selected workspace, the effective structural SVG roster (Layout when present, otherwise Deck), and non-image assets | Default template-aware Strategist work from Stage 2 onward, Quick's current agent before direct authoring, and every later role read this project-local state only, never the library/external installation root. The active planner reads every installed template Design Spec and the effective structural SVG roster; Brand and Style are intentionally roster-free, and Deck structure is shadowed when Layout is present. Continuous Executor reuses that context; fresh Executor reads the Design Spec once and each selected complete SVG, when any, only before first use or after its SHA changes. | | `templates/template_execution_manifest.json` (`v1`) + `templates/template_execution/*.text-slots.json` (`v2-min`) | Derived template index | Compact prototype/source-import summary plus per-prototype text-slot diagnostics; the sidecar integrity hash is tool-only | Materialization may publish these deterministic records, but page-context does not inject or require them and models do not read them during page authoring. The complete prototype SVG is the sole visual/template authority; never author from either JSON artifact. | | `/svg/` | Imported native-payload backing | Complete PPTX-derived metadata, hidden carriers, fallback evidence, and source structure | Keep immutable; create-template materialization may resolve a validated source ref against these files, but models do not edit or bulk-read them | | `/svg-flat/` | Optional complete-page verification backing | Self-contained visual composition generated only by explicit `--inheritance-mode both` | Keep immutable when requested; never use as authoring or materialization input | -| `/authoring-svg/` | Template-creation author source | Layered editable SVG IR for imported Master, Layout, and Slide objects | Template_Designer reads and edits this bundle; final template SVGs are materialized from it rather than copied from lossless backing | +| `/authoring-svg/` | Template-creation author source | Canonical compact layered SVG IR for imported Master, Layout, and Slide objects | The PPTX import transaction publishes it already normalized and decoration-factored; Template_Designer reads and edits it, and final template SVGs are materialized from it rather than copied from lossless backing | | `/authoring-svg/authoring_summary.json` | Model-readable authoring index | Current SVG roster plus compact per-file canvas, size, text, image, vector, placeholder, and source-ref counts | Models read this before authoring SVGs; regenerate after direct IR edits | | `/authoring-svg/authoring_manifest.json` | Tool-only authoring provenance contract | Per-document source/authoring hashes and document-local source-ref paths | Generated atomically with the IR; materialization validates it before reusing native payload; never load it into model context or duplicate raw payload here | | `/authoring-svg-flat/` | Optional complete-page verification IR | Self-contained page composition view with its own summary and provenance manifest | Generate only from an explicitly requested `svg-flat/`; use to verify composition, while layered `authoring-svg/` remains the canonical editable source | @@ -56,11 +56,11 @@ history, or resumable planning state. Context loss restarts the Quick run. | `confirm_ui/recommendations.stage1.json`, `.stage2.json` | Confirmation proposals | Template-independent communication contract, then template-aware complete solution plus production mechanics | Author the Stage-1 communication recommendation without using candidate indexes or workspaces as evidence; candidate display state may be prepared independently. Its page confirms communication plus template mode/selection in one submission. Create Stage 2 only after the selection is installed or free design closes and the handoff/equivalent state is ready. `template_application` decides only how to use installed project-local state. The active unconfirmed stage may be overwritten; normal progression leaves confirmed Stage 1 intact. | | `confirm_ui/result.json` | Confirmation result | Persisted user-confirmed input evidence | Generate Step 4 reads the final object once into active context; Strategist consumes it completely into `design_spec.md`. Normal downstream work does not reopen it; fresh recovery may read it once when no retained final state exists. | | `svg_output/` | Page-design author source | Main-agent handwritten SVG pages containing the complete visible design, including each native formula's exact LaTeX marker and ordinary SVG preview | Quality checker and native PPTX export read this as the canonical visual/page-layout source; templates and locks do not add missing visible objects at export. Formula export replaces only its explicit marker subtree under [`native-formula.md`](./native-formula.md). | -| `notes/total.md` | Conditional speaker-note source | Complete notes before splitting | Step 6 writes only when the effective Speaker Notes outcome is enabled; Step 7.1 splits | +| `notes/total.md` | Conditional speaker-note source | Complete notes before splitting | Step 6 writes only when the effective Speaker Notes outcome is enabled; Step 7.1 or Quick §4 splits | | `notes/slide_*.md` | Conditional split notes | Per-slide notes generated from `total.md` | Derived by `total_md_split.py` only when speaker notes are enabled | | `svg_final/` | Default-only derived visual preview | Self-contained post-processed SVGs that may be opened directly or inserted as SVG pictures | Default rebuilds it from `svg_output/` with `finalize_svg.py`; Quick omits it. Never use it as a supported PPTX source. | | `validation/workflow.log` | Cold workflow audit log | Append-only Python command envelopes, material tagged outcomes, bounded warning/OK/stderr samples, per-run omission counts, and selective manual entries for important details with no owning Python output | `project_manager.py init` creates the log and records its milestone. Later project-scoped Python tools find that existing log through the shared CLI bootstrap and record the bounded audit selection without a wrapper command; their full console output is not copied. A helper whose arguments/cwd do not identify the active project receives `PPT_MASTER_PROJECT_PATH=` on the same Python command. A role may run `workflow_log.py` once for a material non-Python stage handoff or rework reason, user-approved exception, or manual recovery choice; never duplicate artifacts, routine progress, or private reasoning. Detached services retain detailed output in their component logs. Never read this log during normal generation/resume or use it as stage, artifact, or quality authority; inspect it only for an explicit user-requested run review. In Quick it remains an incomplete operational audit, not a design history or resume source. | -| `validation/svg_quality_report.json` | Final SVG quality provenance | Final SVG gate split into blocking / introduced / inherited / source-import categories, bound to the checked SVG bytes by SHA-256 | Default runs `svg_quality_checker.py --stage final --json`; Quick adds `--quick-generate` so the checker ignores Design Spec/lock and validates the lockless flat roster. Export links the report only when fingerprints match; Quick requires that link to pass before PPTX creation. | +| `validation/svg_quality_report.json` | Final SVG quality provenance | Final SVG gate split into blocking / introduced / inherited / source-import categories, bound to the checked SVG bytes by SHA-256 | Default runs `svg_quality_checker.py --canonical-authoring --stage final --json`; Quick adds `--quick-generate` so the checker ignores Design Spec/lock and infers flat versus structured validation from the complete SVG roster. Export links the report only when fingerprints match; Quick requires that link to pass before PPTX creation. | | `validation/.report.json` | Published-package audit | PPTX package/resource postflight status, part counts, and quality-gate linkage | Both Generate profiles write it and emit `[POSTFLIGHT]` after package validation. | | `exports/` | Delivery artifacts | Native DrawingML PPTX and explicit native-object/narration variants | Default Step 7.3 or Quick direct export writes final deliverables from `svg_output/`. | | `backup//svg_output/` | Default-path frozen author-source archive | Re-export source without re-running LLM | Both Generate profiles write a snapshot for default-path exports; explicit `-o/--output` skips it. | @@ -77,7 +77,7 @@ history, or resumable planning state. Context loss restarts the Quick run. | PPTX structure | `analysis/.slide_library.json` owns native geometry, slot facts, and SmartArt layout/relationships for direct PPTX workflows. The Beautify inventory only deterministically inlines the subset required by its validation contract; neither the full ledger nor its stdout projections replace `slide_library.json` as the native-structure fact source. | | Design contract | Final confirmation once → audited `design_spec.md` → optional same-file refinement/approval → context-authored lock. Never maintain a parallel draft/lock. Executor may apply `Template Application` prose but never replace identity. Repair divergence from the approved Design Spec/context unless it fails active-decision fidelity. | | Flat packaging authority | Free-design, brand-only, Style-only, and every plan with `template_reuse_scope: style` declare `pptx_structure.mode: flat` and omit `pptx_masters`, `pptx_layouts`, `page_pptx_layouts`, and `page_layouts`. A Style installed alongside Layout/Deck changes only Direction / method and does not force the non-Style structure plan to flat. `svg_output/` owns the complete Slide-local visual design without root Master/Layout identity, fixed-layer ownership, or placeholder metadata. Export materializes one clean project-owned Master plus one Blank Layout, applies the locked theme defaults, removes stock content placeholders/Layout inventory, and retains only the standard date/footer/slide-number capability hooks. | -| Template structure authority | `template_reuse_scope: mirror|layout` uses `page_layouts` for each page's authoring-input prototype. `pptx_masters` / `pptx_layouts` own the unique reusable output definitions, while `page_pptx_layouts` owns page assignment. Strict keeps the prototype contract; adaptive may use a current or new Layout already declared by Strategist. A construction-discovered structural change returns upstream for definition and assignment repair before authoring resumes. Mirror additionally preserves literal visuals/text topology; layout allows project-controlled reflow/re-skinning. Unused definitions may register without a published Slide. Templates validate provenance but never add missing visible page objects during export. | +| Template structure authority | `template_reuse_scope: mirror|layout` uses `page_layouts` for each page's authoring-input prototype. `pptx_masters` / `pptx_layouts` own the unique reusable output definitions, while `page_pptx_layouts` owns page assignment. Strict keeps the prototype; adaptive Layout choice is authorized by Strategist or Quick's frozen Template Application. A construction-discovered structural change returns upstream for definition and assignment repair before authoring resumes. Mirror additionally preserves ordinary authored visuals/text topology; only a JSON-first Chart/Table's derived preview children may regenerate while its metadata/structure remain fixed. Layout allows project-controlled reflow/re-skinning. Unused definitions may register without a published Slide. Templates validate provenance but never add missing visible page objects during export. | | Fact classes | External facts resolve through `sources/*.facts.json`; invented demo KPIs/targets/internal ratios are labeled `scenario` in `design_spec.md §IX` and visibly in the page. Never promote scenario data into the external fact registry. | | Imported-template authoring | Editable SVGs under `authoring-svg/` own create-template edits, `authoring_summary.json` owns model-facing orientation, and `authoring_manifest.json` owns tool-only source-object identity. Lossless `svg/` owns immutable native payload and fallback evidence; optional `svg-flat/` owns only complete-page verification. Materialized `templates/*.svg` own the validated deliverable contract and contain no IR-only source refs. | | Legacy template input | Old unmapped/distilled/preserve structured projects and incomplete template packages are not migrated in place. [`create-template`](../workflows/create-template.md) authors a new current workspace: original PPTX Type A may preserve existing native topology in mirror; legacy SVG-only Type B is visual reference for `standard` / `fidelity`. Intentional free-design, Brand-only, and Style-only `flat` projects are already current. The exporter does not migrate or visually cluster legacy structure. | @@ -86,7 +86,7 @@ history, or resumable planning state. Context loss restarts the Quick run. | SVG source | `svg_output/` is the only author source for generated pages. | | Page-design closure | On SVG-authoring routes, every visible exported-slide object exists in the corresponding page SVG or an explicitly referenced visual asset. | | Package-behavior separation | Speaker notes, animations, transitions, narration, and direct native-PPTX workflows keep their owning artifacts; do not force them into SVG metadata. | -| Post-processed SVG | In Default Generate, `svg_final/` is disposable, must be rebuilt in Step 7.2, and serves only as a self-contained visual preview / manually insertable SVG picture. Quick omits it. | +| Post-processed SVG | In Default Generate, `svg_final/` is an optional disposable preview rebuilt by Step 7.2; it serves only as a self-contained visual preview / manually insertable SVG picture. Quick omits it. | | Workflow audit log | `validation/workflow.log` automatically records each project-scoped Python command envelope, all explicit error/failure and receipt/report lines, bounded warning/OK/stderr samples, summary context, and omission counts, plus explicitly selected manual audit entries. It does not retain the full console stream or automatically capture direct file-authoring actions, host-native tools, pre-project conversion, binary-buffer writes, hidden child output, or detached-service activity; absence of a detail line proves nothing about those stages. Automatic recording failure is advisory and never changes the owning tool's outcome; a failed explicit manual append reports failure because no entry was recorded. | | Export source | The only supported generated-PPTX route reads `svg_output/` through the project SVG-to-DrawingML converter. A diagnostic `-s final` override does not change ownership or create a supported release route. | | Shape-conversion boundary | PowerPoint's manual Convert-to-Shape operation on `svg_final/` is outside the project compatibility contract. | @@ -111,7 +111,7 @@ contract. | `/authoring-svg/authoring_summary.json` | Current authoring SVGs plus tool-only manifest roster | `python3 ${SKILL_DIR}/scripts/svg_authoring_view.py /authoring-svg --refresh-summary`; in-place vector/picture extraction refreshes it automatically | | `notes/slide_*.md` | `notes/total.md`, when speaker notes are enabled | `python3 ${SKILL_DIR}/scripts/total_md_split.py ` | | `svg_final/` | `svg_output/` plus project assets | `python3 ${SKILL_DIR}/scripts/finalize_svg.py ` | -| `validation/svg_quality_report.json` | `svg_output/`, plus locks/template provenance in Default Generate | Default: `python3 ${SKILL_DIR}/scripts/svg_quality_checker.py --stage final --json`; Quick: append `--quick-generate` | +| `validation/svg_quality_report.json` | `svg_output/`, plus locks/template provenance in Default Generate | Default: `python3 ${SKILL_DIR}/scripts/svg_quality_checker.py --canonical-authoring --stage final --json`; Quick: append `--quick-generate` | | Native PPTX + `validation/.report.json` | `svg_output/` plus notes/assets and final quality report | `python3 ${SKILL_DIR}/scripts/svg_to_pptx.py ` | | Quick native PPTX | `svg_output/`, prepared resources, passing Quick final report | `python3 ${SKILL_DIR}/scripts/svg_to_pptx.py --quick-generate` | diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-base.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-base.md index bbf6d9fb..080c95ec 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-base.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-base.md @@ -25,7 +25,7 @@ Evaluate branches from each object's actual information model, not only from a C **Hard rule — flat PowerPoint structure**: Free-design, brand-only, Style-only, and every `template_reuse_scope: style` project use `pptx_structure.mode: flat`: write no root Master/Layout identity, `data-pptx-layer`, or `data-pptx-placeholder`; every visible object remains Slide-local, and the root declares exactly one canonical `data-pptx-page-role` (`cover` / `toc` / `section` / `content` / `ending`). A Style workspace supplies reusable communication/design direction, composition rhythm, and information-expression defaults without page prototypes. Its identity-adjacent color, typography, icon, and image defaults yield to the final Brand/Deck identity and confirmed project lock. When a Style is installed alongside Layout/Deck, it changes only Direction / method and follows the resolved non-Style structure route. Export materializes one clean project-owned Master plus one Blank Layout from the current lock. Add `data-pptx-role` only to structural page-frame objects whose package, page-number, or animation behavior is not already expressed by specialized metadata; the marked element uses a stable unique `id`. See [`semantic-svg.md`](./semantic-svg.md). -**Hard rule — supported PPTX route**: The only supported generated-PPTX path is `svg_output/` through the project SVG-to-DrawingML converter. Step 7.2 still generates `svg_final/` as a mandatory self-contained visual preview that may be inserted as an SVG picture. Do not treat PowerPoint's manual Convert-to-Shape operation as an authoring target or compatibility requirement. +**Hard rule — supported PPTX route**: The only supported generated-PPTX path is `svg_output/` through the project SVG-to-DrawingML converter. Step 7.2 generates `svg_final/` as an optional self-contained visual preview that may be inserted as an SVG picture; its absence never blocks export. Do not treat PowerPoint's manual Convert-to-Shape operation as an authoring target or compatibility requirement. > Note: this rule covers page design only. Speaker notes, animations, transitions, narration, and direct native-PPTX workflows retain their separate artifacts and package-level processing. @@ -33,11 +33,10 @@ Evaluate branches from each object's actual information model, not only from a C ## 1. Effect Capability Discovery -**Mandatory — select by visual job**: establish each page's semantic skeleton, -then run the already-loaded [`svg-effects.md`](./svg-effects.md) §6.1 procedure -before finalizing, with its Visual Job Router as recall; use §6.13 for a -coordinated page recipe when useful. The catalog expands construction -vocabulary; it creates no effect quota. Active cross-page continuous action +**Reference — effects vocabulary**: [`svg-effects.md`](./svg-effects.md) §6.1 +lists the visual jobs an effect can serve, with its Visual Job Router as recall, +and §6.13 offers coordinated page recipes. The catalog expands construction +vocabulary. Active cross-page continuous action additionally loads [`animations.md`](./animations.md) §3.1 before authoring both endpoints. @@ -52,7 +51,7 @@ baked/alternative-only. ## 2. Design Parameter Confirmation (Mandatory Step) -Before the first SVG page, output a confirmation listing: the compact communication objective, canvas dimensions, body font size, color scheme (primary/secondary/accent HEX), font plan, and the live-preview URL reported by the launcher. If the preview launch failed, state that failure before generating SVGs instead of silently proceeding. Prevents purpose/spec/execution drift. +Before the first SVG page, output a confirmation listing: the compact communication objective, canvas dimensions, body font size, color scheme (primary/secondary/accent HEX), font plan, the measured line capacity of the body and annotation roles (run `python3 ${SKILL_DIR}/scripts/text_measure.py measure` on one representative CJK/Latin line per role and state "≈ N chars per W px"; the checker's module check adds DrawingML wrapping headroom, so a hand count of characters × font size underestimates by roughly 15%), and the live-preview URL reported by the launcher. If the preview launch failed, state that failure before generating SVGs instead of silently proceeding. Prevents purpose/spec/execution drift. ### 2.1 Execution context validity (Mandatory) @@ -70,17 +69,17 @@ Consume stdout directly; stop on non-zero exit. The projection is derived, not a **Same-context repair**: in a valid uncompacted context, a bounded repair that preserves roster/order/identity/communication needs only affected Design Spec/lock fragment readback plus `project_manager.py validate`. Any broader or invalid-context repair requires the complete reads above. -**Hard rule — exact page roster**: `design_spec.md §IX` is the ordered queue: one final slide per entry, with the same id/order. The UI range no longer applies. Never add, drop, merge, split, or reorder; repair/reconfirm the Design Spec first. +**Hard rule — exact page roster**: `design_spec.md §IX` is the ordered queue: one final slide per entry, with the same id/order. Never add, drop, merge, split, or reorder while drawing. In a continuous run the same context may first repair the affected §IX blocks and `page_rhythm` rows and rerun `project_manager.py validate` when the page count stays inside the Stage-1 confirmed range; leaving that range reconfirms Stage 1. **Hard rule — binding selection vs realization**: use Strategist-selected semantic content, resources/paths, structured-template Master/Layout routing keys, core fonts, palette anchors, icon-library/stroke anchors, and crop boundaries. Adapt realization—including which prepared project-local icon, if any, best serves each page—without changing those binding selections, except sparse local font/color garnish allowed below. Missing or unresolved material stops execution and returns to Strategist-owned acquisition/failure recovery; never search, generate, download, sync, invent, or substitute it. Binding selection changes require upstream repair. **Reference — planning advice, not a layout lock**: treat §V/§IX `Layout`, cover/closing composition, capability recommendations, §III motif direction, Chart/Table `family/key` construction references, and §VIII image-layout patterns as non-binding inputs. Consider each, then adopt, adapt, or decline it without upstream repair when the same semantic job and every binding user/template/resource constraint remain satisfied. Executor owns final carrier choice, page-scale composition, information-preserving visualization realization, geometry, spacing, coordinates, native preset/Boolean/freeform construction, and effects. -**Hard rule — content vs expression**: `design_spec.md §IX` owns each page's semantic content and supplies complete preferred wording and block texture; those expression choices are not verbatim requirements unless explicitly literal. Executor may paraphrase, condense repetition, regroup or reorder material within the same page, and switch among prose, bullets, keywords, labels, or visual annotation when fit or readability benefits. The result must remain information-equivalent: preserve the `Core message`, `Audience move`, and every substantive claim, fact, data value, proper name, qualifier or caveat, relationship, key argument or evidence, and literal requirement. Never add a claim, move content across pages, or drop information to make the layout fit; return an unfit or underspecified block for Design Spec repair. +**Hard rule — content vs expression**: `design_spec.md §IX` owns each page's semantic content and supplies either complete preferred wording and block texture (`complete` depth) or a short block list (`brief` depth); written expression choices are not verbatim requirements unless explicitly literal. Executor may paraphrase, condense repetition, regroup or reorder material within the same page, and switch among prose, bullets, keywords, labels, or visual annotation when fit or readability benefits. The result must remain information-equivalent: preserve the `Core message`, `Audience move`, and every substantive claim, fact, data value, proper name, qualifier or caveat, relationship, key argument or evidence, and literal requirement. Never add a claim, move content across pages, or drop information to make the layout fit; return an unfit or underspecified block for Design Spec repair. Use named lock roles literally when that role applies, and use optional `Template Application` from the retained Design Spec. Choose contextual page-local values from the Design Spec, style, content, and current composition rather than forcing every object into a lock row. A page-context delta overrides neither facts nor constraints. Deprecated `page-context --bundle` is a compatibility no-op. -**Source verification**: §IX owns the complete page brief; the page delta does not carry the source corpus. Read sources only to resolve listed `Fact IDs` or verify required claims, quotes, names, or data. Do not add facts, claims, or selected content. Return underspecified blocks for Design Spec repair. +**Source verification**: §IX owns the page brief at its confirmed depth; the page delta does not carry the source corpus. Read sources only to resolve listed `Fact IDs` or verify required claims, quotes, names, or data. Do not add facts, claims, or selected content. Return underspecified blocks for Design Spec repair. **Per-page communication trace**: Read `communication.objective`, `communication.core_message`, and the current §IX `Core message` + `Audience move` before choosing composition. The page must advance the compact objective and move the audience as authored in §IX; the global core message remains the deck-wide north star. A page that cannot state this movement is an upstream outline defect — surface `warning: P has no communication move` instead of compensating with decorative layout. Do not invent a new purpose, ask, or outcome at execution time. Structural pages may advance the contract by establishing relevance / tension / decision frame or by completing the final commitment; they are not exempt from having a reason to exist. @@ -92,11 +91,11 @@ Use named lock roles literally when that role applies, and use optional `Templat |---|---| | `text` | Make the visible page independently understandable. Preserve complete prose, explicit labels / captions / sources, tables, and necessary detail; use bullets only for genuinely parallel or ordered items. | | `balanced` | Keep the primary claim and its evidence on the page; when notes are enabled, let them add interpretation and transitions. Mix prose, structured evidence, and necessary lists according to their semantic relationship. | -| `presentation` | Make one claim and one dominant visual expression legible at projection distance. Keep visible copy concise; when notes are enabled, put explanation and transitions there instead of creating paragraph dumps or compressed bullet prose. When notes are disabled, rely only on the confirmed presenter/page channels and never omit required content on the assumption that notes will carry it. | +| `presentation` | Make one claim and one dominant visual expression legible at projection distance. Keep visible copy concise; when notes are enabled, explanation and transitions can live there. When notes are disabled, rely only on the confirmed presenter/page channels and never omit required content on the assumption that notes will carry it. | Apply the content-vs-expression contract above within the selected reading mode. Never drop or invent facts to force a mode. When the authored texture materially conflicts with the lock, render the least-destructive faithful composition and surface `warning: P content texture conflicts with consumption_mode ` as an upstream outline issue; do not encode this subjective judgment in the checker. -**Default — authored texture (may override when information-equivalent)**: start from each `design_spec.md §IX Content` block's written texture because it is the Strategist's recommended expression. Keep prose when its continuity carries causal, argumentative, narrative, qualification, or emphasis relationships; use bullets or keywords when the material is genuinely parallel or ordered, or another information-equivalent structure is clearer. Never convert solely because a list is easier to lay out or an inherited template exposes a list slot. +**Default — authored texture (may override when information-equivalent)**: at `complete` depth start from each `design_spec.md §IX Content` block's written texture because it is the Strategist's recommended expression; at `brief` depth each block already carries its phrasing; expand it into page copy under the reading mode. Keep prose when its continuity carries causal, argumentative, narrative, qualification, or emphasis relationships; use bullets or keywords when the material is genuinely parallel or ordered, or another information-equivalent structure is clearer. Never convert solely because a list is easier to lay out or an inherited template exposes a list slot. - **Hard rule — one paragraph, one text frame**: use one `` per prose paragraph, never sibling `` elements for its visual lines. Keep the first authored line as direct text; later lines use direct `` children that repeat parent `x`, retain effective font size, and use positive relative `dy`. An all-`` form may start at `dy="0"`. Default retains these breaks without PowerPoint wrapping; `--reflow-text` enables reflow. Start from the shared leading ranges in [`shared-standards-core.md`](./shared-standards-core.md) §4.2, then adjust for the typeface, reading distance, explicit user/template requirements, and locked style. - **Template precedence**: an inherited slot never overrides the content relationship. If faithful expression needs prose, widen or reflow the container, or drop that card; never convert solely to fill a list slot. @@ -110,12 +109,13 @@ Apply the content-vs-expression contract above within the selected reading mode. **Execution anchors and contextual values**: -- Base icons may use any SVG already prepared under `/icons/`. `icons.library` records the Strategist's primary bundled style choice and `icons.inventory` indexes its curated synced pool; neither assigns icons to pages or limits other project-local assets. `simple-icons` brand marks appear there only when the content actually needs that real brand; they are not a separately confirmed library. -- Illustrated icons are prepared transparent slice files under `images/` and follow [`executor-image.md`](./executor-image.md), even when they perform the same compact semantic job as an SVG icon. Never move them into `icons/`, add them to `icons.inventory`, or render them through ``. Use or combine them with prepared SVG icons when the page benefits, keeping the result visually coherent and applying no coverage quota. +- Base icons may use any SVG already prepared under `/icons/`. `icons.library` records the Strategist's primary bundled style choice and `icons.inventory` indexes its curated synced pool; neither assigns icons to pages or limits other project-local assets. `simple-icons` entries are real brand marks; they are not a separately confirmed library. +- Illustrated icons are prepared transparent slice files under `images/` and follow [`executor-image.md`](./executor-image.md), even when they perform the same compact semantic job as an SVG icon. Never move them into `icons/`, add them to `icons.inventory`, or render them through ``. - Core color roles retain their meaning. Derive tints, shades, alpha, gradients, and effects; preserve natural asset colors; and use sparse page-local accents for differentiation/ornament. They must not become a competing or recurring palette. +- §V `Spacing anchors` (page margin, block gap, column gutter, corner radius, body leading) are deck-wide identity anchors: reuse them on every page and depart only for a page job, never to make content fit. - Resolve structural families by role: exact `_family` first, then `title_family` for title roles or `body_family` for other unoverridden roles, then legacy `font_family`. Never flatten declared role overrides. A sparse export-safe accent family may style short non-structural display/ornament only—never title/body/data/annotation. Recurrence requires upstream selection. - Font sizes use the named `typography` role values as deck-wide anchors. Map every structural text item to a declared role before drawing; never inherit a template placeholder size. Start from the anchor, then use composition and content fit to adjust that occurrence by at most `±2`px. Keep same-page peers consistent and preserve the role hierarchy; bounded adjustment does not create a new role. -- **Core message ≥ `body`**: map the page's primary claim to declared `lead` / `subtitle`, never below the current body treatment. Footnotes, page numbers, and credits use declared `footnote` / `annotation`; do not invent a smaller role. +- **Text roles**: declared `lead` / `subtitle` carry the page's primary claim; `footnote` / `annotation` carry footnotes, page numbers, and credits. Sizes come from declared roles. - **Write unitless px, with at most two decimals.** Structural and mapped-role text uses only its anchor or a value within its `±2`px band; the sparse display-size exception is defined separately below. Do not substitute familiar pt-style numbers or emit long precision tails. - **Sparse display-size exception**: a short non-structural Hero/Display element may use one undeclared size outside all anchor bands at most twice across the deck without a lock row. The third occurrence makes that size recurring: stop and return to Strategist to name the role in the Design Spec and `spec_lock.md`, then read back and validate the affected fragments before reuse. This exception never applies to titles, body copy, subtitles, annotations, footnotes, captions, data labels, or card copy, and nearby sizes must not be introduced to imitate one recurring treatment. - **Prepared decorative lettering**: When the approved plan selects stable artistic lettering as part of the visual, place its prepared AI/slice file as an image asset and keep the ordinary editable title/subtitle in separate native text frames. Do not recreate the asset with layered glyph copies or native WordArt; when the plan keeps that wording as native text and prepares no lettering asset, use ordinary `` without inventing a missing image. @@ -142,9 +142,9 @@ Before drawing each page, look up its entry in `page_rhythm` (key format `P` | Tag | Layout discipline | |-----|-------------------| -| `anchor` | Structural page (cover / chapter / TOC / ending). `mirror` follows its prototype; `layout` retains its structure system. `style` / free design preserves the §IX cover hook or closing takeaway but may adopt, adapt, or decline the recommended composition. Avoid an information-empty generic cover/sign-off unless content, user direction, or template requires it. | +| `anchor` | Structural page (cover / chapter / TOC / ending). `mirror` follows its prototype; `layout` retains its structure system. `style` / free design preserves the §IX cover hook or closing takeaway but may adopt, adapt, or decline the recommended composition. | | `dense` | Information-heavy. Card grids, multi-column layouts, KPI dashboards, tables, and charts are all permitted. This is the baseline behavior. | -| `breathing` | Low-density impact page. Avoid **multi-card grid layouts** — do not organize content as multiple parallel rounded containers (3-card row, 4-card KPI grid, 2×2 matrix rendered as cards). Use naked text blocks, dividers, whitespace, or full-bleed imagery as the content structure. Single rounded visual elements (hero image corners, callouts, tags, one emphasis block) are fine — the rule is about grid structure, not about the `rx` attribute. Proportions follow information weight (not a preset ratio). Typical forms: hero quote, single large number with one-line interpretation, full-bleed image with floating caption, section transition. | +| `breathing` | Low-density impact page. Naked text blocks, dividers, whitespace, or full-bleed imagery can carry the content structure. Proportions follow information weight (not a preset ratio). Typical forms: hero quote, single large number with one-line interpretation, full-bleed image with floating caption, section transition. | > Mechanical repetition comes from reusing the same carrier and topology without a page job—not from semantic cards themselves. Cards remain appropriate when they express real peer grouping, comparison, hierarchy, or capacity; vary rhythm when the content relationship changes. Context recovery follows §2.1. @@ -157,23 +157,40 @@ Before drawing each page, look up its entry in `page_rhythm` (key format `P` ## 3. Execution Guidelines -- **Element grouping (Mandatory)**: wrap each logical Slide-local body unit in a descriptive, page-unique top-level ``. Every visible direct root `` except a compact helper-authored preset atom declares root-coordinate `data-pptx-bounds="x y width height"`; that text-free atom stays top-level when standalone, uses `data-pptx-frame`, and never carries bounds. Frame/native coordinates do not replace bounds on any other group, and placeholder bounds also supply the slot frame. Nested groups need no bounds and any such values are ignored. Checker compares root bounds with the `viewBox`, recursively checks estimable text against its root module with DrawingML wrapping headroom, and independently checks every estimable visible text carrier against the page without that headroom: through `1px` is ignored; module overflow warns through `5%` and fails above it, while larger page overflow always fails. Unestimable visible text receives an advisory warning. Only a wholly off-canvas direct-root Morph endpoint may set `data-pptx-morph-staging="true"`; keep its text inside its own module bounds, use an explicit pair when Morph remains enabled, and never use the marker for partial overflow. Images, shapes, paths, ``, effects, and object frames remain geometrically free. Flat pages use ordinary groups; structured slots already qualify, while titles, direct Master/Layout atoms, and canvas-level static framing may remain root primitives. On flat pages, give a root background image or full-canvas scrim/decoration rectangle a stable `id` plus `data-pptx-role="background"` / `"decoration"`; never wrap it only to silence the advisory. +- **Element grouping (Mandatory)**: wrap each logical Slide-local body unit in a descriptive, page-unique top-level ``. Every visible direct root `` except a compact helper-authored preset atom declares root-coordinate `data-pptx-bounds="x y width height"`; that text-free atom stays top-level when standalone, uses `data-pptx-frame`, and never carries bounds. Frame/native coordinates do not replace bounds on any other group, and placeholder bounds also supply the slot frame. Nested groups need no bounds and any such values are ignored. Checker fails ordinary root-group overlap exceeding `1px` on both axes; structured slots, structural-role groups, and off-canvas Morph staging groups are exempt, but structured Slide-local groups are not. Checker compares root bounds with the `viewBox`, recursively checks estimable text—including both shared multiline forms—against its root module with DrawingML wrapping headroom, and independently checks every estimable visible text carrier against the page without that headroom: through `1px` is ignored; module overflow warns through `5%` and fails above it, while larger page overflow always fails. Unestimable visible text receives an advisory warning. Only a wholly off-canvas direct-root Morph endpoint may set `data-pptx-morph-staging="true"`; keep its text inside its own module bounds, use an explicit pair when Morph remains enabled, and never use the marker for partial overflow. Images, shapes, paths, ``, effects, and object frames remain geometrically free. Flat pages use ordinary groups; structured slots already qualify, while titles, direct Master/Layout atoms, and canvas-level static framing may remain root primitives. On flat pages, give a root background image or full-canvas scrim/decoration rectangle a stable `id` plus `data-pptx-role="background"` / `"decoration"`; never wrap it only to silence the advisory. - **Reference — not a constraint**: top-level groups set semantic and automatic-animation granularity, but they may contain descriptive nested `` edit groups when the page has meaningful internal subunits. Nested groups need no bounds and create no automatic animation step; use or omit them from the page's actual editing semantics, with no default pattern, depth, or quota. -- **Default — size `data-pptx-bounds` as the intended module zone, not a glyph box (may skip when no text is estimable)**: make the zone as generous as the canvas and sibling layout allow, without overlapping another module zone. An untransformed line spans `y - 0.85 × font_size` to `y + 0.35 × font_size`; width uses the shared SVG-to-PPTX per-run estimate and safety headroom. If text does not fit, first expand a zone that has unused non-overlapping space; otherwise reflow or adapt. Larger bounds do not repair off-canvas text. +- **Default — size `data-pptx-bounds` as the intended module zone, not a glyph box (may skip when no text is estimable)**: make the zone as generous as the canvas and sibling layout allow, without overlapping another module zone. An untransformed line spans `y - 0.85 × font_size` to `y + 0.35 × font_size`; width uses the shared SVG-to-PPTX per-run estimate and safety headroom. The same estimator is callable before writing: `python3 ${SKILL_DIR}/scripts/text_measure.py measure|wrap|box ...` measures lines, wraps one paragraph to a max width as ready `` rows, and computes a text block's module zone; one calibration per role serves the deck, and later calls are for lines that approach a limit, batched through `--stdin`. If text does not fit, first expand a zone that has unused non-overlapping space; otherwise reflow or adapt. Larger bounds do not repair off-canvas text. - **Spec adherence**: follow binding color, canvas, typography, identity, resource, and template anchors; apply layout and other Reference directions under §2.1 without turning them into locks - **Template structure**: inherit the native visual framework only for `template_reuse_scope: mirror|layout`; `style` uses the flat route - **Main-agent ownership**: SVG generation must run in the main agent (not sub-agents) — pages share upstream context for cross-page visual continuity -- **Generation rhythm**: P01 → first-page gate → uninterrupted remaining pages → final gate, in one context without batches or mid-run checker calls. +- **Generation rhythm**: P01 → first-page gate → remaining pages with one page gate per first-exercised `not-exercised` item → final gate, in one context without batches or other mid-run checker calls. - **Fact provenance**: when a §IX page lists `Fact IDs`, resolve each ID from `sources/*.facts.json` and keep the claim/value unchanged. Render a compact source footnote using the source name and a short URL/domain when space permits; when speaker notes are enabled, state the attribution naturally there too. When §IX says `Data class: scenario`, place a visible localized `Scenario data` / `情景数据` label adjacent to the affected KPI/chart and, when notes are enabled, state naturally there that the number is illustrative. Never attach an external fact ID to scenario data or let an unlabeled invented KPI look factual. -- **Mandatory — resolve the page carrier mix before coordinates**: decide background paint/field, editable text and optional lettering, native geometry/lines, prepared photos/scenes/illustration/icon assets, and applicable visualizations in one page-level composition decision. Use any suitable subset; omitting a carrier remains valid. Do not finish a text/container layout and then treat the other families as optional decoration. Use only prepared external resources, preserve every binding resource job and constraint, and choose the actual combination, visual weight, z-order, and local native construction from the page message and hierarchy. The resolved style controls treatment and emphasis, never carrier eligibility, image source, or the complete native construction vocabulary. +- **Mandatory — resolve the page carrier mix before coordinates**: decide background paint/field, editable text and optional lettering, native geometry/lines, prepared photos/scenes/illustration/icon assets, and applicable visualizations in one page-level composition decision. Use only prepared external resources, preserve every binding resource job and constraint, and choose the actual combination, visual weight, z-order, and local native construction from the page message and hierarchy. The resolved style controls treatment and emphasis, never carrier eligibility, image source, or the complete native construction vocabulary. At this decision, recall the construction vocabulary already loaded: the resolved style's §1 `Composition geometry`, [`svg-effects.md`](./svg-effects.md) §6.1 Visual Job Router, and [`native-shape-authoring.md`](./native-shape-authoring.md) §2.1 composition lenses. - **Default — stage each page with the style's composition geometry (may override when another page-fit move is stronger)**: an SVG page is a canvas, not a DOM. Resolve the page-scale move from `spec_lock.md`: a preset uses that selected style's §1 `Composition geometry`; `custom` executes `visual_style_behavior` first, then uses §1 geometry only from exact `visual_style_references` that the behavior assigns a shape or composition job. Other bases contribute only their assigned job, and an unreferenced novel custom follows its behavior alone. Treat every listed move as generative vocabulary rather than a finite menu, then apply [`native-shape-authoring.md`](./native-shape-authoring.md) §2.1's shared exact-fit geometry gate. - **Default — consider the planned motif direction (may override when another coherent expression better serves the deck)**: when §III `Theme` recommends a cross-page motif or element family, decide whether it earns a continuity job. If adopted, keep its reuse coherent while varying scale, crop, density, position, and content interaction by page role; otherwise adapt or decline it and establish a more fitting style-consistent expression. An explicit user/template motif remains binding. +- **Ordinary carriers stay ordinary**: cards, icon-and-label rows, color swatches, soft shadows, and gradient fields are everyday slide carriers. Use them whenever content groups, compares, enumerates, or names a color, material, or sample, and give peers one shared treatment. When the subject itself is a color or material, draw it: a swatch is content, its value comes from the source, and it needs no lock row. +- **Reference — everyday device menu (not a constraint, not a quota)**: the pieces most slides are built from; reach for them by page job and let the locked style set their treatment. + +| Device | Typical job | Realization | +|---|---|---| +| Gradient block or band | Cover / chapter field, title backing, zone separation | `` / `` in 2–3 stops of the deck hue | +| Rounded card | One content module among peers, a feature or option block | `` in `secondary_bg`; a shadow only when it floats over a photo or colored panel | +| Icon with label | Feature markers, list prefixes, step or category cues | `` at 32–48 px in an accent or primary role | +| Numbered circle or badge | Ordered steps, ranked items, chapter marks | `` + centered number; oversized numeral for a chapter | +| Color swatch | A color, material, or sample that the content names | `` / `` filled with the subject's own value, labeled with its name and HEX | +| KPI card | Metric name + hero number + trend or comparison | Card + number at the hero size + small annotation; icon optional | +| Takeaway box | One-sentence conclusion under a title | Tinted band (`fill-opacity` 0.06–0.10) with the sentence in lead size | +| Divider or rule | Separate sections, columns, header from body | Hairline `` in `divider`, or a 2 px accent bar | +| Full-bleed image + scrim | Cover, chapter divider, mood page | Image `slice` + directional gradient scrim + floating title | +| Framed or shaped picture | Portrait, product, place, evidence photo | Circle / rounded clip on ``, hairline frame, caption | +| Quote block | Pull quote, testimonial, source sentence | Oversized quotation mark or accent rule + text at lead size + attribution | +| Timeline or step strip | Ordered events or stages | Baseline `` with ticks/nodes, or chevron presets, labels above/below | + - **Inherited containers**: preserve meaningful template frames; restyle radius, fill, stroke, and depth from the active Design Spec and `spec_lock.md`. Selected Chart/Table reference adaptation is owned by [`executor-visualization.md`](./executor-visualization.md); preview effects never override project styling or structural roles. - **Reference — prefer semantic geometry over preset stacks**: for relationships such as ascending, converging, breaking through, or stacking, first compose faithful primitives and exact presets as one page geometry system; use a Boolean only when the contour itself must merge, open, or fragment. Only when neither construction works should one page-specific polygon/path replace a stack of generic arrows. -- **Reference — create depth with restraint**: use rhythm, spacing, typography, accent bars, and subtle tints before shadows. Reserve lift for a few genuinely floating elements; keep peer grids, dividers, and ordinary body containers flat. When material layering itself is part of the resolved visual style, follow that style's hierarchy instead of flattening its body planes. - **Phased generation** (recommended): - 1. **Visual Construction Phase**: generate all SVG pages sequentially for visual consistency. Apply every triggered information-model branch while drawing. **MUST embed one object-scoped plot-area marker** per §IX-named or Quick-promoted value-driven chart object under [`executor-chart.md`](./executor-chart.md) §2; coordinate calibration is a post-generation step (see [`verify-charts`](../workflows/stages/verify-charts.md)). Write every `=yes` native marker plus JSON metadata atomically under [`native-data-interface.md`](./native-data-interface.md) §2. **Reach for native presets** per §3.0 as you draw each page: a block arrow, chevron, banner/ribbon, callout, standard flowchart node, or star is authored through `preset_shape_svg.py` at draw time — decided by the object's intent as you create it, never by scanning finished paths, and never committed to a bare ``/`` when a preset expresses it (a gradient fill/stroke or a pattern fill is the one paint exception — keep those ordinary SVG; when justified, one registered [`svg-effects.md`](./svg-effects.md) §6.4 shadow/glow stays on the helper-authored shape). **First-page gate (Mandatory)**: after completing the first page, run `python3 ${SKILL_DIR}/scripts/svg_quality_checker.py --stage first-page --json` directly without output filtering. Review the whole P01 issue set, make one consolidated edit pass for every error and any selected warnings, then perform one verification rerun. If it still fails, treat that complete output as the next batch; never check between individual fixes. After it passes, draw P02 through the last page without checker calls. - 2. **Quality Check Gate**: only after every planned SVG exists, run `python3 ${SKILL_DIR}/scripts/svg_quality_checker.py --stage final --json` directly on `svg_output/` without `tail` / `head` / `grep` filtering. One run already reports all pages. Review its complete issue set, fix every `error` plus any selected advisory warnings in one consolidated edit pass, then perform one verification rerun. If it still fails, its complete output begins the next batch cycle; never use checker calls to discover or fix one next issue at a time. Every `warning` is advisory: it never sends the page back for required modification, never authorizes automatic rewriting of compatible user syntax, and needs no acknowledgement/disposition line. Recommendation warnings describe the generated-SVG default; fidelity/quality warnings may be surfaced when material, while the existing input remains releasable. Prototype-identical diagnostics are recorded as `inherited`, source conversion losses as `source-import`, changed/new advisories as `introduced`, and release failures as `blocking` in `validation/svg_quality_report.json`. If release truly depends on a condition, it belongs in `errors`. On success, use the exit status and terminal summary; do not open or `cat` the complete JSON into model context. If terminal output is truncated on failure, read only the relevant issue arrays from the report written by that same run. Do NOT defer error handling to after `finalize_svg.py` — finalize rewrites SVG and masks some violations. + 1. **Visual Construction Phase**: generate all SVG pages sequentially for visual consistency. Apply every triggered information-model branch while drawing. **MUST embed one object-scoped plot-area marker** per §IX-named or Quick-promoted value-driven chart object under [`executor-chart.md`](./executor-chart.md) §2; coordinate calibration is a post-generation step (see [`verify-charts`](../workflows/stages/verify-charts.md)). Write every `=yes` native marker plus JSON metadata atomically under [`native-data-interface.md`](./native-data-interface.md) §2, then record its fallback baseline before that page's gate with `python3 ${SKILL_DIR}/scripts/stamp_native_fallbacks.py /svg_output/.svg --write`; rerun it after any later visible edit inside the marker group, because a missing or stale baseline blocks the canonical gate and native Chart/Table export. **Reach for native presets** per §3.0 as you draw each page: a block arrow, chevron, banner/ribbon, callout, standard flowchart node, or star is authored through `preset_shape_svg.py` at draw time — decided by the object's intent as you create it, never by scanning finished paths, and never committed to a bare ``/`` when a preset expresses it; several presets selected for one page are generated in one `preset_shape_svg.py render-batch --input -` round (a gradient fill/stroke or a pattern fill is the one paint exception — keep those ordinary SVG; when justified, one registered [`svg-effects.md`](./svg-effects.md) §6.4 shadow/glow stays on the helper-authored shape). **First-page gate (Mandatory)**: after completing the first page, run `python3 ${SKILL_DIR}/scripts/svg_quality_checker.py --canonical-authoring --stage first-page --json` directly without output filtering. Review the whole P01 issue set, make one consolidated edit pass for every error and any selected warnings, then perform one verification rerun. If it still fails, treat that complete output as the next batch; never check between individual fixes. After it passes, draw P02 through the last page; the only further mid-run checker call is the first-exercise page gate (`--stage page --page `) on the first page that exercises a `not-exercised` item. + 2. **Quality Check Gate**: only after every planned SVG exists, run `python3 ${SKILL_DIR}/scripts/svg_quality_checker.py --canonical-authoring --stage final --json` directly on `svg_output/` without `tail` / `head` / `grep` filtering. One run already reports all pages. Review its complete issue set, fix every `error` plus any selected advisory warnings in one consolidated edit pass, then perform one verification rerun. If it still fails, its complete output begins the next batch cycle; never use checker calls to discover or fix one next issue at a time. Every `warning` is advisory: it never sends the page back for required modification, never authorizes automatic rewriting of compatible user syntax, and needs no acknowledgement/disposition line. Recommendation warnings describe the generated-SVG default; fidelity/quality warnings may be surfaced when material, while the existing input remains releasable. Prototype-identical diagnostics are recorded as `inherited`, source conversion losses as `source-import`, changed/new advisories as `introduced`, and release failures as `blocking` in `validation/svg_quality_report.json`. If release truly depends on a condition, it belongs in `errors`. On success, use the exit status and terminal summary; do not open or `cat` the complete JSON into model context. If terminal output is truncated on failure, read only the relevant issue arrays from the report written by that same run. Do NOT defer error handling to after `finalize_svg.py` — finalize rewrites SVG and masks some violations. 3. **Logic Construction Phase (conditional)**: after SVGs pass the quality check, batch-generate speaker notes for narrative continuity only when the effective Speaker Notes outcome is enabled. **Mandatory — final carrier-receipt review**: The final checker prints one @@ -238,8 +255,9 @@ This decision applies only while drawing a new object. A suggestion never triggers retrospective scanning, contour classification, or automatic upgrading of ordinary SVG during export. -**Hard rule**: do not hand-write `data-pptx-authoring`, `data-pptx-prst`, -`data-pptx-frame`, adjustment metadata, or registry paths. The preset helper +**Hard rule — helper-written metadata**: `data-pptx-authoring`, `data-pptx-prst`, +`data-pptx-frame`, adjustment metadata, and registry paths are written only by +the preset helper; hand-written values fail the checker. The preset helper generates one compact atomic `` from the shared 187-shape registry, with semantic metadata and base paint written once. Rerun that helper when geometry or paint changes; never edit one of its direct paths. @@ -260,7 +278,7 @@ not actions or hyperlinks. **Hard rule — narrow helper scope**: Both helpers print only their documented stdout fragment(s); neither writes a page or chooses layout. Read every returned -fragment and insert it through the normal `apply_patch` page edit; never +fragment and insert it through the normal page edit; never redirect, loop, or batch helper output into `svg_output/`. @@ -274,7 +292,7 @@ Format: `_.svg`. Use one roster-wide zero-padded index width s Strategist chooses at most one primary bundled stylistic library and may select `simple-icons` alone or alongside it; Executor implements from the complete prepared project-local pool. Library details and selection rules: [`../templates/icons/README.md`](../templates/icons/README.md). This section defines placeholder syntax. -> **Prepared-project boundary.** Any SVG already under `/icons//` is valid execution material, whether selected from a bundled library or supplied by the user, a template, or an import workflow. New authoring must resolve there. The global fallback in `finalize_svg.py embed-icons` is legacy compatibility, not permission for Executor to discover or use an unprepared global icon. +> **Prepared-project boundary.** Any SVG under `/icons//` is valid prepared material. Authoring, preview, finalization, and export resolve only complete case-sensitive `library/name` references there; no global or template-source fallback exists. > **Icon identifiers are case-sensitive filenames.** Every `data-icon` value must use the exact project-local relative basename (`tabler-outline/award`, never `tabler-outline/Award`). Strategist records its curated bundled pool in `spec_lock.md`; Executor need not add other already-prepared project-local icons to that inventory. Custom identifiers preserve the custom file's exact case; the pipeline never silently lowercases names. @@ -307,15 +325,13 @@ Strategist chooses at most one primary bundled stylistic library and may select > > **Missing `icons.stroke_width` in an existing stroke-library lock — fixed compatibility default**: use `2`, emit one warning, and continue. New authoring must still declare the field. > -> Icons are auto-embedded by `finalize_svg.py` — no need to run `embed_icons.py` manually. +> Icons are auto-embedded by `finalize_svg.py` — no need to run `svg_finalize/embed_icons.py` manually. **Project-local verification**: verify the exact prepared file before use: ```bash test -f "/icons//.svg" ``` -**Default — purposeful icon use**: choose prepared icons per page when they compress a label, distinguish parallel categories, clarify a process / KPI / state, or improve navigation and visual rhythm. Omit them when imagery, charts, or typography already carry the meaning. There is no coverage quota, and the prepared pool need not be exhausted. - **Missing project-local icon** → return to Strategist's preparation / `icon_sync.py` gate. Do not search the global library, select an alternative, or copy a candidate in Executor. **Hard rule — prepared assets**: Executor may freely combine project-local icons, regardless of namespace or style. It may not acquire a new icon or treat a globally resolvable file as prepared material. @@ -330,7 +346,7 @@ Read typography from `spec_lock.md`: `_family` → `title_family` / `body_ **Missing required field — `typography.font_family`** → stop and return to Generate Step 4 / [`strategist.md`](strategist.md) §6.2 to repair `spec_lock.md`; do not infer a stack from `design_spec.md`. -**Hard rule**: every SVG `font-family` stack MUST resolve to target-installed/approved Latin and EA faces. PPTX writes one face per script; CSS tails affect preview only, and fonts are not embedded. Missing-face substitution is viewer-selected—not guaranteed Calibri or a later stack entry. +**Hard rule**: every SVG `font-family` stack MUST resolve to target-installed/approved Latin and EA faces. PPTX writes one face per script from the stack: the first named Latin face fills `latin`, the first named CJK face fills `ea` and also `latin` when no named Latin face exists, and a generic family (`sans-serif`, `serif`) fills `latin` only when it precedes every named face. Fonts are not embedded. Missing-face substitution is viewer-selected—not guaranteed Calibri or a later stack entry. --- diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-chart.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-chart.md index 630da694..f09f73e6 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-chart.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-chart.md @@ -42,7 +42,7 @@ A dual-axis chart is valid only when both series share the exact time/category domain and the units and visual identities stay unambiguous; otherwise separate the views. -**Per-object completeness**: preserve every authoritative series, category, point, label, unit, qualifier, source, and scale cue needed to read the chart. When the source cannot determine a required scale or derived value, return the ambiguity upstream in Default or resolve it from explicit source facts in Quick; never fabricate it at draw time. +**Per-object completeness**: preserve every authoritative series, category, point, label, unit, qualifier, source, and scale cue needed to read the chart. For a `=yes` chart, the JSON mirrors the drawn fallback item by item in the same edit: legend labels verbatim, point-level exception colors as `point_colors`, the axis scale, the classic `plot_area` taken from the plot-area marker, visible data labels or summary figures as `data_labels` or companion text, and the actual label / axis / grid colors — omit `text_color` and its siblings rather than guess them. When the source cannot determine a required scale or derived value, return the ambiguity upstream in Default or resolve it from explicit source facts in Quick; never fabricate it at draw time. **Hard rule — schedule geometry**: A schedule is a Gantt chart when dates or durations determine each task bar's `x` and `width`, even if the source was a diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-image.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-image.md index bb111fd2..18d7fd35 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-image.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-image.md @@ -24,7 +24,7 @@ Handle images by status; enum and lifecycle: [`svg-image-embedding.md`](svg-imag **Reference syntax**: see [`svg-image-embedding.md`](svg-image-embedding.md). -**Template-bundled images**: [`apply-template-workspace.md`](../workflows/stages/apply-template-workspace.md) copies them into project `images/`. Outside `mirror`, reference `../images/` and never copy a template SVG's bare sibling href: the rendered page lives in `svg_output/`. `mirror` ([`executor-structured.md`](./executor-structured.md) §1.1) keeps hrefs verbatim; export resolves them against `images/`. +**Template-bundled images**: [`apply-template-workspace.md`](../workflows/stages/apply-template-workspace.md) copies them into project `images/`. Every page, including `mirror`, must rebase the same bytes to exact `../images/`; this transport rewrite is not a visual edit. Never retain a bare or source-template href: preview, validation, and export resolve the written path exactly. **Default — active image integration (may override when plain placement is stronger)**: Treat loaded [`image-layout-patterns.md`](./image-layout-patterns.md) @@ -77,7 +77,7 @@ to variety. **Placeholder**: Dashed border `` + description text -**Crop policy**: read the §VIII row and matching lock projection. On every slide that uses a `crop=no-crop` source (or a legacy trailing `| no-crop`), retain one visible complete instance using one of the nine legal anchors with `meet`, never `none`, and no `clip-path`, `mask`, clipping overflow, or nested `` crop viewport. An auxiliary same-slide detail or lens may crop the same source only while that complete instance remains visible. `crop=adaptive` permits but never requires cropping; choose `meet` or focal-safe `slice` from purpose, ratio, focus, and container. A missing or conflicting `source` / `pattern` / `crop` projection returns upstream instead of being inferred during execution; the accurately projected `pattern` remains a preferred expression that may be adopted, adapted, or declined without rewriting the lock. +**Crop policy**: read the §VIII row and its lock projection (`source`, `crop`). On every slide that uses a `crop=no-crop` source (or a legacy trailing `| no-crop`), retain one visible complete instance using one of the nine legal anchors with `meet`, never `none`, and no `clip-path`, `mask`, clipping overflow, or nested `` crop viewport. An auxiliary same-slide detail or lens may crop the same source only while that complete instance remains visible. `crop=adaptive` permits but never requires cropping; choose `meet` or focal-safe `slice` from purpose, ratio, focus, and container. A missing or conflicting `source` / `crop` projection returns upstream instead of being inferred during execution; the §VIII `Layout pattern` remains a preferred expression that may be adopted, adapted, or declined. **Hard rule — same-source addressable crops, only when adopted**: A layout suggestion, including pattern `#M1-11`, never activates this transport. Pattern diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-notes.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-notes.md index a524be7e..a4dd5ee5 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-notes.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-notes.md @@ -56,4 +56,4 @@ Form one coherent argument in intended reading/reveal order: proposition → evi Put transitions naturally in the opening sentence when useful; never label them. Keep one language. Spell out digits or symbols when literal TTS would sound wrong (for example, Chinese "百分之六十八" rather than "68%"). -After `notes/total.md` is complete, return to Generate Step 7.1; the route authority owns splitting and its success criterion. +After `notes/total.md` is complete, return to Generate Step 7.1 (Default) or `quick-generate.md` §4 (Quick); each route owns splitting and its success criterion. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-structure.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-structure.md index d7fd3d68..0bf901d2 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-structure.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-structure.md @@ -103,12 +103,11 @@ Choose the field and map required atoms, then follow: | `labels` | Copy and caveats visibly attach directly or by leader/tether to what they explain; when a node has multiple text roles, cue → claim/value → support → note remains perceptibly descending and absent roles stay absent | | `garnish` | Removing accents leaves all meaning intact | -**Hard rule — relationship before styling**: establish atoms, field, spine, nodes, and necessary edges before palette, type, effects, or containers. Prefer containment, alignment, baselines, and proximity; add lines/Connectors only for real edges, never to make a page look process-like. +**Hard rule — relationship before styling**: establish atoms, field, spine, nodes, and necessary edges before palette, type, effects, or containers. Containment, alignment, baselines, and proximity express relationships without edges; lines/Connectors express real edges. -**Default — visible structural composition (may override for naked-text -rhythm/style)**: Make one relationship-bearing field, spine, node carrier, or -directional shape the page-scale move; never add geometry merely because -Structure is `yes`. When drawn roles interact, resolve relationship-bearing +**Structural carriers**: a relationship-bearing field, spine, node carrier, or +directional shape can be the page-scale move; Structure `yes` by itself adds no +geometry. When drawn roles interact, resolve relationship-bearing parent contour/direction → contact → joint or intentional void → z-order/occlusion → canvas-edge behavior before labels/garnish. Skip inapplicable operations; implicit/direct roles remain container-free. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-structured.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-structured.md index e498c12e..2152f144 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-structured.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-structured.md @@ -18,7 +18,7 @@ Conditional Executor authority for `template_reuse_scope: mirror|layout` with `p | Current page mapping | Read the retained `spec_lock.md page_layouts` row; a page change does not require another file load | | Selected prototype SVG | Read the complete `templates/.svg` once per valid context and reuse it until a known change or context invalidation | -**Hard rule**: The complete prototype SVG is authoritative. An on-demand page-context result may fingerprint it but carries no prototype payload; never author from a roster, manifest, sidecar, filename, or summary alone. +**Hard rule**: The complete Slide prototype SVG is authoritative and already resolves its Master + Layout context. Standalone Master/Layout definition SVGs are invalid. An on-demand page-context result may fingerprint the selected Slide prototype but carries no payload; never author from a roster, manifest, sidecar, filename, or summary alone. Manifest/text-slot files are derived tool metadata, not model inputs. Missing metadata neither invalidates a legacy workspace nor permits text-topology changes. @@ -53,16 +53,16 @@ Resolve the per-page template SVG directly from the owning `spec_lock.md page_la When `spec_lock.md` records the AI-derived `template_reuse_scope: mirror`, Executor switches to a literal replacement path. The workspace capability `replication_mode: mirror` is a prerequisite, not the trigger by itself: 1. **Per-page reference selection** — Strategist selects one mirror page per project page via `spec_lock.md page_layouts` (e.g., `P04: 015_content`). The basename is the mirror filename without extension; Strategist made this choice by reading `design_spec.md §V Page Roster` descriptions, not by guessing. -2. **Copy, don't fill** — use the retained full mirror SVG as the starting point, then edit slide-specific text in place. Preserve every non-text element and every `data-pptx-*` structure attribute verbatim. Do not reopen the same path + SHA merely because another page selects it. +2. **Copy, don't fill** — use the retained full mirror SVG as the starting point, then edit slide-specific text in place. Preserve every ordinary non-text element and every `data-pptx-*` structure attribute verbatim. The sole exception is a direct JSON-first Chart/Table: keep its marker id/kind/authority and metadata unchanged, while derived preview children may be regenerated from that JSON. Do not reopen the same path + SHA merely because another page selects it. 3. **What you may edit** — decide the semantic slot mapping and replacement text only. Change only visible string values already carried by `` and `` nodes that express slide-specific content (title, body, captions, KPI labels, dates, page numbers). Keep the number, order, nesting relationship, and **all attributes** of every `` / `` node unchanged. Never merge or split nodes, move a string between nodes, add a new tspan, or delete an empty carrier. `svg_quality_checker.py` and export validate attributes, topology, and prototype hashes against the complete prototype internally. -4. **What you must not touch** — element positions, sizes, fonts, colors, fills, strokes, gradients, **which image each `` points at**, `` grouping, sprite-sheet `` wrappers, decorative `` / `` / `` / `` shapes, `` markers, embedded chart data structures. Mirror's value is preserving the source deck's visual identity — any geometric / decorative drift defeats the purpose. **The `href` path is not the image**: normalizing a bare `href="cover_bg.png"` to `href="../images/"` (when Step 3 relocated the asset to `images/`) points at the *same* image and changes nothing visual — that is an allowed path fix, not a fidelity edit. Leaving the bare href as-is is also fine; the exporter and live preview resolve bare hrefs against `images/` either way. +4. **What you must not touch** — element positions, sizes, fonts, colors, fills, strokes, gradients, **which image each ordinary `` points at**, `` grouping, sprite-sheet `` wrappers, decoration, `` markers, or authoritative embedded Chart/Table JSON. JSON-first preview images/shapes are derived and excluded from this literal identity rule. **The `href` path is not the image**: normalize a bare `href="cover_bg.png"` to the exact `href="../images/"` when Step 3 relocates those same bytes to `images/`; this required transport rewrite changes nothing visual. Do not leave the bare href or point back into the source template. 5. **Content fit** — if the replacement needs a different number of text segments/items, do not merge/split nodes, drop sourced content, or restructure the grid. Report `warning: P content does not fit mirror reference ; choose another prototype or change template_reuse_scope to layout/style`, then return to Strategist to select the prototype or scope and update the planning mappings. 6. **Visible text editing** — mirror SVGs may keep literal source text rather than `{{...}}` authoring markers. Edit values in place while retaining imported semantic `data-pptx-placeholder` identity and exact text topology. 7. **Output filename** — follow the standard project SVG naming convention (`_.svg` where `` matches the project page index, not the mirror source index). The mirror filename is the *reference*, not the *output*. **Detecting mirror mode**: read `template_reuse_scope` from the retained lock. `replication_mode: mirror` in the installed template only determines whether that scope is legal; it must never force mirror behavior when the lock records `layout` or `style`. -**Mirror + visualization pages**: Chart, Table, and qualitative topology inside a mirror SVG are already drawn. Replace only permitted text while preserving prototype geometry; do not redraw from a catalog SVG or runtime grammar. A mirror template normally omits `page_visualizations`, and legacy `page_charts` never overrides fidelity. +**Mirror + visualization pages**: Chart, Table, and qualitative topology inside a mirror SVG are already authored. Replace only permitted text and do not redraw from a catalog/runtime grammar. A JSON-first Chart/Table may refresh its approximate preview from unchanged authoritative JSON; this never permits metadata, bounds, marker, slot, or ordinary-visual drift. A mirror template normally omits `page_visualizations`, and legacy `page_charts` never overrides fidelity. **Legacy template boundary**: A template with missing root Master identity, direct atomic placeholders, `data-pptx-layout-kind`, unmapped `baseline`, `preserve`, or `layout_strategy: distill` is not a fallback input. Stop and create a new current workspace through [`create-template`](../workflows/create-template.md) before generation. @@ -72,7 +72,7 @@ Before generating each page, output which template is used: ``` 📝 **Template mapping**: `templates/03a_content_image_text.svg` (free-design routes may use "None") -🎯 **Adherence rules / layout strategy**: [specific description] +🎯 **Adherence rules / application plan**: [specific description] ``` - **Content pages**: template defines only header/footer; content area is free diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-table.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-table.md index 5b62037b..ffce2070 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-table.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/executor-table.md @@ -45,6 +45,7 @@ shared row/column information-model boundary. 3. Place every cell value, unit, qualifier, status, and source-bearing note in its correct intersection. 4. Apply alignment consistently by content role, including comparable numeric alignment and stable header/body hierarchy. 5. Add rules, fills, banding, highlights, and in-cell indicators only after the grid reads correctly in plain form. +6. For a `=yes` table, project the finished grid into the JSON in the same edit: `row_heights`, header fill / text / bold / alignment, whole-row or whole-column fills, first-column emphasis, padding, and per-side `borders` mirroring the drawn rules; a font size plus a uniform border is not a projection. A graphical cell (inset badge, colored chip, mini bar) cannot be expressed by `a:tbl`: return that object to `Native-ready=no`. **Per-cell completeness**: never drop a row, column, summary, footnote, unit, or qualifier to imitate a lighter catalog preview. Reflow text, widen the affected column, rebalance adjacent columns, or increase row height while preserving the active page's information contract and [`executor-base.md`](./executor-base.md) typography bounds. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-base.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-base.md index ec11c65c..d4857b0b 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-base.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-base.md @@ -12,7 +12,7 @@ Active when at least one resource row has `Acquire Via: ai` / `web` / `slice`, o | Mode | Trigger | |---|---| -| Default Generate | `generate-ppt` workflow, `design_spec.md §VIII` image rows present | +| Default Generate | `generate-pptx` workflow, `design_spec.md §VIII` image rows present | | Quick Generate | [`quick-generate`](../workflows/profiles/quick-generate.md) is active and the current main agent has resolved one or more required images in active context | | Standalone | Direct request against an existing project | diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-generator.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-generator.md index 3259fd10..09fd919f 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-generator.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-generator.md @@ -472,7 +472,7 @@ Defaulting an entire `ai` resource list to `none` because "SVG can always overla **Forbidden — text that may be reworded**: any word that may later change belongs in Layer 2, not Layer 1. Layer 1 is for stable visual identifiers and designed lettering that is part of the image itself. -**Default — controlled, deck-aligned artistic authorship (may override when the user explicitly requests high expression or confirms a strongly expressive direction)**: For decorative lettering, give the model the exact intended string, communication role, placement/background relationship, deck identity, relative visual weight, and desired energy. The resolved rendering, semantic colors, mood, and page hierarchy define the envelope. Without the stated override, keep expression controlled and glyph-native: carry identity through the glyph silhouette, stroke construction, internal material/texture, contour-bound depth/light, and letterform composition; do not translate the topic into literal illustrations or detached decoration around the word. A lettering-plus-illustration lockup is a separate treatment and requires an explicit user request or confirmed design direction. Within the chosen treatment, let the model decide and combine—or omit—the calligraphic gesture, material, dimensionality, texture, lighting, internal hierarchy, and composition; such terms are possibility space, not an effect recipe. Do not flatten the art merely to simplify extraction: §4.3's separable-treatment gate, key field, clear padding, and cell isolation protect delivery without raising the chosen intensity. When fit is uncertain, use the lower effect density; never infer high expression or external motifs from the topic, place, or wording alone. Keep a multi-line lockup as one element when its hierarchy is part of the art. +**Reference — controlled, deck-aligned artistic authorship** (the user may request high expression or confirm a strongly expressive direction): For decorative lettering, give the model the exact intended string, communication role, placement/background relationship, deck identity, relative visual weight, and desired energy. The resolved rendering, semantic colors, mood, and page hierarchy define the envelope. Controlled, glyph-native expression carries identity through the glyph silhouette, stroke construction, internal material/texture, contour-bound depth/light, and letterform composition; literal topic illustrations or detached decoration around the word compete with the glyph. A lettering-plus-illustration lockup is a separate treatment and requires an explicit user request or confirmed design direction. Within the chosen treatment, let the model decide and combine—or omit—the calligraphic gesture, material, dimensionality, texture, lighting, internal hierarchy, and composition; such terms are possibility space, not an effect recipe. Do not flatten the art merely to simplify extraction: §4.3's separable-treatment gate, key field, clear padding, and cell isolation protect delivery without raising the chosen intensity. When fit is uncertain, use the lower effect density; never infer high expression or external motifs from the topic, place, or wording alone. Keep a multi-line lockup as one element when its hierarchy is part of the art. **Font choice for in-image text — free description, with the deck typography as one optional reference** diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-searcher.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-searcher.md index 9077b3ff..cf4554e4 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-searcher.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/image-searcher.md @@ -62,6 +62,14 @@ Default chain (when `--provider` is unset): Keyed providers without an API key are silently skipped — not an error. +**Reference — provider fit by subject**: Pixabay's API serves at most a +1280 px long-edge file even when its metadata reports a much larger original, +so a landscape hero row with the default 1200×800 floors can fail promotion on +height; prefer Wikimedia or Pexels originals for full-bleed use. For murals, +manuscripts, artworks, and museum objects, pin `provider: wikimedia` on that +row: stock providers tag tourist snapshots with the place name, and those pass +`required_terms` while showing no artwork at all. + **Default — keyed providers for broader stock coverage (may override when zero-config sources fit)**: Configure Pexels or Pixabay when their stock-photo coverage serves the brief. Their absence is not a validation failure; Openverse and Wikimedia remain valid zero-config acquisition paths. --- diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/native-data-interface.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/native-data-interface.md index 271b0b4c..892f5643 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/native-data-interface.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/native-data-interface.md @@ -1,4 +1,4 @@ -> See [`executor-base.md`](./executor-base.md) for the Shape-first page authority and [`shared-standards-core.md`](./shared-standards-core.md) for the mandatory SVG foundation. +> See [`executor-base.md`](./executor-base.md) for page authoring and [`shared-standards-core.md`](./shared-standards-core.md) for the mandatory SVG foundation and object-local authority boundary. # Native Data Interface @@ -40,7 +40,29 @@ this enum. ## 2. PowerPoint-Native Chart / Table Replacement Markers (Opt-in) -[`executor-base.md`](./executor-base.md) remains the single Shape-first authoring authority: the complete visible SVG fallback is required regardless of native eligibility. This section only adds dormant replacement metadata to independently selected objects and defines how export may activate it. +The complete visible SVG fallback remains required for browser preview and +default export. Chart/Table authority is nevertheless object-local rather than +globally Shape-first: + +- **SVG-first (default)** — free-design, Brand-only, and Style-only authoring + omits `data-pptx-native-authority`. The visible marker subtree is the design + authority; JSON is its derived native projection. Canonical authoring records + `data-pptx-fallback-sha256` only after the fallback and JSON are synchronized. + A later visible edit requires regenerating the JSON and deliberately stamping + a new baseline together. Missing or stale baselines leave default fallback + export available but make `--native-charts-and-tables` fail closed. +- **JSON-first (template/native source)** — a validated PPTX import or a + template-owned Chart/Table writes + `data-pptx-native-authority="json"`. Its inline JSON is the semantic and native + authority; the visible SVG subtree is a derived, compact, and potentially + approximate preview. JSON edits regenerate that preview before template + publication, but preview differences never override the JSON. This marker is + legal only on active `chart` / `table` replacement groups, never formulas or + fallback-only status markers. + +Both forms keep the JSON inside the SVG. `native_payloads.json.gz` is reserved +for supported opaque shape restoration payloads and never replaces semantic +Chart/Table JSON. **Hard rule — selected-object authoring**: write the marker and JSON metadata in the same edit for every supported chart and pure text-grid table; both are @@ -65,7 +87,23 @@ upstream repair. `data-pptx-replace-with` marker, and single JSON `` child as one authoring unit. Write all three while that object's data is in context. Do not defer the marker or metadata to `verify-charts`, the final quality gate, or -export. +export. SVG-first authoring then stamps the completed visible subtree; JSON-first +template authoring instead writes the authority marker and derives its preview +from the inline payload. + +After one SVG-first object's fallback and JSON have been reviewed as the same +authoring transaction, stamp the completed marker explicitly: + +```bash +python3 skills/ppt-master/scripts/stamp_native_fallbacks.py \ + "" --write +``` + +Without `--write` the command is read-only. It validates every Chart/Table +payload before changing anything, skips JSON-first markers, and writes only the +current visible-subtree fingerprint. The hash is a synchronization receipt; it +detects later SVG edits but does not itself prove semantic equivalence, so never +stamp independently authored stale JSON merely to satisfy validation. **Hard rule — activation is the opt-in, dormant unless exported with `--native-charts-and-tables`**: A marker only declares that a group is eligible for PowerPoint-native Chart/Table replacement. Normal `svg_to_pptx.py` runs keep the fallback SVG children and convert them into independently editable DrawingML shapes. Pass `--native-charts-and-tables` only when the data source and chart/table-specific object model matter more than cross-renderer layout fidelity: it emits the PowerPoint Chart/Table object and skips the fallback children to avoid duplicates. Native styling preserves the core palette, text, axis, grid, and background colors where possible, but it is still a PowerPoint Chart/Table object rather than a pixel-identical SVG drawing. @@ -92,7 +130,9 @@ marker/ancestor `translate` and `scale` transforms apply only when at least one bound is inferred. `x`, `y`, `width`, and `height` must be finite and resolve inside PowerPoint's 32-bit DrawingML coordinate range; `width` and `height` must resolve to at least one EMU. Native table frames must additionally resolve -to at least one EMU per resolved row and column. +to at least one EMU per resolved row and column. JSON-first markers require all +four bounds directly in metadata; an approximate preview never supplies their +native frame. **Classic plot-area layout**: supported classic charts accept root `plot_area`; ChartEx rejects it. It contains only finite `x`, `y`, `width`, `height` in @@ -101,11 +141,11 @@ writes `c:manualLayout`; omission keeps automatic layout. **Validation**: `svg_quality_checker.py` validates replacement marker kind, JSON metadata, bounds/fallback availability, table rows/columns, supported chart -type, chart data shape, and any imported fallback baseline before export. - -Imported marker freshness, fallback classification, provenance, and legacy -read compatibility are operational import concerns. Keep generated authoring -free of those attributes; use the exact behavior and field index in +type, chart data shape, and the selected authority contract. Canonical +SVG-first authoring requires a fresh fallback baseline; JSON-first markers skip +fallback freshness because their preview is not authoritative. Import +provenance, fallback classification, and legacy spellings remain operational +compatibility fields; use the exact field index in [`conversion.md`](../scripts/docs/conversion.md#native-table-and-chart-import-claims). ```xml @@ -127,9 +167,12 @@ free of those attributes; use the exact behavior and field index in ``` -**Hard rule — transcribe the authored object**: metadata is the native object's -source of truth and must describe the same data and visible chart/table chrome -as the fallback drawn in that marker group. +**Hard rule — project by the selected authority**: for SVG-first authoring, +metadata is derived from and must describe the same data and visible +chart/table chrome as the fallback drawn in that marker group. For JSON-first +template/native-source objects, the inline metadata is authoritative and the +fallback is only a readable derived preview; approximate preview chrome is not +a contract mismatch. | Object | Required projection from the visible fallback | |---|---| @@ -152,18 +195,30 @@ objects have no marker. Finding one marker somewhere on a page is insufficient. rg -n 'data-pptx-replace-with="(chart|table)"|' /svg_output/.svg ``` -**Table schema**: Native tables are rectangular DrawingML grids. Use `columns` -for the optional header row and `rows` for body rows; shorter rows are padded -with blank cells unless `strict_grid: true` is set. Tables may contain at most -1000 resolved rows and 1000 resolved columns. Use `column_widths` and -`row_heights` as relative weights. Weight lists must match the resolved grid, -contain finite non-negative numbers, and include at least one positive value. -If present, `header_rows` must be an integer from `0` through the resolved row -count. Write `strict_grid`, `style.band_row`, and cell `bold` as JSON booleans. -Cell objects accept `text`, `fill`, `color`, -`align`, `valign`, `bold`, `font_size`, `padding`, `border_color`, and -`border_width`, plus optional `lang`; the same `padding`, `border_color`, -`border_width`, and `lang` keys may also live under `style` as table defaults. +**Table schema — `ppt-master.semantic-table.v2` only**: Every payload requires +that exact `schema`; unversioned payloads and alternate field spellings fail. +Native tables are rectangular DrawingML grids. Use `columns` for the optional +header row and `rows` for body rows; shorter rows are padded unless +`strict_grid: true`. Tables support at most 1000 resolved rows and columns. +`column_widths` / `row_heights` are finite non-negative relative weights that +match the grid and include one positive value. `header_rows` is an integer in +the resolved row range. `strict_grid`, `style.band_row`, and `bold` are JSON +booleans. + +PPTX import factors exact repetition into `defaults.cell`, +`defaults.paragraph`, `defaults.run`, and lower-case kebab-case `cell_styles`; +cells select a style with `cell_style`. Precedence is cell defaults → named +style → cell fields. Only object-valued `padding` merges by member; `borders` +and other values replace. Content/topology stays cell-local. This is JSON +inheritance, not SVG CSS: `'} row={row_index}") - continue - row_cells = rows[row_index].findall("a:tc", NS) - if col_index < 0 or col_index >= len(row_cells): - errors.append(f"{selectors[0] if selectors else '
'} row={row_index} col={col_index}") - continue - target_cell = row_cells[col_index] - if _table_cell_merge_info(target_cell)["is_merge_slave"]: - target = selectors[0] if selectors else "
" - errors.append( - f"{target} row={row_index} col={col_index} is a merged-cell slave " - "[table_cell_is_merge_slave]; edit the merge anchor instead" - ) - continue - pending_edits.append((target_cell, _table_cell_text(cell_edit))) - if errors: - raise RuntimeError(f"Invalid table edit target(s) on slide {source_slide}: {'; '.join(errors)}") - for target_cell, text in pending_edits: - _set_container_text(target_cell, text) diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/text_fill.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/text_fill.py deleted file mode 100644 index d441325a..00000000 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/text_fill.py +++ /dev/null @@ -1,117 +0,0 @@ -"""apply: replace text inside cloned slide shapes while keeping frames editable. - -``_set_container_text`` is the shared text-writing primitive and is also reused -by ``table_fill`` for table-cell edits. -""" - -from __future__ import annotations - -from typing import Any -from xml.etree import ElementTree as ET - -from .ooxml import NS, _qn, _shape_identity, _text_containers -from .selectors import _replacement_text - - -def _shape_key_maps(slide_root: ET.Element, source_slide: int) -> dict[str, ET.Element]: - maps: dict[str, ET.Element] = {} - for order, container in enumerate(_text_containers(slide_root), start=1): - shape_id, shape_name = _shape_identity(container, order) - maps[f"slot_id:s{source_slide:02d}_sh{shape_id}"] = container - maps[f"shape_id:{shape_id}"] = container - if shape_name: - maps[f"shape_name:{shape_name}"] = container - return maps - - -def _ensure_text_nodes(container: ET.Element) -> list[ET.Element]: - text_nodes = container.findall(".//a:t", NS) - if text_nodes: - return text_nodes - tx_body = container.find(".//p:txBody", NS) - if tx_body is None: - tx_body = container.find(".//a:txBody", NS) - if tx_body is None: - return [] - paragraph = tx_body.find("a:p", NS) - if paragraph is None: - paragraph = ET.SubElement(tx_body, _qn(NS["a"], "p")) - run = paragraph.find("a:r", NS) - if run is None: - run = ET.SubElement(paragraph, _qn(NS["a"], "r")) - text_node = run.find("a:t", NS) - if text_node is None: - text_node = ET.SubElement(run, _qn(NS["a"], "t")) - return [text_node] - - -def _ensure_paragraph_text_node(paragraph: ET.Element) -> list[ET.Element]: - text_nodes = paragraph.findall(".//a:t", NS) - if text_nodes: - return text_nodes - run = paragraph.find("a:r", NS) - if run is None: - run = ET.SubElement(paragraph, _qn(NS["a"], "r")) - text_node = run.find("a:t", NS) - if text_node is None: - text_node = ET.SubElement(run, _qn(NS["a"], "t")) - return [text_node] - - -def _set_paragraph_text(paragraph: ET.Element, text: str) -> None: - text_nodes = _ensure_paragraph_text_node(paragraph) - text_nodes[0].text = text - for node in text_nodes[1:]: - node.text = "" - - -def _set_container_text(container: ET.Element, text: str) -> None: - lines = text.splitlines() or [""] - paragraphs = container.findall(".//a:p", NS) - if len(lines) > 1 and paragraphs: - for index, paragraph in enumerate(paragraphs): - if index < len(lines): - _set_paragraph_text(paragraph, lines[index]) - else: - _set_paragraph_text(paragraph, "") - if len(lines) > len(paragraphs): - _set_paragraph_text(paragraphs[-1], "\n".join(lines[len(paragraphs) - 1 :])) - return - - text_nodes = _ensure_text_nodes(container) - if not text_nodes: - raise RuntimeError("Matched shape does not contain a text body") - if len(lines) <= len(text_nodes): - for index, node in enumerate(text_nodes): - node.text = lines[index] if index < len(lines) else "" - return - text_nodes[0].text = text - for node in text_nodes[1:]: - node.text = "" - - -def _apply_replacements_to_slide( - slide_root: ET.Element, - *, - source_slide: int, - replacements: list[dict[str, Any]], -) -> None: - maps = _shape_key_maps(slide_root, source_slide) - errors: list[str] = [] - for replacement in replacements: - selectors = [] - if replacement.get("slot_id"): - selectors.append(f"slot_id:{replacement['slot_id']}") - if replacement.get("shape_id"): - selectors.append(f"shape_id:{replacement['shape_id']}") - if replacement.get("shape_name"): - selectors.append(f"shape_name:{replacement['shape_name']}") - container = next((maps[key] for key in selectors if key in maps), None) - if container is None: - if replacement.get("optional"): - continue - errors.append(", ".join(selectors) or "") - continue - _set_container_text(container, _replacement_text(replacement)) - if errors: - raise RuntimeError(f"Missing replacement target(s) on slide {source_slide}: {'; '.join(errors)}") diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/transitions.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/transitions.py deleted file mode 100644 index b59110d1..00000000 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/transitions.py +++ /dev/null @@ -1,150 +0,0 @@ -"""apply: page-to-page transitions for cloned slides. - -Template Fill preserves each source transition unless the CLI or a per-slide -plan entry requests a replacement. Effects and OOXML mutation come from the -shared ``pptx_transitions`` core so every PPTX path uses the same writer. -""" - -from __future__ import annotations - -from typing import Any -from xml.etree import ElementTree as ET - -from pptx_transitions import ( - AdvanceUpdate, - EnterUpdate, - apply_slide_motion, - normalize_transition_effect_request, - validate_seconds, -) - -KEEP_TRANSITION = "keep" -# Preserve source transitions unless the CLI or a per-slide plan entry selects -# a replacement. The duration is consumed only when a visual effect is written. -DEFAULT_TRANSITION = KEEP_TRANSITION -DEFAULT_TRANSITION_DURATION = 0.5 -TRANSITION_OBJECT_FIELDS = frozenset( - { - "effect", - "effect_options", - "duration", - "advance_after", - } -) - -_UNSET = object() - - -def transition_unknown_fields(raw: dict[str, Any]) -> list[str]: - """Return unsupported fields from a per-slide transition object.""" - return sorted(set(raw) - TRANSITION_OBJECT_FIELDS) - - -def _set_slide_transition( - slide_root: ET.Element, - *, - effect: str | None, - duration: float, - effect_options: dict[str, object] | None = None, - advance_after: float | None = None, -) -> bool: - """Apply a legacy template-fill transition through the shared core. - - ``None`` and ``keep`` preserve the source transition. ``none`` removes the - visual transition while retaining an explicitly requested auto-advance. - Legacy ``advance_after`` allowed both click and timed advance, so it maps to - ``both`` rather than the stricter ``after`` mode. The return value reports - whether the resulting slide contains an automatic advance. - """ - if effect is None or effect == KEEP_TRANSITION: - enter = EnterUpdate(policy="preserve") - advance = AdvanceUpdate( - mode="preserve" if advance_after is None else "both", - after=advance_after, - ) - elif effect == "none": - enter = EnterUpdate(policy="none", effect=None, duration=duration) - advance = AdvanceUpdate( - mode="click" if advance_after is None else "both", - after=advance_after, - ) - else: - enter = EnterUpdate( - policy="replace", - effect=effect, - duration=duration, - effect_options=effect_options, - ) - advance = AdvanceUpdate( - mode="click" if advance_after is None else "both", - after=advance_after, - ) - - try: - return apply_slide_motion( - slide_root, - enter=enter, - advance=advance, - ) - except ValueError as exc: - raise RuntimeError(str(exc)) from exc - - -def _resolve_slide_transition( - item: dict[str, Any], - *, - default_effect: str | None, - default_duration: float, -) -> tuple[str | None, dict[str, object], float, float | None]: - """Pick a slide's transition from its plan entry, falling back to CLI defaults.""" - raw = item.get("transition", _UNSET) - if raw is _UNSET: - if default_effect in (None, "none", KEEP_TRANSITION): - return default_effect, {}, default_duration, None - effect, effect_options = normalize_transition_effect_request( - default_effect, - allow_none=False, - ) - return effect, effect_options, default_duration, None - if isinstance(raw, dict): - unknown = transition_unknown_fields(raw) - if unknown: - raise RuntimeError( - "Transition has unknown field(s): " + ", ".join(unknown) - ) - effect = raw.get("effect", default_effect) - raw_options = raw.get("effect_options") - if raw_options is not None and "effect" not in raw: - raise RuntimeError( - "Transition effect_options requires an explicit effect" - ) - duration = raw.get("duration", default_duration) - advance_after = raw.get("advance_after") - else: - effect = None if raw is None else str(raw) - raw_options = None - duration = default_duration - advance_after = None - effect_options: dict[str, object] = {} - if effect is not None and effect not in ("none", KEEP_TRANSITION): - try: - effect, effect_options = normalize_transition_effect_request( - effect, - raw_options, - allow_none=False, - ) - except ValueError as exc: - raise RuntimeError(str(exc)) from exc - elif raw_options not in (None, {}): - raise RuntimeError( - "Transition effect_options requires an explicit native effect" - ) - try: - resolved_duration = validate_seconds( - duration, - "transition duration", - allow_zero=False, - ) - except ValueError as exc: - raise RuntimeError(str(exc)) from exc - return effect, effect_options, resolved_duration, advance_after diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/validator.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/validator.py deleted file mode 100644 index 492593e0..00000000 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_fill_pptx/validator.py +++ /dev/null @@ -1,491 +0,0 @@ -"""validate: read back the latest template-fill export and check core contract.""" - -from __future__ import annotations - -import re -import subprocess -import sys -from pathlib import Path -from typing import Any, Callable - -from .checker import _chart_lookup, _slot_lookup, _table_lookup -from .ooxml import _load_json, _write_json -from .selectors import ( - _chart_selectors, - _replacement_selectors, - _replacement_text, - _table_cell_text, - _table_selectors, -) - - -_SCRIPTS_DIR = Path(__file__).resolve().parents[1] -_SLIDE_HEADING_RE = re.compile(r"^## Slide\s+(\d+)\s*$", re.MULTILINE) -_SPEAKER_NOTES_RE = re.compile(r"^### Speaker Notes\s*$", re.MULTILINE) -_MARKDOWN_LINK_RE = re.compile(r"(?", re.IGNORECASE) -_TABLE_SEPARATOR_RE = re.compile(r"^:?-{3,}:?$") -_ESCAPED_READBACK_CONTROL_RE = re.compile( - r"^\\(## Slide\s+\d+|### Speaker Notes)$" -) - - -def _latest_export(project_path: Path) -> Path: - exports_dir = project_path / "exports" - candidates = [path for path in exports_dir.glob("*.pptx") if path.is_file()] - if not candidates: - raise RuntimeError(f"No PPTX exports found in: {exports_dir}") - return max(candidates, key=lambda path: path.stat().st_mtime) - - -def _readback_slide_count(markdown: str) -> int: - return len(_SLIDE_HEADING_RE.findall(markdown)) - - -def _slide_sections(markdown: str) -> dict[int, str]: - matches = list(_SLIDE_HEADING_RE.finditer(markdown)) - sections: dict[int, str] = {} - for index, match in enumerate(matches): - end = ( - matches[index + 1].start() - if index + 1 < len(matches) - else len(markdown) - ) - sections[int(match.group(1))] = markdown[match.end():end].strip() - return sections - - -def _split_notes(section: str) -> tuple[str, str]: - match = _SPEAKER_NOTES_RE.search(section) - if match is None: - return section, "" - return section[:match.start()].strip(), section[match.end():].strip() - - -def _normalize_line(value: str) -> str: - return re.sub(r"\s+", " ", value).strip() - - -def _unescape_readback_control_line(value: str) -> str: - """Restore the display text of an escaped read-back section marker.""" - match = _ESCAPED_READBACK_CONTROL_RE.fullmatch(value) - return match.group(1) if match else value - - -def _paragraph_lines(value: object) -> list[str]: - text = str(value or "").replace("\r\n", "\n").replace("\r", "\n") - return [ - normalized - for line in text.split("\n") - if (normalized := _normalize_line(line)) - ] - - -def _markdown_line_variants(markdown: str) -> list[set[str] | None]: - variants: list[set[str] | None] = [] - for raw_line in markdown.splitlines(): - stripped = raw_line.strip() - if ( - not stripped - or stripped.startswith("|") - or stripped.startswith("![") - or stripped.startswith("> [Chart") - or stripped == "_No extractable text content._" - ): - variants.append(None) - continue - display = _MARKDOWN_LINK_RE.sub(r"\1", stripped) - without_prefix = _LIST_PREFIX_RE.sub("", display, count=1) - line_variants = { - _unescape_readback_control_line(_normalize_line(candidate)) - for candidate in (stripped, display, without_prefix) - } - line_variants.discard("") - variants.append(line_variants) - return variants - - -def _contains_paragraphs(markdown: str, value: str) -> bool: - expected = _paragraph_lines(value) - if not expected: - return True - actual = _markdown_line_variants(markdown) - if len(expected) > len(actual): - return False - for start in range(len(actual) - len(expected) + 1): - if all( - actual[start + offset] is not None - and expected[offset] in actual[start + offset] - for offset in range(len(expected)) - ): - return True - return False - - -def _table_cells(markdown: str) -> list[list[str]]: - cells: list[list[str]] = [] - for raw_line in markdown.splitlines(): - stripped = raw_line.strip() - if not (stripped.startswith("|") and stripped.endswith("|")): - continue - values = re.split(r"(? bool: - expected = _paragraph_lines(value) - if not expected: - return True - return expected in _table_cells(markdown) - - -def _contains_chart_text(markdown: str, value: str) -> bool: - return ( - _contains_paragraphs(markdown, value) - or _contains_table_cell(markdown, value) - ) - - -def _first_library(project_path: Path) -> dict[str, Any] | None: - libraries = sorted((project_path / "analysis").glob("*.slide_library.json")) - if not libraries: - return None - return _load_json(libraries[0]) - - -def _matched_library_target( - lookup: dict[tuple[int, str], dict[str, Any]] | None, - source_slide: int, - selectors: list[str], -) -> dict[str, Any] | None: - """Return the first library target matched by the runtime selector order.""" - if lookup is None: - return None - return next( - ( - target - for selector in selectors - if (target := lookup.get((source_slide, selector))) is not None - ), - None, - ) - - -def _replacement_tokens( - plan: dict[str, Any], - library: dict[str, Any] | None, -) -> list[tuple[int, str, str, str]]: - tokens: list[tuple[int, str, str, str]] = [] - slot_lookup = _slot_lookup(library) if library is not None else None - for plan_slide, slide in enumerate(plan.get("slides", []), start=1): - source_slide = int(slide.get("source_slide", 0)) - replacements = slide.get("replacements", []) - if not isinstance(replacements, list): - continue - slide_tokens: list[tuple[str, bool]] = [] - for replacement in replacements: - if not isinstance(replacement, dict): - continue - text = _replacement_text(replacement) - if not _paragraph_lines(text): - continue - slot = _matched_library_target( - slot_lookup, - source_slide, - _replacement_selectors(replacement), - ) - if ( - library is not None - and replacement.get("optional") - and slot is None - ): - continue - is_title = ( - slot is not None - and str(slot.get("role") or "") == "title_candidate" - ) - slide_tokens.append((text, is_title)) - if ( - library is None - and slide_tokens - and not any(is_title for _, is_title in slide_tokens) - ): - first_text, _ = slide_tokens[0] - slide_tokens[0] = (first_text, True) - for text, is_title in slide_tokens: - tokens.append( - ( - plan_slide, - text, - ( - "title_missing_in_readback" - if is_title - else "body_text_missing_in_readback" - ), - "title" if is_title else "replacement text", - ) - ) - return tokens - - -def _table_tokens( - plan: dict[str, Any], - library: dict[str, Any] | None, -) -> list[tuple[int, str]]: - tokens: list[tuple[int, str]] = [] - table_lookup = _table_lookup(library) if library is not None else None - for plan_slide, slide in enumerate(plan.get("slides", []), start=1): - source_slide = int(slide.get("source_slide", 0)) - for table_edit in slide.get("table_edits", []) or []: - table = _matched_library_target( - table_lookup, - source_slide, - _table_selectors(table_edit), - ) - if ( - library is not None - and table_edit.get("optional") - and table is None - ): - continue - for cell in table_edit.get("cells", []) or []: - text = _table_cell_text(cell) - if _paragraph_lines(text): - tokens.append((plan_slide, text)) - return tokens - - -def _format_chart_series_value(value: object) -> str: - """Match ppt_to_md's display formatting for chart series values.""" - if value is None: - return "" - if isinstance(value, float) and value.is_integer(): - return str(int(value)) - return str(value) - - -def _chart_tokens( - plan: dict[str, Any], - library: dict[str, Any] | None, -) -> list[tuple[int, str]]: - tokens: list[tuple[int, str]] = [] - chart_lookup = _chart_lookup(library) if library is not None else None - for plan_slide, slide in enumerate(plan.get("slides", []), start=1): - source_slide = int(slide.get("source_slide", 0)) - for chart_edit in slide.get("chart_edits", []) or []: - chart = _matched_library_target( - chart_lookup, - source_slide, - _chart_selectors(chart_edit), - ) - if ( - library is not None - and chart_edit.get("optional") - and chart is None - ): - continue - for category in chart_edit.get("categories", []) or []: - if str(category).strip(): - tokens.append((plan_slide, str(category))) - for series in chart_edit.get("series", []) or []: - name = str(series.get("name") or "").strip() - if name: - tokens.append((plan_slide, name)) - for value in series.get("values", []) or []: - text = _format_chart_series_value(value) - if text: - tokens.append((plan_slide, text)) - return tokens - - -def _append_page_token_checks( - *, - results: list[dict[str, Any]], - summary: dict[str, int], - sections: dict[int, str], - tokens: list[tuple[int, str, str, str]], - status: str, - matcher: Callable[[str, str], bool], -) -> None: - summary_key = status.lower() - for plan_slide, text, code, label in tokens: - body, _ = _split_notes(sections.get(plan_slide, "")) - if matcher(body, text): - summary["ok"] += 1 - continue - summary[summary_key] += 1 - results.append( - { - "status": status, - "code": code, - "plan_slide": plan_slide, - "message": f"{label} not found on the corresponding read-back slide", - "text": text, - } - ) - - -def validate_project(project_path: Path) -> dict[str, Any]: - """Run read-back validation for a template-fill project.""" - project_path = project_path.expanduser().resolve() - plan_path = project_path / "analysis" / "fill_plan.json" - if not plan_path.is_file(): - raise RuntimeError(f"Missing fill plan: {plan_path}") - - plan = _load_json(plan_path) - output_path = _latest_export(project_path) - validation_dir = project_path / "validation" - validation_dir.mkdir(parents=True, exist_ok=True) - readback_path = validation_dir / "readback.md" - - ppt_to_md = _SCRIPTS_DIR / "source_to_md" / "ppt_to_md.py" - try: - subprocess.run( - [sys.executable, str(ppt_to_md), str(output_path), "-o", str(readback_path)], - cwd=_SCRIPTS_DIR.parents[2], - check=True, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - ) - except FileNotFoundError as exc: - raise RuntimeError(f"Missing executable: {sys.executable}") from exc - except subprocess.CalledProcessError as exc: - details = (exc.stderr or exc.stdout or "").strip() - raise RuntimeError(details or "ppt_to_md read-back failed") from exc - - markdown = readback_path.read_text(encoding="utf-8", errors="replace") - results: list[dict[str, Any]] = [] - summary = {"ok": 0, "warn": 0, "error": 0} - sections = _slide_sections(markdown) - - expected_slides = len(plan.get("slides", []) or []) - actual_slides = _readback_slide_count(markdown) - if expected_slides == actual_slides: - summary["ok"] += 1 - else: - summary["error"] += 1 - results.append( - { - "status": "ERROR", - "code": "slide_count_mismatch", - "expected": expected_slides, - "actual": actual_slides, - "message": "read-back slide count does not match fill_plan.slides", - } - ) - - for plan_slide in range(1, expected_slides + 1): - if plan_slide in sections: - continue - summary["error"] += 1 - results.append( - { - "status": "ERROR", - "code": "slide_missing_in_readback", - "plan_slide": plan_slide, - "message": "planned slide section is missing from read-back Markdown", - } - ) - - library = _first_library(project_path) - _append_page_token_checks( - results=results, - summary=summary, - sections=sections, - tokens=_replacement_tokens(plan, library), - status="ERROR", - matcher=_contains_paragraphs, - ) - _append_page_token_checks( - results=results, - summary=summary, - sections=sections, - tokens=[ - (plan_slide, text, "table_text_missing_in_readback", "table cell text") - for plan_slide, text in _table_tokens(plan, library) - ], - status="ERROR", - matcher=_contains_table_cell, - ) - _append_page_token_checks( - results=results, - summary=summary, - sections=sections, - tokens=[ - (plan_slide, text, "chart_text_missing_in_readback", "chart text") - for plan_slide, text in _chart_tokens(plan, library) - ], - status="WARN", - matcher=_contains_chart_text, - ) - - for plan_slide, slide in enumerate(plan.get("slides", []) or [], start=1): - _, notes = _split_notes(sections.get(plan_slide, "")) - planned_notes = str(slide.get("notes") or slide.get("speaker_notes") or "") - if _paragraph_lines(planned_notes): - if _contains_paragraphs(notes, planned_notes): - summary["ok"] += 1 - continue - summary["error"] += 1 - results.append( - { - "status": "ERROR", - "code": "notes_missing_in_readback", - "plan_slide": plan_slide, - "message": ( - "planned speaker notes not found on the corresponding " - "read-back slide" - ), - "text": planned_notes, - } - ) - elif notes.strip(): - summary["warn"] += 1 - results.append( - { - "status": "WARN", - "code": "unexpected_notes_in_readback", - "plan_slide": plan_slide, - "message": ( - "read-back slide contains speaker notes although the " - "plan slide has no notes field" - ), - } - ) - - report = { - "schema": "template_fill_pptx_validate.v1", - "project": str(project_path), - "export": str(output_path), - "readback": str(readback_path), - "summary": summary, - "results": results, - } - _write_json(validation_dir / "validate_report.json", report) - return report - - -def print_validate_report(report: dict[str, Any]) -> None: - """Print a compact validation report.""" - summary = report["summary"] - print(f"validate: ok={summary['ok']} warn={summary['warn']} error={summary['error']}") - print(f"export: {report['export']}") - print(f"readback: {report['readback']}") - for item in report["results"]: - text = item.get("text") - suffix = f" text={text!r}" if text else "" - print(f"{item['status']} {item['code']}: {item['message']}{suffix}") diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_import/manifest.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_import/manifest.py index a3310e80..89d5c346 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_import/manifest.py +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_import/manifest.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Internal helper: extract lightweight template assets and style metadata from a PPTX file. +"""Internal helper: extract template resources and style metadata from a PPTX file. This helper is intentionally limited in scope: - extract reusable media assets @@ -10,8 +10,8 @@ This helper is intentionally limited in scope: It does NOT try to convert arbitrary PPTX shapes into SVG templates. Output contract (single source of truth): - /manifest.json — all factual metadata (theme, assets, slides, layouts, masters) - /assets/ — extracted reusable image assets + /analysis/manifest.json — factual metadata and resource inventory + /images/ — reusable image assets This module is a pure library. The CLI entry point lives in ``pptx_template_import.py`` at the scripts root. @@ -19,10 +19,8 @@ This module is a pure library. The CLI entry point lives in from __future__ import annotations -import json import posixpath import re -import shutil import zipfile from collections import Counter, defaultdict from dataclasses import dataclass @@ -34,6 +32,10 @@ from pptx_to_svg.ooxml_loader import ( blip_embed_relationship_ids, parse_ooxml_boolean, ) +from pptx_workspace import ( + inventory_package_resources, + write_workspace_resources, +) NS = { @@ -177,11 +179,6 @@ def emu_to_pixels(value: int) -> int: return int(round(value / EMU_PER_INCH * 96)) -def sanitize_filename(value: str) -> str: - value = re.sub(r"[^A-Za-z0-9._-]+", "_", value.strip()) - return value.strip("._") or "asset" - - def part_svg_filename(role: str, seq: int, part_path: str) -> str: stem = PurePosixPath(part_path).stem safe_stem = re.sub(r"[^A-Za-z0-9_-]+", "_", stem).strip("_") or role @@ -560,27 +557,9 @@ def build_manifest( layout_parent[layout_path] = master_path layout_parts.append(layout_path) - asset_dir = output_dir / "assets" - if asset_dir.exists(): - shutil.rmtree(asset_dir) - asset_dir.mkdir(parents=True, exist_ok=True) - - copied_assets: dict[str, str] = {} - for info in zf.infolist(): - if not info.filename.startswith("ppt/media/") or info.is_dir(): - continue - original_name = PurePosixPath(info.filename).name - safe_name = sanitize_filename(original_name) - destination = asset_dir / safe_name - stem = destination.stem - suffix = destination.suffix - counter = 2 - while destination.exists(): - destination = asset_dir / f"{stem}_{counter}{suffix}" - counter += 1 - with zf.open(info.filename) as src, open(destination, "wb") as dst: - shutil.copyfileobj(src, dst) - copied_assets[info.filename] = destination.name + resource_inventory = inventory_package_resources(zf) + write_workspace_resources(output_dir, resource_inventory) + copied_assets = resource_inventory.image_name_map() slide_records: list[SlideRecord] = [] asset_usage: Counter[str] = Counter() @@ -788,12 +767,13 @@ def build_manifest( }, "slideSize": slide_size, "theme": theme_summary, - "assets": { - "exportDir": "assets", - "commonAssets": common_assets, - "allAssets": sorted(copied_assets.values()), - "assetMap": copied_assets, + "images": { + "exportDir": "images", + "commonImages": common_assets, + "allImages": sorted(copied_assets.values()), + "imageMap": copied_assets, }, + "resources": resource_inventory.manifest(), "pageTypeCandidates": dict(sorted(page_type_map.items())), "layouts": layouts_top, "masters": masters_top, diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_import/native_structure.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_import/native_structure.py index 0ca85605..d5474671 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_import/native_structure.py +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_import/native_structure.py @@ -22,10 +22,12 @@ import shutil from pathlib import Path from typing import Any +from pptx_workspace import NATIVE_STRUCTURE_PATH, SOURCE_PPTX_PATH + SCHEMA = "ppt-master.native-structure.v1" -SOURCE_TEMPLATE_NAME = "source_template.pptx" -CONTRACT_NAME = "native_structure.json" +SOURCE_TEMPLATE_NAME = SOURCE_PPTX_PATH.as_posix() +CONTRACT_NAME = NATIVE_STRUCTURE_PATH.as_posix() def _sha256(path: Path) -> str: @@ -39,6 +41,7 @@ def _sha256(path: Path) -> str: def _copy_source_template(source: Path, destination: Path) -> None: if source.resolve() == destination.resolve(): return + destination.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source, destination) @@ -122,6 +125,7 @@ def build_native_structure( "imageAssets": master.get("imageAssets", []), "shapeImageAssets": master.get("shapeImageAssets", []), "drawableShapeCount": master.get("drawableShapeCount", 0), + "placeholders": master.get("placeholders", []), "layoutKeys": [ layout_key_by_path[layout["path"]] for layout in layouts @@ -152,6 +156,7 @@ def build_native_structure( for slide in slides: slide_contracts.append({ "index": slide["index"], + "packagePart": slide.get("slidePath"), "pageType": slide.get("pageType"), "layoutKey": layout_key_by_path.get(slide.get("layoutPath")), "masterKey": master_key_by_path.get(slide.get("masterPath")), @@ -194,7 +199,9 @@ def write_native_structure_bundle( source_copy = output_dir / SOURCE_TEMPLATE_NAME _copy_source_template(source_pptx, source_copy) contract = build_native_structure(source_pptx, manifest) - (output_dir / CONTRACT_NAME).write_text( + contract_path = output_dir / CONTRACT_NAME + contract_path.parent.mkdir(parents=True, exist_ok=True) + contract_path.write_text( json.dumps(contract, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_preview_pptx.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_preview_pptx.py index 8587bae3..3fff0722 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_preview_pptx.py +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/template_preview_pptx.py @@ -2,8 +2,7 @@ """ PPT Master - Template Preview PPTX Exporter -Export public SVG prototypes as a structured review deck while retaining -definition-only Layout prototypes in the native package. +Export complete Slide SVG prototypes as a structured review deck. Usage: python3 scripts/template_preview_pptx.py [-o output.pptx] @@ -11,7 +10,6 @@ Usage: Examples: python3 scripts/template_preview_pptx.py projects/my_template python3 scripts/template_preview_pptx.py templates/decks/my_template -o review.pptx - python3 scripts/template_preview_pptx.py templates/decks/legacy --visual-only Dependencies: python-pptx @@ -21,10 +19,8 @@ from __future__ import annotations import argparse import contextlib -import math import re import shutil -import statistics import sys import tempfile from collections.abc import Iterator @@ -40,12 +36,14 @@ configure_utf8_stdio() from pptx import Presentation # noqa: E402 from svg_to_pptx.drawingml.theme_fonts import ( # noqa: E402 - MasterTextStyleSpec, + infer_master_text_style_spec, ) -from svg_to_pptx.drawingml.utils import font_px_to_hpt # noqa: E402 from svg_to_pptx.pptx_package.builder import ( # noqa: E402 create_pptx_with_native_svg, ) +from svg_to_pptx.pptx_package.template_structure import ( # noqa: E402 + load_template_source_themes, +) _FRONTMATTER_ID_RE = re.compile( @@ -60,18 +58,8 @@ _CANVAS_VIEWBOX_RE = re.compile( r"^canvas_viewbox\s*:\s*[\"']?([^\"'\r\n]+?)[\"']?\s*$", re.MULTILINE, ) -_FONT_SIZE_RE = re.compile(r"^([0-9]+(?:\.[0-9]+)?)(?:px)?$") _FILENAME_UNSAFE_RE = re.compile(r"[\\/:*?\"<>|\x00-\x1f]+") _PLACEHOLDER_MARKER_RE = re.compile(r"\{\{([A-Z][A-Z0-9_]*)\}\}") -_TITLE_PLACEHOLDERS = frozenset({"title", "subtitle"}) -_BODY_PLACEHOLDERS = frozenset({ - "body", - "date", - "footer", - "slide-number", -}) -_DEFAULT_TITLE_PX = 40.0 -_DEFAULT_BODY_PX = 24.0 def _review_marker_text(match: re.Match[str]) -> str: @@ -144,22 +132,6 @@ def _review_svg_sources( yield review_files -def _partition_svg_prototypes( - svg_files: list[Path], - *, - visual_only: bool, -) -> tuple[list[Path], list[Path]]: - """Separate public pages from canonical definition-only Layout SVGs.""" - if visual_only: - return svg_files, [] - public_files: list[Path] = [] - definition_files: list[Path] = [] - for path in svg_files: - target = definition_files if path.stem.startswith("layout_") else public_files - target.append(path) - return public_files, definition_files - - _TEMPLATE_SPEC_NAME_RE = re.compile( r"design_spec\.(?Pbrand|style|layout|deck)\.[^/\\]+\.md" ) @@ -221,33 +193,34 @@ def _resolve_workspace(path: Path) -> tuple[Path, Path]: candidate = path.expanduser().resolve() if _roster_spec(candidate / "templates") is not None: return candidate, candidate / "templates" - - if _roster_spec(candidate) is not None: - if candidate.name == "templates" and (candidate.parent / "exports").is_dir(): - return candidate.parent, candidate - return candidate, candidate - raise ValueError( - "template workspace must contain templates/design_spec.md, " - "templates/design_spec...md, or a legacy flat " - "design_spec.md" + "template workspace root must contain templates/design_spec.md or " + "templates/design_spec...md" ) -def _template_id(spec_path: Path, workspace: Path) -> str: - """Read a portable template id, falling back to the workspace directory name.""" +def _template_id(spec_path: Path) -> str: + """Read the required portable template id.""" text = spec_path.read_text(encoding="utf-8") match = _FRONTMATTER_ID_RE.search(text) - raw = match.group(1).strip().strip("'\"") if match else workspace.name + if match is None: + raise ValueError( + f"{spec_path.name} frontmatter must declare layout_id or deck_id" + ) + raw = match.group(1).strip().strip("'\"") safe = _FILENAME_UNSAFE_RE.sub("_", raw).strip(" ._") return safe or "template" def _replication_mode(spec_path: Path) -> str: - """Read the template replication mode, defaulting legacy packages to standard.""" + """Read the required template replication mode.""" text = spec_path.read_text(encoding="utf-8") match = _REPLICATION_MODE_RE.search(text) - return match.group(1) if match else "standard" + if match is None: + raise ValueError( + f"{spec_path.name} frontmatter must declare replication_mode" + ) + return match.group(1) def _canvas_viewbox(spec_path: Path) -> str | None: @@ -262,65 +235,6 @@ def _canvas_viewbox(spec_path: Path) -> str | None: return match.group(1).strip() if match else None -def _style_property(style: str, name: str) -> str | None: - """Return one inline CSS declaration value.""" - for declaration in style.split(";"): - key, separator, value = declaration.partition(":") - if separator and key.strip().lower() == name: - return value.strip() - return None - - -def _font_size_px(element: ET.Element) -> float | None: - """Read one finite positive SVG font size in px.""" - raw = element.get("font-size") - if raw is None: - raw = _style_property(element.get("style", ""), "font-size") - if raw is None: - return None - match = _FONT_SIZE_RE.fullmatch(raw.strip()) - if match is None: - return None - value = float(match.group(1)) - return value if math.isfinite(value) and value > 0 else None - - -def _carrier_sizes(svg_files: list[Path]) -> tuple[list[float], list[float]]: - """Collect authored title/body sizes from semantic placeholder carriers.""" - title_sizes: list[float] = [] - body_sizes: list[float] = [] - for svg_path in svg_files: - root = ET.parse(svg_path).getroot() - for slot in root.iter(): - placeholder = slot.get("data-pptx-placeholder") - if placeholder not in _TITLE_PLACEHOLDERS | _BODY_PLACEHOLDERS: - continue - for carrier in slot.iter(): - if carrier.get("data-pptx-carrier") != "true": - continue - size = _font_size_px(carrier) - if size is None: - continue - target = title_sizes if placeholder in _TITLE_PLACEHOLDERS else body_sizes - target.append(size) - return title_sizes, body_sizes - - -def _master_text_style(svg_files: list[Path]) -> tuple[MasterTextStyleSpec, float, float]: - """Build review-only Master text defaults without requiring a project lock.""" - title_sizes, body_sizes = _carrier_sizes(svg_files) - title_px = float(statistics.median(title_sizes)) if title_sizes else _DEFAULT_TITLE_PX - body_px = float(statistics.median(body_sizes)) if body_sizes else _DEFAULT_BODY_PX - return ( - MasterTextStyleSpec( - title_hpt=font_px_to_hpt(title_px), - body_hpt=font_px_to_hpt(body_px), - ), - title_px, - body_px, - ) - - def _verify_output( output_path: Path, *, @@ -388,10 +302,7 @@ def build_parser() -> argparse.ArgumentParser: ) parser.add_argument( "template_workspace", - help=( - "Workspace containing templates/design_spec.md; legacy flat template " - "directories are also accepted." - ), + help="Workspace root containing templates/design_spec.md.", ) parser.add_argument( "-o", @@ -407,11 +318,11 @@ def build_parser() -> argparse.ArgumentParser: help="Replace an existing review PPTX after an intentional re-export.", ) parser.add_argument( - "--visual-only", + "--native-charts-and-tables", action="store_true", help=( - "Export a legacy SVG roster as slide-local DrawingML for visual review. " - "This does not validate or claim a reusable Master/Layout contract." + "Replace eligible SVG chart/table fallbacks with PowerPoint-native " + "objects. Default review export keeps the visible SVG fallbacks." ), ) return parser @@ -427,27 +338,33 @@ def main(argv: list[str] | None = None) -> int: all_svg_files = sorted(template_dir.glob("*.svg")) if not all_svg_files: raise ValueError(f"template directory has no SVG prototypes: {template_dir}") - svg_files, layout_definition_files = _partition_svg_prototypes( - all_svg_files, - visual_only=args.visual_only, - ) - if not svg_files: + definition_only_files = [ + path.name + for path in all_svg_files + if path.stem.startswith("layout_") + ] + if definition_only_files: raise ValueError( - "template directory contains Layout definitions but no public " - f"SVG prototypes: {template_dir}" + "template workspaces accept only complete Slide prototypes; " + "replace definition-only Layout SVG(s) with authored Slide " + "prototypes: " + ", ".join(definition_only_files) ) + svg_files = all_svg_files spec_path = _roster_spec(template_dir) - template_id = _template_id(spec_path, workspace) + template_id = _template_id(spec_path) replication_mode = _replication_mode(spec_path) + source_themes = load_template_source_themes(template_dir) + if source_themes is not None and replication_mode != "mirror": + raise ValueError( + "source_themes.json is allowed only in a mirror workspace" + ) locked_canvas = _canvas_viewbox(spec_path) - if locked_canvas is None and not args.visual_only: + if locked_canvas is None: raise ValueError( "design_spec.md frontmatter must declare canvas_viewbox" ) - use_full_placeholder_frames = ( - not args.visual_only and replication_mode != "mirror" - ) + use_full_placeholder_frames = replication_mode != "mirror" output_path = ( Path(args.output).expanduser().resolve() if args.output @@ -460,22 +377,15 @@ def main(argv: list[str] | None = None) -> int: f"output already exists: {output_path}; use --force to replace it" ) output_path.parent.mkdir(parents=True, exist_ok=True) - text_style: MasterTextStyleSpec | None = None - if not args.visual_only: - text_style, title_px, body_px = _master_text_style(all_svg_files) + text_style, title_px, body_px = infer_master_text_style_spec( + all_svg_files + ) print("PPT Master - Template Preview PPTX Exporter") print(f" Workspace: {workspace}") print(f" Template source: {template_dir}") - print(f" Public SVG prototypes: {len(svg_files)}") - if layout_definition_files: - print( - " Definition-only Layout prototypes: " - f"{len(layout_definition_files)}" - ) - if args.visual_only: - print(" Review mode: visual-only legacy compatibility") - elif replication_mode == "mirror": + print(f" Slide SVG prototypes: {len(svg_files)}") + if replication_mode == "mirror": print(" Review placeholder frames: preserved source Slide geometry") else: print(f" Review Master defaults: title {title_px:g}px, body {body_px:g}px") @@ -484,18 +394,13 @@ def main(argv: list[str] | None = None) -> int: with _review_svg_sources( workspace, - all_svg_files, + svg_files, shorten_placeholder_markers=use_full_placeholder_frames, - ) as review_all_svg_files: - review_svg_files, review_layout_definition_files = ( - _partition_svg_prototypes( - review_all_svg_files, - visual_only=args.visual_only, - ) - ) + ) as review_svg_files: success = create_pptx_with_native_svg( svg_files=review_svg_files, output_path=output_path, + resource_root=workspace, canvas_format=None, expected_viewbox=locked_canvas, verbose=True, @@ -503,12 +408,12 @@ def main(argv: list[str] | None = None) -> int: enable_notes=False, animation=None, image_optimize=False, - native_objects=True, - pptx_structure="flat" if args.visual_only else "structured", + native_objects=args.native_charts_and_tables, + pptx_structure="structured", use_layout_placeholder_frames=use_full_placeholder_frames, master_text_style_spec=text_style, structure_name=template_id, - layout_definition_files=review_layout_definition_files, + source_theme_xml_by_master=source_themes, ) if not success or not output_path.is_file(): print("Error: template preview export did not produce a PPTX", file=sys.stderr) @@ -526,14 +431,13 @@ def main(argv: list[str] | None = None) -> int: ) return 1 - label = "Visual-only template preview" if args.visual_only else "Template preview" placeholder_status = ( f", {placeholder_count} full-frame placeholder(s)" if use_full_placeholder_frames else "" ) print( - f"[OK] {label} verified: " + "[OK] Template preview verified: " f"{slide_count} slides, {master_count} master(s), " f"{layout_count} layout(s){placeholder_status}" ) diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/06_1145_completion.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/06_1145_completion.svg new file mode 100644 index 00000000..2083aa2e --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/06_1145_completion.svg @@ -0,0 +1,188 @@ + + + + { + "x": 96, + "y": 246, + "width": 724, + "height": 336, + "name": "fiveyear-target-vs-actual", + "type": "column", + "grouping": "clustered", + "categories": [ + "2021", + "2022", + "2023", + "2024", + "2025" + ], + "series": [ + { + "name": "目标", + "values": [ + 341, + 210, + 175, + 329, + 356 + ] + }, + { + "name": "完成", + "values": [ + 313.02, + 275.04, + 175.12, + 286.2, + 130.53 + ] + } + ], + "show_legend": true, + "legend_position": "top", + "axes": { + "category": { + "kind": "text", + "position": "bottom" + }, + "value": { + "kind": "value", + "position": "left", + "minimum": 0, + "maximum": 400, + "major_unit": 100, + "major_gridlines": true + } + }, + "style": { + "colors": [ + "#9FB6CE", + "#004EA1" + ], + "font_family": "Arial", + "axis_font_size": 18, + "legend_font_size": 18, + "chart_area_fill": "none", + "plot_area_fill": "none", + "text_color": "#1A1A1A", + "axis_color": "#DDE3E9", + "grid_color": "#DDE3E9" + }, + "plot_area": { + "x": 140, + "y": 280, + "width": 670, + "height": 260 + }, + "notes": [ + { + "text": "91.79%", + "x": 162, + "y": 292, + "width": 90, + "height": 24, + "font_size": 18, + "bold": true, + "align": "ctr", + "color": "#4A5568" + }, + { + "text": "130.97%", + "x": 296, + "y": 335, + "width": 90, + "height": 24, + "font_size": 18, + "bold": true, + "align": "ctr", + "color": "#4A5568" + }, + { + "text": "100.07%", + "x": 430, + "y": 400, + "width": 90, + "height": 24, + "font_size": 18, + "bold": true, + "align": "ctr", + "color": "#4A5568" + }, + { + "text": "86.99%", + "x": 564, + "y": 300, + "width": 90, + "height": 24, + "font_size": 18, + "bold": true, + "align": "ctr", + "color": "#4A5568" + }, + { + "text": "36.67%", + "x": 698, + "y": 282, + "width": 90, + "height": 24, + "font_size": 18, + "bold": true, + "align": "ctr", + "color": "#C90A4F" + } + ] + } + + + + 目标 + + 完成 + + + + + + + + + + + + 400 + 300 + 200 + 100 + 0 + + + + + + + + + + + + + + + + + + 91.79% + 130.97% + 100.07% + 86.99% + 36.67% + + + 2021 + 2022 + 2023 + 2024 + 2025 + + + diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/07_segment_mix.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/07_segment_mix.svg new file mode 100644 index 00000000..d3b0cd6a --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/07_segment_mix.svg @@ -0,0 +1,75 @@ + + + + { + "x": 96, "y": 246, "width": 724, "height": 336, + "name": "segment-mix-by-year", + "type": "area", + "grouping": "stacked", + "categories": ["2021", "2022", "2023", "2024", "2025"], + "series": [ + {"name": "水资源与环境", "values": [215.42, 40.9, 9.4, 10.33, 8.45]}, + {"name": "能源电力", "values": [24.33, 29.0, 56.6, 52.96, 36.96]}, + {"name": "城市建设与基础设施", "values": [71.37, 202.35, 107.33, 221.85, 69.24]}, + {"name": "其他业务", "values": [1.9, 2.78, 1.79, 1.06, 15.88]} + ], + "show_legend": true, + "legend_position": "top", + "axes": { + "category": {"kind": "text", "position": "bottom"}, + "value": {"kind": "value", "position": "left", "minimum": 0, "maximum": 350, "major_unit": 100, "major_gridlines": true} + }, + "style": { + "colors": ["#004EA1", "#4A7BB5", "#0E7C9B", "#9FB6CE"], + "font_family": "Arial", + "axis_font_size": 18, + "legend_font_size": 18, + "chart_area_fill": "none", + "plot_area_fill": "none", + "text_color": "#1A1A1A", + "axis_color": "#DDE3E9", + "grid_color": "#DDE3E9" + }, + "plot_area": {"x": 140, "y": 280, "width": 670, "height": 260} + } + + + + 水资源与环境 + + 能源电力 + + 城市建设与基础设施 + + 其他 + + + + + + + + + + + 300 + 200 + 100 + 0 + + + + + + + + + + 2021 + 2022 + 2023 + 2024 + 2025 + + + diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/10_competitiveness.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/10_competitiveness.svg new file mode 100644 index 00000000..c2c245f2 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/10_competitiveness.svg @@ -0,0 +1,71 @@ + + + + { + "x": 192, "y": 208, "width": 520, "height": 424, + "name": "competitor-capability-radar", + "type": "radar", + "categories": ["政企合作", "属地化", "市场规模", "赛道适配", "资金实力", "品牌口碑", "技术履约", "成本管控", "资质全链", "风险合规", "新兴赛道", "人才团队"], + "series": [ + {"name": "中国电建", "values": [4.0, 3.67, 3.58, 4.25, 3.5, 3.92, 4.0, 3.5, 3.92, 4.42, 4.75, 3.92]}, + {"name": "中国建筑", "values": [4.92, 4.75, 4.92, 4.25, 4.75, 4.33, 4.25, 4.25, 5.0, 3.75, 3.75, 4.5]}, + {"name": "行业均值", "values": [4.12, 4.01, 3.87, 4.12, 4.02, 3.93, 4.12, 3.77, 4.24, 3.94, 3.94, 3.99]} + ], + "show_value_axis_labels": false, + "style": { + "colors": ["#004EA1", "#0E7C9B", "#9FB6CE"], + "font_family": "Arial", + "axis_font_size": 18, + "chart_area_fill": "none", + "plot_area_fill": "none", + "text_color": "#1A1A1A", + "axis_color": "#DDE3E9", + "grid_color": "#DDE3E9" + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 政企合作 + 属地化 + 市场规模 + 赛道适配 + 资金实力 + 品牌口碑 + 技术履约 + 成本管控 + 资质全链 + 风险合规 + 新兴赛道 + 人才团队 + + + diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/11_market_space.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/11_market_space.svg new file mode 100644 index 00000000..e2774e3c --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/11_market_space.svg @@ -0,0 +1,61 @@ + + + + { + "x": 96, "y": 250, "width": 604, "height": 320, + "name": "market-space-breakdown", + "type": "treemap", + "parent_label_layout": "banner", + "categories": [ + ["城市建设与基础设施", "房建"], ["城市建设与基础设施", "市政"], ["城市建设与基础设施", "综合交通"], + ["水资源与环境", "水利工程"], ["水资源与环境", "水环境治理和生态修复"], ["水资源与环境", "水务工程"], ["水资源与环境", "环保"], + ["能源电力", "光伏"], ["能源电力", "电网"], ["能源电力", "火电"], ["能源电力", "抽水蓄能"], ["能源电力", "风电"], ["能源电力", "常规水电"], + ["战略性新兴业务", "人工智能"], ["战略性新兴业务", "低空经济"], ["战略性新兴业务", "氢能"], ["战略性新兴业务", "新型储能"], ["战略性新兴业务", "土壤修复"], + ["数字化", "工程基建数字化"], ["数字化", "城市治理数字化"], ["数字化", "能源算力数字化"], ["数字化", "战略性新兴数字化"] + ], + "values": [4720, 2420, 573, 2198.31, 211.41, 204.53, 37.6, 559, 300, 240, 223, 209, 45, 200, 80, 50, 46.5, 10.3, 65, 52, 48, 35], + "style": { + "colors": ["#0E7C9B", "#004EA1", "#4A7BB5", "#7FA3C4", "#B8CBE0"], + "font_family": "Arial", + "chart_area_fill": "none", + "text_color": "#FFFFFF" + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + 城市建设与基础设施 + 7713 + 61.57% + 水资源与环境 + 2651.85 + 21.17% + 能源电力 + 1576 + 12.58% + + + diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/15_signing_target.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/15_signing_target.svg new file mode 100644 index 00000000..f0ea2b6f --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/15_signing_target.svg @@ -0,0 +1,228 @@ + + + + { + "x": 96, + "y": 246, + "width": 724, + "height": 336, + "name": "annual-signing-target", + "type": "column", + "grouping": "stacked", + "categories": [ + "2026", + "2027", + "2028", + "2029", + "2030" + ], + "series": [ + { + "name": "水", + "values": [ + 59, + 70, + 81, + 89, + 92 + ] + }, + { + "name": "城", + "values": [ + 66, + 70, + 76, + 85, + 92 + ] + }, + { + "name": "能", + "values": [ + 18, + 21, + 24, + 27, + 28 + ] + }, + { + "name": "战兴业务", + "values": [ + 7, + 8, + 9, + 10, + 11 + ] + } + ], + "show_legend": true, + "legend_position": "top", + "axes": { + "category": { + "kind": "text", + "position": "bottom" + }, + "value": { + "kind": "value", + "position": "left", + "minimum": 0, + "maximum": 240, + "major_unit": 60, + "major_gridlines": true + } + }, + "style": { + "colors": [ + "#004EA1", + "#0E7C9B", + "#4A7BB5", + "#9FB6CE" + ], + "font_family": "Arial", + "axis_font_size": 18, + "legend_font_size": 18, + "chart_area_fill": "none", + "plot_area_fill": "none", + "text_color": "#1A1A1A", + "axis_color": "#DDE3E9", + "grid_color": "#DDE3E9" + }, + "plot_area": { + "x": 140, + "y": 280, + "width": 670, + "height": 260 + }, + "notes": [ + { + "text": "150", + "x": 172, + "y": 344, + "width": 70, + "height": 30, + "font_size": 24, + "bold": true, + "align": "ctr", + "color": "#1A1A1A" + }, + { + "text": "169", + "x": 306, + "y": 323, + "width": 70, + "height": 30, + "font_size": 24, + "bold": true, + "align": "ctr", + "color": "#1A1A1A" + }, + { + "text": "190", + "x": 440, + "y": 300, + "width": 70, + "height": 30, + "font_size": 24, + "bold": true, + "align": "ctr", + "color": "#1A1A1A" + }, + { + "text": "211", + "x": 574, + "y": 278, + "width": 70, + "height": 30, + "font_size": 24, + "bold": true, + "align": "ctr", + "color": "#1A1A1A" + }, + { + "text": "223", + "x": 708, + "y": 265, + "width": 70, + "height": 30, + "font_size": 24, + "bold": true, + "align": "ctr", + "color": "#C90A4F" + } + ] + } + + + + + + + + + + 战兴业务 + + + + + + + + + + + + 240 + 180 + 120 + 60 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 150 + 169 + 190 + 211 + 223 + + + 2026 + 2027 + 2028 + 2029 + 2030 + + + diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/27_business_models.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/27_business_models.svg new file mode 100644 index 00000000..6f3da7ae --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/27_business_models.svg @@ -0,0 +1,77 @@ + + + + { + "schema": "ppt-master.semantic-table.v2", + "x": 96, "y": 272, "width": 1112, "height": 316, + "name": "business-model-toolbox", + "header_rows": 1, + "column_widths": [160, 240, 220, 220, 272], + "columns": ["项目属性", "典型场景", "建议模式", "主要收益来源", "关键合规边界"], + "rows": [ + ["公益性", "老旧小区、城市生命线、公益性水利及生态修复", "政府投资+DB/EPC;必要时 EPC+O", "财政及政策性资金支付的工程或服务费用", "不垫资、不承诺融资兜底;资金落实后实施"], + ["准经营性/使用者付费", "供排水、停车、垃圾处理、综合管廊、部分交通和能源设施", "BOT、BOOT、TOT、ROT 等特许经营", "使用者付费、依法依规的可行性缺口补助", "公开竞争选择经营者;不得承诺保本保收益"], + ["市场化经营性", "园区综合能源、分布式新能源、工商业储能、智慧运维", "股权投资、BOO、EMC、投建营一体化", "售电、供能、节能分享、租赁及运营服务收入", "独立核算、风险自担;退出路径和收益边界清晰"] + ], + "style": { + "font_family": "Microsoft YaHei", + "font_size": 18, + "band_row": false, + "border_color": "#DDE3E9", + "border_width": 1 + } + } + + + + 项目属性 + 典型场景 + 建议模式 + 主要收益来源 + 关键合规边界 + + + + + + + + + + + + + 公益性 + 老旧小区、城市生命线、公益性水利及生态修复 + + 政府投资+DB/EPC;必要时 EPC+O + + 财政及政策性资金支付的工程或服务费用 + + 不垫资、不承诺融资兜底;资金落实后实施 + + 准经营性/使用者付费 + + 供排水、停车、垃圾处理、综合管廊、部分交通和 + 能源设施 + + BOT、BOOT、TOT、ROT 等特许经营 + + 使用者付费、依法依规的可行性缺口补助 + + 公开竞争选择经营者;不得承诺保本保收益 + + 市场化经营性 + + 园区综合能源、分布式新能源、工商业储能、 + 智慧运维 + + 股权投资、BOO、EMC、投建营一体化 + + 售电、供能、节能分享、租赁及运营服务收入 + + 独立核算、风险自担;退出路径和收益边界清晰 + + + + diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/30_risk_matrix.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/30_risk_matrix.svg new file mode 100644 index 00000000..abc2e17b --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/30_risk_matrix.svg @@ -0,0 +1,111 @@ + + + + { + "schema": "ppt-master.semantic-table.v2", + "x": 96, "y": 242, "width": 1112, "height": 388, + "name": "risk-matrix", + "header_rows": 1, + "column_widths": [190, 110, 100, 110, 350, 252], + "columns": ["风险类别", "发生可能性", "影响程度", "综合等级", "重点监测信号", "主要责任主体"], + "rows": [ + ["政策与市场风险", "高", "高", "红色", "规划调整、准入规则变化、项目清单和建设时序变化", "区域总部、市场经营及专业单位"], + ["地方财政与支付风险", "高", "高", "红色", "资金未落实、预算调整、回款逾期、业主负债率上升", "投资、财务、市场及项目公司"], + ["电力市场价格风险", "高", "高", "红色", "现货价格波动、偏差考核增加、机制电量变化、限电上升", "投资、能源专业单位及运营主体"], + ["生态用地与建设条件风险", "中高", "高", "红色", "触及生态保护红线、用地手续滞后、地灾和征拆协调困难", "前期、设计、工程及属地项目单位"], + ["竞争与内部协同风险", "高", "中高", "橙色", "低价竞争、重复营销、内部抢标、资源投入分散", "区域总部及在渝成员企业"], + ["技术迭代风险", "中", "高", "橙色", "设备性能不达标、技术路线更替、运维成本超预期", "科技、设计、采购及运营单位"] + ], + "style": { + "font_family": "Microsoft YaHei", + "font_size": 18, + "band_row": false, + "border_color": "#DDE3E9", + "border_width": 1 + } + } + + + + 风险类别 + 发生可能性 + 影响程度 + 综合等级 + 重点监测信号 + 主要责任主体 + + + + + + + + + + + + + + + + + + + + + + + + 红色 + 红色 + 红色 + 红色 + 橙色 + 橙色 + + + 政策与市场风险 + + + 规划调整、准入规则变化、项目清单和建设时序变化 + + 区域总部、市场经营及专业单位 + + 地方财政与支付风险 + + + 资金未落实、预算调整、回款逾期、业主负债率上升 + + 投资、财务、市场及项目公司 + + 电力市场价格风险 + + + 现货价格波动、偏差考核增加、机制电量变化、限电上升 + + 投资、能源专业单位及运营主体 + + 生态用地与建设条件风险 + + 中高 + + 触及生态保护红线、用地手续滞后、地灾和征拆协调困难 + + 前期、设计、工程及属地项目单位 + + 竞争与内部协同风险 + + 中高 + 低价竞争、重复营销、内部抢标、资源投入分散 + + 区域总部及在渝成员企业 + 技术迭代风险 + + + 设备性能不达标、技术路线更替、运维成本超预期 + + 科技、设计、采购及运营单位 + + + + diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/31_power_market.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/31_power_market.svg new file mode 100644 index 00000000..d25c1838 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/fixtures/native_projection/31_power_market.svg @@ -0,0 +1,95 @@ + + + + { + "schema": "ppt-master.semantic-table.v2", + "x": 96, "y": 242, "width": 1112, "height": 340, + "name": "power-market-calculation", + "header_rows": 1, + "column_widths": [150, 280, 240, 240, 202], + "columns": ["测算模块", "核心参数", "基准情景要求", "压力测试重点", "管理用途"], + "rows": [ + ["电量", "P50/P90 发电量、可利用率、限电率、储能循环次数", "采用代表年和中长期资源评估结果", "利用小时下降、限电和设备衰减叠加", "确定容量、设备方案及偿债边界"], + ["电价与交易", "中长期、现货、机制电价、绿电绿证", "采用最新规则和可验证市场数据", "低价时段增加、机制电量下降、价差扩大", "评估年度收入和套期保值策略"], + ["辅助服务与调节", "调频、备用、需求响应、容量租赁", "仅计入具备准入条件且可持续的收入", "调用频次、性能考核和市场竞争变化", "评价储能、虚拟电厂和灵活性价值"], + ["成本与考核", "偏差考核、交易费、损耗、运维和保险", "全额纳入运营现金流", "偏差率提高、损耗增加、运维超支", "确定报价底线和运维预算"], + ["融资与退出", "利率、偿债覆盖率、残值和退出估值", "与真实融资条件和资产寿命匹配", "利率上升、再融资受限、估值折价", "控制资本金、负债率和退出时点"] + ], + "style": { + "font_family": "Microsoft YaHei", + "font_size": 18, + "band_row": false, + "border_color": "#DDE3E9", + "border_width": 1 + } + } + + + + + 测算模块 + 核心参数 + 基准情景要求 + 压力测试重点 + 管理用途 + + + + + + + + + + + + + + 电量 + P50/P90 发电量、可利用率、限电率、储能循环次数 + + 采用代表年和中长期资源评估结果 + + 利用小时下降、限电和设备衰减叠加 + + 确定容量、设备方案及偿债边界 + + 电价与交易 + 中长期、现货、机制电价、绿电绿证 + + 采用最新规则和可验证市场数据 + + 低价时段增加、机制电量下降、价差扩大 + + 评估年度收入和套期保值策略 + + 辅助服务与调节 + + 调频、备用、需求响应、容量租赁 + + 仅计入具备准入条件且可持续的收入 + + 调用频次、性能考核和市场竞争变化 + + 评价储能、虚拟电厂和灵活性价值 + + 成本与考核 + 偏差考核、交易费、损耗、运维和保险 + + 全额纳入运营现金流 + 偏差率提高、损耗增加、运维超支 + + 确定报价底线和运维预算 + + 融资与退出 + 利率、偿债覆盖率、残值和退出估值 + + 与真实融资条件和资产寿命匹配 + + 利率上升、再融资受限、估值折价 + + 控制资本金、负债率和退出时点 + + + + diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_confirm_ui_design_spec_depth.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_confirm_ui_design_spec_depth.py new file mode 100644 index 00000000..c90d2de0 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_confirm_ui_design_spec_depth.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Focused tests for Confirm UI Design Spec depth validation.""" + +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + + +SCRIPTS_DIR = Path(__file__).resolve().parents[1] +if str(SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPTS_DIR)) + +from confirm_ui import server # noqa: E402 + + +def _recommendations( + depth: object = 'brief', + *, + generation_mode: str = 'continuous', + refine_spec: bool = False, +) -> dict: + data = { + 'recommend': {'generation_mode': generation_mode}, + 'refine_spec': {'value': refine_spec}, + } + if depth is not None: + data['design_spec_depth'] = {'value': depth} + return data + + +def _result( + *, + generation_mode: str = 'continuous', + refine_spec: bool = False, + depth: str = 'brief', +) -> dict: + return { + 'generation_mode': generation_mode, + 'refine_spec': refine_spec, + 'design_spec_depth': depth, + } + + +class ConfirmUiDesignSpecDepthTests(unittest.TestCase): + def test_missing_recommendation_field_is_rejected(self) -> None: + error = server._stage2_production_recommendations_error( + _recommendations(None) + ) + + self.assertIn('design_spec_depth.value', error or '') + + def test_invalid_recommendation_value_is_rejected(self) -> None: + error = server._stage2_production_recommendations_error( + _recommendations(['brief']) + ) + + self.assertIn('"brief" or "complete"', error or '') + + def test_brief_is_accepted_without_coupling_conditions(self) -> None: + self.assertIsNone( + server._stage2_production_recommendations_error(_recommendations()) + ) + self.assertIsNone(server._stage2_production_result_error(_result())) + + def test_split_mode_rejects_brief(self) -> None: + recommendation_error = server._stage2_production_recommendations_error( + _recommendations(generation_mode='split') + ) + error = server._stage2_production_result_error( + _result(generation_mode='split') + ) + + self.assertIn('design_spec_depth.value to "complete"', recommendation_error or '') + self.assertIn('design_spec_depth to "complete"', error or '') + + def test_refinement_rejects_brief(self) -> None: + recommendation_error = server._stage2_production_recommendations_error( + _recommendations(refine_spec=True) + ) + error = server._stage2_production_result_error( + _result(refine_spec=True) + ) + + self.assertIn('design_spec_depth.value to "complete"', recommendation_error or '') + self.assertIn('design_spec_depth to "complete"', error or '') + + def test_legacy_result_defaults_to_complete(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + result_path = Path(temp_dir) / 'result.json' + result_path.write_text( + json.dumps({'stage': 'final', 'status': 'confirmed'}), + encoding='utf-8', + ) + + result = server._read_result_object(result_path) + + self.assertEqual(result['design_spec_depth'], 'complete') + + +if __name__ == '__main__': + unittest.main() diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_image_search.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_image_search.py new file mode 100644 index 00000000..933f8be9 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_image_search.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Regression tests for image-search candidate promotion.""" + +from __future__ import annotations + +import io +import json +import sys +import tempfile +import unittest +from contextlib import redirect_stderr +from pathlib import Path +from unittest.mock import patch + + +SCRIPTS_DIR = Path(__file__).resolve().parents[1] +if str(SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPTS_DIR)) + +import image_search # noqa: E402 + + +class ImageSearchPromotionTests(unittest.TestCase): + def test_quality_failure_reports_actual_required_and_metadata_dimensions( + self, + ) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + output_dir = Path(temp_dir) / "images" + candidate_dir = output_dir / "candidates" / "hero" + candidate_dir.mkdir(parents=True) + candidates_path = candidate_dir / "candidates.json" + candidates_path.write_text( + json.dumps({ + "candidate_storage": "thumbnail-only", + "target_filename": "hero.jpg", + "request": { + "min_width": 1200, + "min_height": 800, + }, + "candidates": [{ + "filename": "candidate_01.jpg", + "provider": "pixabay", + "width": 4836, + "height": 2915, + "download_url": "https://example.invalid/original.jpg", + }], + }), + encoding="utf-8", + ) + + def fake_download(_url: str, destination: str, **_kwargs) -> None: + Path(destination).write_bytes(b"fake downloaded image") + + stderr = io.StringIO() + with ( + patch.object( + image_search, + "download_image", + side_effect=fake_download, + ), + patch.object( + image_search, + "_validate_downloaded_quality", + return_value=False, + ), + patch.object( + image_search, + "_measure_actual_image", + return_value=(1280, 772), + ), + redirect_stderr(stderr), + ): + result = image_search.promote_candidate( + output_dir, + "hero.jpg", + "candidate_01.jpg", + ) + + self.assertEqual(result, 1) + message = stderr.getvalue() + self.assertIn("candidate promotion failed", message) + self.assertIn("measured 1280x772", message) + self.assertIn("required minimum 1200x800", message) + self.assertIn("metadata claimed 4836x2915", message) + self.assertFalse((output_dir / "hero.jpg").exists()) + self.assertNotIn( + "selected", + json.loads(candidates_path.read_text(encoding="utf-8")), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_native_chart_table_parity.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_native_chart_table_parity.py new file mode 100644 index 00000000..632abc47 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_native_chart_table_parity.py @@ -0,0 +1,427 @@ +#!/usr/bin/env python3 +"""Regression tests for native ChartEx and table projection behavior.""" + +from __future__ import annotations + +import sys +import unittest +from pathlib import Path +from xml.etree import ElementTree as ET + + +SCRIPTS_DIR = Path(__file__).resolve().parents[1] +if str(SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPTS_DIR)) + +from svg_to_pptx.native_objects.chartex import ( # noqa: E402 + _chart_ex_series_xml, + _chart_ex_xml, +) +from svg_to_pptx.native_objects import ( # noqa: E402 + native_object_projection_warnings, +) +from svg_to_pptx.native_objects.table import ( # noqa: E402 + _table_border_specs, + _table_border_xml, + _validate_table_payload, +) +from svg_to_pptx.pptx_package.cli import ( # noqa: E402 + _native_object_projection_findings, +) +from svg_quality.checker import SVGQualityChecker # noqa: E402 + + +FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures" / "native_projection" + + +def _fixture_root(filename: str) -> ET.Element: + return ET.parse(FIXTURES_DIR / filename).getroot() + + +def _fixture_marker(filename: str) -> ET.Element: + return next( + elem + for elem in _fixture_root(filename).iter() + if elem.get("data-pptx-replace-with") in {"chart", "table"} + ) + + +class NativeTableBorderTests(unittest.TestCase): + def test_legacy_uniform_border_is_cardinal_only(self) -> None: + for cell, style in ( + ({}, {"border_color": "#DDE3E9", "border_width": 1}), + ({"border_color": "#DDE3E9", "border_width": 1}, {}), + ): + with self.subTest(cell=cell, style=style): + specs = _table_border_specs(cell, style) + border_xml = _table_border_xml(cell, style, None) + + self.assertTrue(all(specs[side] is not None for side in ( + "left", "right", "top", "bottom", + ))) + self.assertIsNone(specs["diagonal_down"]) + self.assertIsNone(specs["diagonal_up"]) + self.assertNotIn("lnTlToBr", border_xml) + self.assertNotIn("lnBlToTr", border_xml) + + def test_diagonal_borders_require_explicit_cell_overrides(self) -> None: + cell = { + "borders": { + "diagonal_down": { + "style": "solid", + "color": "#C90A4F", + "width": 2, + }, + "diagonal_up": {"style": "none"}, + }, + } + + specs = _table_border_specs(cell, {}) + border_xml = _table_border_xml(cell, {}, None) + + self.assertEqual(specs["diagonal_down"].style, "solid") + self.assertEqual(specs["diagonal_up"].style, "none") + self.assertIn("lnTlToBr", border_xml) + self.assertIn("lnBlToTr", border_xml) + + +class NativeTableMergeTests(unittest.TestCase): + @staticmethod + def _payload(covered_cell: dict) -> dict: + return { + "schema": "ppt-master.semantic-table.v2", + "defaults": { + "cell": { + "color": "#1F2937", + "fill": "#FFFFFF", + "padding": {"left": 8, "right": 8}, + "valign": "middle", + }, + }, + "rows": [[ + {"text": "Owner", "col_span": 2}, + covered_cell, + ]], + } + + def test_merge_blankness_uses_authored_cells_before_defaults(self) -> None: + for covered_cell in ( + {}, + {"text": ""}, + {"merge_continuation": True}, + ): + with self.subTest(covered_cell=covered_cell): + _payload, rows, col_count, merge_layout = ( + _validate_table_payload(self._payload(covered_cell)) + ) + self.assertEqual((len(rows), col_count), (1, 2)) + self.assertIn((0, 1), merge_layout) + + for covered_cell in ( + {"text": "authored"}, + {"paragraphs": [{"text": ""}]}, + {"fill": "#F1F4F8"}, + ): + with self.subTest(covered_cell=covered_cell): + with self.assertRaisesRegex(RuntimeError, "must be blank"): + _validate_table_payload(self._payload(covered_cell)) + + +class NativeChartExTests(unittest.TestCase): + @staticmethod + def _chart_data(chart_type: str) -> dict: + if chart_type in {"sunburst", "treemap"}: + return { + "type": chart_type, + "levels": [["A", "B"]], + "values": [1, 2], + } + if chart_type == "histogram": + return {"type": chart_type, "values": [1, 2]} + return { + "type": chart_type, + "categories": ["A", "B"], + "values": [1, 2], + "subtotals": [], + } + + def test_chart_without_title_omits_title_element_and_uses_payload_name(self) -> None: + chart_data = self._chart_data("treemap") + for payload in ( + {"name": "Market & "}, + {"name": "Market & ", "title": ""}, + {"name": "Market & ", "title": {"text": " "}}, + ): + with self.subTest(payload=payload): + chart_xml = _chart_ex_xml( + payload, + chart_data, + chart_rels_id="rId1", + ).decode("utf-8") + + self.assertNotIn("Market & <Space>", chart_xml) + self.assertNotIn("Series 1", chart_xml) + + def test_chart_title_contains_text_and_supplies_series_name(self) -> None: + chart_data = self._chart_data("treemap") + title_xml = ( + '' + 'Visible & <Title>' + '' + ) + for title in ("Visible & ", {"text": "Visible & <Title>"}): + with self.subTest(title=title): + chart_xml = _chart_ex_xml( + {"name": "internal-name", "title": title}, + chart_data, + chart_rels_id="rId1", + ).decode("utf-8") + + self.assertIn(title_xml, chart_xml) + self.assertGreaterEqual( + chart_xml.count("<cx:v>Visible & <Title></cx:v>"), + 2, + ) + + def test_chart_ex_series_families_do_not_hardcode_series_one(self) -> None: + for chart_type in ( + "treemap", + "sunburst", + "histogram", + "pareto", + "waterfall", + "funnel", + ): + with self.subTest(chart_type=chart_type): + series_xml = _chart_ex_series_xml( + {"name": "Native & Series"}, + self._chart_data(chart_type), + ) + self.assertNotIn("Series 1", series_xml) + self.assertIn("Native & Series", series_xml) + + +class NativeProjectionCheckerTests(unittest.TestCase): + EXPECTED_FINDINGS = { + "06_1145_completion.svg": ( + "style.text_color", + "data mark fill #C90A4F", + ), + "07_segment_mix.svg": ( + "legend label is not visible", + "'其他业务'", + "style.text_color", + ), + "10_competitiveness.svg": ( + "style.text_color", + "plot area is visible", + "5 countable radial gridlines", + ), + "11_market_space.svg": ( + "tile color sequence not projected", + "visible text not projected", + "'61.57%'", + ), + "15_signing_target.svg": ( + "style.text_color", + ), + "27_business_models.svg": ( + "fallback row heights are non-uniform", + "header style not projected", + "whole column 5 fill #F4F6F8", + "first-column text style not projected", + "border topology not projected", + ), + "30_risk_matrix.svg": ( + "fallback row heights are non-uniform", + "header style not projected", + "first-column text style not projected", + "border topology not projected", + "inset graphical cell with text", + "Native-ready=no", + ), + "31_power_market.svg": ( + "fallback row heights are non-uniform", + "header style not projected", + "whole column 4 fill #F4F6F8", + "first-column text style not projected", + "border topology not projected", + ), + } + + def test_projection_helpers_report_each_diagnosed_page(self) -> None: + for filename, expected in self.EXPECTED_FINDINGS.items(): + with self.subTest(filename=filename): + warnings = native_object_projection_warnings( + _fixture_marker(filename) + ) + joined = "\n".join(warnings) + for text in expected: + self.assertIn(text, joined) + + def test_explicit_chart_text_axis_and_grid_colors_use_role_inference(self) -> None: + marker = ET.fromstring(""" + <g data-pptx-replace-with="chart" data-pptx-bounds="0 0 400 240"> + <metadata type="application/json"> + { + "x": 0, "y": 0, "width": 400, "height": 240, + "type": "column", + "categories": ["A"], + "series": [{"name": "Series", "values": [1]}], + "style": { + "text_color": "#111111", + "axis_color": "#222222", + "grid_color": "#333333" + } + } + </metadata> + <text x="20" y="220" fill="#AAAAAA">A</text> + <line id="x-axis" x1="20" y1="200" x2="380" y2="200" + stroke="#BBBBBB" /> + <line id="gridline-1" x1="20" y1="100" x2="380" y2="100" + stroke="#CCCCCC" /> + <rect id="bar-0" x="100" y="80" width="80" height="120" + fill="#4472C4" /> + </g> + """) + + joined = "\n".join(native_object_projection_warnings(marker)) + + self.assertIn( + "style.text_color #111111 differs from fallback dominant text_color #AAAAAA", + joined, + ) + self.assertIn( + "style.axis_color #222222 differs from fallback dominant axis_color #BBBBBB", + joined, + ) + self.assertIn( + "style.grid_color #333333 differs from fallback dominant grid_color #CCCCCC", + joined, + ) + + def test_checker_surfaces_projection_findings_as_warnings(self) -> None: + checker = SVGQualityChecker() + for filename, expected in self.EXPECTED_FINDINGS.items(): + with self.subTest(filename=filename): + result = {"errors": [], "warnings": []} + checker._check_native_object_markers( + _fixture_root(filename), + result, + ) + joined = "\n".join(result["warnings"]) + for text in expected: + self.assertIn(text, joined) + self.assertFalse(result["errors"]) + + def test_native_export_preflight_collects_the_same_findings(self) -> None: + fixture_paths = [ + FIXTURES_DIR / filename + for filename in self.EXPECTED_FINDINGS + ] + + findings = _native_object_projection_findings(fixture_paths) + findings_by_page: dict[str, str] = {} + for filename, _marker_id, warning in findings: + findings_by_page.setdefault(filename, "") + findings_by_page[filename] += warning + "\n" + + self.assertEqual(set(findings_by_page), set(self.EXPECTED_FINDINGS)) + for filename, expected in self.EXPECTED_FINDINGS.items(): + with self.subTest(filename=filename): + for text in expected: + self.assertIn(text, findings_by_page[filename]) + + def test_table_parity_accepts_uniform_run_level_bold_and_color(self) -> None: + marker = ET.fromstring(""" + <g data-pptx-replace-with="table" data-pptx-bounds="0 0 200 80"> + <metadata type="application/json"> + { + "schema": "ppt-master.semantic-table.v2", + "x": 0, "y": 0, "width": 200, "height": 80, + "header_rows": 1, + "column_widths": [100, 100], + "row_heights": [40, 40], + "columns": [ + { + "align": "l", + "paragraphs": [{"runs": [{ + "text": "Level", "bold": true, "color": "#1E1A16" + }]}] + }, + { + "align": "l", + "paragraphs": [{"runs": [{ + "text": "Meaning", "bold": true, "color": "#1E1A16" + }]}] + } + ], + "rows": [[ + {"paragraphs": [{"runs": [{ + "text": "Altar", "bold": true, "color": "#1E1A16" + }]}]}, + {"text": "Cosmos", "color": "#2F2A25"} + ]] + } + </metadata> + <line x1="0" y1="0" x2="200" y2="0" stroke="#999999"/> + <line x1="0" y1="40" x2="200" y2="40" stroke="#999999"/> + <line x1="0" y1="80" x2="200" y2="80" stroke="#999999"/> + <g font-weight="bold" fill="#1E1A16"> + <text x="10" y="25">Level</text> + <text x="110" y="25">Meaning</text> + <text x="10" y="65">Altar</text> + </g> + <text x="110" y="65" fill="#2F2A25">Cosmos</text> + </g> + """) + + joined = "\n".join(native_object_projection_warnings(marker)) + + self.assertNotIn("header style not projected", joined) + self.assertNotIn("first-column text style not projected", joined) + + def test_table_parity_accepts_positioned_tspan_lines_for_paragraphs(self) -> None: + marker = ET.fromstring(""" + <g data-pptx-replace-with="table" data-pptx-bounds="0 0 200 80"> + <metadata type="application/json"> + { + "schema": "ppt-master.semantic-table.v2", + "x": 0, "y": 0, "width": 200, "height": 80, + "header_rows": 0, + "rows": [[{"paragraphs": ["First line", "Second line"]}]] + } + </metadata> + <text x="10" y="25"> + <tspan x="10" y="25">First line</tspan><tspan x="10" dy="24">Second line</tspan> + </text> + </g> + """) + + joined = "\n".join(native_object_projection_warnings(marker)) + + self.assertNotIn("fallback text is missing", joined) + + +class NativeTablePayloadRoundTripTest(unittest.TestCase): + def test_marker_validation_keeps_original_table_payload(self): + """The converter re-expands the payload; validation must not hand back an expanded copy.""" + import xml.etree.ElementTree as ET + from svg_to_pptx.native_objects import _validate_native_object_marker_payload + from svg_to_pptx.native_objects.table import _build_native_table # noqa: F401 + from semantic_table import expand_semantic_table_payload + fixture = FIXTURES_DIR / "27_business_models.svg" + root = ET.parse(fixture).getroot() + marker = next( + el for el in root.iter() + if el.get("data-pptx-replace-with") == "table" + ) + kind, payload, rows = _validate_native_object_marker_payload(marker) + self.assertEqual(kind, "table") + self.assertEqual(payload.get("schema"), "ppt-master.semantic-table.v2") + expand_semantic_table_payload(payload) # must still expand cleanly + + +if __name__ == "__main__": + unittest.main() diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_slice_images.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_slice_images.py new file mode 100644 index 00000000..5466eb07 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_slice_images.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Focused tests for illustration-sheet alpha-key diagnostics.""" + +from __future__ import annotations + +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +from PIL import Image, ImageDraw + + +SCRIPTS_DIR = Path(__file__).resolve().parents[1] +SCRIPT = SCRIPTS_DIR / "slice_images.py" + + +class SliceImagesDiagnosticsTests(unittest.TestCase): + def test_strict_alpha_reports_measured_sheet_border_and_exact_rerun(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + sheet_path = root / "sheet.png" + output_dir = root / "output" + image = Image.new("RGB", (100, 80), (87, 178, 101)) + draw = ImageDraw.Draw(image) + draw.rectangle((30, 22, 70, 58), fill=(170, 40, 55)) + draw.point((0, 0), fill=(84, 176, 100)) + draw.point((99, 79), fill=(88, 180, 102)) + image.save(sheet_path) + + result = subprocess.run( + [ + sys.executable, + str(SCRIPT), + str(sheet_path), + "--grid", "1x1", + "--names", "element", + "--trim", + "--alpha", + "--strict-alpha", + "--bg", "#00FF00", + "--tolerance", "12", + "--output", str(output_dir), + ], + capture_output=True, + text=True, + check=False, + ) + + self.assertEqual(result.returncode, 1) + self.assertIn("key background #00FF00", result.stderr) + self.assertIn("dominant #57B265", result.stderr) + self.assertIn("max channel spread 3", result.stderr) + self.assertIn("--bg #57B265 --tolerance 12", result.stderr) + self.assertFalse((output_dir / "element.png").exists()) + + +if __name__ == "__main__": + unittest.main() diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_svg_quality_checker.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_svg_quality_checker.py new file mode 100644 index 00000000..006c3782 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_svg_quality_checker.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +"""Focused regression tests for SVG text and module-bound checks.""" + +from __future__ import annotations + +import sys +import unittest +from pathlib import Path +from unittest.mock import patch +from xml.etree import ElementTree as ET + + +SCRIPTS_DIR = Path(__file__).resolve().parents[1] +if str(SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPTS_DIR)) + +from svg_quality import checker as checker_module # noqa: E402 +from svg_quality.checker import SVGQualityChecker # noqa: E402 + + +SVG_NS = 'http://www.w3.org/2000/svg' + + +def _parse_svg( + body: str, + root_attributes: str = '', + view_box: str = '0 0 1000 1000', +) -> ET.Element: + return ET.fromstring( + f'<svg xmlns="{SVG_NS}" viewBox="{view_box}" ' + f'{root_attributes}>{body}</svg>' + ) + + +def _empty_result() -> dict: + return {'errors': [], 'warnings': []} + + +class SVGQualityCheckerBoundsTests(unittest.TestCase): + def test_leading_direct_text_and_positioned_tspans_are_estimable(self) -> None: + root = _parse_svg( + '<g id="module" data-pptx-bounds="0 0 1000 1000">' + '<text id="mixed" x="10" y="30" font-size="20">' + 'Leading <tspan font-weight="700">bold</tspan>' + '<tspan x="10" dy="28">Second line</tspan>' + '</text></g>' + ) + text_element = root.find(f'.//{{{SVG_NS}}}text') + self.assertIsNotNone(text_element) + parent_by_id = { + id(child): parent + for parent in root.iter() + for child in list(parent) + } + font_sizes = checker_module._resolve_project_font_sizes(root) + letter_spacings = checker_module._resolve_project_letter_spacings( + root, + font_sizes, + ) + + lines = SVGQualityChecker._positioned_text_lines( + text_element, + parent_by_id, + font_sizes, + letter_spacings, + ) + + self.assertIsNotNone(lines) + self.assertEqual(len(lines), 2) + self.assertEqual( + [''.join(run['text'] for run in line[3]) for line in lines], + ['Leading bold', 'Second line'], + ) + result = _empty_result() + SVGQualityChecker()._check_text_bounds(root, result) + self.assertFalse( + any('Cannot verify' in warning for warning in result['warnings']) + ) + + def test_existing_all_tspan_and_inline_single_line_forms_still_resolve(self) -> None: + cases = { + 'all_tspan': ( + '<text x="10" y="30" font-size="20">' + '<tspan x="10">First</tspan>' + '<tspan x="10" dy="28">Second</tspan>' + '</text>', + 2, + ), + 'single_inline': ( + '<text x="10" y="30" font-size="20">' + 'First <tspan font-weight="700">line</tspan>' + '</text>', + 1, + ), + } + for name, (text_svg, expected_lines) in cases.items(): + with self.subTest(name=name): + root = _parse_svg( + '<g id="module" data-pptx-bounds="0 0 1000 1000">' + f'{text_svg}</g>' + ) + text_element = root.find(f'.//{{{SVG_NS}}}text') + parent_by_id = { + id(child): parent + for parent in root.iter() + for child in list(parent) + } + font_sizes = checker_module._resolve_project_font_sizes(root) + letter_spacings = checker_module._resolve_project_letter_spacings( + root, + font_sizes, + ) + bounds = SVGQualityChecker._estimated_text_bounds( + text_element, + parent_by_id, + font_sizes, + letter_spacings, + ) + self.assertIsNotNone(bounds) + if expected_lines == 2: + lines = SVGQualityChecker._positioned_text_lines( + text_element, + parent_by_id, + font_sizes, + letter_spacings, + ) + self.assertEqual(len(lines), expected_lines) + else: + runs = SVGQualityChecker._resolved_single_line_text_runs( + text_element, + parent_by_id, + font_sizes, + letter_spacings, + ) + self.assertIsNotNone(runs) + + def test_module_bounds_use_headroom_but_page_bounds_do_not(self) -> None: + root = _parse_svg( + '<g id="module" data-pptx-bounds="0 0 100 100">' + '<text id="text" x="0" y="20" font-size="12">Text</text>' + '</g>' + ) + include_headroom_calls = [] + + def estimated_bounds(*_args, include_headroom=True, **_kwargs): + include_headroom_calls.append(include_headroom) + right = 106.0 if include_headroom else 100.0 + return (0.0, 0.0, right, 20.0) + + result = _empty_result() + checker = SVGQualityChecker() + with patch.object( + checker, + '_estimated_text_bounds', + side_effect=estimated_bounds, + ): + checker._check_text_bounds(root, result) + + self.assertEqual(include_headroom_calls, [True, False]) + self.assertTrue( + any('data-pptx-bounds' in error for error in result['errors']) + ) + self.assertFalse( + any('root viewBox' in error for error in result['errors']) + ) + + def test_horizontal_overflow_reports_per_cluster_width_and_capacity( + self, + ) -> None: + cjk_text = '天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏' + module_root = _parse_svg( + '<g id="module" data-pptx-bounds="0 0 540 100">' + f'<text x="0" y="30" font-size="24">{cjk_text}</text>' + '</g>' + ) + module_result = _empty_result() + SVGQualityChecker()._check_text_bounds(module_root, module_result) + module_error = next( + error + for error in module_result['errors'] + if 'data-pptx-bounds on the horizontal axis' in error + ) + self.assertTrue( + module_error.endswith( + '≈25.4 px per CJK char at 24px incl. headroom; ' + '≈21 chars fit in 540 px' + ), + module_error, + ) + + page_root = _parse_svg( + '<g id="module" data-pptx-bounds="0 0 1000 100">' + f'<text x="0" y="30" font-size="24">{cjk_text}</text>' + '</g>', + view_box='0 0 540 100', + ) + page_result = _empty_result() + SVGQualityChecker()._check_text_bounds(page_root, page_result) + page_error = next( + error + for error in page_result['errors'] + if 'exceeds the root viewBox on the horizontal axis' in error + ) + self.assertTrue( + page_error.endswith( + '≈24.0 px per CJK char at 24px without headroom; ' + '≈22 chars fit in 540 px' + ), + page_error, + ) + + def test_root_module_overlap_is_error_with_minimal_exemptions(self) -> None: + cases = ( + ( + 'overlap_above_tolerance', + _parse_svg( + '<g id="first" data-pptx-bounds="0 0 100 100"/>' + '<g id="second" data-pptx-bounds="98.9 0 100 100"/>' + ), + True, + ), + ( + 'overlap_at_tolerance', + _parse_svg( + '<g id="first" data-pptx-bounds="0 0 100 100"/>' + '<g id="second" data-pptx-bounds="99 0 100 100"/>' + ), + False, + ), + ( + 'static_page_frame', + _parse_svg( + '<g id="background" data-pptx-role="background" ' + 'data-pptx-bounds="0 0 1000 1000"/>' + '<g id="content" data-pptx-bounds="100 100 200 200"/>' + ), + False, + ), + ( + 'structured_slots', + _parse_svg( + '<g id="picture" data-pptx-placeholder="picture" ' + 'data-pptx-bounds="0 0 500 500"/>' + '<g id="title" data-pptx-placeholder="title" ' + 'data-pptx-bounds="100 100 300 100"/>', + 'data-pptx-master="master" ' + 'data-pptx-master-name="Master" ' + 'data-pptx-layout="layout" ' + 'data-pptx-layout-name="Layout"', + ), + False, + ), + ( + 'structured_ordinary_modules', + _parse_svg( + '<g id="first" data-pptx-bounds="0 0 100 100"/>' + '<g id="second" data-pptx-bounds="50 0 100 100"/>', + 'data-pptx-master="master" ' + 'data-pptx-master-name="Master" ' + 'data-pptx-layout="layout" ' + 'data-pptx-layout-name="Layout"', + ), + True, + ), + ( + 'off_canvas_morph_staging', + _parse_svg( + '<g id="morph-a" data-pptx-morph-staging="true" ' + 'data-pptx-bounds="1100 0 100 100"/>' + '<g id="morph-b" data-pptx-morph-staging="true" ' + 'data-pptx-bounds="1100 0 100 100"/>' + ), + False, + ), + ) + checker = SVGQualityChecker() + for name, root, expects_overlap_error in cases: + with self.subTest(name=name): + result = _empty_result() + checker._check_module_bounds_contract(root, result) + overlap_errors = [ + error + for error in result['errors'] + if 'data-pptx-bounds overlaps' in error + ] + self.assertEqual(bool(overlap_errors), expects_overlap_error) + + +if __name__ == '__main__': + unittest.main() diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_text_measure.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_text_measure.py new file mode 100644 index 00000000..fa6e3eb2 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_text_measure.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +"""Focused tests for the authoring-time text measurement CLI.""" + +from __future__ import annotations + +import json +import subprocess +import sys +import unittest +from pathlib import Path +from xml.etree import ElementTree as ET + + +SCRIPTS_DIR = Path(__file__).resolve().parents[1] +if str(SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPTS_DIR)) + +from svg_to_pptx.drawingml.elements import ( # noqa: E402 + estimate_single_line_text_frame_width, +) +from text_measure import ( # noqa: E402 + _CLOSING_PUNCTUATION, + _OPENING_PUNCTUATION, + _render_wrapped_svg, + measure_text, + text_box, + wrap_text, +) +from svg_quality.checker import SVGQualityChecker # noqa: E402 + + +SCRIPT = SCRIPTS_DIR / 'text_measure.py' +SAMPLE = 'Current macro-free package; the main editable format in modern PowerPoint' +CJK_PARAGRAPHS = ( + '通体蓝瓦圆顶对应天穹,不靠体量压人,而用“圆”与“空”消解人与天地的隔阂;' + '蓝瓦贴合青天之色,模糊人间与天宇的边界。', + '三层汉白玉圆台层层递进,暗藏天时历法;古人在此祭天,以高台承接天地阳气,' + '把四时流转、阴阳交替的规律固化为仪式。', +) + + +def _run_cli(*args: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, str(SCRIPT), *args], + capture_output=True, + text=True, + check=False, + ) + + +class TextMeasureTests(unittest.TestCase): + def test_measure_matches_checker_estimator(self) -> None: + run = { + 'text': SAMPLE, + 'font_size': 22.0, + 'font_family': 'Calibri', + 'font_weight': 'normal', + 'letter_spacing': 0.0, + } + expected = estimate_single_line_text_frame_width([run]) + + self.assertAlmostEqual(measure_text(SAMPLE, size=22), expected) + result = _run_cli('measure', SAMPLE, '--size', '22') + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, f'880.4\t{SAMPLE}\n') + + def test_wrap_lines_never_exceed_max_width(self) -> None: + max_width = 180.0 + lines, widths, oversized = wrap_text( + 'Editable DrawingML text stays measurable', + size=22, + max_width=max_width, + ) + + self.assertGreater(len(lines), 1) + self.assertEqual(oversized, []) + for line, width in zip(lines, widths): + direct = measure_text(line, size=22) + self.assertAlmostEqual(width, direct) + self.assertLessEqual(direct, max_width) + + def test_wrap_can_request_raw_width_without_headroom(self) -> None: + with_headroom = measure_text(SAMPLE, size=22) + raw = measure_text(SAMPLE, size=22, include_headroom=False) + + self.assertGreater(with_headroom, raw) + result = _run_cli( + 'wrap', SAMPLE, '--size', '22', '--max-width', '900', + '--x', '96', '--dy', '30', '--no-headroom', '--json', + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json.loads(result.stdout) + self.assertEqual(payload['widths'], [raw]) + + def test_oversized_token_is_emitted_alone_with_warning(self) -> None: + token = 'UnbreakableToken' + lines, widths, oversized = wrap_text(token, size=22, max_width=20) + + self.assertEqual(lines, [token]) + self.assertGreater(widths[0], 20) + self.assertEqual(oversized, [(token, widths[0])]) + result = _run_cli( + 'wrap', token, '--size', '22', '--max-width', '20', + '--x', '96', '--dy', '30', + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, f'{token}\n') + self.assertIn('Warning: token exceeds max width', result.stderr) + + def test_cjk_wrap_keeps_closing_punctuation_with_previous_cluster(self) -> None: + lines, widths, oversized = wrap_text('甲乙,丙丁', size=20, max_width=45) + + self.assertEqual(lines, ['甲', '乙,', '丙丁']) + self.assertEqual(oversized, []) + self.assertTrue(all(width <= 45 for width in widths)) + + def test_cjk_wrap_fits_checker_module_bounds_and_obeys_line_punctuation(self) -> None: + groups: list[str] = [] + for index, paragraph in enumerate(CJK_PARAGRAPHS): + lines, widths, oversized = wrap_text( + paragraph, + size=24, + family='Microsoft YaHei', + max_width=540, + ) + + self.assertEqual(oversized, []) + self.assertTrue(all(width <= 540 for width in widths)) + for line in lines: + self.assertNotIn(line[0], _CLOSING_PUNCTUATION) + self.assertNotIn(line[-1], _OPENING_PUNCTUATION) + + top = index * 180 + rendered = _render_wrapped_svg( + lines, + x=100, + y=top + 30, + dy=36, + ) + groups.append( + f'<g id="module-{index}" data-pptx-bounds="100 {top} 540 160">' + f'{rendered}</g>' + ) + + root = ET.fromstring( + '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720" ' + 'font-family="Microsoft YaHei" font-size="24">' + + ''.join(groups) + + '</svg>' + ) + result = {'warnings': [], 'errors': [], 'info': {}} + SVGQualityChecker()._check_text_bounds(root, result) + + self.assertEqual(result['warnings'], []) + self.assertEqual(result['errors'], []) + + def test_box_arithmetic_and_anchor_adjustment(self) -> None: + expected_x = {'start': 100.0, 'middle': 60.0, 'end': 20.0} + for anchor, left in expected_x.items(): + with self.subTest(anchor=anchor): + bounds = text_box( + x=100, + baseline_y=50, + size=20, + lines=2, + dy=24, + width=80, + anchor=anchor, + ) + self.assertEqual(bounds['x'], left) + self.assertEqual(bounds['y'], 33.0) + self.assertEqual(bounds['width'], 80.0) + self.assertEqual(bounds['height'], 48.0) + self.assertEqual(bounds['top'], 33.0) + self.assertEqual(bounds['bottom'], 81.0) + + result = _run_cli( + 'box', '--x', '100', '--y', '50', '--size', '20', + '--lines', '2', '--dy', '24', '--width', '80', '--anchor', 'middle', + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual( + result.stdout, + 'data-pptx-bounds="60 33 80 48"\ttop=33\tbottom=81\n', + ) + + def test_wrapped_svg_escapes_xml_text(self) -> None: + rendered = _render_wrapped_svg( + ['A & B', '<C>'], + x=1.234, + dy=20, + y=10, + ) + + self.assertEqual( + rendered, + '<text x="1.23" y="10">A & B' + '<tspan x="1.23" dy="20"><C></tspan></text>', + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_update_spec.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_update_spec.py new file mode 100644 index 00000000..38d73c07 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/tests/test_update_spec.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""End-to-end regression tests for synchronized spec and SVG updates.""" + +from __future__ import annotations + +import re +import shutil +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + + +SCRIPTS_DIR = Path(__file__).resolve().parents[1] +if str(SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPTS_DIR)) + +from stamp_native_fallbacks import stamp_file # noqa: E402 + + +UPDATE_SPEC = SCRIPTS_DIR / "update_spec.py" +SVG_CHECKER = SCRIPTS_DIR / "svg_quality_checker.py" +FALLBACK_HASH_RE = re.compile(r'data-pptx-fallback-sha256="([0-9a-f]{64})"') + + +class UpdateSpecNativeFallbackTests(unittest.TestCase): + def _copy_minimal_project(self, root: Path) -> Path: + fixture = root / "fixture" + svg_dir = fixture / "svg_output" + svg_dir.mkdir(parents=True) + (fixture / "spec_lock.md").write_text( + """<!-- ppt-master-schema: spec-lock/v1 --> +# Execution Lock + +## canvas +- viewBox: 0 0 1280 720 +- format: PPT 16:9 + +## colors +- background: #FFFFFF +- accent: #B23A2A +- body_text: #1F2937 + +## typography +- font_family: Microsoft YaHei +- body: 24 +- title: 36 + +## pptx_structure +- mode: flat +""", + encoding="utf-8", + ) + svg_path = svg_dir / "01_native_table.svg" + svg_path.write_text( + """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720" + data-pptx-page-role="content" font-family="Microsoft YaHei"> + <rect id="background" data-pptx-role="background" x="0" y="0" width="1280" height="720" fill="#FFFFFF"/> + <g id="accent-table" data-pptx-replace-with="table" data-pptx-bounds="100 100 300 80"> + <metadata type="application/json">{ + "schema": "ppt-master.semantic-table.v2", + "name": "accent-table", + "x": 100, "y": 100, "width": 300, "height": 80, + "header_rows": 0, + "column_widths": [300], + "row_heights": [80], + "style": { + "font_family": "Microsoft YaHei", + "font_size": 24, + "band_row": false, + "border_color": "#B23A2A", + "border_width": 1 + }, + "rows": [[{"text": "Accent", "color": "#B23A2A"}]] + }</metadata> + <rect x="100" y="100" width="300" height="80" fill="#FFFFFF" stroke="#B23A2A"/> + <text x="120" y="150" font-size="24" fill="#B23A2A">Accent</text> + </g> +</svg> +""", + encoding="utf-8", + ) + svg_first, json_first, changed = stamp_file(svg_path, write=True) + self.assertEqual((svg_first, json_first, changed), (1, 0, True)) + + project = root / "project" + shutil.copytree(fixture, project) + return project + + def test_update_restamps_touched_native_fallback_and_checker_passes( + self, + ) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + project = self._copy_minimal_project(Path(temp_dir)) + lock_path = project / "spec_lock.md" + svg_path = project / "svg_output" / "01_native_table.svg" + original_lock = lock_path.read_bytes() + original_svg = svg_path.read_bytes() + original_hash = FALLBACK_HASH_RE.search( + original_svg.decode("utf-8") + ) + self.assertIsNotNone(original_hash) + + dry_run = subprocess.run( + [ + sys.executable, + str(UPDATE_SPEC), + str(project), + "colors.accent=#8C2F1F", + "--dry-run", + ], + check=False, + capture_output=True, + text=True, + ) + self.assertEqual(dry_run.returncode, 0, dry_run.stderr) + self.assertIn( + "[dry-run] native fallbacks: 1 marker(s) would be re-stamped", + dry_run.stdout, + ) + self.assertEqual(lock_path.read_bytes(), original_lock) + self.assertEqual(svg_path.read_bytes(), original_svg) + + update = subprocess.run( + [ + sys.executable, + str(UPDATE_SPEC), + str(project), + "colors.accent=#8C2F1F", + ], + check=False, + capture_output=True, + text=True, + ) + self.assertEqual(update.returncode, 0, update.stderr) + self.assertIn( + "native fallbacks: 1 marker(s) re-stamped", + update.stdout, + ) + updated_svg = svg_path.read_text(encoding="utf-8") + self.assertNotIn("#B23A2A", updated_svg) + self.assertIn("#8C2F1F", updated_svg) + updated_hash = FALLBACK_HASH_RE.search(updated_svg) + self.assertIsNotNone(updated_hash) + self.assertNotEqual(original_hash.group(1), updated_hash.group(1)) + + checker = subprocess.run( + [ + sys.executable, + str(SVG_CHECKER), + str(svg_path), + "--canonical-authoring", + "--stage", + "final", + ], + check=False, + capture_output=True, + text=True, + ) + self.assertEqual( + checker.returncode, + 0, + checker.stdout + checker.stderr, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/text_measure.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/text_measure.py new file mode 100644 index 00000000..d3f803f4 --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/text_measure.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +"""PPT Master - Text Measurement + +Measure, wrap, or calculate bounds with the SVG checker's width estimator. + +Usage: + python3 scripts/text_measure.py <measure|wrap|box> [options] +Examples: + python3 scripts/text_measure.py measure "Editable text" --size 22 +Dependencies: + Standard library and PPT Master sibling modules +""" + +from __future__ import annotations + +import argparse +import html +import json +import math +import sys +import unicodedata +from functools import partial +from pathlib import Path + + +_SCRIPTS_DIR = Path(__file__).resolve().parent +if str(_SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(_SCRIPTS_DIR)) + +from console_encoding import configure_utf8_stdio # noqa: E402 +from svg_to_pptx.drawingml.elements import estimate_single_line_text_frame_width # noqa: E402 +from svg_to_pptx.drawingml.utils import split_project_text_clusters # noqa: E402 + + +_CLOSING_PUNCTUATION = frozenset(',.;:!?)]}、,。;:!?)》」』】”’') +_OPENING_PUNCTUATION = frozenset('([{(《「『【“‘') +_PREFERRED_BREAK_PUNCTUATION = frozenset(',。;:') +_LATIN_TOKEN_CONNECTORS = frozenset("'’._:/+%@#-") +_WEIGHTS = ('normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900') + + +def _bounded_float(value: str, *, minimum: float | None = None, strict: bool = False) -> float: + number = float(value) + if not math.isfinite(number): + raise argparse.ArgumentTypeError('must be a finite number') + if minimum is not None and (number < minimum or strict and number == minimum): + relation = 'greater than' if strict else 'at least' + raise argparse.ArgumentTypeError(f'must be {relation} {minimum:g}') + return number + + +_positive_float = partial(_bounded_float, minimum=0.0, strict=True) +_nonnegative_float = partial(_bounded_float, minimum=0.0) + + +def _positive_int(value: str) -> int: + number = int(value) + if number < 1: + raise argparse.ArgumentTypeError('must be at least 1') + return number + + +def _format_number(value: float) -> str: + rounded = round(value, 2) + return '0' if rounded == 0 else f'{rounded:.2f}'.rstrip('0').rstrip('.') + + +def measure_text( + text: str, *, size: float, family: str = 'Calibri', + weight: str = 'normal', letter_spacing: float = 0.0, + include_headroom: bool = True, +) -> float: + """Measure one line with the checker-owned DrawingML estimator.""" + run = dict( + text=text, font_size=size, font_family=family, + font_weight=weight, letter_spacing=letter_spacing, + ) + return estimate_single_line_text_frame_width( + [run], + include_headroom=include_headroom, + ) + + +def _is_latin_or_number_cluster(cluster: str) -> bool: + """Return whether a rendered cluster belongs to a Latin/number token.""" + bases = [ + ch + for ch in cluster + if unicodedata.category(ch) not in {'Mn', 'Mc', 'Me'} + ] + return bool(bases) and all( + ch.isdigit() or 'LATIN' in unicodedata.name(ch, '') + for ch in bases + ) + + +def _lexical_units(text: str) -> list[str]: + """Split a paragraph while keeping Latin words and numbers atomic.""" + clusters = split_project_text_clusters(' '.join(text.split())) + units: list[str] = [] + pending_space = False + index = 0 + while index < len(clusters): + cluster = clusters[index] + if cluster.isspace(): + pending_space = bool(units) + index += 1 + continue + + end = index + 1 + if _is_latin_or_number_cluster(cluster): + while end < len(clusters): + next_cluster = clusters[end] + if _is_latin_or_number_cluster(next_cluster): + end += 1 + continue + connector = ( + next_cluster in _LATIN_TOKEN_CONNECTORS + or ( + next_cluster == ',' + and clusters[end - 1].isdigit() + ) + ) + if ( + connector + and end + 1 < len(clusters) + and _is_latin_or_number_cluster(clusters[end + 1]) + ): + end += 2 + continue + break + + prefix = ' ' if pending_space else '' + units.append(prefix + ''.join(clusters[index:end])) + pending_space = False + index = end + return units + + +def _protected_units(text: str) -> list[str]: + units = _lexical_units(text) + protected: list[str] = [] + for unit in units: + content = unit.lstrip() + if protected and ( + content[0] in _CLOSING_PUNCTUATION + or protected[-1].rstrip()[-1] in _OPENING_PUNCTUATION + ): + protected[-1] += unit + else: + protected.append(unit) + return protected + + +def _joined_units(units: list[str], start: int, end: int) -> str: + return ''.join(units[start:end]).lstrip() + + +def _preferred_break_after(text: str) -> bool: + tail = text.rstrip() + while ( + tail + and tail[-1] in _CLOSING_PUNCTUATION + and tail[-1] not in _PREFERRED_BREAK_PUNCTUATION + ): + tail = tail[:-1] + return bool(tail) and tail[-1] in _PREFERRED_BREAK_PUNCTUATION + + +def wrap_text( + text: str, *, size: float, max_width: float, family: str = 'Calibri', + weight: str = 'normal', letter_spacing: float = 0.0, + include_headroom: bool = True, +) -> tuple[list[str], list[float], list[tuple[str, float]]]: + """Greedily wrap text and return lines, widths, and oversized units.""" + style = dict( + size=size, + family=family, + weight=weight, + letter_spacing=letter_spacing, + include_headroom=include_headroom, + ) + units = _protected_units(text) + if not units: + return [''], [0.0], [] + + lines: list[str] = [] + widths: list[float] = [] + oversized: list[tuple[str, float]] = [] + start = 0 + while start < len(units): + fit_widths: dict[int, float] = {} + preferred_end: int | None = None + end = start + while end < len(units): + candidate_end = end + 1 + candidate = _joined_units(units, start, candidate_end) + candidate_width = measure_text(candidate, **style) + if candidate_width > max_width: + break + fit_widths[candidate_end] = candidate_width + if _preferred_break_after(candidate): + preferred_end = candidate_end + end = candidate_end + + if end == len(units): + line = _joined_units(units, start, end) + lines.append(line) + widths.append(fit_widths[end]) + break + + if end == start: + unit = units[start].lstrip() + unit_width = measure_text(unit, **style) + lines.append(unit) + widths.append(unit_width) + oversized.append((unit, unit_width)) + start += 1 + continue + + line_end = preferred_end or end + lines.append(_joined_units(units, start, line_end)) + widths.append(fit_widths[line_end]) + start = line_end + return lines, widths, oversized + + +def _render_wrapped_svg(lines: list[str], *, x: float, dy: float, y: float | None) -> str: + escaped = [html.escape(line, quote=False) for line in lines] + tspan = f'<tspan x="{_format_number(x)}" dy="{_format_number(dy)}">' + inner = escaped[0] + ''.join(f'{tspan}{line}</tspan>' for line in escaped[1:]) + return inner if y is None else ( + f'<text x="{_format_number(x)}" y="{_format_number(y)}">{inner}</text>' + ) + + +def text_box( + *, x: float, baseline_y: float, size: float, lines: int, dy: float, + width: float, anchor: str, +) -> dict[str, float]: + """Calculate the module bounds for a positioned text block.""" + left = x - width / 2 if anchor == 'middle' else x - width if anchor == 'end' else x + top = baseline_y - 0.85 * size + bottom = baseline_y + (lines - 1) * dy + 0.35 * size + return dict(x=left, y=top, width=width, height=bottom - top, top=top, bottom=bottom) + + +def _add_style_arguments(parser: argparse.ArgumentParser) -> None: + parser.add_argument('--size', type=_positive_float, required=True) + parser.add_argument('--family', default='Calibri') + parser.add_argument('--weight', choices=_WEIGHTS, default='normal') + parser.add_argument('--letter-spacing', type=_bounded_float, default=0.0) + parser.add_argument( + '--no-headroom', + action='store_true', + help='Use the raw estimator instead of DrawingML wrapping headroom.', + ) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description='Measure and wrap SVG authoring text.') + subparsers = parser.add_subparsers(dest='command', required=True) + + measure = subparsers.add_parser('measure', help='Measure single-line text.') + measure.add_argument('text', metavar='TEXT', nargs='*') + measure.add_argument('--stdin', action='store_true') + + wrap = subparsers.add_parser('wrap', help='Wrap one paragraph.') + wrap.add_argument('text', metavar='TEXT', nargs='?') + wrap.add_argument('--stdin', action='store_true') + wrap.add_argument('--max-width', type=_positive_float, required=True) + wrap.add_argument('--x', type=_bounded_float, required=True) + wrap.add_argument('--dy', type=_positive_float, required=True) + wrap.add_argument('--y', type=_bounded_float) + + box = subparsers.add_parser('box', help='Calculate text-block bounds.') + box.add_argument('text', metavar='TEXT', nargs='*') + box.add_argument('--x', type=_bounded_float, required=True) + box.add_argument('--y', type=_bounded_float, required=True) + box.add_argument('--lines', type=_positive_int, required=True) + box.add_argument('--dy', type=_positive_float) + box.add_argument('--width', type=_nonnegative_float) + box.add_argument('--anchor', choices=('start', 'middle', 'end'), default='start') + for command in (measure, wrap, box): + command.add_argument('--json', action='store_true') + _add_style_arguments(command) + return parser + + +def main(argv: list[str] | None = None) -> int: + configure_utf8_stdio() + parser = build_parser() + args = parser.parse_args(argv) + style = dict( + size=args.size, + family=args.family, + weight=args.weight, + letter_spacing=args.letter_spacing, + include_headroom=not args.no_headroom, + ) + + if args.command == 'measure': + if args.stdin and args.text: + parser.error('measure accepts positional TEXT or --stdin, not both') + if not args.stdin and not args.text: + parser.error('measure requires positional TEXT or --stdin') + texts = sys.stdin.read().splitlines() if args.stdin else args.text + results = [{'text': text, 'width': measure_text(text, **style)} for text in texts] + if args.json: + print(json.dumps(results, ensure_ascii=False)) + else: + sys.stdout.write(''.join(f'{item["width"]:.1f}\t{item["text"]}\n' for item in results)) + return 0 + + if args.command == 'wrap': + if args.stdin and args.text is not None: + parser.error('wrap accepts positional TEXT or --stdin, not both') + if not args.stdin and args.text is None: + parser.error('wrap requires positional TEXT or --stdin') + text = sys.stdin.read().rstrip('\r\n') if args.stdin else args.text + lines, widths, oversized = wrap_text(text, max_width=args.max_width, **style) + for token, width in oversized: + warning = f'Warning: token exceeds max width ({width:.1f} > {args.max_width:.1f}): {token}' + print(warning, file=sys.stderr) + if args.json: + height = (len(lines) - 1) * args.dy + 1.2 * args.size + payload = dict(lines=lines, widths=widths, max_width=args.max_width, height=height) + print(json.dumps(payload, ensure_ascii=False)) + else: + print(_render_wrapped_svg(lines, x=args.x, dy=args.dy, y=args.y)) + return 0 + + if args.lines > 1 and args.dy is None: + parser.error('box requires --dy when --lines is greater than 1') + if args.width is None and len(args.text) != args.lines: + parser.error('box without --width requires one positional TEXT per line') + if args.width is not None and args.text: + parser.error('box accepts positional TEXT only when --width is omitted') + width = args.width + if width is None: + width = max(measure_text(text, **style) for text in args.text) + bounds = text_box( + x=args.x, baseline_y=args.y, size=args.size, lines=args.lines, dy=args.dy or 0.0, + width=width, anchor=args.anchor, + ) + rounded = {key: round(value, 2) for key, value in bounds.items()} + if args.json: + print(json.dumps(rounded, ensure_ascii=False)) + else: + values = ' '.join(_format_number(bounds[key]) for key in ('x', 'y', 'width', 'height')) + top, bottom = _format_number(bounds['top']), _format_number(bounds['bottom']) + print(f'data-pptx-bounds="{values}"\ttop={top}\tbottom={bottom}') + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/update_spec.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/update_spec.py index ac73c903..5dc5b147 100755 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/update_spec.py +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/update_spec.py @@ -34,6 +34,10 @@ from pathlib import Path from console_encoding import configure_utf8_stdio from project_management.project_specs import parse_spec_lock as parse_lock +from stamp_native_fallbacks import ( + NativeFallbackStampError, + prepare_native_fallback_baselines, +) configure_utf8_stdio() @@ -127,6 +131,21 @@ def _plan_font_family_updates( return planned +def _plan_native_fallback_updates( + planned: list[tuple[Path, str, int]], +) -> tuple[list[tuple[Path, str, int]], int]: + """Re-stamp changed SVG text in memory before the joint publish.""" + stamped: list[tuple[Path, str, int]] = [] + restamped_markers = 0 + for path, text, replacement_count in planned: + payload, _svg_first, _json_first, restamped = ( + prepare_native_fallback_baselines(text.encode("utf-8"), path) + ) + stamped.append((path, payload.decode("utf-8"), replacement_count)) + restamped_markers += restamped + return stamped, restamped_markers + + def _publish_text_updates(updates: list[tuple[Path, str]]) -> None: """Publish existing text files as one rollback unit.""" if not updates: @@ -315,23 +334,7 @@ def main() -> int: print(f"error: {exc}", file=sys.stderr) return 2 planned_svg = _plan_color_updates(svg_dir, old_value, new_value) - changed = [ - (path, count) - for path, _text, count in planned_svg - ] lock_changes = [(key, old_value, new_value)] - if not args.dry_run: - try: - _publish_text_updates([ - *( - (path, new_text) - for path, new_text, _count in planned_svg - ), - (lock, planned_lock), - ]) - except (OSError, RuntimeError, ValueError) as exc: - print(f"error: update was not published: {exc}", file=sys.stderr) - return 2 elif section == "typography" and key == "font_family": family_keys = [ name @@ -353,25 +356,9 @@ def main() -> int: {name: new_value for name in family_keys}, ) planned_svg = _plan_font_family_updates(svg_dir, new_value) - changed = [ - (path, count) - for path, _text, count in planned_svg - ] except (KeyError, ValueError) as e: print(f"error: {e}", file=sys.stderr) return 2 - if not args.dry_run: - try: - _publish_text_updates([ - *( - (path, new_text) - for path, new_text, _count in planned_svg - ), - (lock, planned_lock), - ]) - except (OSError, RuntimeError, ValueError) as exc: - print(f"error: update was not published: {exc}", file=sys.stderr) - return 2 else: print( f"error: {section}.{key} is not supported by update_spec.py.\n" @@ -381,6 +368,29 @@ def main() -> int: ) return 2 + try: + planned_svg, restamped_markers = _plan_native_fallback_updates(planned_svg) + except (NativeFallbackStampError, UnicodeError) as exc: + print(f"error: native fallback baselines could not be prepared: {exc}", file=sys.stderr) + return 2 + + changed = [ + (path, count) + for path, _text, count in planned_svg + ] + if not args.dry_run: + try: + _publish_text_updates([ + *( + (path, new_text) + for path, new_text, _count in planned_svg + ), + (lock, planned_lock), + ]) + except (OSError, RuntimeError, ValueError) as exc: + print(f"error: update was not published: {exc}", file=sys.stderr) + return 2 + if args.dry_run: for lock_key, previous, replacement in lock_changes: print( @@ -388,12 +398,17 @@ def main() -> int: f"{section}.{lock_key} {previous} → {replacement}" ) print(f"[dry-run] svg_output/: {len(changed)} file(s) would be updated") + print( + f"[dry-run] native fallbacks: {restamped_markers} marker(s) " + "would be re-stamped" + ) else: for lock_key, previous, replacement in lock_changes: print( f"spec_lock.md: {section}.{lock_key} {previous} → {replacement}" ) print(f"svg_output/: {len(changed)} file(s) updated") + print(f"native fallbacks: {restamped_markers} marker(s) re-stamped") for p, n in changed: suffix = "replacement" if n == 1 else "replacements" print(f" - {p.name} ({n} {suffix})") diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/VISUALIZATION_TEMPLATE_AUTHORING.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/VISUALIZATION_TEMPLATE_AUTHORING.md index 7658f58c..229e36ab 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/VISUALIZATION_TEMPLATE_AUTHORING.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/VISUALIZATION_TEMPLATE_AUTHORING.md @@ -170,11 +170,11 @@ family 与信息模型:Chart 的数值映射、Table 的行 × 列事实网格 | 做法 | 要求 | |---|---| -| 字体继承 | 公共 `font-family` 放在根 `<svg>` 或清楚的父 `<g>` | -| 属性继承 | 同组重复的 `fill`、`stroke`、字号或锚点可提升到父组 | +| 字体继承 | 公共 `font-family` 放在根 `<svg>`;局部差异放在清楚的父 `<g>` | +| 属性继承 | 同组重复的 `fill`、`stroke`、字号或锚点提升到父组;根节点不放 paint | | 注释 | 保留结构、语义和机器标记;删除色名、营销解释和重复说明 | | 文本 | 普通单行直接写在 `<text>`;只有多 run/多行需要 `<tspan>` | -| 坐标 | 页面坐标使用必要精度;按上游合同运行 `compact_svg_coordinates.py` | +| 坐标 | 页面坐标从写入时就使用必要精度;质量检查只读验证,不在之后改写 | | ID | 使用 `chart-area`、`series-1`、`card-1` 等结构名称,避免示例业务名 | ### 4.2 禁止的压缩 @@ -450,15 +450,14 @@ Table;日期或持续时间决定 `x`/`width` 的排期是 `chart/gantt_chart` ```bash # 单文件 SVG 合同 python3 skills/ppt-master/scripts/svg_quality_checker.py \ - skills/ppt-master/templates/<family-directory>/<key>.svg + skills/ppt-master/templates/<family-directory>/<key>.svg \ + --canonical-authoring # Canonical family/key python3 skills/ppt-master/scripts/visualization_recall.py validate \ <family>/<key> -# 可安全压缩的页面坐标(默认 dry-run) -python3 skills/ppt-master/scripts/compact_svg_coordinates.py \ - skills/ppt-master/templates/<family-directory>/<key>.svg +# 作者态必须已紧凑;Checker 只读验证,不在检查后重写 ``` **验证**:修改后至少完成 XML 解析、独立 SVG 渲染、Checker、默认 Shape-first 导出,以及 marker 模板的 native Chart/Table 导出。 diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/decks/README.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/decks/README.md index bd96498f..44069bf3 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/decks/README.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/decks/README.md @@ -43,9 +43,9 @@ exact roots are `library`; other exact roots remain stage, which owns path normalization, compatibility checks, installation, and installation after Stage 1 and before Stage 2. Template-aware reading begins in final Stage 2 from the installed project-local copy. -Quick applies a supplied exact Deck root directly and otherwise uses free -design. It uses installed prototypes as flat authoring inputs; reusable -native Master/Layout compilation remains a default lock-backed capability. +Quick applies a supplied exact Deck root directly and otherwise uses flat free +design. It authors the installed Master/Layout/slot contract as lockless +structured Slides unless the user explicitly requests visual-only flat use. This file owns the Deck schema and application-context boundary. Chat discovery reads the same index and returns exact roots; a bare ID never resolves implicitly. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/design_spec_reference.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/design_spec_reference.md index 27a46922..a78aac52 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/design_spec_reference.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/design_spec_reference.md @@ -8,6 +8,8 @@ Strategist reads the complete final confirmation once, writes this artifact from After final confirmation, compose the entire document in active context from the retained final state, source analysis, and project context. Then create `<project_path>/design_spec.md` once, from the first line through §X. +**Depth follows the confirmed `design_spec_depth`** — `brief` (default) or `complete`; both keep every required heading and every machine-read field. `brief` serves a continuous run whose author also draws the pages — §I records production mechanics without restating Stage-1 prose, §VI may leave the scenario column empty, §IX `Content` is a short block list — one bullet per block, each in the phrasing that fits it (a real sentence for prose, `·`-joined parallel fragments, `/`-joined labels) — never full page copy, and `Layout` is one optional line. `complete` writes full wording and layout prose; split mode, `refine_spec: true`, and preservation profiles force it. + **Mandatory — new-project write**: The first non-empty line is exactly `<!-- ppt-master-schema: design-spec/v1 -->`, followed by `# <Project Name> - Design Spec`. Write every required section with final values and the complete page roster; include conditional §VII only when a real catalog reference is selected. Do not create a placeholder-bearing project file, copy example rows, or patch a scaffold field by field. `project_manager.py scaffold-spec` remains an optional manual convenience and overwrite-safe troubleshooting tool. It is not part of normal Generate authoring. Resume and refine paths edit an existing completed Design Spec rather than replacing it with a scaffold. @@ -88,6 +90,8 @@ Use these exact subsections and field shapes: | Accent | <HEX> | <semantic use> | | Secondary accent | <HEX> | <semantic use> | | Body text | <HEX> | <semantic use> | +| Secondary text | <HEX> | <captions, annotations, footnotes> | +| Divider | <HEX> | <rules, borders, hairlines> | ## IV. Typography System @@ -118,7 +122,8 @@ Use these exact subsections and field shapes: - **Hierarchy direction**: <how attention should move across a typical page> - **Composition tendency**: <non-binding macro direction; no coordinates or authoring method> - **Cross-page continuity**: <what may recur or vary across the roster> -- **Spacing posture**: <dense, open, or variable behavior by page rhythm; no fixed gaps> +- **Spacing posture**: <dense, open, or variable by page rhythm> +- **Spacing anchors**: <five deck-wide px values — page margin, block gap, column gutter, corner radius, body leading — kept stable across pages like the color and type anchors> ## VI. Icon Usage Specification @@ -129,7 +134,7 @@ Use these exact subsections and field shapes: | --- | --- | ``` -Preserve Title/Body characters and resolved stacks; omit blank Typography upgrade and never place it in a stack. For each justified recurring family override, add the role to Font Plan plus `- **<Role> stack**: <complete ordered stack>`. Possible roles are `Annotation`, `Footer`, `Footnote`, `Data`, `Emphasis`, `Quote`, and `Code`; add only recurring, intentional differences. Add non-locked `Role rationale` only for an extra family. Do not collapse distinct Title/Body stacks or discard a declared optional role. Each Font Size Hierarchy value is a role anchor: Executor may vary one occurrence `±2px`; a short non-structural Hero/Display size may stay unlisted only while the same value is planned at most twice, and its third occurrence needs a named row. Add every recurring palette role and typography-size anchor established by the plan; do not enumerate one-off paint or font-family garnish. For confirmed custom directions, add the applicable `Mode References`, `Mode Behavior`, `Visual Style References`, and `Visual Style Behavior` lines under Theme Style. Include `Stroke Width` under §VI only for a stroke library. `simple-icons` may accompany the one primary bundled library and is recorded only when actual content requires real brand marks; it is never a separate confirmation choice. The icon table records the curated synced SVG pool and broad semantic scenarios, not exact page placement or mandatory use. User-provided, template-carried, imported, custom, and other prepared SVGs under the project `icons/` directory remain usable without being forced into that bundled selection. Leave the §VI table empty when no bundled or brand SVG icons are prepared. Illustrated icons are AI image resources: their production sheet and placed slice rows belong in §VIII, and only placed slices project to `spec_lock.md images`. +Preserve Title/Body characters and resolved stacks; omit blank Typography upgrade and never place it in a stack. For each justified recurring family override, add the role to Font Plan plus `- **<Role> stack**: <complete ordered stack>`. Possible roles are `Annotation`, `Footer`, `Footnote`, `Data`, `Emphasis`, `Quote`, and `Code`; add only recurring, intentional differences. Add non-locked `Role rationale` only for an extra family. Do not collapse distinct Title/Body stacks or discard a declared optional role. Each Font Size Hierarchy value is a role anchor: Executor may vary one occurrence `±2px`; a short non-structural Hero/Display size may stay unlisted only while the same value is planned at most twice, and its third occurrence needs a named row. Add every recurring palette role and typography-size anchor established by the plan; do not enumerate one-off paint or font-family garnish. For confirmed custom directions, add the applicable `Mode References`, `Mode Behavior`, `Visual Style References`, and `Visual Style Behavior` lines under Theme Style. Include `Stroke Width` under §VI only for a stroke library. `simple-icons` may accompany the one primary bundled library and is recorded only when actual content requires real brand marks; it is never a separate confirmation choice. The icon table records the synced SVG pool and, at `complete` depth, broad semantic scenarios — never page placement. User-provided, template-carried, imported, custom, and other prepared SVGs under the project `icons/` directory remain usable without being forced into that bundled selection. Leave the §VI table empty when no bundled or brand SVG icons are prepared. Illustrated icons are AI image resources: their production sheet and placed slice rows belong in §VIII, and only placed slices project to `spec_lock.md images`. When §VIII contains any `Acquire Via: ai` row, add this subsection under §III and preserve the complete confirmed AI direction: @@ -180,7 +185,7 @@ Put native paint/overlay intent in §IX `Layout` plus `Images` for imagery—not ### 2.4 Complete page roster and notes -Write one ordered Slide block per page. Slide count and order must equal §I `Page Count`; `Content` is a complete page brief, not a skeleton. +Write one ordered Slide block per page. Slide count and order must equal §I `Page Count`; `Content` is a complete page brief at `complete` depth and a short block list at `brief` depth — neither is a skeleton. ```markdown ## IX. Content Outline @@ -190,10 +195,10 @@ Write one ordered Slide block per page. Slide count and order must equal §I `Pa #### Slide 01 - <page name> - **Audience move**: <audience state before → after> -- **Layout**: <non-binding macro composition, hierarchy, and visual focus; include the chosen prototype when template-active> +- **Layout**: <non-binding macro composition, hierarchy, and visual focus; chosen prototype when template-active; optional at brief depth> - **Title**: <preferred page title> - **Core message**: <one governing assertion> -- **Content**: <complete intended on-slide content and hierarchy> +- **Content**: <complete content at complete depth; short block list at brief depth> - **Mathematical content**: <exact expression as a delimiter-free LaTeX body; omit when none> ## X. Speaker Notes Requirements @@ -203,7 +208,7 @@ Write one ordered Slide block per page. Slide count and order must equal §I `Pa - **Content**: <notes content and source-handling policy> - **Total duration**: <resolved duration> - **Notes style**: <formal, conversational, interactive, or resolved equivalent> -- **Presentation purpose**: <inform, persuade, inspire, instruct, report, or resolved combination> +- **Presentation purpose**: <the confirmed communication intent from §I> ``` When Speaker Notes is disabled, keep §X with only diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/icons/README.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/icons/README.md index d7bb5b71..afc0cd8e 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/icons/README.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/icons/README.md @@ -24,7 +24,7 @@ This directory is the **global library**. The active resource owner copies chose python3 skills/ppt-master/scripts/icon_sync.py <project_path> tabler-outline/home tabler-outline/bulb simple-icons/github ``` -Missing names and a single selection batch that mixes the four stylistic libraries exit non-zero; `simple-icons` may coexist for real brand marks. Once files are under `<project>/icons/`, they form the prepared project asset pool and may be combined freely with user-provided, custom, or imported icons. `finalize_svg.py embed-icons` embeds **project-first**; its per-icon global fallback exists for legacy compatibility, not new asset discovery. +Missing names and a single selection batch that mixes the four stylistic libraries exit non-zero; `simple-icons` may coexist for real brand marks. Once files are under `<project>/icons/`, they form the prepared project asset pool and may be combined freely with user-provided, custom, or imported icons. `finalize_svg.py --only embed-icons`, preview, validation, and native export resolve only this project-local pool. **Custom icons**: drop your own `.svg` into `<project>/icons/<lib>/` (any `<lib>`, e.g. `custom/`) and reference it as `data-icon="<lib>/<name>"` — it embeds like any library icon. @@ -63,6 +63,11 @@ Use placeholder syntax **during SVG generation**: `data-icon` is case-sensitive because it resolves a real filename. Bundled library directories and basenames are canonical lowercase: use `tabler-outline/award`, not `tabler-outline/Award`. Custom icons retain the exact case of their files; the resolver intentionally does not lowercase identifiers. +Every placeholder resolves only against `<project_path>/icons/`. A complete +`library/name` identifier is mandatory; bare names, abbreviated namespaces, +paths into `templates/icons/`, and unsynced bundled-library files fail instead +of falling back to another root. + `finalize_svg.py` auto-embeds all placeholders during post-processing. To run manually: ```bash diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/README.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/README.md index 4bb5ec7d..b6fb85b7 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/README.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/README.md @@ -58,9 +58,9 @@ Choosing and confirming an entry runs the conditional stage, which owns path normalization, compatibility checks, installation, and installation after Stage 1 and before Stage 2. Template-aware reading begins in final Stage 2 from the installed project-local copy. -Quick applies a supplied exact Layout root directly and otherwise uses free -design. It uses installed prototypes as flat authoring inputs; reusable -native Master/Layout compilation remains a default lock-backed capability. +Quick applies a supplied exact Layout root directly and otherwise uses flat free +design. It authors the installed Master/Layout/slot contract as lockless +structured Slides unless the user explicitly requests visual-only flat use. This file owns the Layout schema and its identity/application boundary. Chat discovery reads the same index and returns exact roots; a bare ID never resolves implicitly. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core/templates/19_chart_insight.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core/templates/19_chart_insight.svg index 1e8d1841..144421f5 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core/templates/19_chart_insight.svg +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core/templates/19_chart_insight.svg @@ -19,7 +19,7 @@ <g id="chart-insight-chart-slot" data-pptx-placeholder="chart" data-pptx-idx="1" data-pptx-bounds="88 176 712 408"> <g id="chart-insight-chart-carrier" data-pptx-carrier="true" - data-pptx-replace-with="chart"> + data-pptx-replace-with="chart" data-pptx-fallback-sha256="e39e729fbcd88647bc948bf86f6974592347d171897ee0568376b13330fdb95b"> <metadata type="application/json">{"name":"chart_insight","x":88,"y":176,"width":712,"height":408,"type":"column","categories":["Q1","Q2","Q3","Q4"],"series":[{"name":"Actual","values":[42,55,63,78]},{"name":"Plan","values":[48,58,68,74]}],"show_legend":true,"legend_position":"bottom","style":{"colors":["#64748B","#CBD5E1"],"chart_area_fill":"#F4F6F8","plot_area_fill":"#FFFFFF","text_color":"#475569","axis_color":"#CBD5E1","grid_color":"#E2E8F0","font_family":"Arial"}}</metadata> <rect x="88" y="176" width="712" height="408" rx="8" fill="#FFFFFF"/> <rect x="586" y="202" width="12" height="12" rx="2" fill="#64748B"/> diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core/templates/20_table_summary.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core/templates/20_table_summary.svg index eb719943..056153e6 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core/templates/20_table_summary.svg +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core/templates/20_table_summary.svg @@ -19,8 +19,8 @@ <g id="table-summary-table-slot" data-pptx-placeholder="table" data-pptx-idx="1" data-pptx-bounds="88 176 776 408"> <g id="table-summary-table-carrier" data-pptx-carrier="true" - data-pptx-replace-with="table"> - <metadata type="application/json">{"name":"table_summary","x":88,"y":176,"width":776,"height":408,"strict_grid":true,"column_widths":[3,2,2],"row_heights":[1,1,1,1,1],"style":{"font_family":"Arial","font_size":17,"header_font_size":16,"header_fill":"#E2E8F0","header_text":"#334155","body_fill":"#FFFFFF","body_text":"#475569","band_fill":"#F8FAFC","border_color":"#D6DCE3","border_width":1,"padding":{"left":12,"right":12,"top":6,"bottom":6},"valign":"middle"},"columns":[{"text":"Metric","bold":true},{"text":"Current","align":"r","bold":true},{"text":"Target","align":"r","bold":true}],"rows":[[{"text":"Adoption"},{"text":"68%","align":"r","bold":true},{"text":"80%","align":"r"}],[{"text":"Cycle time"},{"text":"12 d","align":"r","bold":true},{"text":"8 d","align":"r"}],[{"text":"Quality"},{"text":"94%","align":"r","bold":true},{"text":"97%","align":"r"}],[{"text":"Cost index"},{"text":"1.08","align":"r","bold":true},{"text":"1.00","align":"r"}]]}</metadata> + data-pptx-replace-with="table" data-pptx-fallback-sha256="6537e9228a8e8691fd1870f794329de56b7c51a8c6c40fe4e37d2d8963659f61"> + <metadata type="application/json">{"schema":"ppt-master.semantic-table.v2","name":"table_summary","x":88,"y":176,"width":776,"height":408,"strict_grid":true,"column_widths":[3,2,2],"row_heights":[1,1,1,1,1],"style":{"font_family":"Arial","font_size":17,"header_font_size":16,"header_fill":"#E2E8F0","header_text":"#334155","body_fill":"#FFFFFF","body_text":"#475569","band_fill":"#F8FAFC","border_color":"#D6DCE3","border_width":1,"padding":{"left":12,"right":12,"top":6,"bottom":6},"valign":"middle"},"columns":[{"text":"Metric","bold":true},{"text":"Current","align":"r","bold":true},{"text":"Target","align":"r","bold":true}],"rows":[[{"text":"Adoption"},{"text":"68%","align":"r","bold":true},{"text":"80%","align":"r"}],[{"text":"Cycle time"},{"text":"12 d","align":"r","bold":true},{"text":"8 d","align":"r"}],[{"text":"Quality"},{"text":"94%","align":"r","bold":true},{"text":"97%","align":"r"}],[{"text":"Cost index"},{"text":"1.08","align":"r","bold":true},{"text":"1.00","align":"r"}]]}</metadata> <rect x="88" y="176" width="776" height="408" rx="8" fill="#FFFFFF" stroke="#D6DCE3" stroke-width="1"/> <rect x="88" y="176" width="776" height="72" rx="8" fill="#E2E8F0"/> <rect x="88" y="248" width="776" height="84" fill="#FFFFFF"/> diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core_43/templates/15_chart_insight.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core_43/templates/15_chart_insight.svg index 7c0de134..87cdff44 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core_43/templates/15_chart_insight.svg +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core_43/templates/15_chart_insight.svg @@ -19,7 +19,7 @@ <g id="chart-insight-chart-slot" data-pptx-placeholder="chart" data-pptx-idx="1" data-pptx-bounds="76 156 576 536"> <g id="chart-insight-chart-carrier" data-pptx-carrier="true" - data-pptx-replace-with="chart"> + data-pptx-replace-with="chart" data-pptx-fallback-sha256="0d80a55b47e50bcd9ffe0f604bb9a8e033d015e503c6f836a1701e2d67b8016d"> <metadata type="application/json">{"name":"chart_insight","x":76,"y":156,"width":576,"height":536,"type":"column","categories":["Q1","Q2","Q3","Q4"],"series":[{"name":"Actual","values":[42,55,63,78]},{"name":"Plan","values":[48,58,68,74]}],"show_legend":true,"legend_position":"bottom","style":{"colors":["#64748B","#CBD5E1"],"chart_area_fill":"#F4F6F8","plot_area_fill":"#FFFFFF","text_color":"#475569","axis_color":"#CBD5E1","grid_color":"#E2E8F0","font_family":"Arial"}}</metadata> <rect x="76" y="156" width="576" height="536" rx="8" fill="#FFFFFF"/> <rect x="440" y="192" width="12" height="12" rx="2" fill="#64748B"/> diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core_43/templates/16_table_summary.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core_43/templates/16_table_summary.svg index b1f11caf..ae32e401 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core_43/templates/16_table_summary.svg +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/presentation_core_43/templates/16_table_summary.svg @@ -19,8 +19,8 @@ <g id="table-summary-table-slot" data-pptx-placeholder="table" data-pptx-idx="1" data-pptx-bounds="76 156 616 536"> <g id="table-summary-table-carrier" data-pptx-carrier="true" - data-pptx-replace-with="table"> - <metadata type="application/json">{"name":"table_summary","x":76,"y":156,"width":616,"height":536,"strict_grid":true,"column_widths":[3,2,2],"row_heights":[1,1,1,1,1],"style":{"font_family":"Arial","font_size":17,"header_font_size":16,"header_fill":"#E2E8F0","header_text":"#334155","body_fill":"#FFFFFF","body_text":"#475569","band_fill":"#F8FAFC","border_color":"#D6DCE3","border_width":1,"padding":{"left":12,"right":12,"top":6,"bottom":6},"valign":"middle"},"columns":[{"text":"Metric","bold":true},{"text":"Current","align":"r","bold":true},{"text":"Target","align":"r","bold":true}],"rows":[[{"text":"Adoption"},{"text":"68%","align":"r","bold":true},{"text":"80%","align":"r"}],[{"text":"Cycle time"},{"text":"12 d","align":"r","bold":true},{"text":"8 d","align":"r"}],[{"text":"Quality"},{"text":"94%","align":"r","bold":true},{"text":"97%","align":"r"}],[{"text":"Cost index"},{"text":"1.08","align":"r","bold":true},{"text":"1.00","align":"r"}]]}</metadata> + data-pptx-replace-with="table" data-pptx-fallback-sha256="df057a9b89e36115d79de59428589712fe4178be319de783016927bf26a420ad"> + <metadata type="application/json">{"schema":"ppt-master.semantic-table.v2","name":"table_summary","x":76,"y":156,"width":616,"height":536,"strict_grid":true,"column_widths":[3,2,2],"row_heights":[1,1,1,1,1],"style":{"font_family":"Arial","font_size":17,"header_font_size":16,"header_fill":"#E2E8F0","header_text":"#334155","body_fill":"#FFFFFF","body_text":"#475569","band_fill":"#F8FAFC","border_color":"#D6DCE3","border_width":1,"padding":{"left":12,"right":12,"top":6,"bottom":6},"valign":"middle"},"columns":[{"text":"Metric","bold":true},{"text":"Current","align":"r","bold":true},{"text":"Target","align":"r","bold":true}],"rows":[[{"text":"Adoption"},{"text":"68%","align":"r","bold":true},{"text":"80%","align":"r"}],[{"text":"Cycle time"},{"text":"12 d","align":"r","bold":true},{"text":"8 d","align":"r"}],[{"text":"Quality"},{"text":"94%","align":"r","bold":true},{"text":"97%","align":"r"}],[{"text":"Cost index"},{"text":"1.08","align":"r","bold":true},{"text":"1.00","align":"r"}]]}</metadata> <rect x="76" y="156" width="616" height="536" rx="8" fill="#FFFFFF" stroke="#D6DCE3" stroke-width="1"/> <rect x="76" y="156" width="616" height="72" rx="8" fill="#E2E8F0"/> <rect x="76" y="228" width="616" height="116" fill="#FFFFFF"/> diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/report_core/templates/08_chart_insight.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/report_core/templates/08_chart_insight.svg index 6dbcef15..94f6bec6 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/report_core/templates/08_chart_insight.svg +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/report_core/templates/08_chart_insight.svg @@ -21,7 +21,7 @@ <g id="chart-insight-chart-slot" data-pptx-placeholder="chart" data-pptx-idx="1" data-pptx-bounds="68 196 760 424"> <g id="chart-insight-chart-carrier" data-pptx-carrier="true" - data-pptx-replace-with="chart"> + data-pptx-replace-with="chart" data-pptx-fallback-sha256="0883c0890ad63b3ec8b2a31e5a5f4177d14bdb6e496ec226460a88433b20c938"> <metadata type="application/json">{"name":"chart_insight","x":68,"y":196,"width":760,"height":424,"type":"column","categories":["Q1","Q2","Q3","Q4"],"series":[{"name":"Actual","values":[42,55,63,78]},{"name":"Plan","values":[48,58,68,74]}],"show_legend":true,"legend_position":"top","style":{"colors":["#64748B","#CBD5E1"],"chart_area_fill":"#F8FAFC","plot_area_fill":"#FFFFFF","text_color":"#475569","axis_color":"#CBD5E1","grid_color":"#E2E8F0","font_family":"Arial"}}</metadata> <rect x="68" y="196" width="760" height="424" rx="6" fill="#FFFFFF"/> <rect x="600" y="224" width="10" height="10" rx="2" fill="#64748B"/> diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/report_core/templates/09_table_summary.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/report_core/templates/09_table_summary.svg index b0bb9d12..4acaf5f2 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/report_core/templates/09_table_summary.svg +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/layouts/report_core/templates/09_table_summary.svg @@ -21,8 +21,8 @@ <g id="table-summary-table-slot" data-pptx-placeholder="table" data-pptx-idx="1" data-pptx-bounds="68 196 816 424"> <g id="table-summary-table-carrier" data-pptx-carrier="true" - data-pptx-replace-with="table"> - <metadata type="application/json">{"name":"table_summary","x":68,"y":196,"width":816,"height":424,"strict_grid":true,"column_widths":[3,2,2],"row_heights":[1,1,1,1,1],"style":{"font_family":"Arial","font_size":15,"header_font_size":14,"header_fill":"#F1F5F9","header_text":"#334155","body_fill":"#FFFFFF","body_text":"#475569","band_fill":"#F8FAFC","border_color":"#E2E8F0","border_width":1,"padding":{"left":10,"right":10,"top":5,"bottom":5},"valign":"middle"},"columns":[{"text":"Metric","bold":true},{"text":"Current","align":"r","bold":true},{"text":"Target","align":"r","bold":true}],"rows":[[{"text":"Adoption"},{"text":"68%","align":"r","bold":true},{"text":"80%","align":"r"}],[{"text":"Cycle time"},{"text":"12 d","align":"r","bold":true},{"text":"8 d","align":"r"}],[{"text":"Quality"},{"text":"94%","align":"r","bold":true},{"text":"97%","align":"r"}],[{"text":"Cost index"},{"text":"1.08","align":"r","bold":true},{"text":"1.00","align":"r"}]]}</metadata> + data-pptx-replace-with="table" data-pptx-fallback-sha256="8855518e6bc3c692919ed68d368f493fa05fdecd16eabc5beece070b24353f30"> + <metadata type="application/json">{"schema":"ppt-master.semantic-table.v2","name":"table_summary","x":68,"y":196,"width":816,"height":424,"strict_grid":true,"column_widths":[3,2,2],"row_heights":[1,1,1,1,1],"style":{"font_family":"Arial","font_size":15,"header_font_size":14,"header_fill":"#F1F5F9","header_text":"#334155","body_fill":"#FFFFFF","body_text":"#475569","band_fill":"#F8FAFC","border_color":"#E2E8F0","border_width":1,"padding":{"left":10,"right":10,"top":5,"bottom":5},"valign":"middle"},"columns":[{"text":"Metric","bold":true},{"text":"Current","align":"r","bold":true},{"text":"Target","align":"r","bold":true}],"rows":[[{"text":"Adoption"},{"text":"68%","align":"r","bold":true},{"text":"80%","align":"r"}],[{"text":"Cycle time"},{"text":"12 d","align":"r","bold":true},{"text":"8 d","align":"r"}],[{"text":"Quality"},{"text":"94%","align":"r","bold":true},{"text":"97%","align":"r"}],[{"text":"Cost index"},{"text":"1.08","align":"r","bold":true},{"text":"1.00","align":"r"}]]}</metadata> <rect x="68" y="196" width="816" height="424" rx="6" fill="#FFFFFF" stroke="#E2E8F0" stroke-width="1"/> <rect x="68" y="196" width="816" height="64" rx="6" fill="#F1F5F9"/> <rect x="68" y="260" width="816" height="90" fill="#FFFFFF"/> diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/schemas/spec_lock.schema.json b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/schemas/spec_lock.schema.json index 7108563d..c025f47c 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/schemas/spec_lock.schema.json +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/schemas/spec_lock.schema.json @@ -13,7 +13,6 @@ "typography", "icons", "page_rhythm", - "pptx_structure", "forbidden" ], "properties": { @@ -183,7 +182,7 @@ { "id": "pptx_structure", "pattern": "^pptx_structure$", - "required": true, + "required": false, "required_fields": ["mode"], "allowed_fields": ["mode", "template_reuse_scope", "template_adherence"], "field_enums": { diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/spec_lock_reference.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/spec_lock_reference.md index c957a80a..5df95d46 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/spec_lock_reference.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/spec_lock_reference.md @@ -22,7 +22,7 @@ After Generate Step 4 Gate 1, read the completed Design Spec and current page/re | `communication` | `primary_language`, `audience`, `objective`, `core_message` | New lock: canonical BCP-47; old lock may omit it. Reject `und` and Chinese without script/region. `objective` merges intent/outcome; `consumption_mode` is optional off PPT | | `mode` | `mode` | Preset or `custom` | | `visual_style` | `visual_style` | Preset or `custom` | -| `colors` | Stable semantic color roles | Core identity and recurring roles only; contextual SVG paints need no row; `image_rendering` appears only for AI images | +| `colors` | Stable semantic color roles | Core identity and recurring roles only, including the standard `secondary_text` and `divider` neutrals; contextual SVG paints need no row; `image_rendering` appears only for AI images | | `typography` | `font_family`, `body`, `title` | Core family/size anchors; new locks also write explicit `title_family` and `body_family`; size anchors are unitless px numbers | | `icons` | `library`, `inventory` | `library` is the Strategist's primary bundled style choice or `none`; content-driven `simple-icons/*` may be prepared alone or accompany it; `inventory` indexes the curated synced SVG pool rather than page usage or all usable project-local icons; `stroke_width` is conditional | | `page_rhythm` | One `P<NN>` row per page | Values: `anchor`, `dense`, `breathing` | @@ -109,7 +109,7 @@ New locks always write `title_family` and `body_family`, even when their values - `icons.library` records the primary stylistic library selected from `chunk-filled`, `tabler-filled`, `tabler-outline`, or `phosphor-duotone`, or `none` when no generic bundled icons are selected. Content-driven `simple-icons/*` brand marks may appear alone or alongside it in `inventory` without becoming a stylistic library or a separate confirmation choice. The inventory indexes the curated synced SVG pool without assigning page usage; every SVG already under `<project_path>/icons/` remains valid prepared execution material. Illustrated-icon slices create no icon-lock field: their exact paths belong under `images`, and the unplaced parent sheet stays out of the lock. - `objective` grammar: one concise sentence preserving the deck goal and audience success condition. - `image_rendering` grammar: one catalog id, or `custom` with `image_rendering_behavior`. -- `images`: `- <key>: <path> | source=<via> | pattern=<layout> | crop=<adaptive|no-crop>`; e.g. `- p04: images/a.png | source=user | pattern=full-height image beside the evidence | crop=no-crop`. Use the canonical `images/<filename>` path; `source` and `crop` exactly project §VIII, while `pattern` preserves its non-empty normalized free-form suggestion and any optional hierarchical catalog ids. The pattern remains a recommendation for Executor recall that may be adopted, adapted, or declined, not a geometry or realization lock. Omit unplaced sheets. +- `images`: `- <key>: <path> | source=<via> | crop=<adaptive|no-crop>`; e.g. `- p04: images/a.png | source=user | crop=no-crop`. Use the canonical `images/<filename>` path; `source` and `crop` exactly project §VIII. The §VIII `Layout pattern` is not projected: Executor reads it from the §VIII row as a recommendation that may be adopted, adapted, or declined. A legacy `pattern=<layout>` segment is still accepted. Omit unplaced sheets. - Custom reference grammar: comma-separated exact catalog ids with no duplicates. Reference fields are valid only for `custom`; omit them for a genuinely novel direction. - `stroke_width` grammar: `1.5`, `2`, or `3`; present only for `tabler-outline`. - `page_rhythm` grammar: `P` + at least two digits (`P01`, `P100`) followed by `anchor|dense|breathing`. @@ -122,7 +122,7 @@ New locks always write `title_family` and `body_family`, even when their values - `pptx_masters` grammar: `<master_key>: <PowerPoint picker name>`. - `pptx_layouts` grammar: `<layout_key>: <master_key> | <PowerPoint layout name> | <prototype source>`. - `page_pptx_layouts` grammar: `P` + at least two digits followed by a declared Layout key. -- `page_layouts` grammar: `P` + at least two digits followed by a template SVG basename. +- `page_layouts` grammar: `P` + at least two digits followed by a complete Slide template SVG basename. Definition-only `layout_<layout_key>` files are obsolete and are also invalid as `pptx_layouts` sources; author a complete Slide prototype for each reusable Layout. Catalog-based custom example: diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/tables/hierarchical_table.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/tables/hierarchical_table.svg index ad174a5d..eefd5cb5 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/tables/hierarchical_table.svg +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/tables/hierarchical_table.svg @@ -14,6 +14,7 @@ data-pptx-replace-with="table"> <metadata type="application/json"> { + "schema": "ppt-master.semantic-table.v2", "name": "hierarchical-table", "x": 80, "y": 145, diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/tables/record_table.svg b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/tables/record_table.svg index fca2a939..2901136c 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/tables/record_table.svg +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/templates/tables/record_table.svg @@ -14,6 +14,7 @@ data-pptx-replace-with="table"> <metadata type="application/json"> { + "schema": "ppt-master.semantic-table.v2", "name": "record-table", "x": 80, "y": 150, diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template.md index 72e2a013..1aa2491c 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template.md @@ -16,7 +16,7 @@ Create one reusable template workspace under either the **global template librar **Hard rule — one workspace routing contract**: Output scope changes the workspace parent, Design Spec filename placement, and index registration—not the spec schema or asset routes. Both scopes use required `templates/`, optional `images/` / `icons/`, and optional on-demand `exports/`, with the same relative asset references and validation command. Create Template must not create an optional directory or placeholder file solely to retain an empty path. An initialized project may already contain empty `images/`, `icons/`, or `exports/` scaffolding; leave it untouched, do not count it as template output, and omit the path from completion unless this workflow wrote or adopted a real file there. Do not maintain a library-only self-contained-flat package branch or a project-only thin-bundle branch. -> **Boundary against template-fill and in-place structure edits**: Create Template does not fill content into a PPTX, add Master/Layout structure to an existing PPTX/SVG, or directly output the user's final generated deck. It authors a separate reusable workspace; an optional PPTX is review evidence only. To generate a deck, return the workspace root as an exact candidate to [`generate-pptx`](./generate-pptx.md) Step 3, confirm it with Stage 1, then author new SVG pages from the installed state. A project-scoped workspace selected for its own project is consumed in place after that confirmation. +> **Boundary against existing-deck and in-place structure edits**: Create Template does not fill content into a PPTX, add Master/Layout structure to an existing PPTX/SVG, or directly output the user's final generated deck. It authors a separate reusable workspace; an optional PPTX is review evidence only. To generate a deck, return the workspace root as an exact candidate to [`generate-pptx`](./generate-pptx.md) Step 3, confirm it with Stage 1, then author new SVG pages from the installed state. A project-scoped workspace selected for its own project is consumed in place after that confirmation. > **Boundary against page-image reconstruction**: screenshots and page visuals > in this route are evidence for reusable rules and prototypes. When the user @@ -38,8 +38,8 @@ Create Template is the fixed user-facing entry and common contract. It selects o Select Create Brand only for identity-only intent. Select Create Style when the portable value is a communication method, evidence discipline, and visual direction but there is no official identity, page geometry, or prototype roster to retain. Select Create Layout only when identity remains downstream-selectable and the reusable artifact does not prescribe communication objectives, audience outcomes, a required narrative sequence, or scenario-specific starting content. Select Create Deck when structure carries brand identity or reusable application semantics. A complete source PPTX alone does not determine the kind: classify only the stable rules worth reusing. Ask one discriminator question only when the user's requested reusable artifact is genuinely ambiguous; once selected, enter that child workflow and do not repeat route selection inside its confirmation gate. See [`templates/README.md`](../templates/README.md) for the shared kind and -workspace model. Downstream template application and installation remain owned by -[`generate-pptx.md`](./generate-pptx.md) Step 3. +workspace model. Generate Step 4 Stage 1 owns application/installation: +[`apply-template-workspace`](./stages/apply-template-workspace.md). ## Output scope — library (default) vs project @@ -59,7 +59,7 @@ Both scopes write this contract: ├── templates/ # library: design_spec.md; project: one qualified spec per kind plus the effective Layout-or-Deck SVG roster ├── images/ # optional; every bitmap; SVG href is ../images/<name> ├── icons/ -│ └── imported/ # optional; one canonical copy of imported vector assets +│ └── imported/ # optional; one canonical copy of imported decoration vectors └── exports/ # conditional; required package evidence for multi-Master templates ``` @@ -100,8 +100,8 @@ The rows are evidence channels, not mutually exclusive routes. Run every matchin | Type | What the user supplied | Tool / read path | Internal strategies supported by the evidence | |------|-------------------------|------------------|-----------------------------------------------| -| **A** `.pptx` reference | A `.pptx` file path | `pptx_template_import.py` → `manifest.json` + `native_structure.json` + `source_template.pptx` + layered SVGs + `assets/`; flat verification SVGs are opt-in | `standard` / `fidelity` / `mirror` | -| **B** Existing SVG assets | `projects/<x>/svg_output/`, a current template workspace root, a legacy flat template root, or a loose `.svg` folder | Normalize the source directory, create an editable authoring IR bundle with `svg_authoring_view.py`, then use its page SVGs; also read companion `design_spec.md` / `spec_lock.md` when present | `standard` / `fidelity`; `mirror` only when the source already carries a complete explicit Master/Layout/placeholder/native-object contract | +| **A** `.pptx` reference | A `.pptx` file path | `pptx_template_import.py` → `analysis/manifest.json` + `analysis/native_structure.json` + `sources/source.pptx` + layered SVGs + semantic resource directories; flat verification SVGs are opt-in | `standard` / `fidelity` / `mirror` | +| **B** Existing SVG assets | `projects/<x>/svg_output/`, a current template workspace root, or a loose `.svg` folder | Normalize the source directory, create an editable authoring IR bundle with `svg_authoring_view.py`, then use its page SVGs; also read companion `design_spec.md` / `spec_lock.md` when present | `standard` / `fidelity`; `mirror` only when the source already carries a complete explicit Master/Layout/placeholder/native-object contract | | **C** Image / visual references | PNG/JPG/WebP images, screenshots, moodboards, PDF page visuals, or a visual-reference folder | `ls` + `Read` each supplied visual or PDF (multimodal recognition) | `standard` only by itself | | **D** Text / document / website / asset references | Direct conversation text, pasted requirements, Markdown/TXT, DOCX/PDF/HTML/URL, brand/design manuals, or supplied logo/icon/font assets | Use direct text as-is; read plain text/Markdown; convert supported documents/URLs with `source_to_md.py` into a temporary analysis workspace; inventory explicit assets | `standard` only by itself | | **E** No reference material | A template request with no external source and no substantive brief yet | Skip analysis; collect every required value in Steps 2–3 | `standard` only | @@ -122,18 +122,28 @@ below are Create Layout/Create Deck concerns. | Preserve provenance | Keep facts, explicit user decisions, and AI suggestions distinct. Surface contradictions in Step 2 instead of resolving them silently. | | Protect mirror | Supplemental text, images, websites, or assets may explain the source but cannot alter a confirmed `mirror` graph or visuals. Use `standard` / `fidelity` when the user wants those inputs to change the resulting system. | -Type A is the canonical mirror path: `manifest.json`, `native_structure.json`, layered lossless `svg/`, and inheritance facts describe the native structure that still exists in the PPTX package. Optional `svg-flat/` files are complete-page verification views, never structure authority. In `standard` / `fidelity`, imported facts and visuals do not define output topology. +Type A is the canonical mirror path: analysis manifests/inheritance own surviving +native structure; `authoring-svg/` is new compact editable SVG projected from +that evidence. Lossless `svg/` is immutable validation/non-visible-payload +backing, never visible authoring. Optional flat files verify full pages only. In +`standard` / `fidelity`, imported facts/visuals do not define output topology. -**Type B source normalization**: when the supplied root exposes any `templates/` Design Spec, use `<input>/templates/` as the SVG/spec source and resolve its workspace assets from sibling `<input>/images/` and `<input>/icons/`. Otherwise, use the supplied directory itself as the legacy-flat/loose SVG source. Directory flatness is not a semantic-structure signal. +**Type B source normalization**: when the supplied root exposes any `templates/` Design Spec, use `<input>/templates/` as the SVG/spec source and resolve its workspace assets from sibling `<input>/images/` and `<input>/icons/`. Otherwise, treat the supplied directory only as a loose SVG evidence source. Directory flatness is not a semantic-structure signal and never makes it a reusable workspace. Type B is supported with caveats: -- **mirror on type B** — require a complete current explicit source contract. Preserve page count/order, literal visuals, root Master/Layout identities, slot metadata, supported native-object metadata, and source ownership in the **new** workspace. Page type for `<NNN>_<page_type>.svg` is read from the source filename when it follows the PPT Master naming convention (`01_cover.svg` → `cover`, `03a_content_two_col.svg` → `content`); fall back to `content` otherwise. A loose visual-only SVG folder has no native structure to preserve and cannot use mirror. +- **mirror on type B** — require a complete current explicit source contract. Preserve page count/order, similar presentation, each source Slide's declared Layout/Master chain, slot metadata, supported native-object metadata, and source ownership in the **new** workspace; SVG code and node identity need not match. Page type for `<NNN>_<page_type>.svg` is read from the source filename when it follows the PPT Master naming convention (`01_cover.svg` → `cover`, `03a_content_two_col.svg` → `content`); fall back to `content` otherwise. A loose visual-only SVG folder has no native structure to preserve and cannot use mirror. Unreferenced identities are not mirror output. - **fidelity on type B** — inspect the complete page roster as visual reference, then design a broader new roster and its own Master/Layout/slot system. Existing keys, families, and repeated source chrome are not output-topology inputs. - **legacy or unstructured type B** — old `baseline` / `preserve` / `layout_strategy: distill` / `data-pptx-layout-kind` / direct-atomic-placeholder inputs, and SVGs with no root Master identity, are visual/contextual reference for `standard` / `fidelity` only. Author a new current contract in the output workspace. Use the original PPTX Type A path when existing native Master/Layout facts must be mirrored; do not mutate the SVG source or claim topology recovery from incomplete metadata. - **selected free-design subset on type B** — ingest only the explicitly named pages as visual reference, then author a new current structured contract in the output workspace. Do not scan or copy the whole `svg_output/` directory or silently turn unselected pages into template variants. -**Internal creation-strategy boundary**: `standard`, `fidelity`, and `mirror` are implementation labels derived after understanding the request; they are not user-facing creation modes. `standard` and `fidelity` author a new SVG roster and a new Master/Layout/slot system from the requested result. `mirror` preserves native facts that actually exist in a PPTX package or complete current Type B contract. Mirror may mechanically normalize transport representation for the current compiler, including fixed-layer group expansion, but it never infers missing historical intent or modifies the input. Because mirror preserves supported visual and application facts, Create Layout may use it only when the source contract already satisfies the Layout boundary: brand-neutral and application-neutral. A source outside that boundary must be re-authored internally or retained as a Deck. The strategy creates a template workspace, not a downstream generated deck; future decks are authored anew and do not inherit the source page count/order requirement. +**Internal creation-strategy boundary**: These three labels are internal. +`standard` / `fidelity` review all source structure, then author a compact or +broader source-aligned roster with one Slide prototype per retained Layout. +`mirror` authors compact parsed SVG for every source Slide, preserving reachable +graph, meaning, ownership, and similar presentation—not code identity. Create +Layout mirror requires a brand/application-neutral contract; otherwise re-author +a Layout or retain a Deck. Future decks need not keep source page count/order. ### 1A. `.pptx` reference @@ -145,11 +155,12 @@ python3 skills/ppt-master/scripts/pptx_template_import.py "<reference_template.p This produces, in one workspace: -- `manifest.json` — single source of truth: slide size, theme colors, fonts, per-master theme summaries, asset inventory, placeholder metadata, SVG file paths, per-slide / per-layout / per-master metadata (including source-owned inherited-shape visibility), page-type candidates -- `native_structure.json` — analysis contract: stable master/layout keys, layout picker names, placeholder type/index/geometry, inherited-shape visibility, source hash, and source-graph quality facts -- `source_template.pptx` — byte-preserved analysis copy for visual/package cross-checking; it is not copied into the final template package -- `assets/` — extracted reusable image assets; `manifest.json` owns the asset-name mapping and SVG `href` values reuse that mapping -- `conversion-report.json` — source-recovery and fidelity diagnostics; retain it for audit because these warnings are not duplicated in the structural manifests +- `analysis/manifest.json` — source facts: slide size, theme colors, fonts, per-master theme summaries, resource inventory, placeholder metadata, SVG file paths, per-slide / per-layout / per-master metadata (including source-owned inherited-shape visibility), and page-type candidates +- `analysis/native_structure.json` — stable Master/Layout keys, picker names, placeholder type/index/geometry, inherited-shape visibility, source hash, and source-graph quality facts +- `sources/source.pptx` — byte-preserved backing package for visual/package cross-checking and source identity validation; it is not copied into the final template package or used to replace authored visible SVG +- `images/` — PowerPoint image media, including raster images and SVG/EMF/WMF; `analysis/manifest.json` owns the asset-name mapping and SVG `href` values reuse it +- `sounds/`, `audio/`, `video/`, and `native-payloads/` — conditional semantic resource directories; only populated directories are created +- `validation/conversion-report.json` — source-recovery and fidelity diagnostics; retain it for audit because these warnings are not duplicated in the structural manifests - `svg/` — **primary view** (layered template view): - `svg/master_*.svg` — every slide master in the deck rendered once, including masters that no sample slide currently uses (template packages routinely ship more masters than the visible samples reference) - `svg/layout_*.svg` — every slide layout in the deck rendered once (its own contribution; master shapes do **not** repeat here) @@ -157,32 +168,32 @@ This produces, in one workspace: - `svg/inheritance.json` — which Layout/Master each Slide consumes plus source-owned `showInheritedShapes` / `showMasterShapes` booleans; Layout shapes follow the Slide's `showInheritedShapes`, while Master shapes require that value and the referenced Layout's `showMasterShapes`; backgrounds remain independent - `svg-flat/` — **optional verification view** (only with `--inheritance-mode both`; one self-contained SVG per slide): - `svg-flat/slide_NN.svg` — effective Master/Layout contributions permitted by the source visibility flags plus Slide-local content, painted into one SVG so opening any slide on its own shows the full page like PowerPoint would. Background inheritance remains independent. Use this for previews / screenshot pipelines / "what does the slide actually look like" sanity checks. -- The default `--inheritance-mode layered` emits only the canonical layered view. Pass `both` when a separate complete-page verification tree is worth the storage cost, or `flat` for round-trip use cases (legacy: `svg/` becomes self-contained slides without the master/layout/inheritance files). -- The importer does not generate a duplicate narrative summary or persistent SVG-size CSV. Read compact facts from `manifest.json`; run ad hoc size measurements outside the canonical workspace when needed. +- The default `--inheritance-mode layered` emits only the canonical layered view. Pass `both` when a separate complete-page verification tree is worth the storage cost, or `flat` when a projection-only self-contained `svg/` tree without master/layout/inheritance files is explicitly required. Imported-deck round-trip uses the separate `authoring-svg-flat/` contract. +- The importer does not generate a duplicate narrative summary or persistent SVG-size CSV. Read compact facts from `analysis/manifest.json`; run ad hoc size measurements outside the canonical workspace when needed. Import fidelity rules: -- Placeholder metadata is recorded in `manifest.json`; master / layout SVGs show lightweight dashed guides with labels only in `svg/`, not in `svg-flat/`. +- Placeholder metadata is recorded in `analysis/manifest.json`; master / layout SVGs show lightweight dashed guides with labels only in `svg/`, not in `svg-flat/`. - Charts, SmartArt, diagrams, and OLE objects are typed placeholders in `svg/`. In `svg-flat/`, they use a preview image with a small badge when one exists; otherwise they stay visible as placeholders. Tables are converted to real SVG. - Missing media and external linked images fail the import. EMF / WMF Office vector media are converted to PNG previews when supported by the local toolchain; otherwise the import fails. It is an analysis aid, not a final direct template conversion. -**Lossless payload backing + editable authoring IR**: +**Immutable source evidence + compact editable authoring SVG**: Keep `svg/` +unchanged for validation and supported non-visible payload; it is never visible +template source. Keep optional `svg-flat/` unchanged. The import transaction +also writes canonical compact `authoring-svg/` and, with `both`, its flat view; +no second projection precedes Template_Designer review. -Keep `<import_workspace>/svg/` unchanged as lossless native-payload backing. If the optional `<import_workspace>/svg-flat/` verification tree was requested, keep it unchanged too. Before the Template_Designer reads or edits any imported page SVG, create the canonical non-destructive authoring IR bundle: - -```bash -python3 skills/ppt-master/scripts/svg_authoring_view.py "<import_workspace>/svg" -o "<import_workspace>/authoring-svg" --projection-kind layered -``` - -Only when the import explicitly used `--inheritance-mode both`, create the optional complete-page verification IR: - -```bash -python3 skills/ppt-master/scripts/svg_authoring_view.py "<import_workspace>/svg-flat" -o "<import_workspace>/authoring-svg-flat" --projection-kind flat -``` - -Each bundle contains editable SVGs, a model-readable `authoring_summary.json`, and a tool-only `authoring_manifest.json`. The projection removes opaque text payload, duplicate hidden geometry carriers, and import-only identity attributes while keeping visible shape intent, compact preset/frame metadata, structure markers, logical ids, valid asset references, and a reserved `data-pptx-source-ref` on each imported logical object. Model-facing `data-pptx-frame` values and safe transform page coordinates use at most two decimals; normalized crop ratios, path geometry, transform linear coefficients, and the immutable lossless source retain their required precision. The summary lists the current SVG roster plus per-file canvas, size, text, image, vector, placeholder, and source-ref counts. The machine manifest records relative source files, document hashes, source paths, and initial authoring-subtree hashes; it does not duplicate opaque payload and MUST NOT enter model context. Source refs are unique within one document and are interpreted by tools together with that document's manifest record. +The bundle contains compact SVGs, model-readable `authoring_summary.json`, and +tool-only `authoring_manifest.json`. Projection removes opaque/duplicate/import- +only carriers while retaining visible intent, compact frame/preset and structure +markers, ids, assets, inline Chart/Table JSON, and per-object source refs. +Model-facing safe page coordinates use at most two decimals; crop/path/matrix +values and lossless evidence retain required precision. The summary indexes the +roster/counts. The manifest owns source paths/hashes and initial subtree hashes, +never duplicates opaque payload, and MUST NOT enter model context. Refs are +document-local and tool-resolved. In-place vector and picture normalization refreshes the summary automatically. After any other direct IR edit, refresh it before the next analysis pass: @@ -191,29 +202,44 @@ After any other direct IR edit, refresh it before the next analysis pass: python3 skills/ppt-master/scripts/svg_authoring_view.py "<import_workspace>/authoring-svg" --refresh-summary ``` -`authoring-svg/` is the canonical editable IR for template creation. The lossless trees are read only by materialization when an unchanged referenced object needs supported native payload or fallback evidence. Do not edit or copy the lossless SVGs directly. The IR is not a finished template directory and must be materialized into the active authoring workspace's validated `templates/*.svg` before preview or export. +`authoring-svg/` is canonical visible authoring. Template_Designer inspects each +required document and may redraw/normalize it while preserving meaning, +structure, and similar presentation. Lossless trees support provenance and +non-visible/explicit-break recovery only; never edit/copy them or replace a +visible authored subtree. Validate/publish to `templates/*.svg` before +preview/export. -For a Type A `mirror`, final materialization is owned by -`mirror_template_materialize.py`; never assemble the structured output by -copying lossless SVGs or `svg-flat/` pages into `templates/`. The command runs -only after the confirmed IR edits and vector-readability pass described below. +For Type A `mirror`, `mirror_template_materialize.py` validates/publishes but +does not author visible design. Run it only after Template_Designer review, +needed fidelity edits, and the vector-readability pass. Never copy lossless or +flat pages into `templates/`. `standard` / `fidelity` remain newly authored Template_Designer output and do not use this compiler. -**Vector illustration readability pass**: +**Creation-time vector readability**: -Factor large decorative vector groups out of the lightweight IR documents so the model-facing SVGs stay readable while export remains native shapes. Never run this in place on the lossless import SVGs: +`pptx_template_import.py` factors only large non-semantic decorative vector +groups while the import workspace is still staged. The first published +`authoring-svg/` files therefore already contain compact +`<use data-icon="imported/..." data-pptx-asset-role="decoration"/>` +references; do not run a second in-place readability or compaction pass. +`--inheritance-mode both` reuses the layered inventory while creating the +optional flat view, so only genuinely flat-only vectors create another asset. -```bash -# layered view — primary read surface and canonical extracted-vector inventory -python3 skills/ppt-master/scripts/extract_svg_assets.py "<import_workspace>/authoring-svg" --icons-dir "<import_workspace>/icons" --icon-namespace imported --inplace --id-prefix layered --min-decoration-bytes 3000 --clean-stale - -# optional flat verification view — run only when authoring-svg-flat/ exists; -# reuse matching layered assets so only genuinely flat-only vectors create files -python3 skills/ppt-master/scripts/extract_svg_assets.py "<import_workspace>/authoring-svg-flat" --icons-dir "<import_workspace>/icons" --icon-namespace imported --reuse-inventory "<import_workspace>/authoring-svg_vector_asset_inventory.json" --inplace --id-prefix flat --min-decoration-bytes 3000 --clean-stale -``` - -The authoring SVGs in `<import_workspace>/authoring-svg/` and, when requested, `<import_workspace>/authoring-svg-flat/` are rewritten in place with compact `<use data-icon="imported/..."/>` placeholders. Each in-place extraction refreshes that bundle's `authoring_summary.json` automatically. Extracted assets have one canonical copy under `<import_workspace>/icons/imported/`; never duplicate them under `templates/icons/`. The root `icons/` directory remains a namespace container and must not contain rewritten page SVGs or inventories. The inventory is written beside the processed IR directory and records every preserved `data-pptx-source-ref`; re-inlining an asset therefore re-establishes the referenced object mapping before materialization. The existing icon embedding path re-inlines the extracted assets before final export, preserving multi-color artwork and non-square viewBox geometry as native SVG shapes. Text-bearing groups are never extracted; text must stay readable/editable in the working SVG. Extraction triggers on either many drawable elements or a large pure-vector XML block, so long single-path illustrations are factored out too. Pure-vector decoration runs inside text-bearing groups use a lower size threshold, allowing card borders and decorative paths to be extracted without hiding text. Referenced defs (`gradient` / `pattern` / `filter` / `clipPath` / `marker`) are copied into each asset and namespaced so the asset is self-contained after re-inline. If both layered and flat views are processed into the same icon namespace, keep distinct `--id-prefix` values to avoid asset ID collisions. `--clean-stale` removes only stale generated assets for the current SVG filenames and prefix inside the selected namespace; it is safe in this import workspace but should not be used against a shared hand-curated icon directory without a specific prefix. +Extracted assets have one canonical copy under +`<import_workspace>/icons/imported/`; never duplicate them under +`templates/icons/`. The root `icons/` directory remains a namespace container +and must not contain rewritten page SVGs or inventories. Every imported asset +root, placeholder, and v2 inventory record declares the `decoration` role. Any +subtree containing a semantic marker, text, table, chart, relationship, or +other meaning-bearing content remains inline; extraction and both consumers +fail closed if that boundary is crossed. Eligible decoration records may +retain `data-pptx-source-ref`, so re-inlining re-establishes their object +mapping before materialization. The existing icon embedding path re-inlines +the extracted assets before final export, preserving multi-color artwork and +non-square viewBox geometry as native SVG shapes. Referenced defs (`gradient` +/ `pattern` / `filter` / `clipPath` / `marker`) are copied into each asset and +namespaced so the asset is self-contained after re-inline. The layered pass owns the canonical extracted-vector pool. Each new asset records a source fingerprint before generated ID namespacing. The flat pass MUST consume the layered inventory through `--reuse-inventory`: an exact fingerprint match writes only a `<use>` reference to the existing layered asset, while an unmatched flat-only subtree may create one new asset under the `flat` prefix. Do not independently extract the two views into parallel asset sets. With `--clean-stale`, a rerun also removes obsolete generated `flat_*` duplicates while retaining every reused layered reference. @@ -250,8 +276,9 @@ Master/Layout ownership. Do not apply it to placeholders, individual imported native shapes, native table/chart fallbacks, icon placeholders, or compact authored presets. `mirror` must keep the source native group/picture identity and therefore must not use this normalization. The original lossless `svg/` -tree remains unchanged and authoritative. Any optional `svg-flat/` tree remains -unchanged but is verification-only. +tree remains immutable source/package evidence; the reviewed compact authoring +tree owns visible output. Any optional `svg-flat/` tree remains unchanged but +is verification-only. `extract_svg_assets.py` remains a different operation: it factors vectors out for model readability and re-inlines them as native shapes before export. It @@ -261,32 +288,29 @@ does not turn those vectors into a picture. | Mode | Required read set | |---|---| -| `standard` / `fidelity` | `manifest.json`, exported assets, `svg/inheritance.json`, `authoring-svg/authoring_summary.json`, and every cleaned layered IR document (`authoring-svg/master_*.svg` / `layout_*.svg` / `slide_NN.svg`). Do not read `authoring_manifest.json`; it is compiler-only. The layered IR is the complete read surface: it covers Layouts unused by any sample slide (invisible in `svg-flat/` yet still template vocabulary), and per-page composition follows from `inheritance.json`. Cleaned flat pages are optional composition spot checks, not a required second pass over the same shapes. Source topology remains non-binding; the two modes differ in output design (`fidelity` designs a broader roster covering the useful visual range), not in read coverage. | -| `mirror` | `manifest.json`, `native_structure.json`, `svg/inheritance.json`, `authoring-svg/authoring_summary.json`, and every cleaned layered Master/Layout/Slide IR document. Do not read `authoring_manifest.json`; `mirror_template_materialize.py` loads and validates it internally. The layered `authoring-svg/` tree is the sole editable and materialization input. Cleaned `authoring-svg-flat/` slides are optional visual composition checks only; never edit or feed them into template materialization. Materialization may resolve unchanged refs against the matching lossless backing without placing opaque payload in model context. | +| `standard` / `fidelity` | `analysis/manifest.json`, exported semantic resources, `svg/inheritance.json`, `authoring-svg/authoring_summary.json`, and every cleaned layered IR document (`authoring-svg/master_*.svg` / `layout_*.svg` / `slide_NN.svg`). Do not read `authoring_manifest.json`; it is compiler-only. The layered IR is the complete read surface: it covers Layouts unused by any sample slide (invisible in `svg-flat/` yet still template vocabulary), and per-page composition follows from `inheritance.json`. Cleaned flat pages are optional composition spot checks, not a required second pass over the same shapes. Source topology remains non-binding; the two modes differ in output design (`fidelity` designs a broader roster covering the useful visual range), not in read coverage. | +| `mirror` | Read both analysis manifests, inheritance, authoring summary, every source Slide SVG, and only reachable Master/Layout SVGs. Do not read the tool-only manifest; the publisher validates that set. Layered `authoring-svg/` is the sole visible edit/publication input and Template_Designer may redraw it. Flat views are optional checks; lossless backing validates facts and supplies supported non-visible semantics only. | -Use the compact facts in `manifest.json` for orientation. Use screenshots or the original PPTX only for visual cross-checking. Do not bulk-read opaque lossless payload into model context. +Use the compact facts in `analysis/manifest.json` for orientation. Use screenshots or the original PPTX only for visual cross-checking. Do not bulk-read opaque lossless payload into model context. Interpretation rule (carries forward into Steps 2 and 4): -- `manifest.json` is the source of truth for facts about the source deck: slide size, theme colors, fonts, background inheritance, reusable asset inventory, declared source layout/master structure, and slide reuse relationships. It dictates which source facts mirror may preserve during materialization, but not `standard` / `fidelity` output topology. +- `analysis/manifest.json` is the source of truth for facts about the source deck: slide size, theme colors, fonts, background inheritance, reusable resource inventory, declared source layout/master structure, and slide reuse relationships. It dictates which source facts mirror may preserve during materialization, but not `standard` / `fidelity` output topology. - `authoring_summary.json` is the model-facing index for the current authoring SVG roster and readability statistics. Regenerate it after direct IR edits before analysis. - `authoring_manifest.json` is machine-only provenance. Do not open or quote it in model context; the mirror compiler validates it internally against the edited IR and immutable backing. -- `native_structure.json` is the source of truth for source PowerPoint identity: stable layout keys, picker names, parent masters, placeholder types/indices, and the source-package hash. Mirror preserves those facts one-to-one. `standard` / `fidelity` do not mine them into the new structure. -- `manifest.json`, `native_structure.json`, and `svg/inheritance.json` intentionally overlap only at contract boundaries so materialization can cross-check source identity, graph ownership, and visibility; do not collapse them into a cache or substitute one for another -- exported `assets/` are the canonical reusable image pool — `<image>` references in `svg/` already point at these files directly -- exported `icons/imported/*.svg` files are the canonical reusable vector illustration pool, but they are **not** part of the default read set. Use `authoring_summary.json` `icon_refs` and the cleaned SVGs first. Query `*_vector_asset_inventory.json` by an exact asset id only when source-ref or fingerprint detail is required; do not load the complete inventory into model context. Open a specific imported SVG only when that asset affects the current design decision. +- `analysis/native_structure.json` is the source of truth for source PowerPoint identity: stable layout keys, picker names, parent masters, placeholder types/indices, and the source-package hash. Mirror preserves facts reachable from source Slides one-to-one. `standard` / `fidelity` inspect the complete inventory as evidence but author new identities and ownership. +- `analysis/manifest.json`, `analysis/native_structure.json`, and `svg/inheritance.json` intentionally overlap only at contract boundaries so materialization can cross-check source identity, graph ownership, and visibility; do not collapse them into a cache or substitute one for another +- exported `images/` are the canonical reusable image pool — `<image>` references in `svg/` already point at these files directly; SVG/EMF/WMF image media stay in this pool rather than moving to a generic asset directory +- exported `icons/imported/*.svg` files are the canonical reusable decoration pool, but they are **not** part of the default read set. Use `authoring_summary.json` `icon_refs` and the cleaned SVGs first. Query `*_vector_asset_inventory.json` by an exact asset id only when source-ref or fingerprint detail is required; do not load the complete inventory into model context. Open a specific imported SVG only when that decoration affects the current design decision. - cleaned layered authoring SVGs are the mirror editing and verification surface; they expose source ownership without requiring the model to read opaque payload. Do not use them to promote, demote, merge, or split source structure. - cleaned complete-page IR documents are optional composition spot checks for authored modes and verification views for mirror. They never replace the layered editable IR or immutable payload backing. - screenshots remain useful for judging composition and style, but should not override extracted factual metadata unless the import result is clearly incomplete -**Mirror complete-graph gate**: compare every `native_structure.json` Layout -and Master with the layered `authoring_summary.json` roster before offering `mirror`. -Every source Layout—including one unused by all source slides—must have a -layered IR document and matching payload backing from which a reusable -definition SVG can be materialized. Every source Master must own at least one retained Layout. Missing IR documents -or ambiguous parentage are blocking; unused identities themselves are supported -and must not be dropped. The compiler performs the exact source-ref and hash -checks from `authoring_manifest.json`. +**Mirror reachable-graph gate**: Before offering `mirror`, compare every source +Slide and referenced Layout/Master with the authoring summary. Missing reachable +SVG/evidence or ambiguous parentage blocks. Omit unused identities. The +publisher verifies source SHA, refs, graph/assignment closure, and subtree hash +status; an authored change never triggers visible XML restoration. ### Basic norm extraction (mandatory when reference content exists) @@ -301,25 +325,31 @@ page order/count, page mappings, canvas, and native structure. | Norm area | Extract from | Record as | |---|---|---| -| Canvas / page geometry | `manifest.json` slide size, SVG `width` / `height` / `viewBox` | `[fact]` canvas format, pixel dimensions, source `viewBox`, and aspect ratio | +| Canvas / page geometry | `analysis/manifest.json` slide size, SVG `width` / `height` / `viewBox` | `[fact]` canvas format, pixel dimensions, source `viewBox`, and aspect ratio | | Identity system | theme colors, font usage, logo / emblem assets, recurring backgrounds | `[fact]` when imported; `[suggested]` only for visual estimates | | Layout grammar | masters / layouts, repeated chrome, margins, columns, card grids, section dividers | Template-specific rules, not generic spacing boilerplate | | Image system | image crops/clips, scrim/overlay treatments, baked-alpha treatments, full-bleed zones, hero-image placement, mosaic rules, captions | Template-specific image-placement rules with source examples | | Density rhythm | title scale, content block count, whitespace balance, dense vs. breathing pages | Page-type guidance for Strategist / Executor | | Page roster semantics | cover / TOC / chapter / content / ending variants and their intended content slots | `design_spec.md §V Page Roster` rows | | Asset policy | source images / icons / textures that are part of the template vs. sample-only content | `design_spec.md §VI Assets` or omit sample-only assets | -| Native PowerPoint structure | `native_structure.json` plus inheritance facts | Mirror maps the validated source graph one-to-one into a new workspace. Standard/fidelity author an independent output graph and do not distill source common structure. | +| Native PowerPoint structure | `analysis/native_structure.json` plus inheritance facts | Mirror maps each source Slide's reachable chain one-to-one. Standard/fidelity review the complete inventory, then author a compact or broader useful graph expressed through Slide prototypes. | Distinguish observed facts from template rules: "`slide_07` uses a left photo crop" is a fact; "content pages may use a left photo rail for location / product / case-study pages" is the reusable rule. **Read gate**: - `standard` / `fidelity`: read `authoring_summary.json`, every layered IR Master, Layout, and Slide, and the inheritance map; flat pages are optional spot checks -- `mirror`: read `authoring_summary.json`, verify and report every layered IR Master, Layout, and Slide plus the inheritance map, and leave `authoring_manifest.json` to the compiler +- `mirror`: read `authoring_summary.json`, verify every source Slide and its referenced Layout/Master plus the inheritance map, report retained and omitted identities, and leave `authoring_manifest.json` to the compiler -Do not treat authoring IR documents as final template assets. `standard` / `fidelity` author new SVGs from the confirmed brief and IR references. Mirror edits the IR and materializes it with lossless native-payload backing. +Authoring SVGs are not installed assets. `standard` / `fidelity` author new +SVGs; mirror reviews/edits compact parsed SVG, then publishes it with source +validation and supported non-visible recovery only. -> **Mirror-mode materialization path** — use `native_structure.json` and `svg/inheritance.json` as model-readable structural authority. The cleaned layered IR is the editable source, `authoring_summary.json` is its model-facing index, and lossless layered SVGs are immutable payload backing; optional flat SVGs are verification-only. `mirror_template_materialize.py` consumes the machine manifest internally. Preserve only the roster, appearance, ownership, placeholders, converter-supported native metadata, and available SVG fallbacks that are actually present and validated; do not synthesize missing facts or a different graph. +> **Mirror authoring/publication** — Native structure/inheritance own structure; +> layered `authoring-svg/` owns compact editable visuals; lossless/flat trees are +> validation/non-visible backing only. Template_Designer owns visible fidelity; +> the publisher validates/composes the current tree and reachable graph without +> copying lossless visible subtrees. ### 1B. Existing SVG assets @@ -395,7 +425,7 @@ Skip the analysis. Step 2 will list every Required item as `[decision]`; nothing Compose one concise natural-language proposal that states the template the AI intends to create, **labelling each material value's provenance**: -- **`[fact]`** — independently traceable external authority or machine-observable source metadata (e.g. theme color from `manifest.json`, image dimensions, or an identified official manual); a user-authored brief file is not a fact merely because it is a file +- **`[fact]`** — independently traceable external authority or machine-observable source metadata (e.g. theme color from `analysis/manifest.json`, image dimensions, or an identified official manual); a user-authored brief file is not a fact merely because it is a file - **`[suggested]`** — AI-inferred from analysis or context (e.g. tone summary, applicable scenarios; visually estimated values from type C) - **`[decision]`** — an explicit user-authored instruction, including exact values supplied in conversation, pasted text, or a user-written brief file (e.g. a template name, a preservation requirement, a palette, or a layout rule) - **`[derived]`** — an internal execution value the AI derives from the request and evidence so tools can run deterministically; it is recorded for provenance but never presented as a choice the user must understand @@ -415,7 +445,7 @@ Compose one concise natural-language proposal that states the template the AI in | Theme direction | Create Layout/Create Deck only. Describe the intended light/dark/mixed behavior in plain language. Create Brand records identity colors instead and does not own a page theme mode. | | Canvas | Create Layout/Create Deck only. State the recommended canvas with exact pixel size and `viewBox`; do not enumerate same-ratio alternatives unless the user asks or the evidence is genuinely ambiguous. | | Creation plan | Create Layout/Create Deck only. Describe what will be preserved, what will be rebuilt, how broad the prototype roster will be, and how native structure will be handled. The AI derives the internal `replication_mode` from this prose after confirmation; never ask the user to select `standard`, `fidelity`, or `mirror`. | -| Native structure plan | Create Layout/Create Deck only. State the planned Master/Layout/slot result. For newly authored output, show the planned Master families and reject one-Master-per-Layout organization or equivalent duplicate Masters. For literal preservation, summarize the exact supported source facts that will be mapped into the new workspace. | +| Native structure plan | Create Layout/Create Deck only. State the Master/Layout/slot result: compact authored `standard`, broader useful `fidelity`, or source-Slide-reachable `mirror`. Every authored Layout needs a Slide prototype; reject duplicate Masters. | | Asset bundling | Create Brand/Create Layout/Create Deck only. Recommend included assets, plus excluded candidate assets with a one-line reason when reference assets exist. Create Style records textual provenance only and writes no asset payload. | Items to surface: @@ -425,7 +455,7 @@ Items to surface: | Output scope | Yes | `[decision]` — `library` (default, globally reusable and indexed) or `project` (qualified Design Spec under one initialized shared project root) | | Target project | Yes for `project`; N/A for `library` | `[decision]` — explicit path to the initialized target workspace; validate it during the Step 4 preflight | | New template ID | Yes | `[decision]` when supplied; otherwise propose a filesystem-safe ASCII slug as `[suggested]`. In library scope it also becomes the matching index key | -| Template display name | Yes | `[decision]` when supplied; otherwise `[suggested]`, often from `manifest.json.source.name` for type A | +| Template display name | Yes | `[decision]` when supplied; otherwise `[suggested]`, often from `analysis/manifest.json.source.name` for type A | | Category | Create Layout/Create Deck only | `[decision]` when explicit; otherwise `[derived]` for indexing — Create Deck: `brand` / `general` / `scenario` / `government` / `special`; Create Layout: `general` / `scenario` / `government` / `special` | | Applicable scenarios | Yes | Create Brand: identity use cases. Create Style: broad best-fit discovery context only, without binding a target audience, outcome, or recurring application. Create Layout: content shapes and delivery settings its geometry can support, without communication or narrative ownership. Create Deck: recurring presentation situations inside the application contract. `[suggested]` from analysis unless explicitly authored or externally sourced; user confirms. | | Application context and representative page roles | Create Deck only | `[decision]` when supplied explicitly; otherwise `[suggested]` from recurring source patterns. Describe the source and intended family without assigning required/optional/repeatable status or fixed/replaceable/example-only policy. | @@ -433,18 +463,18 @@ Items to surface: | Communication method and evidence discipline | Create Style only | `[decision]` when explicit; otherwise `[suggested]` from repeated source behavior. State argument flow, message/evidence discipline, page-role vocabulary, and data-expression rules without fixing a page sequence. | | Visual-system defaults | Create Style only | `[decision]` when explicit; otherwise `[suggested]` from evidence. Palette, typography, mode, and visual-style values remain overrideable seeds, never Brand identity truth or direct final Stage-2 locks. | | Review focus | Create Style only | `[decision]` when explicit; otherwise `[suggested]`. These checks apply only if the user separately enables visual review; the Style cannot trigger that stage. | -| Theme mode | Create Layout/Create Deck only | A: `[fact]` from `manifest.json` background colors. B: `[fact]` from SVG `fill`. C: `[suggested]` from visual estimate. D: `[fact]` from an independently identified external authority, `[decision]` from user-authored text in any carrier, otherwise `[suggested]`. E: `[decision]`. | +| Theme mode | Create Layout/Create Deck only | A: `[fact]` from `analysis/manifest.json` background colors. B: `[fact]` from SVG `fill`. C: `[suggested]` from visual estimate. D: `[fact]` from an independently identified external authority, `[decision]` from user-authored text in any carrier, otherwise `[suggested]`. E: `[decision]`. | | Canvas format and dimensions | Create Layout/Create Deck only | A/B: `[fact]` from slide size or SVG `width` / `height` / `viewBox`; show `canvas_format`, `canvas_width`, `canvas_height`, `canvas_viewbox`, and `source_viewbox`. C: `[suggested]` from image aspect ratio. D: `[fact]` from an independently identified external authority or `[decision]` from user-authored text in any carrier when specified. E: `[decision]`, default `ppt169` (`1280x720`, `0 0 1280 720`). | | Internal creation strategy | Create Layout/Create Deck only | `[derived]` from the confirmed natural-language plan and evidence. `standard` is the compact authored implementation, `fidelity` requires A/B page evidence for broader source-aligned coverage, and `mirror` requires A or B with a complete explicit structure contract for literal materialization. Create Layout mirror additionally requires a brand-neutral and application-neutral source. Persist `replication_mode` for tools, not as a user-facing mode. | -| Native structure facts | Create Layout/Create Deck with Type A or structured Type B | `[fact]` from `native_structure.json` / source SVG contract: master/layout counts, parentage, page assignments, placeholder identities, and multi-master status. Mirror preserves these validated facts in the new workspace; authored modes do not use them as output topology. | -| Structure ownership plan | Create Layout/Create Deck only | `[derived]` from the requested result. Authored output creates new Master/Layout ownership, including the reusable-family reason for every additional Master; literal preservation maps source ownership without synthesis. Every Master must own at least one emitted Layout and every Layout must have at least one emitted prototype. | +| Native structure facts | Create Layout/Create Deck with Type A or structured Type B | `[fact]` from `analysis/native_structure.json` / source SVG contract: master/layout counts, parentage, page assignments, placeholder identities, and multi-master status. Mirror preserves the source-Slide-reachable subset in the new workspace and records the remaining identities only as omitted scope; authored modes do not use source topology as output topology. | +| Structure ownership plan | Create Layout/Create Deck only | `[derived]`. Authored output explains each additional Master; mirror maps only source-Slide-reachable ownership. Every Master owns an emitted Layout and every Layout has a complete Slide prototype. | | Reference treatment | Create Layout/Create Deck when a reference exists | `[derived]` per page from the user's prose: closely reproduce geometry/decoration where requested, otherwise adapt the reference into the newly authored system. Literal materialization preserves supported source facts mechanically. | | Basic template norms | Yes when reference exists | Create Brand uses the identity fields and provenance rules from its child workflow. Create Style uses portable method, page-role, evidence/data, composition/density, visual-default, and image/icon rules while discarding project-specific context. Create Layout/Create Deck use `[fact]` / `[suggested]` layout grammar, image system, density rhythm, page roster semantics, and asset policy from Step 1. | | Reference source | Optional | already known if Step 1 ran | | Theme color | Create Brand/Create Deck only | A: `[fact]` from theme XML. B: `[fact]` from dominant SVG `fill`. C: `[suggested]` from visual estimate (HEX is approximate). D: `[fact]` from an independently identified external authority, `[decision]` from user-authored text in any carrier, otherwise `[suggested]`. E: `[decision]`. Create Layout may use neutral preview paint but stores no identity color. | -| Fonts | Create Brand/Create Deck only | A: `[fact]` from `manifest.json`. B: `[fact]` from SVG `font-family`. C: font family is not derivable — use `[decision]` if the user supplies one. D: `[fact]` from an independently identified external authority or `[decision]` from user-authored text in any carrier. E: `[decision]` when a custom stack is wanted. Create Layout stores no typeface identity or final type scale; its structural text roles, alignment, wrapping, and capacity remain part of page grammar. | +| Fonts | Create Brand/Create Deck only | A: `[fact]` from `analysis/manifest.json`. B: `[fact]` from SVG `font-family`. C: font family is not derivable — use `[decision]` if the user supplies one. D: `[fact]` from an independently identified external authority or `[decision]` from user-authored text in any carrier. E: `[decision]` when a custom stack is wanted. Create Layout stores no typeface identity or final type scale; its structural text roles, alignment, wrapping, and capacity remain part of page grammar. | | Design style | Required for Create Style; optional otherwise | `[decision]` when explicit; otherwise `[suggested]` from analysis. For Style this is an overrideable visual-direction seed, not an exact lookup token or identity lock. | -| Assets list | Optional for Create Brand/Create Layout/Create Deck; N/A for Create Style | A: `[fact]` from `assets/` listing; user picks which to bundle. B/C/D: retain each file's source and let the user confirm adoption. E: none. Style may cite the reference textually but never adopts an asset. | +| Assets list | Optional for Create Brand/Create Layout/Create Deck; N/A for Create Style | A: `[fact]` from `analysis/manifest.json` plus populated semantic resource directories; user picks which to bundle. B/C/D: retain each file's source and let the user confirm adoption. E: none. Style may cite the reference textually but never adopts an asset. | | Keywords | Create Style/Create Layout/Create Deck only | `[suggested]` from analysis (3–5 short tags); user confirms. Create Brand has no keywords field or keyword index payload. | When the bundle includes Type A for Create Layout/Create Deck, also include in this message: @@ -478,10 +508,10 @@ Skipping this gate — including silently inferring values from reference files, - [ ] For Create Layout/Create Deck, the canvas format is fixed before SVG generation - [ ] For Create Layout/Create Deck, the AI-derived internal strategy is consistent with the bundle evidence (`fidelity` requires A/B page evidence; `mirror` requires A or structured B; C/D/E channels alone permit only `standard`); Create Layout mirror evidence is already brand-neutral and application-neutral - [ ] Every supplied visual, textual, documentary, web, and asset channel has been analyzed or explicitly excluded; mixed-input conflicts are surfaced rather than silently resolved -- [ ] For Create Layout/Create Deck mirror, the source graph and supported geometry are complete; every source Layout absent from the source-slide roster is planned as a definition-only prototype, and any genuinely missing/unsupported facts were reported; Create Layout mirror contains no retained brand identity or reusable application policy +- [ ] For Create Layout/Create Deck mirror, every source Slide and its reachable Layout/Master chain is valid and context-complete; omitted unreferenced identities and genuinely missing/unsupported reachable facts were reported; Create Layout mirror contains no retained brand identity or reusable application policy - [ ] Child-specific norms from prior content have been surfaced and accepted, or explicitly marked N/A when no reference exists: identity/provenance for Create Brand; communication/evidence/visual-direction behavior for Create Style; layout/image/density/asset behavior for Create Layout/Create Deck - [ ] For Create Style, the portable communication method, open page-role vocabulary, evidence/data rules, visual defaults, image/icon direction, and advisory review focus are confirmed; project-specific audience/outcome/page sequence and all identity/structure fields remain N/A -- [ ] For Create Layout/Create Deck, the plan makes structure ownership explicit: authored output creates a new structure without source-topology distillation; literal materialization maps validated source ownership one-to-one into a new workspace +- [ ] For Create Layout/Create Deck, the plan makes structure ownership explicit: authored output creates compact (`standard`) or broader useful (`fidelity`) Slide prototypes after reviewing complete source evidence; literal materialization maps source Slides and their reachable graph one-to-one - [ ] For Create Deck, the recurring presentation family, intended audiences/outcomes, and representative page roles are understood without turning them into mandatory page/content policies; for Create Layout, no application contract or brand identity has leaked into the structure brief - [ ] For Create Brand, all required identity fields from its child workflow are confirmed and canvas/replication/native-structure fields remain N/A - [ ] For `library`, metadata is complete enough to register into the relevant index; for `project`, the same portable template metadata is complete and no global registration is planned @@ -539,13 +569,13 @@ author replacement contours. When the bundle includes Type A, pass the following internal package to the role: - finalized brief from Step 3 -- `manifest.json` -- `native_structure.json` and `source_template.pptx` -- `conversion-report.json` when source-recovery diagnostics exist -- exported `assets/` +- `analysis/manifest.json` +- `analysis/native_structure.json` and `sources/source.pptx` +- `validation/conversion-report.json` when source-recovery diagnostics exist +- exported `images/` and any other populated semantic resource directories - `*_vector_asset_inventory.json`, when the vector readability pass extracted assets, as an exact-id query surface only; do not load it or `icons/imported/*.svg` wholesale - `authoring-svg/authoring_summary.json` and editable layered IR documents; keep `authoring_manifest.json` bundled for compiler use but do not load it into the role context; optional `authoring-svg-flat/` is a visual cross-check only and never a template materialization input -- for `mirror` only, matching immutable `svg/` payload backing plus `svg/inheritance.json`; immutable `svg-flat/` remains an optional visual cross-check +- for `mirror` only, matching immutable `svg/` source/package evidence plus `svg/inheritance.json`; immutable `svg-flat/` remains an optional visual cross-check and never replaces authored visible SVG - optional screenshots, if available When the bundle includes Type B, pass `authoring_summary.json`, the cleaned SVG file list from the analysis workspace, `*_vector_asset_inventory.json` as an exact-id query surface if extraction ran, any companion `design_spec.md` / `spec_lock.md`, and the analysis notes. Do not bulk-read the inventory or extracted vectors; open individual `icons/imported/*.svg` files only when needed. @@ -558,11 +588,11 @@ The role interprets the package according to the AI-derived internal creation st | Mode | Final SVG authority | Structure behavior | |---|---|---| -| `standard` / `fidelity` | Newly authored SVGs based on the confirmed brief and visual references | Design an intentional new Master/Layout/slot system. Source topology is neither preserved nor distilled into the output. | -| `mirror` | Editable `authoring-svg/` IR plus native-structure facts and lossless payload backing | Materialize source pages, Master/Layout identities and parentage, placeholder identity/bounds, ownership, and supported native-object metadata one-to-one in the new workspace. Materialization resolves unchanged source refs; it does not copy the lossless tree as the editable source. | +| `standard` / `fidelity` | Newly authored SVGs from the brief and complete source evidence | Author a compact or broader useful Master/Layout/slot system; do not retain identities merely because they exist. | +| `mirror` | Reviewed compact authoring SVG plus inline native JSON and structure facts | Publish source Slides and their reachable Layout/Master chains from the current authored tree; resolve inherited context without changing ownership. Similar presentation is required, code isomorphism is not. | -For Type A `mirror`, materialize the reviewed layered IR into a workspace with -no existing roster, using the deterministic compiler: +For Type A `mirror`, publish the reviewed/authored layered compact SVG into a +workspace with no existing roster, using the deterministic validator/publisher: ```bash python3 skills/ppt-master/scripts/mirror_template_materialize.py \ @@ -572,13 +602,18 @@ python3 skills/ppt-master/scripts/mirror_template_materialize.py \ Destination `templates/` may be absent/empty or hold unique qualified Brand/Style specs; a Layout-over-Deck stage may also hold one qualified Deck spec without its roster. A bare spec, Layout spec, active roster, or other -payload blocks materialization. Before atomic publication, the command verifies -the layered manifest/source refs, lossless SVG/source-PPTX hashes, complete -native/inheritance graph, and vector inventory. It emits source-ordered page -SVGs, unused-Layout definition -SVGs, `icons/imported/`, referenced `images/` / `templates/assets/`, and one +payload blocks publication. Before atomic publication, the command verifies +the layered manifest, source SHA/known refs, reachable native/inheritance graph, +assignments, closure, and vector inventory. Authoring subtree hash changes are +classified as legitimate edits; the command never rehydrates an ordinary +visible lossless subtree. It emits source-ordered Slide +SVGs only, plus `icons/imported/`, referenced +`images/`, `audio/`, `video/`, or +`native-payloads/imported/` resources as applicable, and one deduplicated `templates/native_payloads.json.gz` store when supported native -payload or repeated restoration metadata exists. It also writes +payload or repeated restoration metadata exists. A PPTX-backed mirror also +writes `templates/source_themes.json` with the exact Theme of each retained +Master. It also writes `templates/template_execution_manifest.json` with schema `ppt-master.template-execution-manifest.v1`, a compact tool-readable prototype roster and grouped source-import warning summary. Each prototype points to one @@ -596,8 +631,9 @@ not create the Design Spec. Template_Designer writes `<design_spec_path>` from the confirmed brief and the materialized roster before Step 5. A rerun targets a workspace with no roster rather than overwriting a partially reviewed one. -**Hard rule — mode-specific authorship**: `standard` and `fidelity` author new -project-canonical SVG documents. When one registered PowerPoint preset exactly +**Hard rule — mode-specific authorship**: `standard` and `fidelity` review all +source Master/Layout evidence, then author a compact or broader useful canonical +system without copying unused/duplicate identities. When one registered PowerPoint preset exactly expresses one complete object, they use the compact canonical `<g>` emitted by `preset_shape_svg.py`, following [`native-shape-authoring.md`](../references/native-shape-authoring.md); its @@ -608,33 +644,37 @@ paint changes require a new helper render. When actual `standard` / `fidelity` construction needs a Boolean result over supported shape/text operands, Template_Designer decides whether to use `shape_boolean_svg.py` under [`native-shape-authoring.md`](../references/native-shape-authoring.md) §6; a -brief/reference suggestion does not lock the operation. `mirror` preserves the expanded -lossless source contract in a new workspace and may only normalize transport details required by -the current compiler. Mirror never performs commonality -extraction, semantic synthesis, merge/split, promotion/demotion, renaming, or -re-parenting. +brief/reference suggestion does not lock the operation. `mirror` preserves the +source structure ownership, meaning, similar visible result, and supported +native facts in a new workspace while spelling them in the same compact +authoring contract used by other modes. It may redraw/normalize visible SVG, +equivalent inheritance, safe page-space metadata, and compiler transport +without changing object meaning. Mirror never performs semantic synthesis, +promotion/demotion, renaming, or re-parenting. **Hard rule — multi-Master package boundary**: More than one Master is valid only when `mirror` preserves an existing source graph in the new workspace or an authored template intentionally defines distinct reusable design families. `standard` / `fidelity` must not create one Master per Layout or duplicate equivalent Masters merely for organization. Every declared Master must own at least one emitted Layout, and every declared Layout must be selected by at least one prototype SVG so the complete graph can be compiled and verified. | Package concern | Requirement | |---|---| -| Theme ownership | Every registered Slide Master receives its own Theme part. Two Masters must never resolve to the same `ppt/theme/themeN.xml`. Theme cloning is exporter-owned; do not author or bundle Theme XML in the template workspace. | +| Theme ownership | Every registered Slide Master receives its own Theme part. Two Masters must never resolve to the same `ppt/theme/themeN.xml`. Theme cloning is exporter-owned. Authored modes do not bundle Theme XML; a PPTX-backed mirror may carry the compiler-written `source_themes.json` sidecar. | | Creation identity | Any generated `p14:creationId` on Slides, Layouts, or Masters is a valid unsigned 32-bit value and unique across those parts. Cloned structural parts always receive fresh values. | | Numeric registration | Master and Layout registration IDs are valid and unique in their owning lists; Layout numeric IDs are unique across the complete package, including across different Masters. | | Relationship graph | The presentation registers the exact Master and Slide rosters; each Master registers exactly its owned Layouts; each Layout targets exactly one declared Master; each Slide targets exactly its declared Layout. | SVG authors own the semantic roster, parentage, picker names, direct atoms, and slots. The exporter owns OOXML part cloning, Theme isolation, relationship registration, and package identity. Do not encode package repair workarounds in individual template SVGs. -Do not package `native_structure.json` or `source_template.pptx` as template inputs. In `standard` / `fidelity`, author Master/Layout direct semantic atoms and bounded slot groups deliberately from the intended reusable behavior. A validated compact canonical authored-preset `<g>` compiles to one native shape and therefore counts as one semantic atom; it may own a Master/Layout fixed layer or serve as the one direct carrier of an `object` slot. Ordinary groups are not structural atoms or single-object carriers. In `mirror`, edit the layered authoring IR and use inheritance/native facts to preserve source ownership; the lossless trees remain payload backing. Recursively expand fixed Master/Layout group wrappers only because the structured contract requires semantic atoms; preserve transforms, styles, paint order, and appearance, and never flatten or regroup by semantic judgment. +Do not package `analysis/native_structure.json` or `sources/source.pptx` as template inputs. In `standard` / `fidelity`, author Master/Layout direct semantic atoms and bounded slot groups deliberately from the intended reusable behavior. A validated compact canonical authored-preset `<g>` compiles to one native shape and therefore counts as one semantic atom; it may own a Master/Layout fixed layer or serve as the one direct carrier of an `object` slot. Ordinary groups are not structural atoms or single-object carriers. In `mirror`, edit the layered compact authoring SVG and use inheritance/native facts to preserve source ownership; the lossless trees remain immutable validation/non-visible-payload backing. Recursively expand fixed Master/Layout group wrappers only because the structured contract requires semantic atoms; keep comparable paint order and presentation, but do not require identical SVG transforms/styles/nodes or regroup by semantic judgment. -`<design_spec_path> §V` records the newly authored roster for `standard` / `fidelity`. For `mirror`, add the `Source Preservation Map` required by [template-designer.md](../references/template-designer.md), with one row per source slide and its preserved Master/Layout assignment. Do not add a synthesis-decision table. +`<design_spec_path> §V` records every emitted Slide. Mirror adds the [required Source Preservation Map](../references/template-designer.md) for source-Slide assignments and, when relevant, one sentence that unreferenced source identities exist but were not materialized. Do not add per-identity analysis or model-authored synthesis rationale. **Native-shape metadata boundary**: The authoring IR removes opaque payload from model context while retaining stable source refs. `standard` / `fidelity` use helper-generated compact canonical preset groups and project SVG/assets -rather than copied source payload. `mirror` materialization rehydrates only -native metadata already supported by the converter when a referenced -Slide-local/slot object's initial authoring hash still matches. Fixed layers are normalized to semantic atoms; +rather than copied source payload. `mirror` publication validates known refs +and may recover only non-visible native metadata already supported by the +converter. It always keeps the current compact visible authoring tree; matching +hashes never authorize lossless visible-subtree replacement. Fixed layers are +normalized to semantic atoms; unsupported or edited objects keep the current SVG fallback and are reported rather than silently replaced by stale metadata. Do not reproduce the preset syntax here; its single authority is @@ -643,27 +683,27 @@ usage guidance in the native-shape reference. Downstream, Strategist inspects the installed workspace and current content, then derives an internal application plan. The plan may use the full roster or a subset, repeat or reorder prototypes, and choose literal reuse, structural reuse, or visual-reference-only behavior. For exporter compatibility it records `template_reuse_scope` and, when structured, `template_adherence`; these are machine execution values, not user choices. `page_layouts` selects one complete authoring prototype per generated page, `pptx_masters` / `pptx_layouts` declare unique reusable definitions, and `page_pptx_layouts` assigns generated pages. No internal value forces a future generated deck to keep the source page count or order. -**Apply the confirmed natural-language intent to authored output**: in `standard` / `fidelity`, reproduce reference geometry and decoration where the request calls for close preservation, and adapt compositions where it calls for a reusable redesign. Mirror preserves every supported source visual represented by the validated IR and does not use this authored-page distinction. +**Apply the confirmed natural-language intent to authored output**: in `standard` / `fidelity`, reproduce reference geometry and decoration where the request calls for close preservation, and adapt compositions where it calls for a reusable redesign. Mirror authors compact SVG from every supported source visual represented by the parsed evidence, keeping a similar presentation without requiring the same code or nodes. **Sprite-sheet preservation (do NOT simplify away)**: PPTX-exported assets are often sprite sheets — a single tall/large image referenced from multiple slides, each cropping a different region via nested `<svg ... viewBox="...">` wrappers around `<image width="1" height="1">`. This nesting is **load-bearing geometry**, not redundant structure. When rebuilding, preserve the exact `viewBox` crop and the outer `<svg>` placement for every image; do not flatten to a single `<image>` with direct `x/y/width/height`. Verify by sampling: if any asset's pixel dimensions don't match the on-page display aspect, it is a sprite and the wrapper must stay. -**Mirror materialization contract** (type A or B): when the derived implementation writes `replication_mode: mirror`, the Template_Designer role: +**Mirror authoring/publication contract** (type A or B): when the derived implementation writes `replication_mode: mirror`, the Template_Designer role: -1. **Materializes one output SVG per source page** in `<authoring_workspace>/templates/`. Edit and normalize the matching `authoring-svg/` IR document, then run `mirror_template_materialize.py`; the compiler consumes the tool-only authoring manifest together with native structure facts and immutable payload backing. Do not hand-copy or independently rebuild its graph. Preserve the source Master/Layout keys and picker names, Layout parentage, slide assignment, placeholder type/index/bounds, inherited-shape visibility, ownership, paint order, and supported native metadata that are present and validated. Mechanical namespace, root-declaration, asset-path, and fixed-layer group normalization is allowed only when source ownership and appearance remain unchanged. - - Type A model-facing source: `<import_workspace>/authoring-svg/authoring_summary.json` plus the editable SVGs; `<import_workspace>/svg/`, `svg/inheritance.json`, and `native_structure.json` provide payload and structural backing. The compiler alone reads `<import_workspace>/authoring-svg/authoring_manifest.json`. Optional `<import_workspace>/svg-flat/` is verification-only. +1. **Authors and publishes one output SVG per source Slide** in `<authoring_workspace>/templates/`. Inspect every matching `authoring-svg/` document, redraw/normalize where useful for compactness while keeping similar presentation and source meaning, refresh its summary, then run `mirror_template_materialize.py`. The publisher consumes the tool-only authoring manifest together with native structure facts and immutable source evidence; it validates/composes the current authored tree and never restores ordinary visible source XML. Do not hand-copy the lossless tree or independently rebuild the graph. Preserve each Slide's reachable Master/Layout keys and picker names, Layout parentage, assignment, placeholder type/index/bounds, inherited-shape visibility, ownership, and supported native facts. Code/node identity is not required. + - Type A model-facing source: `<import_workspace>/authoring-svg/authoring_summary.json` plus the editable compact SVGs; `<import_workspace>/svg/`, `svg/inheritance.json`, and `analysis/native_structure.json` provide immutable source/non-visible payload and structural backing. The publisher alone reads `<import_workspace>/authoring-svg/authoring_manifest.json`. Optional `<import_workspace>/svg-flat/` is verification-only. - Type B model-facing source: `<svg_analysis_workspace>/authoring-svg/authoring_summary.json` plus its editable SVGs; the complete explicit source SVG contract is immutable backing - - For every source Layout unused by all source slides, additionally materialize one definition-only SVG named `layout_<layout_key>.svg` from its layered authoring IR document and payload backing. It carries the exact root identity, fixed atoms, and placeholder contract but is not a generated page assignment. Use source placeholder prompts/carriers; do not invent business content. This definition SVG lets downstream export register the Layout and any otherwise-unused parent Master without retaining an internal carrier slide. + - Each source Slide SVG resolves Master + Layout + Slide context and keeps layer markers explicit. Source Master/Layout identities unused by every Slide produce no file; use `standard` / `fidelity` to re-author useful ones as complete Slide prototypes. 2. **Renames each file** using the source-order-first convention `<NNN>_<page_type>.svg`, where `<NNN>` is the source-order index zero-padded to 3 digits and `<page_type>` is typically `cover` / `toc` / `chapter` / `content` / `ending` (fall back to `content` when the type cannot be confidently classified). Examples: `001_cover.svg`, `002_toc.svg`, `003_content.svg`, ..., `050_ending.svg`. - - Type A: derive `<page_type>` from `manifest.json.pageTypeCandidates` + - Type A: derive `<page_type>` from `analysis/manifest.json.pageTypeCandidates` - Type B: derive `<page_type>` from the source filename when it follows the PPT Master convention (`01_cover.svg` → `cover`, `03a_content_two_col.svg` → `content`); otherwise infer from page content or fall back to `content` 3. **Routes bundled assets through the common workspace contract** and rewrites every `<image href="...">` consistently. Keep stable source asset identity in mirror; do not rename, merge, or replace assets by semantic judgment. - - Type A: assets come from `<import_workspace>/assets/` + - Type A: image media come from `<import_workspace>/images/`; other resources retain their semantic source directories - Type B: resolve relative paths in source `<image href="...">` against the source SVG location and copy each unique asset; if the source already follows PPT Master conventions (assets co-located with SVGs in the same directory), copy the whole asset set and then rewrite paths - - Both scopes: write bitmaps to `<authoring_workspace>/images/`, point SVG references at `../images/<name>`, and keep non-bitmap template-source assets under `<authoring_workspace>/templates/`. -4. **Copies imported vector assets once** to `<authoring_workspace>/icons/imported/` and rewrites their placeholders to `<use data-icon="imported/<name>"/>`. Never place a second copy under `templates/icons/`. Other explicitly adopted icon-library references keep their existing library namespace. Do not inline these assets manually in the template working SVGs; template validation, preview, and final export all resolve icons from the workspace-root `icons/` directory. + - Both scopes: write raster, SVG, EMF, and WMF image media to `<authoring_workspace>/images/`, point SVG references at `../images/<name>`, and route audio, video, or opaque payloads to their semantic workspace directories. +4. **Copies decoration-only imported vector assets once** to `<authoring_workspace>/icons/imported/` and rewrites their placeholders to `<use data-icon="imported/<name>" data-pptx-asset-role="decoration"/>`. Semantic authoring objects stay inline. Never place a second copy under `templates/icons/`. Other explicitly adopted icon-library references keep their existing library namespace. Do not inline these assets manually in the template working SVGs; template validation, preview, and final export all resolve icons from the workspace-root `icons/` directory. 5. Writes `<design_spec_path>` per [template-designer.md](../references/template-designer.md) §1. The §V Page Roster remains a factual prototype index; explicit SVG metadata is the native Master/Layout contract. `replication_mode: mirror` records how the workspace was created and only makes literal downstream reuse technically possible; it never selects that behavior or forces a 1:1 slide sequence. -Mirror mode does not simplify the visual target or synthesize layer ownership. The sprite-sheet preservation rule applies because crop wrappers carry visible geometry; preserve those wrappers and their source scope faithfully. +Mirror mode may simplify SVG code but does not redesign the visual target or synthesize layer ownership. The sprite-sheet preservation rule applies because crop wrappers carry visible geometry; preserve their crop behavior and source scope faithfully. **Expected outputs from this step** (full spec → [template-designer.md](../references/template-designer.md)): @@ -694,7 +734,7 @@ becomes the prototype Slide placeholder, while **Create Brand branch**: run the child workflow's §4 checklist and the shared project-safe validator below in both scopes. It detects `kind: brand`, validates the identity-only frontmatter/sections/colors/provenance/asset references, and does not require an SVG roster or touch a global index. Any failure blocks completion. ```bash -python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_workspace>/templates" --template-mode +python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_workspace>/templates" --template-mode --canonical-authoring ``` In `library` scope, additionally run the registrar dry-run so `brand_id` is checked against the library directory/index key: @@ -712,7 +752,7 @@ and fallback values, portable ID, and one-file roster-free package boundary. The child checklist remains authoritative for semantic scope and provenance. ```bash -python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_workspace>/templates" --template-mode +python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_workspace>/templates" --template-mode --canonical-authoring ``` In `library` scope, additionally run the registrar dry-run so `style_id` is @@ -733,13 +773,14 @@ ls -la "<template_source>" ls -la "<authoring_workspace>/images" "<authoring_workspace>/icons" ``` -Compact safe page-space metadata and transform coordinates, then run SVG -validation on the template directory. Keep canonical authored-preset and native -record frames unchanged: +Run read-only SVG validation on the template directory. The Template Designer +must write canonical compact SVG directly; mirror materialization applies the +same normalization in memory before its first template write. Keep canonical +authored-preset and native record frames unchanged: ```bash -python3 skills/ppt-master/scripts/compact_svg_coordinates.py "<template_source>" --inplace --keep-native-frames -python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_source>" --template-mode --format <canvas_format> +python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_source>" \ + --template-mode --canonical-authoring --format <canvas_format> ``` `--template-mode` makes the checker: @@ -751,14 +792,14 @@ python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_source>" --t - require every SVG root to declare one output Master and Layout; zero-slot Layouts are valid - reject ordinary Master/Layout `<g>` elements, nested structure markers, missing slot bounds, and carrier-bound slots without exactly one compatible carrier; a validated compact canonical authored-preset `<g>` is the sole fixed-layer group exception and may be one `object` carrier - validate cross-page Master equality plus same-key Layout atom/slot equality -- warn when distinct Layout keys have identical static framing/slot contracts. Resolve this for `standard` / `fidelity`; mirror may retain the distinct source identities and records that fact in its Source Preservation Map +- warn when distinct Layout keys have identical static framing/slot contracts. Resolve this for `standard` / `fidelity`; mirror keeps distinct source identities and records them in its Source Preservation Map This checker validates the authoring contract, not the compiled OOXML package. Theme ownership, package IDs, and registered part relationships are verified by `template_preview_pptx.py` in Step 6. **Checklist**: - [ ] `<design_spec_path>` follows the kind-specific package skeleton: deck = descriptive Overview / Color / Signature / Page Roster plus conditional sections; layout = structure-owned Signature / Page Roster with no Overview, application contract, or identity sections. Generic constraints (SVG rules, pattern libraries, ratio bands, canonical placeholder table) are NOT restated. The source-derived basic norms are present as template-specific layout / image / density / asset rules, not generic advice. Deck Overview identifies recurring situations, audiences/outcomes, delivery assumptions, and representative narrative/page roles; §V Page Roster factually describes every emitted prototype without required/optional/repeatable or fixed/replaceable/example-only policy -- [ ] Every page declared in `<design_spec_path> §V Page Roster` exists as an SVG file in the template directory (and vice versa — no orphan files) +- [ ] Every SVG on disk is a complete Slide prototype with a §V roster row; when mirror omits unreferenced source identities, one §V scope sentence records that fact without per-identity analysis - [ ] Variant filenames follow the letter-suffix convention (e.g. `03a_content_two_col.svg`); variants typically reuse the parent type's placeholder set unless the spec frontmatter declares otherwise - [ ] If TOC exists, placeholder pattern uses the canonical indexed form - [ ] `<design_spec_path>` frontmatter declares `canvas_format`, `canvas_width`, `canvas_height`, and `canvas_viewbox`; PPTX/SVG-backed templates also declare `source_canvas_width`, `source_canvas_height`, and `source_viewbox` @@ -766,22 +807,22 @@ This checker validates the authoring contract, not the compiled OOXML package. T - [ ] Model-facing placeholder bounds and transform page coordinates use at most two decimals; normalized crop/viewBox ratios, path geometry, transform scale/rotation coefficients, authored-preset frames, and tool-side native frames retain their required precision - [ ] Placeholder names follow the canonical convention where applicable; templates with intentionally different vocabularies (e.g. `{{KEY_MESSAGE}}` instead of `{{PAGE_TITLE}}`) should declare a `placeholders:` frontmatter block to silence advisory warnings - [ ] Asset files referenced by SVGs exist at their resolved paths. In both scopes, bitmap references resolve through `../images/`; no bitmap remains accidentally stranded in `templates/` -- [ ] `<design_spec_path>` frontmatter declares `native_structure_mode: structured`; no `native_structure.json` or `source_template.pptx` is packaged +- [ ] `<design_spec_path>` frontmatter declares `native_structure_mode: structured`; no `analysis/native_structure.json` or `sources/source.pptx` is packaged - [ ] Every SVG root declares Master/Layout key and picker names; Master/Layout visuals are direct semantic atoms and obey the explicit paint-order contract. Ordinary `<g>` elements remain forbidden there; a validated helper-generated compact canonical preset `<g>` is the sole group exception because it compiles to one native shape. Structural `data-pptx-role` is used only when specialized metadata cannot express required package/page-number/animation behavior - [ ] Every slot is a direct `<g id>` with explicit design-zone bounds and exactly one compatible direct carrier, or an explicit composite `object` proxy. A validated compact canonical preset `<g>` may be the one carrier of an `object` slot; an ordinary multi-object group may not. Zero-slot Layouts remain valid - [ ] For `standard` / `fidelity`, every placeholder bound is the complete editable box rather than the current marker text's tight bounds; general body/object carriers begin at the upper-left and only intentional short focal roles remain centered - [ ] In review output, authored placeholder prompts remain readable; `template_preview_pptx.py` uses preview-only sample text and leaves every canonical source marker and carrier style unchanged - [ ] `standard` / `fidelity` output SVGs and their Master/Layout/slot contracts were newly authored without preserving or distilling source topology - [ ] Every additional authored Master represents a distinct reusable design family, not one Layout or an equivalent duplicate; every declared Master owns at least one emitted Layout and every declared Layout has at least one emitted prototype -- [ ] Mirror output preserves source slide order, Master/Layout identity and parentage, placeholder facts, and ownership; fixed-layer group expansion is mechanical and pixel-equivalent, and the Source Preservation Map lists every source slide +- [ ] Mirror output preserves source slide order, the full identity and parentage of every retained Master/Layout, placeholder facts, ownership, source meaning, and similar presentation; SVG code/node identity is not required, and the Source Preservation Map lists every source slide - [ ] Mirror materialization wrote one compact `ppt-master.template-execution-manifest.v1` roster and one linked `ppt-master.template-text-slots.v2-min` diagnostic sidecar per prototype; each slot has only `selector`, `role`, `current_text`, `text_segments`, and `tspan_count`; neither artifact is injected into page authoring, while validation/export check the complete prototype - [ ] Mirror roots preserve source inherited-shape visibility with canonical lowercase `data-pptx-show-master-shapes` and `data-pptx-show-inherited-shapes`; same-key Layouts agree on the former, while each Slide retains its own latter value -- [ ] Mirror preflight covered the complete source graph; each unused Layout has one `layout_<layout_key>.svg` definition prototype and each otherwise-unused Master is retained through at least one such Layout +- [ ] Mirror preflight covered every source Slide and its reachable Layout/Master chain; unreferenced source identities produced no SVG and are reported as omitted - [ ] For `standard` / `fidelity`, no duplicate-Layout-contract warning remains; mirror may keep equivalent source Layout identities when the preservation map explains them -- [ ] All template-creation edits used the authoring IR; Type A mirror used `mirror_template_materialize.py`, validated its manifest/hash/graph/source-ref closure before atomic publication, reused only converter-supported payload for hash-matching Slide-local/slot refs, deduplicated supported opaque payload and repeated native restoration attributes into `templates/native_payloads.json.gz`, stripped IR-only source-ref metadata, and kept fixed Master/Layout visuals as direct atoms -- [ ] If any SVG references an extracted vector, it uses `data-icon="imported/<name>"` and the sole SVG asset exists at `<authoring_workspace>/icons/imported/<name>.svg`; `templates/icons/` does not exist and no separate illustration embedding script was added +- [ ] All template-creation edits used the compact authoring SVG; Type A mirror used `mirror_template_materialize.py` only after Template_Designer review, validated source SHA/known refs/manifest/graph/assignment closure before atomic publication, published the current visible tree without lossless subtree rehydration, recovered only supported non-visible semantics, kept authoritative Chart/Table JSON inline, deduplicated supported opaque payload and repeated native restoration attributes into `templates/native_payloads.json.gz`, stripped IR-only source refs, and kept fixed Master/Layout visuals as direct atoms +- [ ] If any SVG references an extracted vector, it uses `data-icon="imported/<name>" data-pptx-asset-role="decoration"`; the v2 inventory and sole SVG asset under `<authoring_workspace>/icons/imported/` declare the same role; no semantic object or descendant is externalized, `templates/icons/` does not exist, and no separate illustration embedding script was added - [ ] For `fidelity` mode: every sprite-sheet asset retains its nested `<svg viewBox=...>` crop wrapper; no image whose file aspect differs from its on-page aspect was flattened to a bare `<image>` -- [ ] For `mirror` mode: source-page SVG count equals source page count, while additional files are exactly the required `layout_<layout_key>.svg` definitions for unused source Layouts; source-page filenames follow the `<NNN>_<page_type>.svg` convention; **no new `{{...}}` authoring placeholders were inserted into materialized source-page SVGs**; §V Page Roster lists every emitted file and marks definition-only prototypes explicitly +- [ ] For `mirror` mode: SVG count equals source Slide count, filenames follow `<NNN>_<page_type>.svg`, no standalone Master/Layout SVG exists, and **no new `{{...}}` authoring placeholders were inserted**; §V lists every emitted Slide prototype This step is a **hard gate**. Do not generate a review PPTX, register, install a staged structural transition, or hand the workspace to the main pipeline until validation passes. After a staged project install, rerun the checker on the final `<target_project>/templates/`; it validates the effective Layout roster when Layout and Deck coexist. A one-Master template may skip Step 6 when no review was requested; a multi-Master template must continue to Step 6 and may not register or complete before that package gate passes. @@ -804,6 +845,17 @@ creates `exports/` on demand. Copy a requested/required successful review artifact into the target project's `exports/` during the same atomic install. The script consumes `templates/*.svg` directly, compiles the declared structured Master/Layout contract, and reopens the result. For `standard` / `fidelity`, it uses ephemeral SVG copies with concise preview-only placeholder samples so long `{{...}}` markers stay readable; canonical source SVGs and placeholder semantics are not modified. It does not require a project `spec_lock.md`, does not create a persistent intermediate project, and does not infer or distill structure. +The default review keeps visible SVG Chart/Table fallbacks. Marker presence or +template origin never activates native replacement. When JSON-first capability +must also be verified, write a second, distinctly named review explicitly: + +```bash +python3 skills/ppt-master/scripts/template_preview_pptx.py \ + "<authoring_workspace>" \ + --native-charts-and-tables \ + -o "<authoring_workspace>/exports/<template_id>_template_preview_native.pptx" +``` + The first export refuses an existing output. After intentionally fixing the template and replacing its prior review deck, rerun with `--force`; never rely on a silent overwrite: ```bash @@ -836,7 +888,7 @@ Branch on the confirmed output scope: | `library` | Run the registrar below after Step 5 passes and Step 6 also passes whenever it was requested or required by a multi-Master roster | | `project` | Skip the registrar entirely. Do not edit any global template index or library README; continue to Step 8 with index status `Not registered (project workspace)` | -Run the unified registrar with the kind flag; it derives the corresponding index entry from `templates/design_spec.md` (frontmatter when present, prose fallback otherwise) plus the actual `templates/*.svg` file list. The registrar retains read compatibility with old flat library packages; new creation never writes that shape: +Run the unified registrar with the kind flag; it derives the corresponding index entry from `templates/design_spec.md` (frontmatter when present, prose fallback otherwise) plus the actual `templates/*.svg` file list. The workspace must already use the current nested contract: ```bash # For brand @@ -993,11 +1045,9 @@ Brand/Layout/Deck copy or consume package-owned `templates/` plus any existing `images/` and `icons/`; Style consumes only its own spec and ignores sibling project scaffolding. It then authors new `svg_output/` pages under the template contract and exports a new PPTX. Neither the reference -PPTX/SVG nor the template prototypes are upgraded in place. A legacy-flat -Brand/Layout/Deck package root remains readable only when it satisfies its -current kind contract; for Layout/Deck that includes the structured SVG -contract. Style has no legacy-flat form. Otherwise create a new workspace -through this route. +PPTX/SVG nor the template prototypes are upgraded in place. Any older flat or +semantic-legacy package is input evidence only; create a new current workspace +through this route before it can be selected by Generate. --- diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-brand.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-brand.md index abbbee33..11cf07f6 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-brand.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-brand.md @@ -143,7 +143,7 @@ Return these facts to Create Template: For both scopes, Create Template Step 5 validates the portable Brand contract without registration: ```bash -python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_workspace>/templates" --template-mode +python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_workspace>/templates" --template-mode --canonical-authoring ``` For `library` scope, additionally validate the directory/index identity with: diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-deck.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-deck.md index b2edd15e..25d780d4 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-deck.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-deck.md @@ -120,10 +120,15 @@ In addition to Create Template Steps 5–6, verify: - Every SVG in the roster satisfies the shared Master/Layout/slot contract and the roster is bidirectionally complete. - Every referenced image/icon exists under the same workspace; this workflow created no optional directory solely to leave it empty. Pre-existing initialized-project scaffolding is allowed and remains untouched. -For library scope, Create Template validates and registers with: +For library scope, Create Template Step 5 validates the directory/index identity with: ```bash python3 skills/ppt-master/scripts/register_template.py <deck_id> --kind deck --dry-run +``` + +After that gate and any triggered Create Template Step 6 pass, Step 7 registers with: + +```bash python3 skills/ppt-master/scripts/register_template.py <deck_id> --kind deck ``` diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-layout.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-layout.md index f914e750..015256c9 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-layout.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-layout.md @@ -33,7 +33,7 @@ Use Create Template Step 1 for source ingestion and internal creation-strategy f - Colors, font families, branded weight choices, final absolute sizes, logos, voice, and icon style remain source context only. Do not copy them into the layout identity because a layout has no identity segment. - A source scenario may inform the content shapes or delivery conditions the geometry can support. Do not turn that fit into an application contract. If the reusable artifact prescribes the objective, outcome, narrative sequence, boilerplate, or content policy, return to Create Template dispatch and select Create Deck. - When the source is branded, state in plain language that Create Layout will omit the identity. The AI therefore derives an authored internal strategy. If the user wants the identity retained with the structure, return to Create Template dispatch and select Create Deck before the shared confirmation marker is emitted. -- Internally, `standard` and `fidelity` author a new Master/Layout/slot system. `mirror` may be derived only when the complete current source contract is already brand-neutral and application-neutral; it preserves validated structure and visual facts in a new workspace without modifying the source. Never ask the user to choose among these labels. +- Internally, `standard` and `fidelity` inspect the complete source inventory and author a new Master/Layout/slot system. `mirror` may be derived only when the source-Slide-reachable contract is complete, brand-neutral, and application-neutral; it preserves that reachable structure and its visual facts in a new workspace without modifying the source. Never ask the user to choose among these labels. Direct conversation text, pasted requirements, converted documents/websites, images, and supplied assets may define or illustrate reusable structure. In a mixed bundle, combine those channels without treating identity-only evidence as layout ownership. Exact user-authored instructions remain decisions whether they arrive in chat or a user-written brief file; vague prose remains suggested interpretation until the shared confirmation gate. @@ -113,10 +113,15 @@ In addition to Create Template Steps 5–6, verify: - Neutral prototype paint is not described as a locked brand identity. - `replication_mode: mirror` is rejected for any source that retains organization-specific identity or reusable application rules; use authored Layout mode or Create Deck instead. -For library scope, Create Template validates and registers with: +For library scope, Create Template Step 5 validates the directory/index identity with: ```bash python3 skills/ppt-master/scripts/register_template.py <layout_id> --kind layout --dry-run +``` + +After that gate and any triggered Create Template Step 6 pass, Step 7 registers with: + +```bash python3 skills/ppt-master/scripts/register_template.py <layout_id> --kind layout ``` diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-style.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-style.md index 863c89f7..5c712eea 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-style.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/create-template/create-style.md @@ -175,7 +175,7 @@ Return these facts to Create Template: For both scopes, Create Template Step 5 validates the portable Style contract without registration: ```bash -python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_workspace>/templates" --template-mode +python3 skills/ppt-master/scripts/svg_quality_checker.py "<template_workspace>/templates" --template-mode --canonical-authoring ``` For `library` scope, additionally validate the directory/index identity with: diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/edit-native-pptx.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/edit-native-pptx.md new file mode 100644 index 00000000..f149705e --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/edit-native-pptx.md @@ -0,0 +1,282 @@ +--- +description: Edit Native PPTX route — import a finished PowerPoint deck into a source-preserving SVG workspace, reference unchanged pages, edit or recompose selected pages, add notes/narration/motion, and export a new PPTX +--- + +# Edit Native PPTX Route + +> Run when the user brings an existing `.pptx` whose design must survive — as a template to fill with new content, as a deck to partially rewrite or restructure, or as a finished deck that only needs notes, narration, timings, or transitions. This route never regenerates the deck from scratch and never runs the Generate SVG pipeline. + +The source deck becomes a `pptx_to_svg.py --roundtrip` workspace. Every source slide is available as a compact editable SVG plus immutable native backing. Pages the plan leaves untouched are **referenced**: export restores their original slide XML byte-for-byte. Pages the plan edits are rebuilt only where they changed; every unchanged object on them restores its native form. Notes, narration audio, and motion are overlays on the preserved slide and never rewrite visible content. + +**Boundary against other routes**: + +| User wants | Route | +|---|---| +| Fill a raw PPTX template with new content, keep its design | This route | +| Keep a finished PPTX, add speaker notes / narration / auto-advance / transitions | This route | +| Rewrite some pages of a finished PPTX, keep the rest exactly | This route | +| Drop, reorder, or repeat pages of an existing PPTX without redesign | This route | +| Regenerate every page with a new visual design (beautify, 1:1) | Generate PPTX, [`beautify-pptx`](./profiles/beautify-pptx.md) | +| Split / merge / re-outline an existing PPTX into a new deck | Generate PPTX, PPTX as source material | +| Create a reusable brand / style / layout / deck asset from the PPTX | [`create-template`](./create-template.md) | + +**Hard rule — no Generate pipeline**: Do not run `pptx_template_import.py`, `project_manager.py init`, `finalize_svg.py`, or create `svg_output/` for this route. The round-trip workspace is the project; `svg_to_pptx.py --roundtrip` is the only exporter that restores source slides. + +--- + +## 1. When to Run + +| Pattern | Example | +|---|---| +| Raw PPTX called a template + new material or topic | "Use this PowerPoint template to make a deck about X" | +| Existing deck + selective reuse | "Only keep the pages that fit, in this order" | +| Existing deck + copy replacement | "Keep the design, swap in this text" | +| Existing deck + page-level rewrite | "Redo page 5 and 7, leave everything else" | +| Existing deck + page combination | "Merge the two market pages into one, drop the old chart, add the new KPI" | +| Existing deck as skeleton + a few new pages | "Keep the deck, add a summary page and a Q3 page in the same style" | +| Finished deck + delivery add-ons, visible slides stable | "Add narration and auto-play", "add fade transitions", "write speaker notes" | + +**Deterministic routing**: Do not ask a route-choice question for these shapes. Ask one discriminator only when the request is ambiguous between preserving pages (this route) and redesigning them (Generate). + +--- + +## 2. Inputs + +🚧 **GATE**: The user has provided: + +| Input | Required | Notes | +|---|---:|---| +| Source PPTX | Yes | Finished deck or template; the native design authority | +| New material | Only when content changes | Text, Markdown, documents, or URLs converted with `source_to_md.py`. A bare topic without facts is not enough: ask for material, or gather it from user-approved URLs before planning | +| Delivery intent | Optional | Audience, page count, must-keep / must-drop pages, notes, narration, transitions, auto-advance | + +**Hard rule — facts**: Every substantive claim written into a page or a note comes from the user material; the §4.3 content mapping names the source for each page, and a page without one is dropped. Placeholder wording in the template is never carried into the output as content. + +--- + +## 3. Import the Round-trip Workspace + +Create the workspace directly under `projects/`; it is the project. + +```bash +python3 skills/ppt-master/scripts/pptx_to_svg.py "<source.pptx>" \ + -o "projects/<slug>_<YYYYMMDD>" --inheritance-mode both --roundtrip +``` + +| Path | Content | Reading rule | +|---|---|---| +| `authoring-svg-flat/slide_NN.svg` | One compact editable SVG per source slide, in source order | Open only the pages you will edit or need to judge for reuse | +| `authoring-svg-flat/authoring_summary.json` | Roster plus per-page canvas, text, image, vector, placeholder, source-ref, and source-proxy counts | Read first; plan from it before opening any SVG | +| `images/`, `icons/imported/`, `audio/`, `video/`, `sounds/` | Source media and imported decorative vectors | Keep names. Export hashes materialized files; changed bytes rebuild every output page whose source Slide/Layout/Master/notes graph references that package part, and a format mismatch fails export | +| `notes/slide_NN.md` | Source speaker notes when present | Edit, delete, or add per output page (§6) | +| `native-payloads/`, `analysis/` | Immutable native backing and tool-owned contracts | Tool-owned; opening them costs context and changes nothing — do not read, edit, or quote | +| `sources/source.pptx` | Exact source package | Read only through `ppt_to_md.py "<workspace>/sources/source.pptx" -o "<workspace>/validation/source_readback.md"` when you need page text without opening every SVG (notes on unchanged pages, content mapping) | +| `validation/`, `exports/` | Diagnostics and published decks | Tool-written | + +**Hard rule — source proxies are atomic**: An `<image data-pptx-source-proxy="native-restore">` element stands for an unsupported native object (SmartArt, complex effects, media frames). Leave it unchanged to restore the original object; a Slide-local proxy may be deleted; an inherited Master/Layout proxy stays. Editing a proxy or its preview asset fails export. + +--- + +## 4. Plan the Output Deck + +**Default — layout-first selection (may override when the user fixes the page mapping)**: Treat the roster as a slide library, not an outline. A source page's layout already encodes a rhetorical shape — hero statement, lead-then-detail, comparison, stepwise progression, metric row, dense explanation. Match each target message to a page whose structure expresses that same logic; drop the content or the page rather than force a fit. Use fewer pages than the source when that reads better; repeat one good layout for several messages when they share its pattern. + +**Default — source order is not the outline (may override when the user asks to preserve it)**: The target story controls output order. Source slides may move, be omitted, or be reused several times. + +**Default — skeleton first (may override when the user asks for new pages)**: The source deck is the reference material and the skeleton of the output. Most output pages keep a source page's structure; sub-content may be recombined freely across pages, and new pages are added where the story needs them rather than as a rule. + +### 4.1 Page plan + +Write `page_plan.json` at the workspace root only when the output differs from the source roster (subset, reorder, repeat, or a copied page). Without the file, export is the identity round trip and every page is referenced or edited in place. + +```json +{ + "schema": "ppt-master.roundtrip-page-plan.v1", + "pages": [ + {"source_slide": 1}, + {"source_slide": 4, "svg": "chapter_market.svg"}, + {"source_slide": 7}, + {"source_slide": 7, "svg": "kpi_second_half.svg"}, + {"source_slide": 12} + ] +} +``` + +| Field | Rule | +|---|---| +| `pages` | Complete output order; non-empty | +| `source_slide` | One-based source index of the page whose native slide backs this output page | +| `svg` | Authoring filename inside `authoring-svg-flat/`; omit to use that source page's `slide_NN.svg`. To reuse one source page twice, copy its SVG to a new name (`cp slide_07.svg kpi_second_half.svg`) and list the copy — every output page needs a distinct file, and every extra file must appear in the plan | + +Only `schema` and `pages` at the root and `source_slide` / `svg` per page are accepted; the exporter rejects any other field. + +**Forbidden — plans the exporter refuses** (fail-closed, fix the plan instead of forcing): +- A same-deck slide jump whose destination is omitted or repeated (include the target exactly once, or remove the link from the page) +- Unknown, duplicated, or cross-owned `svg` filenames; `source_slide` out of range + +Omitting a source slide deliberately drops the audio, video, or undecodable payloads only that slide owns; export prints a note listing them. + +**Combining pages**: One output page always has exactly one skeleton — its `source_slide`. To merge several source pages, pick the page whose layout carries the result as the skeleton, then copy the needed elements from the other pages' SVGs into it and delete what the merged page no longer needs. Bring an object across pages only through the adopt command below — never by pasting raw SVG, because source refs are page-local and a pasted object would be mistaken for one of the skeleton's own. The adopted object keeps its visual form by materializing effective inherited presentation attributes (including `font-family`, `fill`, `opacity`, and CSS-resolved values) and composing ancestor transforms onto the copy. It loses its native identity and is rebuilt from SVG, so the combined page counts as `rebuilt`. A source proxy (§3) cannot leave its own page; a merge that needs one keeps that page as the skeleton instead. + +```bash +python3 skills/ppt-master/scripts/svg_authoring_view.py \ + "projects/<slug>_<YYYYMMDD>/authoring-svg-flat" \ + --adopt-object slide_05.svg:<element-id> --into chapter_market.svg +``` + +The object lands at the end of the target page; then edit its position and content like any authored element. + +**New pages**: A brand-new page also needs a skeleton so it inherits the deck's Master/Layout (background, logo, page number). Copy the closest source page under a new name, list it in the plan with that page's `source_slide`, delete its Slide-local content, and author the new content on the empty canvas; inherited proxies stay. The page counts as `rebuilt`. + +> Note: with a plan present, presentation-level sections and custom shows are dropped and slide ids are renumbered; visible slides are unaffected. + +### 4.2 Enhancement modules + +| Module | Default | Carrier | +|---|---|---| +| Speaker notes | Source notes travel with every page; add or rewrite only where the plan says | `notes/<svg-stem>.md` | +| Narration audio | Off unless requested; implies notes on every output page | [`generate-audio`](./stages/generate-audio.md) → `audio/<stem>.*` | +| Auto-advance from narration | On when narration is requested | `svg_to_pptx.py --use-narration-timings` | +| Page transitions | Preserve source; replace only on request | `svg_to_pptx.py -t <effect>` or per-slide rows in `animations.json` | +| Object animations | Preserve source; author only on explicit request | `animations.json`, see [`animations.md`](../references/animations.md) | +| Native chart / table data | Source data unless the plan edits it | Inline JSON authority on the page; export needs `--native-charts-and-tables` (§7) | + +### 4.3 Confirmation + +⛔ **BLOCKING**: Present one plan and wait for explicit confirmation before editing any SVG, writing notes, generating audio, or exporting: + +| Item | Show | +|---|---| +| Output roster | Ordered list: output page → source slide → referenced unchanged / edited / new copy, with a one-line reason for each edited or dropped page | +| Content mapping | Which material goes to which page; anything dropped for lack of a fitting layout | +| Enhancement modules | Each module on/off with its effect and duration where relevant | +| Known refusals | Any §4.1 fail-closed case the plan must avoid | + +Chat confirmation is sufficient; write `page_plan.json` after confirmation. + +--- + +## 5. Edit Pages + +Load [`shared-standards-core.md`](../references/shared-standards-core.md) before the first edit. Load [`svg-effects.md`](../references/svg-effects.md) only when authoring new visual elements, and [`native-data-interface.md`](../references/native-data-interface.md) only when changing native chart or table data. + +**Hard rule — edit only planned pages**: A page marked referenced is not opened for writing. Export proves it: a referenced page appears under `passthrough` / `cloned_passthrough` (no overlay) or `patched` (notes or motion overlay only) in the receipt (§7) — never under `rebuilt`. + +**Hard rule — edit in place, keep identity**: Change text, paint, position, or content inside the existing page tree. Keep every `data-pptx-*` attribute on objects you did not intend to change; an object whose source attributes survive is restored natively, an object you rewrote is converted from your SVG. Do not paste a page from `svg_output/` conventions or another deck over a round-trip page. + +| Edit | Rule | +|---|---| +| Text replacement | Fit the slot's visual capacity from its geometry and font size, not the old placeholder length. Resolve overflow in this order: rewrite shorter → split across another selected page → choose a larger source layout; shrinking type is last and never deck-wide. §5's capacity gate rejects text that leaves its frame | +| Cover / chapter pages | Replace title, subtitle, author, section label only | +| Dense content pages | Compress material to the slot count the page already has; move overflow to another selected page | +| Native tables | Imported tables carry `data-pptx-native-authority="json"`; edit cell text in that inline `ppt-master.semantic-table.v2` JSON and keep row/column structure unless the design calls for a different table. Export with `--native-charts-and-tables` (§7) — without it the stale preview ships | +| Native charts | Imported charts carry the same JSON authority; edit categories and series values there and leave chart type and formatting to the source. Same export flag | +| Images | Replace by pointing the existing `<image>` at a new file under `images/`; keep the frame | +| New elements | Author canonical compact SVG per shared standards; icons come from `icon_sync.py "<workspace>" <lib/name>`; AI images from `image_gen.py --manifest` when the user wants generated visuals | +| Objects from another page | Use `--adopt-object` (§4.1); it strips source identity, inlines cross-page vector assets, and keeps chart/table JSON authority. Never paste raw SVG across pages; proxies cannot move | +| Source proxies | Leave or delete; never edit (§3) | + +**Mandatory after editing** — refresh the summary (page-plan copies are accepted), then run the capacity gate: + +```bash +python3 skills/ppt-master/scripts/svg_authoring_view.py \ + "projects/<slug>_<YYYYMMDD>/authoring-svg-flat" --refresh-summary +python3 skills/ppt-master/scripts/svg_quality_checker.py "projects/<slug>_<YYYYMMDD>" --roundtrip +``` + +🚧 **GATE**: The checker's `--roundtrip` mode estimates edited text against its frame and canvas. Errors block export until the text is rewritten, split, or moved to a larger layout; warnings are reviewed and either fixed or accepted with a stated reason. The exporter remains the final gate and fails closed on a page it cannot restore or convert. + +--- + +## 6. Notes, Narration, and Motion + +Skip this section when no module in §4.2 is enabled beyond preserving source notes. + +**Notes** — keyed by output SVG stem: + +| Case | Behavior | +|---|---| +| `notes/<stem>.md` exists for a canonical page | Replaces source notes; delete the file to remove source notes | +| `notes/<stem>.md` exists for a copied page | Notes for that output page only | +| No file for a copied page | Inherits the source page's notes | + +**Hard rule — spoken prose only**: `svg_to_pptx.py` embeds each note verbatim and `notes_to_audio.py` reads it aloud verbatim, so a heading, bullet, `[tag]`, or duration line is spoken and shown. Write 2–5 natural sentences per content page, one or two for cover / chapter / ending, transitions as prose, one language per deck. Source the content from the page's SVG text or the §3 read-back plus the user material; a note never adds a claim the page or material does not carry. + +**Narration audio**: Run [`generate-audio`](./stages/generate-audio.md) Steps 1–4 with the workspace path after notes are complete (`notes_to_audio.py "<workspace>" --provider <p> --voice <v> --rate <r>`); the source deck's own media in `audio/` (imported files not named after a page) is left alone. `notes_to_audio.py` resolves the roster from `page_plan.json` (copies inherit source notes) and refuses an incomplete roster, listing the missing stems. Audio lands at `audio/<stem>.*` per output page. Stop after audio generation; §7 integrates it. + +**Motion**: Load [`animations.md`](../references/animations.md) when transitions or object animations are requested. `animations.json` rows are keyed by output SVG stem; a copied page inherits its source row unless it has its own. + +**Hard rule — rebuilt animation targets**: Rebuilding an object that a source animation targets (for example a chart whose data you edited) leaves that animation without a target, and export stops with `Edited slide removed source animation target(s)`. Give that page its own row so its motion becomes explicit — `"<stem>": {"animation": {"effect": "none"}}` drops the source build, or author the page's animation in the row — then export again. + +--- + +## 7. Export and Validate + +```bash +python3 skills/ppt-master/scripts/svg_to_pptx.py "projects/<slug>_<YYYYMMDD>" --roundtrip +``` + +| Request | Add | +|---|---| +| Replace transitions deck-wide | `-t <effect> [--transition-duration <s>]` | +| Narration with auto-advance | `--recorded-narration audio --use-narration-timings` (round-trip export reads the workspace `animations.json` by default) | +| Per-slide motion | `--animation-config animations.json` | +| Object animation policy | `-a <preset>` (default `none`) | +| Strip all notes | `--no-notes` | +| Native chart / table data edited | `--native-charts-and-tables` | + +Export writes into `exports/` and prints the exact output path (a `_narrated` or `_native_charts_tables` suffix may apply — use the printed path in every later command) plus one receipt: + +```text +Round-trip export summary: output_pages=N passthrough=P cloned_passthrough=C patched=M rebuilt=R +``` + +| Bucket | Meaning | Assert | +|---|---|---| +| `passthrough` | Identity page, original XML and relationships | Referenced pages without a plan and without any notes/motion overlay | +| `cloned_passthrough` | Planned page, original XML on a cloned part | Referenced pages with a plan and without any overlay | +| `patched` | Source shape XML is kept while shape order, notes, transitions, animation, or narration timing may change | Pages with z-order-only edits or package overlays that do not rebuild a source shape | +| `rebuilt` | Visible authoring or a referenced materialized resource changed | Exactly the pages marked edited in §4.3 plus every output page that references a changed resource — a delivery-only job must show `rebuilt=0` | + +**Validation**: + +```bash +python3 skills/ppt-master/scripts/pptx_delivery_check.py "<printed_output.pptx>" \ + > "projects/<slug>_<YYYYMMDD>/validation/<output_stem>.delivery.json" +python3 skills/ppt-master/scripts/source_to_md/ppt_to_md.py \ + "<printed_output.pptx>" -o "projects/<slug>_<YYYYMMDD>/validation/readback.md" +``` + +| Check | Expected | +|---|---| +| Delivery check | No structural errors; review advisories | +| Slide count | Equals plan length, or source count without a plan | +| Key titles and replaced text | Present in the read-back | +| Notes count | Matches planned notes | +| Receipt buckets | Match the confirmed roster | + +```markdown +## ✅ Edit Native PPTX Complete + +- [x] Round-trip workspace imported at `projects/<slug>_<YYYYMMDD>/` +- [x] Plan confirmed by the user; `page_plan.json` written when the roster differs from the source +- [x] Only planned pages edited; `authoring_summary.json` refreshed; `svg_quality_checker.py --roundtrip` reports no errors +- [x] Notes / audio / motion prepared as confirmed +- [x] `svg_to_pptx.py --roundtrip` receipt matches the confirmed roster +- [x] Delivery JSON and read-back written under `validation/` +- [x] Final deck at the exporter's printed `exports/` path +``` + +--- + +## 8. Current Boundary + +| Capability | Status | +|---|---| +| Reference unchanged pages byte-for-byte; select / reorder / repeat / omit pages | Supported | +| Edit text, paint, images, native table cells, native chart data on selected pages | Supported; unchanged objects restore natively; chart/table data edits export only with `--native-charts-and-tables` | +| Author new elements on an edited page | Supported through canonical compact SVG | +| Preserve SmartArt, complex effects, embedded media | Supported as atomic source proxies; not editable | +| Notes, narration audio, auto-advance, transitions, object animations | Supported as overlays keyed by output page | +| Delete inherited source notes on a copied page | Not supported; give the copy its own `notes/<stem>.md` | +| Edit a source proxy, change slide size, add Master/Layout structure | Not supported; use Create Template → Generate for a new structure | diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/generate-pptx.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/generate-pptx.md index 701b5bc0..4784483f 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/generate-pptx.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/generate-pptx.md @@ -17,7 +17,7 @@ call. Never change CWD or inherit a prior shell working directory. **Generate-specific execution discipline**: - The current main agent hand-writes every SVG page; never delegate page generation or run a Python, Node, or shell generator over `svg_output/`. -- Initial SVG cadence: P01 → first-page gate → uninterrupted remaining pages → final gate. Grouped batches and mid-run checker calls are forbidden. +- Initial SVG cadence: P01 → first-page gate → remaining pages (one page gate per first-exercised `not-exercised` item) → final gate. Batches and other mid-run checker calls are forbidden. - `preset_shape_svg.py` and `shape_boolean_svg.py` may provide only their documented stdout fragment(s) after the main agent chooses the object's role, operands, paint, and z-order; neither helper chooses layout or writes a page. - Gate checklists are internal verification, not user-facing output. On success, continue automatically and emit at most one compact status line when useful; on failure, report only the blocking items and required recovery. @@ -32,9 +32,9 @@ request does not explicitly select Quick. | Any route that authors or regenerates slide visuals through SVG | `svg_output/` is the complete page-design source: every visible text, image, shape, chart/table fallback, block/inline native-formula preview, and layout element that should appear on the exported slide is present in that page SVG or referenced by it. | | Templates, `design_spec.md`, and `spec_lock.md` | Authoring/control inputs. They guide SVG creation but MUST NOT supply visible slide content that is absent from the completed SVG during export. | | Semantic SVG markers | Minimal rendering-neutral compiler hints used only after existing Layout/Layer/Placeholder/Native metadata has been considered. Chart/table markers preserve their visible SVG fallback; block and inline formula markers carry exact LaTeX and replace only their registered ordinary SVG preview with editable Office Math during PPTX export. | -| `svg_final/` | Mandatory derived, self-contained SVG visual preview in the default pipeline. It may be opened directly or inserted into PowerPoint as an SVG picture, but it is not a supported PPTX source and carries no manual Convert-to-Shape compatibility contract. Quick-generate skips it. | +| `svg_final/` | Optional derived, self-contained SVG visual preview in the default pipeline; release export never reads it. It may be opened directly or inserted into PowerPoint as an SVG picture, but it is not a supported PPTX source and carries no manual Convert-to-Shape compatibility contract. Quick-generate skips it. | | SVG-to-PPTX export | The only supported generated-PPTX route reads `svg_output/` and maps its content through the project converter to DrawingML/native objects. It compiles only the selected route's explicit structure contract: `flat` keeps represented content Slide-local, while `structured` may place explicitly scoped content in Master/Layout/Slide parts. It MUST NOT infer structure, upgrade `flat`, or invent new visible page content. | -| Native PPTX routes and presentation-behavior stages | Remain outside SVG page-design closure. `template-fill-pptx`, `native-enhance-pptx`, animations, transitions, speaker notes, narration, and package relationships are not required to round-trip through SVG. | +| Edit Native PPTX and presentation-behavior stages | Remain outside SVG page-design closure. `edit-native-pptx` restores unchanged source slides natively; animations, transitions, speaker notes, narration, and package relationships are not required to round-trip through SVG. | **MUST — page-design closure**: For an SVG-authoring route, inspect the final page SVG to determine what the exported slide looks like. Do not reinterpret “SVG is the page-design language” as “SVG is the complete PPTX package description language.” @@ -157,7 +157,7 @@ still determine stage and readiness. The transcript is cold audit evidence: never read it during normal generation; open it only when the user explicitly asks to review the run. -Format options must be named with concrete dimensions. Default: `ppt169` = `1280x720`, `viewBox="0 0 1280 720"`. Other examples: `ppt43` = `1024x768`, `story` = `1080x1920`, `banner` = `1920x1080`. For the full format list, see `references/canvas-formats.md`. +Registered formats: [`canvas-formats.md`](../references/canvas-formats.md). Import source content (choose based on the situation): @@ -176,15 +176,15 @@ For PPTX sources, `import-sources` automatically runs the standard intake enrich python3 ${SKILL_DIR}/scripts/pptx_intake.py <project_path>/sources/<source.pptx> -o <project_path>/analysis ``` -For each PPTX it writes `<stem>.identity.json` (canvas, theme palette/fonts, observed usage) and `<stem>.slide_library.json` (text slots, geometry, native tables, native chart caches, SmartArt nodes/connections), and merges that deck's Strategist-facing digest into the single multi-deck index `analysis/source_profile.json` (`decks[]`, one self-contained entry per source deck, with prefixed artifact pointers). In the main generation path these are source facts and recommendation candidates, not replica constraints; the beautify profile and Fill Native PPTX route decide separately which fields become locked constraints. +For each PPTX it writes `<stem>.identity.json` (canvas, theme palette/fonts, observed usage) and `<stem>.slide_library.json` (text slots, geometry, native tables, native chart caches, SmartArt nodes/connections), and merges that deck's Strategist-facing digest into the single multi-deck index `analysis/source_profile.json` (`decks[]`, one self-contained entry per source deck, with prefixed artifact pointers). In the main generation path these are source facts and recommendation candidates, not replica constraints; the beautify profile decides separately which fields become locked constraints. -Multi-deck: several PPTX files may be imported into one main-pipeline project — each gets its own `<stem>.*` artifacts and a deck entry in `source_profile.json`. `source_profile.json` stays the single must-read index (one entry for a one-deck project, several for a combined-source project). Stems must be distinct; re-importing the same stem replaces that deck's entry. The beautify profile and Fill Native PPTX route remain single-deck (1:1 to one chosen source deck) and read that deck's `<stem>.*` artifacts. +Multi-deck: several PPTX files may be imported into one main-pipeline project — each gets its own `<stem>.*` artifacts and a deck entry in `source_profile.json`. `source_profile.json` stays the single must-read index (one entry for a one-deck project, several for a combined-source project). Stems must be distinct; re-importing the same stem replaces that deck's entry. The beautify profile remains single-deck (1:1 to one chosen source deck) and reads that deck's `<stem>.*` artifacts. **Source ownership boundary**: Use the automatic import mode shown above. Only inputs already under the repository's `projects/` tree move into the target project's `sources/`; every other local path is copied and remains untouched, even if `--move` is supplied. Use `--copy` when a projects-local input must also remain in place. If Step 1 wrote Markdown beside the original sources, pass that source path/directory once. If Step 1 used `-o` to write Markdown elsewhere, pass both the original source path(s)/directory and the Markdown output path(s)/directory. Intermediate artifacts (e.g., `_files/`) are handled automatically. Direct supported bitmap inputs follow both boundaries: the original is archived under `sources/`, and a collision-safe basename is copied into `images/` for analysis and §VIII planning. SVG/EMF/WMF remain source assets unless they arrive through a converter companion manifest that supplies their display metadata. This does not classify an asset's role; Strategist still decides whether it is used. -**✅ Checkpoint — Confirm project structure created successfully, `sources/` contains all source files, converted materials are ready. Proceed to Step 3.** +**✅ Checkpoint — Confirm project structure created successfully, `sources/` contains all source files, converted materials are ready. Proceed to Step 3.** `import-sources` exits 0 when any input converts; read the printed `skipped` reasons and treat those inputs as absent sources. --- @@ -229,7 +229,7 @@ multiple supplied roots remain unselected candidates. `free_design` selects none **Raw PPTX boundary**: A raw PPTX remains valid source material, but it is not a template workspace candidate. Raw PPTX plus new content uses -[`template-fill-pptx`](./template-fill-pptx.md). To create a reusable workspace, +[`edit-native-pptx`](./edit-native-pptx.md). To create a reusable workspace, run [`create-template`](./create-template.md), then return with the generated root. Never add Master/Layout/placeholder structure directly to an existing PPTX or SVG project. @@ -293,8 +293,7 @@ Read ${SKILL_DIR}/templates/charts/chart-vocabulary.md Read ${SKILL_DIR}/templates/tables/table-vocabulary.md ``` -This is a capability map, not a usage checklist: retain zero-use outcomes and -the Strategist/Executor ownership boundary. Author the three whole solution +This is a capability map; retain the Strategist/Executor ownership boundary. Author the three whole solution intents before mapping any component basis. Freeze every referenced mode/style/rendering id from the indexes, then read once only the deduplicated union of those exact detail files and finish the three custom behaviors. A novel @@ -304,13 +303,13 @@ no image rows while retaining recommendation-only rendering candidates. Only an project-local template state loads the template module, and only after Stage 1 is confirmed; a bare template/style name does not. -> ⚠️ **Mandatory artifact gates**: after final confirmation, author complete `design_spec.md` from `${SKILL_DIR}/templates/design_spec_reference.md`. After Gate 1 and any refinement approval, author `spec_lock.md` from `${SKILL_DIR}/templates/spec_lock_reference.md` plus approved Design Spec/context. Author each new artifact once without placeholders or `scaffold-*` (manual-only). Schema validity does not prove semantic fidelity. +> ⚠️ **Mandatory artifact gates**: after final confirmation, author `design_spec.md` at the confirmed `design_spec_depth` from `${SKILL_DIR}/templates/design_spec_reference.md`. After Gate 1 and any refinement approval, author `spec_lock.md` from `${SKILL_DIR}/templates/spec_lock_reference.md` plus approved Design Spec/context. Author each new artifact once without placeholders or `scaffold-*` (manual-only). Schema validity does not prove semantic fidelity. **Artifact ownership**: fact-channel and source/derived artifact boundaries are defined in [`references/artifact-ownership.md`](../references/artifact-ownership.md). This Step uses those ownership rules; it does not redefine them. -**`<project_path>/analysis/` is the project's intermediate-analysis folder: the canonical home for machine-extracted source/asset facts — the PPTX intake bundle (`source_profile.json` index + per-deck `<stem>.identity.json` / `<stem>.slide_library.json`) and `image_analysis.csv`. It holds facts, not design contracts — `design_spec.md` / `spec_lock.md` stay at the project root.** The MUST-read contract covers only the **compact structured data files (`.json` / `.csv`)**; other artifacts that may live under `analysis/` (e.g. a beautify `source_svg_import/` vector reference package) are NOT bulk-read — they are read selectively only when a specific workflow step calls for them. Before the Strategist confirmation stage, Strategist MUST read the auto-extracted fact files already in `analysis/` — currently `source_profile.json` (PPTX intake), when present. This file is the multi-deck index: read it once for the `decks[]` digests (canvas / chart / table / SmartArt entries per source deck), then open a specific deck's `<stem>.identity.json` / `<stem>.slide_library.json` only if you need its full raw facts. Use these entries as **factual source context** (format default + content facts); when several decks are present, synthesize across all of them. The source's **palette / typography / visual identity are a reference, not a constraint**: the main pipeline may inherit them where they fit the content and the confirmed style, or design fresh where they don't — the Strategist's judgment, never an obligation to either keep or discard. (Template-fill preserves the native source design by editing cloned slides directly; beautify defaults to the source identity but still follows the confirmed values; the main pipeline treats source identity as reference only and defaults to fresh design.) (`image_analysis.csv` lands later, at the image-analysis step below, and is the authoritative regenerated image-fact view there — re-derived from the live `images/` folder, not a durable store.) +**`<project_path>/analysis/` is the project's intermediate-analysis folder: the canonical home for machine-extracted source/asset facts — the PPTX intake bundle (`source_profile.json` index + per-deck `<stem>.identity.json` / `<stem>.slide_library.json`) and `image_analysis.csv`. It holds facts, not design contracts — `design_spec.md` / `spec_lock.md` stay at the project root.** The MUST-read contract covers only the **compact structured data files (`.json` / `.csv`)**; other artifacts that may live under `analysis/` (e.g. a beautify `source_svg_import/` vector reference package) are NOT bulk-read — they are read selectively only when a specific workflow step calls for them. Before the Strategist confirmation stage, Strategist MUST read the auto-extracted fact files already in `analysis/` — currently `source_profile.json` (PPTX intake), when present. This file is the multi-deck index: read it once for the `decks[]` digests (canvas / chart / table / SmartArt entries per source deck), then open a specific deck's `<stem>.identity.json` / `<stem>.slide_library.json` only if you need its full raw facts. Use these entries as **factual source context** (format default + content facts); when several decks are present, synthesize across all of them. The source's **palette / typography / visual identity are a reference, not a constraint**: the main pipeline may inherit them where they fit the content and the confirmed style, or design fresh where they don't — the Strategist's judgment, never an obligation to either keep or discard. (Edit Native PPTX preserves source-native objects through its source-backed round trip; beautify defaults to the source identity but still follows the confirmed values; the main pipeline treats source identity as reference only and defaults to fresh design.) (`image_analysis.csv` lands later, at the image-analysis step below, and is the authoritative regenerated image-fact view there — re-derived from the live `images/` folder, not a durable store.) -**Channel ownership — read each fact once from its owning channel.** In the main pipeline the **content contract is the content-type files in `sources/`** — primarily `<stem>.md`, but also any user-supplied content the import archived there: `.md` / `.markdown` / `.txt` / `.csv` / `.tsv` / `.json` / `.jsonl` / `.yaml` / `.yml` (a `metrics.json` or `data.csv` may carry core content — judge by what the file holds). Text, tables, chart data values, and SmartArt node wording come from these (`ppt_to_md` transcribes native charts as Markdown tables and SmartArt nodes as hierarchical bullets). **Do NOT read pipeline sidecars in `sources/` as content**: `*.conversion_profile.json` (conversion audit) and `*_files/image_manifest.json` (asset index) are process metadata — open them only to audit a conversion or resolve assets, never as slide content. Converted-source originals archived in `sources/` (`.pdf` / `.pptx` / `.docx` / `.xlsx` / `.html` / `.epub` / `.tex` / `.rst` / `.ipynb` / `.typ`, etc.) are read via their converted `<stem>.md`, not scanned directly in the main pipeline. The `analysis/` chart / table / diagram entries are a **structural digest** for outline decisions (which slides carried charts, tables, or SmartArt; chart types / series names; SmartArt layout and hierarchy) — not a second copy of the content values; do NOT also pull chart values or SmartArt wording from `<stem>.slide_library.json` in the main pipeline. The `<stem>.slide_library.json` full structured data is owned by the direct-PPTX workflows: template-fill uses it as the native fill contract while preserving SmartArt unchanged; beautify uses it for native chart / table data and SmartArt relationships while keeping all wording from the Markdown. +**Channel ownership — read each fact once from its owning channel.** In the main pipeline the **content contract is the content-type files in `sources/`** — primarily `<stem>.md`, but also any user-supplied content the import archived there: `.md` / `.markdown` / `.txt` / `.csv` / `.tsv` / `.json` / `.jsonl` / `.yaml` / `.yml` (a `metrics.json` or `data.csv` may carry core content — judge by what the file holds). Text, tables, chart data values, and SmartArt node wording come from these (`ppt_to_md` transcribes native charts as Markdown tables and SmartArt nodes as hierarchical bullets). **Do NOT read pipeline sidecars in `sources/` as content**: `*.conversion_profile.json` (conversion audit) and `*_files/image_manifest.json` (asset index) are process metadata — open them only to audit a conversion or resolve assets, never as slide content. Converted-source originals archived in `sources/` (`.pdf` / `.pptx` / `.docx` / `.xlsx` / `.html` / `.epub` / `.tex` / `.rst` / `.ipynb` / `.typ`, etc.) are read via their converted `<stem>.md`, not scanned directly in the main pipeline. The `analysis/` chart / table / diagram entries are a **structural digest** for outline decisions (which slides carried charts, tables, or SmartArt; chart types / series names; SmartArt layout and hierarchy) — not a second copy of the content values; do NOT also pull chart values or SmartArt wording from `<stem>.slide_library.json` in the main pipeline. The `<stem>.slide_library.json` full structured data is owned by beautify for native chart / table data and SmartArt relationships while keeping all wording from the Markdown. **Confirmation orchestration**: field meaning and recommendation logic belong to the active Strategist modules; [`confirm_ui.md`](../scripts/docs/confirm_ui.md) owns the JSON schema, server lifecycle, staged-result contract, port behavior, and equivalent chat fallback. @@ -348,7 +347,7 @@ Stage-2 summary without fabricating UI results. Silence confirms nothing. | `confirm_ui/template_options.json` | Candidate schema/language plus supplied exact roots; library entries remain server-owned index data | Stage-1 submission writes user-owned `template_selection.json` with `phase: template`, `status: confirmed` | | `confirm_ui/recommendations.stage1.json` | Communication contract, `content_divergence`, and canvas only; no template-derived recommendation | The same submission writes `result.json` with `status: stage1-confirmed` | | `confirm_ui/template_handoff.json` | Only through `--complete-template-selection`, after the Stage-1 selection and free-design closure or successful installation | `status: ready`, bound to the current selection hash; prerequisite for Stage 2 | -| `confirm_ui/recommendations.stage2.json` | `stage: stage2`; complete deck solution plus conditional AI path, generation mode, refine-spec, proactive speaker notes, custom animations, and narration audio | `stage: final`, `status: confirmed` | +| `confirm_ui/recommendations.stage2.json` | `stage: stage2`; complete deck solution plus conditional AI path, generation mode, refine-spec, design-spec depth, proactive speaker notes, custom animations, and narration audio | `stage: final`, `status: confirmed` | If the user rejects the current recommendation before confirming it, regenerate by overwriting that same stage file and have the page refresh; do not create revision-suffixed files. This never authorizes one stage file to carry another stage's payload. @@ -603,7 +602,7 @@ Read ${SKILL_DIR}/references/executor-base.md # REQUIRED: flat/shared execut **Scheduled lock re-read (Default Generate only)**: when another page follows, re-read `spec_lock.md` once after P05/P10/P15/… per [`executor-base.md`](../references/executor-base.md) §2.1. -**Exact page roster**: render `design_spec.md §IX` one-for-one, in order. Any add/drop/merge/split/reorder requires Spec repair/refinement first. +**Exact page roster**: render `design_spec.md §IX` one-for-one, in order. Any add/drop/merge/split/reorder requires Spec repair first; a continuous run may repair within the confirmed range per [`executor-base.md`](../references/executor-base.md) §2.1. **Page content**: §IX is preferred wording and semantic authority. Use it when it works; adapt it when presentation benefits while preserving intent, facts, and explicit literal requirements. Read sources only to verify requested evidence; return incomplete blocks to Step 4 instead of enriching them during execution. @@ -618,7 +617,7 @@ Read the exact execution references named by this deck's retained construction block plus the resolved mode/style detail files as one batch: ``` Read ${SKILL_DIR}/references/shared-standards-core.md # REQUIRED: SVG compatibility + shared aesthetic/leading baseline -Read ${SKILL_DIR}/references/svg-effects.md # REQUIRED: §6.1 job diagnostic + effects/construction vocabulary +Read ${SKILL_DIR}/references/svg-effects.md # REQUIRED: effects/construction vocabulary (§6.1 Visual Job Router as recall) Read ${SKILL_DIR}/references/native-shape-authoring.md # REQUIRED: native-shape selection and Boolean construction Read ${SKILL_DIR}/references/preset-shape-vocabulary.md # REQUIRED: complete 187-name authoring vocabulary Read ${SKILL_DIR}/references/executor-structure.md # REQUIRED: qualitative relationship and topology grammar @@ -628,7 +627,7 @@ Read ${SKILL_DIR}/references/modes/<resolved-id>.md # one preset id, or e Read ${SKILL_DIR}/references/visual-styles/<resolved-id>.md # one preset id, or each `visual_style_references` id ``` -Keep the core's shared visual-quality defaults and `svg-effects.md` §6.1 job diagnostic active during page authoring, with its Visual Job Router as recall; they are not passive compatibility reading. Explicit user/template requirements and the locked style override compatible aesthetic defaults, never technical Required / Forbidden boundaries. +Keep the core's shared visual-quality defaults active during page authoring, with `svg-effects.md` §6.1's Visual Job Router as recall; they are not passive compatibility reading. Explicit user/template requirements and the locked style override compatible aesthetic defaults, never technical Required / Forbidden boundaries. > Read only the role core, always-on construction references, exact locked detail files, and conditionally triggered modules below. The selection indexes remain planning-only. A preset reads its one locked file. For `custom`, read only the exact bases named by optional `mode_references` / `visual_style_references`: apply one under the corresponding behavior, or synthesize several by their stated contributions. If absent, read no preset file and follow the behavior directly. Do not infer adjacent bases, glob a catalog, or blend unselected identities. @@ -660,7 +659,7 @@ decision nor locks geometry/native readiness. ```bash python3 ${SKILL_DIR}/scripts/svg_editor/server.py <project_path> --live --daemon ``` -- Start when Executor begins; `svg_output/` may be empty. Default: first free port from `5050`; `--port N`: strict bind. Read the actual URL from output or `<project_path>/live_preview/lock.json`. +- Start when Executor begins; `svg_output/` may be empty. Default: first free port from `6060`; `--port N`: strict bind. Read the actual URL from output or `<project_path>/live_preview/lock.json`. - Before the first SVG, report that URL or the launch failure; never claim an unavailable preview. - Run it as a long-running side process/session; do not wait for it to exit before generating SVG pages. Do not wait for user confirmation after startup. - **Service must keep running** until one of: (a) the user clicks **Exit preview** in the browser, or (b) the user explicitly asks in chat to stop it. Generation continues even if the user closes the editor. @@ -679,11 +678,11 @@ sidecars, or guessed family paths. **Page-context**: use the read-only projector only for the diagnostic/telemetry triggers in Executor §2.1, never as a routine pre-page load. > ⚠️ **Main-agent only**: SVG generation MUST stay in the current main agent — page design depends on full upstream context. Do NOT delegate to sub-agents. -> ⚠️ **Generation rhythm**: P01 → first-page gate → uninterrupted remaining pages → final gate. After context invalidation, reload under §2.1 before continuing; do not insert batches or mid-run checker calls. +> ⚠️ **Generation rhythm**: P01 → first-page gate → remaining pages (one page gate per first-exercised `not-exercised` item) → final gate. After context invalidation, reload under §2.1 before continuing. **Visual Construction Phase**: generate SVG pages sequentially, one at a time, in one continuous pass → `<project_path>/svg_output/` -Each completed SVG MUST be a standalone, complete representation of that slide's visible design. Template SVGs and locked planning artifacts may guide construction, but export must not reach back to them to add visible objects omitted from `svg_output/`. Speaker notes, animation, narration, transitions, and direct native-PPTX workflows remain separately owned artifacts/capabilities. Native shapes are Executor-local authoring capabilities, not planned resources: follow [`native-shape-authoring.md`](../references/native-shape-authoring.md), read the complete current preset vocabulary before the first page, choose page-fit contours before their authoring forms, keep exact native atoms independent when possible, materialize a Merge Shapes Boolean result only where contour semantics require it, and use necessary freeform last. Diagram relationships follow the same Shape-first gate; do not infer a preset from contour similarity. +Each completed SVG MUST contain the slide's complete visible design; export never reaches back to templates or planning artifacts for omitted visible objects. A Chart/Table marked `data-pptx-native-authority="json"` is the sole object-local exception: its inline JSON is authoritative and its visible subtree is an approximate derived preview. Notes, animation, narration, transitions, and direct native-PPTX workflows remain separate. Native shapes are Executor-local capabilities: follow [`native-shape-authoring.md`](../references/native-shape-authoring.md), read the full preset vocabulary before page one, prefer independent native atoms, use Merge Shapes only when contour semantics require it, and use freeform last. Diagram relationships follow the same Shape-first gate; never infer a preset from contour similarity. **Motion-ready image composition**: Only when an explicit user motion instruction, the effective Custom Animations outcome in `design_spec.md §I` is @@ -702,23 +701,32 @@ custom animation. A page-transition-only request requires no extra visible layer; deterministic Morph still needs the continuing object as a direct-root group on both pages. -`template_reuse_scope: mirror|layout` pages MUST start from the complete `page_layouts` SVG, keep inherited visible objects, and preserve root Master/Layout identity plus stable atoms/slots. Strict preserves that reusable contract; under `layout`, the once-loaded Design Spec's `Template Application` may still authorize carrier text/tspan reflow inside unchanged slot bounds. Adaptive uses the current or new Layout key/name already declared by Strategist. If construction proves that fixed atoms or slot topology/bounds must change, stop and return upstream for Strategist to repair the owning plan and lock, validate and read back the affected fragments, then resume; Executor never mutates `spec_lock.md`. `mirror` changes only visible text values while preserving text/tspan topology and attributes. `style` follows the flat paragraph below without structure metadata. +`template_reuse_scope: mirror|layout` pages MUST start from the complete `page_layouts` SVG and preserve inherited visuals, root Master/Layout identity, atoms, and slots. Strict keeps that contract; `layout` may apply authorized carrier text/tspan reflow within unchanged slot bounds. Adaptive uses a Strategist-declared Layout. If construction requires fixed-atom or slot topology/bounds changes, return upstream for plan/lock repair and validation; Executor never edits `spec_lock.md`. `mirror` changes only permitted text while preserving ordinary text/tspan topology/attributes. A JSON-first Chart/Table preserves marker id/kind/authority, metadata schema/bounds, and structure; its preview children may regenerate from the same JSON. `style` follows the flat rule below. -`template_reuse_scope: style`, Style-only, free-design, and brand-only pages use `pptx_structure.mode: flat`. A Style-only workspace always derives `template_reuse_scope: style`; Style never supplies prototype mappings. When installed alongside Layout/Deck, Style changes only Direction / method and follows the selected non-Style structure plan. On a flat page, draw the complete page directly: keep backgrounds, repeated chrome, headings, text, images, and decoration as ordinary Slide-local SVG content. Do not plan `pptx_masters` / `pptx_layouts` / `page_pptx_layouts`, do not add root Master/Layout identity, and do not add `data-pptx-layer` or `data-pptx-placeholder` metadata. Group logical content normally with top-level `<g id>` elements. Export materializes one clean project-owned Master plus one Blank Layout, applies the locked theme colors/fonts/title-body defaults, removes stock content placeholders and unused built-in Layouts, and retains only the standard date/footer/slide-number capability hooks. It does not promote or deduplicate page content. +`template_reuse_scope: style`, Style-only, free-design, and brand-only pages use +`pptx_structure.mode: flat`. Style supplies no prototype mappings; beside +Layout/Deck it changes only Direction/method and follows that structure plan. +Draw the complete flat page as ordinary Slide-local SVG. Omit +`pptx_masters` / `pptx_layouts` / `page_pptx_layouts`, root Master/Layout +identity, layers, and placeholders; group logical content with top-level +`<g id>`. Export creates one project Master plus Blank Layout, applies locked +theme defaults, removes stock placeholders/Layouts, retains standard +date/footer/slide-number hooks, and never promotes/deduplicates page content. Do not duplicate specialized identity with `data-pptx-role`. Add it only to structural page-frame objects whose package, page-number, or animation behavior is not already expressed by `data-pptx-layer`, `data-pptx-placeholder`, or `data-pptx-replace-with`; such an element needs a stable unique `id`. Do not add generic content roles to ordinary titles, body text, cards, KPIs, diagrams, charts, icons, or images. Full contract: [`references/semantic-svg.md`](../references/semantic-svg.md). **First-page gate (Mandatory)** — after the **first** SVG page, before drawing page 2: ```bash -python3 ${SKILL_DIR}/scripts/svg_quality_checker.py <project_path> --stage first-page --json +python3 ${SKILL_DIR}/scripts/svg_quality_checker.py <project_path> \ + --canonical-authoring --stage first-page --json ``` -Run the command unfiltered—do not pipe it through `tail`, `head`, `grep`, or another output truncator. Review the complete P01 issue set from that one run before editing. Select any advisory warnings worth addressing, fix all blocking errors and selected warnings in one consolidated edit pass, then perform one verification rerun. Do not rerun merely to reveal the next issue. If verification still fails, treat its complete output as the next batch and repeat the same review → consolidated edit → single verification cycle; never check between individual fixes. If the terminal output itself is truncated, read only the relevant issue arrays from `validation/svg_quality_first_page_report.json`; do not launch another checker run for discovery. After the gate passes, draw P02 through the final page without checker calls. +Run the command unfiltered (no `tail`/`head`/`grep`). Review the complete P01 issue set from that one run before editing. Select any advisory warnings worth addressing, fix all blocking errors and selected warnings in one consolidated edit pass, then perform one verification rerun. If verification still fails, treat its complete output as the next batch and repeat the same review → consolidated edit → single verification cycle; never check between individual fixes. If the terminal output itself is truncated, read only the relevant issue arrays from `validation/svg_quality_first_page_report.json`; do not launch another checker run for discovery. **Mandatory — read P01 as a method sample, then emit the classification before editing**: the gate validates how the remaining pages will be authored, not only this page. | Signal | Reading | |---|---| -| Two or more issues share a category and direction | Method-level bias — resolve it to the authoritative rule before P02; a correction fitted to the observed offset only patches this sample. For text extents that rule is `svg_to_pptx.drawingml.elements.estimate_single_line_text_frame_width(runs)`, with `${SKILL_DIR}/scripts` on `sys.path` and every run key present — `text`, `font_size`, `font_family`, `font_weight`, `letter_spacing` — since omissions under-measure | +| Two or more issues share a category and direction | Method-level bias — resolve it to the authoritative rule before P02; a correction fitted to the observed offset only patches this sample. For text extents that rule is the shared estimator, exposed as `python3 ${SKILL_DIR}/scripts/text_measure.py measure|wrap|box` — calibrate each role once; measure only lines near a limit (`--stdin` batches) | | One isolated issue tied to this page's structure | Page-local — fix and continue | | A recurring element appears for the first time (page furniture, caption format, section numbering, accent discipline) | It will be copied to every later page — confirm its semantics now | @@ -728,14 +736,18 @@ Emit one line before the consolidated edit: gate-signal: method=<rule resolved, or none> | page-local=<count> | not-exercised=<list> ``` -`not-exercised` names what P01 could not test — a cover typically omits multi-line text, columns, charts, image captions, and data objects. Carry every resolved rule forward as arithmetic; P02 through the final page run without further tool calls. +`not-exercised` names what P01 could not test — a cover typically omits multi-line text, columns, charts, image captions, and data objects. Carry every resolved rule forward as arithmetic. + +**Mandatory — first-exercise gate**: the first page exercising a listed item runs `python3 ${SKILL_DIR}/scripts/svg_quality_checker.py <project_path> --canonical-authoring --stage page --page <svg>` once (items first exercised together share it), fixes blocking items, then continues. Every other page runs without checker calls. **Quality Check Gate (Mandatory)** — only after every planned SVG exists, BEFORE annotation handling and speaker notes: ```bash -python3 ${SKILL_DIR}/scripts/svg_quality_checker.py <project_path> --stage final --json +python3 ${SKILL_DIR}/scripts/svg_quality_checker.py <project_path> \ + --canonical-authoring --stage final --json ``` - **MUST**: Before this gate, every §IX `Native-ready` entry `<object-key>=yes` already has one matching draw-time marker group and JSON metadata child; `=no` and incidental microvisuals remain ordinary SVG. A legacy bare `yes|no` is readable only when that page has exactly one eligible object; it never derives from §VII. -- Run the command unfiltered—do not pipe it through `tail`, `head`, `grep`, or another output truncator. One invocation already scans every page and reports the complete issue set. +- **Authority gate**: JSON-first Chart/Table validates inline schema/bounds; its preview has no freshness authority. SVG-first native-ready markers require a current `data-pptx-fallback-sha256`, stamped after SVG/JSON synchronization. Missing/stale baselines block canonical/native export, not fallback export. +- Run the command unfiltered (no `tail`/`head`/`grep`). One invocation already scans every page and reports the complete issue set. - On failure, review all `blocking` errors and all advisory warnings from that run before editing. Choose which warnings merit work, fix every blocking error and the selected warnings in one consolidated edit pass, then perform one verification rerun. If it still fails, its complete output begins the next batch cycle; never run the checker between individual fixes or use repeated invocations to discover one next issue at a time. If terminal output is truncated, extract only `categories.blocking.issues` and, when needed, `categories.introduced.issues` from the report written by that same run. - Every `warning` is advisory and non-blocking: do not return the page for mandatory modification, do not auto-normalize user-authored compatible syntax, and do not require an acknowledgement/disposition line. Recommendation warnings identify the generated-SVG default; fidelity/quality warnings may be reported when material, but the existing input may ship unchanged. If a condition must be corrected before release, the checker must classify it as an `error`, not a `warning`. - The same rule applies to structured-template warnings (empty/framing-only Layout, bare Master, duplicate layout keys): they may guide an optional template cleanup, but warnings alone never fail the quality gate. Flat `style`, free-design, and brand-only routes still rely on their existing hard errors for invalid structure metadata or incomplete required locks. @@ -822,7 +834,7 @@ command and proceed directly to Step 7.2. python3 ${SKILL_DIR}/scripts/finalize_svg.py <project_path> ``` -**Success criterion**: `<project_path>/svg_final/` contains one self-contained preview SVG for every published slide. This mandatory derived preview does not replace `svg_output/` as the native-export source. +**Success criterion**: `<project_path>/svg_final/` contains one self-contained preview SVG for every published slide. This optional derived preview does not replace `svg_output/` as the native-export source, and its absence never blocks Step 7.3. #### Step 7.3 — Export the Native PPTX @@ -833,6 +845,11 @@ Choose exactly one notes mode: | Speaker Notes `enabled` | `python3 ${SKILL_DIR}/scripts/svg_to_pptx.py <project_path>` | | Speaker Notes `disabled` | `python3 ${SKILL_DIR}/scripts/svg_to_pptx.py <project_path> --no-notes` | +Append `--native-charts-and-tables` only for an explicit editable PowerPoint +Chart/Table delivery decision. Templates, markers, semantic tables, and imported +charts never activate it. Without the flag, Chart/Table uses its SVG fallback; +formula native behavior remains intrinsic. + For deck-wide motion settings, append the resolved flags from [`animations.md`](../references/animations.md). When the conditional custom stage preserves or produces `<project_path>/animations.json`, keep the base command above: diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/governance/failure-recovery.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/governance/failure-recovery.md index 696a506e..af7e9639 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/governance/failure-recovery.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/governance/failure-recovery.md @@ -4,7 +4,7 @@ description: Cross-route stop/continue governance with a concrete recovery matri # Failure Recovery Governance -Global stop/continue rules for all four top-level routes, plus concrete failure handling for Generate PPTX. Section 2 applies across routes; Sections 1 and 3 apply only to Generate PPTX. Owning route and stage documents may add narrower handling, but must not weaken the global rules or duplicate this matrix. +Global stop/continue rules for all three top-level routes, plus concrete failure handling for Generate PPTX. Section 2 applies across routes; Sections 1 and 3 apply only to Generate PPTX. Owning route and stage documents may add narrower handling, but must not weaken the global rules or duplicate this matrix. **Hard rule**: A failed required artifact blocks the next gate. A failed convenience surface falls back to the canonical channel and does not block the active route. @@ -23,7 +23,7 @@ Global stop/continue rules for all four top-level routes, plus concrete failure | `spec_lock.md` changes confirmed identity or omits a required execution anchor/routing decision | Yes | Re-author the affected lock rows from the completed Design Spec and current context; do not enumerate page-local literals | No unless the Design Spec itself is incomplete | Step 4 Gate 2 — lock context fidelity | | Execution exposes a missing Strategist-owned role/plan detail | Yes for the affected page | Repair affected Design Spec/lock fragments under [`executor-base.md`](../../references/executor-base.md) §2.1 | Only if confirmed intent changes | Step 4 Gate 1/2 → Step 6 current page | | Execution context is fresh, resumed, restarted, compacted/summary-only, external, or unknown | Yes until rebuilt | Read complete Design Spec, then lock, once; reload triggered inputs and latest completed SVG when mid-deck | No | Step 6 current page | -| Step 3 rejects a legacy or incomplete template contract | Yes | Stop template consumption; create a new current workspace through Create Template from the original PPTX/reference, then return with its exact workspace root | Only when required source evidence or template choices are unavailable | Create Template → Generate PPTX Step 3 | +| `apply-template-workspace` rejects a legacy or incomplete template contract | Yes | Stop template consumption; create a new current workspace through Create Template from the original PPTX/reference, then return with its exact workspace root | Only when required source evidence or template choices are unavailable | Create Template → Generate PPTX Step 4 Stage 1 (`apply-template-workspace`) | | Native formula marker validation or LaTeX compilation failure | Yes for the affected page | Repair the marker metadata or source LaTeX and rerun the SVG checker; there is no formula-image fallback | Clarify the intended equation only when the source is ambiguous | Active SVG authoring step | | AI image generation failure | Default blocks at the recovery decision; Quick does not | Default `auto`: try A → B, then ask once to repair/retry, generate manually, or cancel/replan affected AI images; explicit `api` / `host-native`: retry only that path, then ask the same question. Quick removes the exhausted AI/dependent-slice jobs, replans them with native editable text/SVG or already prepared non-AI assets, and continues | Default chooses one outcome; manual files are still required before export | Default: Step 5 decision → same path, Step 4 affected-plan repair, or Step 7 image readiness gate; Quick: current resource plan | | Web image search/download failure | No | Adjust query/source per image-searcher rules, then mark `Needs-Manual` if unresolved | Only if the resource is required and no acceptable substitute exists | Step 5 | diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/index.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/index.md index 60072e2c..b76935e9 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/index.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/index.md @@ -12,8 +12,7 @@ Maintainer-only inventory for adding, moving, or removing workflow documents. Ru |---|---|---| | `generate-pptx` | Top-level route | [`generate-pptx.md`](./generate-pptx.md) | | `create-template` | Top-level route | [`create-template.md`](./create-template.md) | -| `template-fill-pptx` | Top-level route | [`template-fill-pptx.md`](./template-fill-pptx.md) | -| `native-enhance-pptx` | Top-level route | [`native-enhance-pptx.md`](./native-enhance-pptx.md) | +| `edit-native-pptx` | Top-level route | [`edit-native-pptx.md`](./edit-native-pptx.md) | ## 2. Supporting Documents @@ -35,7 +34,7 @@ Maintainer-only inventory for adding, moving, or removing workflow documents. Ru | `visual-review` | Quality gate | [`stages/visual-review.md`](./stages/visual-review.md) | Before Generate Step 7 | | `live-preview` | Editor stage | [`stages/live-preview.md`](./stages/live-preview.md) | Generate preview / post-export | | `customize-animations` | Post-processing stage | [`stages/customize-animations.md`](./stages/customize-animations.md) | Generate conditional export | -| `generate-audio` | Shared audio stage | [`stages/generate-audio.md`](./stages/generate-audio.md) | Generate / Enhance Native | +| `generate-audio` | Shared audio stage | [`stages/generate-audio.md`](./stages/generate-audio.md) | Generate / Edit Native | | `failure-recovery` | Governance | [`governance/failure-recovery.md`](./governance/failure-recovery.md) | All routes | ## 3. Maintenance Rules diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/native-enhance-pptx.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/native-enhance-pptx.md deleted file mode 100644 index b34ce9ed..00000000 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/native-enhance-pptx.md +++ /dev/null @@ -1,409 +0,0 @@ ---- -description: Native enhancement platform for existing PPTX files, with delivery checks and scoped OOXML updates without SVG conversion ---- - -# Enhance Native PPTX Route - -> Top-level route for enhancing an existing PowerPoint deck without regenerating it. The current write scope is speaker notes, narration audio, slide auto-advance timings, and global or per-slide page transitions; read-only delivery checks always run. - -This route treats a `.pptx` as the artifact to preserve. It archives the source file into a lightweight project, uses `ppt_to_md.py` only to understand slide content, then patches the archived PPTX package directly through OOXML zip operations. - ---- - -## 1. Platform Contract - -| Rule | Contract | -|---|---| -| Source file | If already under `projects/`, move it into the enhancement project; otherwise copy it | -| Visible slides | Do not rewrite existing text, shapes, images, charts, tables, masters, or layouts | -| Existing hyperlinks | Preserve hyperlink XML and relationships unchanged | -| Route | Direct PPTX package patching; no SVG conversion | -| Output | A new `.pptx` under `<project>/exports/` | -| Project kind | `native_pptx_enhancement` | - -**Hard rule**: Native enhancement is append-oriented. It may add notes, media, timings, transitions, relationships, and content-type records. It must not regenerate slides. - -**Forbidden — SVG pipeline**: -- Do not run `pptx_template_import.py` -- Do not create `svg_output/` -- Do not run `finalize_svg.py` -- Do not run `svg_to_pptx.py` - -**Hard rule — public entrypoint**: Route and document all new work through -`native_enhance_pptx.py`. The legacy `native_narration_pptx.py` command remains -only as a thin CLI compatibility shim; it is not a separate route. The core -continues to accept the legacy `native_narration_pptx_project.v1` project schema. - -**OOXML execution model**: - -```text -source.pptx -→ unzip to temporary work directory -→ patch only required package parts -→ rezip to exports/<source>_enhanced.pptx -``` - ---- - -## 2. Module Scope - -| Module | V1 status | Behavior | -|---|---:|---| -| `narration.notes` | Enabled | Add or replace speaker notes generated from slide content | -| `narration.audio` | Enabled | Embed one audio file per slide | -| `narration.timings` | Enabled | Set narrated slides to auto-advance from page-start lead-in, audio duration, and page-tail padding | -| `narration.transitions` | Enabled | Add page-level transitions for narrated/selected slides | -| `delivery.check` | Enabled | Read-only package/font/media/hidden-slide/file-size and existing-motion audit | -| `delivery.compress` | Planned | Reduce embedded media size for oversized packages; the audit above already reports them | -| `presenter` | Planned | Q&A notes, speaker cues, rehearsal artifacts | -| `animation` | Planned | Explicit object-level animation only | -| `visible-stamp` | Planned | Watermark/footer/logo; requires explicit confirmation | - -**Default — current write scope only**: Do not implement planned write modules inside this route yet. Keep mutations limited to notes, narration audio, timings, and page transitions. - -**Object animation boundary**: `delivery.check` reports existing object-animation presence, and apply proves its fingerprint is unchanged. It does not author or edit object animations. The shared animation writer builds a complete timing tree for generated slides and is not safe to append to an arbitrary native slide. - ---- - -## 3. When to Run - -| Condition | Action | -|---|---| -| Existing `.pptx` + wants notes / narration / voiceover / auto-play / page transitions while keeping format stable | Run this route | -| Existing `.pptx` + asks to optimize it but says not to change existing content or layout | Run this route only for V1 narration enhancements; clarify any visible-slide request | -| Existing `.pptx` + asks to beautify or re-layout | Enter Generate PPTX with the [`beautify-pptx`](./profiles/beautify-pptx.md) profile | -| Existing `.pptx` + asks to fill new content into the design | Use [`template-fill-pptx`](./template-fill-pptx.md) | -| PPT Master generated project with `svg_output/` | Stay in Generate PPTX and run the shared [`generate-audio`](./stages/generate-audio.md) stage | - ---- - -## 4. Create the Project and Draft Plan - -🚧 **GATE**: User provided an existing `.pptx`. - -Run: - -```bash -python3 skills/ppt-master/scripts/native_enhance_pptx.py init "<source.pptx>" --name "<project_slug>" -``` - -Project layout: - -| Path | Purpose | -|---|---| -| `<project>/project.json` | Project schema, kind, enabled modules, source paths, defaults | -| `<project>/sources/<source>.pptx` | Archived source PPTX used for package patching | -| `<project>/sources/<source>.md` | `ppt_to_md.py` output for slide understanding | -| `<project>/analysis/slide_index.json` | Slide order and PPTX slide part mapping | -| `<project>/notes/` | Per-slide spoken notes, named `001.md`, `002.md`, ... | -| `<project>/audio/` | Per-slide narration media, named `001.mp3`, `002.mp3`, ... | -| `<project>/exports/` | Enhanced PPTX copies | -| `<project>/validation/` | Delivery checks, readiness reports, and read-back artifacts | - -**Validation**: `project.json` contains `schema: native_pptx_enhancement_project.v1`, `kind: native_pptx_enhancement`, and `modules` containing `notes`, `audio`, `timings`, `transitions`, and `delivery.check`. - -`init` records the archived source SHA-256 and ordered slide-part roster, then writes the intake audit to `<project>/validation/report.json`. Package-integrity, OPC part/content-type/relationship, XML, slide-inventory, transition, or object-animation errors stop before a project-local source is moved. The only retained historical structural baseline is the narrowly recognized legacy notes-slide relationship to a missing notes master; it remains visible in the report and apply may not add any new structural error. - -**Source import rule**: When `<source.pptx>` is inside the repo's `projects/` tree, `init` moves it into `<project>/sources/`. When it is outside `projects/`, `init` copies it into `<project>/sources/`. The mode is recorded in `project.json` as `source_import.mode`. - -The `init` command also writes: - -```text -<project>/analysis/enhancement_plan.json -``` - -**Hard rule**: Treat this draft plan as the first user-facing artifact. Do not generate notes, list voices, generate audio, or apply package patches before the user confirms which enhancements to add. - ---- - -## 5. Enhancement Plan Confirmation - -🚧 **GATE**: Step 4 complete; `<project>/analysis/enhancement_plan.json` exists. - -If the project already existed or notes/audio coverage changed, refresh the draft: - -```bash -python3 skills/ppt-master/scripts/native_enhance_pptx.py plan "<project>" -``` - -`plan` preserves module settings, refreshes coverage, and emits a -reconfirmation `draft`. It changes `audio.enabled: true` / -`notes.enabled: false` to `notes.enabled: true`; `validate`/`apply` reject the -old state. Audio remains unchecked until `validate` runs ffprobe. Supplied CLI -flags override. - -Present the plan to the user before generating notes or audio: - -| Module | Recommended default | Confirmation question | -|---|---|---| -| `notes` | Enabled; required whenever audio is enabled | Add/replace speaker notes generated from slide content? | -| `audio` | Enabled when user wants narration/video/autoplay | After notes are complete, generate one narration audio file per slide? | -| `timings` | Enabled with audio; 0.8s page-start floor and 0.4s page-tail padding | Set slide auto-advance from narration timing? | -| `transitions` | Enabled, `fade` 0.5s | Add page transitions? Which canonical native effect, Effect Options, and duration? | -| `delivery.check` | Always on, read-only | No confirmation required; review errors and advisories | - -**⛔ BLOCKING**: Stop here and wait for explicit user confirmation. Do not generate notes, generate audio, or patch the PPTX until the user confirms the module plan. - -**Hard dependency — notes before audio**: Confirming `audio.enabled: true` -also requires `notes.enabled: true`. If complete per-slide notes do not already -exist, run Step 6 and generate them before entering audio configuration or -audio generation. Never generate narration directly from slide text or bypass -the notes artifact. - -**Transition/timing ownership**: - -| Confirmed state | Enter transition | Slide advance | -|---|---|---| -| Transitions enabled with an effect | Replace with that exact effect and duration | Preserve unless timings is enabled | -| Transitions disabled with a non-`none` configured effect | Preserve the source effect, including unknown `AlternateContent` | Preserve unless timings is enabled | -| Explicit `none` | Remove the visual effect | Preserve, or write timing-only advance when timings is enabled | -| Timings enabled with audio | Keep the resolved enter policy | Use page-start lead-in plus audio duration plus page-tail padding; click disabled | -| Timings disabled | Apply the confirmed enter policy only | Use duration only to reject source auto-advance that would truncate narration; do not derive, add, or change `advTm` / `useTimings` | - -The timing module's `narration_start_floor` and `narration_padding` are -independent optional values. When omitted, use `0.8` and `0.4` seconds -respectively. For a destination-page transition of `T` seconds, the -post-transition lead-in is -`max(0, narration_start_floor - T)`: narration does not begin during the -transition, and the transition duration itself remains unchanged. A start -floor of `0` means start immediately after the transition completes. A -preserved legacy transition that exposes only `spd` has no exact millisecond -duration; keep the full configured floor after that transition instead of -guessing a PowerPoint-specific duration. When timings are disabled, preserve -source `advTm`, but fail if it would advance before the delayed narration ends. - -The confirmed `modules.transitions` object may include `effect_options` beside -an explicit canonical `effect`. Use -`pptx_animations.py --describe-transition <effect>` for its exact fields. -Old names remain accepted only when reading compatibility input; a newly -written plan stores the canonical effect and any implied options. - -For explicit page selection or page-specific settings, add `slides` keyed by -the 1-based `index` in `analysis/slide_index.json`: - -```json -{ - "modules": { - "transitions": { - "enabled": false, - "effect": "fade", - "duration": 0.5, - "apply_without_audio": false, - "slides": { - "2": {}, - "3": {"duration": 0.8}, - "4": { - "effect": "push", - "effect_options": {"direction": "left"} - }, - "5": {"effect": "none"}, - "6": {"effect": "preserve"} - } - } - } -} -``` - -| Per-slide entry | Behavior | -|---|---| -| `{}` | Select the page and inherit the global effect/options/duration | -| Partial object | Inherit omitted global fields; a new explicit effect uses its own default options | -| `effect: none` | Remove the visual transition; timings remain independently owned | -| `effect: preserve` | Preserve the source visual transition; narration timing may still update advance | - -A `slides` entry always selects that page. Without audio, enabled global effects -and explicit global `none` apply deck-wide; `apply_without_audio` is ignored. -With audio, the flag extends the global policy from narrated to all pages. -Disabled non-`none` effects preserve unlisted pages. Morph uses PowerPoint -automatic matching; this route does not rename native objects for deterministic -pairs. - -**Hard rule — no silent downgrade**: a requested native effect must be written with its complete validated Effect Options. Unknown effects or inapplicable options fail; unknown source effects are preserved when the transition module is disabled. - -After confirmation, update `<project>/analysis/enhancement_plan.json`: - -```json -{ - "status": "confirmed" -} -``` - -Also set each confirmed module's `enabled` value. Disabled modules must stay in the file with `enabled: false`, not be deleted. - ---- - -## 6. Generate Notes From Existing Slides - -🚧 **GATE**: Step 5 confirmed; `notes.enabled` is true; `<project>/sources/<source>.md` exists. - -Read: - -| File | Use | -|---|---| -| `<project>/sources/<source>.md` | Visible slide text, tables, extracted notes, image references | -| `<project>/analysis/slide_index.json` | Exact slide count and target note filenames | - -Write: - -```text -<project>/notes/001.md -<project>/notes/002.md -... -``` - -**Hard rule**: Notes are spoken narration only. Do not include stage directions, implementation comments, timing labels, markdown tables, or visible-slide rewrite instructions. - -**Hard rule**: Notes must be faithful to the slide. They may explain visible content, but must not add unsupported facts. - -| Slide type | Notes length | -|---|---| -| Cover / section divider | 1-2 short sentences | -| Dense content page | 2-4 sentences | -| Chart / table page | Explain the reading path, then state the takeaway | -| Ending page | One concise close | - -Run coverage check: - -```bash -python3 skills/ppt-master/scripts/native_enhance_pptx.py validate "<project>" --materials notes -``` - -> Note: This keeps source/plan/transition/carrier checks but does not require -> audio. Missing/invalid notes return `2`; structural/semantic errors return -> `1`. Step 8 runs full validation after audio. - ---- - -## 7. Shared Audio Configuration - -🚧 **GATE**: Step 6 complete; `audio.enabled` is true. - -Run [`generate-audio`](./stages/generate-audio.md) Steps 1–3. That shared stage exclusively owns language selection, provider/voice catalog lookup, recommendation rules, and the one-shot confirmation. Do not repeat or fork those rules here. - -Record the confirmed config into `project.json`: - -```json -{ - "audio": { - "provider": "edge", - "voice": "zh-CN-YunjianNeural", - "rate": "+0%" - } -} -``` - ---- - -## 8. Run the Shared Audio Stage - -🚧 **GATE**: Step 7 confirmed; complete non-empty notes files exist under -`<project>/notes/` for every slide. - -Run [`generate-audio`](./stages/generate-audio.md) Step 4 with `<project>` and the confirmed values. Stop after audio generation; do not run its Generate-PPTX-only `svg_to_pptx.py --recorded-narration` integration. This route integrates audio through Step 9 instead. - -**Naming contract**: Audio stems match note stems: `001.md` → `001.mp3`. - -Validate: - -```bash -python3 skills/ppt-master/scripts/native_enhance_pptx.py validate "<project>" -``` - ---- - -## 9. Apply V1 Enhancements - -🚧 **GATE**: Enhancement plan is confirmed; notes are ready if requested; audio is ready if requested. - -Run: - -```bash -python3 skills/ppt-master/scripts/native_enhance_pptx.py apply "<project>" -``` - -Optional: - -```bash -python3 skills/ppt-master/scripts/native_enhance_pptx.py apply "<project>" \ - --transition fade \ - --transition-duration 0.5 \ - --narration-start-floor 0.8 \ - --narration-padding 0.4 \ - --apply-transition-without-audio \ - --overwrite -``` - -`--apply-transition-without-audio` matters only with audio enabled: it extends -the global enter policy from narrated slides to all slides. Explicit slide -entries always opt in. Without audio, enabled transitions apply to every slide. - -`apply` reruns the same source/readiness/plan checks as `validate`. Enabling -audio always requires every selected file to be decodable by ffprobe; enabling -timings additionally consumes that duration for `advTm`. It refuses -partial requested material, a changed source hash/slide roster, or a source -that already contains a `native_enhance_audio_*` carrier. New audio/poster -parts use collision-free names; an existing poster is reused only when its -bytes match the tool marker exactly. Output must be a new `.pptx` under -`exports/` or an external location; apply never overwrites either source or -writes into project control directories. Every apply attempt invalidates the -previous validation receipt, and a failed preflight records its current errors -instead of leaving stale passed evidence. - -Patch scope: - -| Package area | Append/update | -|---|---| -| `ppt/notesSlides/` | Notes slide parts | -| `ppt/notesMasters/` | Notes master only when needed | -| `ppt/slides/_rels/slideN.xml.rels` | Relationships for notes/audio/media/poster | -| `ppt/media/` | Narration audio and transparent poster | -| `ppt/slides/slideN.xml` | Hidden autoplay audio shape and page timing | -| `ppt/presProps.xml` | `showPr useTimings=1` only when this run writes automatic slide advance | -| `[Content_Types].xml` | Required content types | - -**Hard rule**: Do not modify existing slide shapes, text bodies, images, chart -data, master/layout parts, hyperlink XML/relationships, or existing non-target -relationships. - -Before publishing the candidate, apply validates transitions, timing/object -animation structure, ZIP integrity, unique parts, internal relationships, -slide count, and hidden-slide state. The narrowly allowed legacy missing -notes-master finding may remain exactly equivalent, but the candidate must not -introduce any structural error. Apply then writes both audits and the -introduced-error delta to `<project>/validation/report.json`. - ---- - -## 10. Validate Output - -Run read-back: - -```bash -python3 skills/ppt-master/scripts/source_to_md/ppt_to_md.py \ - "<project>/exports/<source>_enhanced.pptx" \ - -o "<project>/validation/readback.md" -``` - -Check: - -| Check | Expected | -|---|---| -| Slide count | Same as source | -| Visible content | No intentional changes | -| Notes | Present on intended slides | -| Audio media | Present under `ppt/media/` when generated | -| Auto-play | Narrated slides wait for the resolved page-start lead-in, then advance after audio duration plus page-tail padding | -| Transition | Requested effect remains exact; preserved `AlternateContent` keeps its primary and fallback branches | -| Timings disabled | Source `advTm` and package `useTimings` are not changed | -| Delivery check | No newly introduced structural errors; source baseline and font/media/hidden-slide advisories reviewed | - -```markdown -## ✅ Native PPTX Enhancement V1 Complete - -- [x] Project initialized at `<project>` -- [x] Source PPTX archived into `<project>/sources/` -- [x] Confirmed native enhancement modules applied -- [x] Enhanced PPTX exported to `<project>/exports/<file>.pptx` -- [x] Delivery postflight written to `<project>/validation/report.json` -- [x] Read-back validation written to `<project>/validation/readback.md` -``` diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/profiles/beautify-pptx.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/profiles/beautify-pptx.md index 81cb6029..f1c60304 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/profiles/beautify-pptx.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/profiles/beautify-pptx.md @@ -4,7 +4,7 @@ description: Generate profile for 1:1, content-faithful re-layout of an existing # Beautify PPTX (Re-layout) Profile -> Generate profile, not a top-level route. [`template-fill-pptx.md`](../template-fill-pptx.md) reuses a deck's design and swaps in new content; this profile keeps a deck's content and redoes its layout. +> Generate profile, not a top-level route. [`edit-native-pptx.md`](../edit-native-pptx.md) keeps a deck's native design and edits selected pages; this profile keeps a deck's content and redoes its layout. Re-lays-out an existing `.pptx`: text is preserved **verbatim** and source palette / fonts are the preselected recommendation. Only explicit user @@ -32,7 +32,7 @@ Beautify constraints in this file apply in either runtime. **Hard rule — content is frozen**: every text string from the source is preserved exactly (no add / remove / reword / reorder). Beautification freedom lives only in layout, hierarchy, spacing, and visual rhythm. -**Hard rule — not a patch, not a fill**: this regenerates a native deck through the selected Default or Quick SVG → PPTX runtime. It does **not** edit the source file in place, and it is **not** [`template-fill-pptx`](../template-fill-pptx.md) (which clones source slides and replaces text). It also does not parse an arbitrary third-party template for text-only substitution (the rejected #53 direction) — it builds every page from scratch. +**Hard rule — not a patch, not a fill**: this regenerates a native deck through the selected Default or Quick SVG → PPTX runtime. It does **not** edit the source file in place, and it is **not** [`edit-native-pptx`](../edit-native-pptx.md) (which restores unchanged source slides and edits only planned pages). It also does not parse an arbitrary third-party template for text-only substitution (the rejected #53 direction) — it builds every page from scratch. **Distinct from mirror templates**: `replication_mode: mirror` ([`executor-structured.md`](../../references/executor-structured.md) §1.1) keeps layout + visuals verbatim and edits text. Beautify is the inverse — content verbatim, layout redone, source identity recommended unless the user overrides it. @@ -251,8 +251,9 @@ Rows are abbreviated; follow Confirm UI's four-locale contract and omit `english "primary_language": "<source main language>", "recommend": { "canvas": "<step3-canvas-id>", - "mode": "briefing", - "visual_style": "<closest visual-style id to the source look>", + "mode": "custom", + "visual_style": "custom", + "image_strategy": "custom", "icons": "<sensible default icon library>", "image_usage": ["provided"] }, @@ -264,15 +265,31 @@ Rows are abbreviated; follow Confirm UI's four-locale contract and omit `english "delivery_context": { "value": "<primary presenter-led / reader-led / hybrid / recorded; hybrid names its lead and secondary use; occasion if inferable>" }, "artifact_afterlife": { "value": "<review / approval / archive / hand-off / reuse / none planned>" }, "content_divergence": { "value": "keep source wording and page structure verbatim", "locked": true }, - "color": { "selected": 0, "candidates": [ - { "name_zh": "复刻源 PPT(推荐)", "name_en": "Source replica (recommended)", "name_ja": "元PPTを再現(推奨)", "palette": { "background": "#...", "secondary_bg": "#...", "primary": "#...", "accent": "#...", "secondary_accent": "#...", "body_text": "#..." } }, - { "name_zh": "实际用色(observed)", "name_en": "Observed palette", "name_ja": "実際の使用色(observed)", "palette": { "background": "#...", "secondary_bg": "#...", "primary": "#...", "accent": "#...", "secondary_accent": "#...", "body_text": "#..." } }, - { "name_zh": "备选配色 A", "name_en": "Alternative palette A", "name_ja": "代替配色A", "palette": { "background": "#...", "secondary_bg": "#...", "primary": "#...", "accent": "#...", "secondary_accent": "#...", "body_text": "#..." } } - ] }, - "typography": { "selected": 0, "candidates": [ - { "name_zh": "复刻源 PPT(推荐)", "name_en": "Source replica (recommended)", "name_ja": "元PPTを再現(推奨)", "heading": { "primary": "...", "english": "...", "css": "<PPT-safe stack>" }, "body": { "primary": "...", "english": "...", "css": "<PPT-safe stack>" }, "body_size": <dominant observed.sizes_pt × 4/3, as px> }, - { "name_zh": "备选字体 A", "name_en": "Alternative pairing A", "name_ja": "代替ペアリングA", "heading": { "primary": "...", "english": "...", "css": "<PPT-safe stack>" }, "body": { "primary": "...", "english": "...", "css": "<PPT-safe stack>" }, "body_size": <canvas-appropriate baseline> }, - { "name_zh": "备选字体 B", "name_en": "Alternative pairing B", "name_ja": "代替ペアリングB", "heading": { "primary": "...", "english": "...", "css": "<PPT-safe stack>" }, "body": { "primary": "...", "english": "...", "css": "<PPT-safe stack>" }, "body_size": <canvas-appropriate baseline> } + "design_directions": { "selected": 0, "candidates": [ + { + "id": "source-replica", "name_en": "Source replica (recommended)", + "mode": "custom", "mode_behavior_zh": "briefing 基底;逐页结构、顺序与文字 1:1 逐字不变。", + "visual_style": "custom", "visual_style_behavior_zh": "复刻源 PPT 视觉身份与版式。", "icons": "…", + "color": { "palette": { "background": "#...", "secondary_bg": "#...", "primary": "#...", "accent": "#...", "secondary_accent": "#...", "body_text": "#..." } }, + "typography": { "heading": { "primary": "…" }, "body": { "primary": "…" }, "body_size": <dominant observed.sizes_pt × 4/3, as px> }, + "image_strategy": { "rendering": "custom", "behavior_zh": "…" } + }, + { + "id": "alternative-a", + "mode": "custom", "mode_behavior_zh": "briefing 基底;逐页结构与文字 1:1 逐字不变。", + "visual_style": "custom", "visual_style_behavior_zh": "…", "icons": "…", + "color": { "palette": { "background": "#...", "secondary_bg": "#...", "primary": "#...", "accent": "#...", "secondary_accent": "#...", "body_text": "#..." } }, + "typography": { "heading": { "primary": "…" }, "body": { "primary": "…" }, "body_size": <canvas-appropriate baseline> }, + "image_strategy": { "rendering": "custom", "behavior_zh": "…" } + }, + { + "id": "alternative-b", + "mode": "custom", "mode_behavior_zh": "briefing 基底;逐页结构与文字 1:1 逐字不变。", + "visual_style": "custom", "visual_style_behavior_zh": "…", "icons": "…", + "color": { "palette": { "background": "#...", "secondary_bg": "#...", "primary": "#...", "accent": "#...", "secondary_accent": "#...", "body_text": "#..." } }, + "typography": { "heading": { "primary": "…" }, "body": { "primary": "…" }, "body_size": <canvas-appropriate baseline> }, + "image_strategy": { "rendering": "custom", "behavior_zh": "…" } + } ] } } ``` diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/profiles/quick-generate.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/profiles/quick-generate.md index 59af26b0..8c7c2ba7 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/profiles/quick-generate.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/profiles/quick-generate.md @@ -27,7 +27,7 @@ never change CWD or inherit a prior shell working directory. | Interaction | The current main agent decides content, design, resources, and implementation without Strategist, Confirm UI, or approval stops | | Execution memory | Keep routine page, visual, and resource decisions only in the current active context; losing that context restarts Quick instead of reconstructing a plan from project files | | Inputs | Any supported Generate input; convert/import sources and run bounded factual research when the input requires them | -| Templates | Directly validate and install at most one exact workspace root per kind supplied for this run; when none are supplied, use free design without catalog selection or Confirm UI | +| Templates | Directly validate and install at most one exact workspace root per kind supplied for this run; before P01, inspect the complete installed template SVG roster and freeze one natural-language application paragraph in active context; when none are supplied, use free design without catalog selection or Confirm UI | | Resources | Prepare every project-local image, icon, and required provenance/manifest artifact before its SVG; author native formula markers and hyperlink anchors directly in the affected SVG; sound waits for §4 | | Planning artifacts | Do not author a root project `design_spec.md`, `spec_lock.md`, confirmation payloads, or any substitute planning artifact; installed `templates/design_spec.<kind>.<id>.md` files remain template input | | Traceability | Operational resource manifests, checker reports, postflight, and bounded Python command/outcome audit entries may remain, but they do not record the AI's design reasoning or form a resumable generation history | @@ -249,18 +249,30 @@ the project-local asset pools. All later reads use that installed state, never the original roots. Before writing P01, read every installed -`templates/design_spec.<kind>.<id>.md` once and, for Layout/Deck, inspect the -relevant SVG prototypes. Apply Brand identity, Style direction/method, the -selected structure owner's useful prototype geometry, and Deck application -context directly in the active context under the existing segment precedence +`templates/design_spec.<kind>.<id>.md` once and, for Layout/Deck, inspect every +SVG prototype in the installed roster. Apply Brand identity, Style +direction/method, the selected structure owner's prototype geometry, and Deck +application context directly in the active context under the existing segment precedence ([`apply-template-workspace`](../stages/apply-template-workspace.md) §5). A segment owner's instruction about how a value should dominate, recede, or stay rare binds as strongly as the value itself; a Style composition or whitespace tendency never demotes a Brand's declared dominant color to an incidental -accent. Follow explicit instructions about literal or visual-only -use; otherwise decide which prototypes to use, skip, repeat, reorder, or adapt -while authoring. Persist no separate template-application artifact. If no -template was installed, make the same design choices freely. +accent. + +Resolve one concise **Template Application** paragraph before P01 and freeze it +for this run. Explicit user instructions win. Otherwise decide from the current +content and the complete installed roster; when no stronger fit exists, default +to reference-led use of the template's design and structural system. Common +readings are: reference-led may redesign after full-roster study; augment-only +freezes existing non-slot objects, permits slot edits, and only adds; replacement-only +changes information carriers while preserving the rest. These are examples, +not enum values or a menu. The paragraph states which prototypes may be used, skipped, repeated, +reordered, or adapted and what stays fixed versus replaceable. Any exceptional +rule names the exact SVG basename instead of a semantic label such as “cover”. +Keep the paragraph only in active context; do not create a substitute planning +artifact. If a detail becomes uncertain later, re-read the exact installed SVG +instead of relying on memory or reopening the source PPTX. If no template was +installed, make the same design choices freely. Before resolving the one-pass design, read this fixed planning-capability batch in one pass: @@ -278,11 +290,15 @@ Read ${SKILL_DIR}/templates/tables/table-vocabulary.md ``` This batch is the complete capability map for planning, not a usage checklist: -zero use of any capability remains valid. Resolve the best whole solution from -the project brief and loaded decision authorities, freeze its exact -mode/style/rendering ids, then read only those selected detail files or custom -bases. A novel custom reads none. Never open unselected detail siblings to -compare candidates, glob a catalog, or let them influence the decision. Decide +zero use of any capability remains valid. Resolve one recommended whole +solution directly; do not materialize Default's three candidates. Without an +installed template, choose the strongest overall fit from the project brief and +loaded decision authorities. With one, choose the viable solution that most +fully expresses the resolved template context and frozen Template Application, +varying only dimensions they leave open. Freeze its exact mode/style/rendering +ids, then read only those selected detail files or custom bases. A novel custom +reads none. Never open unselected detail siblings to compare candidates, glob a +catalog, or let them influence the decision. Decide whether AI images are useful as a separate source judgment; even when the answer is no, retain the chosen rendering direction for visual coherence. Keep the chosen mode, style, rendering, and exact bases in active context only. @@ -363,13 +379,12 @@ meaningful pause—chapter transition, standalone emphasis, or SCQA bridge—and must stand alone. Create no artifact, checkpoint, lock, or second authoring/review pass. -**Mandatory — one-pass page carrier resolution, not a coverage quota**: During +**Default — page carrier resolution (may stay implicit when a page's mix is obvious)**: During the same transient-roster resolution and before resource preparation or -coordinates, decide each page's complete mix of background, editable text and +coordinates, decide each page's mix of background, editable text and optional lettering, native geometry and lines, photos/scenes, illustrations/icons, and applicable visualizations. Decide their primary, -structural, and supporting jobs together; do not finish a text/container layout -and then treat the other families as optional decoration. Only selected image, +structural, and supporting jobs together. Only selected image, lettering, or illustrated-icon jobs with plausible page roles create image resources; ordinary SVG/emoji icons retain their curated-pool boundary. Omitting any carrier is valid after this review; Quick speed, resolved style, @@ -379,7 +394,7 @@ or easier syntax never skips it. **Hard rule**: Credentials do not decide image need or the initial carrier plan. Do not inspect backend configuration or probe a provider before planning. Web acquisition retains zero-config providers; actual AI generation capability is resolved only during resource preparation, where the declared Quick no-AI replan below owns automated exhaustion. -**Default — visual grounding before a zero-image deck (may override when the full-roster carrier review finds no useful image job)**: Honor an explicit no-image requirement. When the audience must recognize, experience, compare, or choose an externally verifiable subject, place, product, or setting, plan supplied/extracted or web images. Prepare AI imagery proactively where invented or deliberately stylized expression materially improves a planned visual job; this may be a complete image or transparent elements composed with other page carriers. A zero-image result remains valid when no image job improves communication. This is a semantic decision, not an image-count quota. +**Default — visual grounding before a zero-image deck (may override when the full-roster carrier review finds no useful image job)**: Honor an explicit no-image requirement. When the audience must recognize, experience, compare, or choose an externally verifiable subject, place, product, or setting, plan supplied/extracted or web images. Prepare AI imagery proactively where invented or deliberately stylized expression materially improves a planned visual job; this may be a complete image or transparent elements composed with other page carriers. This is a semantic decision, not an image-count quota. **Mandatory — materialize a selected composable illustration family**: When the carrier resolution selects one, resolve the family before SVG authoring. @@ -396,9 +411,9 @@ with SVG icons to the page and deck fit under [`image-generator.md`](../../references/image-generator.md) §4.3; apply no coverage quota and never treat the slices as SVG inventory. -**Mandatory — proactive decorative-lettering capability scan**: During that same one-pass -carrier resolution, when the user has not forbidden AI, scan the frozen roster -for display strings anywhere in the deck. Two questions expose candidates: is +**Reference — decorative-lettering candidates**: when the user has not +forbidden AI, any display string in the frozen roster is a candidate. Two +questions expose candidates: is that wording stable, and could an artistic treatment plausibly communicate better than native type? Passing both exposes a possible AI visual job; it does not select lettering or add AI by itself. Page role, string length, line count, @@ -451,10 +466,8 @@ This carrier menu does not satisfy or replace the per-page Structure decision in Resolving one visual style, `Illus.` propensity, or generated-image rendering resolves how imagery **looks**; it resolves the source for no page. A named place, building, product, artwork, person, or other externally verifiable subject stays a web/supplied candidate no matter how illustrative the deck looks. When such a subject is deliberately not shown as itself, state that choice and its reason in the final report rather than leaving it implicit. -**Mandatory — complete Chart/Table capability review**: During that same -carrier resolution, compare every page's information model against every entry -in the already-loaded Chart and Table expression vocabularies. These -complete capability maps expose what exists; their descriptions do not rank +**Reference — Chart/Table vocabularies**: the already-loaded Chart and Table +expression vocabularies list what exists for a page's information model; their descriptions do not rank candidates or replace judgment from the actual information, and they are neither usage quotas nor whitelists. Do not select a catalog reference for qualitative shape composition. Choose at most one primary Chart/Table @@ -476,7 +489,7 @@ Prepare only the resource paths needed by the decided pages: |---|---| | Supplied/extracted image | Copy the selected file into `images/`; preserve its factual/provenance context and use the measured file rather than an invented substitute | | Image-to-PPTX reconstruction asset | In Codex, preserve identity graphics through an exact vector, deterministic redraw, sufficient source asset, or reference-based high-resolution reconstruction; keep data graphics native-and-verified or exact. For scene imagery, build the minimum registered clean-base/midground/subject/foreground group; batch padded-bbox-disjoint objects into one shared plate, then split them with grid slicing or independent nested-SVG bbox crops | -| Bundled/custom/brand SVG icon | Follow the [icon library contract](../../templates/icons/README.md), choose at most one coherent primary generic library when generic icons are useful, sync a project pool covering recurring semantics and likely page-local needs without assigning icons to pages, and add `simple-icons` marks only when actual content names the corresponding brand | +| Bundled/custom/brand SVG icon | Follow the [icon library contract](../../templates/icons/README.md), use one primary generic library per pool (`icon_sync.py` rejects mixed batches), sync the project pool without assigning icons to pages, and prepare `simple-icons` marks for brands the content names | | Formula | Create no resource file. Retain the exact source LaTeX, then choose ordinary text, an inline native marker, or a block native marker under §3; the registered SVG preview is discarded by native export | | AI image | Follow `image-base.md` + `image-generator.md`; apply only the chosen rendering preset or exact custom bases, never blend unselected catalog identities, and keep `image_prompts.json` plus its human-readable sidecar | | Web image | Follow `image-base.md` + `image-searcher.md`; keep query/status data and `image_sources.json`, including any required on-slide attribution | @@ -524,17 +537,18 @@ terminal-status rows. ## 3. Direct SVG Authoring -Always read the following fixed authoring references directly in one batch; do -not route among them one file at a time: +Read fixed references together, never file by file: [`shared-standards-core.md`](../../references/shared-standards-core.md), [`svg-effects.md`](../../references/svg-effects.md), [`native-shape-authoring.md`](../../references/native-shape-authoring.md), [`preset-shape-vocabulary.md`](../../references/preset-shape-vocabulary.md), [`semantic-svg.md`](../../references/semantic-svg.md), [`executor-structure.md`](../../references/executor-structure.md), -and [`topology-assembly.md`](../../references/topology-assembly.md). Retain -only the mode/style detail files selected during one-pass design resolution and -realize that chosen direction. Exact `*_references` define the catalog material +and [`topology-assembly.md`](../../references/topology-assembly.md). +Installed Layout/Deck structure loads +[`pptx-structure-interface.md`](../../references/pptx-structure-interface.md). +Keep one-pass-selected mode/style files and realize that direction. Exact +`*_references` define the catalog material actually used by a custom: apply one basis under its behavior, synthesize several by their stated contributions, or follow the behavior directly when none exist. @@ -627,7 +641,7 @@ Chart/Table reference and final information model are independent loading signals; load every applicable authority. Selection never makes an object native-ready or replaces the per-page Structure decision. -Keep the core's shared visual-quality / leading defaults and `svg-effects.md` §6.1 job diagnostic active while authoring, with its Visual Job Router as recall. Explicit user/template requirements and the resolved style override compatible aesthetic defaults, never technical Required / Forbidden boundaries, carrier eligibility, or native capability discovery. Treat selected style composition examples as generative vocabulary rather than a finite layout menu. +Keep the core's shared visual-quality / leading defaults active while authoring, with `svg-effects.md` §6.1's Visual Job Router as recall. Explicit user/template requirements and the resolved style override compatible aesthetic defaults, never technical Required / Forbidden boundaries, carrier eligibility, or native capability discovery. Treat selected style composition examples as generative vocabulary rather than a finite layout menu. **Per-page execution anchors**: apply the transient core-message, typography-role, semantic-color, body-frame, density, and composition anchors resolved in §2 while authoring; they guide the current run without creating a persisted planning artifact. @@ -649,19 +663,34 @@ viewBox. Template canvas is a default, not a compatibility gate; an explicit user canvas may adapt the installed visual system. The first SVG establishes the export canvas; every remaining page must match it exactly. -**PPTX structure**: author flat, Slide-local SVG only, including when a Layout or -Deck workspace is installed. In that branch, visibly realize the resolved -template rules and prototype geometry in the complete pages; do not fall back to -free design or merely explain how the template could be used. Include the -complete visible page and all resource references in each SVG; set one root -`data-pptx-page-role` from `cover`, `toc`, `section`, `content`, or `ending`, -and omit Master/Layout/layer/placeholder metadata. A request that specifically -requires reusable native Master/Layout/placeholder output is incompatible with -the lockless Quick exporter and must use the default lock-backed profile. +**PPTX structure**: runtime speed does not flatten template structure. Free +design and Brand/Style-only use author flat, Slide-local SVG and omit +Master/Layout/layer/placeholder metadata. When Layout or Deck owns structure, +author every page as a complete structured Slide SVG: preserve or deliberately +adapt the selected prototype's root Master/Layout identity, fixed layers, and +semantic slots, then place current Slide content on top. A Style contribution +never strips that structure. Only an explicit instruction to use the workspace +as visual language while discarding its structure permits flat output. + +Structured Quick pages use the same all-or-none SVG contract as Default: once +any page declares PPTX structure metadata, every page root declares non-empty +Master/Layout keys and picker names, and every reused Layout repeats an +identical fixed-layer/slot contract. The current agent may author a new complete +Layout under the selected Master when the frozen application paragraph calls +for adaptation; it never infers ownership from repeated Slide-local geometry. +The lockless final checker and exporter derive `structured` solely from that +complete SVG roster. Include all visible content and resource references in each +SVG. Flat pages set one root `data-pptx-page-role` from `cover`, `toc`, +`section`, `content`, or `ending`; structured pages omit it. **Typography**: name a concrete target-installed/approved PowerPoint family under [`shared-standards-core.md`](../../references/shared-standards-core.md) -§4.1; do not depend on a lock or generated font asset. +§4.1; do not depend on a lock or generated font asset. Before P01, measure one +representative line per recurring text role with +`python3 ${SKILL_DIR}/scripts/text_measure.py measure` and keep "≈ N chars per +W px" in active context; the final checker's module check adds DrawingML +wrapping headroom, so characters × font size underestimates by roughly 15%. +Those per-role constants cover most lines; measure only lines that approach a limit, many at once through `--stdin`, and `wrap` only a genuinely long paragraph. **Generation pacing**: the current main agent hand-writes the SVG roster in order. Use P01 to calibrate visual identity and cover-specific expression; use @@ -696,12 +725,12 @@ already present. After every page and required referenced resource exists, run the Quick branch of [`verify-charts`](../stages/verify-charts.md) when any data-driven chart was -authored. Complete all coordinate repairs first; then run the one lockless final -SVG check: +authored. Complete all coordinate repairs first, then use the one lockless +final check to prove the pages were authored in canonical compact form: ```bash python3 ${SKILL_DIR}/scripts/svg_quality_checker.py <project_path> \ - --quick-generate --stage final --json + --quick-generate --canonical-authoring --stage final --json ``` Fix every blocking error and rerun the same command. @@ -764,9 +793,14 @@ python3 ${SKILL_DIR}/scripts/svg_to_pptx.py <project_path> \ `--quick-generate` reads `svg_output/` as the page source and resolves the project-local assets referenced by those SVGs. It infers one consistent canvas, -uses a lockless flat PowerPoint package, and does not force-disable ordinary -export options. Notes, Custom Animations, and narration remain off unless -selected by the agent or required by the Quick video rule above. Do not run +then infers one all-page structure mode: no structure metadata produces a clean +flat package, while complete explicit Master/Layout/slot metadata produces a +structured package. It does not read or require `spec_lock.md` and does not +force-disable ordinary export options. Notes, Custom Animations, and narration remain off unless +selected by the agent or required by the Quick video rule above. Append +`--native-charts-and-tables` only when the effective delivery decision +explicitly requires native Chart/Table objects; marker presence, imported +origin, and semantic tables never activate replacement implicitly. Do not run `finalize_svg.py`. After the validated base export, run [`generate-audio`](../stages/generate-audio.md) when Narration Audio is enabled; it owns page audio/SRT, narrated PPTX, the optional raw native MP4, and the @@ -798,6 +832,8 @@ or lock. - [x] Every selected formula uses the checker-valid ordinary/inline/block form with a matching visible SVG preview and no formula image resource - [x] Every selected hyperlink uses a checker-valid inline/whole-object anchor and an exact external or same-deck target - [x] Resolved SVG pages and their project-local references exist +- [x] The frozen Template Application paragraph was applied; every installed Layout/Deck SVG was read, and any page-specific exception names an exact SVG basename +- [x] Quick structure matches the installed template capability: free/Brand/Style-only pages are flat, while a Layout/Deck structure owner remains explicit and all-page consistent unless the user explicitly requested visual-only use - [x] Every role declared by an installed template spec is locatable in the finished pages, or its non-use is deliberate — checked per installed spec, not from memory - [x] Every triggered capability-specific preparation and pre-checker verification completed - [x] The current final report's carrier receipt was compared with the retained page jobs and any factual contradiction was repaired before export, without treating counts as quotas diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/routing.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/routing.md index 652ca307..0dac0909 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/routing.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/routing.md @@ -1,5 +1,5 @@ --- -description: Deterministic selection among PPT Master's four top-level artifact routes. +description: Deterministic selection among PPT Master's three top-level artifact routes. --- # Routing Rules @@ -16,10 +16,10 @@ route selection. After selection, the active runtime authority owns execution. | Rule | Behavior | |---|---| -| One artifact lifecycle | Every request enters Generate PPTX, Create Template, Fill Native PPTX, or Enhance Native PPTX | +| One artifact lifecycle | Every request enters Generate PPTX, Create Template, or Edit Native PPTX | | Supporting documents are not top-level routes | Create Template child workflows, generation profiles, stages, and governance documents refine the selected route; never offer them as competing top-level routes | | Missing prerequisite | State the missing prerequisite and stop that route; do not invent an alternative | -| Ambiguous existing-deck request | Ask one discriminator question only when needed: regenerate visible slides, fill native slide shells with new content, or preserve slides and add native behavior? | +| Ambiguous existing-deck request | Ask one discriminator question only when needed: regenerate the visible design (Generate), or preserve the native deck and edit it (Edit Native PPTX)? | | Explicit user override | Honor explicit route instructions only when the route preconditions are satisfied | **Forbidden — route-choice menus**: Do not present multiple implementation paths when the request already matches one row in §2. Ordinary design choices remain at the selected route's existing confirmation gate. @@ -32,8 +32,7 @@ route selection. After selection, the active runtime authority owns execution. |---|---|---|---|---|---| | Generate PPTX | Create, reconstruct, or visually regenerate a presentation/video from sources or a topic; templates remain optional | Image to PPTX: [`image-to-pptx`](./profiles/image-to-pptx.md), always Quick; Beautify: [`beautify-pptx`](./profiles/beautify-pptx.md), Default or Quick; ordinary [`generate-pptx`](./generate-pptx.md) / [`quick-generate`](./profiles/quick-generate.md) | Facts exist or research can gather them; Image to PPTX also requires Codex and an ordered page-frame roster | Author SVG pages and export a new PPTX | Default: spec/lock/SVG/PPTX; Quick: optional source/resource artifacts, no spec/lock, SVG/PPTX; either may derive narrated PPTX/MP4 | | Create Template | Create a reusable brand/style/layout/deck template from one or more PPTX/SVG files, images/PDFs, direct or file-based text, documents/websites, brand assets, or a mixed reference bundle | [`create-template`](./create-template.md) | A reusable-template request exists; reference material is optional, and project scope additionally requires an initialized target project | Author a new portable workspace; never modify any reference file in place | Workspace with required `templates/`, optional `images/` / `icons/`, and optional review `exports/` | -| Fill Native PPTX | Use a raw PPTX's native slide shells and replace/fill content | [`template-fill-pptx`](./template-fill-pptx.md) | Source PPTX plus new material/topic | Clone and patch PPTX through OOXML; no SVG pipeline | New filled PPTX in project `exports/` | -| Enhance Native PPTX | Keep a finished PPTX's visible slides stable while adding notes, audio, timings, or transitions | [`native-enhance-pptx`](./native-enhance-pptx.md) | Finished source PPTX exists | Append/update scoped OOXML parts; no slide regeneration | New enhanced PPTX in project `exports/` | +| Edit Native PPTX | Keep an existing PPTX's native design: fill it with new content, edit or restructure selected pages, or add notes, narration, timings, or transitions with visible slides untouched | [`edit-native-pptx`](./edit-native-pptx.md) | Source PPTX exists; new material only when content changes | `pptx_to_svg.py --roundtrip` workspace: unchanged pages restore byte-for-byte, edited pages rebuild only edited objects, notes/motion overlay | New PPTX in workspace `exports/` | --- @@ -46,7 +45,8 @@ route selection. After selection, the active runtime authority owns execution. | The effective delivery purpose is recorded, self-running, or video-directed | Inside the already selected Default or explicit Quick runtime, load [`video-design`](../references/video-design.md) before whole-solution/page planning. This is a conditional design reference, not a profile or fifth route; notes, animation, audio, and optional native MP4 remain owned by their existing stages | | Explicit quick/fast, skip-strategy, or direct SVG-to-PPTX intent without an active fidelity profile | Load [`quick-generate`](./profiles/quick-generate.md) directly without loading `generate-pptx.md`: prepare sources/resources as needed, let the current agent decide without interaction, directly apply at most one exact workspace root per kind supplied for this run, otherwise use free design, omit Strategist/Confirm UI/spec/lock, hand-author SVG, run the lockless final checker, and export the final PPTX | | Topic only, or supplied sources leave planning-critical factual gaps | Run [`topic-research`](./stages/topic-research.md) inside the selected Generate profile's source preparation: immediately for topic-only input, or after conversion and reading for source-backed input; research only the identified gaps | -| Existing PPTX may be split, merged, dropped, reordered, or re-outlined | Treat the PPTX as source content through the selected Generate authority's source intake; continue Default unless explicit Quick intent selected that runtime | +| Existing PPTX must be split, merged, or re-outlined into newly designed pages | Treat the PPTX as source content through the selected Generate authority's source intake; continue Default unless explicit Quick intent selected that runtime | +| Existing PPTX pages are dropped, reordered, or repeated without redesign | Not Generate: route to Edit Native PPTX, whose `page_plan.json` owns selection, order, and repetition | | Default Generate reaches planning | Step 3 prepares template candidates without interaction. Stage 1 then confirms the communication contract and free-design/template choice together; only a confirmed non-free choice runs [`apply-template-workspace`](./stages/apply-template-workspace.md) before Stage 2 | | Explicit current brand/style/layout/deck workspace root outside Image to PPTX | Default Generate preserves the exact path as a Stage-1 template candidate; Quick Generate validates and installs it directly without Steps 3–4 or Confirm UI. Classify it as `library` only when its normalized root exactly matches a registered index entry; otherwise retain `explicit`. Consume the workspace root, never only its inner `templates/` directory | | Split-mode project resumes in a fresh chat | Run [`resume-execute`](./stages/resume-execute.md) inside the active Generate route | @@ -57,7 +57,7 @@ route selection. After selection, the active runtime authority owns execution. | User requests preview, selection, or annotation application outside Image to PPTX | Use the default Generate pipeline and run [`live-preview`](./stages/live-preview.md) at the stage defined there; explicit Quick + preview intent falls back to default rather than dropping preview. Image to PPTX remains Quick-only and uses its mandatory canonical-frame recomposition comparison instead of this interactive stage | | User requests page transitions, auto-advance, or deck-wide animation settings without page-specific motion planning or an existing `animations.json` | Load [`animations`](../references/animations.md) and apply its export-level contract | | `<project_path>/animations.json` already exists, the user explicitly requests per-slide/object-level animation control, or the effective Custom Animations outcome in `design_spec.md §I` is enabled | Run [`customize-animations`](./stages/customize-animations.md) after the final SVG quality gate and any enabled speaker-note pass, before Generate Step 7. A §IX `Motion suggestion` informs an active pass but never triggers it alone | -| Generate PPTX receives an explicit narration request or has effective Narration Audio enabled in `design_spec.md §I`; Enhance Native PPTX has a confirmed `audio.enabled: true` module | Run [`generate-audio`](./stages/generate-audio.md) after the owning route's notes/export readiness; Generate audio implies effective Speaker Notes enabled | +| Generate PPTX receives an explicit narration request or has effective Narration Audio enabled in `design_spec.md §I`; Edit Native PPTX has narration confirmed in its plan | Run [`generate-audio`](./stages/generate-audio.md) after the owning route's notes readiness; audio implies notes on every output page | **Hard rule — fidelity profiles, not fifth routes**: Image to PPTX and Beautify change different source/page invariants and are mutually exclusive. Image to @@ -69,12 +69,14 @@ loads both runtimes. stays inside Generate PPTX but owns an explicit SVG → PPTX short circuit. Page count alone never activates or blocks it. Conversion, bounded research, and project-local resources remain available. Package capabilities may be requested -or agent-selected. Quick may consume exact Brand/Style/Layout/Deck workspaces as -flat authoring inputs, with at most one contribution per kind. All four kinds -may combine; Layout takes structural precedence over Deck. A multi-kind project -root contributes all of its specs atomically; -compiling reusable Master/Layout/placeholder structure still requires the -default lock-backed Generate pipeline. Once selected, Quick +or agent-selected. Quick may consume exact Brand/Style/Layout/Deck workspaces, +with at most one contribution per kind. All four kinds may combine; Layout takes +structural precedence over Deck. A multi-kind project root contributes all of +its specs atomically. Brand/Style-only and free-design Quick pages remain flat; +when Layout or Deck owns structure, Quick authors the complete explicit +Master/Layout/slot metadata and its lockless checker/exporter infers structured +output from that all-page SVG contract. Runtime choice controls interaction and +durable planning, not whether an installed structural template survives. Once selected, Quick is the complete runtime procedure and never loads `generate-pptx.md`; Default never loads `quick-generate.md`. Image to PPTX is the narrow profile-owned Quick activation; Beautify may select either runtime, but never both. @@ -86,7 +88,7 @@ Quick activation; Beautify may select either runtime, but never both. **Hard rule — no direct structure grafting**: An existing PPTX or SVG is never upgraded in place by adding Master/Layout/placeholder structure. If reusable native structure is required: 1. Run [`create-template`](./create-template.md) to produce a separate validated workspace. -2. Pass that workspace root to [`generate-pptx`](./generate-pptx.md) as a Stage-1 template candidate. +2. Pass that workspace root to Default as a Stage-1 template candidate, or supply its exact root directly to explicit Quick Generate. 3. Author new structured SVG pages whose Master/Layout contract exists from their first generated draft. 4. Export a new PPTX from those pages. @@ -97,10 +99,9 @@ When a PPTX already contains native Master/Layout parts, `create-template` mirro | Input | Route behavior | |---|---| | One or more images containing page frames + explicit final-deck reconstruction intent | Generate PPTX with the Codex-supported, Quick-only [`image-to-pptx`](./profiles/image-to-pptx.md); normalize page frames first and do not infer reusable native structure from pixels | -| Raw PPTX called a template + new content | Fill Native PPTX unless the user explicitly asks for a reusable template workspace | +| Raw PPTX called a template + new content | Edit Native PPTX unless the user explicitly asks for a reusable template workspace | | Any supported reference bundle or direct-text brief + reusable template request | Create Template | -| Current template workspace root + content | [`generate-pptx`](./generate-pptx.md) Stage-1 template choice | -| Legacy-flat Brand/Layout/Deck root satisfying its current kind contract; Layout/Deck also require current structured SVGs | [`apply-template-workspace`](./stages/apply-template-workspace.md) compatibility reader; Style has no flat form | +| Current template workspace root + content | Default: [`generate-pptx`](./generate-pptx.md) Stage-1 template choice; explicit Quick: direct validated workspace application | | Semantic-legacy or incomplete structured package | Create a new workspace through Create Template; do not migrate in place | | Request to add a master directly to an existing PPTX/SVG | Unsupported; explain the Create Template → Generate PPTX lifecycle | @@ -132,9 +133,9 @@ or reusable scenario/content semantics. | Artifact state | Narration route | |---|---| | Main-generated project with notes and exported deck | Shared [`generate-audio`](./stages/generate-audio.md) stage | -| Arbitrary finished PPTX that must preserve visible slides | Enhance Native PPTX; its narration module invokes the same shared audio-stage rules | +| Arbitrary finished PPTX that must preserve visible slides | Edit Native PPTX; its narration module invokes the same shared audio-stage rules against the round-trip workspace | -Object animation for generated SVG projects uses the animation stage. Native PPTX routes preserve existing object-animation fingerprints and do not silently claim an animation-editing capability. +Object animation for generated SVG projects uses the animation stage. Edit Native PPTX preserves source motion by default and writes requested motion as an overlay per [`animations.md`](../references/animations.md). --- diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/apply-template-workspace.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/apply-template-workspace.md index dc58be51..c2276ce7 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/apply-template-workspace.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/apply-template-workspace.md @@ -18,7 +18,6 @@ resolve to one of these current contracts: | Input shape | Spec and SVG source | Asset source | |---|---|---| | Current workspace root | `<root>/templates/design_spec.md`, or one `design_spec.<kind>.<id>.md` per kind, plus `<root>/templates/` | Existing `<root>/images/` and `<root>/icons/` | -| Compatible legacy-flat Brand/Layout/Deck root | `<root>/design_spec.md`; Layout/Deck also require current-contract SVGs under `<root>/` | Package-local files | | Current Create Template handoff | Its exact validated library or project workspace root | Existing portable sibling `images/` and `icons/`; already installed only when the root is the target project | Spec naming and kind declaration follow [`templates/README.md`](../../templates/README.md); a root exposing several kind-qualified specs contributes all of them. Do not accept only another project's inner `templates/` directory because that omits sibling assets. @@ -43,9 +42,9 @@ may combine only with non-overlapping kinds. Default permits one explicit root beside registered choices; Quick applies the same kind constraint. Reject duplicate kinds before validation. -**Hard rule — raw source boundary**: A raw PPTX is not a template workspace. Raw PPTX plus new content uses [`template-fill-pptx`](../template-fill-pptx.md). When the user wants reusable SVG/template generation, run [`create-template`](../create-template.md) first; its validated workspace-root handoff becomes a Stage-1 candidate and is preselected only when it is the sole supplied root. Never add Master/Layout/placeholder structure directly to an existing PPTX or SVG project. +**Hard rule — raw source boundary**: A raw PPTX is not a template workspace. Raw PPTX plus new content uses [`edit-native-pptx`](../edit-native-pptx.md). When the user wants reusable SVG/template generation, run [`create-template`](../create-template.md) first; its validated workspace-root handoff becomes a Stage-1 candidate and is preselected only when it is the sole supplied root. Never add Master/Layout/placeholder structure directly to an existing PPTX or SVG project. -**Compatibility gate**: Reject semantic-legacy or incomplete structured packages, including old baseline/distillation metadata, incomplete Master identity, or legacy direct atomic placeholders. Create a new current workspace through Create Template; use the original PPTX when native topology must be preserved. A legacy-flat Brand/Layout/Deck directory is readable only when it satisfies its current kind contract; Layout/Deck also require a current structured SVG contract. Style has no legacy-flat form. +**Current-contract gate**: Reject flat-root, semantic-legacy, or incomplete structured packages, including old baseline/distillation metadata, incomplete Master identity, or legacy direct atomic placeholders. Create a new current workspace through Create Template; use the original PPTX when native topology must be preserved. ## 2. Read the Matching Schema @@ -66,11 +65,10 @@ registration: Brand/Style are roster-free, the active structure validates its roster, and a shadowed Deck still validates its declared contract: ```bash -python3 skills/ppt-master/scripts/svg_quality_checker.py "<workspace_root>/templates" --template-mode +python3 skills/ppt-master/scripts/svg_quality_checker.py "<workspace_root>/templates" --template-mode --canonical-authoring ``` -Any error blocks installation. A compatible legacy-flat root uses its own root -as the checker target. +Any error blocks installation. ## 3. Structured Preflight @@ -86,8 +84,8 @@ Before copying a Deck or Layout workspace, inspect every SVG root and slot. Bran Validate each normalized root once. Resolve the effective structural owner as Layout when selected, otherwise Deck; install only its SVG/non-bitmap -structural payload, but install every selected spec. A library or -compatible legacy-flat root contributes one bare `design_spec.md`; install it +structural payload, but install every selected spec. A library root contributes +one bare `templates/design_spec.md`; install it as `design_spec.<kind>.<id>.md`, where `<id>` comes from the matching frontmatter id field. A current project root may contribute several qualified specs; preserve each validated qualified filename. Never merge spec bodies, @@ -96,7 +94,7 @@ and never copy one multi-kind root's shared SVG or asset pool once per kind. | Installed file | Meaning | |---|---| | `templates/design_spec.<kind>.<id>.md` | A template contribution installed into or authored in this project | -| `templates/design_spec.md` | Library or compatible legacy-flat source shape only; never valid beside qualified project specs | +| `templates/design_spec.md` | Library source shape only; never valid beside qualified project specs | For every copied spec, prepend exactly one provenance line under its H1, then leave the rest of the document untouched. An in-place root is not rewritten: @@ -109,8 +107,12 @@ leave the rest of the document untouched. An in-place root is not rewritten: - Copy every selected spec from the root to its resolved qualified destination. - If the root contributes the effective structural owner, copy its declared - SVG roster and other non-bitmap structural files once. Do not copy a Deck - roster when Layout is selected; its structure is shadowed by design. + SVG roster and other non-bitmap structural files once, including mirror + `source_themes.json` when present. Do not copy a Deck roster when Layout is + selected; its structure is shadowed by design. Preserve inline + `<metadata type="application/json">` and + `data-pptx-native-authority="json"` exactly; semantic Chart/Table JSON never + moves into a sidecar during installation. - Copy the root's real package-owned `images/` and `icons/` files once. A Style-only root has none; reject a Style-only library package carrying asset or review payloads. @@ -121,12 +123,10 @@ After that root-level copy, kinds have these downstream effects: | Kind | Consumption behavior | |---|---| | `brand` | Identity is constrained; structure remains free unless the selected set also includes Layout or Deck. | -| `style` | Expose reusable direction/method without identity truth, page prototypes, or native structure. Default Style-only and Style + Brand derive `template_reuse_scope: style` and stay flat; Style + Layout/Deck follows the selected structure plan. Quick always realizes the resolved combination as flat pages. A Style workspace never activates visual review. | -| `layout` | Expose the actual reusable structure and take structural precedence over Deck; Default Strategist later inspects these prototypes, while Quick's current agent uses them for immediate flat authoring decisions in active context. | +| `style` | Expose direction/method without identity, prototypes, or structure. Style-only and Style + Brand stay flat; Style + Layout/Deck follows that structure owner. Style never activates visual review. | +| `layout` | Expose reusable structure and take precedence over Deck; Default plans against its prototypes, while Quick reads the complete roster and authors its Master/Layout/slot contract directly. | | `deck` | Expose descriptive application context and identity. It also supplies structure and the actual prototype roster only when no Layout is selected. | -For a compatible legacy-flat package, route SVG/spec/non-bitmaps to project `templates/`, bitmaps to project `images/`, and declared icons to project `icons/`. Do not infer legacy Master/Layout semantics from the flat directory shape. - **Atomic install preflight**: 1. Resolve every source and destination path. @@ -150,9 +150,20 @@ agent before direct authoring, and every later role read only is installation input, not a later prompt source. If source and target are the same project root, that in-place root already satisfies this boundary. -Template SVGs are authoring prototypes, not export-time overlays. The generated page remains complete in `svg_output/`; `page_layouts` selects the complete prototype and its explicit structure contract for authoring. -Quick instead realizes the selected prototypes into complete flat, Slide-local -SVGs and never writes `page_layouts` or Master/Layout/placeholder metadata. +Template SVGs are complete Slide authoring prototypes, not export-time overlays. They already resolve Master + Layout context, so `page_layouts` selects one directly. Standalone Master/Layout definition SVGs are invalid; an unselected authored Slide prototype may still back a reusable Layout definition. +Default records `page_layouts` and the durable structure lock. Quick has no such +planning artifact: it freezes the natural-language application paragraph in +active context and writes the selected Master/Layout/slot contract directly +into the complete output SVG pages. Quick stays flat only without a Layout/Deck +structure owner or under an explicit visual-only instruction. + +For a template-owned Chart/Table carrying +`data-pptx-native-authority="json"`, the installed inline JSON remains the +object's data/native authority. A generated page may retain its existing compact +preview or regenerate an approximate preview from updated JSON, but it must not +derive replacement JSON from that preview. Keep the authority marker and JSON +inside the generated SVG so default fallback and explicit native export remain +two renderings of one object contract. ## 5. Segment Precedence Is Resolved While Reading diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/customize-animations.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/customize-animations.md index 74126e21..5cc9136f 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/customize-animations.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/customize-animations.md @@ -146,7 +146,7 @@ changed. Use the owning route's checker form; Quick Generate must add its lockless profile flag: ```bash -python3 skills/ppt-master/scripts/svg_quality_checker.py <project_path> --stage final --json +python3 skills/ppt-master/scripts/svg_quality_checker.py <project_path> --canonical-authoring --stage final --json # Quick Generate: insert --quick-generate before --stage. ``` diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/generate-audio.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/generate-audio.md index a749e7cb..20ab51eb 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/generate-audio.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/generate-audio.md @@ -11,20 +11,20 @@ This stage is **context-independent**: it reads `notes/*.md` and queries the sel **Trigger**: In Generate PPTX, run when the effective `Narration Audio` outcome in `design_spec.md` §I is `enabled`; a later explicit request first updates that outcome and its provenance. Quick Generate instead runs when the request or -current agent's active-context decision selects narration. In Enhance Native -PPTX, run when its confirmed enhancement plan has `audio.enabled: true`. +current agent's active-context decision selects narration. In Edit Native +PPTX, run when the confirmed plan enables narration. **Hard dependency — speaker notes**: Audio requires complete per-slide speaker notes. Generate PPTX additionally requires its effective `Speaker Notes` -outcome to be enabled; Enhance Native PPTX follows its confirmed notes/audio -plan, where enabling audio also enables notes. Quick records the same dependency +outcome to be enabled; Edit Native PPTX follows its confirmed plan, where +narration requires a note for every output page. Quick records the same dependency in active context. Do not enter audio generation while the owning route's notes are missing or incomplete; generate and validate those notes first, then resume this stage. ## When to Run -- Per-page narration files exist at `notes/*.md`. In Generate PPTX, split `notes/total.md` during Step 7.1. In Enhance Native PPTX, the notes module writes numeric files such as `001.md`. +- Per-page narration files exist at `notes/*.md`. In Generate PPTX, split `notes/total.md` during Step 7.1. In Edit Native PPTX, notes are `notes/<svg-stem>.md` keyed by output page; the round-trip roster comes from `page_plan.json` and copies inherit source notes. - Default mode: `edge-tts` is installed (`python3 -m pip install edge-tts`). - The stage is page-level only: one note becomes `audio/<stem>.<audio-ext>` plus `audio/<stem>.srt` on provider-timed paths, or one audio file with Qwen / explicit CosyVoice audio-only mode. Never substitute one long track or automatic splitting. - Final/literal script notes are synthesized verbatim. Source SRT timecodes are pacing evidence only; new provider timing owns the generated audio/SRT set. @@ -54,9 +54,9 @@ this stage. If per-slide notes are missing, recover through the owning route. Generate PPTX returns to its enabled notes branch and then runs -`total_md_split.py <project_path>`; Enhance Native PPTX returns to -`native-enhance-pptx` Step 6 and writes numeric notes directly. Never run the -Generate splitter against a Native Enhance project. +`total_md_split.py <project_path>`; Edit Native PPTX returns to +[`edit-native-pptx`](../edit-native-pptx.md) §6 and writes `notes/<svg-stem>.md` +per output page. Never run the Generate splitter against a round-trip workspace. --- @@ -127,15 +127,15 @@ when narration-cue sync or subtitle delivery needs page-local SRT; on the native-export branch, audio-only narration can still calibrate the sound mix from its complete per-page tracks. -**Default / Enhance Native — one-shot interaction (mandatory)**: +**Default / Edit Native — one-shot interaction (mandatory)**: -For Default or Enhance Native, send one message that resolves all five configuration decisions and recommends each value. Before offering automatic video export, run `python3 skills/ppt-master/scripts/powerpoint_video.py --check`; do not present an unavailable local capability as executable. Do NOT split into multiple rounds. +For Default or Edit Native, send one message that resolves all five configuration decisions and recommends each value. Before offering automatic video export, run `python3 skills/ppt-master/scripts/powerpoint_video.py --check`; do not present an unavailable local capability as executable. Do NOT split into multiple rounds. An explicit slideshow-capture choice does not run this availability check; it uses the manual Windows playback handoff below. -**Cloned-voice fast path**: if the user mentioned a cloned voice / 克隆音色 / 复刻音色 / "my own voice" along with a `voice_id`, skip the voice-recommendation list — set the named provider (`elevenlabs` / `minimax` / `qwen` / `cosyvoice`) and pin that `voice_id`. Quick applies its exception above; Default and Enhance Native confirm only rate + embed + video. +**Cloned-voice fast path**: if the user mentioned a cloned voice / 克隆音色 / 复刻音色 / "my own voice" along with a `voice_id`, skip the voice-recommendation list — set the named provider (`elevenlabs` / `minimax` / `qwen` / `cosyvoice`) and pin that `voice_id`. Quick applies its exception above; Default and Edit Native confirm only rate + embed + video. -**Message template** (Chinese; translate to user's chat language if different). “Embed” means caller-specific integration: SVG re-export for Generate PPTX, or native OOXML application for Enhance Native PPTX. +**Message template** (Chinese; translate to user's chat language if different). “Embed” means caller-specific integration: SVG re-export for Generate PPTX, or `svg_to_pptx.py --roundtrip --recorded-narration audio` for Edit Native PPTX. > 检测到 notes 主语言为 **<语言>**(locale: `<locale>`)。基于 deck 调性(<风格>),我推荐以下配置: > @@ -169,8 +169,9 @@ uses the manual Windows playback handoff below. ## Step 4: Execute (no further interaction) **Blocking notes preflight**: `notes_to_audio.py` resolves the complete notes -roster from `svg_output/*.svg` on Generate projects or -`analysis/slide_index.json` on Native Enhance projects. Before any TTS request, +roster from `svg_output/*.svg` on Generate projects or from `page_plan.json` / +the identity roster on Edit Native PPTX round-trip workspaces (copies inherit +source notes). Before any TTS request, every expected note must exist, be readable, and contain spoken text. Exit code `2` returns the caller to its notes-generation step; never continue with partial audio generation. @@ -408,7 +409,7 @@ that handoff remains incomplete until a real capture is accepted. | Caller | After audio generation | |---|---| | Generate PPTX | Derive narration-cued motion when selected; otherwise pass canonical motion, inherit base motion, or use explicit all-motion-off. Export with `--recorded-narration audio`; Quick also passes `--quick-generate --with-notes`. Native video uses conversion trace plus raw export and cue mix as required. Explicit capture returns the narrated PPTX for the handoff above, skips trace-only sound work and mixing, then aligns subtitles against the accepted capture. | -| Enhance Native PPTX | Return to [`native-enhance-pptx`](../native-enhance-pptx.md) Step 9. Native video passes its final PPTX to `powerpoint_video.py`; explicit capture uses the same handoff above and skips mixing. | +| Edit Native PPTX | Return to [`edit-native-pptx`](../edit-native-pptx.md) §7 and export with `--roundtrip --recorded-narration audio --use-narration-timings`. Native video passes its final PPTX to `powerpoint_video.py`; explicit capture uses the same handoff above and skips mixing. | For Qwen or explicit CosyVoice audio-only mode, embed/export the audio normally but skip `narration_timing.json`, `narration_sync.py animations`, SRT merge, and diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/live-preview.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/live-preview.md index 97ce9188..98183fb4 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/live-preview.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/live-preview.md @@ -36,12 +36,12 @@ python3 ${SKILL_DIR}/scripts/svg_editor/server.py <project_path> --daemon The launcher starts the server in the background on its selected port, waits for `GET /api/health`, records the actual pid + port in `<project_path>/live_preview/lock.json`, opens the browser when possible, and edits `<project_path>/svg_output/` in place. After it prints the running URL, tell the user in their language, in one short message: -- editor is at the URL reported by the launcher, e.g. `http://127.0.0.1:5050` +- editor is at the URL reported by the launcher, e.g. `http://127.0.0.1:6060` - **Direct edit** (deterministic tweaks — wording, color, coordinates, SVG attributes): select an element → change the controls in the right panel → preview updates immediately, but nothing is written to `svg_output/` until **Apply changes**. `Ctrl+Z` or the **Undo** button drops staged edits step by step; applied changes are logged to `<project>/live_preview/edits.jsonl`. Re-export stays chat-driven and separate: say "re-export" / "重新导出" to refresh the PPTX. - **Annotate** (changes that need AI judgement / re-layout): select an element → write the instruction, optionally starting from a quick type such as move / resize / replace image / copy / relayout → click **Add annotation** to stage it → click **Apply changes** to write annotation markers → return to the chat and say `apply my annotations` (or quote the browser prompt) - to skip the editor, just describe the change in chat -Launch immediately — the user already asked for preview. Report the actual URL from the output or project lock; never infer it from `5050`. Remote access → see the appendix. +Launch immediately — the user already asked for preview. Report the actual URL from the output or project lock; never infer it from `6060`. Remote access → see the appendix. --- @@ -79,7 +79,7 @@ Triggered by the user signals listed in "When to Run". - **Unsaved-work guard**: staged direct edits and annotation changes (added or removed) live in server memory until **Apply changes**; closing the tab triggers the browser's native "leave site?" prompt while any are unapplied, since an idle timeout or process kill would drop them. - **Re-export is chat-driven**: applying changes updates `svg_output/` only. Refreshing the PPTX (finalize + svg_to_pptx) stays a chat step — the editor never runs the export pipeline or presents browser-side export as part of applying edits. - **Stop conditions**: the service stops when the user clicks **Exit preview** in the browser, asks in chat to stop it, the idle timeout fires, or the process is killed externally. -- **Port**: without `--port`, use the first free port from `5050`; `--port N` binds `N` strictly and fails if unavailable. Read the actual URL from launch output or `<project_path>/live_preview/lock.json`. +- **Port**: without `--port`, use the first free port from `6060`; `--port N` binds `N` strictly and fails if unavailable. Read the actual URL from launch output or `<project_path>/live_preview/lock.json`. - **Idle timeout**: plain mode `900s`, `--live` mode `7200s`; override with `--timeout <seconds>` (`0` disables). - **Single instance per project**: `<project_path>/live_preview/lock.json` records the running pid + actual port and is the discovery source for project-local consumers. A second launch reuses the live instance unless an explicit, different `--port N` was requested; that mismatch fails and requires `--shutdown` before restart. Stale locks (dead pid) are overwritten on the next launch. Legacy root locks at `<project_path>/.live_preview.lock` are still detected when they point to a live process. - **Transient ids**: each element gets a temporary `_edit_N` id while the editor is running. On save, only annotated elements keep their id; unannotated `_edit_N` ids are stripped before write-back. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/verify-charts.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/verify-charts.md index a1d94049..fd8feaaf 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/verify-charts.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/stages/verify-charts.md @@ -140,9 +140,11 @@ After updating any page, follow the active profile's checker order. Default reruns its quality checker to confirm nothing broke: ```bash -python3 skills/ppt-master/scripts/svg_quality_checker.py <project_path> +python3 skills/ppt-master/scripts/svg_quality_checker.py <project_path> --canonical-authoring --stage final --json ``` +This rerun writes the current `final` report required by Step 7.3. + Quick completes every chart comparison/repair first, then returns to `quick-generate.md` §4 and runs its one lockless final checker. Do not insert a checker call between Quick chart pages. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/template-fill-pptx.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/template-fill-pptx.md deleted file mode 100644 index b3202ad1..00000000 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/template-fill-pptx.md +++ /dev/null @@ -1,379 +0,0 @@ ---- -description: Fill Native PPTX route — use a native PowerPoint template deck, select fitting pages, and fill new material back without SVG conversion ---- - -# Fill Native PPTX Route - -> Run when the user provides a raw `.pptx` template plus new content / a new topic and asks to generate a `.pptx` from that template. Typical requests include "use this PPT template to generate a PPTX", "fill this deck with the new content", "fill this back into the template", "replace the copy in this PowerPoint", or "keep the original PowerPoint pages and swap in this material". This route treats the existing `.pptx` as a native slide library and produces a new `.pptx` by selecting, cloning, and patching source slides. - -This route is **independent** from the SVG generation pipeline. It treats the source PPTX as a native template / slide library, keeps the original PowerPoint design intact, and writes a new `.pptx` by cloning selected source slides and replacing text directly in OOXML. - -**Boundary against template-based generation**: run this route for raw PPTX template + generated PPTX requests. Skip this route only when the user explicitly wants a reusable template workspace or an SVG-generated deck that can freely select / repeat / skip / adapt template pages. In that case, they must run [`create-template.md`](./create-template.md) first and then provide the generated workspace root to Generate PPTX. - -| User wants | Route | -|---|---| -| Generate a PPTX from a raw PPTX template | This route | -| Directly edit / fill cloned PPTX slides | This route | -| Create a reusable design asset from the PPTX | `create-template` | -| Generate a new PPT from a reusable template package | Main pipeline Step 3 with the explicit template directory path | -| Generate through the SVG pipeline directly from a raw PPTX "template" | Not allowed; create the template package first | - -## When to Run - -Recognize requests that combine an existing PowerPoint template with new content or a topic and ask for a generated `.pptx` without explicitly requesting the reusable SVG/template-package route: - -| Pattern | Example | -|---|---| -| Existing `.pptx` + "fill back" intent | "Use this deck and fill in the attached material" | -| Raw PPTX template + generated PPTX | "Use this PowerPoint template to generate a PPTX about this topic" | -| Existing `.pptx` + selective reuse | "Do not keep every page; only use the slides that fit" | -| Existing `.pptx` + copywriting replacement | "Keep the original design and replace the copy with this text" | -| Native PPT template fill | "Use this PowerPoint template for this content and fill the slides directly" | -| Direct wording | "Fill this deck with the new content" | - -**Hard rule**: Do not run `pptx_to_svg.py`, `pptx_template_import.py`, `finalize_svg.py`, or `svg_to_pptx.py` for this route. SVG conversion is for presentation generation / template creation; this route is direct PowerPoint editing. - -**Deterministic routing rule**: do not ask a route-choice question for raw PPTX template + generated PPTX requests; route them here. If the user asks for SVG/template-workspace generation from a raw PPTX, state that `create-template` must run first and stop this route until they provide the generated workspace root. - ---- - -## Step 1: Inputs - -🚧 **GATE**: The user has provided: - -| Input | Required | Notes | -|---|---:|---| -| Source PPTX | Yes | Original design deck to reuse as a slide library | -| Content material | Yes | User text, Markdown, document, URL-derived source, or a clear topic brief | -| Target output intent | Optional | Audience, page count, tone, must-keep pages, must-drop pages | - -If the content material is only a topic with no supporting facts, gather or ask for source material first. Do not invent detailed factual content. - ---- - -## Step 2: Create the Project Workspace - -Create a dedicated project directory under `projects/`. Do not write outputs directly into `projects/` root. Reuse the standard project manager so source import rules stay consistent with the rest of the repository: - -```bash -python3 skills/ppt-master/scripts/project_manager.py init "<project_name>" -python3 skills/ppt-master/scripts/project_manager.py import-sources "<project_dir>" "<source.pptx>" "<material...>" -``` - -The source PPTX slide size remains the native canvas authority. Do not add an -initialization `--format` unless that source canvas has already been verified -as an exact registered format. - -**Source import rule**: `project_manager.py import-sources` moves only sources under repository `projects/` and copies all others. `--copy` preserves a projects-local input; `--move` never widens that scope. Reuse this path. - -Use this fixed layout: - -| Path | Required content | -|---|---| -| `<project_dir>/sources/` | Source PPTX and user-provided text / Markdown / converted materials | -| `<project_dir>/analysis/` | Slide library JSON, page-selection reasoning, and final fill plan | -| `<project_dir>/exports/` | Final generated PPTX only | -| `<project_dir>/validation/` | Read-back Markdown, extracted validation assets, and validation notes | - -**Hard rule**: A template-fill project is a project, not a loose output file. The final answer must point to `<project_dir>/exports/<name>.pptx`, and all intermediate artifacts must remain inside `<project_dir>`. - ---- - -## Step 3: Extract the PPTX Intake Bundle - -`project_manager.py import-sources` automatically runs the standard PPTX intake for imported PowerPoint files and writes `<stem>.slide_library.json` into `<project_dir>/analysis/`. If you are working from a manually assembled project that does not have the intake artifact, run the template-fill analyzer directly: - -```bash -python3 skills/ppt-master/scripts/template_fill_pptx.py analyze "<project_dir>/sources/<source.pptx>" -o "<project_dir>/analysis/<stem>.slide_library.json" -``` - -Read `<project_dir>/analysis/<stem>.slide_library.json` (intake prefixes per-deck artifacts by the template deck's file stem) and identify: - -| Field | Use | -|---|---| -| `slides[].page_type` | Cover / chapter / content / ending candidate | -| `slides[].text_summary` | Current semantic purpose of the source page | -| `slides[].slots[]` | Replaceable text slots with `slot_id`, `role`, `geometry`, paragraph count, and old text | -| `slides[].slots[].role` | Title / body / label candidate hint | -| `slides[].tables[]` | Native PowerPoint tables with `table_id`, row / column counts, per-cell coordinates/text, and merge anchor/slave topology | -| `slides[].charts[]` | Native PowerPoint charts with `chart_id` and an `edit_capability` safety result derived from the actual chart XML | -| `slides[].diagrams[]` | SmartArt layout, semantic nodes, hierarchy/connections, geometry, and extraction status; inventory-only | - -**Selection rule**: Pick pages by content fitness, not by source order alone. A source page is useful only if its visible structure can carry the target message without heavy redesign. - -A page's layout already encodes a rhetorical shape — a single hero statement, a lead-then-detail split, a 2×2 comparison, a stepwise progression, a metric row. Match the source material's own logic to a page whose structure expresses that same logic; do not pour unrelated content into a slot just because it is empty. When no selected page fits a piece of content well, drop that page or that content rather than forcing it — a forced fill reads as stiff. It is fine to use fewer pages than the source deck has. - -**Layout-first planning**: Treat `<stem>.slide_library.json` as a layout inventory, not as an ordered deck outline. Before writing `fill_plan.json`, infer each reusable source page's affordance from JSON fields: - -| JSON signal | Layout planning use | -|---|---| -| `slides[].page_type` | Identify cover / TOC / chapter / ending candidates, but do not preserve their original order by default | -| `slots[].role` counts | Infer whether the page is a hero statement, comparison, multi-card list, timeline, metric row, or dense explanation | -| `slots[].geometry` | Estimate whether each text slot is a short label, medium title, body block, caption, or decorative number | -| `slots[].text_metrics.font_size_px` | Estimate text capacity together with geometry; larger type means fewer safe characters | -| `slots[].text_summary` | Read the source page's original rhetorical pattern, not its literal placeholder wording | -| `diagrams[].layout` + `nodes` | Understand the SmartArt's source meaning; template-fill preserves it unchanged and cannot map new text into it | - -**SmartArt boundary**: A selected source slide keeps its original native SmartArt parts. `check-plan` warns because the fill plan cannot replace SmartArt node text; choose another layout unless the original diagram content is intentionally retained, or explicitly accept the warning. - -**Hard rule**: The target story controls output order. Source slides may move forward, move backward, be omitted, or be reused several times when their layout matches multiple target messages. Never treat source slide order as a default outline unless the user explicitly asks to preserve it. - -**Required mapping pass**: Record a concise page-to-layout rationale in each planned slide before finalizing the plan. Use the per-slide `layout_rationale` object in `fill_plan.json` with `layout_pattern`, `why_fit`, and `risk`. This is human-review evidence that selection came from template structure rather than sequential replacement; it is not a mechanical checker gate. - ---- - -## Step 4: Build the Fill Plan - -Create a scaffold: - -```bash -python3 skills/ppt-master/scripts/template_fill_pptx.py scaffold "<project_dir>/analysis/<stem>.slide_library.json" -o "<project_dir>/analysis/fill_plan.json" --slides "1,3,4" -``` - -Then edit `<project_dir>/analysis/fill_plan.json` by hand from the source material. The plan is the single execution contract. - -**Pages are reusable**: the output is the ordered `slides` list, not a one-to-one copy of the source deck. A source page is not single-use — list the same `source_slide` as many times as you need, each entry with its own `replacements`, to drive several output slides from one good layout (e.g., reuse a single content layout for five content pages). Likewise you may omit source pages entirely and put the selected ones in any order. - -**Scaffold boundary**: `scaffold --slides` is only a convenience starter. If the final plan needs repeated source pages or a story order that differs from the template order, duplicate / reorder entries in `fill_plan.json` manually or generate the plan from `<stem>.slide_library.json`; do not let scaffold output constrain the deck structure. - -The plan structure: - -```json -{ - "schema": "template_fill_pptx_plan.v1", - "status": "draft", - "source_pptx": "projects/source.pptx", - "accepted_warnings": [ - { - "plan_slide": 3, - "slot_id": "s03_sh5", - "code": "text_capacity", - "reason": "User accepted dense wording" - } - ], - "slides": [ - { - "source_slide": 1, - "purpose": "cover", - "layout_rationale": { - "layout_pattern": "hero cover", - "why_fit": "Large title and subtitle slots fit the opening message without redesign.", - "risk": "Subtitle must stay short." - }, - "notes": "Speaker notes for this filled slide.", - "transition": "fade", - "replacements": [ - { - "slot_id": "s01_sh4", - "text": "New title" - } - ], - "table_edits": [ - { - "table_id": "s01_tbl3", - "cells": [ - {"row": 0, "col": 0, "text": "Metric"}, - {"row": 0, "col": 1, "text": "Value"} - ] - } - ], - "chart_edits": [ - { - "chart_id": "s01_ch4", - "categories": ["A", "B"], - "series": [ - {"name": "Series 1", "values": [10, 20]} - ] - } - ] - } - ] -} -``` - -**Per-slide plan discipline**: - -| Decision | Rule | -|---|---| -| `status` | Keep `"draft"` until the user has reviewed the page sequence / reuse / deletion decisions. Set to `"confirmed"` only after approval. | -| `source_slide` | Repeat the same value across multiple entries to reuse one source layout for several output slides; order is free and must follow the target story rather than source deck order | -| `layout_rationale` | Human review aid for page selection. Include `layout_pattern`, `why_fit`, and `risk`; it is not a mechanical checker gate. | -| `accepted_warnings` | Optional audit trail for warnings the user or agent explicitly accepts. `check-plan` warnings remain non-blocking; errors must be fixed. | -| `notes` | Optional spoken speaker notes for the filled slide — see **Speaker notes** below; write prose, not a copy of the on-slide text | -| `transition` | Optional per-slide page transition; overrides the `apply --transition` default. New plans use one canonical native gallery effect from [`animations.md`](../references/animations.md) §3; old names remain read-compatible. Accepts `none` to remove the visual effect, `keep` to preserve the source, or an object containing only `effect`, `effect_options`, `duration`, and `advance_after`, such as `{ "effect": "push", "effect_options": { "direction": "left" }, "duration": 0.6, "advance_after": 5 }`. `check-plan` and `apply` reject unknown object fields. | -| `replacements` | Target by `slot_id` whenever possible; `shape_id` and `shape_name` are fallback selectors | -| `table_edits` | Optional native table cell edits; target by `table_id` whenever possible and use zero-based `row` / `col` | -| `chart_edits` | Optional native chart data edits; target by `chart_id`, set `categories`, and provide one or more `series` | -| Short text | For labels / chapter names / directory items, fit the slot's visual capacity from geometry and font size; do not rely on old placeholder length alone | -| Body text | May be moderately freer than the original, but keep paragraph count, visual width, and information density near the slot's geometry capacity | -| Empty slots | Use `scaffold --include-empty` only when a real placeholder is empty in the source deck | -| Native tables | Keep the original table row and column count; edit ordinary cells or a merge anchor only. A merge slave is not visible and is rejected by both `check-plan` and `apply`. This route never changes table structure. | -| Native charts | Each series `values` list must match the category count. Single-plot classic charts whose every series uses `c:cat/c:val` are editable; analyzer/checker preflight the structure and the runtime writer revalidates the actual chart XML before mutation. | -| Chart edit boundary | A single classic plot is editable when every series uses `c:cat/c:val`, including stock, 3D, surface, and other classic plot types. Date-axis and multi-level categories are accepted with a warning because replacement categories are flattened to one level. Scatter, bubble, ChartEx/unknown frames, multi-plot/combo charts, missing-series charts, and non-`c:cat/c:val` data models are rejected. Use beautify / main pipeline to redraw unsupported charts, or leave the native chart untouched. | -| Facts | Every substantive claim must come from the user material | - -**Fit check before apply**: - -- Cover pages: replace title / subtitle / author only. -- Chapter pages: use short section labels. -- Dense content pages: compress material to bullets matching the existing slot capacity. -- Decorative or image-heavy pages: avoid forcing long prose into label-sized slots. -- Repeated source pages: every repeated entry must carry a distinct purpose and replacement set; avoid visual repetition unless the repeated layout expresses the same rhetorical pattern. -- Reordered source pages: verify the new sequence reads as a coherent story; template page numbers, decorative section markers, and notes must be updated to match the output order. - -**Speaker notes (the `notes` field)** — distilled from the main pipeline's Logic Construction Phase, scaled to one note per planned slide: - -Each `notes` value is **pure spoken narration**: write only what a presenter would say aloud, so the same text also works if the deck is later sent through `notes_to_audio.py`. The note explains and connects; it must not just restate the words already on the slide. - -| Rule | Detail | -|---|---| -| Length | 2–5 natural sentences carrying the page's core message; cover / chapter / ending pages can be one or two sentences | -| Transitions | Carry page-to-page flow in the opening sentence as natural prose ("在明确了背景之后……" / "Having framed X, let's turn to Y") — never bracketed `[过渡]` / `[Transition]` tags | -| Plain prose only | No `#` heading line, no `- ` bullet lists, no `要点:① …` / `Key points:` lines, no `时长:2分钟` / `Duration:` annotations — embedded notes keep them verbatim and TTS would read them aloud | -| Number readability | Spell out figures when literal TTS pronunciation is awkward (Chinese "百分之六十八" over "68%"; plain English integers and percentages are fine) | -| One language | Match the deck's language; do not mix languages inside one note | -| Source-bound | Every substantive claim comes from the user material, same as `replacements` | - -Example `notes` value for a Chinese content slide: - -```json -"notes": "在看清整体市场格局之后,我们把镜头拉近到成都二手房的头部板块。当前挂牌均价同比上涨约百分之十二,但成交周期反而拉长到九十天以上,说明买方观望情绪在加重。这组数据是后面定价策略的基础,请重点留意。" -``` - ---- - -## Step 5: Check Text Capacity - -Run the data-based capacity check before applying the plan: - -```bash -python3 skills/ppt-master/scripts/template_fill_pptx.py check-plan "<project_dir>/analysis/<stem>.slide_library.json" "<project_dir>/analysis/fill_plan.json" -o "<project_dir>/analysis/check_report.json" -``` - -Interpret the report: - -| Warning type | Action | -|---|---| -| Short label exceeds visual width | Rewrite shorter or choose a layout with a larger label slot; do not shrink font by default | -| Title too long | Rewrite first; only use font-size changes as a last resort | -| Body much longer than source slot | Compress, split across another selected page, or choose a larger source page | -| SmartArt source content remains unchanged | Pick another source slide unless the original SmartArt wording is intended; otherwise record the accepted warning | -| Missing target | Fix `slot_id` / `shape_id`; do not apply the plan | - -`check-plan` emits stable `code` fields in its JSON results so warnings can be tracked without parsing message text. Warnings are advisory and do not fail the command; record any intentionally accepted warning in `accepted_warnings` when it matters for review. Errors are blocking and must be fixed before apply. - -**Default fitting policy**: Check fit against visual capacity, not raw character count. CJK characters, Latin letters, numbers, and punctuation occupy different visual widths; old placeholder text is only a weak signal. Use `capacity_visual_width` when present, together with `slots[].geometry` and `slots[].text_metrics.font_size_px`, to decide whether to rewrite, split, or choose a different source layout. Do not use per-item font shrinking as a default strategy because it breaks template consistency. - ---- - -## Step 6: Apply the Plan - -⛔ **BLOCKING GATE**: The user has reviewed the planned output order, omitted pages, reused pages, and material-to-layout fit. Set `<project_dir>/analysis/fill_plan.json` top-level `status` to `"confirmed"` only after that review. `apply` rejects an unconfirmed plan by default; `--force` exists only for deliberate recovery/debug use. - -Run: - -```bash -python3 skills/ppt-master/scripts/template_fill_pptx.py apply "<project_dir>/sources/<source.pptx>" "<project_dir>/analysis/fill_plan.json" -o "<project_dir>/exports/<output.pptx>" -``` - -By default `apply` preserves every cloned slide's existing transition. Select -`--transition <effect>` to replace it with a canonical gallery effect from -[`animations.md`](../references/animations.md) §3, and use -`--transition-duration <seconds>` for the replacement duration; old names -remain accepted only as compatibility CLI inputs. Pass `--transition none` for -no visual motion. `--transition keep` states the default preservation policy -explicitly. A per-slide `transition` field overrides the CLI and may include -native `effect_options`; these require an explicit effect and are validated -effect-by-effect. `advance_after` keeps click advance enabled and adds timed -advance; it also works with `none` (timing-only transition) and `keep` (source -effect preserved, Choice/Fallback timing updated together). - -`apply` appends a timestamp automatically. For example, `-o "<project_dir>/exports/demo.pptx"` writes `demo_YYYYMMDD_HHMMSS.pptx`. If the filename already ends with `_YYYYMMDD_HHMMSS`, it is left unchanged. - -The script: - -| Behavior | Result | -|---|---| -| Clones selected source slides | Original slide design, relationships, images, layouts, and animations are preserved where PowerPoint supports them | -| Replaces text nodes | Text frames remain editable in PowerPoint | -| Writes `notes` fields | Speaker notes are embedded as native PowerPoint notes slides | -| Applies `--transition` / per-slide `transition` | Applies the requested visual-transition and slide-advance policy; `keep` may preserve no carrier and `none` may remove it | -| Rebuilds presentation slide list | Output deck contains only the planned slide sequence | -| Adds timestamp to PPTX filename | Matches the main SVG-to-PPTX export convention | -| Drops orphaned source parts | Output carries only the selected pages and the layouts / media / charts they still reference (reachability prune) | - -**Hyperlink preservation**: External hyperlinks remain unchanged. A same-deck -slide jump is retargeted only when its source destination maps unambiguously to -one output slide; a self-link maps to the current clone. If the destination was -omitted or reused into multiple output slides, `apply` fails instead of linking -to an orphan or choosing a target silently. - -**Animation policy**: Template-fill preserves each cloned slide's existing -object animation XML (the SVG pipeline's generated object animation defaults -are not applied here). It also preserves source page transitions by default. -Use `apply --transition` or a per-slide `transition` field only when the user -requests a replacement or removal. `keep` preserves direct and -`mc:AlternateContent` transition effects without converting unknown effects to -`fade`; explicit replacement removes the old logical carrier before writing one -new carrier. If the user asks to change object-level animation order / timing / -effects, treat that as a separate direct-PPTX animation customization task. - ---- - -## Step 7: Validate Output - -Run a lightweight readability check: - -```bash -python3 skills/ppt-master/scripts/template_fill_pptx.py validate "<project_dir>" -``` - -The validator finds the latest PPTX in `<project_dir>/exports/`, runs `ppt_to_md.py` into `<project_dir>/validation/readback.md`, and writes `<project_dir>/validation/validate_report.json`. `exports/` must contain only final deliverables. - -Verify: - -| Check | Expected | -|---|---| -| Output filename | Ends with `_YYYYMMDD_HHMMSS.pptx` | -| Slide count | Matches `len(fill_plan.slides)` | -| Key title text | Appears in the extracted Markdown | -| Native table cells | Updated values appear in the extracted Markdown table | -| Native chart data | Updated labels / values are readable from the extracted Markdown when `ppt_to_md.py` can surface them | -| Multi-line body text | Preserves intended line / paragraph breaks | -| Speaker notes | Read-back note count matches planned `notes` fields | -| Missing target errors | None from `template_fill_pptx.py apply` | - -If the extracted text is correct but visual overflow is likely, reduce the text in `fill_plan.json` and re-run Step 4. - -```markdown -## ✅ Template Fill Complete - -- [x] Standard PPTX intake extracted from the source deck, including `<stem>.slide_library.json` -- [x] `fill_plan.json` selects only pages that fit the target story -- [x] User reviewed the story structure and `fill_plan.json` has `status: "confirmed"` -- [x] `check-plan` run; errors fixed; warnings reviewed / optionally recorded in `accepted_warnings` -- [x] Output PPTX generated through direct OOXML text replacement -- [x] Speaker notes embedded when `notes` fields are present -- [x] `template_fill_pptx.py validate` read-back check passed -``` - ---- - -## Current Boundary - -| Capability | Status | -|---|---| -| Select / reorder / repeat source slides | Supported | -| Replace text in existing text frames | Supported | -| Edit native PowerPoint table cell text | Supported for ordinary cells and merge anchors; merge slaves fail closed | -| Edit native PowerPoint chart categories / series data | Supported for single-plot classic `c:cat/c:val` charts; runtime XML validation remains authoritative | -| Read SmartArt node text / hierarchy / layout | Supported in intake and planning | -| Preserve existing native SmartArt unchanged | Supported by recursive private-part cloning | -| Preserve original visual design | Supported by cloning slide parts directly | -| Page-to-page transitions | Supported via `apply --transition` or per-slide `transition` | -| Replace images | Not in v1 | -| Object-level animations | Not authored in v1; entrance, emphasis, motion-path, and exit effects are preserved from source only and handled as a separate task | -| Edit chart formatting / axes / legend layout | Not in v1 | -| Edit or generate native SmartArt | Not supported; regenerated visual routes use ordinary editable shapes | -| Automatic visual overflow detection | Not in v1; use text-capacity judgment from the library slots | -| Material-divergence reshaping (§c content strategy) | Not applicable — this route fills text into existing slots, it does not author an outline from a source, so the Generate PPTX `content_divergence` free-text field has no role here |