Release v0.53.0
Date: 2026-04-04
Production-grade defaults, editor architecture decomposition, and canvas render performance.
Milestone A: Production-Grade Default
HARDEN_PRODUCTION tri-state config
HARDEN_PRODUCTION is now auto-derived from NODE_ENV=production when unset. Strict .refine() validation rejects invalid values like "banana". The resolved config uses Object.freeze() with HARDEN_PRODUCTION_EXPLICIT to distinguish auto-derived from explicit.
Named-user bootstrap
New scripts/generate-editor-users.mjs CLI generates EDITOR_USERS_JSON with crypto-strong keys. Shared-key identity advisory fires only when editor-login-capable (API_KEY_EDITOR, API_KEY_ADMIN, or legacy API_KEY) and no EDITOR_USERS_JSON.
Cluster-aware rate limiting
RATE_LIMIT_STORE=redis with REDIS_URL enables Redis-backed rate limiting. Dynamic ioredis import, startup fail-fast ping, RATE_LIMIT_FAIL_OPEN for degraded-mode semantics, graceful shutdown. rateLimitRedis added to GET /health/ready with state-transition logging.
Documentation sweep
8 docs files updated: deployment-guide, api-guide, evaluator-guide, release-checklist, runbook, README, .env examples, validate-deploy.sh.
Milestone B: Editor Decomposition
Store decomposition
ui.store.ts:selectionAnchorId,inlineEditingNodeId,richTextPanelModeassets.store.ts:assets,assetsLoaded,setAssetsclipboard.store.ts:clipboard,copyNode,cutNode,pasteNodeeditor.store.tsreduced from ~35 to ~23 fields/actions
Component decomposition
EditorShellsplit intoEditorHeader(~280 lines) +DialogCluster(~80 lines)EditorShellreduced from 631 to ~290 lines, 17 to 8 store subscriptionsPropertiesTabextracted with sharedPropertiesTabIdtype
Preview selector facade
preview.selectors.ts with usePreviewDataJson, useUpdatePreviewDataJson, usePreviewDisplayMode, useSetPreviewDisplayMode. All 8 consumers migrated.
Milestone C: Performance & Resilience
Canvas render performance
useIsNodeSelected(id)granular selector: per-node boolean subscription prevents N re-renders on selection changeReact.memoonNodeWrapper- Per-node find-replace selectors in NodeWrapper
OutlinePanelselection moved intoSortableOutlineRowcomponent- HeadingNodeView, TextNodeView, TableNodeView migrated to shared selector
API client resilience
fetchWithTimeout: 30s default, composable AbortSignal, already-aborted fast path, listener cleanup infinallywithRetry: exponential backoff (3 attempts, 1s base) for idempotent GETs- All 6
fetch()calls inapi.tsreplaced: 30s default, 60s renders, 120s uploads fetchPreviewStatusresult-based retry (3 attempts onservice_unreachableor 5xx)TimeoutErrorclassified as transport failure in PublishGateDialog, PreviewPanel, DockedPreview
Save failure retry
Persistent retryable status with retrySave callback that bypasses publish gate. Tab-scoped via retryTabIdRef (clears on tab switch). Edit-invalidated (clears when template changes after failure).
API test coverage expansion
- Asset upload: 7 cases (happy path, missing file, 413 size limit, SVG rejection, MIME mismatch, 401, 403)
- Rate-limit enforcement: 4 cases (auth endpoint limit, render route limit, headers, retry-after)
- CORS preflight: 3 cases (allowed origin, disallowed origin, wildcard mode)
Upgrade notes
Breaking: HARDEN_PRODUCTION default change
HARDEN_PRODUCTION now defaults to true when NODE_ENV=production. Existing production deployments that relied on hardening being off by default must add HARDEN_PRODUCTION=false to their environment. All compose files and .env examples have been updated with explicit HARDEN_PRODUCTION=false for evaluation use.
Compose file changes
compose.yaml, compose.postgres.yaml, and compose.container.yaml now include HARDEN_PRODUCTION: "false". Existing custom compose overrides are unaffected.
Validation
CI-verified
ci(lint, typecheck, editor build, check-version)test-file-mode(API file-mode tests)test-sqlserver(SQL Server storage tests)test-e2e(Playwright editor workflows)test-e2e-auth(Playwright auth flows)docker-build-smoke(Docker image build + health check)
Locally verified
pnpm --filter @pulp-engine/editor typecheck— cleanpnpm --filter @pulp-engine/editor test— 794 passedpnpm --filter @pulp-engine/editor build— succeedspnpm --filter @pulp-engine/api typecheck— cleanpnpm --filter @pulp-engine/api test:file— file-mode tests passingpnpm --filter @pulp-engine/api test -- src/__tests__/asset-upload.test.ts— 7 passedpnpm --filter @pulp-engine/api test -- src/__tests__/rate-limit-enforcement.test.ts— 4 passedpnpm --filter @pulp-engine/api test -- src/__tests__/security-hardening.test.ts— passingpnpm --filter @pulp-engine/api test -- src/__tests__/config-validation.test.ts— passingpnpm lint— cleannode scripts/check-version.mjs— passed (on tagged commit)
Not verified
- PostgreSQL storage integration (covered by CI
test-file-modejob with Prisma, not exercised locally) - SQL Server storage (covered by CI
test-sqlserverjob, not exercised locally) - S3 asset binary store (no CI or local coverage; requires real S3 bucket)
- Redis rate-limit store end-to-end (config validation tested, runtime requires real Redis)
- Container and socket render modes (require Docker runtime)
- Docker image production startup (covered by CI
docker-build-smoke, not exercised locally)