Skip to content

Optional OfficeCLI rendering: xlsx charts → PNG#23

Merged
moonlight-lupin merged 2 commits into
mainfrom
feat/officecli-chart-render
Jul 18, 2026
Merged

Optional OfficeCLI rendering: xlsx charts → PNG#23
moonlight-lupin merged 2 commits into
mainfrom
feat/officecli-chart-render

Conversation

@moonlight-lupin

Copy link
Copy Markdown
Owner

Narrow-scope, opt-in integration of the third-party OfficeCLI binary for the one thing openpyxl cannot do: draw an .xlsx chart to an image. Chart generation stays on openpyxl — nothing here is required.

Stacks on #22 (it extends workbook.py), so this PR targets that branch. Merge #22 first.

What it does

Set dashboard.render_png: true on an xlsx visualise plan → one chart_png artefact per chart, cropped to the chart, plus the renderer version in details.renderer.

Cropping uses view <file> screenshot --range '<chart data-path>' (e.g. /By region/chart[1], from query <file> chart --json). A cell range does not work — a chart is a floating object, and a cell-range crop renders blank.

Optionality (the important bit)

Binary Result
present .xlsx + one PNG per chart, success
absent .xlsx written exactly as before, success_with_warnings + a clear warning — never an error

envcheck.py probes it beside Tesseract.

Two things found by testing, not assumed

  1. SVG is PowerPoint-only. I'd hoped to inline SVG into the HTML dashboard; view <xlsx> svg returns "SVG preview is only supported for .pptx files." So Excel charts render to PNG.
  2. OfficeCLI leaves a resident holding an OS file handle — the workbook could not be moved, deleted or reopened afterwards (Windows PermissionError). Every render path now closes it in a finally; a regression test asserts the workbook is releasable after a render.

Safety / scope

  • The toolkit's only subprocess: argument list, never shell=True, no caller data interpolated into a shell string, time-boxed; non-zero exit or timeout degrades to "no image" rather than raising.
  • Read-only — openpyxl writes the workbook; OfficeCLI only reads it to make a picture. It never authors or mutates the numbers.
  • Docs describe it as third-party (Apache-2.0) and attribute the "runs fully locally" property to the vendor rather than asserting it on the toolkit's behalf.

Test plan

Tests pass with or without the binary installed (the live path self-skips, so CI is unaffected).

  • 94 pytest · 61 engine self-tests · bin/data-lint · golden plans — all green
  • Verified end-to-end against OfficeCLI v1.0.138

🤖 Generated with Claude Code

moonlight-lupin and others added 2 commits July 18, 2026 20:57
Narrow-scope, opt-in integration of the third-party OfficeCLI binary for the one
thing openpyxl cannot do: draw an .xlsx chart to an image. Chart *generation*
stays on openpyxl; nothing here is required.

- scripts/officecli_render.py — available()/version()/chart_paths()/
  render_chart_pngs()/render_sheet_png()/status(). Absent binary => empty results,
  no exception. Verified against OfficeCLI v1.0.138.
- dashboard.render_png (schema + runtime): emits one chart_png artefact per chart,
  cropped to the chart's bounding box via `view <f> screenshot --range <data-path>`
  (a cell range does NOT work — a chart is a floating object). Records the renderer
  version in details.renderer. Binary absent => the .xlsx is still written and the
  run returns success_with_warnings, never an error.
- Releases the OfficeCLI *resident* in a finally. Reading a document starts a
  resident holding an OS file handle; without closing it the workbook could not be
  moved/deleted/reopened afterwards (Windows PermissionError). Found empirically;
  regression test asserts the workbook is releasable after a render.
- Subprocess hygiene (the toolkit's only subprocess): argument list, never
  shell=True, no caller data interpolated into a shell string, time-boxed, non-zero
  exit/timeout degrades rather than raises. Read-only — never authors or mutates a
  workbook.
- envcheck probes it beside Tesseract; COMPATIBILITY + workbook-charts.md document
  it as optional/third-party, and attribute the "fully local" property to the
  vendor rather than asserting it. CHANGELOG under 0.7.0.

Tests pass with or without the binary installed (the live path self-skips).
94 pytest / 61 engine self-tests / data-lint / golden plans green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OfficeCLI is optional, so the contract should be absolute: once the .xlsx is
written, nothing in the renderer can fail the run. It was not — a probe found
three escape routes.

- officecli_render: every public function is now total. render_chart_pngs /
  render_sheet_png no longer propagate a filesystem error from mkdir (an
  unwritable out_dir killed a run whose workbook had already succeeded);
  chart_paths, version, status and release swallow unexpected subprocess-layer
  exceptions. release() runs in a finally, so it especially must never raise.
- agent_runtime._run_visualise: the render_png block, including the
  `import officecli_render`, is wrapped — a missing or broken adapter module
  becomes a warning rather than an ImportError that costs the run its artefact.

Two regression tests, both mutation-checked (they fail when either guard is
narrowed): a unit test across every failure mode, and an end-to-end test through
run_plan asserting status=success_with_warnings with the xlsx artefact intact.

Gates: data-lint OK, 96 pytest, 63 standalone, 6/6 golden plans.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@moonlight-lupin
moonlight-lupin changed the base branch from cursor/viz-blocks-and-analyse-handoff-3e24 to main July 18, 2026 13:10
@moonlight-lupin
moonlight-lupin merged commit 10694b6 into main Jul 18, 2026
3 checks passed
cursor Bot added a commit that referenced this pull request Jul 18, 2026
@moonlight-lupin
moonlight-lupin deleted the feat/officecli-chart-render branch July 19, 2026 02:02
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.

1 participant