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-pressedto 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/Cancelactions, format hint (https:// · mailto: · tel:), live warning on unsafe protocols, and automatichttps://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:
- Space — pick up the node.
- Arrow Up / Down — move within the parent container.
- Space — drop at the new position.
- 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):
| File | Tests | Scope |
|---|---|---|
template-validator.test.ts | 28 | Unchanged |
rich-text-tiptap.test.ts | 20 | Unchanged |
RichTextVisualEditor.test.tsx | 60 | Toolbar accessibility, roving tabindex, colour palette, link popover, protocol validation, URL normalisation |
RichTextNodeView.test.tsx | 20 | Inline canvas editing, empty-state CSS classes, setInlineEditingNode lifecycle, keyboard accessibility |
RichTextNodeProps.test.tsx | 15 | Inline-editing notice, tab initialisation, role="status", role="alert", aria-expanded |
NodeWrapper.test.tsx | 7 | Drag handle tabIndex (idle vs. editing), aria-label, role="button" |
node-label.test.ts | 28 | Section/heading/text/type-name label resolution, Handlebars strip, 25-char truncation, makeLabelFor |
Playwright E2E scenarios (new):
| File | Scenarios | Scope |
|---|---|---|
rich-text.spec.ts | 9 | Inline enter/exit, bold persistence through store round-trip, link popover flow, Visual/JSON coordination, undo/redo, Handlebars survival |
keyboard-reorder.spec.ts | 3 | Drag 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.