diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 4a88e263a..c7ac75726 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -268,18 +268,6 @@ }, "category": "开发工具" }, - { - "name": "ui-ux-pro-max", - "source": { - "source": "local", - "path": "./plugins/codex/plugins/ui-ux-pro-max" - }, - "policy": { - "installation": "AVAILABLE", - "authentication": "ON_INSTALL" - }, - "category": "设计" - }, { "name": "shadcn", "source": { @@ -292,6 +280,18 @@ }, "category": "开发工具" }, + { + "name": "ui-ux-pro-max", + "source": { + "source": "local", + "path": "./plugins/codex/plugins/ui-ux-pro-max" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "设计" + }, { "name": "ppt-master", "source": { diff --git a/config/external-sources.lock.json b/config/external-sources.lock.json index ae1b9d325..c44a3bdbf 100644 --- a/config/external-sources.lock.json +++ b/config/external-sources.lock.json @@ -24,8 +24,8 @@ "repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git", "ref": "main", "adapter": "claude-skill", - "commit": "10d6ca310541d3ffeee6dceda0a29e373796f321", - "syncedAt": "2026-06-21T16:00:01Z" + "commit": "9a863a527534eeff97cad45430b8e5678ab45c40", + "syncedAt": "2026-06-22T16:00:01Z" }, { "id": "shadcn", @@ -69,8 +69,8 @@ "repo": "https://github.com/hugohe3/ppt-master.git", "ref": "main", "adapter": "claude-skill", - "commit": "c0ace9d5d7cfa991d550ec4c54cf7722ffbcff07", - "syncedAt": "2026-06-21T16:00:01Z" + "commit": "bf9ae01a8df43b1a86f459ed007e84ef465ae257", + "syncedAt": "2026-06-22T16:00:01Z" }, { "id": "next-skills", diff --git a/plugins/codex/.agents/plugins/marketplace.json b/plugins/codex/.agents/plugins/marketplace.json index d58d764ee..ba6895671 100644 --- a/plugins/codex/.agents/plugins/marketplace.json +++ b/plugins/codex/.agents/plugins/marketplace.json @@ -268,18 +268,6 @@ }, "category": "开发工具" }, - { - "name": "ui-ux-pro-max", - "source": { - "source": "local", - "path": "./plugins/ui-ux-pro-max" - }, - "policy": { - "installation": "AVAILABLE", - "authentication": "ON_INSTALL" - }, - "category": "设计" - }, { "name": "shadcn", "source": { @@ -292,6 +280,18 @@ }, "category": "开发工具" }, + { + "name": "ui-ux-pro-max", + "source": { + "source": "local", + "path": "./plugins/ui-ux-pro-max" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "设计" + }, { "name": "ppt-master", "source": { diff --git a/plugins/codex/plugins/mcp-playwright/MCP_SOURCE.json b/plugins/codex/plugins/mcp-playwright/MCP_SOURCE.json index 092ff54f3..b5c89a433 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-06-21T16:01:13Z" + "syncedAt": "2026-06-22T16:01:37Z" } diff --git a/plugins/codex/plugins/ppt-master/README.md b/plugins/codex/plugins/ppt-master/README.md index 850a3c4b4..f4cffa274 100644 --- a/plugins/codex/plugins/ppt-master/README.md +++ b/plugins/codex/plugins/ppt-master/README.md @@ -7,9 +7,8 @@ English | [中文](./README_CN.md) -

- This project is kept free and open source with the support of PackyCode, APIKEY.FUN, RunAPI and other sponsors. -

+
+This project is kept free and open source with the support of PackyCode, APIKEY.FUN, RunAPI and other sponsors. @@ -26,6 +25,8 @@ English | [中文](./README_CN.md)
+
+ > [!IMPORTANT] > ### This is a tool, not a wishing well > Don't expect it to hand you a finished, perfect deck in one shot. Its real value is taking most of the tedious work off your plate; the polishing that's left is yours — a natively editable deck exists precisely so you can keep working on it, not a flat image you can't touch. The cheaper the model, the more there is to do. How good the result turns out comes down to your skill with this project and with PowerPoint. @@ -193,6 +194,8 @@ PPT Master runs in **any tool with agent capability** — read/write files, exec **🔑 Want to use Claude / GPT / Gemini but don't have access yet?** Project sponsors **[PackyCode](https://www.packyapi.com/register?aff=ppt-master)**, **[APIKEY.FUN](https://apikey.fun/register?aff=PPT-MASTER)** and **[RunAPI](https://runapi.co/register?aff=WMLJ)** can help — all offer pay-as-you-go access to Claude, GPT, Gemini and more, no subscription required. **PackyCode**: 10% off with promo code **`ppt-master`** at top-up. **APIKEY.FUN**: prices as low as **7% of official rates**; register via our link for an exclusive permanent discount of up to 5% on top-ups. **RunAPI**: 150+ models via one API Key at prices as low as **10% of official rates**; register and contact an administrator to claim **¥7 in free credit**. +**🔀 Juggling several providers?** Once you hold keys from more than one of them, [cc-switch](https://github.com/farion1231/cc-switch) — a cross-platform desktop app — lets you one-click switch API providers for Claude Code, Codex, Gemini CLI and more, no manual config editing. + ### 3. Set Up **Option A — Download ZIP** (no Git required): click **Code → Download ZIP** on the [GitHub page](https://github.com/hugohe3/ppt-master), then unzip. @@ -308,6 +311,10 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to get involved. [SVG Repo](https://www.svgrepo.com/) · [Tabler Icons](https://github.com/tabler/tabler-icons) · [Simple Icons](https://github.com/simple-icons/simple-icons) · [Phosphor Icons](https://github.com/phosphor-icons/core) · [Robin Williams](https://en.wikipedia.org/wiki/Robin_Williams_(author)) (CRAP principles) +## Related Tools + +[cc-switch](https://github.com/farion1231/cc-switch) — a cross-platform desktop app to manage and one-click switch API providers across Claude Code, Codex, Gemini CLI and more. Handy when running PPT Master inside any of these agents. + ## Contact & Collaboration Looking to collaborate, integrate PPT Master into your workflow, or just have questions? diff --git a/plugins/codex/plugins/ppt-master/THIRD_PARTY_SOURCE.json b/plugins/codex/plugins/ppt-master/THIRD_PARTY_SOURCE.json index 63a4ee11b..52bc97fe9 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": "c0ace9d5d7cfa991d550ec4c54cf7722ffbcff07", + "commit": "bf9ae01a8df43b1a86f459ed007e84ef465ae257", "adapter": "claude-skill", "sourcePath": "skills/ppt-master", - "syncedAt": "2026-06-21T16:00:01Z" + "syncedAt": "2026-06-22T16:00:01Z" } 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 521b61258..bf90142bb 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/SKILL.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/SKILL.md @@ -323,14 +323,14 @@ Read references/strategist.md **Confirm UI Auto-Launch (Mandatory — default visual confirmation surface)**: by default the Eight Confirmations are presented through an interactive local page (color swatches, live font previews, candidate picks); the chat path is the always-valid fallback. Steps: -1. Write the recommendations to `/confirm_ui/recommendations.json` (full schema + field mapping: [`scripts/docs/confirm_ui.md`](scripts/docs/confirm_ui.md)). Two kinds of field: **enumerable** (canvas / mode / visual_style / icons / formula policy / generation mode; plus image usage with a Custom path; plus AI source only when image usage may include `ai`) — the page lists common options from `confirm_ui/static/catalogs.json`, so you only name the recommended canonical `id` in a `recommend` block (canvas may be a catalog id like `ppt169` or a custom size/prose; style = `mode` + `visual_style`, two independent picks; icon ids are real libraries such as `tabler-outline`, or `emoji` for system emoji; image usage uses `ai` / `web` / `provided` / `placeholder` / `none`, or a custom prose plan when several sources must be combined; never write bare `"custom"` for image usage — write the actual mixed plan, e.g. "AI cover + user product assets + web industry images"; write `image_ai_path` only when recommending `image_usage: "ai"` or a custom plan that includes AI); **generative** (color, typography, generated-image style) — author **≥3 candidates** each (creative recommendations always offer real choice, never a single silent option — same rule as strategist h.5; fewer than 3 only on the honest-shortfall exception, with a stated reason) (color: user-facing core `palette` with background/secondary_bg/primary/accent/secondary_accent/body_text; typography: CJK + Latin for `heading` and `body` with `css` preview stacks, plus `body_size` as the body baseline px; when recommending generated images, `image_strategy.candidates` with rendering × palette combinations from strategist h.5). `page_count` / `audience` / `content_divergence` are plain values (free text). Only open fields show a Custom box: `canvas`, `mode`, `visual_style`, `icons`, `image_usage`, and typography custom text. Closed fields (`image_ai_path`, `formula_policy`, `generation_mode`, `refine_spec`) stay finite. `content_divergence` is a **free-text** field shown under audience in §c — the user states in their own words how closely to follow the source vs how freely to reshape it (blank = balanced; facts stay sourced at every level). Write it as `content_divergence: { "value": "" }`. It is consumed by Strategist when authoring `§IX`, recorded in `design_spec.md §I`, carries no page-count coupling, and is **not** written to `spec_lock.md`. Set `lang` to the page language; visible candidate text should match `lang`, or provide bilingual `name_zh` / `name_en` and `note_zh` / `note_en` fields. Reuse the same candidate thinking as strategist h.5. +1. Write the recommendations to `/confirm_ui/recommendations.json` (full schema + field mapping: [`scripts/docs/confirm_ui.md`](scripts/docs/confirm_ui.md)). Two kinds of field: **enumerable** (canvas / mode / visual_style / icons / formula policy / generation mode / delivery purpose; plus image usage with a Custom path; plus AI source only when image usage may include `ai`) — the page lists common options from `confirm_ui/static/catalogs.json`, so you only name the recommended canonical `id` in a `recommend` block (canvas may be a catalog id like `ppt169` or a custom size/prose; style = `mode` + `visual_style`, two independent picks; icon ids are real libraries such as `tabler-outline`, or `emoji` for system emoji; image usage uses `ai` / `web` / `provided` / `placeholder` / `none`, or a custom prose plan when several image sources are mixed; never write bare `"custom"` for image usage — write the actual mixed plan, e.g. "AI cover + user product assets + web industry images"; write `image_ai_path` only when recommending `image_usage: "ai"` or a custom plan that includes AI); **generative** (color, typography, generated-image style) — author **≥3 candidates** each (creative recommendations always offer real choice, never a single silent option — same rule as strategist h.5; fewer than 3 only on the honest-shortfall exception, with a stated reason) (color: user-facing core `palette` with background/secondary_bg/primary/accent/secondary_accent/body_text; typography: CJK + Latin for `heading` and `body` with `css` preview stacks, plus `body_size` as the body baseline shown to the user (**pt** for PPT canvases, px for non-PPT) and `delivery_purpose` (PPT only: `text` / `balanced` / `presentation`, seeds the pt default); when recommending generated images, `image_strategy.candidates` with rendering × palette combinations from strategist h.5). `page_count` / `audience` / `content_divergence` are plain values (free text). Only open fields show a Custom box: `canvas`, `mode`, `visual_style`, `icons`, `image_usage`, and typography custom text. Closed fields (`image_ai_path`, `formula_policy`, `generation_mode`, `refine_spec`, `delivery_purpose`) stay finite. `content_divergence` is a **free-text** field shown under audience in §c — the user states in their own words how closely to follow the source vs how freely to reshape it (blank = balanced; facts stay sourced at every level). Write it as `content_divergence: { "value": "" }`. It is consumed by Strategist when authoring `§IX`, recorded in `design_spec.md §I`, carries no page-count coupling, and is **not** written to `spec_lock.md`. Set `lang` to the page language; visible candidate text should match `lang`, or provide bilingual `name_zh` / `name_en` and `note_zh` / `note_en` fields. Reuse the same candidate thinking as strategist h.5. 2. Launch the page **in the background and wait for the browser confirmation** (the child server runs detached; the parent command returns after `result.json` is freshly written). **Run this command with a long tool timeout — 600000 ms** — so the `--wait` (≈590 s budget) can complete: ```bash python3 ${SKILL_DIR}/scripts/confirm_ui/server.py --daemon --wait ``` Page opens at `http://localhost:5050` — the **same port as the Step 6 live preview** (they never run at once: this page shuts down at the end of Step 4, freeing the port). If another project already holds 5050, the launcher **auto-advances to the next free port** (5051, …) and serves this project there — read the actual URL from the launch log and report that. When the user clicks **Confirm**, the command exits 0 and Step 4 reads `result.json` immediately; do not require a second chat confirmation. **Launch or wait failure is non-fatal**: if it fails or times out (flask missing, port blocked, no GUI / remote / web host, browser never confirms in time), do **NOT** troubleshoot. The detached page stays open, so a slow user may confirm after the wait returns — therefore **on any non-zero exit, re-check `/confirm_ui/result.json` once (a fresh `status: confirmed`) before** dropping to the chat-summary fallback below. 3. **Always also print the eight recommendations as a short summary in chat, with the URL.** This keeps the chat fallback valid whether or not the browser opened. If the page never appears, the user simply confirms or edits in chat as before. -4. This is the ⛔ BLOCKING wait. Preferred page path: the `--wait` command returns after the page writes a fresh `/confirm_ui/result.json`; immediately read that file and use its values. On a non-zero exit, re-check `result.json` once (per step 2) — a fresh `status: confirmed` still wins. Chat fallback path: only if no fresh result exists (page didn't open, wait timed out with no confirmation, or the user replies in chat with edits) take the chat values directly. Either path converges. A confirmed `result.json` is an explicit user choice: `generation_mode: "split"` means split mode was chosen; `refine_spec: true` means the refine-spec workflow was chosen. +4. This is the ⛔ BLOCKING wait. Preferred page path: the `--wait` command returns after the page writes a fresh `/confirm_ui/result.json`; immediately read that file and use its values. On a non-zero exit, re-check `result.json` once (per step 2) — a fresh `status: confirmed` still wins. Chat fallback path: only if no fresh result exists (page didn't open, wait timed out with no confirmation, or the user replies in chat with edits) take the chat values directly. Either path converges. **Typography normalization is mandatory before any spec writing**: for PPT canvases, chat-confirmed `pt` values must be converted immediately to unitless px (`px = pt × 4/3`) so `design_spec.md`, `spec_lock.md`, and SVG all see px only; keep the original pt only as provenance (`body_size_pt` / `sizes_pt`) if useful. A confirmed `result.json` is an explicit user choice: `generation_mode: "split"` means split mode was chosen; `refine_spec: true` means the refine-spec workflow was chosen. 5. **Close the confirm page (Mandatory cleanup — every path).** Once you have the confirmed values (page **or** chat), shut the confirm server down before leaving Step 4 so it cannot keep holding port 5050 (which Step 6 live preview reuses): ```bash python3 ${SKILL_DIR}/scripts/confirm_ui/server.py --shutdown diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/canvas-formats.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/canvas-formats.md index 5e7fecd61..00113a4a3 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/canvas-formats.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/canvas-formats.md @@ -62,7 +62,7 @@ Content purpose? | Story | — | Middle 1500px | Top safe zone 120px, bottom 180px | | WeChat Article Header | Center/left-aligned 48-72px | — | Image on right or as background | -> **Body font baseline scales with canvas height** — a baseline tuned for 16:9 (18–24px) is far too small on tall canvases (Xiaohongshu / Story / A4). Pick the baseline from the confirmed canvas, not the recommended one; see the per-canvas anchors in [`strategist.md`](strategist.md) §g "Font Size Ramp". +> **Body font baseline scales with canvas and delivery purpose** — a PPT 16:9 baseline confirmed for read-close / business / projection cannot be carried onto tall canvases (Xiaohongshu / Story / A4). Pick the baseline from the confirmed canvas, not the recommended one; see the per-canvas anchors and PPT pt→px normalization in [`strategist.md`](strategist.md) §g "Font Size Ramp". ## ViewBox Examples 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 57278240b..278f7481a 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 @@ -39,7 +39,9 @@ Resolve the per-page template SVG via `spec_lock.md page_layouts` (authoritative > Note: `page_layouts` disambiguates the multiple content variants modern templates ship (e.g., `graduation_defense` has 8); the legacy table cannot. -**Templates supply structure, not skin (non-mirror)**: a chart or layout template's gradients, drop-shadows, and palette are placeholder. Inherit its geometry, label / legend placement, and series-encoding logic; re-skin every fill / stroke to the deck's `visual_style` + `spec_lock.colors` — flat styles strip the gradients and shadows, gradient / glass styles repaint their own. Forbidden — shipping a template's default `` / `cardShadow` / Tailwind fills unchanged. Mirror templates are the exception: §1.1 preserves their visuals verbatim. +**Templates supply structure, not skin (non-mirror)**: a chart or layout template's gradients, drop-shadows, palette, **and font sizes** are placeholder. Inherit its geometry, label / legend placement, and series-encoding logic; re-skin every fill / stroke to the deck's `visual_style` + `spec_lock.colors` — flat styles strip the gradients and shadows, gradient / glass styles repaint their own. Forbidden — shipping a template's default `` / `cardShadow` / Tailwind fills unchanged. Mirror templates are the exception: §1.1 preserves their visuals verbatim. + +**Font size is skin, not geometry (non-mirror).** A chart / layout template's hardcoded `font-size` values (often 11–16px, sized for the template's own dense placeholder text) are NOT inherited — classify each text into its `spec_lock.md` role and use that role's locked size, exactly as you re-skin color. **Structural roles (page title / body / subtitle / annotation / footnote) hold their one deck-wide size on every page** — the template's placeholder px never overrides it; same-role text drifting page to page is what makes a deck look unprofessional. **Geometry adapts to the type, never the reverse**: when the locked size is larger than the template's placeholder text, widen / heighten the card, open spacing, and recompute child `y` / `dy` to make room — do not shrink the font to fit the inherited container. The Executor renders the page it was given; page count and per-page density are the Strategist's call, fixed at confirmation — do **not** re-paginate, split the page, or drop authored content to cope with size here. Only when a single block still cannot fit after the geometry is fully reflowed may you shrink **that block** as a bounded last resort — and **only body text** is ever shrunk this way. Title, subtitle, annotation / caption, footnote and page number are **locked once set and never adjusted to fit** — their values hold across the whole deck. Step the overflowing body block's `font-size` down by `2.67`px (one 2pt step) at a time, and only if it still overflows step it down again, up to a cumulative floor of **`5.33`px (4pt) below the locked body size** (e.g. `26.67` → no smaller than `21.33`). This is a **local, single-block** reduction — the deck-wide locked body size is unchanged on every other block and page. (The Executor works in unitless px — spec_lock and SVG carry no `pt`; `2.67`px ≈ 2pt, `5.33`px ≈ 4pt.) If the block still overflows at the 4pt floor, surface a `warning:` rather than silently restructure the page. (Mirror templates are the exception: §1.1 preserves their sizes verbatim — there the source deck's typography *is* the spec.) ### 1.1 Mirror-mode templates — reference-style consumption @@ -106,7 +108,8 @@ Before the first SVG page, output a confirmation listing: canvas dimensions, bod - Colors (fill / stroke / stop-color) MUST come from `colors` - Icons MUST come from `icons.inventory`; library MUST equal `icons.library` - Font family from `typography`: use role override (`title_family` / `body_family` / `emphasis_family` / `code_family`) if declared, else fall back to `font_family` -- Font sizes follow a **ramp anchored on `typography.body`**, not a closed menu. Use the declared slots when they fit. Intermediate sizes (e.g., 40px hero number, 13px annotation) are allowed if the ratio to `body` falls within the role's band (see `design_spec.md §IV ramp table`). Sizes outside every band require extending the lock first. +- Font sizes follow a **ramp anchored on `typography.body`**, not a closed menu. **Structural roles — page title, body, subtitle, annotation / caption, footnote / page number — render at one consistent size deck-wide, taken from their `spec_lock` slot; never re-pick a structural role's size page by page or carry a template's placeholder px.** This locks the **role**, not every glyph: a page may still carry deliberate typographic hierarchy — a lead-in sentence, an inline emphasis figure, a pull-quote, a kicker, a hero number — but each of those is its **own role / feature element** with its own size, **applied consistently deck-wide** (declare a recurring one as its own `spec_lock` slot). In-band intermediate sizes are for exactly these feature elements. What is banned is the *same* role drifting size to fit a container or by page whim — that scatter is what reads as unprofessional. Sizes outside every band require extending the lock first. +- **Write `font-size` to at most 2 decimals** — prefer a whole number; keep the 2-decimal form only for a slot that carries it from `spec_lock` (e.g. `body: 26.67`). Never emit long tails like `20.8026`: the exporter snaps the final size to the nearest 0.5pt, so extra px precision is wasted noise. - Images MUST reference files listed under `images`; no invented filenames - Formula PNGs are images with `Acquire Via: formula` / `Status: Rendered`; place them only from the listed file path and never recreate the formula as text. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/strategist.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/strategist.md index 6d93791ea..9e0281b6a 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/strategist.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/strategist.md @@ -36,7 +36,7 @@ Recommend format based on scenario (see [`canvas-formats.md`](canvas-formats.md) ### b. Page Count Confirmation -Provide specific page count recommendation based on source document content volume. +Provide specific page count recommendation based on source document content volume **and the deck's delivery purpose** (文字型 packs denser → the same source fits in fewer pages; 展示型 is one-idea-per-page → the same source may need more) — see §6.1 Content Planning Strategy. The user's confirmed count still wins; delivery purpose governs density and per-page treatment within it. ### c. Key Information Confirmation @@ -219,39 +219,58 @@ See [`../templates/icons/README.md`](../templates/icons/README.md) for the curre > - **Calligraphic display** — 隶书 LiSu / 华文行楷 STXingkai / 华文新魏 STXinwei (closest safe substitute: `KaiTi` / `FangSong`); cover / section / hero titles only, never body > - **Brand-specific** — McKinsey Bower, corporate VI typefaces -#### Font Size Ramp (all sizes in px) +#### Font Size Ramp (confirmation layer in pt → spec layer in px) -> **Ramp, not a fixed menu.** All sizes derive from the `body` baseline as a ratio. `spec_lock.md typography` declares `body` plus the slots this deck uses (`title` / `subtitle` / `annotation` by default; add `cover_title` / `hero_number` / `chart_annotation` as needed). Executor may pick any intermediate px within a role's ratio band. +> **Ramp, not a fixed menu.** All sizes derive from the `body` baseline as a ratio. `spec_lock.md typography` declares `body` plus the slots this deck uses (`title` / `subtitle` / `annotation` by default; add `cover_title` / `hero_number` / `subheading` / `lead` / `chart_annotation` as needed). **Structural roles (page title / body / subtitle / annotation / footnote) resolve to one size each and hold it deck-wide** — that consistency is what reads as professional. Picking an intermediate in-band size is for special / feature elements (hero number, display title, one-off emphasis); declare a recurring special size as its own slot so it stays consistent too. -Baseline scales with **canvas height first, then content density** — the `18`/`24px` figures below anchor to a 720-tall 16:9 canvas (≈2.5–3.3% of height); hold that proportion on taller canvases, never carrying a 16:9 number onto a larger one. Within a canvas, density picks the point in its band (chart-heavy low · medium mid · relaxed / poster / cover high), relative to that canvas's anchor, not a fixed 16:9 px. +> **Unit boundary (HARD rule).** PPT canvases may use **pt only in the confirmation layer** (`recommendations.json`, the Confirm UI, or chat fallback). Before writing `design_spec.md`, normalize the confirmed font sizes to **unitless px** (`px = pt × 4/3`) and keep px as the only design/execution value in `design_spec.md`, `spec_lock.md`, and SVG. Keep original pt only as provenance (`body_size_pt` / `sizes_pt`) when useful. Non-PPT canvases use px everywhere. Never write `pt` / `px` / `em` units into `spec_lock.md` or SVG; those layers carry unitless px numbers only. Geometry — margins, gaps, card sizes — is px everywhere. -| Canvas | Height | Body baseline (dense–relaxed) | -|---|---|---| -| PPT 16:9 / 4:3 | 720 / 768 | 18–24 | -| Xiaohongshu | 1242×1660 | 40–55 | -| WeChat / IG 1:1 | 1080×1080 | 27–36 | -| Story / Portrait | 1080×1920 | 48–64 | -| A4 | 1240×1754 | 44–58 | +**Baseline — pick by delivery purpose first, then density.** Delivery purpose is one of the Eight Confirmations (item 7 typography); it is the primary driver because the same canvas reads very differently when read close vs. projected. It is a **deck-wide** axis — beyond the body baseline it also drives page density / count / rhythm; see §6.1 for that side. Here it sets the body baseline: -> **Canvas drives the baseline — re-derive on change, never carry.** The body baseline is a function of the *confirmed* canvas (item a), not the recommended one. If the user overrides the recommended canvas in confirmation, recompute the baseline (and therefore the whole ramp) for the new canvas before writing `spec_lock.md`: the `body_size` in `recommendations.json` was sized for the recommended canvas and is stale the moment the canvas changes. +| Delivery purpose (PPT 16:9) | Body (pt) | Default | Reads as | +|---|---|---:|---| +| 文字型 · read-close (report, data-dense brief, leave-behind file) | 14–18 | 16pt | screen / handout reading at arm's length | +| 均衡 · business (presented **and** read; roadshow, review) — **default** | 18–22 | 20pt | mixed projection + reading | +| 展示型 · presentation (projected, sparse; keynote, launch, classroom) | 22–28 | 24pt | room projection, glance from the back | -| Common recommendation | Points per Page | Body Baseline | Suitable Scenarios | -|----------------|----------------|---------------|-------------------| -| Relaxed | 3-5 items | 24px | Keynote-style, training materials | -| Dense | 6+ items | 18px | Data reports, consulting analysis | +Within the chosen purpose band, **density** picks the point (chart-heavy / 6+ items → low end · medium → mid · sparse / poster / cover → high end), and **visual style** nudges it (technical / data toward low, corporate / instructional toward mid, editorial / narrative / showcase toward high). -| Level | Ratio to body | 24px baseline | 18px baseline | +| Canvas | Height | Body baseline | Unit | +|---|---|---|---| +| PPT 16:9 / 4:3 | 720 / 768 | 14–28 (by delivery purpose above) | **pt** | +| Xiaohongshu | 1242×1660 | 40–55 | px | +| WeChat / IG 1:1 | 1080×1080 | 27–36 | px | +| Story / Portrait | 1080×1920 | 48–64 | px | +| A4 | 1240×1754 | 44–58 | px | + +> **PPT confirms in pt, specs carry px.** Only the PPT canvases (16:9 / 4:3) expose pt in the confirmation layer because they export to a real PowerPoint slide where pt is meaningful. Social / print canvases (Xiaohongshu / Story / A4, viewed close on phone or in print) have no PPT pt semantics, so author their body baseline directly in px and skip the conversion step. + +> **Confirmed values win — never recompute over them.** The user's confirmed sizes are authoritative. **Confirm UI path**: take `result.json` `typography.body_size` / `sizes` (already px) **verbatim** — do **not** re-derive from the canvas even if the user changed it. The page deliberately does not auto-rescale font sizes when the canvas changes (it only updates the suggested-range hint), so `result.json` already reflects the user's intent; recomputing here would silently override their choice. **Chat-fallback path** (no `result.json`): size from the confirmed canvas + delivery purpose (convert via `pt_to_px.py` for PPT). The `body_size` in `recommendations.json` is only a stale hint once the canvas changes — use the confirmed value, not the recommendation. + +| Level | Ratio to body | 32px baseline (24pt UI) | 24px baseline (18pt UI) | |-------|---------------|---------------|---------------| -| Cover title (hero headline) | 2.5-5x | 60-120px | 45-90px | -| Chapter / section opener | 2-2.5x | 48-60px | 36-45px | -| Page title | 1.5-2x | 36-48px | 27-36px | -| Hero number (consulting KPIs) | 1.5-2x | 36-48px | 27-36px | -| Subtitle | 1.2-1.5x | 29-36px | 22-27px | -| **Body** | **1x** | **24px** | **18px** | -| Annotation / caption | 0.7-0.85x | 17-20px | 13-15px | -| Page number / footnote | 0.5-0.65x | 12-16px | 9-12px | +| Cover title (hero headline) | 2.5-5x | 80-160px | 60-120px | +| Chapter / section opener | 2-2.5x | 64-80px | 48-60px | +| Page title | 1.5-2x | 48-64px | 36-48px | +| Hero number (consulting KPIs) | 1.5-2x | 48-64px | 36-48px | +| Subtitle | 1.2-1.5x | 38-48px | 29-36px | +| Lead-in / intro | 1.1-1.4x | 35-45px | 26-34px | +| Subheading | 1.1-1.3x | 35-42px | 26-31px | +| **Body** | **1x** | **32px** | **24px** | +| Annotation / caption | 0.7-0.85x | 22-27px | 17-20px | +| Page number / footnote | 0.5-0.65x | 16-21px | 12-16px | -> Two baseline columns are illustrative only — for any other baseline (16/20/22/28/32…), multiply the row's ratio. Checker reads live `body` from `spec_lock.md`. Executor may pick any px within a role's band without pre-declaring; values outside **every** band require lock extension first. +> Two baseline columns are illustrative only — for any other normalized `body` px value (21.33 / 26.67 / 29.33 / 32 / ...), multiply the row's ratio. Structural roles (page title / body / subtitle / annotation / footnote) take their locked slot value and stay there on every page — not a per-page pick. In-band freedom without pre-declaring is for special / feature elements (hero number, display title, one-off emphasis); a recurring special size should be declared as its own slot. The subtitle / lead / subheading bands overlap on purpose — pick by role, not size, then hold each at one size deck-wide. Values outside **every** band require lock extension first. + +> **pt → px: where it happens (Mandatory).** PPT canvases confirm in pt; `design_spec.md` / `spec_lock.md` / SVG carry **unitless px only**. The conversion `px = pt × 4 ⁄ 3` happens **once**, by path. (At export the px is converted back with `× 0.75` and **snapped to the nearest 0.5pt** so PowerPoint always shows a clean integer-or-half-point size; a confirmed integer pt round-trips exactly — e.g. 20pt → 26.67px → 20.0pt.) +> - **Confirm UI path** — already done at submit. `result.json` `typography.body_size` / `sizes` are **already px** (`body_size_pt` / `sizes_pt` are kept only as provenance). Use the px directly; do **not** re-convert. +> - **Chat-fallback path** — no `result.json` exists, so you convert, via the deterministic helper (never mental math): +> ```bash +> python3 skills/ppt-master/scripts/pt_to_px.py [ ] # e.g. 20 → 26.67 +> ``` +> Write its printed px into `design_spec.md §IV` + `spec_lock.md typography`. Honor any per-role size the user pinned as that slot's locked value; derive the rest from the ramp. +> +> **Validation** — before leaving the Strategist: every font-size in `design_spec.md` / `spec_lock.md` is the **px** value (e.g. `26.67`), never the confirmed pt number (`20`) written raw. Copying a pt number in unconverted is the one silent error the unit guard cannot catch — `20` is a valid bare number that renders at 15pt. Convert (via the page or `pt_to_px.py`), don't copy. > **Hero in single-focus / breathing pages**: when one element *is* the entire page — a large number, a headline, a key phrase — it is the visual subject, not body content. Such heroes may borrow the cover-title band (2.5–5×); for greater emphasis, declare a hero slot in `spec_lock.md` (e.g., `hero_number` / `hero_headline`) — checker exempts declared slots with no fixed upper limit. The row above "Hero number (consulting KPIs) 1.5–2×" applies only to numeric KPIs in dashboard/data layouts, not to full-page focal elements. @@ -734,6 +753,16 @@ Templates are starting points. The Strategist may adjust based on content and au Content-outline and speaker-notes strategy follow the deck's locked **mode** — see [`modes/_index.md`](./modes/_index.md) and the locked mode's file. The guidance below applies within any mode: +**Delivery purpose drives the whole plan, not just type size.** `result.json delivery_purpose` — 文字型 (read-close) / 均衡 (business, default) / 展示型 (presentation), confirmed in item 7 — is a **deck-wide consumption mode**. It seeds the normalized body baseline (§g) **and** governs how content is distributed: + +| Delivery purpose | Per-page density & treatment | §IX content per page | page_rhythm lean | +|---|---|---|---| +| 文字型 · read-close | dense — pack more per page, fuller layouts | prose paragraphs, more blocks, tables / fine detail; complete sentences | leans `dense` | +| 均衡 · business (default) | balanced | one primary + supporting points; moderate text | mixed | +| 展示型 · presentation | sparse — one idea per page, generous whitespace | keywords / short phrases, a single core message, large visual; never paragraph dumps | leans `anchor` / `breathing` | + +This is what makes the axis meaningful: a 展示型 deck and a 文字型 deck built from the **same source** must differ in per-page text volume, layout density, and rhythm — **not only in font size**. Page count (item b) stays the user's call; delivery purpose governs the **density and treatment within it**, and informs the page-count recommendation when the user has not fixed one. Record the chosen purpose in `design_spec.md §I`. The `page_rhythm` leans are a bias, not a quota — the filler-page ban and "rhythm follows narrative" rule still hold. (Preservation paths — beautify / template-fill — keep source structure verbatim: honor purpose only in styling, never to re-paginate.) + **Per-block expression**: phrase each §IX content block in the mode that fits it — prose, bullet, keyword, or any phrasing the content calls for — not a default bullet. Take the cue from the source's texture: a narrative source (article / transcript / talk) leans prose — resist compressing its argument pages into fragments; a data sheet leans bullet/keyword. Write the real sentence into §IX itself, not a skeleton point to expand later. One page mixes modes; let layout pull each (narrative → prose, structural/chart → bullets/keywords). > Note: §IX is the only content copy the Executor re-reads after context compression — what you write there is what survives. @@ -742,7 +771,7 @@ Content-outline and speaker-notes strategy follow the deck's locked **mode** — | Chapter | Content Requirements | |---------|---------------------| -| I. Project Information | Project name, canvas format, page count, style, audience, scenario, date | +| I. Project Information | Project name, canvas format, page count, style, audience, scenario, delivery purpose, date | | II. Canvas Specification | Format, dimensions, viewBox, margins, content area | | III. Visual Theme | Style description, light/dark theme, tone, color scheme (with HEX table), gradient scheme | | IV. Typography System | Font plan (per-role families — title / body / emphasis / code), font size hierarchy | @@ -760,7 +789,7 @@ Content-outline and speaker-notes strategy follow the deck's locked **mode** — 3. Save to: `projects/.../design_spec.md` 4. **Generate execution lock**: read `templates/spec_lock_reference.md` and produce `projects/.../spec_lock.md` — a distilled, machine-readable short form of the color / typography / icon / image / **page_rhythm** / **page_layouts** / **page_charts** decisions above. This file is what the Executor re-reads before every page (see [executor-base.md](executor-base.md) §2.1). The values in `spec_lock.md` MUST exactly match the decisions recorded in `design_spec.md`; if they ever diverge, `spec_lock.md` wins and `design_spec.md` should be treated as historical narrative. - **page_rhythm is mandatory**: Based on the page list in §IX Content Outline, assign each page one of `anchor` / `dense` / `breathing` (see `spec_lock_reference.md` for the full vocabulary). This is what breaks the uniform "every page is a card grid" feel — without it the Executor defaults all pages to `dense`. - - **Rhythm follows narrative, not quota**: `breathing` pages mark natural pauses — chapter transitions, standalone emphasis (hero quote / big number), SCQA bridges. Dense decks may legitimately be all `dense`. **Do NOT invent filler pages** ("Thank you", empty dividers) to pad rhythm — every `breathing` page must say something independent. + - **Rhythm follows narrative, not quota**: `breathing` pages mark natural pauses — chapter transitions, standalone emphasis (hero quote / big number), SCQA bridges. Dense decks may legitimately be all `dense`. **Do NOT invent filler pages** ("Thank you", empty dividers) to pad rhythm — every `breathing` page must say something independent. Delivery purpose biases the overall lean (展示型 toward more `anchor` / `breathing`, 文字型 toward `dense`; see §6.1) — a bias, never a quota. - **Cover impact is mandatory**: Page `P01` is the deck's first visual contract, not a generic title slide. In `design_spec.md §IX`, add a `Cover impact` line for `P01` that names one concrete hook and one concrete composition strategy. Use the source's strongest available signal: a provocative core claim, object / scene metaphor, hero number, founder / product / audience moment, or a distilled conflict. Pair it with one concrete composition strategy — such as `full-bleed image + floating title`, `typographic poster`, `hero object`, `data hook`, `editorial scene`, `high-contrast abstract geometry`, or a fresh composition the deck's subject suggests (these are starting points, not the allowed set). If no external or AI image is available, still specify a native-SVG visual hook; do not fall back to "title + subtitle + decorative background". (Beautify / template-fill keep the source cover verbatim — this rule does not apply on those preservation paths.) - **Cover rhythm lock**: `P01` remains `anchor` in `spec_lock.md page_rhythm`, but its §IX `Cover impact` must prevent content-page patterns. Do not plan multi-card grids, agenda-like bullets, or equal-weight columns on the cover unless a template explicitly requires that structure, or a preservation path (beautify / template-fill) is transcribing the source cover verbatim. - **Closing impact (only when the deck closes)**: the deck's last page is its final visual contract — the strongest impression after the cover. When the deck genuinely lands on a conclusion / call-to-action / final-takeaway page, give it a `Closing impact` line in §IX: name the one thing the audience should leave with (a distilled takeaway, a forward call, a memorable restatement of the core claim) + one composition that delivers it — never a generic "Thank you" / contact-only slide or a centered-title reprise of the cover. **Do NOT invent a closing page to satisfy this** — the filler-page ban above still holds; apply it only to the page where the deck actually resolves. Same exemptions as the cover: skip on template / beautify / template-fill preservation paths. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/confirm_ui/static/app.js b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/confirm_ui/static/app.js index f0674036b..b4eb0563e 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/confirm_ui/static/app.js +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/confirm_ui/static/app.js @@ -55,6 +55,14 @@ font_body_size: "Body baseline size", font_body_size_hint: "All type sizes derive from this body baseline.", body_size_hint_canvas: "This canvas suggests ~{lo}–{hi}px (scales with canvas height).", + body_size_hint_pt: "PPT body is confirmed in pt — this purpose suggests ~{lo}–{hi}pt (default {def}pt). Saved as px after confirmation.", + body_size_hint_oor: "(Current value is outside this range — it is not auto-converted across canvases; check it fits.)", + delivery_purpose: "Delivery purpose", + delivery_purpose_hint: "Read-close decks can run smaller; projected decks need larger type.", + size_override: "Per-role size override:", + size_role_title: "title", + size_role_subtitle: "subtitle", + size_role_annotation: "annotation", custom_typography: "Custom typography", custom_typography_placeholder: "Type your font plan, e.g. Heading: Georgia + KaiTi; Body: Microsoft YaHei + Arial…", custom_color: "Custom color", @@ -125,6 +133,14 @@ font_body_size: "正文基准字号", font_body_size_hint: "所有字号按这个正文基准推导。", body_size_hint_canvas: "当前画布建议 ~{lo}–{hi}px(随画布高度缩放)。", + body_size_hint_pt: "PPT 正文确认时用 pt — 该交付目的建议 ~{lo}–{hi}pt(默认 {def}pt),确认后保存为 px。", + body_size_hint_oor: "(当前数值不在此区间——换画布不会自动换算,请确认是否合适。)", + delivery_purpose: "交付目的", + delivery_purpose_hint: "近读型可以小一点;投影型需要更大的字。", + size_override: "逐角色字号覆盖:", + size_role_title: "标题", + size_role_subtitle: "副标题", + size_role_annotation: "注释", custom_typography: "自定义字体方案", custom_typography_placeholder: "输入字体方案,如:标题用楷体;正文用微软雅黑…", custom_color: "自定义配色", @@ -497,7 +513,17 @@ var sec = section(1, "sec_canvas"); enumField(sec, CAT.canvas, recOrFirst("canvas", CAT.canvas), function () { return STATE.canvas; }, - function (v) { STATE.canvas = v; refreshBodySizeHint(); }, { allowCustom: true }); + function (v) { + STATE.canvas = v; + if (!STATE.typography) STATE.typography = { name: "", heading: {}, body: {} }; + // Canvas changes dimensions only — never silently rewrite font sizes + // the user can see / edit. The size hint re-renders with the new + // canvas; a default body is filled only when none is set yet. + if (!STATE.typography.body_size) { + STATE.typography.body_size = defaultBodySizeForCanvas(v, STATE.delivery_purpose); + } + renderAll(); + }, { allowCustom: true }); host.appendChild(sec); } @@ -561,6 +587,18 @@ // Replaced when the typography section mounts; the canvas section calls it so // the body-size hint tracks the chosen canvas height. var refreshBodySizeHint = function () {}; + // Replaced when the typography section mounts; body-size / delivery changes + // call it so the per-role size overrides the user hasn't pinned re-derive. + var refreshSizeInputs = function () {}; + + // Per-role size slots the user can edit directly (parallel to color roles). + // Defaults derive from `body` via mid-band ramp ratios (strategist.md §g); + // values follow body's unit (pt on PPT canvases, px otherwise). + var SIZE_ROLES = ["title", "subtitle", "annotation"]; + var SIZE_RATIO = { title: 1.75, subtitle: 1.35, annotation: 0.78 }; + function deriveSize(role, bodyVal) { + return Math.round((bodyVal || 0) * (SIZE_RATIO[role] || 1)); + } // Canvas height (viewBox user units) parsed from a catalog `dim` like // "1242×1660" or from a custom canvas string containing WxH; null if unknown. @@ -571,6 +609,99 @@ return m ? parseInt(m[2], 10) : null; } + function bodySizeRatioBand(canvasVal) { + var dim = null; + (CAT.canvas || []).forEach(function (o) { if (o.id === canvasVal) dim = o.dim; }); + var raw = String(dim || canvasVal || ""); + var id = String(canvasVal || "").toLowerCase(); + var isPpt = id === "ppt169" || id === "ppt43" || + /1280\s*[×xX*]\s*720/.test(raw) || + /1024\s*[×xX*]\s*768/.test(raw); + return isPpt ? { lo: 0.031, hi: 0.047 } : { lo: 0.025, hi: 0.033 }; + } + + // PPT canvases (16:9 / 4:3) carry the pt design language + pt→px conversion; + // social / print canvases have no PowerPoint pt semantics and stay in px. + function isPptCanvas(canvasVal) { + var dim = null; + (CAT.canvas || []).forEach(function (o) { if (o.id === canvasVal) dim = o.dim; }); + var raw = String(dim || canvasVal || ""); + var id = String(canvasVal || "").toLowerCase(); + return id === "ppt169" || id === "ppt43" || + /1280\s*[×xX*]\s*720/.test(raw) || + /1024\s*[×xX*]\s*768/.test(raw); + } + + // PPT confirmation pt band + default per delivery purpose (see strategist.md §g). + // The confirmed pt is converted to px (×4/3) before any spec is written. + function deliveryPtBand(purposeId) { + if (purposeId === "text") return { lo: 14, hi: 18, def: 16 }; + if (purposeId === "presentation") return { lo: 22, hi: 28, def: 24 }; + return { lo: 18, hi: 22, def: 20 }; // balanced — the default + } + + function defaultBodySizeForCanvas(canvasVal, purposeId) { + if (isPptCanvas(canvasVal)) return deliveryPtBand(purposeId).def; + var h = canvasHeight(canvasVal); + if (!h) return 40; + var band = bodySizeRatioBand(canvasVal); + return Math.round(h * (band.lo + band.hi) / 2); + } + + function roundSize(value) { + return Math.round(value * 100) / 100; + } + + function ptToPx(value) { + return roundSize(value * 4 / 3); + } + + function normalizeTypographyForSubmit(payload) { + if (!payload.typography || typeof payload.typography !== "object") return; + var typ = payload.typography; + var ppt = isPptCanvas(payload.canvas); + var body = parseFloat(typ.body_size); + if (!isFinite(body)) { + // Cleared / invalid body field — fall back so role sizes never submit + // against an empty anchor, and (on PPT) so the pt→px branch still runs + // instead of leaking pt-valued role sizes through as px. + body = defaultBodySizeForCanvas(payload.canvas, payload.delivery_purpose); + typ.body_size = body; + } + if (ppt && isFinite(body)) { + typ.body_size_pt = roundSize(body); + typ.body_size = ptToPx(body); + typ.body_size_unit = "px"; + typ.body_size_source_unit = "pt"; + if (typ.sizes && typeof typ.sizes === "object") { + var sizesPt = {}; + var sizesPx = {}; + Object.keys(typ.sizes).forEach(function (role) { + var raw = parseFloat(typ.sizes[role]); + if (!isFinite(raw)) return; + sizesPt[role] = roundSize(raw); + sizesPx[role] = ptToPx(raw); + }); + typ.sizes_pt = sizesPt; + typ.sizes = sizesPx; + } + return; + } + if (isFinite(body)) { + typ.body_size = roundSize(body); + typ.body_size_unit = "px"; + } + if (typ.sizes && typeof typ.sizes === "object") { + Object.keys(typ.sizes).forEach(function (role) { + var raw = parseFloat(typ.sizes[role]); + if (isFinite(raw)) typ.sizes[role] = roundSize(raw); + }); + } + delete typ.body_size_pt; + delete typ.sizes_pt; + delete payload.delivery_purpose; + } + function renderColor(host) { var cands = (REC.color && REC.color.candidates) || []; var sec = section(5, "sec_color"); @@ -729,32 +860,38 @@ customInput.placeholder = t("custom_typography_placeholder"); customInput.style.display = "none"; - function selectFont(idx) { + function selectFont(idx, preserveSizing) { var c = normTypography(cands[idx] || {}); + var prev = STATE.typography || {}; STATE.typography = { name: c.name || "", heading: c.heading || {}, body: c.body || {}, - body_size: c.body_size || (STATE.typography && STATE.typography.body_size) || "" + body_size: (preserveSizing && prev.body_size) ? prev.body_size : (c.body_size || prev.body_size || ""), + sizes: (preserveSizing && prev.sizes) ? Object.assign({}, prev.sizes) : Object.assign({}, c.sizes || {}) }; if (sizeInput) sizeInput.value = STATE.typography.body_size || ""; customInput.style.display = "none"; grid.querySelectorAll(".font-card").forEach(function (card, i) { card.classList.toggle("selected", i === idx); }); + refreshSizeInputs(); // fill any role with no value yet; never overwrites existing values refreshStylePreview(); } function selectCustomTypography() { + var prev = STATE.typography || {}; STATE.typography = { name: "custom", custom: customInput.value || "", heading: {}, body: {}, - body_size: (STATE.typography && STATE.typography.body_size) || "" + body_size: prev.body_size || "", + sizes: Object.assign({}, prev.sizes || {}) // switching font family must not drop sizes }; grid.querySelectorAll(".font-card").forEach(function (card) { card.classList.remove("selected"); }); customCard.classList.add("selected"); customInput.style.display = "block"; customInput.focus(); + refreshSizeInputs(); refreshStylePreview(); } @@ -766,7 +903,7 @@ top.appendChild(el("span", "font-card-name", localized(c, "name") || (t("option_prefix") + " " + (idx + 1)))); var meta = t("font_heading") + " " + t("cjk") + ":" + (head.cjk || "—") + " / " + t("latin") + ":" + (head.latin || "—") + " · " + t("font_body") + " " + t("cjk") + ":" + (body.cjk || "—") + " / " + t("latin") + ":" + (body.latin || "—"); - if (c.body_size) meta += " · " + t("font_body_size") + ":" + c.body_size + "px"; + if (c.body_size) meta += " · " + t("font_body_size") + ":" + c.body_size + (isPptCanvas(STATE.canvas) ? "pt" : "px"); top.appendChild(el("span", "font-card-meta", meta)); card.appendChild(top); var hbox = el("div", "font-sample-heading-box"); fontSample(hbox, head, head.css); card.appendChild(hbox); @@ -796,32 +933,115 @@ sizeInput.max = "96"; sizeInput.step = "1"; sizeInput.value = (STATE.typography && STATE.typography.body_size) || ""; - sizeInput.placeholder = "18 / 24"; + sizeInput.placeholder = isPptCanvas(STATE.canvas) ? "16 / 20 / 24" : "40 / 48"; sizeInput.addEventListener("input", function () { if (!STATE.typography) STATE.typography = { name: "", heading: {}, body: {} }; + // Independent input — body never auto-changes the role sizes (no + // interlinking); the role inputs carry their own values. STATE.typography.body_size = sizeInput.value; + refreshBodySizeHint(); // hint text only (e.g. out-of-range flag) — no value cascade refreshStylePreview(); }); sizeRow.appendChild(sizeInput); var sizeHint = el("div", "toggle-desc"); sizeRow.appendChild(sizeHint); - // Suggest a canvas-appropriate baseline (≈2.5–3.3% of canvas height) so - // a 16:9 default is not silently kept on a tall canvas. Hint only — the - // user's value is never overwritten; downstream §g re-derives if ignored. + // Hint only — the user's value is never overwritten; downstream §g + // re-derives if ignored. PPT canvases speak pt (range by delivery + // purpose); non-PPT canvases speak px (≈% of canvas height). refreshBodySizeHint = function () { - var h = canvasHeight(STATE.canvas); var txt = t("font_body_size_hint"); - if (h) { - txt += " " + t("body_size_hint_canvas") - .replace("{lo}", Math.round(h * 0.025)) - .replace("{hi}", Math.round(h * 0.033)); + var lo, hi; + if (isPptCanvas(STATE.canvas)) { + var pb = deliveryPtBand(STATE.delivery_purpose); + lo = pb.lo; hi = pb.hi; + txt += " " + t("body_size_hint_pt") + .replace("{lo}", pb.lo).replace("{hi}", pb.hi).replace("{def}", pb.def); + } else { + var h = canvasHeight(STATE.canvas); + var band = bodySizeRatioBand(STATE.canvas); + if (h) { + lo = Math.round(h * band.lo); hi = Math.round(h * band.hi); + txt += " " + t("body_size_hint_canvas") + .replace("{lo}", lo).replace("{hi}", hi); + } + } + // Flag (hint only — never auto-corrected) a value outside the range, + // e.g. a pt number kept after switching to a px canvas, so a cross-unit + // switch is visible instead of silently submitting a mis-unit size. + var cur = parseFloat(STATE.typography && STATE.typography.body_size); + if (isFinite(cur) && isFinite(lo) && isFinite(hi) && (cur < lo || cur > hi)) { + txt += " " + t("body_size_hint_oor"); } sizeHint.textContent = txt; }; refreshBodySizeHint(); sizeField.appendChild(sizeRow); + + // Delivery purpose (PPT only) — informs the recommended pt baseline and the + // suggested-range hint. Picking it updates the hint + strategy signal only; + // it does not rewrite the body field (inputs are independent, no cascade). + if (isPptCanvas(STATE.canvas)) { + var purposeField = el("div", "subfield"); + purposeField.appendChild(el("div", "subfield-label", t("delivery_purpose"))); + enumField(purposeField, CAT.delivery_purpose, + recOrFirst("delivery_purpose", CAT.delivery_purpose), + function () { return STATE.delivery_purpose; }, + function (v) { + STATE.delivery_purpose = v; + // Purpose only updates the suggested-range hint and the strategy + // signal it carries to result.json — it never rewrites the body + // size the user sees (no input interlinking). + refreshBodySizeHint(); + refreshStylePreview(); + }); + sec.appendChild(purposeField); + } sec.appendChild(sizeField); + // Per-role size override (parallel to color's per-role HEX override): the + // ramp derives title / subtitle / annotation from body, but the user may + // set each explicitly. Values follow body's unit (pt on PPT, px else). + var sizeOverride = el("div", "hex-override"); + sizeOverride.appendChild(el("div", "subfield-label", t("size_override"))); + var srow = el("div", "hex-row"); + var sizeInputs = {}; + SIZE_ROLES.forEach(function (role) { + var wrap = el("div", "hex-cell"); + wrap.appendChild(el("div", "hex-cell-label", t("size_role_" + role))); + var inp = document.createElement("input"); + inp.type = "number"; inp.min = "6"; inp.max = "200"; inp.step = "1"; + inp.addEventListener("input", function () { + if (!STATE.typography) STATE.typography = { name: "", heading: {}, body: {} }; + if (!STATE.typography.sizes) STATE.typography.sizes = {}; + // Independent input — each role holds its own value; no cascade. + STATE.typography.sizes[role] = inp.value; + refreshStylePreview(); + }); + sizeInputs[role] = inp; + wrap.appendChild(inp); srow.appendChild(wrap); + }); + sizeOverride.appendChild(srow); + sec.appendChild(sizeOverride); + + // Inputs are independent — this only **fills a role that has no value yet** + // (a one-time starting suggestion from the ramp) and reflects the current + // value into the input. It never overwrites an existing value, so editing + // body / purpose / canvas does not cascade into the role sizes, and a + // re-render (canvas / language switch) preserves exactly what the user sees. + refreshSizeInputs = function () { + if (!STATE.typography) STATE.typography = { name: "", heading: {}, body: {} }; + if (!STATE.typography.sizes) STATE.typography.sizes = {}; + var bodyVal = parseFloat(STATE.typography.body_size) || + (isPptCanvas(STATE.canvas) ? deliveryPtBand(STATE.delivery_purpose).def : 40); + SIZE_ROLES.forEach(function (role) { + var cur = STATE.typography.sizes[role]; + var hasVal = cur !== undefined && cur !== null && cur !== ""; + if (!hasVal) STATE.typography.sizes[role] = deriveSize(role, bodyVal); + if (sizeInputs[role]) sizeInputs[role].value = STATE.typography.sizes[role]; + }); + }; + refreshSizeInputs(); + var subfp = el("div", "subfield"); subfp.appendChild(el("div", "subfield-label", t("formula_policy"))); enumField(subfp, CAT.formula_policy, recOrFirst("formula_policy", CAT.formula_policy), @@ -831,7 +1051,7 @@ var selIdx = -1; if (STATE.typography && STATE.typography.name) cands.forEach(function (c, i) { if (c.name === STATE.typography.name) selIdx = i; }); - if (selIdx >= 0) selectFont(selIdx); + if (selIdx >= 0) selectFont(selIdx, true); else if (STATE.typography && STATE.typography.name === "custom") { customInput.value = STATE.typography.custom || ""; customCard.classList.add("selected"); @@ -888,7 +1108,10 @@ var acc = hexOr(pal.accent, pri); var sacc = hexOr(pal.secondary_accent, acc); var txt = hexOr(pal.body_text, "#1d2430"); - var bodyPx = Math.max(12, Math.min(26, parseInt(typ.body_size, 10) || 18)); + // body_size is pt on PPT canvases (×4/3 → px) and px elsewhere. + var rawSize = parseFloat(typ.body_size) || (isPptCanvas(STATE.canvas) ? 20 : 18); + var bodyPx = Math.max(12, Math.min(34, + isPptCanvas(STATE.canvas) ? rawSize * 4 / 3 : rawSize)); var headStack = previewFontStack(head.cjk, head.css); var headLatStack = previewFontStack(head.latin, head.css); var bodyStack = previewFontStack(body.cjk, body.css); @@ -1027,6 +1250,7 @@ // write to now-detached nodes until renderStylePreview remounts it. refreshStylePreview = function () {}; refreshBodySizeHint = function () {}; + refreshSizeInputs = function () {}; renderCanvas(host); renderPages(host); renderAudience(host); @@ -1077,7 +1301,8 @@ name: t0.name || "", heading: t0.heading || {}, body: t0.body || {}, - body_size: t0.body_size || typographyBodySize(REC.typography) + body_size: t0.body_size || typographyBodySize(REC.typography), + sizes: Object.assign({}, t0.sizes || {}) }; STATE.formula_policy = pick("formula_policy", CAT.formula_policy); @@ -1086,6 +1311,15 @@ STATE.generation_mode = pick("generation_mode", CAT.generation_mode); STATE.refine_spec = !!((REC.refine_spec && REC.refine_spec.value) || (REC.recommend && REC.recommend.refine_spec)); + // Delivery purpose drives the PPT confirmation pt baseline; default balanced + // (not the catalog-first id) when the Strategist did not recommend one. + STATE.delivery_purpose = recId("delivery_purpose") || "balanced"; + // Guarantee a body baseline even when a candidate omitted body_size, on + // any canvas (PPT → pt default by purpose, non-PPT → px from canvas height), + // so role sizes never derive from an empty anchor. + if (STATE.typography && !STATE.typography.body_size) { + STATE.typography.body_size = defaultBodySizeForCanvas(STATE.canvas, STATE.delivery_purpose); + } } // ---- confirm + close ------------------------------------------------- @@ -1098,7 +1332,8 @@ function confirm() { var btn = document.getElementById("btn-confirm"); - var payload = Object.assign({}, STATE); + var payload = JSON.parse(JSON.stringify(STATE)); + normalizeTypographyForSubmit(payload); var customImagePlan = usesCustomImagePlanValue(payload.image_usage); if (payload.image_usage === "custom" || (customImagePlan && !String(payload.image_usage).trim())) { document.getElementById("confirm-status").textContent = t("image_usage_custom_required"); diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/confirm_ui/static/catalogs.json b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/confirm_ui/static/catalogs.json index fb241feea..164f96c6a 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/confirm_ui/static/catalogs.json +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/confirm_ui/static/catalogs.json @@ -448,5 +448,31 @@ "label_zh": "分段模式", "label_en": "split" } + ], + "delivery_purpose": [ + { + "id": "text", + "label": "text", + "label_zh": "文字型 · 近读", + "label_en": "text / read-close", + "desc_zh": "当文件读、近距离看(报告、数据密集 brief、留底材料)。正文可小,14–18pt,默认 16pt。", + "desc_en": "Read close as a file (report, data-dense brief, leave-behind). Body can run small, 14–18pt, default 16pt." + }, + { + "id": "balanced", + "label": "balanced", + "label_zh": "均衡 · 商务", + "label_en": "balanced / business", + "desc_zh": "既投影也阅读(路演、业务评审)。默认档,18–22pt,默认 20pt。", + "desc_en": "Both projected and read (roadshow, business review). The default tier, 18–22pt, default 20pt." + }, + { + "id": "presentation", + "label": "presentation", + "label_zh": "展示型 · 演讲", + "label_en": "presentation / keynote", + "desc_zh": "投影、远距离扫一眼、内容稀疏(keynote、发布、课堂)。22–28pt,默认 24pt。", + "desc_en": "Projected, glanced from the back, sparse (keynote, launch, classroom). 22–28pt, default 24pt." + } ] } diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/docs/confirm_ui.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/docs/confirm_ui.md index cb38c1cb8..d9937e634 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/docs/confirm_ui.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/docs/confirm_ui.md @@ -39,7 +39,7 @@ pip install flask ## Catalogs — `static/catalogs.json` (the finite option universe) -The front-end loads `/api/catalogs` (served by the confirm server) and falls back to the static `/static/catalogs.json` if that route is unavailable. `/api/catalogs` returns the static file **with the `canvas` list synced live from `config.py CANVAS_FORMATS`** — the set of formats and their `dim` come from config (single source of truth, zero drift), while bilingual labels / use text stay in catalogs.json (a plain fallback label is synthesized for any new id config adds). Keys: `canvas`, `modes`, `visual_styles` (grouped), `icons`, `image_usage`, `image_ai_path`, `formula_policy`, `generation_mode`. Each entry is `{ "id", "label", "label_zh", "label_en", ... }`; descriptions use `desc_zh` / `desc_en`, and `visual_styles` groups use `group_zh` / `group_en`. The front-end falls back to legacy `label` / `desc` / `group`, so old catalogs still load, but new user-facing catalog text must be bilingual. English labels should mirror canonical reference names (`pyramid`, `swiss-minimal`, `Path A`, `mixed`, etc.); Chinese labels should be translated for users. Descriptions render inline after the option title, not as a separate selected-option line. `visual_styles` is `[{ "group", "group_zh", "group_en", "items": [...] }]`. For `canvas` you only need to maintain the bilingual labels in catalogs.json; the format set and dimensions are authoritative in `config.py CANVAS_FORMATS`. +The front-end loads `/api/catalogs` (served by the confirm server) and falls back to the static `/static/catalogs.json` if that route is unavailable. `/api/catalogs` returns the static file **with the `canvas` list synced live from `config.py CANVAS_FORMATS`** — the set of formats and their `dim` come from config (single source of truth, zero drift), while bilingual labels / use text stay in catalogs.json (a plain fallback label is synthesized for any new id config adds). Keys: `canvas`, `modes`, `visual_styles` (grouped), `icons`, `image_usage`, `image_ai_path`, `formula_policy`, `generation_mode`, `delivery_purpose`. Each entry is `{ "id", "label", "label_zh", "label_en", ... }`; descriptions use `desc_zh` / `desc_en`, and `visual_styles` groups use `group_zh` / `group_en`. The front-end falls back to legacy `label` / `desc` / `group`, so old catalogs still load, but new user-facing catalog text must be bilingual. English labels should mirror canonical reference names (`pyramid`, `swiss-minimal`, `Path A`, `mixed`, etc.); Chinese labels should be translated for users. Descriptions render inline after the option title, not as a separate selected-option line. `visual_styles` is `[{ "group", "group_zh", "group_en", "items": [...] }]`. For `canvas` you only need to maintain the bilingual labels in catalogs.json; the format set and dimensions are authoritative in `config.py CANVAS_FORMATS`. ## Round-trip data contract @@ -57,7 +57,8 @@ Both files live under `/confirm_ui/`. "icons": "tabler-outline", "image_usage": "web", "formula_policy": "mixed", - "generation_mode": "continuous" + "generation_mode": "continuous", + "delivery_purpose": "balanced" }, "page_count": { "value": "12-15" }, "audience": { "value": "..." }, @@ -82,7 +83,7 @@ Both files live under `/confirm_ui/`. { "name": "...", "note": "...", "heading": { "cjk": "思源黑体", "latin": "Inter", "css": "'Source Han Sans SC','Inter',sans-serif", "sample_cjk": "标题示例", "sample_latin": "Heading Sample" }, "body": { "cjk": "思源黑体", "latin": "Inter", "css": "...", "sample_cjk": "正文示例", "sample_latin": "Body sample" }, - "body_size": 18 } + "body_size": 20 } ] }, "image_strategy": { @@ -114,7 +115,9 @@ Both files live under `/confirm_ui/`. - For a custom image plan, the page treats "may include AI" as true only when the recommendation includes `recommend.image_ai_path` or `image_strategy.candidates`; a custom plan without those signals is handled as non-AI and omits AI controls / fields. - **Color candidates carry the user-facing core `palette`**: `background`, `secondary_bg`, `primary`, `accent`, `secondary_accent`, and `body_text`. The page renders labelled swatches and offers per-role override inputs for precise single-role edits, plus a **Custom color card with a free-text box** (parallel to the custom typography box) — the user can describe the palette in words or paste HEX values instead of filling each role; this writes `color: { "name": "custom", "custom": "" }` to `result.json` for the AI to interpret. Legacy `text` is accepted as an alias for `body_text`, but new files should write `body_text`. Strategist derives secondary text, borders, state colors, and visual-style neutral tiers later when writing `design_spec.md` / `spec_lock.md`; those are not user-facing confirmation choices. - **Candidate display text may be bilingual**: color / typography candidates can provide `name_zh` / `name_en` and `note_zh` / `note_en`; the page falls back to legacy `name` / `note`. -- **Typography candidates split CJK and Latin** for both `heading` and `body`; `css` is the fallback preview `font-family` stack. The page previews CJK sample text with `cjk + css` and Latin sample text with `latin + css`, so the two script choices are visible independently. Each candidate should also include `body_size` (the body baseline in px; common values are 18 and 24, but any reasonable integer is valid). The page exposes `body_size` as an editable numeric field whose hint shows a canvas-appropriate range (≈2.5–3.3% of the confirmed canvas height, parsed from the canvas `dim`), updated when the canvas changes — a hint only, never an override, so a 16:9 default is not silently kept on a tall canvas (Xiaohongshu / Story / A4). It also offers a custom typography text box so the user is not limited to the proposed candidates. +- **Typography candidates split CJK and Latin** for both `heading` and `body`; `css` is the fallback preview `font-family` stack. The page previews CJK sample text with `cjk + css` and Latin sample text with `latin + css`, so the two script choices are visible independently. Each candidate should also include `body_size` — the body baseline, **shown in pt for PPT canvases (16:9 / 4:3), in px for non-PPT canvases** (Xiaohongshu / Story / A4 have no PowerPoint pt semantics). For PPT, the page shows/edits pt; the initial value comes from the candidate's `body_size` (sized for the recommended delivery purpose: 文字型 ~16 · 均衡 ~20 · 展示型 ~24). On submit, the page normalizes PPT values to px before writing `result.json`: `typography.body_size` is px, `typography.body_size_pt` preserves the original pt for provenance. Chat fallback must produce the same normalized contract before `design_spec.md` / `spec_lock.md` are written. The page exposes `body_size` as an editable numeric field whose hint shows a canvas-appropriate range (PPT 14–28pt by delivery purpose, non-PPT ≈2.5–3.3% of height in px). **Inputs are independent — the hint updates with canvas / delivery purpose but never rewrites a value the user can see.** It also offers a custom typography text box so the user is not limited to the proposed candidates. +- **Per-role size override** (parallel to color's per-role HEX override): besides `body_size`, the page exposes **independent** editable inputs for `title` / `subtitle` / `annotation`. Each role is pre-filled **once** with a starting value — the candidate's `typography.sizes[role]` if provided, otherwise a one-time ramp suggestion (`body × ` mid-band ratio) — and then holds its own value. **There is no cross-field cascade**: changing `body_size`, `delivery_purpose`, or canvas updates only the suggested-range hint, never the role values; a re-render preserves exactly what the user sees. The final values are written to `result.json` as `typography.sizes: { "title", "subtitle", "annotation" }` in px; for PPT canvases, `typography.sizes_pt` preserves the original pt values for provenance. Seeding `sizes` in a candidate is optional — omit it and each role gets its one-time ramp suggestion. +- **`delivery_purpose`** (enumerable, PPT only) is the primary driver of the body baseline: `text` (文字型 · read-close), `balanced` (均衡 · business, the default), `presentation` (展示型 · presentation). It is surfaced inside the typography section (item 7) because it informs the recommended body pt and the suggested-range hint above — but the pick itself does not rewrite the body field (inputs are independent). `recommend.delivery_purpose` pre-selects one (default `balanced`); the user's pick writes back to `result.json.delivery_purpose` as a plain id. Strategist uses it to choose the normalized px body baseline and the within-band density/style nudge (see [strategist.md §g](../../references/strategist.md)). Non-PPT canvases omit it. - **Combined style preview** — a compact live "overall impression" strip sits just above the color section and is **sticky**: it pins under the topbar so it stays visible while the user scrolls through the color / icon / typography sections, keeping the picking controls and their combined effect on screen together. It applies the currently selected color palette **and** typography (heading sample in `primary` over `background`, body sample in `body_text`, an `accent` bar, a `secondary_bg` chip) and repaints on every color / HEX-override / font / `body_size` change. It does not replace the per-candidate swatches or font samples (those stay for picking); it is deliberately an abstract style chip, **not** a slide-layout preview — page layout preview remains the live-preview server's job (Step 6). No schema field; it derives entirely from the existing color + typography selections. - **Generated image style candidates** live in `image_strategy.candidates` and are shown only when `image_usage` is `ai` or a custom image plan may include generated images. Each candidate records `rendering`, `palette`, and short `visual` / `color` / `mood` lines from Strategist h.5. The chosen value is written to `result.json.image_strategy`; it is omitted when generated images are not part of the plan. - **`visual_style_spectrum`** (optional) lets the AI surface the deck's aesthetic as a **personality spectrum** instead of one badged style. Each entry is `{ "id", "tag_zh"/"tag_en", "note_zh"/"note_en" }` where `id` is a real `visual_styles` catalog id; the page badges those chips with their temperament `tag` (replacing the single ★) and appends the `note` (a real-world analogy) inline. The full grouped style list and Custom box stay visible below, and `recommend.visual_style` is still the pre-selected default (it should equal the spectrum's safe pick). Author **≥3** spanning safe / shifted / bold (mirrors h.5; honest-shortfall exception applies — fewer only when the constraints genuinely cannot yield 3). The user's pick still writes back to `result.json.visual_style` as a plain id; the spectrum is presentation-only. Omit the field to fall back to the single-recommendation badge. @@ -134,7 +137,8 @@ Both files live under `/confirm_ui/`. "visual_style": "swiss-minimal", "color": { "name": "...", "palette": { "background": "#...", "secondary_bg": "#...", "primary": "#...", "accent": "#...", "secondary_accent": "#...", "body_text": "#..." } }, "icons": "tabler-outline", - "typography": { "name": "...", "heading": { "cjk": "...", "latin": "...", "css": "..." }, "body": { "cjk": "...", "latin": "...", "css": "..." }, "body_size": 18 }, + "typography": { "name": "...", "heading": { "cjk": "...", "latin": "...", "css": "..." }, "body": { "cjk": "...", "latin": "...", "css": "..." }, "body_size": 26.67, "body_size_pt": 20, "body_size_unit": "px", "body_size_source_unit": "pt", "sizes": { "title": 46.67, "subtitle": 36, "annotation": 21.33 }, "sizes_pt": { "title": 35, "subtitle": 27, "annotation": 16 } }, + "delivery_purpose": "balanced", "formula_policy": "mixed", "image_usage": "web", "image_strategy": { "name": "方案 A", "rendering": "vector-illustration", "palette": "cool-corporate", "visual": "...", "color": "...", "mood": "..." }, diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pt_to_px.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pt_to_px.py new file mode 100644 index 000000000..319f01ffb --- /dev/null +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pt_to_px.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +""" +PPT Master - Point-to-Pixel Font Size Converter + +Deterministic pt <-> SVG-px font-size conversion, the single source of truth for +the chat-fallback path of the Eight Confirmations (the Confirm UI does the same +math in app.js at submit time). PPT canvases confirm font sizes in pt; the +execution layer (design_spec.md / spec_lock.md / SVG) carries unitless px only. +Convert here instead of mental arithmetic so the chat path matches the UI path. + + px = pt * 4 / 3 (the inverse of the exporter's px * 0.75; canvas 1280px -> 960pt slide) + +Rounding matches app.js `roundSize` (2 decimals, round-half-up), so a value +confirmed in pt round-trips back to the same pt at export. + +Usage: + python3 scripts/pt_to_px.py [ ...] + python3 scripts/pt_to_px.py --reverse [ ...] + +Examples: + python3 scripts/pt_to_px.py 20 # -> 26.67 (write into spec_lock body) + python3 scripts/pt_to_px.py 20 36 27 15 # body title subtitle annotation -> 26.67 48 36 20 + python3 scripts/pt_to_px.py --reverse 26.67 # -> 20 (what PowerPoint will display) + +Dependencies: + None (only uses standard library) + +See references/strategist.md §g (Font Size Ramp) for the pt/px unit boundary. +""" + +import sys +import math +import argparse +from typing import Optional + +PT_TO_PX = 4 / 3 # px = pt * 4/3 +PX_TO_PT = 0.75 # pt = px * 0.75 (mirrors svg_to_pptx FONT_PX_TO_HUNDREDTHS_PT) + + +def _round_half_up(value: float, decimals: int = 2) -> float: + """Round half-up to ``decimals`` places, matching app.js ``Math.round``.""" + factor = 10 ** decimals + return math.floor(value * factor + 0.5) / factor + + +def pt_to_px(pt: float) -> float: + """Convert a pt font size to its unitless SVG px value.""" + return _round_half_up(pt * PT_TO_PX) + + +def px_to_pt(px: float) -> float: + """Convert an SVG px font size back to the pt PowerPoint will display.""" + return _round_half_up(px * PX_TO_PT) + + +def _fmt(value: float) -> str: + """Drop a trailing ``.0`` so integers print clean (32, not 32.0).""" + return str(int(value)) if value == int(value) else f"{value:.2f}" + + +def _parse_size(raw: str) -> float: + """Accept a bare number or a value with a stray pt/px suffix.""" + return float(raw.strip().lower().removesuffix("pt").removesuffix("px").strip()) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Convert pt font sizes to unitless SVG px (or back with --reverse).", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("sizes", nargs="+", help="One or more font sizes to convert.") + parser.add_argument( + "-r", "--reverse", action="store_true", + help="Convert px -> pt instead of pt -> px (debug / verification).", + ) + return parser + + +def main(argv: Optional[list[str]] = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + convert = px_to_pt if args.reverse else pt_to_px + try: + values = [convert(_parse_size(s)) for s in args.sizes] + except ValueError: + print("Error: every size must be a number (optionally suffixed pt/px).", + file=sys.stderr) + return 1 + print(" ".join(_fmt(v) for v in values)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_quality_checker.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_quality_checker.py index c5c4bbc6e..53b68466b 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_quality_checker.py +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_quality_checker.py @@ -264,31 +264,34 @@ class SVGQualityChecker: # 2. Check forbidden elements self._check_forbidden_elements(content, result) - # 3. Check fonts + # 3. Check font-size values + self._check_font_size_values(content, result) + + # 4. Check fonts self._check_fonts(content, result) - # 4. Check width/height consistency with viewBox + # 5. Check width/height consistency with viewBox self._check_dimensions(content, result) - # 5. Check text wrapping methods + # 6. Check text wrapping methods self._check_text_elements(content, result) - # 6. Check image references (file existence and resolution) + # 7. Check image references (file existence and resolution) self._check_image_references(content, svg_path, result) - # 7. Check object-level animation anchor quality. + # 8. Check object-level animation anchor quality. self._check_animation_group_ids(content, result) - # 7b. Check elements declare a PPTX preset. + # 8b. Check elements declare a PPTX preset. self._check_pattern_fills(content, result) - # 8. Check spec_lock drift (colors / font-family / font-size). + # 9. Check spec_lock drift (colors / font-family / font-size). # Templates do not ship a spec_lock.md, so skip in template # mode to avoid noise. if not self.template_mode: self._check_spec_lock_drift(content, svg_path, result) - # 9. Check web-sourced image attribution. Templates don't carry + # 10. Check web-sourced image attribution. Templates don't carry # image_sources.json; skip in template mode. if not self.template_mode: self._check_sourced_image_attribution(content, svg_path, result) @@ -460,6 +463,31 @@ class SVGQualityChecker: if re.search(r']*\sopacity\s*=', content_lower): result['errors'].append("Detected forbidden (use overlay mask approach)") + def _check_font_size_values(self, content: str, result: Dict): + """Require font-size values to be unitless numeric SVG px values.""" + numeric_re = re.compile(r'^(?:\d+(?:\.\d+)?|\.\d+)$') + bad_values = set() + + for match in re.finditer(r'\bfont-size\s*=\s*(["\'])(.*?)\1', content, re.IGNORECASE): + raw = match.group(2).strip() + if not numeric_re.fullmatch(raw): + bad_values.add(raw) + + for match in re.finditer(r'\bfont-size\s*:\s*([^;"\']+)', content, re.IGNORECASE): + raw = match.group(1).strip() + if not numeric_re.fullmatch(raw): + bad_values.add(raw) + + if bad_values: + shown_values = sorted(bad_values) + shown = ', '.join(shown_values[:5]) + more = len(shown_values) - 5 + suffix = f" (+{more} more)" if more > 0 else "" + result['errors'].append( + f"font-size must be a unitless numeric px value; found {shown}{suffix}. " + "Write e.g. font-size=\"28\", never font-size=\"28px\" or \"21pt\"." + ) + def _check_fonts(self, content: str, result: Dict): """Check font usage. @@ -734,6 +762,22 @@ class SVGQualityChecker: allowed_colors.add(v.upper()) typo = lock.get('typography', {}) + numeric_size_re = re.compile(r'^(?:\d+(?:\.\d+)?|\.\d+)$') + invalid_lock_sizes = [] + for k, v in typo.items(): + if k == 'font_family' or k.endswith('_family'): + continue + if not numeric_size_re.fullmatch(v.strip()): + invalid_lock_sizes.append(f"{k}: {v}") + if invalid_lock_sizes: + shown = ', '.join(invalid_lock_sizes[:5]) + more = len(invalid_lock_sizes) - 5 + suffix = f" (+{more} more)" if more > 0 else "" + result['errors'].append( + f"spec_lock typography sizes must be unitless numeric px values; " + f"found {shown}{suffix}." + ) + # Font families: default `font_family` plus any per-role `*_family` # override (title_family / body_family / emphasis_family / code_family, # per spec_lock_reference.md). Any of these is a legitimate declared @@ -893,9 +937,12 @@ class SVGQualityChecker: @staticmethod def _normalize_size(value: str) -> str: - """Normalize a font-size value for comparison: lowercase, strip spaces, - strip trailing 'px'. Other units (em / rem / %) are kept as-is so that - e.g. '1.5em' vs '24' stay distinct.""" + """Normalize a font-size value for drift comparison. + + Unit-bearing SVG values are reported as errors before drift checking. + The legacy `px` strip remains to avoid a duplicate drift warning after + the hard error has already identified the unit problem. + """ v = value.strip().lower() if v.endswith('px'): v = v[:-2].strip() diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_to_pptx/drawingml_elements.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_to_pptx/drawingml_elements.py index a3a459ebf..2197e8e4f 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_to_pptx/drawingml_elements.py +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/svg_to_pptx/drawingml_elements.py @@ -1007,7 +1007,11 @@ def _build_run_xml( text_dec = run.get('text_decoration', '') - sz = round(fs_px * FONT_PX_TO_HUNDREDTHS_PT) + # Snap the exported font size to the nearest 0.5pt so PowerPoint shows a + # clean value (integer or half-point), never 15.6pt / 22.67pt. Exact size is + # fs_px * FONT_PX_TO_HUNDREDTHS_PT hundredths-of-pt; round that to the + # 50-hundredths (0.5pt) grid. (Line spacing below keeps exact precision.) + sz = int(fs_px * FONT_PX_TO_HUNDREDTHS_PT / 50 + 0.5) * 50 b_attr = ' b="1"' if fw in ('bold', '600', '700', '800', '900') else '' i_attr = ' i="1"' if fstyle == 'italic' else '' u_attr = ' u="sng"' if 'underline' in text_dec else '' 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 49f1e71f0..1d319943d 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 @@ -14,6 +14,7 @@ | **Design Style** | {design_style} | | **Target Audience** | [Filled by Strategist] | | **Use Case** | [Filled by Strategist] | +| **Delivery Purpose** | [文字型 read-close / 均衡 business / 展示型 presentation — confirmed at item 7; a deck-wide consumption mode that drives per-page density, page-count recommendation, page_rhythm lean, and the normalized body baseline. See strategist.md §6.1.] | | **Content Strategy** | [Material divergence — the user's free-text intent on how closely to follow the source vs how freely to reshape it (or "balanced default"); facts stay sourced however free. Confirmed at c; consumed when authoring §IX. Not in spec_lock.] | | **Created Date** | {date_str} | @@ -98,7 +99,7 @@ **Typography direction**: [Fill in one phrase, e.g., "modern CJK sans" / "academic serif" / "brand-specific: McKinsey Bower (requires font install)"] -> Step 4 Confirm UI: present **≥3** typography candidates (creative recommendations always offer real choice — same rule as h.5; fewer only on the honest-shortfall exception, with a stated reason), each splitting CJK + Latin for `heading` and `body` (with `css` preview stacks) and declaring `body_size` as the body baseline px, in `confirm_ui/recommendations.json`; the confirmed candidate from `result.json` seeds the plan below. Schema: [`scripts/docs/confirm_ui.md`](../scripts/docs/confirm_ui.md). +> Step 4 Confirm UI: present **≥3** typography candidates (creative recommendations always offer real choice — same rule as h.5; fewer only on the honest-shortfall exception, with a stated reason), each splitting CJK + Latin for `heading` and `body` (with `css` preview stacks) and declaring `body_size` as the user-facing body baseline (pt for PPT canvases, px for non-PPT), in `confirm_ui/recommendations.json`; the confirmed `result.json` normalizes the execution value to px before this spec is written. Schema: [`scripts/docs/confirm_ui.md`](../scripts/docs/confirm_ui.md). Two views on the same font decisions — fill both, keep them consistent: @@ -129,24 +130,27 @@ Two views on the same font decisions — fill both, keep them consistent: ### Font Size Hierarchy -> **Ramp discipline, not a fixed menu.** `body` is the single anchor; every other size is a ratio of it. Each row below gives the role's allowed ratio band — Executor may pick any px value inside the band (e.g., 40px hero number, 13px chart annotation, 72px cover headline) without pre-declaring intermediates in `spec_lock.md`. -> **Unit**: px uniformly (SVG native) to avoid pt/px conversion errors. -> **Baseline selection**: drive by **content density**, not design style. +> **Ramp discipline, not a fixed menu.** `body` is the single anchor; every other size is a ratio of it. Each row below gives the role's allowed ratio band. **Structural roles (page title / body / subtitle / annotation / footnote) resolve to one size each and stay that size deck-wide** — pick the value once, lock it, reuse it on every page; same-role drift is what makes a deck look unprofessional. The in-band freedom to use an intermediate value without pre-declaring is for **special / feature elements** (hero number, cover / section display headline, one-off emphasis); if such a size recurs, declare it as its own slot so it too stays consistent. +> **Unit boundary (HARD rule).** Author this section in **unitless px**. PPT canvases may be confirmed in pt, but that conversion is already resolved before writing this spec (`px = pt × 4⁄3`); `design_spec.md`, `spec_lock.md`, and SVG carry px only. Never write `pt`, `px`, `em`, or any unit in `spec_lock.md` or SVG. Geometry (margins / gaps / card sizes) is px everywhere. Non-PPT canvases use px throughout. +> **Baseline selection**: drive by **delivery purpose** first (read-close vs. presentation), then content density; visual style only nudges within the chosen band. -**Baseline**: Body font size = [fill in]px (any reasonable integer — `18` and `24` are most common; `16` for chart-heavy, `20`/`22` for medium density, `28-32` for poster / cover decks are all valid. Drive by content density.) +**Baseline (unitless px)**: Body font size = [fill in]. For PPT 16:9, normalize from the confirmed delivery-purpose pt band: **文字型 / read-close** `14–18pt` → `18.67–24`, **均衡 / business** `18–22pt` → `24–29.33` (default `26.67`), **展示型 / presentation** `22–28pt` → `29.33–37.33`. Within the band, density picks the point (6+ items → low · sparse / cover → high) and visual style nudges (technical / data low · corporate / instructional mid · editorial / narrative / showcase high). The user may also pin individual role sizes (`title` / `subtitle` / `annotation`) directly in the Confirm UI — a confirmed per-role value (`result.json typography.sizes`) is already px and becomes the locked slot for that role; the rest derive from the ramp. For non-PPT canvases, author px from the confirmed canvas scale (see [strategist.md §g](../references/strategist.md) per-canvas table). -| Purpose | Ratio to body | Example @ body=24 (relaxed) | Example @ body=18 (dense) | Weight | +| Purpose | Ratio to body | Example @ body=32 (24pt UI) | Example @ body=24 (18pt UI) | Weight | | ------- | ------------- | --------------------------- | ------------------------- | ------ | -| Cover title (hero headline) | 2.5-5x | 60-120px | 45-90px | Bold / Heavy | -| Chapter / section opener | 2-2.5x | 48-60px | 36-45px | Bold | -| Page title | 1.5-2x | 36-48px | 27-36px | Bold | -| Hero number (consulting KPIs) | 1.5-2x | 36-48px | 27-36px | Bold | -| Subtitle | 1.2-1.5x | 29-36px | 22-27px | SemiBold | -| **Body content** | **1x** | **24px** | **18px** | Regular | -| Annotation / caption | 0.7-0.85x | 17-20px | 13-15px | Regular | -| Page number / footnote | 0.5-0.65x | 12-16px | 9-12px | Regular | +| Cover title (hero headline) | 2.5-5x | 80-160 | 60-120 | Bold / Heavy | +| Chapter / section opener | 2-2.5x | 64-80 | 48-60 | Bold | +| Page title | 1.5-2x | 48-64 | 36-48 | Bold | +| Hero number (consulting KPIs) | 1.5-2x | 48-64 | 36-48 | Bold | +| Subtitle | 1.2-1.5x | 38-48 | 29-36 | SemiBold | +| Lead-in / intro | 1.1-1.4x | 35-45 | 26-34 | Regular / Medium | +| Subheading | 1.1-1.3x | 35-42 | 26-31 | SemiBold | +| **Body content** | **1x** | **32** | **24** | Regular | +| Annotation / caption | 0.7-0.85x | 22-27 | 17-20 | Regular | +| Page number / footnote | 0.5-0.65x | 16-21 | 12-16 | Regular | -> The two px columns are illustrations for common baselines. For any other `body` value, multiply by each row's ratio — the checker (`svg_quality_checker._check_spec_lock_drift`) reads the live `body` from `spec_lock.md` and applies the bands, so no code change is needed for a different baseline. +> **Subtitle / lead-in / subheading bands overlap by design** — choose among them by *role*, not size: `subtitle` sits under a title, `lead` is a lead-in / pull-quote in the body flow, `subheading` labels a block inside the content area. Each is its own slot, declared only when the deck uses it, and then held at one size deck-wide like any structural role. Font stays at the **family** level (no new typeface per role): `subheading` → heading / `title_family`, `lead` → `body_family` or `emphasis_family` — size + weight carry the hierarchy. +> The two px columns are illustrations for common baselines. For any other `body` value, multiply by each row's ratio. When the confirmation came from PPT pt, you may record the provenance once (e.g. "confirmed as 20pt") in prose, but the size values in this section and in `spec_lock.md` stay normalized px. The checker (`svg_quality_checker._check_spec_lock_drift`) reads the live `body` (px) from `spec_lock.md` and applies the bands, so no code change is needed for a different baseline. > Sizes outside **every** band remain forbidden — surface the need and extend `spec_lock.md typography` (e.g., `cover_title: 96`) rather than invent a one-off value. 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 5b2b8b37e..e02d3b37e 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 @@ -54,10 +54,10 @@ - body_family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif - emphasis_family: Georgia, SimSun, serif - code_family: Consolas, "Courier New", monospace -- body: 22 -- title: 32 -- subtitle: 24 -- annotation: 14 +- body: 26.67 +- title: 48 +- subtitle: 36 +- annotation: 20 > **All five family lines are listed explicitly** so Strategist considers every role — `code_family` and `emphasis_family` are easily forgotten. In a real `spec_lock.md`: > - Keep any `*_family` whose role genuinely differs from `font_family`. @@ -68,9 +68,9 @@ > > **Source**: copy verbatim from the *Per-role font stacks* list in `design_spec.md §IV Font Plan`. Stack **order** encodes browser-rendering intent (Latin-led vs. CJK-led) that the breakdown table cannot — strings here must match character-for-character. See `design_spec.md §IV` for the explainer. > -> Sizes (`body` / `title` / etc.) are in px, matching SVG units. `body` is the **required baseline anchor** — all other sizes derive as ratios of it (ramp table: `design_spec_reference.md §IV`). +> Sizes (`body` / `title` / etc.) are **unitless px numbers** — the execution unit and the same values recorded in `design_spec.md §IV`. PPT canvases may have been confirmed in pt, but that conversion is resolved before spec writing (`px = pt × 4⁄3`; e.g. body 20pt → `26.67`, title 36pt → `48`, subtitle 27pt → `36`, annotation 15pt → `20`). For non-PPT canvases there is no pt layer — the px is authored directly. Do not write `pt` / `px` / `em` or any unit, and **do not copy a confirmed pt number unconverted** (`20` would render as 15pt — the one error the unit guard cannot catch). `body` is the **required baseline anchor** — all other sizes derive as ratios of it (ramp table: `design_spec_reference.md §IV`). > -> **Size slots are anchors, not a closed menu.** Common slots (`title` / `subtitle` / `annotation`) cover frequent cases. Add role-specific slots (e.g. `cover_title: 72`, `hero_number: 48`, `chart_annotation: 13`) when needed — common for cover-heavy decks, consulting-style hero numbers, dense pages. Executor may use intermediate sizes as long as the ratio to `body` sits in the role's ramp band. +> **Size slots are anchors, not a closed menu.** Common slots (`title` / `subtitle` / `annotation`) cover frequent cases. Add role-specific slots (e.g. `cover_title: 88`, `hero_number: 56`, `subheading: 32`, `lead: 32`, `chart_annotation: 18`) when needed — common for cover-heavy decks, consulting-style hero numbers, dense pages. `subheading` and `lead` sit between `subtitle` and `body`; their bands overlap `subtitle`, so pick by role, not size, and declare only the ones the deck uses. **Structural roles (title / body / subtitle / annotation / footnote) render at their locked size on every page — one role, one size, deck-wide.** Intermediate in-band sizes are for special / feature elements only (hero number, display title, one-off emphasis); declare a recurring one as its own slot so it stays consistent too. > > **⚠️ PPT-safe stack discipline (HARD rule).** PPTX stores one `typeface` per run with no runtime fallback. Every stack MUST end with a cross-platform pre-installed font: `"Microsoft YaHei", sans-serif` / `SimSun, serif` / `Arial, sans-serif` / `"Times New Roman", serif` / `Consolas, "Courier New", monospace`. Non-preinstalled fonts (Inter / Google Fonts / brand typefaces) may lead the stack only when the Design Spec notes the font-install or embedding requirement. > diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/beautify-pptx.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/beautify-pptx.md index 2b590a856..d63df2e94 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/beautify-pptx.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/workflows/beautify-pptx.md @@ -181,8 +181,8 @@ Write `/confirm_ui/recommendations.json` and launch the same confi { "name_zh": "备选配色 A", "name_en": "Alternative palette A", "palette": { "background": "#...", "secondary_bg": "#...", "primary": "#...", "accent": "#...", "secondary_accent": "#...", "body_text": "#..." } } ] }, "typography": { "selected": 0, "candidates": [ - { "name_zh": "复刻源 PPT(推荐)", "name_en": "Source replica (recommended)", "heading": { "cjk": "...", "latin": "...", "css": "" }, "body": { "cjk": "...", "latin": "...", "css": "" }, "body_size": }, - { "name_zh": "实际字体(observed)", "name_en": "Observed fonts", "heading": { "cjk": "...", "latin": "...", "css": "" }, "body": { "cjk": "...", "latin": "...", "css": "" }, "body_size": }, + { "name_zh": "复刻源 PPT(推荐)", "name_en": "Source replica (recommended)", "heading": { "cjk": "...", "latin": "...", "css": "" }, "body": { "cjk": "...", "latin": "...", "css": "" }, "body_size": }, + { "name_zh": "实际字体(observed)", "name_en": "Observed fonts", "heading": { "cjk": "...", "latin": "...", "css": "" }, "body": { "cjk": "...", "latin": "...", "css": "" }, "body_size": }, { "name_zh": "备选字体 A", "name_en": "Alternative pairing A", "heading": { "cjk": "...", "latin": "...", "css": "" }, "body": { "cjk": "...", "latin": "...", "css": "" }, "body_size": } ] } } @@ -191,7 +191,7 @@ Write `/confirm_ui/recommendations.json` and launch the same confi - **Recommend keep, allow override**: pre-fill canvas / mode / visual style / icons / image strategy with the source-faithful default (canvas = Step 3 format, mode = `briefing`, image_usage = `provided` since pictures are reused). Enumerable fields already list every catalog option with the source-faithful one badged, so the user can switch. Beautify's only true non-choices are the frozen text and the strict 1:1 page count (changing those means routing to the main pipeline instead — see CLAUDE.md). The §c material-divergence field is therefore not surfaced here — beautify never reshapes content (text is verbatim). - **Our recommendation is the pre-selected default = the source replica**: for color and typography, author **several candidates** like the from-scratch flow. The pre-selected default (`selected: 0`, the first card) is what beautify recommends — the candidate that **best replicates the source deck's style** (the truest reading of `theme` / `observed`). Replicate-by-default. - **Judge the other alternatives exactly as the from-scratch flow does — fonts as much as colors**: don't invent a beautify-specific rule. Author each non-replica candidate with the **same content-driven judgment the Strategist uses when generating from scratch** (color §e, typography §g), applied to the material this project provides — the source document's content and subject, the company's own theme colors, and any brand signal. Pick the palette **and** the font pairing by what fits *this* deck's content; fonts are chosen by content fit, not just defaulted to a safe face. Reach **≥3 candidates total** (PPT-safe stacks; the same creative-choice rule used elsewhere) so a user who departs from the replica still lands on a considered, content-fitting direction — depart-by-choice. -- **`body_size` is the load-bearing field, and the replica follows the source's own size**: seed the replica candidate's `body_size` (px) from the source's actual body size, not a blind canvas default — take the dominant `observed.sizes_pt` value (the most frequent run-level size, the **body proxy**) and convert to px (`px = pt ÷ 0.75`, e.g. source 13.5pt → `18`). The "most frequent = body" read is a proxy, not a guarantee — `observed.sizes_pt` counts every explicit run size (titles, captions, footnotes, chart/label text included, no placeholder-type resolution), so a deck dense with small labels can let a caption size outrank true body; cross-check the proxy against the page's actual body blocks and the sanity range below before trusting it, and prefer the size the body paragraphs visibly render at over the raw mode when the two disagree. Fall back to `theme.sizes.body` (the declared placeholder size) when `observed.sizes_pt` is empty, and to a canvas-appropriate baseline (≈2.5–3.3% of canvas height) only when neither is present. Note `theme.sizes.body` is the master `bodyStyle` **level-1 declared default** — a coarse value that commonly **over-reads** the real body density (decks often render body at a deeper outline level or override it smaller), so when you land on this fallback treat it as an upper-ish guess and run it through the sanity check below, never as a precise body size. `theme.sizes.body_levels` and `layout_sizes_pt` are **reference context, not extra fallback tiers**: consult them to judge a saner body value when the deck is theme-driven (`observed` empty) — e.g. a deeper `body_levels` entry or a `layout_sizes_pt` hint may read truer than level-1 — but do not auto-seed from them; the seed chain stays `observed → theme.sizes.body → canvas baseline`, and a theme-driven deck whose body size genuinely can't be pinned cleanly is exactly the case the sanity check is for. The canvas hint stays a **sanity range**, not the seed: if the source's own size lands far outside it (a dense source doc reads tiny on a projection canvas), surface that to the user rather than silently snapping — the replica recommendation is the source's size, the user confirms or overrides. Non-replica alternatives may use the canvas baseline. This is what prevents the deck from exporting at an unintentionally small size while still honoring the source. +- **`body_size` is the load-bearing field, and the replica follows the source's own size**: seed the replica candidate's `body_size` (pt, because this is the confirmation layer for a PPT canvas) from the source's actual body size — take the dominant `observed.sizes_pt` value (the most frequent run-level size, the **body proxy**) without converting it. The confirm page writes normalized px to `result.json` (`body_size`) and keeps the original pt as `body_size_pt`; chat fallback must do the same normalization before `design_spec.md` / `spec_lock.md`. The "most frequent = body" read is a proxy, not a guarantee — `observed.sizes_pt` counts every explicit run size (titles, captions, footnotes, chart/label text included, no placeholder-type resolution), so a deck dense with small labels can let a caption size outrank true body; cross-check the proxy against the page's actual body blocks and the sanity range below before trusting it, and prefer the size the body paragraphs visibly render at over the raw mode when the two disagree. Fall back to `theme.sizes.body` (the declared placeholder size) when `observed.sizes_pt` is empty, and to a PPT delivery-purpose baseline (read-close 14–18pt / balanced 18–22pt / presentation 22–28pt) only when neither is present. Note `theme.sizes.body` is the master `bodyStyle` **level-1 declared default** — a coarse value that commonly **over-reads** the real body density (decks often render body at a deeper outline level or override it smaller), so when you land on this fallback treat it as an upper-ish guess and run it through the sanity check below, never as a precise body size. `theme.sizes.body_levels` and `layout_sizes_pt` are **reference context, not extra fallback tiers**: consult them to judge a saner body value when the deck is theme-driven (`observed` empty) — e.g. a deeper `body_levels` entry or a `layout_sizes_pt` hint may read truer than level-1 — but do not auto-seed from them; the seed chain stays `observed → theme.sizes.body → delivery-purpose baseline`, and a theme-driven deck whose body size genuinely can't be pinned cleanly is exactly the case the sanity check is for. The canvas hint stays a **sanity range**, not the seed: if the source's own size lands far outside it (a dense source doc reads tiny on a projection canvas), surface that to the user rather than silently snapping — the replica recommendation is the source's size, the user confirms or overrides. Non-replica alternatives may use the delivery-purpose baseline. This is what prevents the deck from exporting at an unintentionally small size while still honoring the source. ```bash python3 ${SKILL_DIR}/scripts/confirm_ui/server.py --daemon --wait diff --git a/plugins/codex/plugins/ui-ux-pro-max/README.md b/plugins/codex/plugins/ui-ux-pro-max/README.md index 71bb41ec6..6a1f1adde 100755 --- a/plugins/codex/plugins/ui-ux-pro-max/README.md +++ b/plugins/codex/plugins/ui-ux-pro-max/README.md @@ -154,7 +154,7 @@ Each rule includes: - **161 Color Palettes** - Industry-specific palettes aligned 1:1 with the 161 product types - **57 Font Pairings** - Curated typography combinations with Google Fonts imports - **25 Chart Types** - Recommendations for dashboards and analytics -- **15 Tech Stacks** - React, Next.js, Astro, Vue, Nuxt.js, Nuxt UI, Svelte, SwiftUI, React Native, Flutter, HTML+Tailwind, shadcn/ui, Jetpack Compose, Angular, Laravel +- **16 Tech Stacks** - React, Next.js, Astro, Vue, Nuxt.js, Nuxt UI, Svelte, SwiftUI, React Native, Flutter, HTML+Tailwind, shadcn/ui, Jetpack Compose, Angular, Laravel, JavaFX - **99 UX Guidelines** - Best practices, anti-patterns, and accessibility rules - **161 Reasoning Rules** - Industry-specific design system generation (NEW in v2.0) @@ -387,6 +387,7 @@ The skill provides stack-specific guidelines for: | **Angular** | Angular | | **PHP** | Laravel (Blade, Livewire, Inertia.js) | | **Other Web** | Svelte, Astro | +| **Desktop** | JavaFX | | **iOS** | SwiftUI | | **Android** | Jetpack Compose | | **Cross-Platform** | React Native, Flutter | @@ -414,6 +415,9 @@ python3 .claude/skills/ui-ux-pro-max/scripts/search.py "dashboard" --domain char # Stack-specific guidelines python3 .claude/skills/ui-ux-pro-max/scripts/search.py "form validation" --stack react python3 .claude/skills/ui-ux-pro-max/scripts/search.py "responsive layout" --stack html-tailwind +python3 .claude/skills/ui-ux-pro-max/scripts/search.py "tableview binding" --stack javafx +python3 .claude/skills/ui-ux-pro-max/scripts/search.py "atlantafx primer enterprise theme" --stack javafx +python3 .claude/skills/ui-ux-pro-max/scripts/search.py "enterprise tableview density permission" --stack javafx ``` ### Persist Design System (Master + Overrides Pattern) @@ -504,6 +508,24 @@ gh pr create See [CLAUDE.md](CLAUDE.md) for detailed development guidelines. + +## Automated Releases + +This repository uses semantic-release with Conventional Commits to create GitHub releases automatically: + +- `dev` branch creates beta GitHub prereleases such as `2.6.0-beta.1`. +- `main` branch creates official stable GitHub releases such as `2.6.0`. + +Release notes and `CHANGELOG.md` are generated from Conventional Commit messages. Version numbers are synchronized across `skill.json`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, `cli/package.json`, and `cli/package-lock.json` during release preparation. + +Use these commit types for correct version bumps: + +- `fix:` -> patch release +- `feat:` -> minor release +- `feat!:` or `BREAKING CHANGE:` -> major release + +The release workflow only needs the default `GITHUB_TOKEN`; it does not publish to npm. + ## Troubleshooting ### `uipro: unknown command 'uninstall'` or `unknown command 'update'` diff --git a/plugins/codex/plugins/ui-ux-pro-max/THIRD_PARTY_SOURCE.json b/plugins/codex/plugins/ui-ux-pro-max/THIRD_PARTY_SOURCE.json index 077bc9916..2fc6c05d5 100644 --- a/plugins/codex/plugins/ui-ux-pro-max/THIRD_PARTY_SOURCE.json +++ b/plugins/codex/plugins/ui-ux-pro-max/THIRD_PARTY_SOURCE.json @@ -2,8 +2,8 @@ "sourceId": "ui-ux-pro-max", "repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git", "ref": "main", - "commit": "10d6ca310541d3ffeee6dceda0a29e373796f321", + "commit": "9a863a527534eeff97cad45430b8e5678ab45c40", "adapter": "claude-skill", "sourcePath": ".claude/skills/ui-ux-pro-max", - "syncedAt": "2026-06-21T16:00:01Z" + "syncedAt": "2026-06-22T16:00:01Z" } diff --git a/plugins/codex/plugins/ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/javafx.csv b/plugins/codex/plugins/ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/javafx.csv new file mode 100644 index 000000000..6ee5d6670 --- /dev/null +++ b/plugins/codex/plugins/ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/javafx.csv @@ -0,0 +1,76 @@ +No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL +1,Application,Start UI from Application subclass,JavaFX apps should bootstrap the primary Stage through Application.start(),Extend Application and configure Scene in start(),Create UI from a random main method without launching JavaFX,"public class App extends Application { public void start(Stage stage) { stage.setScene(new Scene(root)); stage.show(); } }",new Stage().show(),High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/application/Application.html +2,Threading,Keep work off the FX Application Thread,Long-running work blocks rendering and input when executed on the UI thread,Use Task or Service for background work,Run network database or file work in button handlers,"Task> task = new Task<>() { protected List call() { return repo.load(); } }; new Thread(task).start();",loadLargeFile(); table.setItems(items);,High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/concurrent/Task.html +3,Threading,Update UI only on FX thread,Scene graph changes must happen on the JavaFX Application Thread,Use bindings task handlers or Platform.runLater for UI changes,Mutate controls directly from background threads,"task.setOnSucceeded(e -> table.setItems(FXCollections.observableArrayList(task.getValue())));",new Thread(() -> label.setText("Done")).start(),High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/application/Platform.html +4,Threading,Bind progress to background tasks,Task exposes progress and message properties for responsive feedback,Bind ProgressBar and Label to task properties,Poll progress manually or leave users without feedback,"progress.progressProperty().bind(task.progressProperty()); status.textProperty().bind(task.messageProperty());",while(running) progress.setProgress(x);,Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/concurrent/Task.html +5,FXML,Use FXML for stable declarative layouts,FXML keeps view structure readable for screens with many controls,Place layout in FXML and behavior in controller,Build large screens entirely in one Java method,"",VBox root = new VBox(); root.getChildren().add(... 200 lines ...);,Medium,https://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXMLLoader.html +6,FXML,Keep controllers focused on view behavior,Controllers should coordinate controls and delegate business logic to services,Inject services or call application services from controller,Put database queries and domain rules directly in controller,"public void save() { customerService.save(form.toCommand()); }",public void save() { DriverManager.getConnection(...); },High,https://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXML.html +7,FXML,Use fx:id for injected controls,FXML controls need stable fx:id values that match controller fields,Annotate fields with @FXML and keep ids descriptive,Look up controls by CSS selector for normal wiring,"@FXML private TableView customerTable;",root.lookup("#customerTable"),Medium,https://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXML.html +8,FXML,Fail fast when loading FXML,FXML load errors should surface during screen creation with clear context,Load resources with getResource and handle IOException explicitly,Swallow loader errors and show a blank scene,"URL view = getClass().getResource(\"/views/main.fxml\"); Parent root = FXMLLoader.load(view);",try { FXMLLoader.load(url); } catch(Exception ignored) {},High,https://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXMLLoader.html +9,CSS,Style with style classes,JavaFX CSS works best through reusable styleClass names,Add semantic style classes and define them in CSS,Set long inline style strings throughout code,"button.getStyleClass().add(\"primary-action\");",".setStyle(\"-fx-background-color: #2563eb; -fx-padding: 12; ...\")",Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/doc-files/cssref.html +10,CSS,Use design tokens through looked-up colors,Looked-up colors keep palettes consistent across controls,Define named colors on root and reuse them in CSS,Repeat hex values in every selector,".root { -brand-primary: #2563eb; } .button.primary { -fx-background-color: -brand-primary; }",".save { -fx-background-color: #2563eb; } .link { -fx-text-fill: #2563eb; }",Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/doc-files/cssref.html +11,CSS,Avoid overusing inline effects,Expensive CSS effects and shadows can hurt desktop UI responsiveness,Use subtle shadows only on important elevated surfaces,Apply blur drop shadow and glow to every node,".dialog-card { -fx-effect: dropshadow(gaussian, rgba(0,0,0,.18), 16, 0, 0, 4); }",".table-row-cell { -fx-effect: dropshadow(gaussian, black, 20, .5, 0, 0); }",Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/effect/package-summary.html +12,Layout,Choose layout panes by responsibility,Each pane solves a different layout problem and should be selected intentionally,Use BorderPane for app shell GridPane for forms VBox/HBox for simple stacks,Use absolute positioning for resizable app screens,"BorderPane shell = new BorderPane(); shell.setTop(toolbar); shell.setCenter(content);",Pane root = new Pane(); button.setLayoutX(742);,High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/layout/package-summary.html +13,Layout,Prefer constraints over fixed coordinates,Responsive JavaFX layouts depend on constraints and grow priorities,Use hgrow vgrow column constraints and alignment,Hard-code pixel positions and sizes,"GridPane.setHgrow(nameField, Priority.ALWAYS); column.setPercentWidth(50);",field.setPrefWidth(328); field.setLayoutX(120);,High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/layout/GridPane.html +14,Layout,Set sensible min pref and max sizes,Controls should resize predictably across windows and DPI settings,Use Region.USE_COMPUTED_SIZE and max widths intentionally,Lock every control to fixed width and height,"button.setMaxWidth(Double.MAX_VALUE); VBox.setVgrow(table, Priority.ALWAYS);","button.setMinSize(96, 32); button.setMaxSize(96, 32);",Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/layout/Region.html +15,Layout,Use spacing and padding consistently,Desktop UI needs scan-friendly rhythm and clear grouping,Set spacing padding and Insets through shared constants or CSS,Use inconsistent ad hoc gaps between controls,"form.setHgap(12); form.setVgap(10); form.setPadding(new Insets(16));",box.setSpacing(3); other.setSpacing(17);,Low,https://openjfx.io/javadoc/21/javafx.graphics/javafx/geometry/Insets.html +16,Controls,Use ObservableList for list controls,TableView ListView and ComboBox update automatically from observable collections,Back controls with FXCollections.observableArrayList(),Mutate plain lists and manually refresh controls,"ObservableList rows = FXCollections.observableArrayList(); table.setItems(rows);",List rows = new ArrayList<>(); table.setItems((ObservableList) rows);,High,https://openjfx.io/javadoc/21/javafx.base/javafx/collections/ObservableList.html +17,Controls,Configure TableView cell value factories with properties,Table columns should observe stable JavaFX properties for updates,Expose StringProperty ObjectProperty or use ReadOnlyObjectWrapper,Return transient strings without observable support,"nameCol.setCellValueFactory(data -> data.getValue().nameProperty());",nameCol.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().toString()));,Medium,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/TableColumn.html +18,Controls,Use cell factories for custom rendering,Custom table or list visuals belong in reusable cell factories,Override updateItem and handle empty state,Place complex Nodes directly in model objects,"col.setCellFactory(c -> new TableCell<>() { protected void updateItem(Status s, boolean empty) { super.updateItem(s, empty); setText(empty ? null : s.label()); } });",row.setBadge(new Label("Active"));,Medium,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/Cell.html +19,Controls,Virtualized controls are for large data,TableView ListView TreeView virtualize cells and outperform manual node lists,Use TableView or ListView for hundreds of rows,Create hundreds of HBoxes inside a VBox,"ListView list = new ListView<>(items);",items.forEach(i -> vbox.getChildren().add(new ItemRow(i)));,High,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/ListView.html +20,Controls,Handle empty states explicitly,Empty tables and lists need visible guidance or next actions,Set placeholder nodes for empty data views,Leave blank white areas that look broken,"table.setPlaceholder(new Label(\"No customers match this filter\"));",table.setPlaceholder(null);,Low,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/TableView.html +21,Binding,Use property binding for derived UI state,JavaFX binding reduces imperative synchronization bugs,Bind disabled visible text and progress properties to source state,Manually update every dependent control in each event handler,"saveButton.disableProperty().bind(form.validProperty().not());",if(!valid) saveButton.setDisable(true);,High,https://openjfx.io/javadoc/21/javafx.base/javafx/beans/binding/Bindings.html +22,Binding,Unbind before manual updates,Bound properties cannot be set directly without errors,Call unbind when switching from bound to manual state,Set a bound property directly,"label.textProperty().unbind(); label.setText(\"Ready\");",label.textProperty().bind(task.messageProperty()); label.setText(\"Ready\");,Medium,https://openjfx.io/javadoc/21/javafx.base/javafx/beans/property/Property.html +23,Binding,Use listeners sparingly,Bindings express simple relationships more clearly than listeners,Use listeners for side effects and bindings for values,Create listener chains for simple computed text,"totalLabel.textProperty().bind(Bindings.format(""Total: %d"", total));","count.addListener((o, a, b) -> totalLabel.setText(""Total: "" + b));",Low,https://openjfx.io/javadoc/21/javafx.base/javafx/beans/value/ObservableValue.html +24,Events,Use action handlers for commands,Buttons and menu items should route to named command methods,Use setOnAction or @FXML handler methods with clear names,Put large lambdas inline for complex operations,"@FXML private void handleSave(ActionEvent event) { saveCustomer(); }",saveButton.setOnAction(e -> { validate(); transform(); query(); save(); refresh(); });,Medium,https://openjfx.io/javadoc/21/javafx.base/javafx/event/ActionEvent.html +25,Events,Use event filters for global shortcuts,Filters can intercept keyboard events before child controls consume them,Register accelerators or filters at Scene level,Add duplicate key handlers to every control,"scene.getAccelerators().put(new KeyCodeCombination(KeyCode.S, SHORTCUT_DOWN), this::save);",nameField.setOnKeyPressed(...); table.setOnKeyPressed(...);,Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/Scene.html +26,Accessibility,Connect labels to inputs,Accessible desktop forms need labels associated with controls,Use Label.setLabelFor and clear prompt text,Use placeholder-only labels,"nameLabel.setLabelFor(nameField); nameField.setPromptText(\"Jane Doe\");",nameField.setPromptText(\"Name\");,High,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/Label.html +27,Accessibility,Expose accessible text for icon buttons,Icon-only controls need names for screen readers and tooltips,Set accessibleText and Tooltip on icon buttons,Use unlabeled graphic-only buttons,"button.setAccessibleText(""Refresh""); button.setTooltip(new Tooltip(""Refresh""));","new Button("""", refreshIcon)",High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/AccessibleRole.html +28,Accessibility,Keep keyboard focus visible,Desktop users rely on focus traversal and visible focus indicators,Preserve focus rings and tab order,Remove outlines without alternative focus state,".button:focused { -fx-border-color: -brand-focus; -fx-border-width: 2; }",".button:focused { -fx-background-insets: 0; }",High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/Node.html +29,Accessibility,Use mnemonics for menu and form workflows,Mnemonics make desktop workflows faster and more accessible,Enable mnemonicParsing and choose unique mnemonic letters,Ignore keyboard alternatives for frequent actions,"saveButton.setMnemonicParsing(true); saveButton.setText(""_Save"");","saveButton.setText(""Save"");",Low,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/Labeled.html +30,Validation,Show validation near the field,Users should not hunt for form errors in desktop dialogs,Bind error labels or pseudo classes next to invalid controls,Show only a generic alert after submit,"field.pseudoClassStateChanged(PseudoClass.getPseudoClass(""invalid""), !valid);","new Alert(ERROR, ""Invalid input"").show();",Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/css/PseudoClass.html +31,Validation,Use TextFormatter for constrained input,TextFormatter prevents invalid edits before they enter the model,Attach TextFormatter for numeric dates and masks,Parse and reject invalid text only after submit,"amountField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), 0, c -> c.getControlNewText().matches(""\\d*"") ? c : null));",Integer.parseInt(amountField.getText());,Medium,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/TextFormatter.html +32,Dialogs,Use modal ownership for dialogs,Dialogs should block only the relevant window and return structured results,Set owner modality and use showAndWait,Open unmanaged windows for confirmations,"dialog.initOwner(stage); dialog.initModality(Modality.WINDOW_MODAL); Optional result = dialog.showAndWait();",new Stage().show();,Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/stage/Modality.html +33,Dialogs,Prefer custom DialogPane over ad hoc stages,Dialog gives consistent buttons focus and result handling,Use Dialog for forms confirmations and wizards,Build every modal as a new Stage manually,"Dialog dialog = new Dialog<>(); dialog.getDialogPane().getButtonTypes().addAll(OK, CANCEL);",Stage modal = new Stage(); modal.setScene(new Scene(new VBox()));,Low,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/Dialog.html +34,Images,Load images as resources,Packaged apps need resources resolved from the classpath or module path,Use getResourceAsStream for bundled assets,Use absolute local file paths in production UI,"new Image(getClass().getResourceAsStream(""/images/logo.png""));","new Image(""file:/Users/me/Desktop/logo.png"")",High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/image/Image.html +35,Images,Use background loading for large images,Large image decoding can pause UI startup,Use Image(url true) or a background Task for heavy assets,Load many full-size images synchronously during startup,"Image preview = new Image(url, true);",gallery.add(new Image(url));,Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/image/Image.html +36,Animation,Keep animations purposeful and short,Desktop UI animations should clarify state changes without delaying work,Use 150-250ms transitions for reveal hover and selection,Animate every layout change with long timelines,"FadeTransition ft = new FadeTransition(Duration.millis(180), pane); ft.setToValue(1);","new Timeline(new KeyFrame(Duration.seconds(2), ...)).play();",Low,https://openjfx.io/javadoc/21/javafx.graphics/javafx/animation/package-summary.html +37,Animation,Respect reduced-motion contexts where possible,Some users experience motion sensitivity in desktop apps,Provide a setting to disable decorative animations,Make animation required for comprehension,"if (settings.reducedMotion()) pane.setOpacity(1); else fade.play();",alwaysSpin.play();,Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/animation/Animation.html +38,Performance,Avoid recreating scenes for small state changes,Replacing whole scenes loses state and can flicker,Swap center content or update view models,Rebuild the entire Stage for every navigation click,"shell.setCenter(customerView);",stage.setScene(new Scene(loadMainAgain()));,Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/Scene.html +39,Performance,Reuse loaded views when appropriate,FXML loading and CSS application are not free,Cache stable views or controllers for frequent navigation,Reload heavyweight screens repeatedly without need,"Node settings = viewCache.computeIfAbsent(""settings"", this::loadSettings);","button.setOnAction(e -> shell.setCenter(loadFxml(""settings.fxml"")));",Low,https://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXMLLoader.html +40,Performance,Batch observable list changes,Many single-item updates can cause repeated layout and sort work,Use setAll or addAll for bulk replacement,Loop add items one by one to visible lists,"items.setAll(repository.findAll());",for(Item item : loaded) items.add(item);,Medium,https://openjfx.io/javadoc/21/javafx.base/javafx/collections/ObservableList.html +41,Architecture,Use view models for complex screens,View models keep controller state testable and separate from controls,Expose JavaFX properties from a screen model,Store all state only inside controls,"customerNameField.textProperty().bindBidirectional(viewModel.nameProperty());",String name = customerNameField.getText(); // everywhere,Medium,https://openjfx.io/javadoc/21/javafx.base/javafx/beans/property/package-summary.html +42,Architecture,Separate navigation from feature controllers,Feature controllers should not know how every screen is launched,Use a navigator or application shell service,Call FXMLLoader for unrelated screens from each controller,"navigator.showCustomers();",FXMLLoader.load(getClass().getResource(\"/views/admin.fxml\"));,Medium,https://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXMLLoader.html +43,Modules,Declare required JavaFX modules,Modular JavaFX apps must require the modules they use,Add javafx.controls javafx.fxml and opens controller packages,Depend on classpath accidents only,"module app { requires javafx.controls; requires javafx.fxml; opens app.ui to javafx.fxml; }",module app { requires javafx.controls; },High,https://openjfx.io/openjfx-docs/#modular +44,Packaging,Use jlink or jpackage for desktop delivery,JavaFX apps should ship with the runtime they need,Package a runtime image or native installer,Ask end users to install matching Java and JavaFX manually,"jpackage --name MyApp --module app/app.Main --runtime-image build/image",java -jar app.jar,Medium,https://openjfx.io/openjfx-docs/#modular +45,Testing,Use TestFX for interaction tests,UI flows need automated coverage beyond controller unit tests,Write TestFX tests for key forms dialogs and navigation,Only manually click through releases,"clickOn(""#nameField"").write(""Alice""); clickOn(""Save""); verifyThat(""Saved"", isVisible());",// manual QA only,Medium,https://github.com/TestFX/TestFX +46,Theme,Use AtlantaFX as the enterprise theme baseline,AtlantaFX provides modern JavaFX themes while preserving standard controls,Use AtlantaFX user-agent stylesheet plus a small app CSS layer,Rewrite every standard control style from scratch,"Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet());",scene.getStylesheets().add("/css/huge-custom-theme.css");,High,https://mkpaz.github.io/atlantafx/ +47,Theme,Prefer Primer for enterprise applications,PrimerLight and PrimerDark are neutral enough for dense business workflows,Use PrimerLight as default and PrimerDark for dark mode,Use Dracula or Cupertino as the default enterprise theme,"Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet());",Application.setUserAgentStylesheet(new Dracula().getUserAgentStylesheet());,Medium,https://mkpaz.github.io/atlantafx/themes/ +48,Theme,Layer brand CSS after AtlantaFX,Application CSS should customize brand tokens and business states after the base theme,Add app.css to the Scene after setting AtlantaFX,Edit AtlantaFX source CSS directly,"scene.getStylesheets().add(getClass().getResource(""/css/app.css"").toExternalForm());",modify atlantafx-base CSS files,High,https://mkpaz.github.io/atlantafx/theming/ +49,Theme,Use looked-up colors as enterprise tokens,JavaFX looked-up colors keep brand and semantic colors reusable across controls,Define app-primary app-success app-warning app-danger on root,Repeat hex values in every selector,".root { -app-primary: #2563eb; -app-danger: #dc2626; }",".save { -fx-background-color: #2563eb; } .link { -fx-text-fill: #2563eb; }",High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/doc-files/cssref.html +50,Theme,Keep theme switching centralized,Dark mode switching should not be scattered across controllers,Use a ThemeService that sets user-agent stylesheet and app CSS variants,Let each controller decide its own theme,themeService.apply(ThemeMode.DARK);,if(dark) button.setStyle(...);,Medium,https://mkpaz.github.io/atlantafx/ +51,Theme,Validate contrast for business status colors,Enterprise screens use status colors heavily and need readable contrast,Check text on success warning danger and selected row backgrounds,Assume brand colors are accessible,".status-danger { -fx-text-fill: -app-danger; }",red text on dark red background,High,https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html +52,Theme,Use AtlantaFX style classes before custom CSS,AtlantaFX exposes utility styles that reduce custom CSS drift,Prefer Styles constants or documented style classes,Create one-off class names for every button variant,saveButton.getStyleClass().add(Styles.ACCENT);,"saveButton.getStyleClass().add(""blue-button-42"");",Medium,https://mkpaz.github.io/atlantafx/ +53,Theme,Treat AtlantaFX as a base not the whole design system,AtlantaFX modernizes controls but enterprise UX still needs layout density and workflow rules,Define app shell navigation table density form and validation conventions,Assume theme choice alone solves enterprise usability,"root.getStyleClass().add(""enterprise-shell"");",only set PrimerLight and stop,High,https://mkpaz.github.io/atlantafx/ +54,Icons,Use Ikonli for consistent enterprise icons,Icon fonts integrate cleanly with JavaFX controls and avoid emoji-style UI,Use FontIcon with semantic style classes,Use emoji as toolbar or menu icons,"Button refresh = new Button(""Refresh"", new FontIcon(""mdi2r-refresh""));",new Button("Refresh"),Medium,https://kordamp.org/ikonli/ +55,Components,Use AtlantaFX controls for common app affordances,AtlantaFX provides useful controls such as Card Message ModalPane Popover and ToggleSwitch,Use built-in AtlantaFX controls before adding another dependency,Add ControlsFX for components AtlantaFX already covers,"Message message = new Message(""Saved"", ""Customer updated successfully"");","new Label(""Saved"") with ad hoc styling",Medium,https://mkpaz.github.io/atlantafx/ +56,Components,Add ControlsFX only for missing enterprise controls,ControlsFX is useful for specialized controls but should stay optional,Use ControlsFX for SpreadsheetView PropertySheet CheckComboBox or StatusBar needs,Add ControlsFX by default before requirements are clear,PropertySheet sheet = new PropertySheet(items);,"implementation ""org.controlsfx:controlsfx"" with no usage",Low,https://controlsfx.github.io/ +57,Testing,Test theme-critical flows with TestFX,Theme and CSS changes can break focus visibility dialogs and button affordance,Use TestFX for login save validation and modal workflows,Only inspect AtlantaFX screens manually,"clickOn(""#saveButton""); verifyThat("".message"", isVisible());",manual theme QA only,Medium,https://github.com/TestFX/TestFX +58,Architecture,Use application shell plus feature workspaces,Enterprise JavaFX apps need stable navigation around changing work areas,Use BorderPane shell with navigation toolbar and central workspace,Replace the whole Stage for every feature,"shell.setLeft(navigation); shell.setTop(toolbar); shell.setCenter(workspace);",stage.setScene(new Scene(loadFeature()));,High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/layout/BorderPane.html +59,Architecture,Use MVVM for complex enterprise screens,Large forms and tables need testable state outside the controller,Expose JavaFX properties from view models and bind controls to them,Put all screen state and validation in the controller,amountField.textProperty().bindBidirectional(vm.amountProperty());,controller.amount = amountField.getText();,High,https://openjfx.io/javadoc/21/javafx.base/javafx/beans/property/package-summary.html +60,Architecture,Inject services into controllers,Enterprise controllers should coordinate UI and call application services,Use a controller factory or DI container for services,Create database connections inside FXML controllers,loader.setControllerFactory(type -> injector.getInstance(type));,new CustomerRepository(new DriverManager(...)),High,https://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXMLLoader.html +61,Navigation,Use role-aware navigation models,Menus toolbars and shortcuts should reflect the same permission model,Build navigation items from commands with required roles,Hide buttons in one place and leave shortcuts enabled,"command.enabledProperty().bind(permissionService.allowed(""invoice.approve""));",approveButton.setVisible(false);,High, +62,Workflow,Represent workflow states visibly,Approval and processing screens need clear business state signals,Use semantic badges row styles and disabled actions by workflow state,Use only free text status columns,"row.pseudoClassStateChanged(PseudoClass.getPseudoClass(""blocked""), item.isBlocked());","statusCol.setText(""B"");",Medium,https://openjfx.io/javadoc/21/javafx.graphics/javafx/css/PseudoClass.html +63,TableView,Design TableView for high-density enterprise data,Enterprise users scan compare sort filter and act on rows for long periods,Use compact row height clear columns sorting filtering and selection summary,Use card grids for large tabular datasets,"table.getStyleClass().add(""dense-table""); table.getSortOrder().setAll(updatedAtCol);",new TilePane(customerCards),High,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/TableView.html +64,TableView,Keep row actions predictable,Inline actions in dense tables should be limited and permission-aware,Use context menus or a side detail panel for secondary actions,Place many buttons in every row,"table.setRowFactory(tv -> { TableRow row = new TableRow<>(); row.setContextMenu(orderMenu); return row; });",row contains Edit Delete Approve Print Email buttons,Medium,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/ContextMenu.html +65,TableView,Use server-side paging for large enterprise datasets,Desktop clients should not load entire enterprise tables into memory,Fetch pages or filtered slices from services,Load all records and filter in the UI,"Page page = customerService.search(criteria, pageRequest);",customerRepository.findAll(),High, +66,Forms,Use form sections for enterprise data entry,Long enterprise forms need grouping and progressive disclosure,Group fields into titled sections with validation summaries,Place dozens of inputs in one unbroken GridPane,"TitledPane billing = new TitledPane(""Billing"", billingForm);",new GridPane with 80 controls,Medium,https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/TitledPane.html +67,Forms,Provide validation summary plus field errors,Enterprise forms often need multiple corrections before submission,Show a summary at top and field-level messages near controls,Show only one modal alert after Save,"summary.setItems(vm.validationErrors()); field.pseudoClassStateChanged(INVALID, fieldError);","new Alert(ERROR, ""Invalid form"").showAndWait();",High, +68,Tasks,Make long operations cancellable,Enterprise imports exports sync and reports need cancel paths,Expose cancel button bound to Task running state,Force users to wait or kill the app,cancelButton.setOnAction(e -> task.cancel());,runReportButton.setDisable(true);,High,https://openjfx.io/javadoc/21/javafx.graphics/javafx/concurrent/Task.html +69,Tasks,Surface retryable errors without losing context,Network and service failures should preserve user input and next action,Show inline retry messages and keep form/table state,Clear the screen on service failure,"message.setDescription(""Could not save. Check connection and retry."");",loadErrorScene();,High, +70,Audit,Log business actions through services,Enterprise desktop apps need traceability for sensitive changes,Record user action entity result and timestamp in service layer,Log only UI button clicks,"audit.log(user, ""invoice.approve"", invoiceId, SUCCESS);","System.out.println(""clicked approve"");",Medium, +71,Configuration,Separate user preferences from application config,Enterprise apps need deploy-time config and per-user preferences,Use config files for endpoints and Preferences for UI choices,Hard-code environment URLs and window state,"Preferences.userNodeForPackage(App.class).put(""theme"", ""dark"");","private static final String API = ""http://localhost:8080"";",Medium,https://docs.oracle.com/en/java/javase/21/docs/api/java.prefs/java/util/prefs/Preferences.html +72,Deployment,Package resources and themes inside the runtime image,AtlantaFX app CSS icons and FXML must be available after jpackage,Load resources from classpath or module resources,Load theme files from developer machine paths,"getClass().getResource(""/css/app.css"").toExternalForm();","new File(""src/main/resources/css/app.css"").toURI()",High,https://openjfx.io/openjfx-docs/#modular +73,Deployment,Write logs to user-writable locations,Installed desktop apps may not write inside the application directory,Use platform-specific user data directories for logs and cache,Write logs beside the executable,"Path logs = appData.resolve(""logs/app.log"");",Path.of("app.log"),Medium, +74,Testing,Cover enterprise happy path and failure path,Enterprise UI tests should verify save validation permission and service failure flows,Use TestFX for core workflows and service fakes,Only test controller methods without UI interaction,"clickOn(""Save""); verifyThat(""Customer saved"", isVisible());",controller.save(); assertTrue(saved);,High,https://github.com/TestFX/TestFX +75,Dependencies,Keep optional UI libraries behind actual needs,AtlantaFX should be default but additional libraries should be justified,Start with JavaFX AtlantaFX Ikonli TestFX and add ControlsFX only for missing controls,Adopt many UI libraries at project start,"dependencies { implementation(""io.github.mkpaz:atlantafx-base:2.1.0"") }",implementation controlsfx gemsfx tilesfx materialfx all at once,Medium, diff --git a/plugins/codex/plugins/ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv b/plugins/codex/plugins/ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv index 7146e8483..caf4ff6a5 100644 --- a/plugins/codex/plugins/ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +++ b/plugins/codex/plugins/ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv @@ -6,8 +6,8 @@ No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL 5,Components,Use semantic color props,Use semantic colors like primary secondary error,color="primary" color="error",Hardcoded colors,"","",Medium,https://ui.nuxt.com/docs/getting-started/theme/design-system 6,Components,Use variant prop for styling,Nuxt UI provides solid outline soft subtle ghost link variants,variant="soft" variant="outline",Custom button classes,"","",Medium,https://ui.nuxt.com/docs/components/button 7,Components,Use size prop consistently,Components support xs sm md lg xl sizes,size="sm" size="lg",Arbitrary sizing classes,"","",Low,https://ui.nuxt.com/docs/components/button -8,Icons,Use icon prop with Iconify format,Nuxt UI supports Iconify icons via icon prop,icon="lucide:home" icon="heroicons:user",i-lucide-home format,"","",Medium,https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt -9,Icons,Use leadingIcon and trailingIcon,Position icons with dedicated props for clarity,leadingIcon="lucide:plus" trailingIcon="lucide:arrow-right",Manual icon positioning,"","Add",Low,https://ui.nuxt.com/docs/components/button +8,Icons,Use i-{collection}-{name} format for icons,Nuxt UI v4 uses Iconify i-prefix format — lucide:home is v3 legacy,i-lucide-home i-heroicons-user format,lucide:home format (v3 syntax),"","",High,https://ui.nuxt.com/docs/getting-started/installation/nuxt +9,Icons,Use leadingIcon and trailingIcon props,Position icons with dedicated props for clarity,leadingIcon="i-lucide-plus" trailingIcon="i-lucide-arrow-right",Manual icon positioning or slots,"","Add",Low,https://ui.nuxt.com/docs/components/button 10,Theming,Configure colors in app.config.ts,Runtime color configuration without restart,ui.colors.primary in app.config.ts,Hardcoded colors in components,"defineAppConfig({ ui: { colors: { primary: 'blue' } } })","",High,https://ui.nuxt.com/docs/getting-started/theme/design-system 11,Theming,Use @theme directive for custom colors,Define design tokens in CSS with Tailwind @theme,@theme { --color-brand-500: #xxx },Inline color definitions,@theme { --color-brand-500: #ef4444; },:style="{ color: '#ef4444' }",Medium,https://ui.nuxt.com/docs/getting-started/theme/design-system 12,Theming,Extend semantic colors in nuxt.config,Register new colors like tertiary in theme.colors,theme.colors array in ui config,Use undefined colors,"ui: { theme: { colors: ['primary', 'tertiary'] } }"," without config",Medium,https://ui.nuxt.com/docs/getting-started/theme/design-system @@ -16,7 +16,7 @@ No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL 15,Forms,Handle form submit with @submit,UForm emits submit event with validated data,@submit handler on UForm,@click on submit button,"","",Medium,https://ui.nuxt.com/docs/components/form 16,Forms,Use validateOn prop for validation timing,Control when validation triggers (blur change input),validateOn="['blur']" for performance,Always validate on input,""," (validates on every keystroke)",Low,https://ui.nuxt.com/docs/components/form 17,Overlays,Use v-model:open for overlay control,Modal Slideover Drawer use v-model:open,v-model:open for controlled state,Manual show/hide logic,"",,Medium,https://ui.nuxt.com/docs/components/modal -18,Overlays,Use useOverlay composable for programmatic overlays,Open overlays programmatically without template refs,useOverlay().open(MyModal),Template ref and manual control,"const overlay = useOverlay(); overlay.open(MyModal, { props })","const modal = ref(); modal.value.open()",Medium,https://ui.nuxt.com/docs/components/modal +18,Overlays,Use useOverlay composable for programmatic overlays,Open overlays programmatically — v4 API is create().open() not open(Component),overlay.create(Component).open({ props }) pattern,v3 overlay.open(Component) pattern (removed in v4),"const modal = overlay.create(MyModal); const { result } = modal.open({ title: 'Confirm' })","overlay.open(MyModal, { props: { title: 'Confirm' } })",High,https://ui.nuxt.com/docs/components/modal 19,Overlays,Use title and description props,Built-in header support for overlays,title="Confirm" description="Are you sure?",Manual header content,"","",Low,https://ui.nuxt.com/docs/components/modal 20,Dashboard,Use UDashboardSidebar for navigation,Provides collapsible resizable sidebar with mobile support,UDashboardSidebar with header default footer slots,Custom sidebar implementation,,