From eec1752ca35a1d457f27ff7c1c024cd87d9f8a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Waili=28=E7=93=A6=E7=A0=BE=29?= Date: Wed, 24 Jun 2026 10:02:00 +0800 Subject: [PATCH] chore(skills): sync builtin officecli skills from upstream OfficeCli (54d7b0e6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the upstream skill updates from OfficeCli main since the last sync (c46cedd2 → 54d7b0e6) into crates/aionui-app/assets/builtin-skills/. Changed skills (applied as upstream deltas, preserving the AionUi-local Windows platform-note blocks added in #489): - officecli-docx, officecli-pptx, officecli-xlsx: SKILL.md content refresh (new screenshot/svg/pdf view formats, boolean query operators, top-level --find/--replace, expanded type tables, dump round-trip coverage, etc.) - morph-ppt-3d: fix styles INDEX path → ../morph-ppt/reference/styles/INDEX.md (the prior ../../styles path was dead in the AionCore layout) - auto-inject/officecli/SKILL.md: same root-SKILL.md updates as upstream No code changes; assets only. Each file now equals upstream plus only the AionUi-local Windows note. Co-Authored-By: Claude Opus 4.8 --- .../auto-inject/officecli/SKILL.md | 26 +- .../builtin-skills/morph-ppt-3d/SKILL.md | 2 +- .../builtin-skills/officecli-docx/SKILL.md | 517 +++++++----------- .../builtin-skills/officecli-pptx/SKILL.md | 158 +++--- .../builtin-skills/officecli-xlsx/SKILL.md | 24 +- 5 files changed, 306 insertions(+), 421 deletions(-) diff --git a/crates/aionui-app/assets/builtin-skills/auto-inject/officecli/SKILL.md b/crates/aionui-app/assets/builtin-skills/auto-inject/officecli/SKILL.md index 6b4d6d0cd..08d0abf42 100644 --- a/crates/aionui-app/assets/builtin-skills/auto-inject/officecli/SKILL.md +++ b/crates/aionui-app/assets/builtin-skills/auto-inject/officecli/SKILL.md @@ -123,6 +123,7 @@ officecli validate # Validate against OpenXML schema | `text` | Plain text extraction | `--start N --end N`, `--max-lines N` | | `annotated` | Text with formatting annotations | | | `html` | Static HTML snapshot — same renderer as `watch`, no server needed | `--browser`, `--page N` (docx), `--start N --end N` (pptx) | +| `screenshot` / `svg` / `pdf` / `forms` | PNG via headless browser / SVG (pptx slide) / PDF via exporter plugin / form-fields JSON via format-handler plugin | `-o`, `--screenshot-width/-height`, pptx `--grid N` | Use `view html` for one-shot snapshots (CI artifacts, archival, diffing); use `watch` when you need live refresh or browser-side click-to-select. @@ -151,7 +152,7 @@ PPT also accepts `@name=` (e.g. `shape[@name=Title 1]`), with morph `!!` prefix ### query -CSS-like selectors: `[attr=value]`, `[attr!=value]`, `[attr~=text]`, `[attr>=value]`, `[attr<=value]`, `:contains("text")`, `:empty`, `:has(formula)`, `:no-alt`. +CSS-like selectors: `[attr=value]`, `[attr!=value]`, `[attr~=text]`, `[attr>=value]`, `[attr<=value]`, `:contains("text")`, `:empty`, `:has(formula)`, `:no-alt`. Boolean `and`/`or` supported across `query`/`set`/`remove`: `cell[value>5000 or value<100]`, `cell[(type=Number or type=Date) and value>0]`. Excel row-by-column-name: `Sheet1!row[Salary>5000]`. `set` accepts selectors and Excel-native paths (parity with `get`/`query`). Bare unscoped selectors rejected on `set`/`remove`. ```bash officecli query report.docx 'paragraph[style=Normal] > run[font!=Arial]' @@ -230,20 +231,23 @@ officecli set --prop key=value [--prop ...] ### find — format or replace matched text -Use `find=` with `set` to target specific text for formatting or replacement. Format props are separate `--prop` flags — do NOT nest them. +Use top-level `--find` / `--replace` on `set` (and `--find` on `query`). Legacy `--prop find=X` still works but emits a hint. ```bash # Format matched text (auto-splits runs) -officecli set doc.docx '/body/p[1]' --prop find=weather --prop bold=true --prop color=red +officecli set doc.docx '/body/p[1]' --find weather --prop bold=true --prop color=red -# Regex matching -officecli set doc.docx '/body/p[1]' --prop 'find=\d+%' --prop regex=true --prop color=red +# Regex matching (regex= still a prop flag) +officecli set doc.docx '/body/p[1]' --find '\d+%' --prop regex=true --prop color=red # Replace text (use `/` for whole-document scope) -officecli set doc.docx / --prop find=draft --prop replace=final +officecli set doc.docx / --find draft --replace final + +# docx: tracked Find&Replace +officecli set doc.docx / --find draft --replace final --prop revision.author=Alice # PPT — same syntax, different paths -officecli set slides.pptx / --prop find=draft --prop replace=final +officecli set slides.pptx / --find draft --replace final ``` **Path controls search scope:** `/` = whole document, `/body/p[1]` or `/slide[N]/shape[M]` = specific element, `/header[1]` / `/footer[1]` = headers/footers. @@ -270,9 +274,9 @@ officecli add --from # clon | Format | Types | |--------|-------| -| **pptx** | slide (incl. hidden), shape (textbox — font.latin/ea/cs, direction=rtl), picture (SVG, brightness/contrast/glow/shadow), chart (direction=rtl), table (cell direction=rtl), row (tr), connector (connection/line), group, video (audio/media, trim), equation (formula/math), notes (direction=rtl, lang), comment (RTL via U+200F bidi mark; full CRUD via /slide[N]/comment[M]), paragraph (para), run, zoom (slidezoom), ole (oleobject/object/embed), placeholder (phType=title/body/subtitle/footer/...). slideLayout/slideMaster direction inheritance. | -| **docx** | paragraph (para — direction/font.latin/ea/cs, bold.cs/italic.cs/size.cs for RTL/CJK; lang.latin/ea/cs BCP-47 tags on run; wordWrap toggle), run, table (direction=rtl → bidiVisual), row (tr), cell (td), image (picture/img — SVG supported), header (direction), footer (direction), section (pageNumFmt full ECMA-376 enum incl. Hindi/Arabic/Thai/CJK numerals; direction=rtl on Add/Set; rtlGutter; pgBorders=box shorthand), bookmark, comment, footnote, endnote, formfield (text/checkbox/dropdown), sdt (contentcontrol), chart, equation, field (28 types incl. mergefield/ref/seq/styleref/docproperty/if), hyperlink, style (direction round-trip), toc, watermark, break (pagebreak/columnbreak), ole, **num / abstractNum / lvl** (numbering/list system), **tab** (paragraph or paragraph/table style tab stops). docDefaults.rtl document-wide override; `get /` exposes `locale`. Document protection: `set / --prop protection=forms\|readOnly\|comments\|trackedChanges\|none` | -| **xlsx** | sheet (visible/hidden/veryHidden, print margins, printTitleRows/Cols, rightToLeft sheetView, cascade-aware rename), row, cell (type=richtext+runs, merge=range/sweep, direction=rtl, phonetic guide on add), chart (direction=rtl on per-axis txPr / title; incl. pareto), image (picture — SVG), comment (direction=rtl), table (listobject), namedrange (definedname, volatile, `[@name=X]` selector), pivottable (pivot, calculatedField), sparkline, validation (datavalidation), autofilter, shape, textbox, databar/colorscale/iconset/formulacf/cellIs/topN/aboveAverage (conditional formatting), ole, csv (tsv). Query supports `merge`/`mergedrange` aliases for `mergeCell`. Workbook: password. `value="=SUM(...)"` auto-detects as formula. Chart/picture/shape/slicer accept `anchor=A1:E10`. | +| **pptx** | slide (incl. hidden), shape (font.latin/ea/cs, direction=rtl, underline.color, highlight=COLOR (Add/Set/Get/HTML preview), effective.X+effective.X.src; arrow alias for rightArrow; slideMaster/slideLayout typed add/set/remove), picture (SVG, brightness/contrast/glow/shadow, rotation, link, tooltip), chart (direction=rtl, pieOfPie, barOfPie, axisLine/gridline per-attr setters, animation+chartBuild=byCategory|bySeries, line dropLines/hiLowLines/upDownBars, anchor=x,y,w,h shorthand), table (cell direction=rtl, fill/background, built-in PowerPoint style catalogue, /col[C] get + swap/copyFrom, row/col Move/CopyFrom), row (tr), connector (from/to accept @name=, startshape/endshape SetByPath), group (link, tooltip, deep walk by get/query/add/remove), video/audio (loop, autoStart alias), equation, notes (direction=rtl, lang), comment (legacy + modern p188 threaded round-trip), animation (15 emphasis + 16 exit presets, multi-effect chains, motion-path presets, repeat/restart/autoReverse, chart animations), transition (12 p15 presets + morph/p14), paragraph (para), run, zoom, ole (preview=, full dump round-trip via add-part+raw-set), placeholder (phType=...), model3d (rotation=ax,ay,az; full dump round-trip), smartart (dump round-trip via add-part). | +| **docx** | paragraph (direction/font.latin/ea/cs, bold.cs/italic.cs/size.cs, lang.latin/ea/cs, wordWrap, framePr.\*, tabs shorthand), run (lang slots, direction, underline.color, position half-pts, **revision.type=ins\|del\|format\|moveFrom\|moveTo + revision.action=accept\|reject** with .author/.date — `/revision[@author=X]` selector for filtered accept/reject), table (direction=rtl, hMerge, **virtual column ops**: add/remove/move/copyfrom on /body/tbl[N]/col), row (tr), cell (td), image, header/footer (direction), section (pageNumFmt full enum, direction=rtl, rtlGutter, pgBorders=box), bookmark, comment, footnote, endnote, formfield, sdt, chart, equation, field (28 types), hyperlink, style (direction, indents, pbdr, lineSpacing on Add/Set), toc, watermark, break, ole, **num/abstractNum/lvl**, **tab**, **textbox/shape** (full Add+Get; geometry, fill, line, wrap, alt, anchor, **rotation, verticalText (eaVert/vert/vert270/wordArt\*), gradient, shadow, opacity**), embedded **OLE round-trip on dump→batch**. docDefaults.rtl, autoHyphenation, `get /` exposes locale + /comments /footnotes /endnotes. `create --minimal` for raw OOXML scaffolding. | +| **xlsx** | sheet (visible/hidden/veryHidden, print margins, printTitleRows/Cols, rightToLeft sheetView, cascade-aware rename), row (c{N}= cell-content shorthand; add accepts --from /Sheet/col[L]; formula-ref rewrite on insert), col (formula-ref rewrite, named-range follow on move), cell (type=richtext+runs, merge=range/sweep, direction=rtl, phonetic; **--shift left\|up on remove, shift=right\|down on add** — Excel UI dialog parity; formula auto-detect; OFFSET/INDIRECT in calc), chart (per-axis RTL/title, anchor=x,y,w,h, pareto), image (SVG), comment (direction=rtl), table (listobject), namedrange (definedname, volatile, `[@name=X]`; formula-body inlined at parse), pivottable (cache CoW + cross-pivot sharing, labelFilter, topN, fillDownLabels, calculatedField), sparkline, validation, autofilter, shape, textbox, CF (databar/colorscale/iconset/formulacf/cellIs/topN/aboveAverage), ole, csv. Query supports `merge`/`mergedrange`. Workbook: password. Shape selector enumerates leaves inside grpSp. | ### Pivot tables (xlsx) @@ -335,7 +339,7 @@ When using `--after` or `--before`, `--to` can be omitted — the target contain Continues on error by default (returns exit 1 if any item fails). Use `--stop-on-error` to abort on the first failure. `--force` is the docx-protection bypass. -`officecli dump []` emits a replayable batch JSON for round-trip. Path defaults to `/` (whole document); pass a subtree path (`/body`, `/body/p[N]`, `/body/tbl[N]`, `/theme`, `/settings`, `/numbering`, `/styles`) to scope the dump. `officecli refresh ` recalculates TOC page numbers / PAGE / cross-references after replay (Word backend on Windows; headless-HTML fallback elsewhere). +`officecli dump []` emits a replayable batch JSON for round-trip — `.docx` (full coverage) and `.pptx` (text/tables/pictures/charts/notes/theme + OLE/3D/video/audio/SmartArt/morph/p15 transitions via raw-set passthrough). Path defaults to `/` (whole document); pass a subtree path (`/body`, `/body/p[N]`, `/body/tbl[N]`, `/theme`, `/settings`, `/numbering`, `/styles`) to scope the dump. `officecli refresh ` recalculates TOC page numbers / PAGE / cross-references after replay (Word backend on Windows; headless-HTML fallback elsewhere). `officecli plugins list` extends support to `.doc`, `.hwpx`, `.pdf` export. ```bash echo '[ diff --git a/crates/aionui-app/assets/builtin-skills/morph-ppt-3d/SKILL.md b/crates/aionui-app/assets/builtin-skills/morph-ppt-3d/SKILL.md index aeebd3bf8..498b80c0a 100644 --- a/crates/aionui-app/assets/builtin-skills/morph-ppt-3d/SKILL.md +++ b/crates/aionui-app/assets/builtin-skills/morph-ppt-3d/SKILL.md @@ -257,7 +257,7 @@ Choose a palette that matches the **topic mood** — don't default to generic bl - One color dominates (60-70% visual weight), 1-2 supporting tones, one accent - On light backgrounds: use Body Text color for copy, Muted for captions - On dark backgrounds: use Secondary or `FFFFFF` for copy, Muted for captions -- For additional inspiration, browse `../../styles/INDEX.md` — 50+ visual styles organized by mood (dark, light, warm, vivid, bw). Read `style.md` for design philosophy, `build.sh` for implementation reference. **Learn the approach, do not copy coordinates verbatim** +- For additional inspiration, browse `../morph-ppt/reference/styles/INDEX.md` — 50+ visual styles organized by mood (dark, light, warm, vivid, bw). Read `style.md` for design philosophy, `build.sh` for implementation reference. **Learn the approach, do not copy coordinates verbatim** ### Font Pairings (pick one per deck) diff --git a/crates/aionui-app/assets/builtin-skills/officecli-docx/SKILL.md b/crates/aionui-app/assets/builtin-skills/officecli-docx/SKILL.md index 8df27b9b1..904ebe07e 100644 --- a/crates/aionui-app/assets/builtin-skills/officecli-docx/SKILL.md +++ b/crates/aionui-app/assets/builtin-skills/officecli-docx/SKILL.md @@ -41,73 +41,68 @@ officecli help docx # Verb-scoped (e.g. add field, set s officecli help docx --json # Machine-readable schema ``` -Help is pinned to the installed CLI version. When this skill and help disagree, **help is authoritative**. Special-topic mini-sections below end with an explicit pointer back to help. +Help is pinned to the installed CLI version. When this skill and help disagree, **help is authoritative**. -## Mental Model & Inheritance +## Mental Model -**Mental model.** A `.docx` is a ZIP of XML parts (`document.xml`, `styles.xml`, `numbering.xml`, `header*.xml`, `footer*.xml`, `comments.xml`, ...). Everything the user sees — headings, tables, page numbers, TOC, tracked changes — is XML inside that ZIP. `officecli` gives you a semantic-path API (`/body/p[1]/r[2]`) over it, so you almost never touch raw XML; when you must, use `raw-set`. +A `.docx` is a ZIP of XML parts (`document.xml`, `styles.xml`, `numbering.xml`, `header*.xml`, `footer*.xml`, `comments.xml`, …). Everything the user sees — headings, tables, page numbers, TOC, tracked changes — is XML inside that ZIP. `officecli` gives you a semantic-path API (`/body/p[1]/r[2]`) over it, so you almost never touch raw XML; when you must, use `raw-set` (see the XML appendix). ## Shell & Execution Discipline -**Shell quoting (zsh / bash).** docx paths contain `[]`, some prop values contain `$`. Both are shell metacharacters. Rules: +docx paths contain `[]`; some prop values contain `$`. Both are shell metacharacters. Escaping happens at three layers — keep them separate. -- ALWAYS quote element paths: `"/body/p[1]"`, not `/body/p[1]`. -- Use **single quotes** for any prop value containing `$`: `--prop text='$50M'`. The rule holds at any length — a 200-word body paragraph containing `$50M` needs the whole value inside single quotes, same as a three-word heading: `--prop text='In Q4 we hit $50M ARR, up 18% YoY — the strongest quarter since inception...'`. Mixing `'... $var ...'` and `"... $50 ..."` on long strings is where shell-leak silently strips `$50` → nothing. -- NEVER hand-write `\$`, `\t`, `\n` inside executable examples. The CLI does not interpret backslash escapes; they will land in your file as literal characters. In a cell / paragraph text, a real newline goes through the JSON layer (`batch` heredoc with `"\n"` inside the JSON string). +1. **Shell.** ALWAYS quote element paths: `"/body/p[1]"`, not `/body/p[1]` (zsh/bash glob `[N]`). Single-quote any value containing `$`: `--prop text='$50M'` — at any length, the whole value inside one pair of single quotes. Unquoted `$50M` is stripped to `M`; mixing `'…$var…'` and `"…$50…"` on one long string is where the `$50` silently vanishes. +2. **CLI (`text=`).** The two-char escapes `\n` and `\t` ARE interpreted in `--prop text=` — `\n` becomes a `` soft line break, `\t` a `` — consistently across docx / pptx / xlsx. Double them (`\\n`) for a literal backslash-n (rarely wanted). This applies to row-level table `c1…cN` shortcuts too (`\n` → `` within the cell). +3. **JSON (batch).** A real newline can also be passed as `"\n"` in the JSON string of a `batch` heredoc; same result. -**Incremental execution.** Run commands one at a time and read each exit code. `officecli` mutates the file on every call; a 50-command script that fails at command 3 will cascade silently. One command → check output → continue. After any structural op (new style, table, TOC, section break) run `get` on it before stacking more on top. - -**File-name convention in this skill.** All commands use `"$FILE"` — set once at the top of your script or session (`FILE="your-doc.docx"`) and every command picks it up. Copy-paste blocks and individual examples both assume `$FILE` is set. Do NOT copy a literal `doc.docx` / `review.docx` into an output directory — that is the wrong filename, always substitute your actual target. +If in doubt, `view text` after writing and compare character-for-character. -## Requirements for Outputs +**Incremental execution.** `officecli` mutates the file on every call. Run commands one at a time and check each exit code — a 50-command script that fails at command 3 cascades silently. After any structural op (new style, table, TOC, section break) run `get` on it before stacking more. -Before reaching for a command, know what a good docx looks like. These are the deliverable standards every document MUST meet. +**Resident mode is the default**, not an optimization: `officecli open ` at the start, `officecli close ` at the end — it avoids re-parsing the XML every call. For many paragraphs of one style, use `batch` (one open/save cycle for the whole array). -### All documents +**`$FILE` convention.** All commands use `"$FILE"` — set it once (`FILE="your-doc.docx"`). Never copy a literal `doc.docx` / `review.docx` into output — always substitute your actual target. -**Clear hierarchy.** Every non-trivial document has Title → Heading 1 → Heading 2 → body, not a wall of unstyled `Normal` paragraphs. A reader scans headings first. If `view outline` shows one flat list of paragraphs, the hierarchy is missing. +## Requirements for Outputs -**Explicit heading sizes.** Do NOT rely on Word default style sizes — they drift between templates. Set sizes explicitly: **H1 = 18pt minimum (20pt preferred for long reports)**, H2 = 14pt bold, H3 = 12pt bold. Body = 11-12pt. Line spacing 1.15-1.5x. +Deliverable standards every document MUST meet — know these before reaching for a command. -**One body font, one accent.** Pick one readable body font (Calibri, Cambria, Georgia, Times New Roman) and keep it consistent. Accent color for heading emphasis or table headers — not rainbow formatting. +**Clear hierarchy.** Every non-trivial document has Title → Heading 1 → Heading 2 → body, not a wall of unstyled `Normal` paragraphs. If `view outline` shows one flat list, the hierarchy is missing. -**Spacing through properties, not empty paragraphs.** Use `spaceBefore` / `spaceAfter` on paragraphs. Rows of empty paragraphs render as spacing in Word but break pagination and `view issues` will flag them. +**Explicit heading sizes** (Word default style sizes drift between templates): **H1 ≥ 18pt** (20pt for long reports), H2 = 14pt bold, H3 = 12pt bold, body = 11–12pt, line spacing 1.15–1.5x. Prefer `style=Heading1` over inline sizes so a retheme touches the definition once — but set explicit sizes when you can't trust the template's styles. -**Smart quotes and typographic quality.** New content uses curly quotes (`'`, `'`, `"`, `"`) not ASCII `'` and `"`. Use Unicode directly (`'smart'`) or the XML entities `‘` / `’` / `“` / `”` inside `raw-set`. En-dash `–` for ranges (`2024–2026`), em-dash `—` for parenthetical breaks. +**One body font, one accent.** One readable body font (Calibri, Cambria, Georgia, Times New Roman); accent color for heading emphasis or table headers, not rainbow formatting. -**Headers, footers, page numbers on any document > 1 page.** Page numbers go through a live `PAGE` field, not the literal text "Page 1". Use `--prop field=page` on a footer add — the CLI injects `` for you (see Creating & Editing → Headers & Footers). +**Spacing through properties.** Use `spaceBefore` / `spaceAfter` on paragraphs. Rows of empty paragraphs break pagination and are flagged by `view issues`. -**Preserve existing templates.** When editing a file that already has a look, match it. Existing conventions override these guidelines. +**Typographic quality.** New content uses curly quotes (`'` `'` `"` `"`), not ASCII — Unicode directly or XML entities (`‘`/`’`/`“`/`”`) inside `raw-set`. En-dash `–` for ranges (`2024–2026`), em-dash `—` for parenthetical breaks. -### Visual delivery floor (applies to EVERY document) +**Headers, footers, page numbers on any document > 1 page.** Page numbers go through a live `PAGE` field (`--prop field=page`), never the literal text "Page 1" — the CLI injects `` for you (see Headers & Footers). -Before you declare done, run `officecli view "$FILE" html` and Read the returned HTML path to confirm all of these: +**Preserve existing templates.** When editing a file that already has a look, match it — existing conventions override these guidelines. -- **No placeholder tokens rendered as data.** `$xxx$`, `{var}`, `{{name}}`, ``, `lorem`, `xxxx` must never appear in a heading, body paragraph, cover page, TOC, caption, header, or footer. These are build-time tokens that escaped replacement. If you want a literal `{name}` in a template for a human to fill, wrap it in a visible instruction paragraph ("Replace `{name}` before sending") so no one confuses it with finished content. -- **No truncated titles or overflowing cells.** Long headings / table cell values must fit the page and the column. If a cell overflows, widen the column or set `wrapText` on the cell. -- **Page numbers render as real numbers.** Confirm `get --depth 3` on the footer shows `` children — not just a run with literal text `"Page"`. The footer must contain a live field, not a static word. -- **TOC present when document has 3+ headings.** Add with `--type toc`. The TOC is a live field — some viewers show the heading list immediately, others show `Update field to see table of contents` until the user recalculates (F9 in Word). -- **Cover page ≥ 60% filled, last page ≥ 40% filled.** A cover that is 80% blank space looks unfinished. Pad with subtitle / author / date / scope statement / key highlights / decorative band. A last page with just "Thank you" centered also reads as unfinished — add conclusion, next steps, contact, legal notice. -- **No `\$`, `\t`, `\n` literals in document text.** If you see these in `view text`, a shell-escape layer leaked. Delete the paragraph and re-enter it. +### Visual delivery floor (applies to EVERY document) -If any of the above fails, STOP and fix before declaring done. +Before declaring done, run `officecli view "$FILE" html` and Read the returned HTML path to confirm ALL of these: -### Hard rules worth repeating (they are how docx goes wrong) +- **No placeholder tokens rendered as data.** `$xxx$`, `{var}`, `{{name}}`, ``, `lorem`, `xxxx` must never appear in a heading, body, cover, TOC, caption, header, or footer. A literal `{name}` meant for a human to fill belongs inside a visible instruction paragraph ("Replace `{name}` before sending"), never as finished content. +- **No truncated titles or overflowing cells.** Widen the column or set `wrapText` rather than trimming content. +- **TOC present when the document has 3+ headings** (`--type toc`). +- **Cover page ≥ 60% filled, last page ≥ 40% filled.** Pad a thin cover with subtitle / author / date / scope / key highlights; pad a "Thank you" last page with conclusion / next steps / contact / legal. +- **No `\$`, `\t`, `\n` literals in document text.** If `view text` shows these, a shell-escape layer leaked — delete the paragraph and re-enter it. -- Single-command footer with page number: `add / --type footer --prop field=page ...` — do NOT pass `--prop fldChar=...` or hand-compose the field. The CLI handles it. -- First-page footer `--type footer --prop type=first --prop text=""` automatically triggers `differentFirstPage`. Do NOT `set / --prop differentFirstPage=true` separately — that prop is UNSUPPORTED and silently fails. -- TOC add: `--type toc --prop levels="1-3" --prop hyperlinks=true --index 0`. Do NOT pass `--prop pagenumbers=true` — UNSUPPORTED (page numbers render automatically). +If any fails, STOP and fix before declaring done. ## Common Workflow Six steps. Every non-trivial build follows this shape. -1. **Choose the mode.** Always use `officecli open ` at the start and `officecli close ` at the end. Resident mode is the default, not an optimization — it avoids re-parsing the XML on every command. For many paragraphs of the same style, use `batch` (≤ 12 ops per block for reliability). -2. **Orient.** For a new file, `officecli create "$FILE"`. For existing, `officecli view "$FILE" outline` first — get the heading tree, section count, whether a TOC / watermark / tracked changes are already there. Never start editing blind. -3. **Build incrementally.** Structural first, content next, formatting last. Styles and numbering defs → sections / page setup → headings and body → tables / images / fields / TOC → headers / footers → comments. After each structural op, `get` it back to confirm shape before stacking on top. -4. **Format to spec.** Explicit heading sizes, spacing, widths, alignment, tabs, list indents. Formatting is not optional polish — per Requirements for Outputs it is part of the deliverable. -5. **Close, then recalculate fields.** `officecli close "$FILE"` writes XML to disk. TOC / PAGE / NUMPAGES / SEQ / PAGEREF fields have **cached values** that may be stale or empty. When a human opens the file in Word, they press F9 to recalc. For the CLI's purposes, confirm fields *exist* (via `get --depth 3` finding ``) rather than trusting the text value — the text is the cached render, the field is the truth. -6. **QA — assume there are problems.** See the QA section. You are not done when your last command exited 0; you are done after one fix-and-verify cycle finds zero new issues. +1. **Open the file.** `officecli open "$FILE"` (resident default). New file: `officecli create "$FILE"` first. +2. **Orient.** Existing file: `officecli view "$FILE" outline` — heading tree, section count, whether a TOC / watermark / tracked changes already exist. Never edit blind. +3. **Build incrementally.** Structural first, content next, formatting last: styles & numbering defs → sections / page setup → headings & body → tables / images / fields / TOC → headers / footers → comments. After each structural op, `get` it back before stacking on top. +4. **Format to spec.** Explicit heading sizes, spacing, widths, alignment, tabs, list indents — formatting is part of the deliverable, not optional polish. +5. **Close, then trust structure over cached text.** `officecli close "$FILE"` writes the XML. TOC / PAGE / NUMPAGES / SEQ / PAGEREF fields carry **cached values** that may be stale or empty until a human recalculates (F9 in Word). Confirm fields *exist* (`get --depth 3` finds ``) rather than trusting the visible text. +6. **QA — assume there are problems.** You are done after one fix-and-verify cycle finds zero new issues, not when your last command exited 0. See QA. ## Quick Start @@ -127,37 +122,27 @@ officecli close "$FILE" officecli validate "$FILE" ``` -Verified: `validate` returns `no errors found`; `get /footer[1] --depth 3` shows the 5-run PAGE field chain (the begin / instrText / separate / cached value / end runs that wrap the live field), not a static `"Page"` string; for the raw `` XML behind those runs, use `officecli raw "$FILE" "/footer[1]" | grep fldChar`. This is the shape of every build: open → structure → content → format → footer/fields → close → validate. +Verified: `validate` returns `no errors found`; `get /footer[1] --depth 3` shows the 5-run PAGE field chain (begin / instrText / separate / cached value / end). ## Reading & Analysis -Start wide, then narrow. `outline` tells you what structure is already there; jump into `view text` / `get` / `query` only once you know where to look. - -**Open the rendered document to eyeball your own work.** -- `officecli view $FILE html` — Read the returned HTML to audit the rendered output. Headings, tables, page breaks visible. Catches heading hierarchy issues, empty paragraphs-as-spacing, missing TOC entries. -- `officecli watch $FILE` keeps a live preview running for the human user — they can open it at their own discretion. Use only when the user wants to watch along; agent self-check uses `view html` above. -Use `view html` as your **first visual check after a batch of edits**. For final visual verification, the user opens the `.docx` in their Word / WPS / Pages viewer. - -**Orient.** Heading tree, section count, table / image counts, watermark, tracked changes presence. - -```bash -officecli view "$FILE" outline -``` - -**Extract text for content QA or LLM context.** Paths are shown as `[/body/p[N]]` so you can jump back with `get`. Scope with `--start` / `--end` / `--max-lines` on long documents. +Start wide, then narrow. `outline` tells you what's already there; jump into `view text` / `get` / `query` once you know where to look. ```bash -officecli view "$FILE" text --start 1 --end 80 +officecli view "$FILE" outline # heading tree, section count, table/image counts, watermark, tracked-changes presence — orient here first +officecli view "$FILE" html # Read the returned HTML path: first visual check after a batch of edits (hierarchy, empty-para spacing, missing TOC) +officecli view "$FILE" text --start 1 --end 80 # text for content QA; paths shown as [/body/p[N]] so you can jump back with get officecli view "$FILE" annotated # values + style/font/size + warnings per run officecli view "$FILE" stats # paragraph counts, font usage, style distribution officecli view "$FILE" issues # empty paras, missing alt text, spacing anomalies ``` -**Inspect one element.** XPath-style semantic paths (1-based, like XPath). Always quote — shells glob `[N]`. +`officecli watch "$FILE"` keeps a live preview running for the human user to open at their discretion — agent self-check uses `view html`. Final visual verification is the user opening the `.docx` in Word / WPS / Pages. + +**Inspect one element.** XPath-style semantic paths (1-based). Always quote — shells glob `[N]`. Use `[last()]` (with parens) for the last element; `[last]` errors. Add `--json` for machine output. ```bash officecli get "$FILE" / # document root: metadata, page setup -officecli get "$FILE" /body --depth 1 # body children overview officecli get "$FILE" "/body/p[1]" # one paragraph officecli get "$FILE" "/body/p[1]/r[1]" # one run (character-level formatting) officecli get "$FILE" "/body/tbl[1]" --depth 3 # table with rows and cells @@ -166,9 +151,7 @@ officecli get "$FILE" "/styles/Heading1" # style definition officecli get "$FILE" /numbering --depth 2 # numbering abstractNum + num bindings ``` -Add `--json` for machine output. Use `[last()]` (with parentheses) to address the last element: `/body/tbl[last()]/tr[1]`. `[last]` without parens errors. - -**Query across the document.** CSS-like selectors, for systematic checks rather than hand-walking. +**Query across the document.** CSS-like selectors, for systematic checks rather than hand-walking. Operators: `=`, `!=`, `~=` (contains), `>=`, `<=`, `[attr]` (exists). Full reference: `officecli query --help`. ```bash officecli query "$FILE" 'paragraph[style=Heading1]' # all H1s @@ -179,30 +162,28 @@ officecli query "$FILE" 'paragraph[size>=24pt]' # numeric comparison officecli query "$FILE" 'field[fieldType!=page]' # fields other than PAGE ``` -Operators: `=`, `!=`, `~=` (contains), `>=`, `<=`, `[attr]` (exists). Full selector reference: `officecli query --help`. +`query --json` wraps results in `.data.results[]` — `jq '.data.results | length'` to count. -**Large documents.** When a document is long enough that `view text` is unwieldy, use `view outline` to navigate by heading and `query` to jump directly to what you need — don't dump the whole body into context. +**Large documents.** Navigate by heading with `view outline` and jump with `query`; don't dump the whole body into context. ## Creating & Editing -The verbs: `add` (new element), `set` (change a prop), `remove`, `move`, `swap`, `batch`, `raw-set` (last-resort XML). Ninety percent of a docx build is paragraphs, runs, tables, a couple of images, a TOC, and a footer. +Verbs: `add` (new element), `set` (change a prop), `remove`, `move`, `swap`, `batch`, `raw-set` (last-resort XML). Ninety percent of a build is paragraphs, runs, tables, a couple of images, a TOC, and a footer. ### Paragraphs, runs, styles -A paragraph (`p`) is a block; a run (`r`) is a span of consistent character formatting inside it. Set paragraph-level properties (style, alignment, spacing, indent) on the `p`; set font / size / color / bold on the `r`. +A paragraph (`p`) is a block; a run (`r`) is a span of consistent character formatting inside it. Set paragraph-level props (style, alignment, spacing, indent) on the `p`; set font / size / color / bold on the `r`. ```bash officecli add "$FILE" /body --type paragraph --prop text="Executive Summary" --prop style=Heading1 --prop size=18pt --prop bold=true --prop spaceAfter=12pt officecli set "$FILE" "/body/p[1]/r[1]" --prop color=1F4E79 ``` -**Use styles, not ad-hoc formatting.** `style=Heading1` references the document's style definition — change the definition once, all headings update. Inline `size=18pt` on every heading is a style-bypass; when you need to retheme you have to touch every paragraph. - -Use `spaceBefore` / `spaceAfter` for vertical spacing. Never use chains of empty paragraphs — they break pagination and are flagged by `view issues`. +Use `spaceBefore` / `spaceAfter` for vertical spacing — never chains of empty paragraphs. For left indent use `--prop indent=720` (twips), `firstLineIndent=360` for first line, `hangingIndent=720` for hanging; leading spaces fire `view issues`. ### Tables -Tables are `/body/tbl[N]` with rows `tr[N]` and cells `tc[N]`. Add the table with a row and column count, then fill. +Tables are `/body/tbl[N]` with rows `tr[N]` and cells `tc[N]`. Add with row/column counts, then fill. ```bash officecli add "$FILE" /body --type table --prop rows=4 --prop cols=3 --prop width=100% @@ -210,43 +191,45 @@ officecli set "$FILE" "/body/tbl[1]/tr[1]" --prop header=true --prop c1=Quarter officecli set "$FILE" "/body/tbl[1]/tr[1]/tc[1]/p[1]/r[1]" --prop bold=true ``` -Row-level `set` supports `height`, `header`, and `c1 / c2 / ... / cN` text shortcuts — `cN` generalises to any column count, use as many as the table has columns (a 7-column matrix accepts `c1` through `c7`). Cell formatting (bold, fill, color) goes on the cell's paragraph / run. For per-cell borders, use the paragraph-level `pbdr.*` dotted-attr on the cell's inner paragraph instead of cell-level `border.bottom` (the cell-level border prop currently places `` in the wrong XML position and fails `validate` — see Known Issues). +Row-level `set` supports `height`, `header`, and `c1 / c2 / … / cN` text shortcuts (`cN` generalises to any column count). Cell formatting (bold, fill, color) goes on the cell's paragraph / run — **not** row-level. For per-cell borders, set cell-level `border.*` on the `tc` (`--prop border.bottom="single;6;000000;0"`), or paragraph-level `pbdr.*` on the inner paragraph. + +**Horizontal rule = a paragraph bottom border, never a 1-row table.** A table-as-divider renders as an empty min-height box (worst in headers/footers). Use `pbdr.bottom` (`STYLE;SIZE;COLOR`) on the paragraph instead: + +```bash +officecli set "$FILE" "/body/p[3]" --prop pbdr.bottom="single;6;2E75B6" +``` ### Lists (bullets, numbered, multi-level) -For single-level bullets or numbers, set `listStyle` on the paragraph (`listStyle` is a paragraph prop, NOT a run prop — common mistake): +For single-level bullets/numbers, set `listStyle` on the paragraph (`listStyle` is a paragraph prop, NOT a run prop — common mistake): ```bash officecli add "$FILE" /body --type paragraph --prop text="First item" --prop listStyle=bullet -officecli add "$FILE" /body --type paragraph --prop text="Second item" --prop listStyle=bullet ``` -For multi-level (legal-style 1 / 1.1 / 1.1.1 / appendix numbering), add an `abstractNum` then a `num`, then reference the `numId` from each paragraph: +For multi-level (legal-style 1 / 1.1 / 1.1.1), add an `abstractNum`, then a `num`, then reference the `numId` per paragraph: ```bash -officecli add "$FILE" /numbering --type abstractnum --prop format=decimal -officecli add "$FILE" /numbering --type num --prop abstractNumId=1 +officecli add "$FILE" /numbering --type abstractnum --prop format=decimal # → abstractNum id=0 +officecli add "$FILE" /numbering --type num --prop abstractNumId=0 # → num id=1 officecli add "$FILE" /body --type paragraph --prop text="Section one" --prop numId=1 --prop ilvl=0 ``` -After adding, verify with `officecli query "$FILE" 'paragraph[numId>0]'` that every `numId` reference points at a real ``. See `officecli help docx abstractnum` and `officecli help docx num` for all level and format options. +IDs are 0-based: the first `abstractNum` is id=0; the `num` references it via `abstractNumId=0` and is itself assigned id=1. A non-existent `abstractNumId` errors, so check ids after creating. Verify with `officecli query "$FILE" 'paragraph[numId>0]'`. See `help docx abstractnum` / `help docx num` for level and format options. -### Tab stops (dot leaders, right-aligned page numbers) +### Tab stops (signature lines, leader rows) -Used for positional layout — a signature line, a TOC-entry-style "Chapter 1 ........ 12" row, a form field slot. Tab stops are a first-class `tab` element added as a child of the paragraph: +Tab stops are a first-class `tab` child of the paragraph; `pos` accepts `6in`/`6cm`/twips, `val` ∈ `left`/`center`/`right`, `leader` ∈ `none`/`dot`/`hyphen`/`underscore`. See `help docx tab`. ```bash officecli add "$FILE" "/body/p[1]" --type tab --prop pos=6in --prop val=right --prop leader=dot -officecli add "$FILE" "/body/p[2]" --type tab --prop pos=3cm --prop val=left --prop leader=underscore ``` -`pos` accepts `6in` / `6cm` / twips. `val` ∈ `left` / `center` / `right`. `leader` ∈ `none` / `dot` / `hyphen` / `underscore`. Paths are 1-based: `/body/p[N]/tab[K]`. See `officecli help docx tab` for the full grammar. - -**Leader rendering caveat.** `leader=dot` / `underscore` on a tab definition alone does not emit dots/underscore in the output — the leader only renders when a real `` character is present inside a run of that paragraph, and the high-level API does not insert `` runs. For visible signature lines or dot-leader TOC-style rows you have two working options: (a) use literal characters — `text="_______________________________________"` for a signature line, or `"Chapter 1 ............ 12"` for a leader row — visually equivalent and ships reliably; or (b) `raw-set` a `` into the paragraph before the leading line. +**Leader caveat.** `leader=dot` alone emits no dots — the leader renders only when a real `` character sits in a run between the text and the tab stop. Put one there with `\t` in the text: define the stop (`add tab --prop pos=6in --prop val=right --prop leader=dot`), then `--prop text="Chapter 1\t12"` — the `\t` becomes the `` and dots fill to the right-aligned page number. (Literal `text="Chapter 1 ......... 12"` also ships, but a real tab stop aligns cleanly.) ### Fields (PAGE / NUMPAGES / DATE / MERGEFIELD / REF) -Fields are live values computed at render time. Two props carry all the info: `fieldType` picks the field; `name` supplies the target (merge field name or bookmark for `ref`); `format` adds switches (date patterns, number formats). +Fields are live values computed at render time. `fieldType` picks the field; `name` supplies the target (merge name or `ref` bookmark); `format` / `instr` add switches. | Field | Use | Example | |---|---|---| @@ -256,73 +239,71 @@ Fields are live values computed at render time. Two props carry all the info: `f | `mergefield` | template merge token | `--prop fieldType=mergefield --prop name=CustomerName` | | `ref` | cross-reference to a bookmark | `--prop fieldType=ref --prop name=bookmarkName` | -The full `fieldType` enum (30+ values: `page`, `pagenum`, `pagenumber`, `numpages`, `date`, `time`, `author`, `title`, `filename`, `section`, `sectionpages`, `mergefield`, `ref`, `pageref`, `noteref`, `seq`, `styleref`, `docproperty`, `if`, `createdate`, `savedate`, `printdate`, `edittime`, `lastsavedby`, `subject`, `numwords`, `numchars`, `revnum`, `template`, `comments`, `keywords`) is in `officecli help docx field`. **There is NO `fieldInstr` fieldType** — use the `instr` prop (alias `instruction`) to inject raw field instruction text when typed shortcuts fall short. Picture switches (`MERGEFIELD Amount \# "#,##0.00"`, `DATE \@ "yyyy年MM月"`) go via `--prop instr='...'` on mergefield and via `--prop format='yyyy-MM-dd'` on date/time (mergefield's `format` prop is ignored with a warning — use `instr` instead). - -**SEQ / PAGEREF cached-value trap.** `seq` and `pageref` are CLI-expressible (`--prop fieldType=seq --prop identifier=Figure`, `--prop fieldType=pageref --prop name=bookmark`) and pass `validate`, but every instance emits cached `` of `1` regardless of position — so three `SEQ Figure` captions render as `Figure 1 / Figure 1 / Figure 1` in viewers that do not recompute on open. Set `` in settings (via `raw-set`) and/or patch the cached `` after each SEQ. Academic papers with multiple figures/tables: see the `officecli-academic-paper` skill for the full SEQ patch recipe. - -For a standalone MERGEFIELD inside a paragraph: +Full `fieldType` enum (30+ values incl. `pageref`, `seq`, `styleref`, `docproperty`, `createdate`, …) is in `help docx field`. **There is NO `fieldInstr` fieldType** — use the `instruction` prop for raw field instruction text when typed shortcuts fall short. Picture switches (`MERGEFIELD Amount \# "#,##0.00"`, `DATE \@ "yyyy年MM月"`) go via `--prop instruction='…'` (mergefield's `format` prop is ignored with a warning — use `instruction`). ```bash officecli add "$FILE" "/body/p[3]" --type field --prop fieldType=mergefield --prop name=customer_name -# Renders as «customer_name» — visible placeholder, replaced in Word at mail-merge time. +# Renders «customer_name» — visible placeholder, replaced in Word at mail-merge time. ``` -Verified: canonical form passes `validate` and renders `«customer_name»` on open. Confirm all MERGEFIELDs exist with `officecli query "$FILE" 'field[fieldType=mergefield]'`. +**MERGEFIELD templates: never render placeholder literals.** A `{{customer_name}}` or `$NAME$` shown as body text is a failed template the recipient sees — insert a real MERGEFIELD (above), or confine literal tokens to an obvious instruction paragraph. Confirm with `query 'field[fieldType=mergefield]'`. + +**SEQ / PAGEREF / TOC field values.** officecli doesn't store rendered field values at write time. Recompute by what each path needs: + +- **SEQ numbering** (`Figure 1/2/3`): `officecli set "$FILE" / --prop recalcFields=seq` counts SEQ fields in body document order and writes the cached values (`evaluated` flips true; switches/formats in `help docx document`). Heading-relative `\s` and SEQ in headers/footers defer to Word. +- **PAGE / PAGEREF / NUMPAGES / TOC page numbers** need pagination, which officecli has no engine for — `officecli set "$FILE" /settings --prop updateFields=true` defers them to Word on open. -**MERGEFIELD templates: do NOT render placeholder literals.** If a template shows `{{customer_name}}` or `$NAME$` as body text, a human recipient sees the literal token — that is a failed template. Either (a) insert a real MERGEFIELD via the `field` type above, which Word replaces at mail-merge time, or (b) put literal tokens only inside an obvious instruction paragraph ("Replace `{{customer_name}}` before sending"). See Requirements for Outputs → Visual delivery floor. +Use both on a multi-figure document. Academic papers: see the `officecli-academic-paper` skill. ### Headers & Footers (page numbering) -The single-command pattern — the CLI injects `` so you do not compose the field by hand: +Single-command pattern — the CLI injects ``, so you never compose the field by hand: ```bash # Empty first-page footer — auto-enables differentFirstPage so the cover has no page number officecli add "$FILE" / --type footer --prop type=first --prop text="" - # Default footer with live page number officecli add "$FILE" / --type footer --prop type=default --prop align=center --prop size=9pt --prop text="Page " --prop field=page ``` -When both a first-page footer and a default footer exist, the default footer is `/footer[2]`. If only a default footer, it is `/footer[1]`. **Verify**: `get --depth 3` must show `fldChar` children, not just a run with literal text `"Page"`. `view outline` prints "Footer: Page" for both live fields AND static text — do not rely on it. - -Do NOT `set / --prop differentFirstPage=true` separately — that prop is UNSUPPORTED and silently fails. Adding a first-type footer is how you flip the bit. - -For composite footers like "Page X of Y" (PAGE + NUMPAGES in one paragraph), see `officecli help docx footer` and use `raw-set` with two `` field instructions — high-level single-command does not compose two fields in one run. +When both exist, the default footer is `/footer[2]`; alone it is `/footer[1]`. **Verify**: `get --depth 3` must show `fldChar` children, not just a run with literal `"Page"` (`view outline` prints "Footer: Page" for both live fields AND static text — don't rely on it). Do NOT `set --prop differentFirstPage=true` — that prop is unsupported (rejected with exit 2, not silently); adding a first-type footer flips the bit. For composite **Page X of Y**, see recipe (b). ### Table of Contents -For any document with 3+ headings (Requirements): +For any document with 3+ headings: ```bash officecli add "$FILE" /body --type toc --prop levels="1-3" --prop title="Table of Contents" --prop hyperlinks=true --index 0 ``` -The TOC is a live field — when a human opens the file, the viewer either populates it on open or shows it after the user recalculates (F9 in Word). Do NOT pass `--prop pagenumbers=true` — UNSUPPORTED; page numbers render automatically. +Page numbers render automatically (`--prop pageNumbers=true` toggles them explicitly). Address the TOC directly (1.0.60+): `/toc[1]` or `/tableofcontents` resolve to the first TOC field for `get`/`set`/`remove` without hand-walking XPath. -**Addressing the TOC (1.0.60+).** Direct paths `/toc[1]` or `/tableofcontents` resolve to the first TOC field without hand-walking XPath — use these as the primary path for `get` / `set` / `remove`: +**TOC delivery step (mandatory before handoff).** The live TOC field is a placeholder until recalculated. Some viewers populate it on first open; others show the literal `Update field to see table of contents` until the reader recalculates. Pick by recipient: -```bash -officecli get "$FILE" "/toc[1]" --depth 2 # primary path — no raw-set needed to locate -officecli get "$FILE" "/tableofcontents" --depth 2 # alias, same target -``` +- **Will recalculate (or press F9):** run `officecli set "$FILE" /settings --prop updateFields=true` so Word recomputes the TOC (and all fields) on open, and/or add a visible "Press F9 to refresh the TOC and page numbers" instruction. Done. +- **Cannot / will not recalculate:** use the **static TOC fallback — recipe (f)** (the live field renders a placeholder until recalculated; no headless pipeline can pre-populate it). + +Ship-check: `officecli query "$FILE" 'p:contains("Update field to see")'` must return empty whenever the reader won't recalculate. A match means switch to recipe (f). -**TOC delivery step — treat this as mandatory before handing the file off.** **The live TOC field is a placeholder until recalculated.** Some viewers show the real heading list on first open; others show the literal string `Update field to see table of contents` until the reader recalculates. Two workarounds — pick one based on who reads the file: +### Images + +Pictures go inside a run. Alt text is mandatory for accessibility — pass `alt` directly at create time: -- **Recipients who will open in a viewer that recalculates (or who will press F9)**: add a visible instruction ("Press F9 to refresh the TOC and page numbers"). No further action needed. -- **Recipients who cannot / will not recalculate**: use the **static TOC fallback — see Report-level recipes (f) below**. No CLI-only pipeline currently populates `` with the cached heading rows that Word writes on save. Headless conversion tools cannot pre-render the TOC on Word's behalf — their TOC handling and pagination differ, so relying on them to "fill" the TOC for a Word recipient is unsafe. `raw-set` on `//w:sdt/w:sdtContent` is theoretically possible but requires reconstructing the exact per-heading XML (with correct bookmarks, PAGEREF chains, and cached page numbers) and has not worked reliably. Hand-write the static fallback instead. +```bash +officecli add "$FILE" "/body/p[5]" --type picture --prop src=logo.png --prop width=1.5in --prop alt="Acme logo" +``` -Ship-check: `officecli query "$FILE" 'p:contains("Update field to see")'` must return empty whenever the reader won't recalculate. If it matches, the TOC is unpopulated — switch to recipe (f). +Confirm `officecli query "$FILE" 'image:no-alt'` is empty before delivery. -### Images +### Charts -Pictures go inside a run. Alt text is mandatory for accessibility, but **add rejects `alt` at create time** (CLI bug C-D-3): add first, then `set`. +For data, add a **native chart** — editable, themeable, accessible, re-renders in Word — never a flat PNG screenshot of a chart. `data="Label:v1,v2,…"` per series; one `data=` per series (or `series1=`/`series2=`). ```bash -officecli add "$FILE" "/body/p[5]" --type picture --prop src=chart.png --prop width=4in -officecli set "$FILE" "/body/p[5]/r[last()]" --prop alt="Q4 revenue by region, bar chart" +officecli add "$FILE" /body --type chart --prop chartType=bar --prop title="Revenue by Region" --prop categories="EMEA,APAC,Americas" --prop data="2026:120,150,180" ``` -Confirm with `officecli query "$FILE" 'image:no-alt'` — output should be empty before delivery. +`chartType` ∈ bar / column / line / pie / area / scatter (`help docx chart` for axis/legend/series styling). A PNG via `--type picture` is only a fallback for an exotic chart officecli can't build. ### Hyperlinks and bookmarks @@ -332,210 +313,172 @@ External links go via `hyperlink`: officecli add "$FILE" "/body/p[2]" --type hyperlink --prop uri="https://example.com" --prop text="our site" ``` -**Internal links (to a bookmark within the document) are NOT supported by the high-level `hyperlink` command** — it rejects fragment URLs. Use `raw-set` with ``, or pair a `PAGEREF` field with visible text. See `officecli help docx hyperlink` and `officecli help docx bookmark`. +**Internal links** (to a bookmark) use `--prop anchor=bookmarkName` — not a `#fragment` in `uri`: + +```bash +officecli add "$FILE" "/body/p[2]" --type hyperlink --prop anchor=chapter1 --prop text="See Chapter 1" +``` + +Pairing a `PAGEREF` field with visible text is the alternative. See `help docx hyperlink` / `help docx bookmark`. ### Sections and page setup -Document root `/` carries page setup (`pageWidth`, `pageHeight`, margins). Multi-section documents (landscape insert, column layout) add a `section` break; use `officecli help docx section` for the section prop list. +Document root `/` carries page setup (`pageWidth`, `pageHeight`, margins, in twips). Multi-section documents (landscape insert, columns) add a `section` break — see `help docx section`. Both camelCase (`pageWidth`, canonical) and lowercase alias (`pagewidth`) are accepted; prefer camelCase. ```bash officecli set "$FILE" / --prop pageWidth=12240 --prop pageHeight=15840 --prop marginTop=1440 --prop marginLeft=1440 +# Newspaper-style multi-column flow (columnSpace in twips; 720 = 0.5in): +officecli set "$FILE" / --prop columns=2 --prop columnSpace=720 ``` -Section accepts both camelCase (`pageWidth`, canonical) and lowercase alias (`pagewidth`). Prefer camelCase. +### Forcing page breaks — belt-and-suspenders + +Two mechanisms exist; **neither alone is reliable across every viewer**. Depending on viewer and preceding content, `` may be ignored OR `` rendered as a soft break — opposite failures. Apply BOTH on every H1 you want on a fresh page, the TOC heading, and the cover-closing paragraph: + +```bash +officecli add "$FILE" /body --type pagebreak --index # 1. pagebreak element BEFORE the heading +officecli set "$FILE" "/body/p[]" --prop pageBreakBefore=true # 2. on the heading itself +``` + +`--prop break=newPage` (1.0.61+) is a shorter alias for `pageBreakBefore=true` (accepts `newPage|page|nextPage|pageBreak`). Same XML, same belt-and-suspenders rule. Preview with `view html` and count pages. ### Report-level recipes -Four patterns that come up on every long-form report and aren't covered by the Quick Start. Each has been executed and `validate`-passed. +Patterns that come up on every long-form report. Each has been executed and `validate`-passed. -**(a) Rich cover page — hit the ≥ 60% filled floor.** A bare title + date cover reads as unfinished. Stack a confidentiality banner, title, subtitle, client/project/date block, and a 3-line key-themes strip: +**(a) Rich cover page — hit the ≥ 60% filled floor.** Stack a confidentiality banner, title, subtitle, client/project/date block, and a key-themes strip, then force the next section onto a new page: ```bash officecli add "$FILE" /body --type paragraph --prop text="CONFIDENTIAL — CLIENT USE ONLY" --prop align=center --prop size=9pt --prop color=C00000 --prop spaceAfter=24pt officecli add "$FILE" /body --type paragraph --prop text="Strategic Growth Review" --prop style=Title --prop size=32pt --prop bold=true --prop align=center --prop font=Cambria --prop spaceAfter=8pt officecli add "$FILE" /body --type paragraph --prop text="FY26 Outlook and Scenario Planning" --prop italic=true --prop size=16pt --prop align=center --prop spaceAfter=36pt officecli add "$FILE" /body --type paragraph --prop text='Prepared for: Acme Corp. Leadership Team' --prop align=center --prop size=11pt -officecli add "$FILE" /body --type paragraph --prop text='Engagement: 2026-04 — 2026-06' --prop align=center --prop size=11pt -officecli add "$FILE" /body --type paragraph --prop text='Author: Advisory Partners' --prop align=center --prop size=11pt --prop spaceAfter=36pt +officecli add "$FILE" /body --type paragraph --prop text='Engagement: 2026-04 — 2026-06' --prop align=center --prop size=11pt --prop spaceAfter=36pt officecli add "$FILE" /body --type paragraph --prop text="Key themes: 1) margin resilience, 2) EMEA expansion, 3) capital allocation." --prop align=center --prop italic=true --prop size=10pt -# Force the next section to start on a new page — belt-and-suspenders for cross-viewer reliability -# (pageBreakBefore alone is unreliable across viewers; --type pagebreak alone also flakes) officecli add "$FILE" /body --type pagebreak officecli set "$FILE" "/body/p[last()]" --prop pageBreakBefore=true ``` -**(b) Page X of Y footer — composite PAGE + NUMPAGES.** Add the footer paragraph first, then three child ops build `Page of ` in one paragraph. Visual outcome: footer reads `Page 3 of 12` with both numbers live. This is the official `officecli help docx footer` recipe. +**(b) Page X of Y footer — composite PAGE + NUMPAGES.** Add the footer paragraph, then three child ops build `Page of ` live. The official `help docx footer` recipe. ```bash officecli add "$FILE" / --type footer --prop type=default --prop text="Page " --prop align=center --prop size=9pt officecli add "$FILE" "/footer[1]/p[1]" --type field --prop fieldType=page officecli add "$FILE" "/footer[1]/p[1]" --type run --prop text=" of " officecli add "$FILE" "/footer[1]/p[1]" --type field --prop fieldType=numpages -# Verify the 3 field fragments exist: -officecli get "$FILE" "/footer[1]/p[1]" --depth 1 | grep -o fldChar | wc -l # expect ≥ 4 (begin+separate+end per field; DON'T use `grep -c` — single-line XML always returns 1) +officecli get "$FILE" "/footer[1]/p[1]" --depth 1 | grep -o fldChar | wc -l # expect ≥ 4; use grep -o ... | wc -l, NOT grep -c (single-line XML returns 1) ``` -**(c) Header row with fill and white bold text.** Don't chain `shd.fill=` (broken). Order matters: populate the header row's cell text FIRST (runs don't exist in empty cells, so a `set .../tc[N]/p[1]/r[1]` on empty cells errors with "No r found"), THEN apply cell fill, THEN run formatting. Visual outcome: dark-blue header band with white bold labels, zebra-striped data rows. +**(c) Header row with fill and white bold text.** Order matters — populate header cell text FIRST (runs don't exist in empty cells; a `set …/tc[N]/p[1]/r[1]` on an empty cell errors "No r found"), THEN cell fill, THEN run formatting: ```bash officecli add "$FILE" /body --type table --prop rows=5 --prop cols=4 --prop width=100% -# 1. Populate header cell text — creates the runs we'll style next officecli set "$FILE" "/body/tbl[1]/tr[1]" --prop header=true --prop c1=Quarter --prop c2=Revenue --prop c3=Growth --prop c4=Status -# 2. Header cells — dark fill + white bold text for col in 1 2 3 4; do officecli set "$FILE" "/body/tbl[1]/tr[1]/tc[$col]" --prop fill=1F4E79 officecli set "$FILE" "/body/tbl[1]/tr[1]/tc[$col]/p[1]/r[1]" --prop bold=true --prop color=FFFFFF done -# 3. Alternating row fills for rows 3, 5 (zebra) for row in 3 5; do for col in 1 2 3 4; do - officecli set "$FILE" "/body/tbl[1]/tr[$row]/tc[$col]" --prop fill=D9E2F3 + officecli set "$FILE" "/body/tbl[1]/tr[$row]/tc[$col]" --prop fill=D9E2F3 # zebra stripe done; done ``` -Verified: without step 1, step 2's run-level `set` errors because empty cells have no `r`. This is the most common trip in table builds. - -**(d) Financial table style — right-align numbers, bold totals, bottom border on total row.** Numbers read right-aligned; totals read bold; a `pbdr.bottom` under the last data row visually separates the total: +**(d) Financial table — right-align numbers, bold totals, bottom border on total row.** ```bash -# Right-align number columns (cols 2-4), paragraph-level for row in 2 3 4 5; do for col in 2 3 4; do officecli set "$FILE" "/body/tbl[1]/tr[$row]/tc[$col]/p[1]" --prop align=right done; done -# Total row (row 5) bold + bottom border on the data paragraphs for col in 1 2 3 4; do officecli set "$FILE" "/body/tbl[1]/tr[5]/tc[$col]/p[1]/r[1]" --prop bold=true officecli set "$FILE" "/body/tbl[1]/tr[4]/tc[$col]/p[1]" --prop pbdr.bottom="single;6;000000;0" done ``` -**(e) Cell with multiple bullets — SWOT / risk matrix / timeline.** Row-level `c1="line1\nline2"` drops a literal `\n`; one cell = one paragraph by default. To stack N bullets inside a single cell, seed the first via `set c1=`, then `add paragraph` under the cell for each subsequent bullet, then `move --index 1` to push the seeded line above its siblings if needed. Visual outcome: a 2×2 SWOT where each quadrant lists 3-5 bullets, each on its own line. +**(e) Cell with multiple bullets (SWOT / risk matrix).** `c1="a\nb"` gives a `` line break within **one** paragraph — fine for plain multi-line text, but bullets need separate paragraphs. Seed the first via `set c1=`, then `add paragraph` (with `listStyle=bullet`) under the cell per subsequent bullet: ```bash -# 2x2 SWOT, cell (1,1) = Strengths with 3 bullets officecli set "$FILE" "/body/tbl[1]/tr[1]" --prop c1="Installed base of 18k enterprise seats" officecli add "$FILE" "/body/tbl[1]/tr[1]/tc[1]" --type paragraph --prop text="Margin structure above peer median" --prop listStyle=bullet -officecli add "$FILE" "/body/tbl[1]/tr[1]/tc[1]" --type paragraph --prop text="Founder-led sales motion in mid-market" --prop listStyle=bullet -# (optional) If the seeded line should also render as a bullet, style it: officecli set "$FILE" "/body/tbl[1]/tr[1]/tc[1]/p[1]" --prop listStyle=bullet ``` -If your seed paragraph lands at the bottom instead of the top (row-level `set c1=` sometimes appends), re-order: `officecli move "$FILE" "/body/tbl[1]/tr[1]/tc[1]/p[N]" --index 0`. +If the seeded line lands at the bottom, re-order: `officecli move "$FILE" "/body/tbl[1]/tr[1]/tc[1]/p[N]" --index 0`. -**(f) Static TOC fallback (cross-viewer reliability).** When delivering to viewers that don't auto-recalculate fields, the live TOC field renders as the literal `Update field to see table of contents`. No CLI-only pipeline can pre-populate a TOC field the way Word does on save — this is a hard black hole, not a recipe gap. Workaround: remove the TOC field, keep the `TOCHeading` style paragraph as a visible header, then hand-write one paragraph per heading with a literal dot-leader line. Visual outcome: a plain text TOC with dots trailing to page numbers, no live field, ships correctly in any reader. +**(f) Static TOC fallback (cross-viewer reliability).** When delivering to viewers that don't auto-recalculate, the live TOC field renders the literal `Update field to see table of contents`. No CLI-only pipeline can pre-populate a TOC field the way Word does on save. Workaround: remove the TOC field, keep a visible heading, hand-write one dot-leader line per heading. ```bash -# 1. Locate and remove the raw TOC field paragraph(s) that carry the "Update field to see..." cached text -officecli query "$FILE" 'p:contains("Update field to see")' # note the /body/p[N] paths +officecli query "$FILE" 'p:contains("Update field to see")' # note the /body/p[N] paths, then: officecli remove "$FILE" "/body/p[N]" # repeat per hit - -# 2. Add a visible heading where the TOC used to be (if not already present) officecli add "$FILE" /body --type paragraph --prop text="Contents" --prop style=TOCHeading --prop size=14pt --prop bold=true --index - -# 3. Hand-write one line per heading with literal dots and page number officecli add "$FILE" /body --type paragraph --prop text="1. Executive Summary ......................................... 3" --prop size=11pt --index -officecli add "$FILE" /body --type paragraph --prop text="2. Market Diagnosis .......................................... 5" --prop size=11pt --index -# ... one per heading +# … one per heading. Page numbers manual; eyeball positions via view html. Live --type toc remains correct for recipients who recalculate. ``` -Use this when the live-field option leaves the literal prompt visible to the reader. Page numbers are manually set. For approximate pagination preview: `officecli view "$FILE" html` and read the returned HTML file to eyeball layout. For exact page numbers: open in your target viewer (Word / WPS / etc.) — precise numbers only come from the final render in that viewer. This recipe assumes you can get approximate page positions from the document structure. `add --type toc` (live field) remains correct for recipients whose viewer recalculates on open (or who will press F9) — this recipe is for everyone else. - -### Forcing page breaks — belt-and-suspenders for cross-viewer reliability - -Two mechanisms exist; **neither alone is reliable across every viewer**. Pagination is heuristic — depending on the viewer and preceding content state, it may silently ignore `` OR render `` as a soft break. The two failures occur in opposite directions depending on the viewer. Apply BOTH on every H1 you want on a fresh page: - -```bash -# 1. Prepend a pagebreak element BEFORE the heading -officecli add "$FILE" /body --type pagebreak --index -# 2. Set pageBreakBefore=true on the heading paragraph itself -officecli set "$FILE" "/body/p[]" --prop pageBreakBefore=true -``` - -Neither alone guarantees a break in every client. Observed on officecli 1.0.60: `pageBreakBefore` alone left 9 chapters mashed into 6 pages in one viewer; `--type pagebreak` alone has also been seen to flake, especially when the file is PDF-converted by a headless renderer. **Recommendation: prefer `pageBreakBefore=true` (more reliable across viewers) and add `--type pagebreak` as the secondary guarantee.** The redundant pair closes the gap. - -**`break=newPage` alias (1.0.61+).** The paragraph / section prop `--prop break=newPage` is a shorter alias that maps to `pageBreakBefore=true` (accepts `newPage | page | nextPage | pageBreak`). Same underlying XML, same behavior — so the belt-and-suspenders rule still applies: use `add --type pagebreak` before the heading AND set `pageBreakBefore=true` / `break=newPage` on the heading paragraph itself. ⚠️ `pageBreakBefore`/`break=` passed to `add` may be silently dropped — always apply it via a subsequent `set`. - -Apply to every H1, the TOC heading, and the cover-closing paragraph. Preview via `view html` (read the returned HTML path) and count pages to confirm. - ### Template delivery — separating Template Notes from end-user content -HR / legal / vendor templates commonly carry internal-only guidance ("replace `{{CompanyName}}`", "list of expected merge columns") that must NOT ship to the end recipient. Two working patterns: +HR / legal / vendor templates carry internal-only guidance ("replace `{{CompanyName}}`") that must NOT ship. Two working patterns: -- **Trailing "Template Notes" section with a clear heading.** Add a `Heading 1` titled "Template Notes for HR Users" (or similar) at the bottom of the document, then all instruction paragraphs underneath. Before distribution, `officecli remove "$FILE" /body/p[N]` every paragraph from the heading downward, or `officecli query "$FILE" 'paragraph[style=Heading1]:contains("Template Notes")'` to locate the boundary. A visible heading makes the section unmistakable at review time and scriptable at delivery time. -- **Bookmark-bounded internal section.** Wrap the guidance between two bookmarks (`add --type bookmark --prop name=__template_notes_start` / `_end`) on the paragraphs before and after the internal content. At delivery, `raw-set` removes everything between the two anchors in one pass. Slightly more fragile but more robust to accidental heading edits. +- **Trailing "Template Notes" section** under a clear `Heading 1` ("Template Notes for HR Users") with all instructions below it; before distribution, `remove` from the heading downward (locate with `query 'paragraph[style=Heading1]:contains("Template Notes")'`). +- **Bookmark-bounded internal section** between `__template_notes_start` / `_end` bookmarks; at delivery `raw-set` removes everything between the anchors. -Either way, the ship-check is: after removal, `officecli query "$FILE" 'p:contains("Template Notes")'` returns empty AND `query 'p:contains("{{")` (literal tokens the guide referenced) also returns empty. If the template notes paragraph survives, a downstream employee will read internal HR language. Treat this as a delivery gate for template builds. +Delivery gate for templates: after removal, `query 'p:contains("Template Notes")'` AND `query 'p:contains("{{")'` both return empty. If a notes paragraph survives, a downstream employee reads internal language. ### Advanced / specialty topics (skip if you are writing a report) -Reports, memos, letters, proposals, and HR templates don't need this section — skip to Raw-set escape hatch. Keep reading only if your document is academic (equations, footnotes, bibliography), a reviewed draft (comments, tracked changes), or marked (watermark). +Reports, memos, letters, proposals, and HR templates don't need this. Keep reading only if your document is academic (equations, footnotes, bibliography), reviewed (comments, tracked changes), or marked (watermark). -**Equations and footnotes.** `--type equation` takes LaTeX — `\frac`, `\sum`, Greek letters, `\mathit` render; `\mathcal` emits invalid XML (use `\mathit` instead). Footnotes auto-number by paragraph index. +**Equations and footnotes.** `--type equation` takes LaTeX — `\frac`, `\sum`, Greek, `\mathit`, `\mathcal` all render. By default it creates a standalone `/body/oMathPara[N]` display block; pass `--prop mode=inline` with a paragraph parent path (`add "/body/p[N]" --type equation --prop formula=… --prop mode=inline`) to drop an inline `` into running text. Footnotes auto-number by paragraph index. Bibliography hanging indent: `firstLineIndent=-720 indent=720` per entry. ```bash officecli add "$FILE" /body --type equation --prop formula="\\frac{a}{b} + \\sum_{i=1}^{n} x_i" officecli add "$FILE" "/body/p[3]" --type footnote --prop text="See Appendix A for methodology." ``` -`--type equation` always creates a standalone `/body/oMathPara[N]` block — never an inline run, even if you pass a paragraph path. For inline math inside running text, `raw-set` an `` (not ``) as a run child. Bibliography with hanging indent: `firstLineIndent=-720 indent=720` per entry (dotted `ind.hanging` is not canonical — see Known Issues). +**Comments and tracked changes.** Bulk accept/reject: `set / --prop accept-changes=all` (or `reject-changes=all`). Locate individual changes with `query ins` and `query del` (`trackedchange` is not a selector). Create tracked changes on a run with `--prop revision.type=ins|del --prop revision.author=…` (`help docx run` for the full `revision.*` set — `format`/`moveFrom`/`moveTo` too). Add a comment: `add "/body/p[4]" --type comment --prop author=… --prop text=…`; reply-thread it with `--prop parentId=N` and mark it resolved with `set "/comments/comment[N]" --prop done=true` (resolve rather than delete to keep the audit trail — `query 'comment[done=false]'` then lists what's still open). Prop schema: `help docx comment` / `help docx run`. -**docx vs academic-paper skill — when to switch.** Stay in docx for: chapter drafts, ≤ 3 footnotes, ≤ 2 equations, no bibliography, no cross-refs. Switch to `academic-paper` when you need ANY of: citation styles (APA / Chicago / Harvard / IEEE / GB 7714), in-text ↔ reference list auto-linking, numbered equations with `\ref`, "List of Figures", auto-updating "see Section 3.2" cross-refs, or author-year ↔ numeric style toggles. +**Watermark.** `add / --type watermark --prop text="DRAFT" --prop color=BFBFBF --prop opacity=0.8` in one command (default opacity 0.5); `set /watermark --prop opacity=…` adjusts it later. -**docx vs word-form skill — when to switch.** Stay in docx for any report, letter, memo, or proposal. Switch to `officecli-word-form` when the document's purpose is **data capture** — fillable intake forms, contracts / SOWs with user-fill slots, HR onboarding forms, medical questionnaires, compliance checklists, mail-merge templates. Those carry `` content controls, `` legacy form fields, or `documentProtection=forms`, none of which this skill teaches. - -**Comments and tracked changes.** Bulk accept/reject: `set / --prop accept-changes=all` (or `reject-changes=all`). Locate individual changes with `query ins` and `query del` — NOT `query trackedchange` (CLI bug C-D-1). Adding an `` or `` from scratch requires `raw-set`. Add a comment with `add "/body/p[4]" --type comment --prop author=... --prop text=...`. Reply threading (`parentId`) and `done=true` resolution are UNSUPPORTED — see C-D-2 / C-D-5 for `raw-set` workarounds. - -**Watermark.** Two steps because `add --prop opacity=...` is UNSUPPORTED (C-D-7): `add / --type watermark --prop text="DRAFT" --prop color=BFBFBF`, then `set /watermark --prop opacity=0.8`. Default opacity is 0.5. +**When to switch skills.** Stay in docx for chapter drafts, ≤ 3 footnotes, ≤ 2 equations, no bibliography/cross-refs. Switch to **`academic-paper`** for citation styles (APA / Chicago / IEEE / GB 7714), in-text↔reference auto-linking, numbered equations with `\ref`, "List of Figures", or auto-updating cross-refs. Switch to **`officecli-word-form`** when the document's purpose is **data capture** — fillable forms, contracts with user-fill slots, questionnaires, mail-merge templates (`` content controls, ``, `documentProtection=forms`). ### Raw-set escape hatch (L1 / L2 / L3) Three tiers of precision; use the lowest that does the job. -- **L1 — high-level props** (`--prop text=...`, `--prop style=Heading1`): your default. Works for 80% of cases. -- **L2 — dotted-attr fallback** (`pbdr.top=`, `ind.left=`, `padding.top=`, `border.*`, `font.size=`, `font.color=`): when L1 lacks the exact knob. Schema-safe for most props. Example: `--prop pbdr.bottom="single;6;1F4E79;0"`. Prefer this over raw-set when the whitelist covers your need. **Two dotted props emit invalid XML today** — `shd.fill=` (missing `w:val`) and `ind.firstLine=` (placed after `w:jc` in `pPr`). Use the canonical L1 form of these instead: `shd=clear;FFFF00` and `firstLineIndent=360`. See Known Issues → Schema-invalid-on-emit. -- **L3 — `raw-set` with XML**: last resort. Tied to OOXML knowledge; no schema protection. Use for tracked-change creation, internal hyperlinks, composite PAGE+NUMPAGES, comment `parentId`, `commentsExtended` `done=1`. +- **L1 — high-level props** (`--prop text=…`, `--prop style=Heading1`): your default. Covers 80%. +- **L2 — dotted-attr fallback** (`pbdr.top=`, `ind.left=`, `shd.fill=`, `padding.top=`, `font.size=`): when L1 lacks the knob. Example: `--prop pbdr.bottom="single;6;1F4E79;0"`. Emits schema-valid XML. +- **L3 — `raw-set` with XML**: last resort, no schema protection. Use for internal hyperlinks, composite fields, and other shapes the typed verbs can't express (see XML appendix). -Borders go through the format `style;size;color;space`: `single;4;FF0000;1`. Hex colors never start with `#`: `FF0000`, not `#FF0000`. Scheme color names (`accent1..6`, `dark1`/`dark2`, `light1`/`light2`, `hyperlink`) are also accepted anywhere a hex color is (1.0.60+) — prefer hex when you need stable colors across themes. +Borders use the format `style;size;color;space`: `single;4;FF0000;1`. Hex colors never start with `#`: `FF0000`. Scheme color names (`accent1..6`, `dark1`/`dark2`, `light1`/`light2`, `hyperlink`) are accepted anywhere a hex color is (1.0.60+) — prefer hex for stable colors across themes. ## QA (Required) -**Assume there are problems. Your job is to find them.** - -Your first document is almost never correct. Treat QA as a bug hunt, not a confirmation step. If you found zero issues on first inspection, you were not looking hard enough. Headings look fine **until** you `view outline` and notice an H3 directly under an H1. The footer shows "Page 1" in `view text` **until** you `get --depth 3` and find it is a static run, not a field. +**Assume there are problems — QA is a bug hunt, not a confirmation step.** Your first document is almost never correct; zero issues on first inspection means you weren't looking hard enough. Headings look fine until `view outline` shows an H3 directly under an H1; the footer shows "Page 1" until `get --depth 3` reveals a static run, not a field. ### Minimum cycle before "done" 1. `officecli view "$FILE" issues` — empty paras, missing alt text, formatting anomalies. -2. `officecli view "$FILE" outline` — heading hierarchy, TOC presence, section count. No skipped levels (H1 → H3). -3. `officecli view "$FILE" text --max-lines 400` — content pass: typos, stray `\$` / `\t` / `\n` literals, placeholder tokens. -4. Query for known classes of defect: - ```bash - officecli query "$FILE" 'p:contains("lorem")' - officecli query "$FILE" 'p:contains("xxxx")' - officecli query "$FILE" 'p:contains("TODO")' - officecli query "$FILE" 'p:contains("{{")' - officecli query "$FILE" 'p:empty' - officecli query "$FILE" 'image:no-alt' - ``` -5. `officecli validate "$FILE"` — schema check. Close any resident first (see Known Issues). -6. **Visual pass — walk every page via the HTML preview.** Run `officecli view "$FILE" html` and Read the returned HTML path. Walk every page. "validate pass" is not delivery; "the preview looks like a real document" is delivery. For human review, run `officecli watch "$FILE"` (user opens the live preview at their own discretion) or have them open the `.docx` directly in Word / WPS. -7. If anything failed, fix, then **rerun the full cycle**. One fix commonly creates another problem. +2. `officecli view "$FILE" outline` — heading hierarchy (no H1 → H3 skips), TOC presence, section count. +3. `officecli view "$FILE" text --max-lines 400` — typos, stray `\$`/`\t`/`\n` literals, placeholder tokens. +4. `officecli validate "$FILE"` — schema check (the Delivery Gate re-runs this on the closed, on-disk file). +5. **Visual pass — whole document as a contact sheet** (vision-capable agents only — if you cannot interpret images, skip this step: steps 1–4 are your ceiling, and flag the document "not visually verified" at handoff). `officecli view "$FILE" screenshot --grid auto -o /tmp/sheet.png`, then Read it. `--grid auto` tiles **every page** into one image (auto column count; `--grid 4` to force) — you *see* pagination, blank pages, heading rhythm, lopsided margins, and TOC/cover placement, not just the DOM. Windows+Word renders each page through real Word; elsewhere HTML. No headless browser (needs Chrome/Edge/Chromium/Firefox or `playwright`)? Fall back to `view html` and flag cross-page breaks / alignment / rhythm as "not visually verified". Thumbnails only **locate**: confirm any fine call (column alignment, line spacing, indents, dark-on-dark, caption placement) on the suspect page at full resolution with `screenshot --page N` (no `--grid`; real Word on Windows). "validate pass" is not delivery; "looks like a real document" is. +6. If anything failed, fix, then **rerun the full cycle** — one fix commonly creates another problem. ### Delivery Gate (run before handing off — any failure = REJECT, do NOT deliver) -Copy-paste this block, set `FILE`, and refuse to declare done until every gate prints its OK line. `REJECT` aborts with exit 1 — the file is NOT deliverable. +Copy-paste, set `FILE`, and refuse to declare done until every gate prints OK. ```bash FILE="your-file.docx" -# Gate 1 — schema. Any error = REJECT. +# Gate 1 — schema. officecli close "$FILE" 2>/dev/null officecli validate "$FILE" | grep -q "no errors found" || { echo "REJECT Gate 1: validate failed"; exit 1; } echo "Gate 1 OK" -# Gate 2 — token leak (shell-escape / template tokens / TOC placeholder / literal \$ \t \n). -# COUNT-then-if pattern: grep -c never false-PASSes. +# Gate 2 — token leak (shell-escape / template tokens / TOC placeholder / literal \$ \t \n). grep -c never false-PASSes. LEAK=$(officecli view "$FILE" text | grep -cE '(\$[A-Za-z_]+\$|\{\{[^}]+\}\}||xxxx|lorem|Update field to see|\\[\$tn])') -[ "$LEAK" -eq 0 ] && echo "Gate 2 OK" || { echo "REJECT Gate 2: $LEAK token-leak line(s)"; officecli view "$FILE" text | grep -nE '(\$[A-Za-z_]+\$|\{\{[^}]+\}\}||xxxx|lorem|Update field to see|\\[\$tn])'; exit 1; } +[ "$LEAK" -eq 0 ] && echo "Gate 2 OK" || { echo "REJECT Gate 2: $LEAK leak line(s)"; officecli view "$FILE" text | grep -nE '(\$[A-Za-z_]+\$|\{\{[^}]+\}\}||xxxx|lorem|Update field to see|\\[\$tn])'; exit 1; } # Gate 3 — live PAGE field exists when a footer is expected. FLD=$(officecli query "$FILE" 'field[fieldType=page]' --json | jq '.data.results | length') @@ -543,134 +486,96 @@ FLD=$(officecli query "$FILE" 'field[fieldType=page]' --json | jq '.data.results echo "Delivery Gate PASS" ``` -Every gate must print its OK line before you declare the file delivered. - ### Field / cached-value spot-check -TOC, PAGE, NUMPAGES, MERGEFIELD are all fields with **cached values** that may be stale or empty at write time. Confirm existence by structure, not by text. - -- [ ] Footer PAGE field: `get /footer[N] --depth 3` lists the runs that carry the `fldChar begin` / `instrText` / `fldChar separate` / cached value / `fldChar end` chain — expect ≥ 5 runs for a single PAGE, ≥ 11 for composite "Page X of Y". For the underlying `` XML, use `officecli raw "$FILE" "/footer[1]" | grep -o fldChar | wc -l` (NOT `grep -c` — single-line XML returns 1, false-PASS risk), or run `officecli query "$FILE" 'field[fieldType=page]'` for a semantic match. If you see a single run with text `"Page"`, the field is missing — re-add with `--prop field=page`. -- [ ] TOC: `get /body/toc[1] --depth 2` must show field structure. In some viewers the TOC shows `1 1 1 1` for page numbers or the literal `Update field to see table of contents` until recalculated (see TOC delivery step). -- [ ] MERGEFIELD: `query 'field[fieldType=mergefield]'` — one entry per template slot. No literal `{{name}}` text elsewhere. -- [ ] SEQ / PAGEREF (if your document uses them via raw-set): confirm each `` chain exists by `raw`-inspecting the `document.xml`. - -**Cross-viewer caveat on PAGE fields**: some viewers render PAGE field text as the literal word "Page" (no number) until the reader recalculates. This is a [RENDERER-BUG], not a skill defect. Judge by whether `fldChar` children exist, not by whether the visible text shows a digit. +Fields carry cached values that may be stale or empty at write time — confirm existence by **structure, not text**. -### Fresh eyes - -When you finish a document, open it fresh. Read `view text` / HTML preview top-to-bottom as if you are a new reviewer — look for typos, formatting inconsistencies, missing headings, orphaned paragraphs, placeholder text that looks like content. +- **Footer PAGE:** `get /footer[N] --depth 3` lists the begin / instrText / separate / cached / end run chain — ≥ 5 runs for one PAGE, ≥ 11 for composite "Page X of Y". A single run with text `"Page"` = field missing; re-add with `--prop field=page`. +- **TOC:** `get /toc[1] --depth 2` shows field structure. Page numbers may read `1 1 1 1` or `Update field to see…` until recalculated (see TOC delivery step). +- **MERGEFIELD:** `query 'field[fieldType=mergefield]'` — one per slot, no literal `{{name}}` elsewhere. ### Honest limit -`officecli validate` catches schema errors, not design errors. A document can pass `validate` with: -- wrong heading hierarchy (H1 → H3) -- wrong font sizes that "look like" Heading 1 but are literal 14pt on Normal -- placeholder tokens rendered as body text -- an empty first-page footer attached to a document that has no cover - -The checklist above — especially the HTML-preview visual pass and the field structure check — is how you catch what validation can't. +`validate` catches schema errors, not design errors — a document can pass it with wrong heading hierarchy, fake-Heading-1 sizes, placeholder tokens as body text, or an empty first-page footer on a coverless document. The contact-sheet visual pass (`screenshot --grid`) and the field-structure check are how you catch what validation can't. ### QA display notes (don't chase these) -- `view text` shows `"1."` for every numbered list item regardless of rendered number. The actual rendered output increments correctly. Not a defect. -- `view issues` flags "body paragraph missing first-line indent" on cover-page paragraphs, centered headings, list items, bibliography entries, callout boxes. First-line indent is only required for APA/academic body text. On professional documents (block style) these warnings are expected. +- `view text` shows `"1."` for every numbered list item regardless of rendered number — actual output increments correctly. +- `view issues` flags "body paragraph missing first-line indent" on cover paragraphs, centered headings, list items, bibliography entries — first-line indent is only required for APA/academic body text; on block-style professional documents these are expected. ## Known Issues & Pitfalls -Organized by source. When something "looks broken", attribute it before chasing it: - -- **[AGENT-ERROR]** — the document itself is wrong (structure / data / formatting). Fix the document. -- **[RENDERER-BUG]** — the document is correct; a specific viewer renders it differently. Don't chase. -- **[SKILL gap]** — the skill didn't teach the relevant rule. Open an issue against the skill. - -### Schema-invalid-on-emit — disabled APIs + working forms - -These props exit 0 at write time but produce XML that fails `validate` on close. Use the working form on the right. - -| Disabled (causes schema error) | Working form | Where it hurts | -|---|---|---| -| `--prop shd.fill=XXXXXX` on paragraph | `--prop shd="clear;XXXXXX"` (canonical) — or for table cells, `--prop fill=XXXXXX` on the cell | `` emitted without required `w:val`; affects every paragraph-shaded row / cover band / callout | -| `--prop ind.firstLine=360` (dotted) | `--prop firstLineIndent=360` (canonical) | Dotted form emits `` AFTER `` in `pPr` — ordering violation. Breaks every indented body paragraph in APA-style academic writing | -| `--prop border.bottom=...` on a table cell (`tc`) | `--prop pbdr.bottom="single;6;1F4E79;0"` on the cell's inner paragraph | `` placed wrong inside ``. See C-D-4 | - -**Before shipping, confirm these props are not in your build pipeline**: +When something "looks broken", attribute it before chasing: **[AGENT-ERROR]** the document is wrong (fix it) · **[RENDERER-BUG]** the document is correct, a viewer renders it differently (don't chase) · **[SKILL gap]** the skill didn't teach the rule (file an issue). -```bash -# In the command log / batch JSON, grep for the three failing forms -grep -nE '(shd\.fill|ind\.firstLine|border\.(top|bottom|left|right)[^a-z])' commands.log -# Any hit = rewrite the command with the working form on the right. -``` - -`raw-set` escape hatch if neither form fits: inject `` or reorder `` / `` after emit. Post-patching with a Python `zipfile` + XML edit is acceptable. - -### Shell escape — three layers to keep separate - -The CLI does not interpret `\$`, `\t`, `\n`. They land in your document as literal characters. - -1. **Shell level.** `$` in a prop value → single-quote the whole value: `--prop text='$50M'`. Unescaped `$50M` gets stripped to `M` by the shell. -2. **JSON level (batch).** Standard JSON escapes — `"\n"`, `"\t"`, `"\""`. A real newline inside a cell/paragraph goes via `"\n"` in JSON (CLI passes the real `\n` char to Word). Writing `\n` (two characters) in a shell-quoted `--prop text=` is a bug — Word shows `\n` text. -3. **Word level.** Word's own literal `\n` is not a newline — it is two characters. If you need a soft line break inside a run, use `` via `raw-set`, or split into separate paragraphs. +### Renderer quirks (cross-viewer, [RENDERER-BUG] — don't chase) -If in doubt, `view text` after writing and compare character-for-character. +Before calling a color/field/chart broken, open the file in the user's target viewer; if it looks correct there it's a viewer quirk. -### CLI bug backlog (short workarounds) +- **PAGE field may render literal "Page"** (no number) until recalculated — judge by `fldChar` presence, not the digit. +- **TOC cached page numbers may read "1 1 1 1"** until F9. +- **Pie / doughnut fill may collapse to one color** in some viewers (column/bar render fine). +- **Form-control checkboxes may render double-boxed**; **OMML equation baselines** may shift across viewers (XML identical). -Skill-layer workarounds; full CLI fixes pending. C-D-3 and C-D-4 are the two you will actually hit on a report build — the rest cluster around academic / reviewed-document territory (see Advanced / specialty topics). +### Common pitfalls -- **C-D-3 `add picture --prop alt=` silent drop.** Add the picture first, then `set` the `alt` on the resulting run — two commands. Confirm with `query 'image:no-alt'`. -- **C-D-4 cell-level `border.bottom` / per-side `border.*` schema error.** `` is placed in the wrong position inside `` and `validate` fails. Workaround: use paragraph-level `pbdr.*` on the cell's inner paragraph (`--prop pbdr.bottom="single;6;1F4E79;0"`), or fix structure with `raw-set`. +| Pitfall | Correct approach | +|---|---| +| `--index` vs `[N]` | `--index` is 0-based; `[N]` paths are 1-based | +| Multiple `add --index N` with the same N | Each insert shifts later content down; reusing N puts later items BEFORE earlier ones — insert in reverse order, or `move --after/--before` anchored on `paraId` | +| Unquoted `[N]` in zsh/bash | Quote every path: `"/body/p[1]"` | +| `[last]` as predicate | Must be `[last()]` with parens | +| Raw twips in spacing | Use unit-qualified values: `12pt`, `0.5cm`, `1.5x` | +| Empty paragraphs for spacing | Use `spaceBefore` / `spaceAfter` | +| Row-level `set` for cell formatting | Row `set` only supports `height`, `header`, `c1..cN` text; format goes on the cell paragraph / run | +| `listStyle` on a run | It's a paragraph property | +| Indent via leading spaces | `indent=720` / `firstLineIndent=360` / `hangingIndent=720` (dotted `ind.left` / `ind.firstLine` also work) | +| Cover page-number suppression via `set differentFirstPage=true` | UNSUPPORTED — add a first-type footer: `--type footer --prop type=first --prop text=""` | +| `--type pagebreak` OR `pageBreakBefore` alone not breaking | Apply BOTH (see Forcing page breaks) | +| Multiple bullet paragraphs in one cell | `c1="a\nb"` makes a `` line break (one paragraph); for separate bullet paragraphs use recipe (e) | +| `raw-set` when dotted-attr would work | Prefer L2 dotted-attr over L3 raw-set | +| Next paragraph inherits the previous Heading style | Set explicit `--prop style=Normal` on the following paragraph | +| Modifying a file open in Word | Close it in Word first | +| Echo into batch breaks on `$`/`'` | Heredoc with single-quoted delimiter: `cat <<'EOF' \| officecli batch …` | -Specialty-only (skip unless you hit them): +## Raw-set XML appendix (L3 patterns) -- **C-D-1** `query trackedchange` returns empty → use `query ins` + `query del`. -- **C-D-2** `set /comments/comment[N] --prop done=true` silent no-op → `raw-set` into `commentsExtended.xml`. -- **C-D-5** Comment `--prop parentId=N` UNSUPPORTED → sibling comment, or `raw-set` ``. -- **C-D-6** `add num --prop abstractNumId=N` may silent-bind wrong when built-ins exist → `get /numbering --depth 2` after add, correct with `set /numbering/num[N] --prop abstractNumId=...`. -- **C-D-7** Watermark `opacity` asymmetric — `add` rejects, `set` accepts → two-step (see Advanced topics). +`raw-set` injects literal OOXML — no schema protection. Element order in ``: `pStyle`, `numPr`, `spacing`, `ind`, `jc`, `rPr` (last). Smart quotes as entities (`‘`/`’`/`“`/`”`). Add `xml:space="preserve"` to any `` with leading/trailing spaces. RSIDs are 8-digit hex. Use "Claude" as the author for tracked changes/comments unless the user names another. -### Renderer quirks (cross-viewer) +**Tracked-change insertion / deletion** — prefer the high-level `--prop revision.type=ins|del` on a run; raw-set only for what the typed path can't express (rejecting/restoring another author's change, below). Replace the whole ``, never inject tags inside a run; copy the original `` into both to preserve formatting. Inside `` use `` (and `` for instructions): -`officecli view html` is the right tool for structural QA (overflow, placeholder leakage, hierarchy, layout) — Read the returned HTML path. Some features vary by the viewer the end user opens the file in. Observed divergences, all [RENDERER-BUG]: +```xml +The term is +30 +60 + days. +``` -- **PAGE field may render as literal "Page" (no number)** in some viewers until the reader recalculates. Judge field presence by `get --depth 3` finding ``, not by eyeballing a digit. -- **TOC cached page numbers may read "1 1 1 1"** until a human opens the file and recalculates (F9 in Word). -- **Pie / doughnut chart fill may collapse to one color** in some viewers (column / bar render fine). Switch to column / bar or accept the render caveat. -- **Form-control checkboxes may render double-boxed** in some viewers. -- **OMML equation baselines** may shift across viewers; the underlying XML is identical. +When deleting ALL content of a paragraph/list item, also mark the paragraph mark deleted (`` inside ``) — otherwise accepting changes leaves an empty paragraph. To **reject another author's insertion**, nest your `` inside their ``; to **restore their deletion**, add a `` after it (don't modify theirs). -Before calling a color, field, or chart broken, open the file in the user's target viewer. If it looks correct there, it is a viewer quirk — do not chase. +**Internal hyperlink to a bookmark** (prefer the high-level `--prop anchor=` path; raw-set only for custom run styling the command can't express): -### `validate` caveats +```xml +See Chapter 1 +``` -- **Do NOT run `validate` while a resident is open.** `view --open` and `validate` briefly conflict on the file; `validate` reports spurious `drawing` / `tableParts` errors. Always `officecli close ` first. -- **`validate` does not check design.** Heading hierarchy, typography, placeholder leakage, empty covers pass validate but fail delivery. See QA section. +**Composite field in one run** (e.g. two fields the single-command path can't compose) — the `fldChar begin / instrText / separate / value / end` chain: -### Batch / resident mode +```xml + + PAGE + +1 + +``` -- **Batch + resident occasional failure** (1-in-10 to 1-in-15). Symptom: "Failed to send to resident". Retry the command, or close/reopen the file. Split large batch arrays into ≤ 12-op chunks for reliability. -- **Echo into batch breaks on `$` / `'`.** Use heredoc: `cat <<'EOF' | officecli batch doc.docx` — single-quoted delimiter prevents shell expansion. -- **Table `--index` positioning unreliable.** `--index N` on table add may be ignored. Add content in the intended order; or remove/re-add surrounding elements. +**Comment markers** are siblings of ``, NEVER inside one (reply threading and resolved-state are high-level — `--prop parentId=`/`done=`, see Comments above): -### Common pitfalls +```xml +annotated text + +``` -| Pitfall | Correct approach | -|---|---| -| `--index` vs `[N]` | `--index` is 0-based (array convention); `[N]` paths are 1-based (XPath) | -| Multiple `add --index N` with the same N | Each insert shifts later content down; reusing the same N puts subsequent items BEFORE earlier ones. Insert in reverse order, or use `move --after/--before` anchored on `paraId` | -| Unquoted `[N]` in zsh/bash | Quote every path: `"/body/p[1]"` | -| `[last]` as predicate | Must be `[last()]` with parens. `/body/tbl[last()]/tr[1]` valid; `[last]` throws "Malformed path segment" | -| Raw twips in spacing | Use unit-qualified values: `12pt`, `0.5cm`, `1.5x` | -| Empty paragraphs for spacing | Use `spaceBefore` / `spaceAfter` on paragraphs | -| Row-level `set` for formatting | Row `set` only supports `height`, `header`, `c1..cN` text. Format goes on cell paragraph / run | -| `listStyle` on a run | `listStyle` is a paragraph property | -| Indent via leading spaces | Use `--prop indent=720` (twips) for left indent, `--prop firstLineIndent=360` for first line, `--prop hangingIndent=720` for hanging. Leading spaces fire `view issues`. Dotted `ind.left` works; dotted `ind.firstLine` does NOT — use canonical names | -| Cover page number suppression via `set differentFirstPage=true` | UNSUPPORTED. Add a first-type footer instead: `--type footer --prop type=first --prop text=""` | -| TOC `--prop pagenumbers=true` | UNSUPPORTED. Page numbers render automatically | -| `--type pagebreak` OR `pageBreakBefore` alone not breaking across viewers | Apply BOTH: `add /body --type pagebreak` before the heading AND `set /body/p[N+1] --prop pageBreakBefore=true`. Some viewers heuristically drop either one; the pair is the only reliable recipe (see Forcing page breaks) | -| Row-level `c1="line1\nline2"` for multi-line cell | `\n` lands as a literal. Use recipe (e): seed one bullet, then `add paragraph` to the cell for each subsequent line | -| Raw-set when dotted-attr would work | Prefer L2 (`pbdr.top=`, `ind.left=`, `font.size=`) over L3 raw-set. `shd.fill=` and `ind.firstLine=` are NOT safe — use canonical `shd=clear;XXXXXX` and `firstLineIndent=N` | -| Next paragraph picks up the previous Heading style | If a Heading2 `Next body line` sneaks through, set explicit `--prop style=Normal` on the following paragraph | -| Modifying a file open in Word | Close it in Word first | +Force field recalc on open with `officecli set "$FILE" /settings --prop updateFields=true` (writes ``; covers the layout-dependent fields PAGE / PAGEREF / NUMPAGES / TOC page numbers — no raw-set needed). For SEQ numbering, prefer `set / --prop recalcFields=seq`, which writes correct cached values now without waiting on Word. ### Help pointer diff --git a/crates/aionui-app/assets/builtin-skills/officecli-pptx/SKILL.md b/crates/aionui-app/assets/builtin-skills/officecli-pptx/SKILL.md index 5085e8a46..ef5d4c587 100644 --- a/crates/aionui-app/assets/builtin-skills/officecli-pptx/SKILL.md +++ b/crates/aionui-app/assets/builtin-skills/officecli-pptx/SKILL.md @@ -45,7 +45,7 @@ Help reflects the installed CLI version. When skill and help disagree, **help is ## Shell & Execution Discipline -**Shell quoting (zsh / bash).** ALWAYS quote element paths (`"/slide[1]/..."`) — zsh globs unquoted `[1]` to `no matches found`. Escapes happen at two layers; the CLI handles one for you: +**Shell quoting (zsh / bash).** ALWAYS quote element paths (`"/slide[1]/..."`) — zsh globs unquoted `[1]` to `no matches found`. Escaping happens at three layers — keep them separate (the CLI handles the second for you): 1. **Shell.** `$` in a value still belongs to the shell — single-quote the whole value: `--prop text='$15M'`. Double-quoted `"$15M"` gets expanded to `M`. The CLI does NOT unescape `\$` for you. 2. **CLI (`text=`).** The two-char escapes `\n` and `\t` ARE interpreted, consistently across pptx / docx / xlsx — `\n` is a line / paragraph break, `\t` is a tab. To produce a literal backslash-n in text, double it (`\\n`); this is rarely what you want. @@ -76,12 +76,16 @@ Rule of thumb: **min shape height ≈ font_pt × 0.05cm**. An 18pt sublabel in a Title must be **≥ 2× body size** (36pt over 20pt works; 28pt over 20pt looks timid). Four legit exceptions to body ≥ 18pt: chart axis labels, legends, footer / page number, and ≤ 5-word KPI sublabels (e.g. "Active users"). Descriptive sentences must be ≥ 18pt. Left-align body; center only titles and hero numbers. If "the cards won't fit", drop cards instead of shrinking font. -**Two fonts max, one palette.** One heading font + one body font (e.g. Georgia + Calibri). One dominant brand color (60–70% weight) + one supporting + one accent. Never mix 4+ colors in body content. +**Two fonts max, one palette.** One heading font + one body font (e.g. Georgia + Calibri) — a third *display* face is fine only for big numerals or the cover title, as long as that heading+body pair stays intact. One dominant brand color (60–70% weight) + one supporting + one accent. Never mix 4+ colors in body content. **The palettes and font pairings in Design Principles are a floor, not a menu:** if the user gave brand colors/fonts or an existing template, match those first; otherwise the named sets are calibrated seeds — blend or diverge freely, as long as the result isn't *worse* than them and still clears the contrast floor. -**Every slide carries a non-text visual.** Shape, chart, icon, gradient band. A bullet-only deck is interchangeable with a Word doc. Exceptions: literal quote slides, code blocks, a single summary-table slide. +**Every slide carries a non-text visual — one that informs.** Shape, chart, icon, gradient band that carries meaning, not decoration. A bullet-only deck is interchangeable with a Word doc. Exceptions: literal quote slides, code blocks, a single summary-table slide. + +**Less is more — every element earns its place.** The visual rule above guards against bullet-walls; it is not licence to clutter. Don't pad with decorative stats, icons, or filler sections that don't inform ("data slop"). If a slide feels empty, fix it with layout and whitespace, not invented content — cut scope rather than bulk it up, and flag a larger addition instead of making it unprompted. **Speaker notes on every content slide.** `--type notes --prop text="..."`. The speaker needs a script; the audience shouldn't read the slide verbatim. +**Copy reads human, not AI.** Titles orient on content, not punchline. No "It's not X. It's Y.", no manufactured tension, no faux-insight ("The magic moment"), no one-word drama ("Momentum."). Cut hype adjectives (seamless, robust, game-changing) — let the number carry it. + **Preserve existing templates.** When a file already has a theme and masters, match them. Existing conventions override these guidelines. ### Visual delivery floor (applies to EVERY deck) @@ -89,23 +93,12 @@ Title must be **≥ 2× body size** (36pt over 20pt works; 28pt over 20pt looks Before declaring done, the per-slide render (see QA) MUST satisfy: - **No placeholder tokens rendered as content.** `{{name}}`, `$fy$24`, ``, `lorem`, `xxxx`, empty `()`/`[]` in chart titles never appear. -- **No overflow past slide edges.** For 16:9 (33.87 × 19.05cm), every shape satisfies `x + width ≤ 33.87cm` AND `y + height ≤ 19.05cm`. `get` and check — don't eyeball. -- **No text overflow inside shapes.** A 72pt KPI in a 4cm-tall box clips. Shrink the number, enlarge the box, or shorten the text — never trim content to fit. -- **Cover slide is content-rich.** Title + subtitle + presenter/client block + date + a brand band or key-takeaway strap. A cover with 80% whitespace reads as a stub. -- **Contrast.** On fills with brightness < 30% (`1E2761`, `36454F`, `000000`, deep forest / berry / cherry), every run of body text, card body, chart series fill, and icon color must be `FFFFFF` or brightness > 80%. Mid-gray (`6B7B8D` ≈ 44%) reads fine on a laptop and vanishes on projection. Verify via `view html` after the dark-fill pass. -- **No `\$` literals in slide text.** If `view text` shows a literal `\$`, the shell didn't unescape it (the CLI does NOT interpret `\$`). Single-quote the value: `--prop text='$15M'`. Note: `\n` and `\t` ARE interpreted as a real paragraph break / tab; seeing those as literals means the value was double-escaped (`\\n`). +- **No overflow off-edge, no clipped text in shapes.** `view issues` flags both (`shape_off_slide` + a text-fit hint). To fix a clip: grow the box or shorten the value — never trim content to fit. +- **Cover carries its orienting elements.** Title + subtitle + presenter/client + date + a brand band or key-takeaway strap — a title-only cover reads as a stub. Generous whitespace around them is still right; rich ≠ crowded. +- **Contrast.** `view issues` auto-flags the common case — opaque dark text on a shape's own dark fill (`low_contrast`). It can't see the rest: icon / chart-series fills, scheme/inherited colors, or text over a *separate* background shape. So on any fill with brightness < 30% (`1E2761`, `36454F`, deep forest / berry / cherry), still confirm every body run, card body, chart series, and icon is `FFFFFF` or brightness > 80% — mid-gray (`6B7B8D` ≈ 44%) reads on a laptop and vanishes on projection. Spot-check via `view html` after the dark-fill pass. If any fails, STOP and fix before declaring done. -### KPI fit math - -**KPI text must fit the card — pre-compute, don't eyeball.** In a 7cm-wide card at 60pt Georgia bold, values with `$` and `.` (wide glyphs) wrap at 4 characters. `$9.4M` breaks the card; use `$9M` + "USD millions" sublabel, or move to the 3-card 9.78cm layout. Upper bound: `max_size_pt ≈ card_width_cm × denom`, where denom = 10 for 1–2 chars, 7 for 3–4 chars, 5 for 5+ chars. - -### `layout=blank` and alt text - -- **`layout=blank` is the default for custom designs.** Titles become plain `shape` elements, not placeholders. `view outline` / `view issues` reporting `(untitled)` / `Slide has no title` is **expected**, not a defect. Use `layout=title` + `placeholder[title]` only when screen-reader outline compatibility matters. -- **Alt text verification.** `view stats "Pictures without alt text: 0"` is a false-positive zero (alt auto-fills to filename) — verify via `view annotated`. - ## Design Principles A deck is not a document. The audience has 3 seconds to get each slide. Before adding anything, ask: "If the audience reads only the biggest element and glances once, do they get the point?" If they have to read the bullets, the biggest element is wrong. @@ -115,13 +108,14 @@ A deck is not a document. The audience has 3 seconds to get each slide. Before a Standard widescreen is **33.87 × 19.05cm**. Treat it as a 12-column grid internally: - **Edge margin ≥ 1.27cm** (0.5") on all sides. -- **Inter-block gap ≥ 0.76cm** (0.3") between cards / columns / rows. +- **Inter-block gap ≥ 0.76cm** (0.3") between cards / columns / rows — pick one value (0.76 or 1.27cm) and use it everywhere; mixed gaps look unfinished. - **≥ 20% negative space per slide.** Filling every pixel reads as amateur. +- **Compose, don't web-center.** Whitespace is structural: a slide top-weighted with open space in the lower third is correct composition, not an empty defect. Intentional asymmetry (content left, breathing room right) reads more designed than centering everything — don't fill a gap just because it's there. - For card grids: `usable = 33.87 − 2·margin − (N−1)·gap`, then `col_width = usable / N`. Don't hand-pick x coordinates. ### Font pairings -Two fonts max — one for headings, one for body. Pair by document register, not by novelty. "Best For" is a prompt, not a decree; if the topic matches a row, use it as the default and move on. +Pair by document register, not by novelty. "Best For" is a prompt, not a decree; a pairing outside this table is fine if it fits — these 8 are seeds, not the set. | Header | Body | Best For | |---|---|---| @@ -134,11 +128,11 @@ Two fonts max — one for headings, one for body. Pair by document register, not | Palatino | Garamond | Elegant editorial, luxury, nonprofit | | Consolas | Calibri | Developer tools, technical / engineering | -Set both fonts explicitly on every shape (`--prop font=Georgia` on title shapes, `--prop font=Calibri` on body shapes) — theme-default inheritance drifts between masters. +Set both fonts explicitly on every shape (`--prop font=Georgia` on titles, `--prop font=Calibri` on body), not via theme inheritance. ### Color and contrast -One dominant color does 60–70% of visual weight, two supporting tones, one accent used sparingly. Never use 4+ colors in body content. Columns are: **Primary** (dominant — the one color you see first), **Secondary** (the supporting tone), **Accent** (sparing, one-hit emphasis), **Text** (body on light fills), **Muted** (captions / axis labels / footer). +The columns: **Primary** (dominant — 60–70% of weight, the color you see first), **Secondary** (supporting tone), **Accent** (sparing, one-hit emphasis), **Text** (body on light fills), **Muted** (captions / axis labels / footer). | Theme | Primary | Secondary | Accent | Text | Muted | |---|---|---|---|---|---| @@ -172,17 +166,19 @@ Wrong chart type kills the 3-second test: Rule of thumb: if > 3 series and > 8 categories, split into two charts or switch to a table. -### Animation restraint +### Animation -Each animation is a cognitive interrupt. Limits: -- **≤ 1 animation per slide**, duration **≤ 600ms**. -- Use only `fade`, `appear`, or a single `zoom-entrance` on a hero slide. -- Never: `bounce`, `swivel`, `fly-from-edge`, `spin`, multi-object choreography. -- Animation is runtime-only — verify in a live presentation viewer. +Use as much or as little as the brand and content call for — a formal finance deck trends to near-zero, a product launch can be more expressive. Animation is a tool, not décor. Three floors keep it from hurting the deck (none caps how much you use): + +- **Purposeful** — each one reveals or emphasizes (progressive bullet reveal, a build-up chart), never decorates. If it doesn't aid comprehension, cut it. +- **Degrades gracefully** — pptx animation renders inconsistently across viewers (Keynote / Slides / web / mobile) and may not play at all, so every slide must read correctly as a *static* frame. Never hide essential content behind a reveal. +- **Verify live** — animation is runtime-only; `view html` and screenshots can't see it, so confirm in a real presentation viewer before shipping. + +Taste steer (not a ban): `fade` / `appear` / a single `zoom-entrance` with snappy durations (~hundreds of ms) fit most decks; `bounce` / `swivel` / `spin` / `fly-from-edge` / dense multi-object choreography usually read amateur — reach for them only when the brand is deliberately playful. ### Layout patterns & data display -Vary layout across slides — repeating the same pattern makes every slide feel identical. Pick one per slide from these building blocks: +Vary layout across slides — repeating the same pattern makes every slide feel identical. These are common building blocks, not the full set — pick one per slide, or build a layout outside the table when the content calls for it: | Pattern | When to use | Key measurement | |---|---|---| @@ -193,35 +189,41 @@ Vary layout across slides — repeating the same pattern makes every slide feel | **Large stat callout** (60-72pt number + ≤5-word sublabel below) | Single KPI, milestone, market size | Use shape, NOT a chart; sublabel 14-16pt muted | **Data display quick rules:** -- One big number reads faster than a chart — use a `shape` with 60-72pt bold for a single KPI. - Comparison columns (before/after, A vs B) beat a table for 2-3 options. - Timelines and process flows: numbered step shapes + connectors, not a bullet list. +### Image treatment (only when a slide uses a photo / screenshot / logo) + +**Read the image first** (open the file) and choose treatment from what you see — don't place blind from a filename. + +- **Full-bleed photo** → size to COVER the region (crop edges), no border. +- **Screenshot / diagram / logo** → size to FIT (never crop content). A transparent or fit image sits on a contrasting fill — drop a colored rectangle behind it, don't let it float on white. +- **Text over a photo** → never raw on the image. Put it on a card, or lay a protection scrim between image and text (a dark rectangle at ~50–60% opacity, or a gradient fading from the text edge). +- Never stretch (distort the aspect ratio); don't overlay text on a busy screenshot. +- Prefer user-provided images / brand assets; no emoji or self-drawn art unless asked. + ### Visual motif commitment -Pick ONE distinctive element (rounded image frames, section numbers in filled circles, single-side border band, diagonal accent strips) and carry it to every slide. Declare it in your build plan first: `## Motif: numbered circles in brand color`. +Pick ONE distinctive element (rounded image frames, section numbers in filled circles, single-side border band, diagonal accent strips) and carry it to every slide — commit across the whole deck; styling one slide and leaving the rest plain reads as abandoned. A secondary motif is fine only if it doesn't compete with the primary. Declare it in your build plan first: `## Motif: numbered circles in brand color`. -### What to avoid (common design mistakes) +### Visual AI-tells to avoid -These are the patterns that make a deck look AI-generated or amateur: +- **No decorative underline under slide titles.** A stripe / rule below a heading is the single most common AI-slide tell — use whitespace or a background-color change instead. +- **No rounded-corner card with a colored left-border accent stripe.** The other classic AI-slide tell — use a solid fill, a top accent band, or whitespace separation instead. +- **No emoji as iconography** unless the brand uses them — use a shape or a real icon asset. -- **NEVER place a decorative line under slide titles.** Underline stripes below headings are the single most common AI-slide tell. Use whitespace or background color change instead. -- **Don't repeat the same layout across consecutive slides.** Alternate between two-column, callout, grid, and half-bleed patterns. Same layout = same visual rhythm = audience tunes out. -- **Don't center body text.** Left-align all paragraphs, lists, card descriptions. Center only slide titles and hero numbers. -- **Don't default to blue** because it feels "professional." Pick the palette that fits the topic — finance reads navy, sustainability reads forest, energy reads coral. -- **Don't use inconsistent spacing.** Choose either 0.76cm or 1.27cm as your inter-block gap and use it everywhere. Mixed gaps look unfinished. -- **Don't create text-only slides.** If a slide has only a title and bullets, add a supporting shape, chart, icon, or image. A purely textual slide is a Word paragraph. -- **Don't style one slide and leave the rest plain.** Commit fully or keep it simple throughout — partial styling reads as abandoned. +Copy-level tells live in "Copy reads human". ## Common Workflow 1. **Open/close mode.** Always `officecli open ` at start + `officecli close ` at end. Resident is the default, not an optimization. Use `batch` for repetitive shape grids. 2. **Orient.** New deck: `officecli create "$FILE"`. Existing: `officecli view "$FILE" outline` first. Never edit blind. -3. **Build in display order.** Add slides in audience-view order: cover → agenda → section-1 divider → section-1 content → section-2 divider → … → closing. `--index` on slide add works, but linear append keeps the build script readable and avoids index-arithmetic bugs. **Before final delivery, confirm slide count + narrative arc match your build plan.** Gate 3's order-sanity check catches cases where the cover ends up as slide 11 of 14 instead of slide 1. -4. **Incremental per slide.** Create slide + background, then title, then supporting shapes / charts / connectors. Always `layout=blank` for custom designs. After each structural op, `get /slide[N] --depth 1` to confirm shape IDs. -5. **Format to spec.** Per the Requirements table; formatting is deliverable, not polish. -6. **Close + verify.** `officecli close` writes the ZIP. Always open in the target presentation viewer before shipping — chart colors, animations, fonts, and zoom are runtime features `view html` can't render. Full verification in QA below. -7. **QA — assume there are problems.** Fix-and-verify until a cycle finds zero new issues. +3. **Title sequence first (plan, don't build yet).** Before creating any slide or shape, write out the full ordered list of slide titles. If someone reading ONLY the titles can't follow the argument, fix the arc now — cheaper in a list than after 14 slides. Pick ONE title grammar — all topic noun-phrases or all action statements, never a mix — and hold it throughout (see "Copy reads human"). +4. **Build in display order.** Add slides in audience-view order: cover → agenda → section-1 divider → section-1 content → section-2 divider → … → closing. `--index` on slide add works, but linear append keeps the build script readable and avoids index-arithmetic bugs. **Before final delivery, confirm slide count + narrative arc match your build plan.** Gate 3's order-sanity check catches cases where the cover ends up as slide 11 of 14 instead of slide 1. +5. **Incremental per slide.** Create slide + background, then title, then supporting shapes / charts / connectors. Always `layout=blank` for custom designs. After each structural op, `get /slide[N] --depth 1` to confirm shape IDs. +6. **Format to spec.** Per the Requirements table; formatting is deliverable, not polish. +7. **Close + verify.** `officecli close` writes the ZIP. Always open in the target presentation viewer before shipping — chart colors, animations, fonts, and zoom are runtime features `view html` can't render. Full verification in QA below. +8. **QA — assume there are problems.** Fix-and-verify until a cycle finds zero new issues. ## Quick Start @@ -261,9 +263,9 @@ Start wide, then narrow. `outline` first, `view text` / `get` / `query` once you ```bash officecli view "$FILE" outline # slide count + titles officecli view "$FILE" annotated # complete per-slide breakdown with fonts, sizes, tables, charts -officecli view "$FILE" text --start 1 --end 5 # text dump (does NOT extract table cells — use get) +officecli view "$FILE" text --start 1 --end 5 # text dump (includes table cell text) officecli view "$FILE" issues # empty slides, overflow hints -officecli view "$FILE" stats # counts + missing alt (false-positive zero — verify via view annotated) +officecli view "$FILE" stats # counts + totals (incl. pictures missing alt) ``` **Inspect one element.** XPath-style paths, 1-based. ALWAYS quote. Prefer `@name=` / `@id=` selectors over positional `[N]` (stable across reorderings). `[last()]` works. Add `--json` for machine output. @@ -292,6 +294,9 @@ officecli view "$FILE" html # prints an HTML preview path; Read i officecli view "$FILE" svg --start 3 --end 3 # single slide SVG (charts + gradients do NOT render in SVG) ``` +**Reading the output — an expected non-defect:** +- **`layout=blank` has no title placeholder.** Titles are plain `shape` elements, so `view outline` reporting `(untitled)` is **expected**, not a defect. Use `layout=title` + `placeholder[title]` only when screen-reader outline compatibility matters. + ## Creating & Editing Verbs: `add` / `set` / `remove` / `move` / `swap` / `batch` / `raw-set`. Ninety percent of a deck is slides, shapes, text, a few charts, pictures, connectors. @@ -316,26 +321,17 @@ officecli add "$FILE" /slide[2] --type shape --prop name=Title --prop text="Key --prop font=Georgia --prop size=36 --prop bold=true --prop color=1E2761 --prop fill=none ``` -Positioning is explicit — no layout engine, you own the grid math. `--prop preset=` picks geometry (`rect`, `roundRect`, `ellipse`, `triangle`, `arrow`, `star5`, ...); custom `M...Z` paths are not supported — pick a preset. **Name shapes at creation** (`--prop name=HeroTitle`) and address later with `"/slide[N]/shape[@name=HeroTitle]"` — positional `/shape[3]` breaks after any z-order / remove. - -> **Prefer `@name=` over `@id=`.** Names you set yourself survive remove-then-add and z-order ops cleanly. After any structural change, re-`get --depth 1` before referencing positional indexes. +Positioning is explicit — no layout engine, you own the grid math. `--prop preset=` picks geometry (`rect`, `roundRect`, `ellipse`, `triangle`, `arrow`, `star5`, ...); custom `M...Z` paths are not supported — pick a preset. **Name shapes at creation** (`--prop name=HeroTitle`) and address later with `"/slide[N]/shape[@name=HeroTitle]"` — names survive z-order / remove-then-add, whereas positional `/shape[3]` (and even `@id=`) shift. Re-`get --depth 1` after any structural change before using positional indexes. ### Text inside shapes (paragraphs, runs, styling) -A shape has paragraphs (`paragraph[K]`) and runs. For one-line text, `--prop text=` on the shape is enough. Multi-line or mixed styling: +A shape has paragraphs (`paragraph[K]`) and runs (`run[K]`). For one-line text, `--prop text=` on the shape is enough; a `\n` in the text makes a paragraph break, `\t` a tab (see Shell & Execution Discipline; double `\\n` for a literal). `add --type paragraph` takes the same style props as a shape (text, align, bold, italic, size, color, font). For mixed styling *within* a line, append a styled run: ```bash -# add --type paragraph accepts only text + align; styling goes through a follow-up set or an add --type run: -officecli add "$FILE" "/slide[2]/shape[@name=Card1]" --type paragraph --prop text="First bullet" -officecli set "$FILE" "/slide[2]/shape[@name=Card1]/paragraph[1]" --prop bold=true --prop size=20 --prop color=FFFFFF - -# Styled run in one step: officecli add "$FILE" "/slide[2]/shape[@name=Card1]/paragraph[1]" --type run \ --prop text=" (inline detail)" --prop size=14 --prop italic=true --prop color=8899BB ``` -For real newlines inside one run, use a batch heredoc with JSON `"\n"`. Shell-quoted `\n` in `--prop text=` is NOT interpreted. - ### Charts Pick chart type per the Design Principles chart-choice table. Full prop list (chartType enum, `seriesN.*`, `data=`/`categories=`, axis options): `help pptx add chart`. Typical multi-series with brand colors: @@ -348,7 +344,7 @@ officecli add "$FILE" /slide[3] --type chart --prop chartType=column \ --prop x=2cm --prop y=4cm --prop width=20cm --prop height=10cm ``` -Gotchas: (1) series cannot be added after creation — include all series at `add` time or `remove` + re-add. (2) chart titles with `()`, `[]`, `TBD` ship as literal text. (3) some viewers normalize chart colors to theme defaults — verify in the target viewer. +Gotchas: (1) chart titles with `()`, `[]`, `TBD` ship as literal text. (2) some viewers normalize chart colors to theme defaults — verify in the target viewer. Series can be added after creation (`add --type series`). ### Pictures @@ -358,7 +354,7 @@ officecli add "$FILE" /slide[4] --type picture --prop src=hero.jpg \ --prop alt="Product hero, gradient lit from right" ``` -Confirm with `officecli query "$FILE" 'picture:no-alt'` — must be empty before delivery (but remember `view stats` is a false-positive zero because alt auto-fills to filename). +Confirm with `officecli query "$FILE" 'picture:no-alt'` — must be empty before delivery. ### Connectors (LEAD — flowcharts / decision trees first-class) @@ -374,7 +370,7 @@ officecli add "$FILE" /slide[5] --type connector \ ### Animations (LEAD) -One preset per slide, ≤ 600ms. Preset names + duration syntax: `help pptx animation`. +Use per the Animation floors above (purposeful, degrades gracefully, verify live). Preset names + duration syntax: `help pptx animation`. ```bash officecli set "$FILE" "/slide[2]/shape[@name=HeroCard]" --prop animation=fade-entrance-400 @@ -383,7 +379,7 @@ officecli set "$FILE" "/slide[2]/shape[@name=HeroCard]" --prop animation=none ### Hyperlinks, tooltips, slide-jump -`--prop link=slide:N` for slide-jump, `link=https://...` for URL, `--prop tooltip="..."` for hover text. (Help only documents the URL form — `slide:N` is skill-only knowledge.) +`--prop link=slide[N]` for an in-deck jump (1-based; target slide must exist), `link=nextslide` / `firstslide` / `lastslide` / `previousslide` / `endshow` for named navigation, `link=https://...` for a URL, `--prop tooltip="..."` for hover text. ### Tables, placeholders, groups, zoom — one-liners @@ -400,7 +396,7 @@ officecli set "$FILE" "/slide[2]/shape[@name=HeroCard]" --prop animation=none ### Deck-level recipes -Patterns not obvious from the primitives. Each gives the **visual outcome** first, then a runnable block. `$FILE` = your filename. Use `/slide[last()]` to address the slide you just added. +Patterns not obvious from the primitives. Each gives the **visual outcome** first, then a runnable block. `$FILE` = your filename. Use `/slide[last()]` to address the slide you just added. The recipes demonstrate **structure and coordinate math** — swap in the palette / fonts you chose for this topic; the navy `1E2761` + Georgia is just the example's theme, not a house style to copy verbatim. **Z-order.** Later-added shapes are on top. Add background decoration FIRST, titles LAST. To fix after the fact: `--prop zorder=back/front` (renumbers siblings — re-`get --depth 1` before stacking more). @@ -475,11 +471,11 @@ for pair in "Step1 Step2" "Step2 Step3" "Step3 Step4"; do done ``` -`shape=elbow` is canonical (`bentConnector3` also works; `bentConnector2` is rejected). `query --json` results are in `.data.results[]` — use `.data.results[0].format.id`, not `.[0].id`. +`shape=elbow` is canonical (`bentConnector2` / `bentConnector3` also accepted). #### (d) Multi-slide deck skeletons -No code block — it's a rhythm. **Alternate dark divider slides with white content slides** using the recipes above: +No code block — it's a rhythm. The sequences below are **illustrations of one working cadence (alternating dark dividers with white content), not required running orders** — derive your actual arc from the content first (see "Title sequence first"), then borrow whatever divider/content rhythm fits: - **10-slide review:** Cover · Agenda · 3 KPI · Div01 · Chart · Chart · Div02 · Flow · Timeline · Close - **20-slide pitch:** same rhythm × 2, sectioned Problem · Solution · Market · Product · Traction · Model · Team · Financials · Ask @@ -535,40 +531,23 @@ Then 4 connectors (`Decide→YesBox`, `Decide→NoBox`, `YesBox→Done`, `NoBox Gates 1–2b are text/schema-level (cannot see a rendered slide); Gate 3 is the only visual check. Done = every gate PASS **and** Gate 3 loop converged. -```bash -FILE="deck.pptx" - -# Gate 1 — schema -officecli validate "$FILE" && echo "Gate 1 OK" || { echo "REJECT Gate 1"; exit 1; } +Each gate is **run a command, judge its output** — the officecli commands are identical on every OS (macOS / Linux / Windows), so no shell scripting is needed; the judging is yours. -# Gate 2 — overflow / format / structure (drop expected layout=blank "no title" noise) -ISSUES=$(officecli view "$FILE" issues 2>&1 | grep -vE "Slide has no title") -echo "$ISSUES" | grep -qE "^\s*\[[A-Z][0-9]+\]" && { echo "REJECT Gate 2:"; echo "$ISSUES"; exit 1; } || echo "Gate 2 OK" - -# Gate 2b — leftover placeholders ("xxxx", "lorem", "", empty (), [], "this slide layout") -LEFT=$(officecli view "$FILE" text | grep -niE 'xxxx|lorem|ipsum||placeholder|this[- ]slide[- ]layout|\(\)|\[\]') -[ -n "$LEFT" ] && { echo "REJECT Gate 2b:"; echo "$LEFT"; exit 1; } || echo "Gate 2b OK" -``` +- **Gate 1 — schema.** `officecli validate ""`. Any schema error → REJECT and fix. +- **Gate 2 — overflow / format / structure.** `officecli view "" issues`. If it lists *any* issue (lines tagged `[O1]`, `[C1]`, `[S1]`, …) → REJECT, fix, re-run until clean. +- **Gate 2b — leftover placeholders.** `officecli view "" text`, then scan the output for `xxxx`, `lorem` / `ipsum`, ``, `placeholder`, "this slide layout", or empty `()` / `[]`. Any hit → REJECT. ### Gate 3 — Visual audit (MANDATORY) Pick **one** path: -**Screenshot (default)** — needs image-Read + a headless browser. **Loop per slide** (viewport screenshot covers only slide 1): - -```bash -n=1 -while officecli view "$FILE" screenshot --page $n -o "/tmp/gate3_$n.png" 2>/dev/null; do - n=$((n+1)) -done -[ $n -eq 1 ] && { echo "no headless backend — using fallback"; SCREENSHOT_FAILED=1; } -``` +**Screenshot (default)** — needs image-Read + a headless browser. Screenshot each slide in turn — `officecli view "" screenshot --page 1 -o slide1.png`, then `--page 2`, … — until the page index runs past the deck (one screenshot = one slide). If it errors on page 1, there's no headless backend → use the fallback below. -Read each PNG against the checklist; delegate to a subagent when the harness has one. +**Judge every PNG against the checklist, adversarially** — "assume problems exist; finding none means you didn't look hard enough." Report one `slide N: ` line per problem, or `PASS`. This step is required however you run it. **If** your harness can spawn a subagent, delegate the judging to a *fresh, independent* one — the agent that built the deck is biased toward "looks fine", a separate pair of eyes is more critical — handing it the screenshots + this checklist and the same adversarial framing. No subagent? Do exactly the same yourself. **Fallback — HTML-text** (no image-Read or no browser): read `view "$FILE" html` as text. DOM cannot prove **dark-on-dark / fine overlap / arrowheads / gap-margin metrics / column alignment** — flag these as "not visually verified" rather than PASS. -**Optional `--grid N`** — only on user request for layout-rhythm, or when `view outline` shows anomalous layout distribution: `officecli view "$FILE" screenshot --grid 3 -o /tmp/grid.png`. +**Optional `--grid N`** — only on user request for layout-rhythm, or when `view outline` shows anomalous layout distribution: `officecli view "" screenshot --grid 3 -o grid.png`. **Per-slide checklist (assume issues exist):** @@ -576,6 +555,7 @@ Read each PNG against the checklist; delegate to a subagent when the harness has - **text overflow** — clipped at slide or shape boundary (KPI cards, narrow boxes) - **narrow text box** — content fits technically but wraps to many short lines (1–2 words each); long sublabel in a 3cm KPI card, body line in a too-tight column - **dark-on-dark** — fill brightness < 30% with text/icon brightness < 80% (incl. dark icons on dark without a contrasting circle) +- **image treatment** — photo stretched/distorted, text raw on a busy image (no card/scrim), screenshot or logo cropped, transparent image floating on white - **missing arrowheads** — flowchart connectors as plain lines - **decorative-line / title mismatch** — accent bar sized for one-line title but title wrapped to two (or vice versa) - **footer / citation collision** — source line, page number, or footnote touching content above @@ -599,4 +579,4 @@ Sanity-check cheatsheet — what breaks on the first try. Design + shell traps. | `/shape[myname]` (bare name in brackets) | Use `@name=` selector: `/shape[@name=myname]` or `/shape[@id=10007]` | | Paths 1-based vs `--index` 0-based | `/slide[1]` = first slide; `--index 0` = first position | | `$` in `--prop text=` | Single-quote: `--prop text='$15M'`. Double-quoted `"$15M"` gets shell-expanded to `M` | -| `\n` / `\t` in `--prop text=` | CLI does NOT interpret. Use multiple `--type paragraph`, or batch heredoc with JSON `"\n"` | +| `\n` / `\t` in `--prop text=` | Interpreted by the CLI: `\n` = paragraph break, `\t` = tab. Double `\\n` for a literal | diff --git a/crates/aionui-app/assets/builtin-skills/officecli-xlsx/SKILL.md b/crates/aionui-app/assets/builtin-skills/officecli-xlsx/SKILL.md index e693db357..9636ffbe2 100644 --- a/crates/aionui-app/assets/builtin-skills/officecli-xlsx/SKILL.md +++ b/crates/aionui-app/assets/builtin-skills/officecli-xlsx/SKILL.md @@ -50,7 +50,7 @@ Help reflects the installed CLI version. When this skill and help disagree, **he - ALWAYS quote element paths: `"/Sheet1/row[1]"`, not `/Sheet1/row[1]`. - Use **single quotes** for any prop value containing `$`: `numFmt='$#,##0'`. - For formulas with cross-sheet `!` references, use `batch` with a `<<'EOF'` heredoc (see Known Issues). -- NEVER hand-write `\$`, `\t`, `\n` inside executable examples. The CLI does not interpret backslash escapes; they will land in your file as literal characters. +- `\n` and `\t` in a prop value ARE interpreted by the CLI — `\n` is a real in-cell line break (pair with `--prop wrapText=true`), `\t` a tab — consistent across xlsx / docx / pptx. Double them (`\\n`) for a literal backslash-n (rarely wanted). (`$` is the shell layer above — single-quote it.) **Incremental execution.** Run commands one at a time and read each exit code. `officecli` mutates the file on every call; a 50-command script that fails at command 3 will cascade silently. One command → check output → continue. @@ -133,7 +133,7 @@ Six steps. Every non-trivial build follows this shape. 2. **Create or load.** `officecli create "$FILE"` (new) or `officecli view "$FILE" outline` (existing — get the lay of the land first). 3. **Build incrementally.** One command, read the output, continue. After any structural op (new sheet, chart, named range, pivot), run `get` on it to confirm shape before stacking more on top. 4. **Format.** Column widths, number formats, freeze panes, tab colors, header fills. Formatting is not optional polish — per "Requirements for Outputs" it is part of the deliverable. -5. **Close, then reckon with the cache.** `officecli close ` writes to disk. Newly-added formulas ship without cached values; when a human opens the file in a spreadsheet app, the app recalculates and populates them. **But your downstream `INDEX/MATCH`, `SUMPRODUCT`, or any formula that references an upstream formula will cache whatever the upstream cached at write-time — often `0` or a stale value — and that cached lie survives into non-recalculating readers.** After any multi-formula build involving array formulas (`SUMPRODUCT`, `SUMIFS` with dynamic criteria) or cross-sheet chains, **re-touch every downstream cell** (run `set` again with the same formula) so the engine recomputes its cache from the freshly-cached upstream. ⚠️ Re-touch on cross-sheet chains via resident is unreliable (see Batch / resident caveats) — prefer non-resident `set` for the re-touch pass. Then `officecli get` a few downstream cells and eyeball that their `cachedValue=` is plausible. **Array-formula fallback:** for `SUMPRODUCT(1/COUNTIF(range, range))` distinct-count patterns, the CLI engine treats the inner division as scalar and caches `1/N` (e.g. `0.001543`) rather than the true distinct count. Re-touching won't fix it. **Fallback: hardcode the correct value + an adjacent comment `"hardcoded distinct count; update if Data rows change"`, and tell the reader at delivery**. Better than shipping a cached lie. Do NOT run `validate` while a resident is open — it reports spurious drawing errors. +5. **Close, then reckon with the cache.** `officecli close ` writes to disk. Newly-added formulas ship without cached values; when a human opens the file in a spreadsheet app, the app recalculates and populates them. **But your downstream `INDEX/MATCH`, `SUMPRODUCT`, or any formula that references an upstream formula will cache whatever the upstream cached at write-time — often `0` or a stale value — and that cached lie survives into non-recalculating readers.** After any multi-formula build involving array formulas (`SUMPRODUCT`, `SUMIFS` with dynamic criteria) or cross-sheet chains, **re-touch every downstream cell** (run `set` again with the same formula) so the engine recomputes its cache from the freshly-cached upstream. ⚠️ Re-touch on cross-sheet chains via resident is unreliable (see Batch / resident caveats) — prefer non-resident `set` for the re-touch pass. Then `officecli get` a few downstream cells and eyeball that their `cachedValue=` is plausible. Do NOT run `validate` while a resident is open — it reports spurious drawing errors. 6. **QA — assume there are problems.** See the QA section. You are not done when your last command exited 0; you are done after one fix-and-verify cycle finds zero new issues. ## Quick Start @@ -301,13 +301,13 @@ Chart types live under `officecli help xlsx chart` — the enum is long (20+). P **The single-column trap.** `dataRange="Sheet1!B2:B13"` looks like "value column" but the engine rejects it with `Chart requires data`. Either widen the range to include the category column (`A2:B13`), or switch to form (c) with explicit `series1.categories`. -**Chart `anchor` and series are immutable after create.** `set chart[N] --prop anchor=...` is rejected (`UNSUPPORTED props: anchor`); likewise new series cannot be appended. To resize, move, or add a series: `officecli remove` the chart, then `officecli add` with the new anchor / full series list. Also note: `remove chart[1]` shifts `chart[2] → chart[1]`, and re-add **appends at the end** — to preserve chart order, remove all and rebuild in order. +**Move / resize a chart after create:** `set chart[N] --prop anchor="F5:N25"` (also `--prop x= --prop y= --prop width= --prop height=`). **Series are still immutable** — to add/change a series, `officecli remove` the chart and `officecli add` with the full series list. Note `remove chart[1]` shifts `chart[2] → chart[1]` and re-add **appends at the end** — to preserve chart order, remove all and rebuild in order. **Anchor sizing.** No auto-fit. A column chart with 5-6 categories + 2 series needs roughly `A5:L22` (12 cols × 18 rows) to show all labels uncut. Narrower and X-axis labels clip; wider and the chart can split across pages on print/export. If in doubt, start narrow, preview via `view html` (Read the returned HTML path), widen in increments. Page layout (below) is the other half of the fix. **Chart `dataRange` — always prefix with the sheet.** Even when the chart lives on the same sheet, write `dataRange="Summary!A17:C22"`, not `A17:C22`. The sheet-less form works inconsistently; the prefixed form is 100% reliable. -officecli adds extended chart types the classic Excel object model lacks: `boxWhisker`, `waterfall`, `funnel`, `histogram`, `treemap`, `sunburst`. Use them when the data calls for them. Known-bad: `chartType=pareto` (produces invalid XML — use `column` or `boxWhisker`). +officecli adds extended chart types the classic Excel object model lacks: `boxWhisker`, `waterfall`, `funnel`, `histogram`, `treemap`, `sunburst`, `pareto`. Use them when the data calls for them. **NEVER put unreplaced template tokens in chart title / series name / legend / axis title.** `$fy$24`, `{var}`, ``, `$VAR`, `{{placeholder}}` render **literally** in the legend — validate passes, but a CFO sees `$fy$24` where "FY2024" should be. Always bind to final text or a cell reference (`title="FY2024 Revenue"` or `series1.name="Sheet1!A1"`). @@ -316,7 +316,7 @@ officecli adds extended chart types the classic Excel object model lacks: `boxWh Three common flavors, each with its own prop shape (consult `officecli help xlsx cf`): - **Color scales**: cells shaded on a gradient by value — `type=colorscale` with `minColor` / `midColor` / `maxColor`. -- **Data bars**: in-cell bars showing magnitude — `type=databar`. ALWAYS set explicit `min` and `max`; defaults emit invalid XML (see Known Issues). +- **Data bars**: in-cell bars showing magnitude — `type=databar`. Set explicit `min` / `max` for consistent scaling across a column; defaults are valid if you omit them. - **Formula rules**: highlight row when a condition is true — `type=formulacf` with `formula="$C2>1000"` and a fill/font. Rule: apply CF sparingly. A workbook where every cell is colored tells the reader nothing. @@ -375,7 +375,7 @@ officecli set "$FILE" "/Sheet1/chart[1]/axis[@role=value]" --prop min=0 --prop m officecli set "$FILE" "/Sheet1/chart[1]/axis[@role=category]" --prop title="Month" ``` -Safe props: `title`, `min`, `max`, `majorGridlines`, `visible`. Do NOT use `labelRotation` — it emits invalid XML today (see Known Issues). +Safe props: `title`, `min`, `max`, `majorGridlines`, `visible`, `labelRotation`. ## QA (Required) @@ -454,19 +454,15 @@ EOF ### CLI bug backlog (short) -Avoid these until fixed; they produce invalid XML or silent breakage. +CLI constraints and gaps to work around — not defects in the output file. -- **`chartType=pareto`** — emits empty `cx:axisId val=""`; `validate` fails after `close`. Substitute `column` or `boxWhisker`. -- **`labelRotation` on axis-by-role** — inserts bad `a:endParaRPr`. Use `title`/`min`/`max`/`majorGridlines`/`visible` only. -- **Data bar without explicit min/max** — default cfvo `val=""` is invalid. Always pass `--prop min=N --prop max=N`. -- **Chart `anchor` and series are immutable after create** — to resize/move/add-series: `remove` + `add`. `remove chart[N]` shifts subsequent indices down; re-add appends at end. +- **Chart series are immutable after create** — to add/change a series: `remove` + `add` with the full series list. (Position is mutable: `set chart[N] --prop anchor=` / `x/y/width/height`.) `remove chart[N]` shifts subsequent indices down; re-add appends at end. - **`validate` while resident open** — reports spurious `tableParts` / `drawing` errors. Always `close` first. - **Batch + resident for formulas — avoid.** Observed deadlocks (CPU 99%, `main pipe busy`, kill -9 required) for cross-sheet formula batches even at 3-5 ops; the prior "≤ 12 ops safe" guideline is **not reliable**. Rule: **cross-sheet formulas go through non-resident one-big-batch OR individual `set`** (100% reliable). Pure value-set batches (no formulas) stay reliable at 50-80+ ops even in resident. **Multiple officecli resident processes on the same machine also contend** — if another agent/session is running resident, expect non-deterministic hangs. - **Conditional formatting naming asymmetry** — the element name for `--type` is `conditionalformatting`; the path suffix is `/cf[N]`. Use `officecli help xlsx conditionalformatting` for schema, `/cf[N]` for paths. - **Sheet `position` prop on add** — help says Add processes `position`, but the prop is often ignored. Reorder with `officecli move --index` / `--after` / `--before` after creating the sheet. - **`remove /sheet[N]` cascade guard** — 1.0.59+ rejects sheet remove/rename when the sheet is referenced by validation / conditional format / sparkline / hyperlink / named range on another sheet. Remove those dependent elements first, then remove the sheet. - **Batch JSON rejects cell `color` alias** — inside batch `props`, `"color": "FF0000"` errors `ambiguous in cell context — use 'font.color' (text) or 'fill' (bg)`. The CLI at shell level accepts `--prop color=...` / `--prop size=14` as aliases on non-cell elements, but inside batch JSON on a cell always write the full dotted name: `"font.color"`, `"font.size"`, `"font.name"`. -- **`SUMPRODUCT((range=criterion)*values)` caches `0` on 1.0.63** — the CLI calc engine does not evaluate array-predicate `SUMPRODUCT` at write-time; runtime Excel/WPS compute fine but the cached `0` ships to non-recalculating readers. **Helper-column fallback:** add a column `F` on the source sheet with `=C2*D2` per row, then aggregate via `=SUMIF(B:B, "Region X", F:F)`. Caches correctly, audits cleanly, and survives non-recalculating viewers. ### Renderer caveats (cross-viewer color fidelity) @@ -480,10 +476,10 @@ Before calling a color or chart "broken", open the file in the user's actual tar ### Escape layers (shell quoting is above; these are the extras) -The CLI does not interpret `\$` / `\t` / `\n` — they land as literal characters. Shell-level rules are in L25-30. Two additional layers: +`$` is the shell layer (single-quote it, above). `\n` / `\t` in a prop value ARE interpreted by the CLI into a real newline / tab. Two more layers: - **JSON level (batch).** Standard JSON escapes — `"\n"`, `"\t"`, `"\""`. A real backslash in the final string is `"\\\\"`. -- **Excel level.** `\n` in a cell for line break → write `"\n"` **inside JSON**. In a shell-quoted prop it stays literal (Excel shows `\n` text). When in doubt, `officecli get` the cell and compare character-for-character. +- **Excel level.** `\n` in a cell is a real line break — pair with `--prop wrapText=true` so Excel shows the wrap. Works in a shell-quoted prop directly (`--prop value='a\nb'`); `"\n"` inside batch JSON gives the same. When in doubt, `officecli get` the cell and compare character-for-character. ### Other common pitfalls