fix(blog): add missing mintelP/TLDR renderers, fix iconList, diagram blocks, reduce AI components to 13
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 7s
Build & Deploy / 🏗️ Build (push) Successful in 11m52s
Build & Deploy / 🚀 Deploy (push) Successful in 14s
Build & Deploy / 🧪 QA (push) Successful in 1m15s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 6m31s
Build & Deploy / 🔔 Notify (push) Successful in 2s

- Add mintelP renderer with inline markdown link/marker support (228 broken blocks)
- Add mintelTldr renderer for summary boxes
- Fix iconList to display item.title instead of empty item.description
- Rewire all 6 diagram block types to render via Mermaid
- Remove ai property from 30 non-essential blocks (46 -> 13)
- Tighten MemeCard to 5 verified templates, max 1 per article
- Fix PerformanceChartBlock syntax after ai removal
This commit is contained in:
2026-03-05 17:39:57 +01:00
parent 1bd516fbe4
commit 4e6f3f29cf
32 changed files with 74 additions and 219 deletions

View File

@@ -5,6 +5,39 @@ import { Mermaid } from "@/src/components/Mermaid";
import { LeadMagnet } from "@/src/components/LeadMagnet";
import { ComparisonRow } from "@/src/components/Landing/ComparisonRow";
import { mdxComponents } from "../content-engine/components";
import React from "react";
/**
* Renders markdown-style inline links [text](/url) as <a> tags.
* Used by mintelP blocks which store body text with links.
*/
function renderInlineMarkdown(text: string): React.ReactNode {
if (!text) return null;
const parts = text.split(/(\[[^\]]+\]\([^)]+\)|<Marker>[^<]*<\/Marker>)/);
return parts.map((part, i) => {
const linkMatch = part.match(/\[([^\]]+)\]\(([^)]+)\)/);
if (linkMatch) {
return (
<a
key={i}
href={linkMatch[2]}
className="text-slate-900 underline underline-offset-4 hover:text-slate-600 transition-colors"
>
{linkMatch[1]}
</a>
);
}
const markerMatch = part.match(/<Marker>([^<]*)<\/Marker>/);
if (markerMatch) {
return (
<mark key={i} className="bg-yellow-100/60 px-1 rounded">
{markerMatch[1]}
</mark>
);
}
return <React.Fragment key={i}>{part}</React.Fragment>;
});
}
const jsxConverters: JSXConverters = {
blocks: {
@@ -49,6 +82,15 @@ const jsxConverters: JSXConverters = {
showShare={true}
/>
),
// --- Core text blocks ---
mintelP: ({ node }: any) => (
<p className="text-base md:text-lg text-slate-600 leading-relaxed mb-6">
{renderInlineMarkdown(node.fields.text)}
</p>
),
mintelTldr: ({ node }: any) => (
<mdxComponents.TLDR>{node.fields.content}</mdxComponents.TLDR>
),
// --- MDX Registry Injections ---
leadParagraph: ({ node }: any) => (
<mdxComponents.LeadParagraph>
@@ -81,37 +123,46 @@ const jsxConverters: JSXConverters = {
/>
),
diagramState: ({ node }: any) => (
<mdxComponents.DiagramState
states={[]}
transitions={[]}
caption={node.fields.definition}
/>
<div className="my-8">
<Mermaid id={`diagram-state-${node.fields.id || Date.now()}`}>
{node.fields.definition}
</Mermaid>
</div>
),
diagramTimeline: ({ node }: any) => (
<mdxComponents.DiagramTimeline
events={[]}
title={node.fields.definition}
/>
<div className="my-8">
<Mermaid id={`diagram-timeline-${node.fields.id || Date.now()}`}>
{node.fields.definition}
</Mermaid>
</div>
),
diagramGantt: ({ node }: any) => (
<mdxComponents.DiagramGantt tasks={[]} title={node.fields.definition} />
<div className="my-8">
<Mermaid id={`diagram-gantt-${node.fields.id || Date.now()}`}>
{node.fields.definition}
</Mermaid>
</div>
),
diagramPie: ({ node }: any) => (
<mdxComponents.DiagramPie data={[]} title={node.fields.definition} />
<div className="my-8">
<Mermaid id={`diagram-pie-${node.fields.id || Date.now()}`}>
{node.fields.definition}
</Mermaid>
</div>
),
diagramSequence: ({ node }: any) => (
<mdxComponents.DiagramSequence
participants={[]}
steps={[]}
title={node.fields.definition}
/>
<div className="my-8">
<Mermaid id={`diagram-seq-${node.fields.id || Date.now()}`}>
{node.fields.definition}
</Mermaid>
</div>
),
diagramFlow: ({ node }: any) => (
<mdxComponents.DiagramFlow
nodes={[]}
edges={[]}
title={node.fields.definition}
/>
<div className="my-8">
<Mermaid id={`diagram-flow-${node.fields.id || Date.now()}`}>
{node.fields.definition}
</Mermaid>
</div>
),
waterfallChart: ({ node }: any) => (
@@ -131,7 +182,7 @@ const jsxConverters: JSXConverters = {
{node.fields.items?.map((item: any, i: number) => (
// @ts-ignore
<mdxComponents.IconListItem key={i} icon={item.icon || "check"}>
{item.description}
{item.title || item.description}
</mdxComponents.IconListItem>
))}
</mdxComponents.IconList>

View File

@@ -11,12 +11,6 @@ export const ArchitectureBuilderBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "ArchitectureBuilder",
description:
"Interactive comparison between a standard SaaS rental approach and a custom Built-First (Mintel) architecture. Useful for articles discussing digital ownership, software rent vs. build, or technological assets. Requires no props.",
usageExample: "'<ArchitectureBuilder />'",
},
fields: [
{
name: "preset",

View File

@@ -11,12 +11,6 @@ export const ArticleBlockquoteBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "ArticleBlockquote",
description: "Styled blockquote for expert quotes or key statements.",
usageExample:
"'<ArticleBlockquote>\n Performance ist keine IT-Kennzahl, sondern ein ökonomischer Hebel.\n</ArticleBlockquote>'",
},
fields: [
{
name: "quote",

View File

@@ -10,13 +10,6 @@ export const ArticleMemeBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "ArticleMeme",
description:
"Real image-based meme from the media library. Use for static screenshots or custom memes that are not available via memegen.link.",
usageExample:
'<ArticleMeme image="/media/my-meme.png" alt="Sarcastic dev meme" caption="When the code finally builds." />',
},
fields: [
{
name: "image",

View File

@@ -11,13 +11,6 @@ export const ButtonBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "Button",
description:
"DEPRECATED: Use <LeadMagnet /> instead for main CTAs. Only use for small secondary links.",
usageExample:
'<Button href="/contact" variant="outline">Webprojekt anfragen</Button>',
},
fields: [
{
name: "label",

View File

@@ -11,13 +11,6 @@ export const CarouselBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "Carousel",
description:
"Interactive swipeable slider for multi-step explanations. IMPORTANT: items array must contain at least 2 items with substantive title and content text (no empty content).",
usageExample:
'\'<Carousel items={[{ title: "Schritt 1", content: "Analyse der aktuellen Performance..."',
},
fields: [
{
name: "slides",

View File

@@ -11,12 +11,6 @@ export const DiagramFlowBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "DiagramFlow",
description:
"Mermaid flowchart diagram defining the graph structure. MUST output raw mermaid code, no quotes or HTML.",
usageExample: "graph TD\\n A[Start] --> B[End]",
},
fields: [
{
name: "definition",

View File

@@ -11,12 +11,6 @@ export const DiagramGanttBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "DiagramGantt",
description: "Mermaid Gantt timeline chart. MUST output raw mermaid code.",
usageExample:
"gantt\\n title Project Roadmap\\n dateFormat YYYY-MM-DD\\n Section Design\\n Draft UI :a1, 2024-01-01, 7d",
},
fields: [
{
name: "definition",

View File

@@ -11,11 +11,6 @@ export const DiagramPieBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "DiagramPie",
description: "Mermaid pie chart diagram. MUST output raw mermaid code.",
usageExample: 'pie title Market Share\\n "Chrome" : 60\\n "Safari" : 20',
},
fields: [
{
name: "definition",

View File

@@ -11,13 +11,6 @@ export const DiagramSequenceBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "DiagramSequence",
description:
"Mermaid sequence diagram showing actor interactions. MUST output raw mermaid code.",
usageExample:
"sequenceDiagram\\n Client->>Server: GET /api\\n Server-->>Client: 200 OK",
},
fields: [
{
name: "definition",

View File

@@ -11,12 +11,6 @@ export const DiagramStateBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "DiagramState",
description:
"Mermaid state diagram showing states and transitions. MUST output raw mermaid code.",
usageExample: "stateDiagram-v2\\n [*] --> Idle\\n Idle --> Loading",
},
fields: [
{
name: "definition",

View File

@@ -11,13 +11,6 @@ export const DiagramTimelineBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "DiagramTimeline",
description:
"Mermaid timeline or journey diagram. MUST output raw mermaid code.",
usageExample:
"timeline\\n title Project Timeline\\n 2024\\n : Q1 : Planning\\n : Q2 : Execution",
},
fields: [
{
name: "definition",

View File

@@ -11,12 +11,6 @@ export const DigitalAssetVisualizerBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "DigitalAssetVisualizer",
description:
"Interactive visualization illustrating the financial difference between software as a liability (SaaS/rent) and software as a digital asset (Custom IP). Great for articles concerning CTO strategies, business value of code, and digital independence. Requires no props.",
usageExample: "'<DigitalAssetVisualizer />'",
},
fields: [
{
name: "assetId",

View File

@@ -16,13 +16,6 @@ export const FAQSectionBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "FAQSection",
description:
"Semantic wrapper for FAQ questions at the end of the article. Put standard Markdown H3/Paragraphs inside.",
usageExample:
"'<FAQSection>\n <H3>Frage 1</H3>\n <Paragraph>Antwort 1</Paragraph>\n</FAQSection>'",
},
fields: [
{
name: "content",

View File

@@ -11,12 +11,6 @@ export const ImageTextBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "ImageText",
description: "Layout component for image next to explanatory text.",
usageExample:
'\'<ImageText image="/img.jpg" title="Architektur">Erklärung...</ImageText>\'',
},
fields: [
{
name: "image",

View File

@@ -11,13 +11,6 @@ export const LinkedInEmbedBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "LinkedInEmbed",
description:
"Embeds a professional post from LinkedIn. Use the activity URN (e.g. urn:li:activity:1234567890).",
usageExample:
"'<LinkedInEmbed urn=\"urn:li:activity:7153664326573674496\" />'",
},
fields: [
{
name: "url",

View File

@@ -11,12 +11,6 @@ export const LoadTimeSimulatorBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "LoadTimeSimulator",
description:
"Interactive visual race simulating the loading experience of a slow legacy CMS vs a fast headless stack. Great for articles discussing load times, technical debt, or user frustration. Requires no props.",
usageExample: "'<LoadTimeSimulator />'",
},
fields: [
{
name: "initialLoadTime",

View File

@@ -11,12 +11,6 @@ export const MarkerBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "Marker",
description:
"Inline highlight (yellow marker effect) for emphasizing key phrases within paragraphs.",
usageExample: "'<Marker>entscheidender Wettbewerbsvorteil</Marker>'",
},
fields: [
{
name: "text",

View File

@@ -13,7 +13,7 @@ export const MemeCardBlock: MintelBlock = {
ai: {
name: "MemeCard",
description:
'Real meme image from memegen.link. template must be a valid memegen.link ID. IMPORTANT: Captions must be EXTREMELY SARCASTIC and PUNCHY (mocking bad B2B agencies, max 6 words per line). Best templates: drake (2-line prefer/dislike), gru (4-step plan backfire), disastergirl (burning house), fine (this is fine dog). Use German captions. Wrap in div with className="my-8".',
"Real meme from memegen.link. ONLY use these templates: drake, distracted-bf, change-my-mind, uno-draw-25, always-has-been. Captions MUST be in German, extremely sarcastic, max 6 words per line. Use pipe | to separate caption lines. Use sparingly: MAX 1 meme per article.",
usageExample: `<div className="my-8">
<MemeCard template="drake" captions="47 WordPress Plugins installieren|Eine saubere Serverless Architektur" />
</div>`,

View File

@@ -11,12 +11,6 @@ export const MetricBarBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "MetricBar",
description:
"Animated horizontal progress bar. Use multiple in sequence to compare metrics. IMPORTANT: value MUST be a real number > 0, never use 0 or placeholder values. Props: label, value (number), max (default 100), unit (default %), color (red|green|blue|slate).",
usageExample: '<MetricBar label="WordPress Sites" value={33',
},
fields: [
{
name: "label",

View File

@@ -11,13 +11,6 @@ export const PerformanceChartBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "PerformanceChart",
description:
"A visual chart illustrating performance metrics (e.g. PageSpeed, TTFB) over time or in comparison. Use to emphasize technical improvements.",
usageExample:
'<PerformanceChart items={[{ label: "Vorher", value: 12 }, { label: "Nachher", value: 98 }]} />',
},
fields: [
{
name: "title",

View File

@@ -11,12 +11,6 @@ export const PerformanceROICalculatorBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "PerformanceROICalculator",
description:
"Interactive simulation calculator showing the monetary ROI of improving load times (based on Deloitte B2B metrics). Use exactly once in performance-related articles to provide a highly engaging simulation. Requires no props.",
usageExample: "'<PerformanceROICalculator />'",
},
fields: [
{
name: "baseConversionRate",

View File

@@ -11,13 +11,6 @@ export const PremiumComparisonChartBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "PremiumComparisonChart",
description:
"Advanced chart for comparing performance metrics with industrial aesthetics.",
usageExample:
'\'<PremiumComparisonChart title="TTFB Vergleich" items={[{ label: "Alt", value: 800, max: 1000, color: "red"',
},
fields: [
{
name: "title",

View File

@@ -12,13 +12,6 @@ export const RevealBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "Reveal",
description:
"Scroll-triggered reveal animation wrapper. Wrap any content to animate on scroll.",
usageExample:
'\'<Reveal>\n <StatsDisplay value="100" label="PageSpeed Score" />\n</Reveal>\'',
},
fields: [
{
name: "direction",

View File

@@ -11,12 +11,6 @@ export const RevenueLossCalculatorBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "RevenueLossCalculator",
description:
"Interactive calculator that estimates financial loss due to slow page load times. Use to build a business case for performance optimization.",
usageExample: "<RevenueLossCalculator />",
},
fields: [
{
name: "title",

View File

@@ -12,12 +12,6 @@ export const SectionBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "Section",
description: "Wraps a thematic section block with optional heading.",
usageExample:
"'<Section>\n <h3>Section Title</h3>\n <p>Content here.</p>\n</Section>'",
},
fields: [
{
name: "title",

View File

@@ -11,13 +11,6 @@ export const StatsDisplayBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "StatsDisplay",
description:
"A single large stat card with prominent value, label, and optional subtext.",
usageExample:
'\'<StatsDisplay value="-20%" label="Conversion" subtext="Jede Sekunde Verzögerung kostet." />\'',
},
fields: [
{
name: "label",

View File

@@ -11,13 +11,6 @@ export const StatsGridBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "StatsGrid",
description:
"Grid of 24 stat cards in a row. Use tilde (~) to separate stats, pipe (|) to separate value|label|subtext within each stat.",
usageExample:
"'<StatsGrid stats=\"53%|Mehr Umsatz|Rakuten 24~33%|Conversion Boost|nach CWV Fix~24%|Top 3 Ranking|bei bestandenen CWV\" />'",
},
fields: [
{
name: "stats",

View File

@@ -11,13 +11,6 @@ export const TrackedLinkBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "TrackedLink",
description:
"A wrapper around next/link that tracks clicks. Use for all INTERNAL navigational links that should be tracked.",
usageExample:
'\'<TrackedLink href="/contact" className="text-blue-600 font-bold">Jetzt anfragen</TrackedLink>\'',
},
fields: [
{
name: "href",

View File

@@ -11,13 +11,6 @@ export const TwitterEmbedBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "TwitterEmbed",
description:
"Embeds a post from X.com (Twitter). Used to provide social proof, industry quotes, or examples. Provide the numerical tweetId.",
usageExample:
'\'<TwitterEmbed tweetId="1753464161943834945" theme="light" />\'',
},
fields: [
{
name: "url",

View File

@@ -11,15 +11,6 @@ export const WaterfallChartBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "WaterfallChart",
description:
"A timeline visualization of network requests (waterfall). Use to show loading sequences or bottlenecks. Labels auto-color coded by type (JS, HTML, IMG).",
usageExample: `<WaterfallChart
title="Initial Load"
events={[
{ name: "Document", start: 0, duration: 150`,
},
fields: [
{
name: "title",

View File

@@ -11,12 +11,6 @@ export const WebVitalsScoreBlock: MintelBlock = {
admin: {
group: "MDX Components",
},
ai: {
name: "WebVitalsScore",
description:
"Displays Core Web Vitals (LCP, INP, CLS) in a premium card layout with automatic traffic light coloring (Good/Needs Improvement/Poor). Use for performance audits or comparisons.",
usageExample: "'<WebVitalsScore values={{ lcp: 2.5, inp: 200, cls: 0.1",
},
fields: [
{
name: "lcp",