Skip to content

feat(serve): preview templates with sample data from previewProps - #1906

Closed
ImDarkTom wants to merge 1 commit into
maizzle:masterfrom
ImDarkTom:feat/preview-props
Closed

ImDarkTom wants to merge 1 commit into
maizzle:masterfrom
ImDarkTom:feat/preview-props

Conversation

@ImDarkTom

@ImDarkTom ImDarkTom commented Sep 29, 2026 •

Copy link
Copy Markdown

Implements request in discussion #1802, which was converted from an issue in #1799.

On the dev server, templates that take props are currently given undefined as serve never passes props, causing previews to show up incorrectly or with blank values as well as warnings being spammed to the log whenever an email with props is previewed.

As the discussion mentions, withDefaults() is not a good solution, as it means that any sample props could potentially leak into production emails if they're accidentally left missing.

This PR introduces a way of showing sample prop data on the dev server via exporting it from a plain <script> block:

<script lang="ts">
interface Props {
    name: string;
    plan: 'Free' | 'Pro';
    redirectUrl?: string;
}

export const previewProps: Props = { name: 'John Doe', plan: 'Pro' }
</script>

<script setup lang="ts">
defineProps<Props>();
</script>

Only the dev server ever uses the exported previewProps. render() ignore them, as proven by the new test in serve.test.ts.

I attempted to run the formatter as given in the contributing guidelines, but no script seemed to exist. I know CONTRIBUTING says to ask before working on significant features, but a discussion for this already exists albeit with no replies. I'm happy to rework this if needed.

Screenshots:
Code Example
Example Result

Summary by CodeRabbit

  • New Features
    • Development previews can now display templates using values exported as previewProps. Values passed directly to a render take precedence.
    • Programmatic rendering supports passing props to populate template properties.
  • Documentation
    • Updated guidance for configuring preview values in templates and converting React Email examples. Clarified that preview values aren’t applied to regular renders or builds.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: b787f7c1-aa8c-4150-bc45-137147f267cb

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9fa8f and df7b0ea.

📒 Files selected for processing (5)
  • skills/maizzle/SKILL.md
  • skills/maizzle/references/CONVERT-REACT-EMAIL.md
  • src/render/createRenderer.ts
  • src/serve.ts
  • src/tests/serve.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

Dev-server renders can use a template’s exported previewProps when caller-provided props are nullish. Regular render() calls do not use those preview values. The documentation now describes this behavior and updates its React Email conversion example.

Changes

Preview Props

Layer / File(s) Summary
Preview rendering and usage
src/render/createRenderer.ts, src/serve.ts, src/tests/serve.test.ts, skills/maizzle/SKILL.md, skills/maizzle/references/CONVERT-REACT-EMAIL.md
Renderer.render accepts a preview option and uses the template’s exported previewProps when caller props are nullish. The dev server enables this option. The test checks that preview values appear in a dev-server render but not a regular render() call. The guides describe the export and update the password-reset example.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant DevServer
  participant getRendered
  participant Renderer.render
  participant TemplateModule
  participant createSSRApp
  DevServer->>getRendered: Request template render
  getRendered->>Renderer.render: Pass source and preview=true
  Renderer.render->>TemplateModule: Load component and previewProps
  TemplateModule-->>Renderer.render: Return default component and previewProps
  Renderer.render->>createSSRApp: Pass component and selected props
Loading

Suggested reviewers: cossssmin

Merge Risk: ⚪ Minimal · up to df7b0

Development previews can use template sample props without changing regular rendering. No actionable merge-blocking issue is identified; merge after normal checks pass.

Security Architecture Review

Security architecture risk: 🔵 Low · up to df7b0

Normal rendering and builds do not automatically use sample values, even when sharing resources with development previews. Development test emails do use those values. Remaining uncertainty concerns development-server exposure and reuse of mutable sample objects.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • observed — The traced exposure is configured templates rendered through existing development endpoints, including test-email sending. Sample content can leave through a configured SMTP transport; without one, sending uses an Ethereal test account. Regular render and build callers do not enable preview.

Trust Boundaries and Controls

  • observed — Render and email request slugs must match files enumerated from configured content patterns before invocation. Requests do not supply preview props. Recipient selection and transport authority predate this PR; the new data source is the selected template's export.
  • observed — Regular render passes only caller props, including when reusing the development renderer. The added regression test checks that preview data appears in the development response but not in a subsequent regular render.

Resilience and Maintainability Implications

  • inferred — Per-call selection prevents the preview flag from persisting into later production calls, but it does not give nested sample values independent ownership. Mutating module-exported values during SSR could affect later previews using the cached module. No concrete mutation path was established, so this is not retained as a security finding.

Hardening Proposals

  • proposed — Document that development test emails inherit previewProps and may use a real configured transport. Recommend synthetic, non-sensitive sample values rather than production credentials or personal data.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 3 files. (2 skipped: 2… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: using previewProps to provide sample data when serving template previews.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 3 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

@cossssmin

Copy link
Copy Markdown
Member

Hey, thanks for picking this up and for linking it to the discussion, appreciate the effort.

That said, I'm going to pass on this one. The problem is real, but I don't think this is the right API for it:

  • It needs a second <script> block. Since <script setup> can't contain exports, every template needs an extra plain <script> just to hold sample data. That's a lot of ceremony for something only used while developing, and it's not a pattern most people would expect to see in an email template.
  • It's a magic export name. previewProps only works if it's spelled exactly right, otherwise nothing tells you. Nothing checks it against defineProps either, so the sample data can drift from the real props without you noticing.
  • It puts dev server logic in the renderer. The renderer is shared: the dev server uses it, and so does any render() call made while the dev server is running. Giving it a preview option and teaching it about a specific module export means a dev-only concern now lives in the production render path. I'd rather keep that separation strict.

What I'd consider instead is a definePreviewProps() macro, closer to what was proposed in #1802:

<script setup lang="ts">
interface Props {
  name: string
  plan: 'Free' | 'Pro'
}

defineProps<Props>()
definePreviewProps<Props>({ name: 'Jane', plan: 'Pro' })
</script>
  • Single <script setup>, with the sample data right next to defineProps.
  • Props stay required and typed, no need to make them optional or add defaults.
  • Only the dev server would ever use it, build and render() would ignore it completely.

Need to think it through a bit more before committing to it though.

In the meantime, you can already do this today without any framework changes: create a preview template that imports the real one and passes it props.

<!-- emails/previews/order-full.vue -->
<script setup lang="ts">
import Order from '../order.vue'
</script>

<template>
  <Order
    name="Jane"
    :items="[{ name: 'Mug', price: 12 }, { name: 'Tee', price: 25 }]"
  />
</template>

The real template stays untouched, so sample data can never end up in a production render(). You can also have multiple variants this way, like order-full.vue and order-empty.vue. Note that if you also run maizzle build, these will get built too, but for a render() setup like the one in #1802 that doesn't really matter.

Closing this for now, but thanks again for the PR!

@cossssmin cossssmin closed this Oct 2, 2026
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