diff --git a/config/external-sources.lock.json b/config/external-sources.lock.json index 849cb7b7..5e1e3e38 100644 --- a/config/external-sources.lock.json +++ b/config/external-sources.lock.json @@ -24,8 +24,8 @@ "repo": "https://github.com/JuliusBrussee/caveman.git", "ref": "main", "adapter": "codex-plugin", - "commit": "2fd153c67988e980fb0b2455c90832159a6a5a25", - "syncedAt": "2026-09-22T16:08:55Z" + "commit": "f5d729488caa8f6a5b6c8086fe2cccd3e8a63f91", + "syncedAt": "2026-10-01T16:00:00Z" }, { "id": "taste-skill", @@ -42,8 +42,8 @@ "repo": "https://github.com/shadcn-ui/ui.git", "ref": "main", "adapter": "claude-skill", - "commit": "db2db460a26fa84fb65c8d903b213925fbdee9ed", - "syncedAt": "2026-09-28T16:00:01Z" + "commit": "d75a96ab781f3d659be1ad287347d5887ce9f2fc", + "syncedAt": "2026-10-01T16:00:00Z" }, { "id": "frontend-slides", @@ -78,8 +78,8 @@ "repo": "https://github.com/hugohe3/ppt-master.git", "ref": "main", "adapter": "claude-skill", - "commit": "680de11f1bef4628b68d5daad9dffec569fbd51f", - "syncedAt": "2026-09-27T16:00:00Z" + "commit": "44c10ed0bc3a9e1df7a25aa179ae7c26db09469b", + "syncedAt": "2026-10-01T16:00:00Z" }, { "id": "grill-me", @@ -130,10 +130,10 @@ "id": "artemis-automation", "pluginName": "artemis-automation", "repo": "https://git.playones.com/chenmingxuan/Artemis-Eapil.git", - "ref": "v1.2.3", + "ref": "v1.3.0", "adapter": "claude-skill", - "commit": "59f458205d7f5ec041a9dc11acc1122f128e827b", - "syncedAt": "2026-09-29T16:00:00Z" + "commit": "606ed941607c9e5482a4acd3f782d2b14acc9d06", + "syncedAt": "2026-10-01T16:00:00Z" }, { "id": "embedded-app-pc-debug", @@ -168,8 +168,8 @@ "repo": "https://github.com/vercel/next.js.git", "ref": "canary", "adapter": "skill-collection", - "commit": "bc8bf60a350071dc192c454674eab3574db8b50c", - "syncedAt": "2026-09-29T16:00:00Z" + "commit": "60cb5b3efc1d65767d8f746dc12003fbd4f4bbcd", + "syncedAt": "2026-10-01T16:00:00Z" }, { "id": "shuorenhua", @@ -177,8 +177,8 @@ "repo": "https://github.com/MrGeDiao/shuorenhua.git", "ref": "main", "adapter": "claude-skill", - "commit": "9e6400d5e2cc050485c38fbf0dda682bce66c180", - "syncedAt": "2026-09-27T16:00:00Z" + "commit": "e7c2b8670bd3e1988bfec199e861cebd87fc5e9c", + "syncedAt": "2026-10-01T16:00:00Z" } ] } diff --git a/plugins/codex/plugins/artemis-automation/.codex-plugin/plugin.json b/plugins/codex/plugins/artemis-automation/.codex-plugin/plugin.json index 1e8eea2a..d4beda20 100644 --- a/plugins/codex/plugins/artemis-automation/.codex-plugin/plugin.json +++ b/plugins/codex/plugins/artemis-automation/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "artemis-automation", - "version": "1.2.3", + "version": "1.3.0", "description": "使用 ARTEMIS MCP 完成 Android 自动化测试、批量执行、失败诊断、历史检索和报告交付。", "author": { "name": "chenmingxuan", diff --git a/plugins/codex/plugins/artemis-automation/THIRD_PARTY_SOURCE.json b/plugins/codex/plugins/artemis-automation/THIRD_PARTY_SOURCE.json index 922ef57a..3eccfd2e 100644 --- a/plugins/codex/plugins/artemis-automation/THIRD_PARTY_SOURCE.json +++ b/plugins/codex/plugins/artemis-automation/THIRD_PARTY_SOURCE.json @@ -1,9 +1,9 @@ { "sourceId": "artemis-automation", "repo": "https://git.playones.com/chenmingxuan/Artemis-Eapil.git", - "ref": "v1.2.3", - "commit": "59f458205d7f5ec041a9dc11acc1122f128e827b", + "ref": "v1.3.0", + "commit": "606ed941607c9e5482a4acd3f782d2b14acc9d06", "adapter": "claude-skill", "sourcePath": "skills/artemis-automation", - "syncedAt": "2026-09-29T16:00:00Z" + "syncedAt": "2026-10-01T16:00:00Z" } diff --git a/plugins/codex/plugins/artemis-automation/skills/artemis-automation/SKILL.md b/plugins/codex/plugins/artemis-automation/skills/artemis-automation/SKILL.md index 392fcba2..3b0030d0 100644 --- a/plugins/codex/plugins/artemis-automation/skills/artemis-automation/SKILL.md +++ b/plugins/codex/plugins/artemis-automation/skills/artemis-automation/SKILL.md @@ -19,7 +19,7 @@ description: 使用 ARTEMIS MCP 在 Android 真机或模拟器上完成自然语 ## 结果、证据与交付 - 执行状态与业务判定分开:`completed` 只说明执行结束,不等于测试通过。检查工具返回的验证摘要、预期/实际结果,以及 `mobile_inspect_trace` 的步骤/截图。证据不足标记“未验证”;前置条件不满足说明阻塞原因,不能编造通过结论。 -- 用 `mobile_export_report(session_id=...)` 获取单任务报告;`formats=["screenshots"]` 获取排序截图,`["video"]` 获取保存的录屏分段,`["all"]` 获取 Word、Excel 和所有可用资料的 ZIP。批次传 `batch_id` 获取汇总和各项任务报告的 ZIP。未指定格式时使用保存的 Word/Excel 偏好。执行中的导出只是当前快照。 +- 用 `mobile_export_report(session_id=...)` 获取单任务报告;`formats=["screenshots"]` 获取排序截图,`["video"]` 获取保存的录屏分段,`["all"]` 获取 Word、Excel 和所有可用资料的 ZIP。批次传 `batch_id` 获取汇总、各项任务报告、步骤截图及已有录屏的 ZIP。未指定格式时使用保存的 Word/Excel 偏好。执行中的导出只是当前快照。 - 工具返回 MCP 主机上的绝对文件路径和下载 URL。向用户交付真实生成的文件链接,并说明完成、失败、未验证、跳过数量及重要限制。不要只说“报告已生成”而不交付文件。 - 用 `mobile_search_history` 按名称、目标、编号或标签搜索全部历史;日期先按用户时区转换为 Unix 秒,`start` 含边界、`end` 不含边界。按返回的 `total/page/size` 翻页,不能把第一页当作全部历史。 - `mobile_inspect_trace` 是只读步骤证据查看;其中历史上下文的 replay 术语不表示重新执行设备动作。 @@ -27,10 +27,10 @@ description: 使用 ARTEMIS MCP 在 Android 真机或模拟器上完成自然语 ## 配置和恢复边界 - `mobile_config` 读取非敏感设置。只有用户明确要求保存或修改默认配置时,才传 `changes` 与 `persist_defaults=true`;这些设置影响后续任务,不能为一次临时需求擅自改变以后所有任务。 -- 模型名称支持预设 `gpt-5.6-luna`、`mimo-v2.6-flash` 和自定义名称,取决于用户配置的 Responses 网关。思考强度最高支持 `max`,默认 `high`、单次请求 300 秒、失败后最多重试 5 次、固定间隔 10 秒;默认关闭视频分析与录屏。不根据模型名称猜测网关能力。 +- 模型名称支持预设 `gpt-5.6-luna`、`gpt-6-luna`、`mimo-v2.6-flash`、`mimo-v2.6-pro` 和自定义名称,取决于用户配置的 Responses 网关。思考强度最高支持 `max`,默认 `high`、单次请求 300 秒、失败后最多重试 5 次;普通错误每次等待 10 秒,429 每次等待 60 秒。首选模型连续 3 次未形成有效工具调用时,当前步骤改用 `gpt-6-luna`(high),兜底最多再尝试 3 次;下一步恢复首选模型。默认关闭视频分析与录屏。不根据模型名称猜测网关能力。 - 强度选 `default` 表示不发送该参数,和 `none`(显式要求关闭)不同。接口拒绝强度参数时明确呈现失败,先询问用户是否改用 `default`,不能静默降级或换模型。 - API Key 只通过网页或 `.env` 配置。不要请求用户把 key 发到聊天中,不读取/回显已有 key。保存后新任务生效;直接编辑环境文件后需重启相应服务,运行中的任务保留自己的配置。 -- 录屏与 AI 视频分析分别配置。批量任务的 `task_config` 可覆盖单项,不修改全局设置;只开视频分析时录制内容不作为可下载文件保存。暂停时停止录屏,恢复后按片段继续,不能把其他任务画面混入该任务资料。 +- 429 等待和兜底参数可在网页模型配置或经用户明确授权的 `mobile_config(section="model", persist_defaults=true)` 保存;当前任务不热切换。录屏与 AI 视频分析分别配置。批量任务的 `task_config` 可覆盖单项,不修改全局设置;只开视频分析时录制内容不作为可下载文件保存。暂停时停止录屏,恢复后按片段继续,不能把其他任务画面混入该任务资料。 - 请求重试是模型调用恢复,不是测试用例重跑。批次默认失败继续、不自动重跑;服务重启后保留状态并暂停,用户明确要求后才继续或复制失败项。 - 批次暂停只停止后续派发;当前任务等待指导时用 `mobile_manage_task(action="pause", trace_id=...)`,真正取消用 `stop`。用户明确要求删除批次记录时先 `mobile_batch(action="get")` 核对编号,再用 `delete`;执行中/排队中的批次不能删,删除会清除该批次的任务历史、报告和文件。服务重启后运行/暂停的任务标记中断,不自动重跑。停止可能保留已经产生的业务效果,不声称自动回滚。 diff --git a/plugins/codex/plugins/artemis-automation/skills/artemis-automation/references/batches.md b/plugins/codex/plugins/artemis-automation/skills/artemis-automation/references/batches.md index c91d4204..287cb8d1 100644 --- a/plugins/codex/plugins/artemis-automation/skills/artemis-automation/references/batches.md +++ b/plugins/codex/plugins/artemis-automation/skills/artemis-automation/references/batches.md @@ -60,6 +60,6 @@ ## 报告 -`mobile_export_report(batch_id=...)` 下载批次 ZIP,包含汇总和各项报告,格式由创建批次时的设置决定。单任务传 `session_id`(单任务 MCP 的 `trace_id`)以及可选 `formats=["docx"]`、`["screenshots"]`、`["video"]` 或 `["all"]`。默认格式通过 `mobile_config(section="reports")` 查看。录屏与 AI 视频分析独立,只有保存了录屏才可下载片段。 +`mobile_export_report(batch_id=...)` 下载批次 ZIP,包含汇总、各项报告和有序步骤截图;任务保存了录屏时,还包含对应录屏片段。报告格式由创建批次时的设置决定。单任务传 `session_id`(单任务 MCP 的 `trace_id`)以及可选 `formats=["docx"]`、`["screenshots"]`、`["video"]` 或 `["all"]`。默认格式通过 `mobile_config(section="reports")` 查看。录屏与 AI 视频分析独立,只有保存了录屏才可下载片段。 结果文件在 MCP 主机的 `artifacts/mcp_exports/`。不要把 Office 文件内容当作文本塞入上下文;先检查工具返回的结构化结论,需要进一步审阅时再用宿主具备的文档工具打开文件。失败和未验证项也必须交付,保持原始证据。 diff --git a/plugins/codex/plugins/caveman/.codex-plugin/plugin.json b/plugins/codex/plugins/caveman/.codex-plugin/plugin.json index 2f7da834..95748012 100644 --- a/plugins/codex/plugins/caveman/.codex-plugin/plugin.json +++ b/plugins/codex/plugins/caveman/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "caveman", - "version": "2.7.0", + "version": "3.0.0", "description": "Caveman 是面向 Codex 的精简表达、代码工作流和上下文压缩 skill 集合。", "author": { "name": "Julius Brussee", @@ -8,7 +8,7 @@ }, "homepage": "https://github.com/JuliusBrussee/caveman", "repository": "https://github.com/JuliusBrussee/caveman", - "license": "MIT", + "license": "Apache-2.0", "keywords": [ "productivity", "communication", diff --git a/plugins/codex/plugins/caveman/THIRD_PARTY_SOURCE.json b/plugins/codex/plugins/caveman/THIRD_PARTY_SOURCE.json index 6d65ee57..0621263b 100644 --- a/plugins/codex/plugins/caveman/THIRD_PARTY_SOURCE.json +++ b/plugins/codex/plugins/caveman/THIRD_PARTY_SOURCE.json @@ -2,8 +2,8 @@ "sourceId": "caveman", "repo": "https://github.com/JuliusBrussee/caveman.git", "ref": "main", - "commit": "2fd153c67988e980fb0b2455c90832159a6a5a25", + "commit": "f5d729488caa8f6a5b6c8086fe2cccd3e8a63f91", "adapter": "codex-plugin", "sourcePath": "plugins/caveman", - "syncedAt": "2026-09-22T16:08:55Z" + "syncedAt": "2026-10-01T16:00:00Z" } diff --git a/plugins/codex/plugins/mcp-playwright/MCP_SOURCE.json b/plugins/codex/plugins/mcp-playwright/MCP_SOURCE.json index ea0e9891..7708eba4 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-09-29T16:02:00Z" + "syncedAt": "2026-10-01T16:02:08Z" } diff --git a/plugins/codex/plugins/next-skills/THIRD_PARTY_SOURCE.json b/plugins/codex/plugins/next-skills/THIRD_PARTY_SOURCE.json index 132e720a..5a013048 100644 --- a/plugins/codex/plugins/next-skills/THIRD_PARTY_SOURCE.json +++ b/plugins/codex/plugins/next-skills/THIRD_PARTY_SOURCE.json @@ -2,8 +2,8 @@ "sourceId": "next-skills", "repo": "https://github.com/vercel/next.js.git", "ref": "canary", - "commit": "bc8bf60a350071dc192c454674eab3574db8b50c", + "commit": "60cb5b3efc1d65767d8f746dc12003fbd4f4bbcd", "adapter": "skill-collection", "sourcePath": "skills", - "syncedAt": "2026-09-29T16:00:00Z" + "syncedAt": "2026-10-01T16:00:00Z" } diff --git a/plugins/codex/plugins/ppt-master/THIRD_PARTY_SOURCE.json b/plugins/codex/plugins/ppt-master/THIRD_PARTY_SOURCE.json index 001bf4c4..3977a1c7 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": "680de11f1bef4628b68d5daad9dffec569fbd51f", + "commit": "44c10ed0bc3a9e1df7a25aa179ae7c26db09469b", "adapter": "claude-skill", "sourcePath": "skills/ppt-master", - "syncedAt": "2026-09-27T16:00:00Z" + "syncedAt": "2026-10-01T16:00:00Z" } diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/artifact-ownership.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/artifact-ownership.md index 9ff2d2e3..4d5a7ff6 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/artifact-ownership.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/artifact-ownership.md @@ -71,7 +71,7 @@ Global artifact ownership rules for PPT Master projects. A selected route or pro | SVG source and closure | `svg_output/` is the only author source; every visible exported object exists in its page SVG or an explicitly referenced asset. The only supported export route reads `svg_output/` through the project converter; diagnostic `-s final` creates no release route; PowerPoint's Convert-to-Shape on `svg_final/` is outside the contract | | Package-behavior separation | Speaker notes, animations, transitions, narration, and direct native-PPTX workflows keep their owning artifacts, never forced into SVG metadata | | Workflow audit log | Records command envelopes, error/failure and receipt/report lines, bounded samples, summary context, omission counts, and selected manual entries; it does not capture direct file authoring, host-native tools, pre-project conversion, binary writes, hidden child output, or detached services — absence of a line proves nothing. Automatic recording failure is advisory; a failed explicit manual append reports failure | -| Confirmation and template selection | Final UI/chat confirmation overrides recommendations and is consumed once; enabled refinement requires approval before lock authoring. Step 3 prepares candidates without reads; `template_options.default_mode` is `free_design` for ordinary requests and `templates` for explicit intent or any exact root; exactly one root may be preselected; template mode requires one selected candidate; every applied selection becomes project-local `templates/` state before Stage 2. Quick applies at most one exact root per kind directly | +| Confirmation and template selection | Final UI/chat confirmation overrides recommendations and is consumed once; enabled refinement requires approval before lock authoring. Step 3 prepares candidates, reading only an exact root's kind and canvas frontmatter; `template_options.default_mode` is `free_design` for ordinary requests and `templates` for explicit intent or any exact root; exactly one root may be preselected; template mode requires one selected candidate; every applied selection becomes project-local `templates/` state before Stage 2. Quick applies at most one exact root per kind directly | | Proactive production outcomes | Resolve notes/animation/narration as explicit instruction → final Stage 2 → defaults; persist outcomes/provenance only in Design Spec §I. Explicit notes-off/audio-on applies Generate's dependency gate; a Stage-2 animation `false` does not suppress an existing sidecar | **Forbidden — mixed ownership**: copying chart values from Markdown into `analysis/` by hand, editing `svg_final/` as the source of a fix, editing imported lossless SVGs instead of their authoring IR, treating `design_spec.md` prose as a replacement for `spec_lock.md`. diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/confirm-surface.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/confirm-surface.md index 687efdb7..fbc54d59 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/references/confirm-surface.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/references/confirm-surface.md @@ -22,7 +22,7 @@ Interpret the instruction semantically — "confirm here", "use the chat window" ## 2. `recommendations.stage1.json` -Author before reading any candidate index, spec, prototype, asset, or template canvas. All seven prose values may be blank; `primary_language` is canonical BCP-47 (`und` and Chinese without script/region are rejected); `lang` is the UI language only. The intent paths (inform / explain / persuade / decide / align / teach / report and account / mobilize / record and hand off) are help text, never a `primary_job` field. +Author before reading any candidate index, spec, prototype, or asset; template facts already in context inform only the canvas and page range, per [`generate-pptx.md`](../workflows/generate-pptx.md) Step 4. All seven prose values may be blank; `primary_language` is canonical BCP-47 (`und` and Chinese without script/region are rejected); `lang` is the UI language only. The intent paths (inform / explain / persuade / decide / align / teach / report and account / mobilize / record and hand off) are help text, never a `primary_job` field. ```json { 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 1340ceb6..0d99a806 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 @@ -68,7 +68,7 @@ Missing material returns upstream, never invented or substituted. ### a. Canvas Format Confirmation -Recommend from the scenario and project initialization ([`canvas-formats.md`](canvas-formats.md)). A template canvas is not Stage-1 evidence; Stage 2 later checks whether selected structure serves the confirmed canvas. +Recommend from the scenario and project initialization ([`canvas-formats.md`](canvas-formats.md)); a template canvas informs it only as [`generate-pptx.md`](../workflows/generate-pptx.md) Step 4 allows. Stage 2 later checks whether selected structure serves the confirmed canvas. ### b. Page Count Confirmation 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 0e619229..4da83bbc 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 @@ -791,6 +791,15 @@ return dict[key] != null ? dict[key] : key; } + // zh / zh-TW / ja text takes a full-width colon and no space between sentences. + function isCjkUi() { + return LANG === "zh" || LANG === "zh-TW" || LANG === "ja"; + } + + function labelSep() { + return isCjkUi() ? ":" : ": "; + } + // Preserve the existing locale order, then accept zh_tw-only candidate // prose from any persisted UI language so browser and server validation agree. // Entries are FIELD SUFFIXES, not BCP-47 tags: "zh-TW" data lives in @@ -1534,7 +1543,7 @@ label = label + (o.dim ? " · " + o.dim : ""); } else { if (o.dim) label += " · " + o.dim; - if (desc) label += (LANG === "zh" || LANG === "ja" ? ":" : " — ") + desc; + if (desc) label += (isCjkUi() ? ":" : " — ") + desc; if (spec && spec.note) label += " · " + spec.note; } copy.appendChild(el("span", "chip-text", label)); @@ -3303,15 +3312,16 @@ // Everything is px — lo/hi are only a sanity envelope for the OOR flag. refreshBodySizeHint = function () { var txt = t("font_body_size_hint"); + var sep = isCjkUi() ? "" : " "; var lo, hi; if (isPptCanvas(STATE.canvas)) { var pb = deliveryBodyPx(STATE.delivery_purpose); lo = pb.lo; hi = pb.hi; - txt += " " + t("body_size_hint_purpose").replace("{def}", pb.def); + txt += sep + t("body_size_hint_purpose").replace("{def}", pb.def); } else { var band = bodySizeBandForCanvas(STATE.canvas, STATE.delivery_purpose); lo = band.lo; hi = band.hi; - txt += " " + t("body_size_hint_canvas") + txt += sep + t("body_size_hint_canvas") .replace("{lo}", lo).replace("{hi}", hi); } // Flag (hint only) a value far outside the @@ -3322,7 +3332,7 @@ ? t("body_size_pt_hint").replace("{pt}", formatPtFromPx(cur)) : ""; if (isFinite(cur) && isFinite(lo) && isFinite(hi) && (cur < lo || cur > hi)) { - txt += " " + t("body_size_hint_oor"); + txt += sep + t("body_size_hint_oor"); } sizeHint.textContent = txt; }; @@ -3552,11 +3562,11 @@ t("image_strategy_select_placeholder"))); var parts = []; if (strategy.rendering) { - parts.push(t("image_strategy_rendering") + ": " + + parts.push(t("image_strategy_rendering") + labelSep() + comparisonValueLabel("rendering", strategy.rendering)); } - if (strategy.visual) parts.push(t("image_strategy_visual") + ": " + strategy.visual); - if (strategy.mood) parts.push(t("image_strategy_mood") + ": " + strategy.mood); + if (strategy.visual) parts.push(t("image_strategy_visual") + labelSep() + strategy.visual); + if (strategy.mood) parts.push(t("image_strategy_mood") + labelSep() + strategy.mood); if (strategy.behavior) parts.push(strategy.behavior); desc.textContent = parts.join(" · ") || t("image_strategy_reference_hint"); } @@ -3714,7 +3724,7 @@ ["image_strategy_visual", customStrategy.visual], ["image_strategy_mood", customStrategy.mood] ].forEach(function (row) { - if (row[1]) customCard.appendChild(el("div", "color-note", t(row[0]) + ":" + row[1])); + if (row[1]) customCard.appendChild(el("div", "color-note", t(row[0]) + labelSep() + row[1])); }); var customCopy = el("div", "ai-custom-candidate-copy", customStrategy.behavior || t("image_strategy_custom_placeholder")); @@ -3812,7 +3822,7 @@ (CAT.image_usage || []).forEach(function (option) { var label = optionLabel(option); var desc = optionDesc(option); - if (desc) label += (LANG === "zh" || LANG === "ja" ? ":" : " — ") + desc; + if (desc) label += (isCjkUi() ? ":" : " — ") + desc; var chip = el("div", "chip"); chip.appendChild(el("span", "chip-text", label)); if (recommendedIds.indexOf(option.id) >= 0) { @@ -4407,6 +4417,7 @@ document.getElementById("sections").style.display = "none"; document.getElementById("actionbar").style.display = "none"; var l = document.getElementById("loading"); + l.setAttribute("data-i18n", "deriving"); l.textContent = t("deriving"); l.style.display = "block"; } @@ -4536,7 +4547,7 @@ if (requested !== "zh" && requested !== "en" && requested !== "ja" && requested !== "zh-TW") return; var hasStored = false; - try { hasStored = !!window.localStorage.getItem("ppt_lang"); } catch (e) { /* ignore */ } + try { hasStored = ["zh", "en", "ja", "zh-TW"].indexOf(window.localStorage.getItem("ppt_lang")) >= 0; } catch (e) { /* ignore */ } if (hasStored) return; LANG = requested; applyStaticTranslations(); @@ -4587,11 +4598,12 @@ setMenuOpen(false); toggleBtn.focus(); if (v !== "ja" && v !== "en" && v !== "zh" && v !== "zh-TW") return; + try { window.localStorage.setItem("ppt_lang", v); } catch (e2) { /* ignore */ } if (v === LANG) return; LANG = v; - try { window.localStorage.setItem("ppt_lang", LANG); } catch (e2) { /* ignore */ } applyStaticTranslations(); refreshLangToggle(toggleBtn); + document.getElementById("confirm-status").textContent = ""; if (REC && CAT) { renderAll(); // STATE persists → selections preserved } 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 759aed6b..f2d7c18d 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 @@ -312,8 +312,10 @@ binding. Completed results stay closed until a new round starts. | `recommendations.stage1.json` (embedded `template_options`) | `"stage1"` | communication contract — content language; audience; open `communication_intent`; audience outcome; core message / primary delivery context + optional secondary use / artifact afterlife / `content_divergence` (all prose fields may be blank); canvas; free-design/template mode and conditional candidate selectors | **Confirm contract & template choice** | writes Stage-1 `result.json` plus `template_selection.json` in one submission; the page stays open and polls while the agent installs and the server verifies the selected roots | | `recommendations.stage2.json` | `"stage2"` | complete deck solution and production — conditional natural-language template application, reading mode, mode, page count, visual direction, color, icons, typography, image usage/rendering, conditional AI acquisition path, proactive notes/custom-animation/narration-audio toggles, generation mode, Design Spec review toggle, and Design Spec depth | **Confirm final plan** | writes `result.json` `{ stage: "final", status: "confirmed", }`, then shuts the page down | -In the UI branch, the AI authors Stage 1 without reading template candidates, -then launches the combined page. In chat/delegated confirmation it authors the +In the UI branch, the AI authors Stage 1 without reading candidate indexes or +workspaces — template facts already in context inform only the canvas and page +range ([`generate-pptx.md`](../../workflows/generate-pptx.md) Step 4) — then +launches the combined page. In chat/delegated confirmation it authors the same communication recommendation before listing template candidates. After the one Stage-1 confirmation, the AI installs any selection, completes the handoff/equivalent state, then inspects only the project-local template and diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/docs/svg-pipeline.md b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/docs/svg-pipeline.md index 665ca7f8..6dc41369 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/docs/svg-pipeline.md +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/docs/svg-pipeline.md @@ -1067,6 +1067,39 @@ Requirements: ## Measuring, wrapping, and calibrating text before authoring +Ordinary generated text frames preserve the SVG's first-line baseline using +`svg_to_pptx/drawingml/text_baseline.py` and the bundled `font_metrics.json`. +The metric table records numeric OpenType fields and source-font versions; +export never reads installed fonts. The selected style uses `typo` metrics +when `USE_TYPO_METRICS` is set, otherwise `win` metrics. Missing italic styles +keep the matching bold face when present, then fall back to regular metrics; +missing faces use an ascent ratio of 0.8. Font aliases resolve through the +same mapping as emitted text runs. + +The first visual line combines the largest size-adjusted ascent and descent +of its active Latin/CJK faces. Default spacing uses a 1.2-times-font-size +line height; explicit `spcPts` spacing has a separate first-baseline model, +including its transition at the natural line height. The renderer rounds +explicit spacing and the final offset to whole points; the authored `spcPts` +value stays intact. Preserved line breaks carry the preceding run's font +properties so presentation defaults do not change line metrics. This behavior +was measured in PowerPoint for Mac 16.113.2; other renderers and font versions +can differ. SVG glyph bounds and checker measurements are not PPT frame offsets. + +Ordinary horizontal PPTX text imports use the same baseline model with font +roles resolved from the source DrawingML, including theme fonts, and carry +`data-pptx-text-baseline="metrics-v1"` on the SVG text element. +Fixed line advances remain absolute; percentage spacing scales the natural +line height, with 100% equivalent to the default spacing. Baseline coordinates +retain enough precision to avoid accumulating inset drift on repeated edits. +The marker survives line splitting and semantic-shape projection. When marked text is +rebuilt inside an exact `data-pptx-frame`, export subtracts the shared baseline +offset from the SVG y coordinate before reconstructing the top inset. Unmarked +exact-frame text retains the earlier `0.85 * font-size` convention, so existing +round-trip workspaces do not require migration. Unchanged source text bodies +still restore their original XML. Native math, baseline-shifted runs, and +vertical text retain their separate positioning models. + `text_measure.py` imports the same single-line DrawingML width estimator used by the SVG quality checker. Use Arial, Times New Roman, Georgia, Verdana, Calibri, Cambria, Trebuchet MS, or diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pptx_to_svg/txbody_to_svg.py b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pptx_to_svg/txbody_to_svg.py index 8fb3fdf4..686b3d0c 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pptx_to_svg/txbody_to_svg.py +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/pptx_to_svg/txbody_to_svg.py @@ -28,6 +28,7 @@ from unicodedata import east_asian_width from xml.etree import ElementTree as ET from hyperlink_contract import SOURCE_HREF_ATTR +from svg_to_pptx.drawingml.text_baseline import drawingml_text_baseline_offset from svg_to_pptx.drawingml.utils import detect_text_lang, is_cjk_char from .color_resolver import ColorPalette, find_color_elem, resolve_color @@ -69,6 +70,8 @@ class TextRun: is_break: bool = False # marks an a:br within a paragraph hyperlink_href: str | None = None formula_latex: str | None = None + font_faces: dict[str, str] = field(default_factory=dict) + baseline_shifted: bool = False HyperlinkResolver = Callable[[str, str], str | None] @@ -90,6 +93,7 @@ class TextParagraph: indent_px: float = 0.0 margin_left_px: float = 0.0 line_height_ratio: float = DEFAULT_LINE_HEIGHT_RATIO + line_spacing_px: float | None = None space_before_px: float = 0.0 space_after_px: float = 0.0 empty_line_font_size_px: float = DEFAULT_FONT_SIZE_PX @@ -163,6 +167,11 @@ def convert_txbody( bins = _read_emu_attr(body_pr, "bIns", DEFAULT_INSETS_EMU["b"]) anchor = body_pr.attrib.get("anchor", "t") if body_pr is not None else "t" wrap_mode = body_pr.attrib.get("wrap", "square") if body_pr is not None else "square" + # DrawingML defaults to overflow: a fixed frame does not hide later lines. + clip_vertical = ( + body_pr is not None + and body_pr.attrib.get("vertOverflow") in {"clip", "ellipsis"} + ) respect_edge_spacing = ( body_pr is not None and body_pr.attrib.get("spcFirstLastPara") in {"1", "true"} @@ -197,15 +206,27 @@ def convert_txbody( space_after, ) ) + anchor_space = inner_h - total_h + # Overflow stays anchored: centered text grows both ways, bottom text up. + # Explicit clipping keeps the leading lines inside the frame instead. + if clip_vertical: + anchor_space = max(0.0, anchor_space) if anchor == "ctr": - cursor_y = inner_y + max(0.0, (inner_h - total_h) / 2.0) + cursor_y = inner_y + anchor_space / 2.0 elif anchor == "b": - cursor_y = inner_y + max(0.0, inner_h - total_h) + cursor_y = inner_y + anchor_space else: cursor_y = inner_y bottom_y = inner_y + inner_h text_blocks: list[str] = [] + # Semantic rebuilding can merge paragraphs back into one native body. + # Keep one baseline convention throughout that body when math or shifted + # runs require the exporter's separate vertical-extent model. + metrics_baseline = not any( + run.formula_latex is not None or run.baseline_shifted + for para in paragraphs for run in para.runs + ) for para, lines, height, before, after in zip( paragraphs, para_lines, @@ -214,13 +235,19 @@ def convert_txbody( space_after, ): cursor_y += before - visible_lines = _clip_lines_to_bottom(para, lines, cursor_y, bottom_y) + visible_lines = ( + _clip_lines_to_bottom(para, lines, cursor_y, bottom_y) + if clip_vertical else lines + ) if visible_lines: text_blocks.append( - _emit_paragraph(para, visible_lines, inner_x, inner_w, cursor_y) + _emit_paragraph( + para, visible_lines, inner_x, inner_w, cursor_y, + metrics_baseline=metrics_baseline, + ) ) cursor_y += height + after - if cursor_y >= bottom_y: + if clip_vertical and cursor_y >= bottom_y: break svg = "\n".join(text_blocks) @@ -668,6 +695,9 @@ def _parse_paragraph( para.margin_left_px = _emu_px_attr_chain(para_style_chain, "marL", 0.0) para.indent_px = _emu_px_attr_chain(para_style_chain, "indent", 0.0) para.line_height_ratio = _line_height_ratio(para_style_chain) + line_spacing = _child_chain(para_style_chain, "a:lnSpc") + if line_spacing is not None and line_spacing.find("a:spcPts", NS) is not None: + para.line_spacing_px = _spacing_points_px((line_spacing,), "a:spcPts") para.space_before_px = _spacing_points_px(para_style_chain, "a:spcBef/a:spcPts") para.space_after_px = _spacing_points_px(para_style_chain, "a:spcAft/a:spcPts") para.bullet_prefix = _resolve_bullet_prefix( @@ -788,6 +818,8 @@ def _apply_norm_autofit( for para in paragraphs: para.empty_line_font_size_px *= font_scale para.line_height_ratio *= line_scale + if para.line_spacing_px is not None: + para.line_spacing_px *= line_scale for run in para.runs: run.font_size_px *= font_scale @@ -944,6 +976,11 @@ def _build_run( strikethrough=strikethrough, letter_spacing_px=letter_spacing_px, hyperlink_href=hyperlink_href, + font_faces={ + "latin": latin_face or "Segoe UI", + "ea": ea_face or latin_face or "Microsoft YaHei", + }, + baseline_shifted=_attr_chain(style_chain, "baseline") not in {None, "0"}, ) @@ -996,7 +1033,7 @@ def _line_height_ratio(sources: tuple[ET.Element | None, ...]) -> float: if spc_pct is None: return DEFAULT_LINE_HEIGHT_RATIO try: - return float(spc_pct.attrib.get("val", "100000")) / 100000.0 + return DEFAULT_LINE_HEIGHT_RATIO * float(spc_pct.attrib.get("val", "100000")) / 100000.0 except ValueError: return DEFAULT_LINE_HEIGHT_RATIO @@ -1492,6 +1529,8 @@ def _copy_run(run: TextRun, *, text: str) -> TextRun: letter_spacing_px=run.letter_spacing_px, hyperlink_href=run.hyperlink_href, formula_latex=run.formula_latex, + font_faces=dict(run.font_faces), + baseline_shifted=run.baseline_shifted, ) @@ -1499,7 +1538,7 @@ def _paragraph_height_from_lines(p: TextParagraph, lines: list[list[TextRun]]) -> float: """Total px height after wrapping. Each line uses its own max font size.""" if not lines: - return p.empty_line_font_size_px * p.line_height_ratio + return _line_height(p, []) height = 0.0 for line in lines: height += _line_height(p, line) @@ -1507,6 +1546,8 @@ def _paragraph_height_from_lines(p: TextParagraph, def _line_height(p: TextParagraph, line: list[TextRun]) -> float: + if p.line_spacing_px is not None: + return p.line_spacing_px return _line_font_size(p, line) * p.line_height_ratio @@ -1533,9 +1574,8 @@ def _clip_lines_to_bottom( cursor_y = top_y for line in lines: line_h = _line_height(para, line) - # PowerPoint lets the first line that starts within the box render even - # when it slightly exceeds the bottom — only suppress lines whose top - # is already at/below the bottom edge. + # Approximate explicit clip/ellipsis at line boundaries. Default + # overflow bypasses this filter; it must not discard source text. if cursor_y >= bottom_y: break visible.append(line) @@ -1554,6 +1594,8 @@ def _emit_paragraph( lines: list[list[TextRun]], inner_x: float, inner_w: float, top_y: float, + *, + metrics_baseline: bool, ) -> str: """Render a paragraph (already split into lines) as one element. @@ -1587,7 +1629,26 @@ def _emit_paragraph( return "" first_run = visible_lines[first_line_idx][0] - first_baseline = top_y + 0.85 * first_run.font_size_px + baseline_offset = 0.85 * first_run.font_size_px + if metrics_baseline: + baseline_offset = drawingml_text_baseline_offset( + [ + { + 'text': run.text, + 'font_size': run.font_size_px, + 'font_family': run.font_family, + 'font_faces': run.font_faces, + 'font_weight': '700' if run.bold else '400', + 'font_style': 'italic' if run.italic else 'normal', + } + for run in visible_lines[first_line_idx] + ], + {}, + default_size=first_run.font_size_px, + line_spacing_px=para.line_spacing_px, + line_spacing_ratio=para.line_height_ratio / DEFAULT_LINE_HEIGHT_RATIO, + ) + first_baseline = top_y + baseline_offset spans: list[str] = [] for line_idx, line in enumerate(visible_lines): @@ -1602,7 +1663,7 @@ def _emit_paragraph( if line_advance is not None: spans.append( f'' + f'dy="{fmt_num(line_advance, 8)}">' ) continue line_has_hyperlink = any(run.hyperlink_href for run in line) @@ -1615,7 +1676,7 @@ def _emit_paragraph( for run in line ) position_attrs = ( - f' x="{fmt_num(anchor_x)}" dy="{fmt_num(line_advance)}"' + f' x="{fmt_num(anchor_x)}" dy="{fmt_num(line_advance, 8)}"' if line_advance is not None else '' ) @@ -1627,19 +1688,21 @@ def _emit_paragraph( if run.formula_latex is not None: spans.append( f'' + f'dy="{fmt_num(line_advance, 8)}">' f'{_run_tspan_markup(run)}' ) else: spans.append( f'{_xml_escape(run.text)}' ) else: spans.append(_run_tspan_markup(run)) base_attrs = _text_base_attrs(first_run, anchor_x, first_baseline, text_anchor) + if metrics_baseline: + base_attrs += ' data-pptx-text-baseline="metrics-v1"' return f"{''.join(spans)}" @@ -1647,7 +1710,7 @@ def _text_base_attrs(run: TextRun | None, x: float, y: float, text_anchor: str) -> str: parts = [ f'x="{fmt_num(x)}"', - f'y="{fmt_num(y)}"', + f'y="{fmt_num(y, 8)}"', f'text-anchor="{text_anchor}"', 'xml:space="preserve"', ] diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/spec_review/static/app.js b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/spec_review/static/app.js index 47c924d4..892e74b4 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/spec_review/static/app.js +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/spec_review/static/app.js @@ -17,6 +17,7 @@ const strings = { blockUnknown: 'Block baseline unknown', blockMissing: 'This block no longer exists', exit: 'Exit review', exitConfirm: 'There are unapplied drafts or unsaved comments. Stop review and discard pending work?', stopped: 'Spec review has stopped. You can close this tab.', + pageTitle: 'PPT Master · Spec Review', languageLabel: 'Language', blocksLabel: 'Spec blocks', scopeLabel: 'Annotation scope', disconnected: 'Cannot reach spec review. Local edits are retained.', rebaseConfirm: 'Stage this text against the current disk version, replacing any current staged draft? Review the source first; Apply will replace this block.', }, zh: { @@ -34,6 +35,7 @@ const strings = { blockUnknown: '该块的意见基准未知', blockMissing: '该块已不存在', exit: '退出评审', exitConfirm: '存在未应用草稿或未保存意见。是否停止评审并放弃待保存内容?', stopped: '评审服务已停止,可以关闭此标签页。', + pageTitle: 'PPT Master · 设计规范评审', languageLabel: '语言', blocksLabel: '设计规范块', scopeLabel: '意见范围', disconnected: '无法连接评审服务,本地编辑已保留。', rebaseConfirm: '以当前磁盘版本为基准暂存此内容,替换服务端的当前草稿?请先核对最新原文;应用时将替换该块。', }, 'zh-TW': { @@ -51,6 +53,7 @@ const strings = { blockUnknown: '該區塊的意見基準未知', blockMissing: '該區塊已不存在', exit: '退出評審', exitConfirm: '存在未套用草稿或未儲存意見。是否停止評審並放棄待儲存內容?', stopped: '評審服務已停止,可以關閉此分頁。', + pageTitle: 'PPT Master · 設計規範審閱', languageLabel: '語言', blocksLabel: '設計規範區塊', scopeLabel: '意見範圍', disconnected: '無法連線評審服務,本機編輯已保留。', rebaseConfirm: '以目前磁碟版本為基準暫存此內容,取代伺服器上的目前草稿?請先核對最新原文;套用時將取代該區塊。', }, ja: { @@ -69,6 +72,7 @@ const strings = { blockUnknown: 'ブロックの基準は不明です', blockMissing: 'このブロックは存在しません', exit: 'レビューを終了', exitConfirm: '未適用の下書きまたは未保存のコメントがあります。レビューを終了して未保存の内容を破棄しますか?', stopped: 'レビューサービスは停止しました。このタブを閉じてください。', + pageTitle: 'PPT Master · 設計仕様レビュー', languageLabel: '言語', blocksLabel: '設計仕様のブロック', scopeLabel: 'コメントの対象', disconnected: 'サービスに接続できません。編集内容は保持されています。', rebaseConfirm: '現在の原文を基準にこの内容を一時保存し、サーバーの下書きを置き換えますか?原文を先に確認してください。適用でこのブロックを置き換えます。', }, }; @@ -79,10 +83,15 @@ const node = (tag, text) => { return result; }; const detected = navigator.language.toLowerCase(); -let lang = detected.startsWith('zh') ? (/tw|hk|hant/.test(detected) ? 'zh-TW' : 'zh') : +let lang = detected.startsWith('zh') ? (/\b(tw|hk|mo|hant)\b/.test(detected) && !/\bhans\b/.test(detected) ? 'zh-TW' : 'zh') : (detected.startsWith('ja') ? 'ja' : 'en'); -try { lang = localStorage.getItem('spec-review-language') || lang; } catch (_) { /* Optional browser storage. */ } -if (!strings[lang]) lang = 'en'; +// `ppt_lang` is the key the other local UIs use; the old key is a read-only fallback. +// Each key is validated on its own so an invalid new value cannot hide a valid old one. +try { + const stored = ['ppt_lang', 'spec-review-language'].map(key => localStorage.getItem(key)) + .find(value => Object.hasOwn(strings, value)); + if (stored) lang = stored; +} catch (_) { /* Optional browser storage. */ } const t = key => strings[lang][key]; // User Markdown never crosses an HTML parser boundary. Unsupported constructs @@ -304,7 +313,7 @@ function renderComments() { $('comments').replaceChildren(); for (const item of comments.filter(item => item.key === annotationKey())) { const card = node('div'); card.className = 'comment'; - card.append(node('small', `${item.id} · ${item.title}`)); + card.append(node('small', `${item.id} · ${item.key === 'global' ? t('global') : item.title}`)); const status = item.key === 'global' ? (item.base_current ? 'current' : 'stale') : (item.block_missing ? 'blockMissing' : (item.block_changed === true ? 'blockChanged' : (item.block_changed === false ? 'blockUnchanged' : 'blockUnknown'))); @@ -372,16 +381,17 @@ async function reload() { if ([...drafts.values()].some(draft => draft.sha256 !== metadata.sha256)) showConflict(true); } function localize() { - document.documentElement.lang = lang; $('language').value = lang; + document.documentElement.lang = lang === 'zh' ? 'zh-CN' : lang; $('language').value = lang; document.querySelectorAll('[data-label]').forEach(element => { element.textContent = t(element.dataset.label); }); + document.querySelectorAll('[data-aria]').forEach(element => { element.setAttribute('aria-label', t(element.dataset.aria)); }); if (stopped) { $('stopped').textContent = t('stopped'); return; } renderStatus(); renderNavigation(); renderPreview(); renderComments(); - if (!$('conflict').hidden) showConflict(); + if (!$('conflict').hidden) { if (current?.missing) $('conflict').textContent = t('missing'); else showConflict(); } } $('language').addEventListener('change', () => { lang = $('language').value; - try { localStorage.setItem('spec-review-language', lang); } catch (_) { /* Optional browser storage. */ } + try { localStorage.setItem('ppt_lang', lang); } catch (_) { /* Optional browser storage. */ } localize(); }); $('source').addEventListener('click', () => { source = !source; renderPreview(); }); diff --git a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/spec_review/static/index.html b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/spec_review/static/index.html index 6dccd4a3..67bc7051 100644 --- a/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/spec_review/static/index.html +++ b/plugins/codex/plugins/ppt-master/skills/ppt-master/scripts/spec_review/static/index.html @@ -3,13 +3,13 @@ - PPT Master · Spec Review + PPT Master · Spec Review -
PPT Master · Spec Review - @@ -25,7 +25,7 @@ -
+

@@ -47,7 +47,7 @@