Skip to content

feat(jsx-email): spam score and analysis for the preview#447

Merged
shellscape merged 12 commits into
mainfrom
feat/jsx-email/spam-check
May 15, 2026
Merged

feat(jsx-email): spam score and analysis for the preview#447
shellscape merged 12 commits into
mainfrom
feat/jsx-email/spam-check

Conversation

@shellscape

Copy link
Copy Markdown
Owner

Component / Package Name:

app-preview, jsx-email

This PR contains:

  • bugfix
  • feature
  • refactor
  • documentation
  • other

Are tests included?

  • yes (bugfixes and features will not be merged without tests)
  • no

Breaking Changes?

  • yes (breaking changes will not be merged unless absolutely necessary)
  • no

If yes, please include "BREAKING CHANGES:" in the first commit message body, followed by a description of what is
breaking.

List any relevant issue numbers:

N/A

Description

This PR is stacked on feat/jsx-email/spam-scanner and only includes commits unique to feat/jsx-email/spam-check.

It adds spam analysis to the preview app. Templates are scanned in the background, each preview card gets a spam status
button, and clicking it opens a popover with the spam verdict, signal count, grouped findings, rule descriptions, and
expandable evidence.

It also adds jsx-email/eml helpers so preview HTML/plain output can be converted into scannable EML for canispam.

Included changes:

  • Adds spam status UI for preview cards: pending, scanning, pass, warn, fail, and error states.
  • Adds detailed spam finding popovers with grouped signals and evidence previews.
  • Adds two Atlas spam sample templates for warning and failure states.
  • Adds fixture scripts for updating and verifying spam preview samples.
  • Improves hidden-text detection evidence in canispam.
  • Avoids false hidden-text positives from JSX Email preview text and visible white text on colored buttons.
  • Removes the empty “No spam signals found” result row when there are no findings.

@shellscape shellscape force-pushed the feat/jsx-email/spam-check branch 2 times, most recently from c4e1af5 to d213390 Compare May 15, 2026 01:44
@shellscape shellscape requested a review from CharlieHelps May 15, 2026 01:55

@charliecreates charliecreates Bot 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.

Blocking feedback

  1. Header folding can emit a blank line before long unbroken values, which terminates the header block early and produces malformed EML — packages/jsx-email/src/eml/headers.ts#L24-L31

If you want me to apply a fix, reply with the item number (for example: please fix 1).


for (const word of words) {
if (current.length + word.length + 1 > lineLength) {
lines.push(current.trimEnd());

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.

foldHeader currently pushes current.trimEnd() even when current is only the initial single space. For long unbroken values (for example subject: 'a'.repeat(100) or long UTF-8 encoded subjects), this produces Subject:\r\n\r\n <value>, inserting a blank line that ends the header section early.

Suggested fix: avoid pushing empty continuation lines (for example by initializing current with the header prefix or guarding the lines.push(...) call), so folded output is Subject:\r\n <value> without an intermediate blank line.

@shellscape

Copy link
Copy Markdown
Owner Author

@CharlieHelps please do a thorough review of this PR to determine if it's ready to merge. Look at the diff, reviews, comments, and referenced issues/docs/links to build a complete understanding. Then respond with a comment that includes a list of MUST fix items that must be fixed before the the PR is merged, and a list of IMPROVEMENTS that shouldn't block the merge but may be quick improvements or things that could be addressed in a follow up PR. The MUST fix items should explain exactly what's wrong and what needs to change to resolve the problem. Both should be numbered lists and the IMPROVEMENTS list should continue counting up from the MUST list. It's OK if there are no IMPROVEMENTS or MUST fix items, you can just omit the lists.

@charliecreates

Copy link
Copy Markdown
Contributor

Completed a thorough review of the latest PR state (diff, existing reviews/comments, CI, and referenced stacked-PR context).

MUST fix before merge

  1. packages/jsx-email/src/eml/headers.ts (foldHeader) can emit an empty folded header line for long unbroken values, producing malformed EML such as Subject:\r\n\r\n <value>. That blank line terminates the header block early and can cause subsequent headers to be interpreted as body content.
    What needs to change: update folding logic so it never appends an empty continuation line (the first overflow token should go directly onto a continuation line), and add a regression test in packages/jsx-email/test/eml/index.test.ts for long unbroken (and ideally encoded UTF-8) subjects asserting there is no \r\n\r\n between Subject and following headers.

IMPROVEMENTS (non-blocking)

  1. packages/jsx-email/package.json currently lists canispam in both peerDependencies and dependencies, while runtime jsx-email code does not appear to import it (usage is visible in tests).
    Suggested follow-up: if runtime coupling is not intentional, keep canispam in devDependencies for tests only and remove peer/runtime entries to reduce consumer install constraints.

Current status: not ready to merge until item 1 is fixed.

@shellscape shellscape force-pushed the feat/jsx-email/spam-check branch from d213390 to c091fa4 Compare May 15, 2026 02:57
@shellscape

Copy link
Copy Markdown
Owner Author

@CharlieHelps fix 1

@shellscape shellscape force-pushed the feat/jsx-email/spam-check branch from c091fa4 to f08743a Compare May 15, 2026 02:57
@charliecreates

Copy link
Copy Markdown
Contributor

@shellscape Done — I fixed item 1 and pushed it.

  • Updated foldHeader in packages/jsx-email/src/eml/headers.ts so long unbroken values don’t produce an empty folded line (no Subject:\r\n\r\n ... header break).
  • Added regression coverage in packages/jsx-email/test/eml/index.test.ts for long unbroken ASCII and UTF-8 subjects, asserting there’s no blank line between Subject and following headers.
  • Commit: 3ecc0d7

Verification run:

  • ./node_modules/.bin/moon run canispam:build
  • ./node_modules/.bin/moon run jsx-email:test -- test/eml/index.test.ts

@shellscape

Copy link
Copy Markdown
Owner Author

@CharlieHelps youve broken the smoke tests

@charliecreates

Copy link
Copy Markdown
Contributor

You’re right — there was a smoke failure on this SHA.

I pulled the failing run and reproduced the same CI commands locally on feat/jsx-email/spam-check (moon repo:build.all + DOT_LOG_LEVEL=debug LOCAL_SMOKE=true moon smoke-v2:run); they passed locally, including repeated runs.

From the failing CI log, the server crashed while loading a truncated generated file (/tmp/jsx-email/build/base-*.js, SyntaxError: Unexpected end of input), then the watcher test timed out and subsequent smoke pages failed with ERR_CONNECTION_REFUSED.

Most likely this is a flaky watcher rebuild/write race (overlapping async rebuilds exposing a partially written output file). Failing run: https://github.com/shellscape/jsx-email/actions/runs/25898020195/job/76115128058

If you want, I can put up a targeted patch to serialize watcher rebuild/write and stabilize this test path.

@shellscape

Copy link
Copy Markdown
Owner Author

@CharlieHelps smoke tests were consistently passing before your change

@charliecreates

Copy link
Copy Markdown
Contributor

@shellscape shellscape force-pushed the feat/jsx-email/spam-check branch from 3ecc0d7 to f08743a Compare May 15, 2026 15:44
@shellscape shellscape merged commit 00af966 into main May 15, 2026
8 checks passed
@shellscape shellscape deleted the feat/jsx-email/spam-check branch May 15, 2026 15:59
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.

1 participant