refactor(email): render existing templates with Svelte 5 - #2258
refactor(email): render existing templates with Svelte 5#2258niemyjski wants to merge 27 commits into
Conversation
✅ Rendering Bug Fixes — VerifiedLatest commit (b5cd6e6) fixes all rendering/formatting issues found in review: Fixed Issues
Screenshots (after fixes, with realistic sample data)Event Notice — single clean message, no branch concatenation, no extra HR before first field TestsLint/Type Check |
There was a problem hiding this comment.
Pull request overview
This PR replaces the legacy Foundation/Gulp email-template pipeline with a Svelte 5 + Vite + Tailwind-based renderer that generates the static Handlebars HTML consumed by Exceptionless.Core mail delivery.
Changes:
- Adds a new Svelte email-template project with build, lint, check, Storybook preview, and template-rendering scripts.
- Recreates the email templates as Svelte components and checks in regenerated HTML outputs for the .NET mailer.
- Adds AppHost registration for the email Storybook and removes the legacy Foundation/Gulp source structure.
Reviewed changes
Copilot reviewed 66 out of 68 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
.gitignore |
Adds .gstack/ ignore entry. |
src/Exceptionless.AppHost/Program.cs |
Registers component and email Storybook JavaScript apps. |
src/Exceptionless.Core/Mail/Templates/event-notice.html |
Regenerated event notice email output. |
src/Exceptionless.Core/Mail/Templates/organization-added.html |
Regenerated organization added email output. |
src/Exceptionless.Core/Mail/Templates/organization-invited.html |
Regenerated organization invitation email output. |
src/Exceptionless.Core/Mail/Templates/organization-notice.html |
Regenerated organization limit/throttle notice output. |
src/Exceptionless.Core/Mail/Templates/organization-payment-failed.html |
Regenerated payment failed email output. |
src/Exceptionless.Core/Mail/Templates/user-email-verify.html |
Regenerated email verification output. |
src/Exceptionless.Core/Mail/Templates/user-password-reset.html |
Regenerated password reset output. |
src/Exceptionless.EmailTemplates/.babelrc |
Removes legacy Babel config. |
src/Exceptionless.EmailTemplates/.gitignore |
Updates ignores for the new Svelte/Vite project. |
src/Exceptionless.EmailTemplates/.npmrc |
Removes legacy npm release-age config. |
src/Exceptionless.EmailTemplates/.prettierignore |
Adds formatting ignore paths. |
src/Exceptionless.EmailTemplates/.prettierrc |
Adds Prettier configuration. |
src/Exceptionless.EmailTemplates/.storybook/main.ts |
Configures Storybook for email previews. |
src/Exceptionless.EmailTemplates/.storybook/preview.ts |
Adds Storybook preview parameters. |
src/Exceptionless.EmailTemplates/LICENSE |
Removes legacy ZURB license file. |
src/Exceptionless.EmailTemplates/README.md |
Rewrites documentation for the new Svelte email workflow. |
src/Exceptionless.EmailTemplates/eslint.config.js |
Adds ESLint flat config. |
src/Exceptionless.EmailTemplates/example.config.json |
Removes legacy mail/Litmus example config. |
src/Exceptionless.EmailTemplates/gulpfile.babel.js |
Removes legacy Gulp build pipeline. |
src/Exceptionless.EmailTemplates/package.json |
Replaces legacy Foundation dependencies/scripts with Svelte/Vite/Storybook tooling. |
src/Exceptionless.EmailTemplates/src/assets/img/.gitkeep |
Removes unused legacy asset placeholder. |
src/Exceptionless.EmailTemplates/src/assets/scss/_settings.scss |
Removes legacy Foundation email settings. |
src/Exceptionless.EmailTemplates/src/assets/scss/app.scss |
Removes legacy SCSS entrypoint. |
src/Exceptionless.EmailTemplates/src/assets/scss/template/_template.scss |
Removes legacy email template styling. |
src/Exceptionless.EmailTemplates/src/build-emails.ts |
Adds renderer/cleaner/validator that writes generated HTML templates. |
src/Exceptionless.EmailTemplates/src/components/ActionsFooter.svelte |
Adds shared actions footer component. |
src/Exceptionless.EmailTemplates/src/components/EmailLayout.svelte |
Adds shared email layout/header wrapper. |
src/Exceptionless.EmailTemplates/src/components/SocialFooter.svelte |
Adds shared social/contact footer. |
src/Exceptionless.EmailTemplates/src/helpers/raw.js |
Removes legacy Handlebars raw helper. |
src/Exceptionless.EmailTemplates/src/layouts/default.html |
Removes legacy default layout. |
src/Exceptionless.EmailTemplates/src/layouts/index-layout.html |
Removes legacy index layout. |
src/Exceptionless.EmailTemplates/src/pages/event-notice.html |
Removes legacy event notice source template. |
src/Exceptionless.EmailTemplates/src/pages/index.html |
Removes legacy preview index page. |
src/Exceptionless.EmailTemplates/src/pages/organization-added.html |
Removes legacy organization added source template. |
src/Exceptionless.EmailTemplates/src/pages/organization-invited.html |
Removes legacy organization invited source template. |
src/Exceptionless.EmailTemplates/src/pages/organization-notice.html |
Removes legacy organization notice source template. |
src/Exceptionless.EmailTemplates/src/pages/organization-payment-failed.html |
Removes legacy payment failed source template. |
src/Exceptionless.EmailTemplates/src/pages/project-daily-summary.html |
Removes legacy daily summary source template. |
src/Exceptionless.EmailTemplates/src/pages/user-email-verify.html |
Removes legacy email verify source template. |
src/Exceptionless.EmailTemplates/src/pages/user-password-reset.html |
Removes legacy password reset source template. |
src/Exceptionless.EmailTemplates/src/partials/social.html |
Removes legacy social partial. |
src/Exceptionless.EmailTemplates/src/stories/EmailPreview.svelte |
Adds iframe-based email preview component. |
src/Exceptionless.EmailTemplates/src/stories/event-notice.stories.svelte |
Adds Storybook story for event notice. |
src/Exceptionless.EmailTemplates/src/stories/organization-added.stories.svelte |
Adds Storybook story for organization added. |
src/Exceptionless.EmailTemplates/src/stories/organization-invited.stories.svelte |
Adds Storybook story for organization invited. |
src/Exceptionless.EmailTemplates/src/stories/organization-notice.stories.svelte |
Adds Storybook story for organization notice. |
src/Exceptionless.EmailTemplates/src/stories/organization-payment-failed.stories.svelte |
Adds Storybook story for payment failed. |
src/Exceptionless.EmailTemplates/src/stories/project-daily-summary.stories.svelte |
Adds Storybook story for daily summary. |
src/Exceptionless.EmailTemplates/src/stories/sample-data.ts |
Adds sample token evaluator/data for email previews. |
src/Exceptionless.EmailTemplates/src/stories/user-email-verify.stories.svelte |
Adds Storybook story for email verification. |
src/Exceptionless.EmailTemplates/src/stories/user-password-reset.stories.svelte |
Adds Storybook story for password reset. |
src/Exceptionless.EmailTemplates/src/templates/event-notice.svelte |
Adds Svelte source for event notice email. |
src/Exceptionless.EmailTemplates/src/templates/organization-added.svelte |
Adds Svelte source for organization added email. |
src/Exceptionless.EmailTemplates/src/templates/organization-invited.svelte |
Adds Svelte source for organization invitation email. |
src/Exceptionless.EmailTemplates/src/templates/organization-notice.svelte |
Adds Svelte source for organization notice email. |
src/Exceptionless.EmailTemplates/src/templates/organization-payment-failed.svelte |
Adds Svelte source for payment failed email. |
src/Exceptionless.EmailTemplates/src/templates/project-daily-summary.svelte |
Adds Svelte source for daily summary email. |
src/Exceptionless.EmailTemplates/src/templates/user-email-verify.svelte |
Adds Svelte source for email verification email. |
src/Exceptionless.EmailTemplates/src/templates/user-password-reset.svelte |
Adds Svelte source for password reset email. |
src/Exceptionless.EmailTemplates/src/theme.ts |
Adds centralized email color/theme tokens. |
src/Exceptionless.EmailTemplates/svelte.config.js |
Adds Svelte preprocessing config. |
src/Exceptionless.EmailTemplates/tsconfig.json |
Adds TypeScript config for the email-template project. |
src/Exceptionless.EmailTemplates/vite.config.ts |
Adds Vite SSR build config. |
src/Exceptionless.Web/ClientApp/package.json |
Prevents ClientApp Storybook from auto-opening a browser. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Dogfood Evidence — Mailpit Email RendersAll 8 templates sent to Mailpit via SMTP and visually verified. HTML Check scores range 87–94%. Bug Found & Fixed:
|
| Template | Result |
|---|---|
| Event Notice | ✅ Error details table, View Event Details CTA, User Info, structured data |
| Daily Summary | ✅ Stats grid (Count/Unique/New), View Timeline, Most Frequent with [REGRESSED] badge |
| Organization Invite | ✅ Join Organization CTA, Connect/Contact footer |
| Organization Added | ✅ View Organization CTA |
| Organization Notice (monthly) | ✅ Monthly plan limit message |
| Payment Failed | ✅ Update Billing Information CTA |
| Account Confirmation | ✅ Verify Address CTA, personalized greeting |
| Password Reset | ✅ Reset Password CTA |
📸 Visual Comparison: All 8 Templates (Before → After)All templates rendered in Mailpit with identical sample data. Left = main branch (Foundation/Inky), Right = PR (Svelte 5 + Tailwind). Email VerifyPassword ResetEvent NoticeDaily Summary
Organization AddedOrganization InvitedOrganization Notice (Throttled)Payment FailedVerification: All 16 emails (8 OLD + 8 NEW) successfully delivered to Mailpit SMTP. All 26 mailer tests pass. 🟢 |
6a49a24 to
07dbfb8
Compare
|
Follow-up reviewer audit complete at I re-read all live review threads, inline comments, submitted reviews, and issue comments against the current branch-vs-main diff. The 18 review threads classify as 14 outdated/already-fixed or superseded-by-the-current implementation and 4 current/already-addressed. One previously resolved human finding had regressed: The thermo-nuclear pass found one additional real boundary gap: parity validation compared visible text and links but did not inspect Gmail Verification:
No remaining actionable review concern or external blocker was found. The complete structural pass found no >1,000-line source file, unsafe boundary/type leak, duplicated canonical helper, non-atomic update, or clear behavior-preserving broad refactor beyond the focused CI simplification above. |
|
Addressed the invitation feedback and audited every internal email URL against the Svelte UI. The organization invitation does not provision an account or password-reset token, so a setup-password page would be the wrong contract. A genuinely new address now follows the invitation email directly to I migrated the remaining legacy email routes to their Svelte equivalents and extended parity validation to discover SvelteKit routes and verify both clean URLs and the current Verification at
Dependencies were updated to the latest compatible releases; remaining outdated entries are major-version upgrades and were intentionally left for separate compatibility work. |
|
Replacement CI is fully green at |
ad0a5d7 to
22ba2ac
Compare
b20e0f9 to
a49820c
Compare
Replace the legacy Foundation for Emails (Gulp/Inky/Panini/SCSS) toolchain with Svelte 5 + @better-svelte-email + Tailwind CSS. - Migrate all 8 email templates to Svelte components - Add shared EmailLayout, ActionsFooter, SocialFooter components - New build system: Vite SSR + @better-svelte-email/server renderer - Output maintains identical visual appearance and Handlebars tokens - All 25 mailer tests pass with new template output - Remove old build tooling (Gulp, Babel, SCSS, Panini, Inky) The compiled HTML templates preserve Handlebars syntax for runtime rendering by HandlebarsDotNet in the .NET backend (unchanged). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> # Conflicts: # src/Exceptionless.Core/Mail/Templates/event-notice.html # src/Exceptionless.Core/Mail/Templates/organization-added.html # src/Exceptionless.Core/Mail/Templates/organization-invited.html # src/Exceptionless.Core/Mail/Templates/organization-notice.html # src/Exceptionless.Core/Mail/Templates/organization-payment-failed.html # src/Exceptionless.Core/Mail/Templates/project-daily-summary.html # src/Exceptionless.Core/Mail/Templates/user-email-verify.html # src/Exceptionless.Core/Mail/Templates/user-password-reset.html # src/Exceptionless.EmailTemplates/src/pages/event-notice.html # src/Exceptionless.EmailTemplates/src/pages/organization-added.html # src/Exceptionless.EmailTemplates/src/pages/organization-invited.html # src/Exceptionless.EmailTemplates/src/pages/organization-notice.html # src/Exceptionless.EmailTemplates/src/pages/organization-payment-failed.html # src/Exceptionless.EmailTemplates/src/pages/project-daily-summary.html # src/Exceptionless.EmailTemplates/src/pages/user-email-verify.html # src/Exceptionless.EmailTemplates/src/pages/user-password-reset.html
- Migrate 8 email templates from Foundation for Emails / Gulp / Inky / Panini / SCSS
to @better-svelte-email/server 2.1.1 + Tailwind CSS
- Security audit: Svelte upgraded 5.34.7 → 5.55.9 (patched 6 XSS SSR CVEs)
- Pixel-perfect visual parity: all 8 templates verified with before/after screenshots
- Centralized design tokens in src/theme.ts (named Tailwind colors: text-primary,
bg-dark, text-alert, etc.) — no more hardcoded hex in .svelte sources
- Fixed XSS: preheader was {@html preheader}, now plain {preheader} text binding
- Add Storybook 10 with stories for all 8 templates + sample data with fillTokens()
- Add ESLint (flat config), Prettier, svelte-check — 0 errors, 25/25 tests pass
- Add @types/node, vite/client types, skipLibCheck for clean type checking
- Fix build script: typed Component, parseInt radix, HTML comment stripping
- Fix JSON-LD '}\n}' → '}}' Handlebars parse collision in cleanHtml
- Remove compilerOptions.generate from vite.config.ts (Svelte 5 no longer supports it)
- Remove old Gulp/Babel/SCSS/Panini/Inky/Foundation toolchain entirely
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rewrite fillTokens in sample-data.ts as a proper Handlebars evaluator
supporting if/else/each blocks, nested depth tracking, @index and
{{../parent}} scope resolution — fixes token concatenation bug where all
{{#if}} branches were showing simultaneously
- Fix cleanHtml() to replace newlines in text nodes with a space instead of
removing them (was causing 'fromwhich', 'yourapplication', etc.)
- Fix text typos in organization-notice.svelte: 'to to continue' → 'to continue',
'to to see' → 'to see', 'being counting' → 'counting'
- Add Storybook (port 6006) and EmailStorybook (port 6008) as AddJavaScriptApp
resources in Aspire AppHost for integrated development dashboard
- Change email Storybook port to 6008 to avoid conflict with Svelte app (6006)
- Rebuild all 8 generated HTML templates with whitespace fixes applied
- 25/25 mailer tests pass
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- vite.config.ts: add closeBundle plugin so 'dev' (--watch) regenerates
HTML templates on every source change; simplify 'build' script to
'vite build' (plugin handles node dist/build.js). Remove try/catch so
renderer failures propagate and fail the build.
- sample-data.ts: fix isTruthy to match Handlebars semantics exactly —
store @index as a real number so {{#if @index}} is falsy at index 0
(matches HandlebarsDotNet integer semantics); strings 'false'/'null'/
'undefined'/'' are falsy, all other strings truthy (matching
Handlebars.js, not JS). Change BASE_URL to http://localhost:7110.
- dependabot.yml: add npm entry for /src/Exceptionless.EmailTemplates
- build.yaml: add test-email-templates CI job that runs lint, check,
build, and verifies generated HTML is committed and up-to-date
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add IsRegressed to GetStackTemplateData in Mailer.cs
- Fix project-daily-summary: wrap all MostFrequent/Newest items in single <ul>
- Refactor SocialFooter to use Row+Column components (remove {#@html} hack)
- Use Preview component in EmailLayout; remove unconditional spacer div
- Replace 200-line custom Handlebars evaluator with handlebars npm package
- Move JSON-LD to wrapJsonLd() helper in src/lib/json-ld.ts (avoids Svelte
parser treating <script> in template literals as real script elements)
- Include src/templates/** in ESLint with no-at-html-tags rule disabled
- Remove .gstack/ from repo-root .gitignore
- Remove obvious code comments from build-emails.ts
- Fix no-unused-vars: remove Link import from user-email-verify.svelte
- Regenerate all 8 HTML templates
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fields is iterated with {{#each Fields}} where @key = property name
and this = value. Using a plain object (not array of objects) matches
HandlebarsDotNet's Dictionary<string,string> iteration semantics and
produces correct key/value pairs in the Storybook preview.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lpit Side-by-side comparisons of all 8 email templates rendered in Mailpit. BEFORE = main branch Foundation/Inky HTML, AFTER = PR Svelte 5 + Tailwind. Both rendered with identical sample data to verify pixel-accurate parity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…daily summary Use @better-svelte-email/components Row and Column instead of raw HTML strings for the 3-column and 4-column stats tables in project-daily-summary.svelte. Also refactor the throttling text to use a single @html block instead of mixing @html with Svelte Link components. All 26 mailer tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nodemailer was installed locally for email-to-Mailpit testing only. It's not needed as a production dependency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts: # src/Exceptionless.Core/Mail/Mailer.cs # tests/Exceptionless.Tests/Mail/MailerTests.cs
# Conflicts: # tests/Exceptionless.Tests/Mail/MailerTests.cs
Refresh the EmailTemplates Svelte/Vite/Storybook toolchain and lockfile while preserving generated output.
# Conflicts: # tests/Exceptionless.Tests/Mail/MailerTests.cs
b440fb2 to
0a66f41
Compare








Summary
Replaces the retired Foundation/Gulp email build with Svelte 5 while preserving the existing nine production emails exactly. This is now the renderer-only third PR in a reviewable stack:
The invitation/login behavior audit is independent in #2528.
What changed
src/Exceptionless.Web/ClientApp/emailsso the web app and email renderer share one Svelte/Vite/TypeScript toolchain and lockfile.build:emailstarget because it produces server-rendered HTML rather than the browser application.main().catch(...)with an async Vite hook and top-levelawait; the build targets Node 24, matching CI.EmailLayout.svelteto shared document/layout rules. Event, daily-summary, action-footer, and social-footer styles now live with their owners.Intentional compatibility details
UserEmailHrefcomes from fix(mail): safely render event links and regression labels #2527. It is not a Svelte requirement; C# constructs and percent-encodes themailto:URI so user text cannot inject another mail parameter.target="_blank". It does not collapse whitespace, minify HTML, strip arbitrary comments, or parse/rewrite JSON-LD.2.1.3release.deepmerge-tsis overridden to the newest security-fixed release eligible under the repository age policy (8.0.1), removing the two high-severity audit findings introduced byhtml-to-text.Scope deliberately excluded
Verification
npm cinpm run build— web application plus 9/9 email templatesnpm run check— 0 errors, 0 warningsnpm run lint— passednpm audit— 0 high/critical findings; 7 pre-existing low findings remain/next/stackexpected,/next/received)Breaking changes
None intended. Template filenames, rendered content, public URLs, and C# Handlebars contracts remain backward-compatible.
Post-Deploy Monitoring & Validation