Skip to content

# Documentation & Examples Restructuring#2

Merged
goworm merged 3 commits into
mainfrom
docs
Mar 21, 2026
Merged

# Documentation & Examples Restructuring#2
goworm merged 3 commits into
mainfrom
docs

Conversation

@IceyLiu

@IceyLiu IceyLiu commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR significantly improves the repository's documentation and example structure to make it more LLM/Agent-friendly. The changes include a complete reorganization of the examples/ directory and major enhancements to SKILL.md.

Changes Overview

📚 Examples Directory Restructuring

Before:

examples/
├── 17 mixed files (scripts for Word, Excel, PPT)
├── outputs/ (mixed outputs)

After:

examples/
├── README.md                    # Main navigation
├── word/                        # 📄 Word examples
│   ├── README.md
│   ├── gen-*.sh (3 scripts)
│   └── outputs/
├── excel/                       # 📊 Excel examples
│   ├── README.md
│   ├── gen-*.sh (2 scripts)
│   └── outputs/
└── ppt/                         # 🎨 PowerPoint examples
    ├── README.md
    ├── gen-*.sh (3 scripts)
    ├── outputs/
    └── templates/               # ⭐ NEW: 34 style templates
        ├── README.md
        └── styles/              # 19 with pre-generated PPTs

Key Improvements:

  • ✅ Clear separation by document type (Word, Excel, PowerPoint)
  • ✅ Dedicated README for each category with detailed documentation
  • ✅ 34 professional PowerPoint style templates added
  • ✅ 19 pre-generated .pptx files ready to use
  • ✅ Organized by color palette (Dark, Light, Warm, Vivid, BW, Mixed)

📖 Enhanced Documentation

1. examples/README.md (NEW - 344 lines)

  • Main index for all examples
  • Learning path: Beginner → Intermediate → Advanced
  • Quick reference tables for commands and view modes
  • Common patterns and tips

2. examples/word/README.md (NEW - 174 lines)

  • Documents 3 Word scripts with detailed explanations
  • Document structure diagram
  • Common commands reference
  • Property tables

3. examples/excel/README.md (NEW - 206 lines)

  • Documents 2 Excel scripts
  • 14+ chart types reference
  • Cell properties and formula examples

4. examples/ppt/README.md (NEW - 373 lines)

  • Documents 3 basic PPT scripts + 34 style templates
  • Links to comprehensive template gallery
  • Shape types, transitions, animations reference

5. examples/ppt/templates/README.md (NEW - 208 lines)

  • Complete style index for 34 professional templates
  • Summary: 19 available (✅), 15 reference-only (⚙️)
  • Organized by palette:
    • 🌑 Dark (8/14): investor-pitch, cosmic-neon, tech-cosmos, etc.
    • ☀️ Light (6/8): minimal-corporate, project-proposal, etc.
    • 🧡 Warm (4/5): minimal-brand, playful-organic, brand-refresh, creative-marketing
    • 🌈 Vivid (1/2): playful-marketing
    • ⬛ BW (1/3): swiss-bauhaus
  • Use case lookup table mapping scenarios to recommended styles

🔧 SKILL.md Enhancements

Additions:

  • ✅ Clearer install & update section
  • ✅ Help system emphasis with detailed navigation guide
  • ✅ Quick start examples for Word, Excel, PowerPoint
  • ✅ Comprehensive view modes table
  • ✅ Enhanced get/query sections with examples
  • ✅ Better structured command reference
  • ✅ Common pitfalls table
  • ✅ Clearer batch mode explanation

Improvements:

  • Better formatting and structure (197 insertions, 42 deletions)
  • More code examples throughout
  • Emphasis on "check help first" pattern
  • Progressive complexity model clearly explained

Impact

For AI Agents:

  • 📈 Discoverability: Clear navigation structure makes it easy to find relevant examples
  • 📖 Learning Path: Progressive examples from beginner to advanced
  • 🎨 Rich Templates: 34 professional styles with working code
  • 🔍 Better Context: Comprehensive READMEs provide context for each example

For Developers:

  • 🗂️ Better Organization: Document types clearly separated
  • 📚 Complete Documentation: Each category has detailed guides
  • 🎯 Use Case Driven: Templates organized by use case and mood
  • 🚀 Ready to Use: 19 pre-generated PPTs as starting points

Files Changed

  • 111 files changed
  • 12,603 insertions(+), 42 deletions(-)
  • Key additions:
    • 5 comprehensive README files
    • 34 style template directories
    • 19 pre-generated .pptx files
    • Complete build scripts and style guides

Testing

  • ✅ All existing example scripts work in new locations
  • ✅ 19/34 style template build.sh scripts successfully execute
  • ✅ All generated PPTs validated with officecli validate
  • ✅ Documentation links verified
  • ✅ File structure confirmed accessible from root

Notes

  • 15/34 style templates are marked as "reference-only (⚙️)" due to path errors in build scripts (documented in templates/README.md)
  • These reference templates still provide valuable design philosophy and can be fixed in future PRs
  • No breaking changes to existing functionality

Related Issues

Addresses the need for better documentation structure to support AI agents and improve developer experience.


Ready to merge

IceyLiu added 3 commits March 22, 2026 05:04
…lates

- Restructure examples/ by document type (word/, excel/, ppt/)
- Move scripts and outputs to dedicated subdirectories
- Add 34 professional PowerPoint style templates (19 pre-generated)
  - 🌑 Dark (8/14): investor-pitch, cosmic-neon, tech-cosmos, etc.
  - ☀️ Light (6/8): minimal-corporate, project-proposal, etc.
  - 🧡 Warm (4/5): minimal-brand, playful-organic, etc.
  - 🌈 Vivid (1/2), ⬛ BW (1/3), 🎨 Mixed (0/1)
- Create comprehensive README documentation for each category
- Add style index with use case lookup tables

Structure:
- examples/word/ (3 scripts + outputs)
- examples/excel/ (2 scripts + outputs)
- examples/ppt/ (3 scripts + 34 style templates)
  - templates/styles/ with design guidelines and build scripts
  - 19 ready-to-use pre-generated PPTs

