Skip to content

feat(editor): implement blur annotation with CSS preview and premium cursor overlay#131

Open
mahdyarief wants to merge 4 commits intowebadderall:mainfrom
mahdyarief:feat/blur-annotate
Open

feat(editor): implement blur annotation with CSS preview and premium cursor overlay#131
mahdyarief wants to merge 4 commits intowebadderall:mainfrom
mahdyarief:feat/blur-annotate

Conversation

@mahdyarief
Copy link
Copy Markdown
Contributor

@mahdyarief mahdyarief commented Mar 29, 2026

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

  • New Feature
  • Refactor / Code Cleanup

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.
image

Testing Guide

  1. Open the video editor.
  2. Add a Blur annotation from the updated types panel.
  3. Observe the perfectly aligned blur preview (WYSIWYG).
  4. Move the cursor over the blur region; notice it remains sharp and sits on top of the blur.
  5. Export the video and verify the blur in the final output.

Checklist

  • I have performed a self-review of my code.
  • I have added any necessary screenshots or videos.
  • I have linked related issue(s) and updated the changelog if applicable.

Summary by CodeRabbit

  • New Features
    • Added a blur annotation type with adjustable intensity and export support.
  • UI
    • Type selector updated to include a Blur tab and a slider showing current intensity.
  • Rendering / Playback
    • Cursor rendering improved for smoother drawing during playback.
  • Stability
    • Safer settings wiring to avoid calling missing handlers; minor UI layout refinements.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "*" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

Adds a new "blur" annotation type across UI, runtime overlay, type definitions, editor state, and export rendering to support configurable blurred regions (with adjustable intensity) in the video editor and exported output.

Changes

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)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

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 ⚠️ Warning 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between cbde700 and 9c9e8ec.

📒 Files selected for processing (7)
  • src/components/video-editor/AnnotationOverlay.tsx
  • src/components/video-editor/AnnotationSettingsPanel.tsx
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/VideoPlayback.tsx
  • src/components/video-editor/types.ts
  • src/lib/exporter/annotationRenderer.ts

Comment on lines +518 to +520
<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>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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 with px
  • 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.

Suggested change
<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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟡 Minor

Replace 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 rejected play() 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 cursorOverlayRef anymore, but the mount path still allocates cursorContainer/PixiCursorOverlay and 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9c9e8ec and 83f0474.

⛔ Files ignored due to path filters (3)
  • electron/native/bin/win32-x64/cursor-monitor.exe is excluded by !**/*.exe
  • electron/native/bin/win32-x64/wgc-capture.exe is excluded by !**/*.exe
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • .gitignore
  • electron/native/bin/win32-x64/helpers-manifest.json
  • src/components/video-editor/AnnotationSettingsPanel.tsx
  • src/components/video-editor/VideoPlayback.tsx
  • src/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

Comment on lines +1403 to +1441
// 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,
}
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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).

Comment on lines +377 to +401
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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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 -50

Repository: webadderall/Recordly

Length of output: 824


🏁 Script executed:

rg "drawImage" src/lib/exporter/annotationRenderer.ts -A 2 -B 2

Repository: webadderall/Recordly

Length of output: 357


🏁 Script executed:

fd -e json "biome\|tsconfig\|eslint" src root -type f 2>/dev/null | head -20

Repository: 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.json

Repository: webadderall/Recordly

Length of output: 4909


🏁 Script executed:

rg "getContext.*2d" src/lib/exporter/annotationRenderer.ts -B 2 -A 3

Repository: 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")

EOF

Repository: webadderall/Recordly

Length of output: 775


🏁 Script executed:

rg "getContext.*2d" src --type ts --type tsx -B 2 -A 5

Repository: webadderall/Recordly

Length of output: 91


🏁 Script executed:

rg "getContext.*2d" src -A 5 -B 2 | head -100

Repository: webadderall/Recordly

Length of output: 6923


🏁 Script executed:

rg "configureHighQuality2DContext" src/lib/exporter/ -A 10 -B 2 | head -50

Repository: 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.json

Repository: 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.

@webadderall
Copy link
Copy Markdown
Owner

I'll take a look at this after we get a stable release of Recordly out, you're welcomed to help!

@mahdyarief
Copy link
Copy Markdown
Contributor Author

ahh okay,

@nguyenquanghoang
Copy link
Copy Markdown

@webadderall Is Blur Annotation included in the official release yet?

@webadderall
Copy link
Copy Markdown
Owner

Reviewing

@nguyenquanghoang
Copy link
Copy Markdown

@mahdyarief Amazing! I really needed this blur feature. Thank you for your contribution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants