Pulp Engine Document Rendering
Get started
Release v0.72.0

Release v0.72.0 — Editor UI parity

Date: 2026-04-17 Tag: v0.72.0

Summary

Four structural authoring features that were already supported by the schema and renderers are now reachable from the editor’s properties panel. Users previously had to hand-edit template JSON to access them. All four now have first-class UI with full test coverage.

This release also fixes a product bug where the Letter and Invoice starter packs were silently producing A4-paper templates, and repairs accumulated test-vs-code drift across six untouched test files (the editor suite had been red at 72/80 files despite the code being correct).

What shipped

Chart combo type and data labels (ChartNodeProps)

  • 'combo' added to the chart-type picker — bar series plus overlaid line series on one panel, with an optional secondary y-axis.
  • showDataLabels checkbox and dataLabelPosition select (outside / inside / center / auto) exposed for every chart type that actually honours them: bar, horizontalBar, stackedBar, groupedBar, and combo. The set matches both the generic schema and the combo renderer’s runtime contract (packages/chart-renderer/src/combo.ts).
  • Secondary-axis editor (label / min / max) with an invalid-number guard: non-finite numeric drafts normalise to undefined before the object is built, so a half-typed value like "abc" in the Min field cannot silently yield {} and enable the secondary scale with defaults.
  • Per-field dirty refs isolate Min and Max drafts from Label. An invalid own-blur keeps the draft and inline warning visible and does not commit. A sibling Label commit preserves any previously-committed Min/Max values rather than dropping them when the sibling draft is invalid.

TOC page numbers and leader style (TocNodePropsInline)

  • showPageNumbers checkbox and leaderStyle select (dots / dashes / none).
  • Helper copy updated to name the PDF-only stamping behaviour: HTML export and on-screen preview leave the page-number slot blank; the PDF renderer stamps actual numbers post-render via @pulp-engine/pdf-transform#stampTocPageNumbers.
  • The control never commits undefined once touched — undefined and 'dots' are semantically identical (both render dot leaders per the model default), so forcing a distinct “unset” option would be ceremony with no observable effect.

PPTX slide layout (RenderConfigEditor)

  • Tri-state Default / Preset / Custom radio. Default commits { pptxLayout: undefined } so the field drops from the serialized template (PPTX renderer falls back to its own LAYOUT_WIDE default).
  • Presets cover LAYOUT_WIDE (13.333 × 7.5 in), LAYOUT_16x9 (10 × 5.625 in), and LAYOUT_4x3 (10 × 7.5 in).
  • Custom mode uses raw string width/height drafts so mid-typed values do not fight a number input; parsing happens at blur with bounds enforcement (positive, ≤ 100 inches per the schema limit). Invalid values show an inline error and do not commit.
  • Drafts for both preset and custom modes survive round trips through Default.

Table custom header rows (TableHeaderRowsEditor, new lazy-loaded panel)

  • Structural authoring for prepended <thead> rows: content (inline HTML / Handlebars), colspan, horizontal alignment, row add/remove/move, cell add/remove. Existing JSON-authored row and cell style objects round-trip untouched — style editing itself is deferred to a follow-up so this release can ship the structural surface.
  • Live per-row colspan-sum chip — green when the row’s cell colspans total columns.length, red warning otherwise with the exact mismatch text. Inline nested-<table> rejection mirrors the schema’s superRefine guard at write time.
  • Zero-columns guard: when the table has no columns yet, the panel renders a disabled empty state rather than offering an add-row button that would produce schema-invalid rows.
  • repeatHeader warning: when repeatHeader is off and any header rows are configured, an inline warning surfaces that those rows will not render (HTML/DOCX emit <thead> only when repeatHeader !== false).
  • Row identity is tracked via synthetic UUIDs maintained in lock-step with headerRows by every local structural handler, routed through a single commitHeaderRows helper. Local moves preserve in-flight drafts across reorders even when rows share identical colspan signatures (a common case because the default new-row shape is a single cell spanning all columns).
  • External-mutation reset uses useLayoutEffect (not useEffect) so the row-id reseed runs synchronously before paint. Undo, redo, node switches, and out-of-band writes therefore tear down and remount every cell editor before the user sees a frame — no stale draft can ever attach to the wrong logical row, not even in a same-length same-shape undo-of-move.

Other fixes

  • Starter-pack Letter paper regression: FINANCIAL_RENDER_CONFIG and LETTER_RENDER_CONFIG had both drifted to paperSize: 'A4'. The Letter and Invoice starter packs now correctly default to Letter paper as their names imply.
  • RENDER_PREVIEW_RESERVED_SLOTS forwarding in RENDER_MODE=child-process — previously the env var was only read in the API process; the worker spawned by ChildProcessRenderDispatcher got a fixed allow-list and silently ignored it. The documented RENDER_PREVIEW_RESERVED_SLOTS=0 rollback path in release-v0.65.0.md was non-functional in this mode. Added positive and no-leakage regression tests.
  • RENDER_MODE=socket controller startup from pnpm-deployed bundles — strict === in the entry-point guard compared process.argv[1] against fileURLToPath(import.meta.url), but pnpm symlink resolution made the two paths diverge. realpathSync(process.argv[1]) now normalises both sides.

Editor test-suite drift cleanup (test-only)

Six files in the editor test suite had drifted out of sync with their production counterparts — no production behaviour changed. Repaired in a dedicated commit so main goes from 72/80 files and 1149/1169 tests green to 80/80 files and 1174/1174 tests green:

  • LoginGate.test.tsx / login-identity.test.tsx: setStoredToken assertions updated for the C.0 Stage 2 tenantId parameter (6th argument).
  • login-identity.test.tsx: getStoredScope added to the auth module mock so EditorShell renders under test.
  • EditorHeader.test.tsx / EditorShell.lazy.test.tsx: History assertions now target the top-level toolbar button (aria-label="Version history") instead of the removed dropdown menu item.
  • PreviewPanel.test.tsx: 7 regex matches updated from pulp-engine api to pulp engine api (post-rename copy alignment).
  • ChartNodeView.test.tsx: node-type chip assertion case-fixed from chart to Chart.

Docs + website sync

  • Website showcase (apps/website/src/pages/showcase.astro), docs/editor-guide.md, and docs/evaluator-guide.md now say the new-template dialog offers 25 built-in starting points (Blank pinned at the top plus 24 tabbed packs across 4 categories), superseding the older “30+ starter packs” and “23 starter packs” phrasings.
  • Editor screenshot refreshed (apps/website/public/screenshots/editor.png).

Breaking changes

None. All editor UI additions are additive; templates authored before this release open unchanged, and every new field committed by the new UI is also accepted by the pre-existing schema.

Residual notes

  • Row and cell style editing for custom header rows is not yet exposed. JSON-authored styles are preserved on round-trip, but adding the style editor to TableHeaderRowsEditor is deferred to a follow-up so it can share a reusable style primitive with the rest of the panel.
  • The editor’s ad-hoc Claude-agent permission list (.claude/settings.json) accumulated a handful of exploratory bash rules during authoring. Left uncommitted; not part of this release.