Skip to content

refactor(email): render existing templates with Svelte 5 - #2258

Open
niemyjski wants to merge 27 commits into
mainfrom
niemyjski/email-templates-modernization
Open

refactor(email): render existing templates with Svelte 5#2258
niemyjski wants to merge 27 commits into
mainfrom
niemyjski/email-templates-modernization

Conversation

@niemyjski

@niemyjski niemyjski commented May 27, 2026

Copy link
Copy Markdown
Member

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:

  1. test(mail): characterize production email contracts #2526 characterizes the existing email contracts.
  2. fix(mail): safely render event links and regression labels #2527 fixes mailto encoding and the existing regression label.
  3. This PR changes only how those templates are authored and generated.

The invitation/login behavior audit is independent in #2528.

What changed

  • Moved the email sources into src/Exceptionless.Web/ClientApp/emails so the web app and email renderer share one Svelte/Vite/TypeScript toolchain and lockfile.
  • Kept email generation as an explicit build:emails target because it produces server-rendered HTML rather than the browser application.
  • Removed the standalone email package, old Foundation/Gulp sources, duplicate dependency configuration, and duplicate CI job.
  • Auto-discovers the Svelte templates and fails unless exactly nine same-named production HTML files exist.
  • Replaced synchronous child-process execution and main().catch(...) with an async Vite hook and top-level await; the build targets Node 24, matching CI.
  • Reduced EmailLayout.svelte to shared document/layout rules. Event, daily-summary, action-footer, and social-footer styles now live with their owners.
  • Preserved the established email palette, content, Handlebars behavior, URLs, JSON-LD, conditional output, and rendered pixels.
  • Kept Storybook out of Aspire and added no Storybook/email integration.

Intentional compatibility details

  • UserEmailHref comes from fix(mail): safely render event links and regression labels #2527. It is not a Svelte requirement; C# constructs and percent-encodes the mailto: URI so user text cannot inject another mail parameter.
  • Renderer cleanup removes only the four currently emitted Svelte SSR markers and Better Svelte Email's default target="_blank". It does not collapse whitespace, minify HTML, strip arbitrary comments, or parse/rewrite JSON-LD.
  • The social footer uses 50/50 columns. Its documented 0.375px desktop correction preserves the old Foundation table rounding; all mobile rules remain component-owned.
  • Better Svelte Email is pinned to the current 2.1.3 release. deepmerge-ts is overridden to the newest security-fixed release eligible under the repository age policy (8.0.1), removing the two high-severity audit findings introduced by html-to-text.

Scope deliberately excluded

Verification

  • npm ci
  • npm run build — web application plus 9/9 email templates
  • generated-output drift check — clean
  • npm run check — 0 errors, 0 warnings
  • npm run lint — passed
  • focused mailer tests — 29 passed
  • semantic parity — exact visible text and ordered actions in 13 representative scenarios
  • pixel parity — 26/26 exact comparisons across desktop and mobile
  • npm audit — 0 high/critical findings; 7 pre-existing low findings remain
  • full ClientApp unit suite — 699 passed; one unrelated, unchanged navigation-command test fails identically when rerun in isolation (/next/stack expected, /next/ received)

Breaking changes

None intended. Template filenames, rendered content, public URLs, and C# Handlebars contracts remain backward-compatible.

Post-Deploy Monitoring & Validation

  • Confirm the first production build reports nine generated email templates and no generated-file drift.
  • Send representative event, organization, billing, daily-summary, verification, and password-reset emails through the normal non-production mail path.
  • Verify links and JSON-LD actions resolve to the same destinations characterized in test(mail): characterize production email contracts #2526.
  • Watch mail rendering/error telemetry for unresolved Handlebars tokens or missing-template failures after deployment.

@niemyjski

Copy link
Copy Markdown
Member Author

✅ Rendering Bug Fixes — Verified

Latest commit (b5cd6e6) fixes all rendering/formatting issues found in review:

Fixed Issues

Issue Root Cause Fix
All {{#if}} branches showing simultaneously fillTokens used regex (couldn't track nesting) Rewrote as character-by-character evaluator with depth tracking
Extra <hr> before first error field {{#if @index}} — was always truthy with naive regex isTruthy('0') returns false; first item correctly skips divider
"fromwhich" word concatenation cleanHtml removed all newlines incl. text node linebreaks Replace \n' ' (space) not ''
"to to continue/see" double word Typo in template source Fixed in organization-notice.svelte
"?Exceptionless" missing space Same whitespace collapse issue Fixed by cleanHtml fix + single-line in template

Screenshots (after fixes, with realistic sample data)

Event Notice — single clean message, no branch concatenation, no extra HR before first field
Password Reset — "from which" correct spacing
Organization Notice — "to continue", "to see" correct
Organization Invited — "? Exceptionless" with proper space
Daily Summary — MostFrequent + Newest lists, [REGRESSED] badge

Tests

Lint/Type Check

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread src/Exceptionless.EmailTemplates/package.json Outdated
Comment thread src/Exceptionless.EmailTemplates/package.json Outdated
Comment thread src/Exceptionless.EmailTemplates/package.json Outdated
Comment thread src/Exceptionless.EmailTemplates/src/stories/sample-data.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 68 out of 70 changed files in this pull request and generated 5 comments.

Comment thread src/Exceptionless.EmailTemplates/src/stories/sample-data.ts Outdated
Comment thread src/Exceptionless.EmailTemplates/eslint.config.js Outdated
Comment thread src/Exceptionless.EmailTemplates/package.json Outdated
Comment thread src/Exceptionless.EmailTemplates/src/templates/project-daily-summary.svelte Outdated
Comment thread src/Exceptionless.EmailTemplates/src/templates/project-daily-summary.svelte Outdated
Comment thread .gitignore Outdated
Comment thread src/Exceptionless.EmailTemplates/src/build-emails.ts Outdated
Comment thread src/Exceptionless.EmailTemplates/src/build-emails.ts Outdated
Comment thread src/Exceptionless.EmailTemplates/src/templates/project-daily-summary.svelte Outdated
Comment thread src/Exceptionless.EmailTemplates/src/templates/project-daily-summary.svelte Outdated
Comment thread src/Exceptionless.EmailTemplates/src/templates/organization-invited.svelte Outdated
Comment thread src/Exceptionless.EmailTemplates/src/stories/sample-data.ts Outdated
Comment thread src/Exceptionless.EmailTemplates/src/components/SocialFooter.svelte Outdated
Comment thread src/Exceptionless.EmailTemplates/src/components/EmailLayout.svelte Outdated
@niemyjski

Copy link
Copy Markdown
Member Author

Dogfood Evidence — Mailpit Email Renders

All 8 templates sent to Mailpit via SMTP and visually verified. HTML Check scores range 87–94%.

Bug Found & Fixed: IsRegressed badge was never rendering

Root cause: GetStackTemplateData() in Mailer.cs returned the stack data without IsRegressed, so {{#if IsRegressed}}[REGRESSED]{{/if}} always evaluated false. The badge silently never appeared for regressed stacks — in main or in this PR until caught.

Fix: Added IsRegressed = s.Status == StackStatus.Regressed to GetStackTemplateData().

Proof it works (visible in daily summary email — see screenshot): the [REGRESSED] prefix appears on ArgumentNullException in the Most Frequent list.

Coverage Gap Fixed

RunMailJobAsync() in MailerTests.cs only logged the rendered body — zero assertions. All 25 tests would pass even if templates rendered empty HTML or left {{token}} placeholders unsubstituted.

Fix:

  • RunMailJobAsync() now returns string body
  • Each of the 26 tests (25 existing + 1 new) asserts template-specific content
  • New SendProjectDailySummaryWithRegressedStackAsync verifies [REGRESSED] badge
dotnet test -- --filter-class Exceptionless.Tests.Mail.MailerTests
# total: 26 | failed: 0 | succeeded: 26

Email Renders (new Svelte 5 + Tailwind templates)

Verified via Mailpit at http://localhost:8025 after npm run build in src/Exceptionless.EmailTemplates:

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

@niemyjski

Copy link
Copy Markdown
Member Author

📸 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 Verify

Email Verify Comparison

Password Reset

Password Reset Comparison

Event Notice

Event Notice Comparison

Daily Summary

Daily Summary Comparison

Note: Both templates show the [REGRESSED] badge because the bug fix in this PR now passes IsRegressed correctly from Mailer.cs. Before this PR, the badge was silently suppressed on main branch despite the template supporting it.

Organization Added

Org Added Comparison

Organization Invited

Org Invited Comparison

Organization Notice (Throttled)

Org Notice Comparison

Payment Failed

Payment Failed Comparison


Verification: All 16 emails (8 OLD + 8 NEW) successfully delivered to Mailpit SMTP. All 26 mailer tests pass. 🟢

@niemyjski
niemyjski force-pushed the niemyjski/email-templates-modernization branch from 6a49a24 to 07dbfb8 Compare July 10, 2026 18:09
@niemyjski

Copy link
Copy Markdown
Member Author

Follow-up reviewer audit complete at 270cbfa974777fb3b6a80c23732bb5ca050d4419.

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: .gstack/ was removed in response to review, then reintroduced later by 07dbfb878. I removed that unrelated ignore entry again and replied on the original thread with the commit-level RCA and proof.

The thermo-nuclear pass found one additional real boundary gap: parity validation compared visible text and links but did not inspect Gmail application/ld+json actions, so structured-data actions could drift without failing the gate. The validator now parses and exact-compares every JSON-LD document between legacy and modern output. I also removed a redundant CI build step because validate:parity already performs the build.

Verification:

  • npm run lint — passed
  • npm run check — 0 errors, 0 warnings
  • npm run validate:parity -- --semantic-only — 13/13 templates; text, links, and JSON-LD exact
  • npm run validate:parity — 13/13 semantic checks plus all 26 desktop/mobile pixel comparisons exact
  • npm run build-storybook — passed
  • npm audit --omit=dev and npm audit — 0 vulnerabilities
  • dotnet test tests/Exceptionless.Tests/Exceptionless.Tests.csproj --no-restore -- --filter-class Exceptionless.Tests.Mail.MailerTests — 27/27 passed
  • git diff --check — passed
  • replacement CI run — email templates, client, E2E/Aspire, container build, version, and CLA passed; full API test/coverage result recorded by the run

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.

@niemyjski

Copy link
Copy Markdown
Member Author

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 /signup?token=...; if that address has acquired an account before the invitation is accepted, /login?token=... now preserves the token through password and OAuth login and sends it to the API so organization membership is completed. Direct organization links also retain the organization from the URL.

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 /next deployment prefix.

Verification at 73b07ce9b:

  • focused invitation E2E: 2/2 passed (new signup and existing-account login)
  • ClientApp build, full npm run validate, and focused auth unit tests passed
  • email lint/check passed
  • all 13 semantic parity scenarios passed
  • all 26 desktop/mobile comparisons are exact pixel matches
  • every internal email link maps to a real Svelte route under /next
  • email package audit: 0 vulnerabilities

Dependencies were updated to the latest compatible releases; remaining outdated entries are major-version upgrades and were intentionally left for separate compatibility work.

@niemyjski

Copy link
Copy Markdown
Member Author

Replacement CI is fully green at 73b07ce9b: API/coverage, client, email templates, full E2E, container build, version, and CLA all passed. Run: https://github.com/exceptionless/Exceptionless/actions/runs/30580372048

Comment thread src/Exceptionless.Web/ClientApp/src/lib/features/auth/index.svelte.ts Outdated
Comment thread src/Exceptionless.EmailTemplates/src/build-emails.ts Outdated
@niemyjski niemyjski changed the title feat: modernize email templates with Svelte 5 + Tailwind + Storybook feat: modernize email templates with Svelte 5 and Tailwind Aug 25, 2026
@niemyjski niemyjski changed the title feat: modernize email templates with Svelte 5 and Tailwind refactor(email): render existing templates with Svelte 5 Aug 25, 2026
@niemyjski
niemyjski changed the base branch from main to feature/email-template-data-fixes August 25, 2026 12:49
@niemyjski
niemyjski force-pushed the feature/email-template-data-fixes branch from ad0a5d7 to 22ba2ac Compare August 25, 2026 20:56
@niemyjski
niemyjski force-pushed the niemyjski/email-templates-modernization branch from b20e0f9 to a49820c Compare August 27, 2026 13:25
niemyjski and others added 26 commits August 27, 2026 08:31
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
@niemyjski
niemyjski force-pushed the niemyjski/email-templates-modernization branch from b440fb2 to 0a66f41 Compare August 27, 2026 13:35
@github-actions

Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
Exceptionless.Insulation 37% 35% 286
Exceptionless.Core 76% 67% 10288
Exceptionless.Web 85% 69% 7974
Exceptionless.AppHost 38% 41% 147
Summary 79% (25549 / 32524) 68% (11964 / 17719) 18695

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants