From 8660126fd6575f40ed9553e8d0b0cf5a11fbf92a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 11:00:04 +0200 Subject: [PATCH] docs(plans): add render pipeline performance plan --- .../2026-04-04-render-pipeline-performance.md | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 docs/plans/2026-04-04-render-pipeline-performance.md diff --git a/docs/plans/2026-04-04-render-pipeline-performance.md b/docs/plans/2026-04-04-render-pipeline-performance.md new file mode 100644 index 0000000..0d77cd3 --- /dev/null +++ b/docs/plans/2026-04-04-render-pipeline-performance.md @@ -0,0 +1,176 @@ +# Render Pipeline Performance Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Reduce repeated preview/render CPU work by caching decoded sources, skipping unused histogram work, deduplicating identical preview requests, and coalescing rapid preview updates. + +**Architecture:** The implementation keeps the existing preview/render feature set but tightens the hot path in layers. First, `source-loader` becomes safely shareable across abortable requests. Next, preview rendering becomes histogram-aware per caller instead of always-on. Then the worker client and hook layer collapse duplicate requests and rapid-fire updates toward the latest relevant preview. All work stays inside the current React/worker pipeline so the rollout is incremental and low-risk. + +**Tech Stack:** Next.js 16, React 19, TypeScript, Vitest, Web Workers, React Flow + +--- + +### Task 1: Make source bitmap caching work for abortable preview/render requests + +**Files:** +- Modify: `lib/image-pipeline/source-loader.ts` +- Create: `tests/image-pipeline/source-loader.test.ts` + +**Step 1: Write the failing tests** + +Add tests that prove: +- two abortable calls for the same URL reuse one underlying fetch/decode pipeline +- an aborted consumer does not poison a later successful consumer +- a failed fetch clears the cache so the next attempt can retry + +**Step 2: Run test to verify it fails** + +Run: `pnpm test tests/image-pipeline/source-loader.test.ts` +Expected: FAIL because the cache currently bypasses any request with `signal`. + +**Step 3: Write minimal implementation** + +Refactor `loadSourceBitmap` so fetch/decode caching is keyed by `sourceUrl` regardless of `signal`, while each caller still gets local abort semantics. Keep failure cleanup behavior. + +**Step 4: Run test to verify it passes** + +Run: `pnpm test tests/image-pipeline/source-loader.test.ts` +Expected: PASS + +**Step 5: Commit** + +Run: +- `git add tests/image-pipeline/source-loader.test.ts lib/image-pipeline/source-loader.ts` +- `git commit -m "fix(image-pipeline): reuse source bitmaps for abortable requests"` + +### Task 2: Make preview histogram generation opt-in + +**Files:** +- Modify: `lib/image-pipeline/preview-renderer.ts` +- Modify: `lib/image-pipeline/worker-client.ts` +- Modify: `lib/image-pipeline/image-pipeline.worker.ts` +- Modify: `hooks/use-pipeline-preview.ts` +- Modify: `components/canvas/nodes/adjustment-preview.tsx` +- Modify: `components/canvas/nodes/compare-surface.tsx` +- Modify: `components/canvas/nodes/render-node.tsx` +- Modify: `tests/use-pipeline-preview.test.ts` + +**Step 1: Write the failing tests** + +Extend tests so they prove: +- callers can disable histogram work and still get image output +- histogram defaults remain available where explicitly needed +- compare/fullscreen preview call sites request no histogram + +**Step 2: Run test to verify it fails** + +Run: `pnpm test tests/use-pipeline-preview.test.ts` +Expected: FAIL because the preview API does not yet accept histogram control. + +**Step 3: Write minimal implementation** + +Add an `includeHistogram` option through preview renderer, worker request/response handling, and `usePipelinePreview`. Keep histogram enabled for `AdjustmentPreview`, disable it for `CompareSurface` and the fullscreen preview in `render-node`. + +**Step 4: Run tests to verify they pass** + +Run: `pnpm test tests/use-pipeline-preview.test.ts` +Expected: PASS + +**Step 5: Commit** + +Run: +- `git add tests/use-pipeline-preview.test.ts lib/image-pipeline/preview-renderer.ts lib/image-pipeline/worker-client.ts lib/image-pipeline/image-pipeline.worker.ts hooks/use-pipeline-preview.ts components/canvas/nodes/adjustment-preview.tsx components/canvas/nodes/compare-surface.tsx components/canvas/nodes/render-node.tsx` +- `git commit -m "perf(image-pipeline): skip unused preview histograms"` + +### Task 3: Deduplicate identical in-flight preview requests + +**Files:** +- Modify: `lib/image-pipeline/worker-client.ts` +- Modify: `tests/use-pipeline-preview.test.ts` +- Create: `tests/image-pipeline/worker-client.test.ts` + +**Step 1: Write the failing tests** + +Add tests that prove: +- two identical preview requests share one worker/fallback execution +- a different preview width or histogram flag creates a separate request +- aborted subscribers are removed without cancelling surviving identical consumers + +**Step 2: Run tests to verify they fail** + +Run: `pnpm test tests/image-pipeline/worker-client.test.ts tests/use-pipeline-preview.test.ts` +Expected: FAIL because every preview call currently creates a fresh worker request. + +**Step 3: Write minimal implementation** + +Add an in-flight dedupe layer keyed by source URL, pipeline steps, preview width, and histogram flag. Keep abort handling per consumer so the shared underlying request only aborts when no consumers remain. + +**Step 4: Run tests to verify they pass** + +Run: `pnpm test tests/image-pipeline/worker-client.test.ts tests/use-pipeline-preview.test.ts` +Expected: PASS + +**Step 5: Commit** + +Run: +- `git add tests/image-pipeline/worker-client.test.ts tests/use-pipeline-preview.test.ts lib/image-pipeline/worker-client.ts` +- `git commit -m "perf(image-pipeline): dedupe inflight preview requests"` + +### Task 4: Coalesce rapid preview updates toward the latest state + +**Files:** +- Modify: `hooks/use-pipeline-preview.ts` +- Modify: `tests/use-pipeline-preview.test.ts` +- Modify: `components/canvas/nodes/use-node-local-data.ts` +- Create or Modify: targeted test covering rapid preview invalidation behavior + +**Step 1: Write the failing tests** + +Add tests that prove: +- rapid sequential preview invalidations only commit the latest visible result +- stale finished renders do not overwrite newer preview state +- preview rendering is deferred/coalesced enough that intermediate slider churn does not fan out one render per transient value + +**Step 2: Run tests to verify they fail** + +Run: `pnpm test tests/use-pipeline-preview.test.ts` +Expected: FAIL because preview work is only lightly delayed today and each update schedules its own render path. + +**Step 3: Write minimal implementation** + +Tighten preview scheduling so it is latest-only and non-urgent during rapid edits. Keep persistence behavior intact, but ensure preview updates collapse around the newest pipeline hash rather than every intermediate one. + +**Step 4: Run tests to verify they pass** + +Run: `pnpm test tests/use-pipeline-preview.test.ts` +Expected: PASS + +**Step 5: Commit** + +Run: +- `git add tests/use-pipeline-preview.test.ts hooks/use-pipeline-preview.ts components/canvas/nodes/use-node-local-data.ts` +- `git commit -m "perf(image-pipeline): coalesce rapid preview updates"` + +### Task 5: Verify the optimization stack end-to-end + +**Files:** +- Verify only + +**Step 1: Run targeted pipeline tests** + +Run: `pnpm test tests/image-pipeline/source-loader.test.ts tests/image-pipeline/worker-client.test.ts tests/use-pipeline-preview.test.ts` +Expected: PASS + +**Step 2: Run broader canvas/image regression coverage** + +Run: `pnpm test tests/light-adjust-node.test.ts components/canvas/__tests__/compare-node.test.tsx` +Expected: `tests/light-adjust-node.test.ts` should pass. `components/canvas/__tests__/compare-node.test.tsx` is currently failing in the clean baseline with missing `CanvasGraphProvider`; if still failing for the same reason, document it as a pre-existing unrelated failure instead of treating it as a regression. + +**Step 3: Run lint on touched files** + +Run: `pnpm lint lib/image-pipeline/source-loader.ts lib/image-pipeline/preview-renderer.ts lib/image-pipeline/worker-client.ts lib/image-pipeline/image-pipeline.worker.ts hooks/use-pipeline-preview.ts components/canvas/nodes/adjustment-preview.tsx components/canvas/nodes/compare-surface.tsx components/canvas/nodes/render-node.tsx components/canvas/nodes/use-node-local-data.ts tests/image-pipeline/source-loader.test.ts tests/image-pipeline/worker-client.test.ts tests/use-pipeline-preview.test.ts` +Expected: PASS + +**Step 4: Summarize verification evidence** + +Capture test/lint output and note any remaining pre-existing failures separately from new work.