Skip to content

fix(notes): use SVG filters for soft shadow in SVG export#7934

Merged
steveruizok merged 8 commits intomainfrom
steveruizok/fix-note-svg-shadow
Feb 25, 2026
Merged

fix(notes): use SVG filters for soft shadow in SVG export#7934
steveruizok merged 8 commits intomainfrom
steveruizok/fix-note-svg-shadow

Conversation

@steveruizok
Copy link
Copy Markdown
Collaborator

@steveruizok steveruizok commented Feb 14, 2026

Closes #5110

When exporting sticky notes as SVG, the shadow was rendered as a simple semi-transparent offset rectangle, producing a hard shadow that looked different from the soft CSS box-shadow seen in-app. This PR replaces that with a proper SVG filter using feGaussianBlur and feOffset to create a two-layer soft drop shadow that closely matches the in-app appearance. The shadow is skipped in dark mode, matching the in-app behavior.

image

Change type

  • bugfix

Test plan

  1. Create a file with one or more sticky notes
  2. Select a sticky note and export as SVG (right-click → export as SVG)
  3. Open the exported SVG in a browser — verify it has a soft drop shadow instead of a hard rectangle shadow
  4. Export in dark mode — verify no shadow is rendered (matches in-app behavior)
  5. Export multiple sticky notes — verify each has its own shadow filter

Release notes

  • Fix sticky notes having a hard shadow instead of a soft drop shadow when exported as SVG.

Note

Medium Risk
Touches SVG export rendering and introduces per-shape SVG defs/filters, which can affect exported output and snapshot stability across viewers. CI workflow change is low risk but may lengthen runs or fail if build prerequisites change.

Overview
Sticky note SVG export now uses a per-note filter definition (registered via ctx.addExportDef) to render a soft, multi-pass drop shadow instead of the previous hard shadow rectangle, and the shadow is omitted in dark mode.

The Playwright snapshot update workflow now builds apps/examples (including asset refresh + prebuild) before running yarn e2e --update-snapshots, reducing snapshot churn from stale builds.

Written by Cursor Bugbot for commit c8277fd. This will update automatically on new commits. Configure here.

Replace the hard offset rectangle shadow with a proper SVG filter
using feGaussianBlur and feOffset to create a soft drop shadow that
closely matches the in-app CSS box-shadow appearance. The shadow
filter is skipped in dark mode, matching the in-app behavior.
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples Ready Ready Preview Feb 25, 2026 6:16pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics Ignored Ignored Preview Feb 25, 2026 6:16pm
tldraw-docs Ignored Ignored Preview Feb 25, 2026 6:16pm
chat-template Skipped Skipped Feb 25, 2026 6:16pm
tldraw-shader Skipped Skipped Feb 25, 2026 6:16pm
workflow-template Skipped Skipped Feb 25, 2026 6:16pm

Request Review

@huppy-bot huppy-bot bot added the bugfix Bug fix label Feb 14, 2026
@steveruizok steveruizok added the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 14, 2026
@github-actions github-actions bot removed the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 14, 2026
</feMerge>
</filter>
),
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dark-mode exports include unused shadow filters

Low Severity

toSvg always calls ctx.addExportDef for the note shadow filter, even when ctx.isDarkMode prevents any filtered shadow rect from rendering. This leaves large unused filter definitions in dark-mode SVG exports, increasing output size and parse cost without affecting visuals.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Member

@mimecuvalo mimecuvalo left a comment

Choose a reason for hiding this comment

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

LGTM

@steveruizok steveruizok added the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 24, 2026
@github-actions github-actions bot removed the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 24, 2026
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

</feMerge>
</filter>
),
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Filter IDs can collide across SVGs

Medium Severity

The new SVG filter id uses shape.id (cast to SafeId) without any per-export scoping, so exporting multiple SVGs that contain the same note shape.id and embedding them in the same document can cause url(#...) to reference the wrong <filter>, producing incorrect shadows.

Fix in Cursor Fix in Web

fill={getColorValue(theme, shape.props.color, 'noteFill')}
filter={`url(#${filterId})`}
/>
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Note fill rendered twice in light mode

Low Severity

In light mode, a filled <rect> with filter is rendered and then an identical filled <rect> is rendered again on top, so the note’s fill is drawn twice. If noteFill ever includes transparency or non-normal blending, this changes exported colors/opacity compared to rendering a single fill.

Fix in Cursor Fix in Web

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
🕒 Queued multiplayer-template b3231a5 Feb 25 2026, 10:01 AM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
🔵 In progress
View logs
image-pipeline-template b3231a5 Feb 25 2026, 10:01 AM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Feb 25, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
multiplayer-template c8277fd Feb 25 2026, 06:18 PM

@steveruizok steveruizok added the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 25, 2026
@github-actions github-actions bot removed the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 25, 2026
@steveruizok steveruizok added the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 25, 2026
@github-actions github-actions bot removed the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 25, 2026
The update-snapshots workflow was missing the build step that the regular
e2e workflow has, which is needed for `vite preview` to serve the app.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@steveruizok steveruizok added the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 25, 2026
@github-actions github-actions bot removed the update-snapshots ⚙️ Regenerates Playwright snapshots and commits them label Feb 25, 2026
@vercel vercel bot temporarily deployed to Preview – workflow-template February 25, 2026 18:14 Inactive
@vercel vercel bot temporarily deployed to Preview – chat-template February 25, 2026 18:14 Inactive
@vercel vercel bot temporarily deployed to Preview – tldraw-shader February 25, 2026 18:14 Inactive
@steveruizok steveruizok added this pull request to the merge queue Feb 25, 2026
Merged via the queue into main with commit 296d1c9 Feb 25, 2026
20 of 21 checks passed
@steveruizok steveruizok deleted the steveruizok/fix-note-svg-shadow branch February 25, 2026 18:24
steveruizok added a commit that referenced this pull request Feb 27, 2026
Add 12 new entries from PRs merged since v4.4.0:
- Featured: click-through on transparent image pixels (#7942)
- API: enum-to-const-object refactor (#8084)
- Improvements: SVG sanitizer (#7896), save-on-blur (#8037)
- Bug fixes: cross-origin download (#8090), zero-size draw (#8067),
  rich text toolbar cleanup (#8050), zoom threshold (#8040),
  selection foreground fallback (#8011), sticky note SVG shadow (#7934),
  arrow frame clamping (#7932), zero pressure draw (#5693)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sticky notes look different when exported as SVG

2 participants