Each style includes:
- style.md: design philosophy, color palette, techniques
- build.sh: reference implementation
- *.pptx: pre-generated presentation (available styles)
@goworm goworm merged commit d71fce6 into main Mar 21, 2026
goworm added a commit that referenced this pull request Mar 27, 2026
# Documentation & Examples Restructuring
goworm added a commit that referenced this pull request Apr 2, 2026
# Documentation & Examples Restructuring
goworm added a commit that referenced this pull request Apr 10, 2026
Three coupled bugs surfaced under concurrent pipe load, all data-loss-capable:

1. close-race — `__close__` wrote "Closing resident." before handler.Dispose()
   ran, so the client saw success while the resident still held the file. A
   racing `rm + new + open` against the same path could then leave two
   residents listening on the same pipe name (MaxAllowedServerInstances
   permits coexistence); OS randomly routed new writes to either, and
   whatever hit the dying resident was saved to the already-deleted inode
   and lost. Fix: introduce an idempotent ShutdownAsync that cancels →
   drains _commandLock → disposes the handler, and have the __close__
   handler await it BEFORE sending the ack. Dispose() now delegates to the
   same shared task so idle-timeout and explicit-close paths agree.

2. silent fallback in TryResident — when ResidentClient.TrySend returned
   null under burst load, the CLI silently fell back to
   DocumentHandlerFactory.Open(editable: true), racing a second writer
   against the live resident. Under a 100-parallel set flood this lost
   96/100 cells even though every client returned rc=0. Fix: two-step
   probe — ping-pipe TryConnect first (fast, isolated), and only if a
   resident is confirmed alive, TrySend with a generous 30s connect timeout
   and retries. If the send still fails, return a distinct "resident busy"
   error (exit code 3) instead of touching the file directly.

3. main-pipe ECONNREFUSED race — the main command loop created one
   NamedPipeServerStream per iteration and only began constructing the
   next one AFTER the handler fire-and-forget, leaving a gap where no
   instance was listening. Under 50+ concurrent clients the gap cost real
   connections (previously masked by #2). Fix: mirror the pre-create
   pattern already used by RunPingResponderAsync (BUG-FUZZER-R6-B-01) so a
   replacement server is always standing by before the accepted instance
   is handed off.

Cross-platform: all changes use portable APIs (NamedPipeClientStream.Connect
int ms, SemaphoreSlim.WaitAsync(TimeSpan), Task.Run). The pre-create pattern
is already proven on Windows by the ping responder.

Verified:
- 500 concurrent gets: 500/500 ok, no busy errors
- 100 concurrent writes: 100/100 cells land correctly
- close + immediate validate: no "file in use"
- close + rm + new + open: exactly one resident, no zombies
- full resident test suite flakes dropped from 15 → 2 (both pre-existing,
  unrelated WatchServer timing flakes)
kuishou68 referenced this pull request in kuishou68/OfficeCLI Apr 15, 2026
… styling

MOD(#2): see docs/cove-desktop-mods.md

- WordHandler.HtmlPreview.Text.cs: wrap w:del/moveFrom in <del>, w:ins/moveTo
  in <ins> instead of skipping deleted content and silently emitting insert
  text; RenderRunHtml now handles DeletedText as first-class content
- WordHandler.HtmlPreview.Css.cs: add del/ins default styles with !important
  on descendants so inline span colors from original runs don't override
  the gray-strikethrough / red visual cue
- README.md: clean up overstated chart description
goworm added a commit that referenced this pull request Apr 17, 2026
Not a full 2D text-flow fix — that needs an iterative solver.
These two tweaks cover more real-world cases with CSS float:

- wp:anchor (shapes/images with wrapSquare/wrapTight):
  The hardcoded 8px margin is replaced with the anchor's actual
  distT / distB / distL / distR attributes (EMU → pt), with a 6pt
  floor on the "inside" margin so text still has breathing room.

- tblpPr (floating tables):
  * Honor tblpXSpec="left"/"right"/"center" in addition to the
    pre-existing page + tblpX>5000 right-detection heuristic.
  * When horzAnchor="margin", fold tblpX into margin-left (or
    margin-right for float:right) so the column offset shows.
  * When vertAnchor is absent or "text" (the default), fold tblpY
    into margin-top so the vertical offset is visible.
  * Use w:topFromText for margin-top too, combined with the offset.
  * Skip vertAnchor="page"/"margin" — those need absolute positioning
    which breaks text flow (leave for a future pass).

Known limitation documented in KNOWN_ISSUES #2 / #7b: vertAnchor=
page/margin, wrapType=tight with non-rectangular exclusion, and
stacked floating anchors at the same Y remain approximate. A true
fix requires JS measurement or porting Word's 2D flow solver.
goworm added a commit that referenced this pull request Apr 17, 2026
The preceding tighter-margins commit still produced broken wrap in
practice: our <div class="page-body"> uses display:flex;
flex-direction:column, and flex layout ignores float on its children.
So a <table style="float:left"> placed directly under .page-body
rendered inline, and an <img style="float:right"> inside a <p>
wrapped only its own paragraph's text — subsequent paragraphs
started full-width below.

New wrapFloats() runs after paginate() settles:
- Find page-body direct children whose outer CSS carries float:*
  OR that contain a descendant <img style="float:*">.
- For each, wrap it plus following prose siblings in a
  <div class="float-wrap" style="display:block;overflow:auto">.
  The BFC establishes a new formatting context where the float's
  rectangle pushes following text sideways, covering multiple
  paragraphs until a heading, another table, or enough content
  has cleared the float's height.
- Stop absorbing at hard breaks (H1-H9, another <table>, .footnotes).

Verified against Office reference via tools/officeshot.py: a
wrapSquare-right image + wrapType-around left-anchored table now
produce the same text-around-float layout as Word within one page.

Known limitation (documented in KNOWN_ISSUES #2 / #7b): vertAnchor=
page/margin, wrapType=tight with non-rectangular exclusion, and
stacked floats at the same Y still fall outside this approximation.
goworm added a commit that referenced this pull request Apr 17, 2026
…er bugs

- tester #2 (JS): tblHeader continuation iterated table.querySelectorAll('tr')
  which pulled nested subtable rows into the split, mangling nested structures.
  Filter to top-level rows via tr.closest('table')===table.
- tester #3 (JS): restart='newSection' compared only prev/current restart value,
  so two adjacent sections both declaring newSection never triggered reset.
  Page-wrapper now carries data-section-idx; JS resets the running counter on
  section boundary, not on restart-value change.
- tester #4: altChunk ContentType equality failed on 'text/html; charset=utf-8'
  and other '+xml' variants — HTML payload fell through to the RTF stripper.
  Split media-type from params and accept xhtml+xml variants.
- fuzzer A: pgSz w/h parsed via UInt32Value.Value crashed on negative raw
  attrs. Wrap Width/Height access in a swallow-to-fallback helper.
- fuzzer B/C: lnNumType CountBy/Start/Distance typed as Int16Value; malformed
  raw values (non-numeric, overflow) threw on .Value. Parse InnerText manually
  via short.TryParse / int.TryParse.
- fuzzer D/E: huge w:ilvl (10000, Int32.MaxValue) either popped an empty
  open-tag stack (crash) or inflated the HTML by 50× per paragraph (DoS).
  Clamp ilvl to OOXML-legal [0,8].
goworm added a commit that referenced this pull request Apr 25, 2026
…cation, /p[N] schema

Round 6 surfaced 4 bugs:

- tester: 'install vscode' / 'install lms' printed 'Unknown target' to
  stderr from the skill phase before InstallMcpFallback succeeded —
  misleading users into thinking the install failed when it actually
  worked. Skip SkillInstaller.Install entirely when the target is an
  MCP-only entry (McpTargets row with empty SkillAliases).

- fuzzer #1: a JSON-RPC request with a non-string 'method' field
  ('method': 42) crashed into the catch (Exception) path with id=null
  and code -32603, violating the spec. Per JSON-RPC 2.0 §5 such inputs
  must respond -32600 (Invalid Request) with the original id echoed.
  Parse id before method; reject non-string method explicitly.

- fuzzer #2: the 64-char error truncation could split a UTF-16
  surrogate pair, causing the orphan high-surrogate to serialize as
  U+FFFD. Move truncation into a shared helper that backs off one
  char when the cut lands on a high surrogate. Both error sites
  (McpServer unknown-format and SchemaHelpLoader unknown-element)
  now go through it.

- bt: schema docs claimed '/p[N]' as a positional shorthand for
  paragraphs (and '/p[N]/r[N]' for runs) but the Word navigator
  doesn't accept them — only '/body/p[N]' resolves. Pre-existing
  doc/code mismatch, removed the unsupported shorthand from the
  schema rather than implementing missing handler support to keep
  this round contained to documentation truth.
goworm added a commit that referenced this pull request Apr 28, 2026
…ide 'instruction'

Round 6 audit caught the help/schema vs implementation mismatch:
field.json declares 'instr' as the canonical raw-instruction key (it is
the same name Get surfaces on instrText readback) with 'instruction'
and 'code' as accepted aliases, and the help docs use 'instr' in their
example. But AddField only ever read 'instruction' from the props
dict, so a user copy-pasting the documented 'instr=...' example saw
'Unknown field type field' from a different code path. Centralize the
three keys in a single GetRawFieldInstruction helper and update the
error message to mention all three accepted forms.

(Note: Bug #2 from Round 6 — SchemaContractTests can't load schema
files because the test csproj lacks a schemas/**/*.json copy step —
is fixable with an MSBuild <None CopyToOutputDirectory> entry, but
the test project tree is intentionally untracked per project policy.
Fix is applied locally to unblock contract testing during this round
but is not part of this commit; project owner can promote it when
tracking the test tree.)
goworm added a commit that referenced this pull request Apr 28, 2026
…ide 'instruction'

Round 6 audit caught the help/schema vs implementation mismatch:
field.json declares 'instr' as the canonical raw-instruction key (it is
the same name Get surfaces on instrText readback) with 'instruction'
and 'code' as accepted aliases, and the help docs use 'instr' in their
example. But AddField only ever read 'instruction' from the props
dict, so a user copy-pasting the documented 'instr=...' example saw
'Unknown field type field' from a different code path. Centralize the
three keys in a single GetRawFieldInstruction helper and update the
error message to mention all three accepted forms.

(Note: Bug #2 from Round 6 — SchemaContractTests can't load schema
files because the test csproj lacks a schemas/**/*.json copy step —
is fixable with an MSBuild <None CopyToOutputDirectory> entry, but
the test project tree is intentionally untracked per project policy.
Fix is applied locally to unblock contract testing during this round
but is not part of this commit; project owner can promote it when
tracking the test tree.)
goworm added a commit that referenced this pull request May 2, 2026
…light, drop static contrast Gate 4

Min cycle and Delivery Gate were two passes at the same checks: Min
cycle #2 (view html) overlapped Gate 5, #3 (token query) overlapped
Gate 2's grep, #4 (close+validate) overlapped Gate 1, #5 (target
viewer) was a 4-line restatement of the view-html-vs-runtime rule
already in Common Workflow #6. The intro itself called Min cycle
"warmup" while Delivery Gate did the real work.

Replace the 6-step Min cycle with a single-line Pre-flight using
view annotated (the one Min-cycle item Gates don't cover — font/size
violations and overflow surfaced before the gate run). Move target
viewer + fix-and-rerun guidance into a tight "After all gates pass"
subsection at the end of QA.

Drop the static-contrast Gate 4 (query+jq dark-fill scan): its
hardcoded list of five hex values misses any deck using a custom
dark fill, while Gate 5's visual review catches dark-on-dark across
all fills. Renumber: Gate 5 → Gate 4. Update cross-references
(intro line, status banner, "Gates 1-4 cannot see rendered slides"
→ "Gates 1-3", "Gates 2-5 are how you catch" → "Gates 2-4").
goworm added a commit that referenced this pull request May 11, 2026
…stale)

Every chart series stores its cached values inside
<c:numCache><c:pt><c:v>...</c:v></c:pt>. When source cells change without
an Excel refresh, the chart silently renders old data — agents and
downstream consumers (pdf export, screenshot) see stale numbers with no
warning.

view issues --type chart_cache_stale now resolves each <c:f> against
live cell values and compares element-wise to the persisted numCache,
emitting "Chart numCache out of sync" with both vectors in Context.

Off by default — walks every chart × series × point and re-reads
source ranges, so default `view issues` stays cheap. Sheet-prefix
parsing reuses ChartRefSheetExists; charts whose ref already broke
(missing sheet) are skipped because #2 already reports them.
goworm added a commit that referenced this pull request May 18, 2026
Three closures on the P0-2 long tail:

1. `tabs=POS:ALIGN[:LEADER][,POS:ALIGN[:LEADER]...]` shorthand on
   AddParagraph / SetElementParagraph / AddStyle / SetStylePath.
   Parses comma-separated segments into <w:tabs> with <w:tab> children
   (val + pos + optional leader). POS accepts bare twips or any unit
   SpacingConverter / ParseSignedTwips understands (5cm, 2in, 12pt,
   plus negative twips for hanging tabs). The new <w:tabs> replaces
   any existing tabs strip — same "definitive write" semantics as
   spacing / indent shorthands. Common signoff use case
   (`tabs=9360:right`) and decimal-aligned columns
   (`tabs=2880:decimal,5760:decimal:dot`) now work via the curated
   surface instead of UNSUPPORTED. Helper ApplyTabsShorthand lives
   in WordHandler.Helpers.cs and is shared across the four call sites.

2. ST_OnOff value normalization in GenericXmlQuery.TryCreateTypedChild.
   Boolean inputs (true/false/on/off/yes/no/1/0) emitted through the
   long-tail single-val fallback now normalize to OOXML canonical
   "1"/"0" when the target's typed Val is OnOffValue / TrueFalseValue.
   Word / LibreOffice / Pages render either form, but strict OOXML
   validators and most reference docs expect "1"/"0", and `set
   kinsoku=true` was leaking the literal "true" to disk. Probed via
   reflection on the SDK's typed Val property type so enum / string /
   numeric vals stay untouched.

3. StyleUnsupportedHints: removed the `tabs` entry now that the
   curated shorthand lands schema-valid <w:tabs>.

Verified locally for #2 (kinsoku=true → <w:kinsoku w:val="1"/>).
Verification for #1 (tabs shorthand) deferred — the build was
blocked by an unrelated in-progress edit in
src/officecli/Handlers/Pptx/PptxBatchEmitter.cs in the shared
worktree; my changes follow the existing AddTab + ApplyStyleParagraphBorders
patterns directly and are wired symmetrically across the four
add/set entry points.
goworm added a commit that referenced this pull request Jun 1, 2026
PowerPoint accepts a second plain <p:transition> sibling after <p:timing>
in addition to a primary transition (typically wrapped in mc:AlternateContent
for morph / p14 / p15 decks). The dump scan stopped at the first
<p:transition> hit and treated everything past it as belonging to the
mc-wrap or the timing block, so a trailing <p:transition advTm="5000"/>
was silently dropped. ReadSlideTransition's regex-based fallback even
saw the wrong advanceTime — it harvested advTm from the mc-inner
<p:transition> attributes rather than the trailing element.

Capture a TrailingTransitionXml field in SlideExoticContent: after the
first <p:transition> slice ends (or the enclosing mc:AlternateContent
wrap closes), look for a second <p:transition> direct child and capture
its outer XML. Emit it via the existing raw-set append on /p:sld so the
trailing slice lands after timing / extLst (mirrors source order on the
animations.pptx slide-6 fixture).

When mc:AlternateContent wraps the primary transition, semantic transition
/ advanceTime / advanceClick props are already stripped from slideProps
upstream, so the trailing raw-set is the only emit and no double-write
occurs. Plain-plain double-transition slides (no mc wrap) remain a known
edge case — the semantic emit covers transition #1 and the raw-set covers
transition #2.
NextDoorLaoHuang-HF pushed a commit to NextDoorLaoHuang-HF/OfficeCLI that referenced this pull request Jun 10, 2026
Three coupled bugs surfaced under concurrent pipe load, all data-loss-capable:

1. close-race — `__close__` wrote "Closing resident." before handler.Dispose()
   ran, so the client saw success while the resident still held the file. A
   racing `rm + new + open` against the same path could then leave two
   residents listening on the same pipe name (MaxAllowedServerInstances
   permits coexistence); OS randomly routed new writes to either, and
   whatever hit the dying resident was saved to the already-deleted inode
   and lost. Fix: introduce an idempotent ShutdownAsync that cancels →
   drains _commandLock → disposes the handler, and have the __close__
   handler await it BEFORE sending the ack. Dispose() now delegates to the
   same shared task so idle-timeout and explicit-close paths agree.

2. silent fallback in TryResident — when ResidentClient.TrySend returned
   null under burst load, the CLI silently fell back to
   DocumentHandlerFactory.Open(editable: true), racing a second writer
   against the live resident. Under a 100-parallel set flood this lost
   96/100 cells even though every client returned rc=0. Fix: two-step
   probe — ping-pipe TryConnect first (fast, isolated), and only if a
   resident is confirmed alive, TrySend with a generous 30s connect timeout
   and retries. If the send still fails, return a distinct "resident busy"
   error (exit code 3) instead of touching the file directly.

3. main-pipe ECONNREFUSED race — the main command loop created one
   NamedPipeServerStream per iteration and only began constructing the
   next one AFTER the handler fire-and-forget, leaving a gap where no
   instance was listening. Under 50+ concurrent clients the gap cost real
   connections (previously masked by iOfficeAI#2). Fix: mirror the pre-create
   pattern already used by RunPingResponderAsync (BUG-FUZZER-R6-B-01) so a
   replacement server is always standing by before the accepted instance
   is handed off.

Cross-platform: all changes use portable APIs (NamedPipeClientStream.Connect
int ms, SemaphoreSlim.WaitAsync(TimeSpan), Task.Run). The pre-create pattern
is already proven on Windows by the ping responder.

Verified:
- 500 concurrent gets: 500/500 ok, no busy errors
- 100 concurrent writes: 100/100 cells land correctly
- close + immediate validate: no "file in use"
- close + rm + new + open: exactly one resident, no zombies
- full resident test suite flakes dropped from 15 → 2 (both pre-existing,
  unrelated WatchServer timing flakes)
NextDoorLaoHuang-HF pushed a commit to NextDoorLaoHuang-HF/OfficeCLI that referenced this pull request Jun 10, 2026
Not a full 2D text-flow fix — that needs an iterative solver.
These two tweaks cover more real-world cases with CSS float:

- wp:anchor (shapes/images with wrapSquare/wrapTight):
  The hardcoded 8px margin is replaced with the anchor's actual
  distT / distB / distL / distR attributes (EMU → pt), with a 6pt
  floor on the "inside" margin so text still has breathing room.

- tblpPr (floating tables):
  * Honor tblpXSpec="left"/"right"/"center" in addition to the
    pre-existing page + tblpX>5000 right-detection heuristic.
  * When horzAnchor="margin", fold tblpX into margin-left (or
    margin-right for float:right) so the column offset shows.
  * When vertAnchor is absent or "text" (the default), fold tblpY
    into margin-top so the vertical offset is visible.
  * Use w:topFromText for margin-top too, combined with the offset.
  * Skip vertAnchor="page"/"margin" — those need absolute positioning
    which breaks text flow (leave for a future pass).

Known limitation documented in KNOWN_ISSUES iOfficeAI#2 / #7b: vertAnchor=
page/margin, wrapType=tight with non-rectangular exclusion, and
stacked floating anchors at the same Y remain approximate. A true
fix requires JS measurement or porting Word's 2D flow solver.
NextDoorLaoHuang-HF pushed a commit to NextDoorLaoHuang-HF/OfficeCLI that referenced this pull request Jun 10, 2026
The preceding tighter-margins commit still produced broken wrap in
practice: our <div class="page-body"> uses display:flex;
flex-direction:column, and flex layout ignores float on its children.
So a <table style="float:left"> placed directly under .page-body
rendered inline, and an <img style="float:right"> inside a <p>
wrapped only its own paragraph's text — subsequent paragraphs
started full-width below.

New wrapFloats() runs after paginate() settles:
- Find page-body direct children whose outer CSS carries float:*
  OR that contain a descendant <img style="float:*">.
- For each, wrap it plus following prose siblings in a
  <div class="float-wrap" style="display:block;overflow:auto">.
  The BFC establishes a new formatting context where the float's
  rectangle pushes following text sideways, covering multiple
  paragraphs until a heading, another table, or enough content
  has cleared the float's height.
- Stop absorbing at hard breaks (H1-H9, another <table>, .footnotes).

Verified against Office reference via tools/officeshot.py: a
wrapSquare-right image + wrapType-around left-anchored table now
produce the same text-around-float layout as Word within one page.

Known limitation (documented in KNOWN_ISSUES iOfficeAI#2 / #7b): vertAnchor=
page/margin, wrapType=tight with non-rectangular exclusion, and
stacked floats at the same Y still fall outside this approximation.
NextDoorLaoHuang-HF pushed a commit to NextDoorLaoHuang-HF/OfficeCLI that referenced this pull request Jun 10, 2026
…er bugs

- tester iOfficeAI#2 (JS): tblHeader continuation iterated table.querySelectorAll('tr')
  which pulled nested subtable rows into the split, mangling nested structures.
  Filter to top-level rows via tr.closest('table')===table.
- tester iOfficeAI#3 (JS): restart='newSection' compared only prev/current restart value,
  so two adjacent sections both declaring newSection never triggered reset.
  Page-wrapper now carries data-section-idx; JS resets the running counter on
  section boundary, not on restart-value change.
- tester iOfficeAI#4: altChunk ContentType equality failed on 'text/html; charset=utf-8'
  and other '+xml' variants — HTML payload fell through to the RTF stripper.
  Split media-type from params and accept xhtml+xml variants.
- fuzzer A: pgSz w/h parsed via UInt32Value.Value crashed on negative raw
  attrs. Wrap Width/Height access in a swallow-to-fallback helper.
- fuzzer B/C: lnNumType CountBy/Start/Distance typed as Int16Value; malformed
  raw values (non-numeric, overflow) threw on .Value. Parse InnerText manually
  via short.TryParse / int.TryParse.
- fuzzer D/E: huge w:ilvl (10000, Int32.MaxValue) either popped an empty
  open-tag stack (crash) or inflated the HTML by 50× per paragraph (DoS).
  Clamp ilvl to OOXML-legal [0,8].
NextDoorLaoHuang-HF pushed a commit to NextDoorLaoHuang-HF/OfficeCLI that referenced this pull request Jun 10, 2026
…cation, /p[N] schema

Round 6 surfaced 4 bugs:

- tester: 'install vscode' / 'install lms' printed 'Unknown target' to
  stderr from the skill phase before InstallMcpFallback succeeded —
  misleading users into thinking the install failed when it actually
  worked. Skip SkillInstaller.Install entirely when the target is an
  MCP-only entry (McpTargets row with empty SkillAliases).

- fuzzer iOfficeAI#1: a JSON-RPC request with a non-string 'method' field
  ('method': 42) crashed into the catch (Exception) path with id=null
  and code -32603, violating the spec. Per JSON-RPC 2.0 §5 such inputs
  must respond -32600 (Invalid Request) with the original id echoed.
  Parse id before method; reject non-string method explicitly.

- fuzzer iOfficeAI#2: the 64-char error truncation could split a UTF-16
  surrogate pair, causing the orphan high-surrogate to serialize as
  U+FFFD. Move truncation into a shared helper that backs off one
  char when the cut lands on a high surrogate. Both error sites
  (McpServer unknown-format and SchemaHelpLoader unknown-element)
  now go through it.

- bt: schema docs claimed '/p[N]' as a positional shorthand for
  paragraphs (and '/p[N]/r[N]' for runs) but the Word navigator
  doesn't accept them — only '/body/p[N]' resolves. Pre-existing
  doc/code mismatch, removed the unsupported shorthand from the
  schema rather than implementing missing handler support to keep
  this round contained to documentation truth.
NextDoorLaoHuang-HF pushed a commit to NextDoorLaoHuang-HF/OfficeCLI that referenced this pull request Jun 10, 2026
…ide 'instruction'

Round 6 audit caught the help/schema vs implementation mismatch:
field.json declares 'instr' as the canonical raw-instruction key (it is
the same name Get surfaces on instrText readback) with 'instruction'
and 'code' as accepted aliases, and the help docs use 'instr' in their
example. But AddField only ever read 'instruction' from the props
dict, so a user copy-pasting the documented 'instr=...' example saw
'Unknown field type field' from a different code path. Centralize the
three keys in a single GetRawFieldInstruction helper and update the
error message to mention all three accepted forms.

(Note: Bug iOfficeAI#2 from Round 6 — SchemaContractTests can't load schema
files because the test csproj lacks a schemas/**/*.json copy step —
is fixable with an MSBuild <None CopyToOutputDirectory> entry, but
the test project tree is intentionally untracked per project policy.
Fix is applied locally to unblock contract testing during this round
but is not part of this commit; project owner can promote it when
tracking the test tree.)
NextDoorLaoHuang-HF pushed a commit to NextDoorLaoHuang-HF/OfficeCLI that referenced this pull request Jun 10, 2026
…light, drop static contrast Gate 4

Min cycle and Delivery Gate were two passes at the same checks: Min
cycle iOfficeAI#2 (view html) overlapped Gate 5, iOfficeAI#3 (token query) overlapped
Gate 2's grep, iOfficeAI#4 (close+validate) overlapped Gate 1, iOfficeAI#5 (target
viewer) was a 4-line restatement of the view-html-vs-runtime rule
already in Common Workflow iOfficeAI#6. The intro itself called Min cycle
"warmup" while Delivery Gate did the real work.

Replace the 6-step Min cycle with a single-line Pre-flight using
view annotated (the one Min-cycle item Gates don't cover — font/size
violations and overflow surfaced before the gate run). Move target
viewer + fix-and-rerun guidance into a tight "After all gates pass"
subsection at the end of QA.

Drop the static-contrast Gate 4 (query+jq dark-fill scan): its
hardcoded list of five hex values misses any deck using a custom
dark fill, while Gate 5's visual review catches dark-on-dark across
all fills. Renumber: Gate 5 → Gate 4. Update cross-references
(intro line, status banner, "Gates 1-4 cannot see rendered slides"
→ "Gates 1-3", "Gates 2-5 are how you catch" → "Gates 2-4").
NextDoorLaoHuang-HF pushed a commit to NextDoorLaoHuang-HF/OfficeCLI that referenced this pull request Jun 10, 2026
…stale)

Every chart series stores its cached values inside
<c:numCache><c:pt><c:v>...</c:v></c:pt>. When source cells change without
an Excel refresh, the chart silently renders old data — agents and
downstream consumers (pdf export, screenshot) see stale numbers with no
warning.

view issues --type chart_cache_stale now resolves each <c:f> against
live cell values and compares element-wise to the persisted numCache,
emitting "Chart numCache out of sync" with both vectors in Context.

Off by default — walks every chart × series × point and re-reads
source ranges, so default `view issues` stays cheap. Sheet-prefix
parsing reuses ChartRefSheetExists; charts whose ref already broke
(missing sheet) are skipped because iOfficeAI#2 already reports them.
NextDoorLaoHuang-HF pushed a commit to NextDoorLaoHuang-HF/OfficeCLI that referenced this pull request Jun 10, 2026
Three closures on the P0-2 long tail:

1. `tabs=POS:ALIGN[:LEADER][,POS:ALIGN[:LEADER]...]` shorthand on
   AddParagraph / SetElementParagraph / AddStyle / SetStylePath.
   Parses comma-separated segments into <w:tabs> with <w:tab> children
   (val + pos + optional leader). POS accepts bare twips or any unit
   SpacingConverter / ParseSignedTwips understands (5cm, 2in, 12pt,
   plus negative twips for hanging tabs). The new <w:tabs> replaces
   any existing tabs strip — same "definitive write" semantics as
   spacing / indent shorthands. Common signoff use case
   (`tabs=9360:right`) and decimal-aligned columns
   (`tabs=2880:decimal,5760:decimal:dot`) now work via the curated
   surface instead of UNSUPPORTED. Helper ApplyTabsShorthand lives
   in WordHandler.Helpers.cs and is shared across the four call sites.

2. ST_OnOff value normalization in GenericXmlQuery.TryCreateTypedChild.
   Boolean inputs (true/false/on/off/yes/no/1/0) emitted through the
   long-tail single-val fallback now normalize to OOXML canonical
   "1"/"0" when the target's typed Val is OnOffValue / TrueFalseValue.
   Word / LibreOffice / Pages render either form, but strict OOXML
   validators and most reference docs expect "1"/"0", and `set
   kinsoku=true` was leaking the literal "true" to disk. Probed via
   reflection on the SDK's typed Val property type so enum / string /
   numeric vals stay untouched.

3. StyleUnsupportedHints: removed the `tabs` entry now that the
   curated shorthand lands schema-valid <w:tabs>.

Verified locally for iOfficeAI#2 (kinsoku=true → <w:kinsoku w:val="1"/>).
Verification for iOfficeAI#1 (tabs shorthand) deferred — the build was
blocked by an unrelated in-progress edit in
src/officecli/Handlers/Pptx/PptxBatchEmitter.cs in the shared
worktree; my changes follow the existing AddTab + ApplyStyleParagraphBorders
patterns directly and are wired symmetrically across the four
add/set entry points.
goworm added a commit that referenced this pull request Jun 16, 2026
Three coupled bugs surfaced under concurrent pipe load, all data-loss-capable:

1. close-race — `__close__` wrote "Closing resident." before handler.Dispose()
   ran, so the client saw success while the resident still held the file. A
   racing `rm + new + open` against the same path could then leave two
   residents listening on the same pipe name (MaxAllowedServerInstances
   permits coexistence); OS randomly routed new writes to either, and
   whatever hit the dying resident was saved to the already-deleted inode
   and lost. Fix: introduce an idempotent ShutdownAsync that cancels →
   drains _commandLock → disposes the handler, and have the __close__
   handler await it BEFORE sending the ack. Dispose() now delegates to the
   same shared task so idle-timeout and explicit-close paths agree.

2. silent fallback in TryResident — when ResidentClient.TrySend returned
   null under burst load, the CLI silently fell back to
   DocumentHandlerFactory.Open(editable: true), racing a second writer
   against the live resident. Under a 100-parallel set flood this lost
   96/100 cells even though every client returned rc=0. Fix: two-step
   probe — ping-pipe TryConnect first (fast, isolated), and only if a
   resident is confirmed alive, TrySend with a generous 30s connect timeout
   and retries. If the send still fails, return a distinct "resident busy"
   error (exit code 3) instead of touching the file directly.

3. main-pipe ECONNREFUSED race — the main command loop created one
   NamedPipeServerStream per iteration and only began constructing the
   next one AFTER the handler fire-and-forget, leaving a gap where no
   instance was listening. Under 50+ concurrent clients the gap cost real
   connections (previously masked by #2). Fix: mirror the pre-create
   pattern already used by RunPingResponderAsync (BUG-FUZZER-R6-B-01) so a
   replacement server is always standing by before the accepted instance
   is handed off.

Cross-platform: all changes use portable APIs (NamedPipeClientStream.Connect
int ms, SemaphoreSlim.WaitAsync(TimeSpan), Task.Run). The pre-create pattern
is already proven on Windows by the ping responder.

Verified:
- 500 concurrent gets: 500/500 ok, no busy errors
- 100 concurrent writes: 100/100 cells land correctly
- close + immediate validate: no "file in use"
- close + rm + new + open: exactly one resident, no zombies
- full resident test suite flakes dropped from 15 → 2 (both pre-existing,
  unrelated WatchServer timing flakes)
goworm added a commit that referenced this pull request Jun 16, 2026
Not a full 2D text-flow fix — that needs an iterative solver.
These two tweaks cover more real-world cases with CSS float:

- wp:anchor (shapes/images with wrapSquare/wrapTight):
  The hardcoded 8px margin is replaced with the anchor's actual
  distT / distB / distL / distR attributes (EMU → pt), with a 6pt
  floor on the "inside" margin so text still has breathing room.

- tblpPr (floating tables):
  * Honor tblpXSpec="left"/"right"/"center" in addition to the
    pre-existing page + tblpX>5000 right-detection heuristic.
  * When horzAnchor="margin", fold tblpX into margin-left (or
    margin-right for float:right) so the column offset shows.
  * When vertAnchor is absent or "text" (the default), fold tblpY
    into margin-top so the vertical offset is visible.
  * Use w:topFromText for margin-top too, combined with the offset.
  * Skip vertAnchor="page"/"margin" — those need absolute positioning
    which breaks text flow (leave for a future pass).

Known limitation documented in KNOWN_ISSUES #2 / #7b: vertAnchor=
page/margin, wrapType=tight with non-rectangular exclusion, and
stacked floating anchors at the same Y remain approximate. A true
fix requires JS measurement or porting Word's 2D flow solver.
goworm added a commit that referenced this pull request Jun 16, 2026
The preceding tighter-margins commit still produced broken wrap in
practice: our <div class="page-body"> uses display:flex;
flex-direction:column, and flex layout ignores float on its children.
So a <table style="float:left"> placed directly under .page-body
rendered inline, and an <img style="float:right"> inside a <p>
wrapped only its own paragraph's text — subsequent paragraphs
started full-width below.

New wrapFloats() runs after paginate() settles:
- Find page-body direct children whose outer CSS carries float:*
  OR that contain a descendant <img style="float:*">.
- For each, wrap it plus following prose siblings in a
  <div class="float-wrap" style="display:block;overflow:auto">.
  The BFC establishes a new formatting context where the float's
  rectangle pushes following text sideways, covering multiple
  paragraphs until a heading, another table, or enough content
  has cleared the float's height.
- Stop absorbing at hard breaks (H1-H9, another <table>, .footnotes).

Verified against Office reference via tools/officeshot.py: a
wrapSquare-right image + wrapType-around left-anchored table now
produce the same text-around-float layout as Word within one page.

Known limitation (documented in KNOWN_ISSUES #2 / #7b): vertAnchor=
page/margin, wrapType=tight with non-rectangular exclusion, and
stacked floats at the same Y still fall outside this approximation.
goworm added a commit that referenced this pull request Jun 16, 2026
…er bugs

- tester #2 (JS): tblHeader continuation iterated table.querySelectorAll('tr')
  which pulled nested subtable rows into the split, mangling nested structures.
  Filter to top-level rows via tr.closest('table')===table.
- tester #3 (JS): restart='newSection' compared only prev/current restart value,
  so two adjacent sections both declaring newSection never triggered reset.
  Page-wrapper now carries data-section-idx; JS resets the running counter on
  section boundary, not on restart-value change.
- tester #4: altChunk ContentType equality failed on 'text/html; charset=utf-8'
  and other '+xml' variants — HTML payload fell through to the RTF stripper.
  Split media-type from params and accept xhtml+xml variants.
- fuzzer A: pgSz w/h parsed via UInt32Value.Value crashed on negative raw
  attrs. Wrap Width/Height access in a swallow-to-fallback helper.
- fuzzer B/C: lnNumType CountBy/Start/Distance typed as Int16Value; malformed
  raw values (non-numeric, overflow) threw on .Value. Parse InnerText manually
  via short.TryParse / int.TryParse.
- fuzzer D/E: huge w:ilvl (10000, Int32.MaxValue) either popped an empty
  open-tag stack (crash) or inflated the HTML by 50× per paragraph (DoS).
  Clamp ilvl to OOXML-legal [0,8].
goworm added a commit that referenced this pull request Jun 16, 2026
…cation, /p[N] schema

Round 6 surfaced 4 bugs:

- tester: 'install vscode' / 'install lms' printed 'Unknown target' to
  stderr from the skill phase before InstallMcpFallback succeeded —
  misleading users into thinking the install failed when it actually
  worked. Skip SkillInstaller.Install entirely when the target is an
  MCP-only entry (McpTargets row with empty SkillAliases).

- fuzzer #1: a JSON-RPC request with a non-string 'method' field
  ('method': 42) crashed into the catch (Exception) path with id=null
  and code -32603, violating the spec. Per JSON-RPC 2.0 §5 such inputs
  must respond -32600 (Invalid Request) with the original id echoed.
  Parse id before method; reject non-string method explicitly.

- fuzzer #2: the 64-char error truncation could split a UTF-16
  surrogate pair, causing the orphan high-surrogate to serialize as
  U+FFFD. Move truncation into a shared helper that backs off one
  char when the cut lands on a high surrogate. Both error sites
  (McpServer unknown-format and SchemaHelpLoader unknown-element)
  now go through it.

- bt: schema docs claimed '/p[N]' as a positional shorthand for
  paragraphs (and '/p[N]/r[N]' for runs) but the Word navigator
  doesn't accept them — only '/body/p[N]' resolves. Pre-existing
  doc/code mismatch, removed the unsupported shorthand from the
  schema rather than implementing missing handler support to keep
  this round contained to documentation truth.
goworm added a commit that referenced this pull request Jun 16, 2026
…ide 'instruction'

Round 6 audit caught the help/schema vs implementation mismatch:
field.json declares 'instr' as the canonical raw-instruction key (it is
the same name Get surfaces on instrText readback) with 'instruction'
and 'code' as accepted aliases, and the help docs use 'instr' in their
example. But AddField only ever read 'instruction' from the props
dict, so a user copy-pasting the documented 'instr=...' example saw
'Unknown field type field' from a different code path. Centralize the
three keys in a single GetRawFieldInstruction helper and update the
error message to mention all three accepted forms.

(Note: Bug #2 from Round 6 — SchemaContractTests can't load schema
files because the test csproj lacks a schemas/**/*.json copy step —
is fixable with an MSBuild <None CopyToOutputDirectory> entry, but
the test project tree is intentionally untracked per project policy.
Fix is applied locally to unblock contract testing during this round
but is not part of this commit; project owner can promote it when
tracking the test tree.)
goworm added a commit that referenced this pull request Jun 16, 2026
…light, drop static contrast Gate 4

Min cycle and Delivery Gate were two passes at the same checks: Min
cycle #2 (view html) overlapped Gate 5, #3 (token query) overlapped
Gate 2's grep, #4 (close+validate) overlapped Gate 1, #5 (target
viewer) was a 4-line restatement of the view-html-vs-runtime rule
already in Common Workflow #6. The intro itself called Min cycle
"warmup" while Delivery Gate did the real work.

Replace the 6-step Min cycle with a single-line Pre-flight using
view annotated (the one Min-cycle item Gates don't cover — font/size
violations and overflow surfaced before the gate run). Move target
viewer + fix-and-rerun guidance into a tight "After all gates pass"
subsection at the end of QA.

Drop the static-contrast Gate 4 (query+jq dark-fill scan): its
hardcoded list of five hex values misses any deck using a custom
dark fill, while Gate 5's visual review catches dark-on-dark across
all fills. Renumber: Gate 5 → Gate 4. Update cross-references
(intro line, status banner, "Gates 1-4 cannot see rendered slides"
→ "Gates 1-3", "Gates 2-5 are how you catch" → "Gates 2-4").
goworm added a commit that referenced this pull request Jun 16, 2026
…stale)

Every chart series stores its cached values inside
<c:numCache><c:pt><c:v>...</c:v></c:pt>. When source cells change without
an Excel refresh, the chart silently renders old data — agents and
downstream consumers (pdf export, screenshot) see stale numbers with no
warning.

view issues --type chart_cache_stale now resolves each <c:f> against
live cell values and compares element-wise to the persisted numCache,
emitting "Chart numCache out of sync" with both vectors in Context.

Off by default — walks every chart × series × point and re-reads
source ranges, so default `view issues` stays cheap. Sheet-prefix
parsing reuses ChartRefSheetExists; charts whose ref already broke
(missing sheet) are skipped because #2 already reports them.
goworm added a commit that referenced this pull request Jun 16, 2026
Three closures on the P0-2 long tail:

1. `tabs=POS:ALIGN[:LEADER][,POS:ALIGN[:LEADER]...]` shorthand on
   AddParagraph / SetElementParagraph / AddStyle / SetStylePath.
   Parses comma-separated segments into <w:tabs> with <w:tab> children
   (val + pos + optional leader). POS accepts bare twips or any unit
   SpacingConverter / ParseSignedTwips understands (5cm, 2in, 12pt,
   plus negative twips for hanging tabs). The new <w:tabs> replaces
   any existing tabs strip — same "definitive write" semantics as
   spacing / indent shorthands. Common signoff use case
   (`tabs=9360:right`) and decimal-aligned columns
   (`tabs=2880:decimal,5760:decimal:dot`) now work via the curated
   surface instead of UNSUPPORTED. Helper ApplyTabsShorthand lives
   in WordHandler.Helpers.cs and is shared across the four call sites.

2. ST_OnOff value normalization in GenericXmlQuery.TryCreateTypedChild.
   Boolean inputs (true/false/on/off/yes/no/1/0) emitted through the
   long-tail single-val fallback now normalize to OOXML canonical
   "1"/"0" when the target's typed Val is OnOffValue / TrueFalseValue.
   Word / LibreOffice / Pages render either form, but strict OOXML
   validators and most reference docs expect "1"/"0", and `set
   kinsoku=true` was leaking the literal "true" to disk. Probed via
   reflection on the SDK's typed Val property type so enum / string /
   numeric vals stay untouched.

3. StyleUnsupportedHints: removed the `tabs` entry now that the
   curated shorthand lands schema-valid <w:tabs>.

Verified locally for #2 (kinsoku=true → <w:kinsoku w:val="1"/>).
Verification for #1 (tabs shorthand) deferred — the build was
blocked by an unrelated in-progress edit in
src/officecli/Handlers/Pptx/PptxBatchEmitter.cs in the shared
worktree; my changes follow the existing AddTab + ApplyStyleParagraphBorders
patterns directly and are wired symmetrically across the four
add/set entry points.
goworm added a commit that referenced this pull request Jun 16, 2026
PowerPoint accepts a second plain <p:transition> sibling after <p:timing>
in addition to a primary transition (typically wrapped in mc:AlternateContent
for morph / p14 / p15 decks). The dump scan stopped at the first
<p:transition> hit and treated everything past it as belonging to the
mc-wrap or the timing block, so a trailing <p:transition advTm="5000"/>
was silently dropped. ReadSlideTransition's regex-based fallback even
saw the wrong advanceTime — it harvested advTm from the mc-inner
<p:transition> attributes rather than the trailing element.

Capture a TrailingTransitionXml field in SlideExoticContent: after the
first <p:transition> slice ends (or the enclosing mc:AlternateContent
wrap closes), look for a second <p:transition> direct child and capture
its outer XML. Emit it via the existing raw-set append on /p:sld so the
trailing slice lands after timing / extLst (mirrors source order on the
animations.pptx slide-6 fixture).

When mc:AlternateContent wraps the primary transition, semantic transition
/ advanceTime / advanceClick props are already stripped from slideProps
upstream, so the trailing raw-set is the only emit and no double-write
occurs. Plain-plain double-transition slides (no mc wrap) remain a known
edge case — the semantic emit covers transition #1 and the raw-set covers
transition #2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants