From 94f2c01590d52962c565eea5d6f47444ff0d5782 Mon Sep 17 00:00:00 2001
From: Luca <102960244+Luca-Timo@users.noreply.github.com>
Date: Sat, 6 Jun 2026 22:50:03 +0200
Subject: [PATCH] feat(projects): flag re-rendered emails in the feed
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Each email in the rollup now carries a 'stored' flag (rendered_html present).
Emails without an exact stored copy show an amber '≈ re-rendered' tag next to
Preview, so it's visible at a glance — not just inside the modal. en + de.
---
backend/src/services/projectService.js | 6 +++++-
frontend/src/i18n/locales/de.json | 3 ++-
frontend/src/i18n/locales/en.json | 3 ++-
.../src/pages/admin/projects/ProjectCockpitPage.tsx | 11 ++++++++++-
frontend/src/services/projects.service.ts | 2 ++
5 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/backend/src/services/projectService.js b/backend/src/services/projectService.js
index f7cefeb1..7b635f4e 100644
--- a/backend/src/services/projectService.js
+++ b/backend/src/services/projectService.js
@@ -255,12 +255,16 @@ async function getProjectOverview(id, perms = {}) {
});
}
})
- .select('id', 'recipient_email', 'email_type', 'status', 'created_at', 'sent_at', 'error_message', 'event_id')
+ .select('id', 'recipient_email', 'email_type', 'status', 'created_at', 'sent_at', 'error_message', 'event_id',
+ // Exact stored preview available? (CASE is cross-DB: SQLite→0/1, PG→int)
+ db.raw('CASE WHEN rendered_html IS NOT NULL THEN 1 ELSE 0 END as has_rendered'))
.orderBy('created_at', 'desc')
.limit(200);
out.emails = emails.map((e) => ({
id: e.id, recipient: e.recipient_email, type: e.email_type, status: e.status,
queuedAt: e.created_at, sentAt: e.sent_at, error: e.error_message, eventId: e.event_id,
+ // false → the cockpit preview will re-render from the current template.
+ stored: !!Number(e.has_rendered),
}));
}
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index 7ca81d4d..a16ae06a 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -3440,7 +3440,8 @@
"retry": "Wiederholen",
"previewTitle": "E-Mail-Vorschau",
"noPreview": "Keine gespeicherte Vorschau für diese E-Mail — sie wurde gesendet, bevor Vorschauen erfasst wurden.",
- "reRendered": "Neu gerendert aus der aktuellen Vorlage — diese E-Mail wurde gesendet, bevor Vorschauen erfasst wurden, und kann daher leicht von der tatsächlich versendeten abweichen."
+ "reRendered": "Neu gerendert aus der aktuellen Vorlage — diese E-Mail wurde gesendet, bevor Vorschauen erfasst wurden, und kann daher leicht von der tatsächlich versendeten abweichen.",
+ "reRenderedTag": "≈ neu gerendert"
},
"toast": {
"created": "Projekt erstellt",
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index cda08043..1bbca587 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -3440,7 +3440,8 @@
"retry": "Retry",
"previewTitle": "Email preview",
"noPreview": "No stored preview for this email — it was sent before previews were captured.",
- "reRendered": "Re-rendered from the current template — this email was sent before previews were captured, so it may differ slightly from what the recipient received."
+ "reRendered": "Re-rendered from the current template — this email was sent before previews were captured, so it may differ slightly from what the recipient received.",
+ "reRenderedTag": "≈ re-rendered"
},
"toast": {
"created": "Project created",
diff --git a/frontend/src/pages/admin/projects/ProjectCockpitPage.tsx b/frontend/src/pages/admin/projects/ProjectCockpitPage.tsx
index a81fbcd6..593bba12 100644
--- a/frontend/src/pages/admin/projects/ProjectCockpitPage.tsx
+++ b/frontend/src/pages/admin/projects/ProjectCockpitPage.tsx
@@ -39,6 +39,7 @@ interface FeedItem {
href?: string | null;
emailId?: number;
emailStatus?: string;
+ reRendered?: boolean;
}
/** Detail-page route for a clickable document, or null when there isn't one
@@ -153,7 +154,7 @@ export const ProjectCockpitPage: React.FC = () => {
key: `email-${e.id}`, kind: 'email', date: e.sentAt || e.queuedAt,
title: t(`projects.feed.email`, 'Email') + ` · ${e.type}`,
subtitle: e.recipient + (e.error ? ` — ${e.error}` : ''),
- status: e.status, emailId: e.id, emailStatus: e.status,
+ status: e.status, emailId: e.id, emailStatus: e.status, reRendered: !e.stored,
});
}
for (const q of data.quotes) {
@@ -361,6 +362,14 @@ export const ProjectCockpitPage: React.FC = () => {
+ {item.reRendered && (
+
+ {t('projects.email.reRenderedTag', '≈ re-rendered')}
+
+ )}
{item.emailStatus === 'sent' && (