feat(editor): implement blur annotation with CSS preview and premium cursor overlay#131
feat(editor): implement blur annotation with CSS preview and premium cursor overlay#131mahdyarief wants to merge 4 commits intowebadderall:mainfrom
Conversation
…obust canvas export pipeline
… sits above annotations
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
Type System Extensions src/components/video-editor/types.ts |
Added "blur" to AnnotationType and optional blurIntensity?: number to AnnotationRegion. |
Blur UI Controls src/components/video-editor/AnnotationSettingsPanel.tsx, src/components/video-editor/SettingsPanel.tsx |
Added blur tab and slider (1–100, default 12) in AnnotationSettingsPanel; threaded onAnnotationBlurIntensityChange / onBlurIntensityChange callbacks and switched several annotation callbacks to optional chaining. |
Annotation Display & State Management src/components/video-editor/AnnotationOverlay.tsx, src/components/video-editor/VideoEditor.tsx |
Overlay renders a 'blur' case using backdrop-filter/WebkitBackdropFilter with blurIntensity; VideoEditor initializes blur regions (content = "Blur Region", default intensity 12) and adds handler to update blurIntensity. |
Cursor Overlay / Playback src/components/video-editor/VideoPlayback.tsx |
Replaced Pixi cursor overlay with a top-level HTML canvas (cursorCanvasRef) plus smoothed cursor state and annotationRegionsRef; updated ticker draw gating and region sync dependencies. |
Blur Export Rendering src/lib/exporter/annotationRenderer.ts |
Added renderBlur(...) helper and dispatch for 'blur' in renderAnnotations; performs scaled blur via offscreen canvas, clamps capture rect, clips to rounded rect (fallback to rect), applies ctx.filter = blur(...), and catches/logs errors. |
Misc / Config .gitignore, electron/native/bin/.../helpers-manifest.json |
Normalized .gitignore entries and added ignores; updated helper manifest binarySha256 and updatedAt timestamps for Windows helpers. |
sequenceDiagram
participant User
participant VideoEditor
participant AnnotationSettingsPanel
participant AnnotationOverlay
participant Exporter
participant Canvas
User->>VideoEditor: select "blur" annotation / adjust intensity
VideoEditor->>AnnotationSettingsPanel: open settings (passes onBlurIntensityChange)
AnnotationSettingsPanel->>VideoEditor: onBlurIntensityChange(intensity)
VideoEditor->>AnnotationOverlay: update annotationRegions with blurIntensity
Note over AnnotationOverlay: playback shows backdropFilter blur using blurIntensity
User->>VideoEditor: request export
VideoEditor->>Exporter: renderAnnotations(annotationRegions)
Exporter->>Canvas: renderBlur(...) (capture, offscreen canvas, apply ctx.filter=blur(...), composite)
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
- feat: Advanced Video Editor Implementation, Native WGC Integration, and AI Auto-Captions #122: Directly related — modifies the same annotation/blur code paths across types, UI components, rendering, and export logic for blur annotation support.
- Revert "feat: Advanced Video Editor Implementation, Native WGC Integration, and AI Auto-Captions" #124: Reverts/removes blur-related changes (types, UI, overlay, exporter) and therefore strongly connected to these edits.
Poem
🐇 A soft blur hop through code and light,
Slider tuned to make the pixels right,
From overlay haze to exported frame,
I twitch my nose — the world looks tame! ✨
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (2 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title clearly summarizes the main change: implementing a blur annotation feature with CSS preview and premium cursor overlay, which matches the core objectives of the PR. |
| Description check | ✅ Passed | PR description is well-structured with all major template sections completed: clear description of blur annotation feature, well-articulated motivation explaining the shift from PixiJS to CSS backdrop-filter, proper type classification, linked issue, included screenshot, detailed testing guide, and completed checklist. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/video-editor/AnnotationSettingsPanel.tsx`:
- Around line 518-520: The UI shows annotation.blurIntensity with a "%" suffix
which is misleading because the blur is applied in pixels; update the display in
AnnotationSettingsPanel (the span currently rendering {annotation.blurIntensity
?? 12}%) to show pixels (e.g., "px") or a mapped pixel value instead: either
change the suffix to "px" and ensure the blur style uses
`${annotation.blurIntensity}px`, or introduce a single helper like
mapBlurIntensityToPx(value) and use it both for the CSS blur (backdrop-filter:
blur(...)) and for the displayed label so the units and value are consistent
across AnnotationSettingsPanel and wherever blur is applied.
In `@src/lib/exporter/annotationRenderer.ts`:
- Around line 361-399: Expand the sampled ImageData by a blur-dependent padding
(e.g., const pad = Math.ceil(intensity * 2)) so the offscreen capture includes
neighboring pixels, but keep the clipping path at the original annotation
bounds; compute paddedSrcX = Math.max(0, srcX - pad), paddedSrcY = Math.max(0,
srcY - pad) and paddedWidth/Height clamped to ctx.canvas dimensions, create the
offscreen with paddedWidth/paddedHeight, put the captured image into the
offscreen, then apply ctx.filter = `blur(${intensity}px)` and draw the offscreen
back onto the main canvas at (paddedSrcX, paddedSrcY) so the blur has room to
bleed while the existing clip (ctx.roundRect/ctx.rect + ctx.clip) still confines
the visible result to the original x,y,width,height; update usages of
srcX/srcY/srcWidth/srcHeight to use the padded variants for capture and the
original x,y,width,height for the clip.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: df66444a-0dd4-4ec5-8c5e-36bdad4e17a4
📒 Files selected for processing (7)
src/components/video-editor/AnnotationOverlay.tsxsrc/components/video-editor/AnnotationSettingsPanel.tsxsrc/components/video-editor/SettingsPanel.tsxsrc/components/video-editor/VideoEditor.tsxsrc/components/video-editor/VideoPlayback.tsxsrc/components/video-editor/types.tssrc/lib/exporter/annotationRenderer.ts
| <span className="text-xs font-mono font-bold text-[#3B82F6] bg-[#3B82F6]/10 px-2 py-0.5 rounded-md border border-[#3B82F6]/20"> | ||
| {annotation.blurIntensity ?? 12}% | ||
| </span> |
There was a problem hiding this comment.
Unit display may confuse users.
The intensity value is displayed with a % suffix, but based on the AI summary, the blur effect is applied as pixels (e.g., backdrop-filter: blur(12px)). Showing "12%" when it actually means "12px" blur could mislead users about the effect scale.
Consider either:
- Removing the
%suffix or replacing withpx - Or mapping the 1-100 range to a sensible pixel range and documenting this internally
Proposed fix to remove misleading percentage
<span className="text-xs font-mono font-bold text-[`#3B82F6`] bg-[`#3B82F6`]/10 px-2 py-0.5 rounded-md border border-[`#3B82F6`]/20">
- {annotation.blurIntensity ?? 12}%
+ {annotation.blurIntensity ?? 12}
</span>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span className="text-xs font-mono font-bold text-[#3B82F6] bg-[#3B82F6]/10 px-2 py-0.5 rounded-md border border-[#3B82F6]/20"> | |
| {annotation.blurIntensity ?? 12}% | |
| </span> | |
| <span className="text-xs font-mono font-bold text-[`#3B82F6`] bg-[`#3B82F6`]/10 px-2 py-0.5 rounded-md border border-[`#3B82F6`]/20"> | |
| {annotation.blurIntensity ?? 12} | |
| </span> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/video-editor/AnnotationSettingsPanel.tsx` around lines 518 -
520, The UI shows annotation.blurIntensity with a "%" suffix which is misleading
because the blur is applied in pixels; update the display in
AnnotationSettingsPanel (the span currently rendering {annotation.blurIntensity
?? 12}%) to show pixels (e.g., "px") or a mapped pixel value instead: either
change the suffix to "px" and ensure the blur style uses
`${annotation.blurIntensity}px`, or introduce a single helper like
mapBlurIntensityToPx(value) and use it both for the CSS blur (backdrop-filter:
blur(...)) and for the displayed label so the units and value are consistent
across AnnotationSettingsPanel and wherever blur is applied.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/video-editor/VideoPlayback.tsx (1)
730-736:⚠️ Potential issue | 🟡 MinorReplace the empty catch blocks with explicit handling.
Biome is already flagging these blocks, and the two
play()branches also hide real preview/webcam resume failures when sync breaks. A small explicit no-op for pointer capture and at least debug-level handling for rejectedplay()promises would keep the lint pass green and preserve diagnostics.🛠️ Minimal cleanup
- } catch { } + } catch { + // Pointer capture may already be released. + } ... - video.play().catch(() => { }); + video.play().catch((error) => { + console.debug("Preview resume was rejected.", error); + }); ... - playPromise.catch(() => { }); + playPromise.catch((error) => { + console.debug("Webcam resume was rejected.", error); + });Also applies to: 913-917, 985-989
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/video-editor/VideoPlayback.tsx` around lines 730 - 736, The empty catch blocks must be replaced with explicit handling: in endFocusDrag (and the similar pointer-capture handlers around lines referenced) replace the bare catch with a no-op comment or a small debug log so pointer capture failures are acknowledged (e.g., "pointer capture not available" or a debug-level logger call), and for the two video.play() branches (the play() calls flagged at the other locations) attach .catch handlers that log at debug level the rejection/error (include the video element id or context) instead of swallowing the error; update functions endFocusDrag and the analogous pointer handlers and the code paths that call video.play() so failures are logged for diagnostics.
🧹 Nitpick comments (2)
.gitignore (1)
39-45:docs/is a broad ignore pattern—confirm this scope.Ignoring the entire
docs/directory can unintentionally drop legitimate documentation updates from future commits. Consider narrowing to generated docs paths only if that was the goal.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.gitignore around lines 39 - 45, The current broad ignore pattern "docs/" in .gitignore will exclude all documentation; if the intent is to ignore only generated docs, replace that entry with specific generated paths (for example the build output like docs/_build/ or docs/site/), or add negation rules to keep source docs (e.g., keep docs/source/ or README in docs/) so authored docs are tracked; update the .gitignore entry "docs/" to a narrower pattern and add a short comment explaining the intent.src/components/video-editor/VideoPlayback.tsx (1)
1067-1087: Remove the orphaned Pixi cursor path after the canvas migration.Nothing in this file drives
cursorOverlayRefanymore, but the mount path still allocatescursorContainer/PixiCursorOverlayand the follow-up effect still reconfigures it. That keeps two cursor-rendering implementations alive and pays mount-time asset/GPU cost for a renderer this file no longer uses.Also applies to: 1454-1476
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/video-editor/VideoPlayback.tsx` around lines 1067 - 1087, Remove the unused Pixi cursor mount path: delete the creation and addition of cursorContainer (the Container instance assigned to cursorContainerRef and added via cameraContainer.addChild) and the conditional allocation of PixiCursorOverlay (the PixiCursorOverlay instance assigned to cursorOverlayRef when cursorOverlayEnabled is true); also remove or no-op any follow-up effects that reconfigure cursorOverlayRef so nothing references cursorContainerRef/cursorOverlayRef anymore (repeat the same removal where the alternate mount exists), and ensure any related refs are set to null to avoid retained GPU/asset cost.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/video-editor/VideoPlayback.tsx`:
- Around line 1403-1441: The top-level cursor overlay is being drawn in raw CSS
pixels and using the full canvas viewport, causing drift under zoom/crop and
blurring on HiDPI; update the cursor draw to (1) size the backing store using
devicePixelRatio (set cursorCanvas.width = cursorCanvas.clientWidth *
devicePixelRatio and height similarly) and scale the 2D context by
devicePixelRatio, and (2) compute and pass the same transformed viewport the
preview uses (derive from animationStateRef.current / the visible video rect or
cropRegionRef.current) into drawCursorOnCanvas instead of the full canvas rect
so the cursor is rendered in the preview’s coordinate space; adjust calls using
cursorCanvasRef, smoothedCursorStateRef, cursorTelemetryRef and
drawCursorOnCanvas (also apply same fix at the other location mentioned).
In `@src/lib/exporter/annotationRenderer.ts`:
- Around line 377-401: The code currently unsafely casts getContext('2d') to
CanvasRenderingContext2D and uses an any cast on drawImage; fix by guarding the
context and typing the offscreen image correctly: call
configureHighQuality2DContext or otherwise check that offscreen.getContext('2d')
returns non-null and assign to a properly typed variable before calling
putImageData, and remove the `as any` by ensuring the value passed to
ctx.drawImage is a CanvasImageSource (branch on offscreen being an
OffscreenCanvas vs HTMLCanvasElement or cast to CanvasImageSource only after the
runtime type check); keep ctx.filter/clip logic the same but only execute it
after validating ctx is non-null.
---
Outside diff comments:
In `@src/components/video-editor/VideoPlayback.tsx`:
- Around line 730-736: The empty catch blocks must be replaced with explicit
handling: in endFocusDrag (and the similar pointer-capture handlers around lines
referenced) replace the bare catch with a no-op comment or a small debug log so
pointer capture failures are acknowledged (e.g., "pointer capture not available"
or a debug-level logger call), and for the two video.play() branches (the play()
calls flagged at the other locations) attach .catch handlers that log at debug
level the rejection/error (include the video element id or context) instead of
swallowing the error; update functions endFocusDrag and the analogous pointer
handlers and the code paths that call video.play() so failures are logged for
diagnostics.
---
Nitpick comments:
In @.gitignore:
- Around line 39-45: The current broad ignore pattern "docs/" in .gitignore will
exclude all documentation; if the intent is to ignore only generated docs,
replace that entry with specific generated paths (for example the build output
like docs/_build/ or docs/site/), or add negation rules to keep source docs
(e.g., keep docs/source/ or README in docs/) so authored docs are tracked;
update the .gitignore entry "docs/" to a narrower pattern and add a short
comment explaining the intent.
In `@src/components/video-editor/VideoPlayback.tsx`:
- Around line 1067-1087: Remove the unused Pixi cursor mount path: delete the
creation and addition of cursorContainer (the Container instance assigned to
cursorContainerRef and added via cameraContainer.addChild) and the conditional
allocation of PixiCursorOverlay (the PixiCursorOverlay instance assigned to
cursorOverlayRef when cursorOverlayEnabled is true); also remove or no-op any
follow-up effects that reconfigure cursorOverlayRef so nothing references
cursorContainerRef/cursorOverlayRef anymore (repeat the same removal where the
alternate mount exists), and ensure any related refs are set to null to avoid
retained GPU/asset cost.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 1c42f199-38df-4505-ae77-d77e63cb5660
⛔ Files ignored due to path filters (3)
electron/native/bin/win32-x64/cursor-monitor.exeis excluded by!**/*.exeelectron/native/bin/win32-x64/wgc-capture.exeis excluded by!**/*.exepackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
.gitignoreelectron/native/bin/win32-x64/helpers-manifest.jsonsrc/components/video-editor/AnnotationSettingsPanel.tsxsrc/components/video-editor/VideoPlayback.tsxsrc/lib/exporter/annotationRenderer.ts
✅ Files skipped from review due to trivial changes (1)
- electron/native/bin/win32-x64/helpers-manifest.json
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/video-editor/AnnotationSettingsPanel.tsx
| // Update cursor overlay on separate top-level canvas | ||
| const cursorCanvas = cursorCanvasRef.current; | ||
| const smoothedState = smoothedCursorStateRef.current; | ||
| if (cursorCanvas && smoothedState && cursorTelemetryRef.current && showCursorRef.current) { | ||
| const timeMs = currentTimeRef.current; | ||
| cursorOverlay.update( | ||
| cursorTelemetryRef.current, | ||
| timeMs, | ||
| baseMaskRef.current, | ||
| showCursorRef.current, | ||
| !isPlayingRef.current || isSeekingRef.current, | ||
| ); | ||
| const ctx = cursorCanvas.getContext('2d'); | ||
| if (ctx) { | ||
| // Match canvas size to container | ||
| if (cursorCanvas.width !== cursorCanvas.clientWidth || cursorCanvas.height !== cursorCanvas.clientHeight) { | ||
| cursorCanvas.width = cursorCanvas.clientWidth; | ||
| cursorCanvas.height = cursorCanvas.clientHeight; | ||
| } | ||
|
|
||
| ctx.clearRect(0, 0, cursorCanvas.width, cursorCanvas.height); | ||
|
|
||
| // Draw cursor on top using utility | ||
| drawCursorOnCanvas( | ||
| ctx, | ||
| cursorTelemetryRef.current, | ||
| timeMs, | ||
| { | ||
| x: 0, | ||
| y: 0, | ||
| width: cursorCanvas.width, | ||
| height: cursorCanvas.height, | ||
| sourceCrop: cropRegionRef.current, | ||
| }, | ||
| smoothedState, | ||
| { | ||
| ...DEFAULT_CURSOR_CONFIG, | ||
| style: cursorStyleRef.current, | ||
| dotRadius: 28 * cursorSizeRef.current, // Matches Pixi scaling approx | ||
| smoothingFactor: cursorSmoothingRef.current, | ||
| motionBlur: cursorMotionBlurRef.current, | ||
| clickBounce: cursorClickBounceRef.current, | ||
| clickBounceDuration: cursorClickBounceDurationRef.current, | ||
| sway: cursorSwayRef.current, | ||
| } | ||
| ); |
There was a problem hiding this comment.
Draw the top-level cursor in the same space as the preview.
drawCursorOnCanvas only gets the viewport you pass here (see src/components/video-editor/videoPlayback/cursorRenderer.ts:1242-1249), but this call always uses the full overlay canvas in raw CSS pixels and never mirrors the current preview transform from animationStateRef.current / the visible video rect. That makes the cursor drift as soon as zoom, crop, or padding are active, and the clientWidth/clientHeight backing store will soften it on HiDPI displays. Use the same transformed viewport the preview uses, and size the backing store to client * devicePixelRatio before drawing.
Also applies to: 1819-1825
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/video-editor/VideoPlayback.tsx` around lines 1403 - 1441, The
top-level cursor overlay is being drawn in raw CSS pixels and using the full
canvas viewport, causing drift under zoom/crop and blurring on HiDPI; update the
cursor draw to (1) size the backing store using devicePixelRatio (set
cursorCanvas.width = cursorCanvas.clientWidth * devicePixelRatio and height
similarly) and scale the 2D context by devicePixelRatio, and (2) compute and
pass the same transformed viewport the preview uses (derive from
animationStateRef.current / the visible video rect or cropRegionRef.current)
into drawCursorOnCanvas instead of the full canvas rect so the cursor is
rendered in the preview’s coordinate space; adjust calls using cursorCanvasRef,
smoothedCursorStateRef, cursorTelemetryRef and drawCursorOnCanvas (also apply
same fix at the other location mentioned).
| let offscreen: HTMLCanvasElement | OffscreenCanvas; | ||
| if (typeof document !== 'undefined') { | ||
| offscreen = document.createElement('canvas'); | ||
| } else { | ||
| offscreen = new OffscreenCanvas(srcWidth, srcHeight); | ||
| } | ||
| offscreen.width = srcWidth; | ||
| offscreen.height = srcHeight; | ||
|
|
||
| const offCtx = offscreen.getContext('2d') as CanvasRenderingContext2D; | ||
| offCtx.putImageData(imageData, 0, 0); | ||
|
|
||
| // Create rounded rect clipping path (matches UI's rounded-lg approx 8px) | ||
| const radius = 8 * scaleFactor; | ||
| ctx.beginPath(); | ||
| if (ctx.roundRect) { | ||
| ctx.roundRect(x, y, width, height, radius); | ||
| } else { | ||
| ctx.rect(x, y, width, height); | ||
| } | ||
| ctx.clip(); | ||
|
|
||
| // Apply the blur filter and draw the captured region back at its source position | ||
| ctx.filter = `blur(${intensity}px)`; | ||
| ctx.drawImage(offscreen as any, srcX, srcY); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/lib/exporter/annotationRenderer.ts | sed -n '370,410p'Repository: webadderall/Recordly
Length of output: 1528
🏁 Script executed:
cat tsconfig.json | head -50Repository: webadderall/Recordly
Length of output: 824
🏁 Script executed:
rg "drawImage" src/lib/exporter/annotationRenderer.ts -A 2 -B 2Repository: webadderall/Recordly
Length of output: 357
🏁 Script executed:
fd -e json "biome\|tsconfig\|eslint" src root -type f 2>/dev/null | head -20Repository: webadderall/Recordly
Length of output: 46
🏁 Script executed:
ls -la | grep -E "biome|eslint|tsconfig"Repository: webadderall/Recordly
Length of output: 333
🏁 Script executed:
cat biome.jsonRepository: webadderall/Recordly
Length of output: 4909
🏁 Script executed:
rg "getContext.*2d" src/lib/exporter/annotationRenderer.ts -B 2 -A 3Repository: webadderall/Recordly
Length of output: 298
🏁 Script executed:
python3 << 'EOF'
# Check what the Canvas drawImage signature expects
# and whether HTMLCanvasElement | OffscreenCanvas is compatible
# The DOM lib defines drawImage as accepting CanvasImageSource
# which is a union of:
# - HTMLCanvasElement
# - HTMLImageElement
# - HTMLVideoElement
# - ImageBitmap
# - OffscreenCanvas
# - VideoFrame (newer)
# So HTMLCanvasElement | OffscreenCanvas should be valid
# Let's check if there's actually a type error by looking at what
# might cause Biome to flag it
print("Checking Canvas.drawImage type signature...")
print("\nExpected overloads for drawImage:")
print("drawImage(image: CanvasImageSource, dx: number, dy: number): void;")
print("drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void;")
print("\nCanvasImageSource is defined as:")
print("type CanvasImageSource = HTMLCanvasElement | HTMLImageElement | HTMLVideoElement |")
print(" ImageBitmap | OffscreenCanvas | VideoFrame;")
print("\nSo HTMLCanvasElement | OffscreenCanvas should be valid...")
print("\nHowever, the `as any` cast is still being used.")
print("This suggests either:")
print("1. TypeScript is not recognizing the union as compatible")
print("2. There's a version mismatch in the DOM types")
print("3. There's a circular type issue or generic constraint")
EOFRepository: webadderall/Recordly
Length of output: 775
🏁 Script executed:
rg "getContext.*2d" src --type ts --type tsx -B 2 -A 5Repository: webadderall/Recordly
Length of output: 91
🏁 Script executed:
rg "getContext.*2d" src -A 5 -B 2 | head -100Repository: webadderall/Recordly
Length of output: 6923
🏁 Script executed:
rg "configureHighQuality2DContext" src/lib/exporter/ -A 10 -B 2 | head -50Repository: webadderall/Recordly
Length of output: 2975
🏁 Script executed:
cat src/lib/exporter/annotationRenderer.ts | sed -n '375,410p'Repository: webadderall/Recordly
Length of output: 1140
🏁 Script executed:
grep -A 2 "noExplicitAny" biome.jsonRepository: webadderall/Recordly
Length of output: 175
Guard the context and avoid the any cast on drawImage.
Line 386's as CanvasRenderingContext2D unsafely suppresses the null return from getContext('2d'), leaving line 387's putImageData call unguarded. Additionally, line 401's as any violates Biome's noExplicitAny: "error" rule. Use a proper null check (like configureHighQuality2DContext in frameRenderer.ts) and handle the type properly—either branch the drawImage call by canvas type or ensure the union type is correctly typed as a CanvasImageSource.
🧰 Tools
🪛 Biome (2.4.9)
[error] 401-401: Unexpected any. Specify a different type.
(lint/suspicious/noExplicitAny)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/exporter/annotationRenderer.ts` around lines 377 - 401, The code
currently unsafely casts getContext('2d') to CanvasRenderingContext2D and uses
an any cast on drawImage; fix by guarding the context and typing the offscreen
image correctly: call configureHighQuality2DContext or otherwise check that
offscreen.getContext('2d') returns non-null and assign to a properly typed
variable before calling putImageData, and remove the `as any` by ensuring the
value passed to ctx.drawImage is a CanvasImageSource (branch on offscreen being
an OffscreenCanvas vs HTMLCanvasElement or cast to CanvasImageSource only after
the runtime type check); keep ctx.filter/clip logic the same but only execute it
after validating ctx is non-null.
|
I'll take a look at this after we get a stable release of Recordly out, you're welcomed to help! |
|
ahh okay, |
|
@webadderall Is Blur Annotation included in the official release yet? |
|
Reviewing |
|
@mahdyarief Amazing! I really needed this blur feature. Thank you for your contribution. |
Description
This PR implements a high-performance Blur Annotation feature with a professional-grade implementation that resolves the coordinate 'gap' issue seen in earlier iterations.
Motivation
Previously, the blur annotation relied on a complex PixiJS-based localized filter that was difficult to synchronize with the editor's UI overlay, leading to visual misalignments (gaps). This change moves the preview to a hardware-accelerated CSS \�ackdrop-filter\ and moves the cursor to a top-level layer for maximum clarity and a 'premium' experience.
Type of Change
Related Issue(s)
#127
Screenshots / Video
The editor now features a perfectly aligned blur preview and a high-performance cursor overlay that sits on top of all annotations.

Testing Guide
Checklist
Summary by CodeRabbit