Pulp Engine Document Rendering
Get started
Release v0.7.0

Pulp Engine v0.7.0

Release date: 2026-03-19

Highlights

  • richText editing upgraded to commercial grade: inline canvas editing, full Tiptap toolbar with colour palette, WCAG-compliant accessibility, and comprehensive test coverage.
  • Keyboard drag/reorder for all node types with live-region screen-reader announcements.
  • Empty-state affordances across heading, text, table, and richText nodes.
  • Browser-level Playwright E2E coverage integrated into CI.
  • Bug fix: semaphore slot leak when Chrome browser startup fails.

Editor

richText — inline canvas editing

richText nodes can now be edited directly on the document canvas without opening the Properties panel. Double-click (or press Enter with the node focused) to open the Tiptap editor inline. Press Escape to close it and return focus to the node. The Properties panel shows a status notice when inline editing is active and keeps both the Visual and JSON tabs accessible.

richText — Visual editor toolbar

The Tiptap toolbar is now feature-complete:

  • Bold, Italic, Underline — toggle marks on selected text; buttons carry aria-pressed to reflect their active state.
  • Colour palette — compact 3-column grid of 8 presets (Black, Dark gray, Red, Amber, Green, Blue, Purple, Pink) plus a “No color” option. No native <input type="color"> is used.
  • Bullet list / Numbered list — toggle ordered and unordered lists with one-level nesting.
  • Link — inline Radix popover with URL input, Save / Remove / Cancel actions, format hint (https:// · mailto: · tel:), live warning on unsafe protocols, and automatic https:// prefixing for bare domains.
  • Shift+Enter — hard line break.

richText — accessibility

The toolbar is a role="toolbar" region with roving tabindex navigation (ArrowLeft/Right, Home, End). All buttons carry aria-label. The inline-editing notice in the Properties panel has role="status". JSON textarea parse errors use role="alert". The “Show schema” collapsible uses aria-expanded.

Keyboard drag/reorder

All drag handles are keyboard-focusable. With a handle focused:

  1. Space — pick up the node.
  2. Arrow Up / Down — move within the parent container.
  3. Space — drop at the new position.
  4. Escape — cancel and restore original position.

A live-region announcement is read on each step, naming the node via resolveNodeLabel (which strips Handlebars expressions and truncates long text). Drag handles on nodes in inline-edit mode have their tabIndex suppressed to avoid interrupting text-editing keyboard flow.

Empty-state affordances

Heading, text, table, and richText nodes now show dashed-border placeholders when their content is empty. richText nodes show “Double-click to edit” when selected and “Rich text” when not selected.

Tab persistence

The Visual / JSON tab selection is persisted in the editor store. Switching between nodes no longer resets to a default tab.

Test coverage

Editor unit tests grew from 54 (3 files) to 178 (7 files):

FileTestsScope
template-validator.test.ts28Unchanged
rich-text-tiptap.test.ts20Unchanged
RichTextVisualEditor.test.tsx60Toolbar accessibility, roving tabindex, colour palette, link popover, protocol validation, URL normalisation
RichTextNodeView.test.tsx20Inline canvas editing, empty-state CSS classes, setInlineEditingNode lifecycle, keyboard accessibility
RichTextNodeProps.test.tsx15Inline-editing notice, tab initialisation, role="status", role="alert", aria-expanded
NodeWrapper.test.tsx7Drag handle tabIndex (idle vs. editing), aria-label, role="button"
node-label.test.ts28Section/heading/text/type-name label resolution, Handlebars strip, 25-char truncation, makeLabelFor

Playwright E2E scenarios (new):

FileScenariosScope
rich-text.spec.ts9Inline enter/exit, bold persistence through store round-trip, link popover flow, Visual/JSON coordination, undo/redo, Handlebars survival
keyboard-reorder.spec.ts3Drag handle tabindex, Space+Arrow reorder, Escape cancel

E2E tests run in a dedicated test-e2e CI job (Playwright, Chromium). The Playwright report is uploaded as an artifact on failure.

Bug fix — semaphore slot leak on browser startup failure

packages/pdf-renderer/src/renderer.ts — if getBrowser() or newPage() threw an exception before the PDF stream was created, the acquired semaphore slot was previously leaked (one slot permanently lost per failure). Both render() and renderStream() now use a try/catch that calls closePage() directly in the failure path, ensuring the slot is released regardless of where the exception occurs. A pageClosed guard prevents double-release. See docs/memory-risk-validation-report.md § Test 5 for details.

Validation note

Local pnpm test cannot complete in the current sandbox due to Vitest worker process permissions (EPERM). CI on GitHub provides the authoritative post-push validation. Playwright E2E tests require a running editor dev server and are not run locally in this environment.