diff --git a/e2e/no-raw-mermaid.test.ts b/e2e/no-raw-mermaid.test.ts new file mode 100644 index 00000000..16098859 --- /dev/null +++ b/e2e/no-raw-mermaid.test.ts @@ -0,0 +1,38 @@ +import { expect, test } from '@playwright/test' +import { readdirSync, readFileSync, statSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +function findMdxFiles(dir: string): string[] { + const results: string[] = [] + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name) + if (entry.isDirectory() && entry.name !== 'node_modules') { + results.push(...findMdxFiles(full)) + } else if (entry.name.endsWith('.mdx')) { + results.push(full) + } + } + return results +} + +test('no raw ```mermaid code blocks in MDX files', () => { + const pagesDir = join(__dirname, '..', 'src', 'pages') + const mdxFiles = findMdxFiles(pagesDir) + + const violations: string[] = [] + for (const file of mdxFiles) { + const content = readFileSync(file, 'utf-8') + if (/^```mermaid\s*$/m.test(content)) { + const relative = file.replace(join(__dirname, '..') + '/', '') + violations.push(relative) + } + } + + expect(violations, [ + 'Found raw ```mermaid code blocks. Use instead:', + ...violations.map((f) => ` - ${f}`), + ].join('\n')).toHaveLength(0) +}) diff --git a/src/components/StaticMermaidDiagram.tsx b/src/components/StaticMermaidDiagram.tsx new file mode 100644 index 00000000..baa1a326 --- /dev/null +++ b/src/components/StaticMermaidDiagram.tsx @@ -0,0 +1,113 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' +import { THEMES } from './MermaidDiagram' + +const FONT_FAMILY = + 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"' + +let idCounter = 0 + +export function StaticMermaidDiagram({ chart }: { chart: string }) { + const containerRef = useRef(null) + const [isDark, setIsDark] = useState(false) + + useEffect(() => { + const check = () => + setIsDark( + document.documentElement.style.colorScheme === 'dark' || + document.documentElement.classList.contains('dark'), + ) + check() + const obs = new MutationObserver(check) + obs.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class', 'style'], + }) + return () => obs.disconnect() + }, []) + + useEffect(() => { + const el = containerRef.current + if (!el) return + + let cancelled = false + + async function renderChart() { + const { default: mermaid } = await import('mermaid') + if (cancelled) return + + const th = isDark ? THEMES.dark : THEMES.light + + const nodeBg = isDark ? '#27272a' : th.actorFill + const nodeBorder = isDark ? '#52525b' : th.actorStroke + const clusterBg = isDark ? '#3f3f46' : th.blockHeaderBg + const clusterBorder = isDark ? '#52525b' : th.blockStroke + + mermaid.initialize({ + startOnLoad: false, + theme: 'base', + themeVariables: { + fontFamily: FONT_FAMILY, + fontSize: '14px', + primaryColor: clusterBg, + primaryTextColor: th.text, + primaryBorderColor: clusterBorder, + lineColor: th.line, + secondaryColor: nodeBg, + tertiaryColor: clusterBg, + mainBkg: nodeBg, + nodeBorder, + clusterBkg: clusterBg, + clusterBorder, + titleColor: th.text, + edgeLabelBackground: nodeBg, + }, + flowchart: { + htmlLabels: true, + curve: 'basis', + padding: 12, + nodeSpacing: 40, + rankSpacing: 40, + }, + }) + + const id = `static-mermaid-${++idCounter}` + try { + const { svg } = await mermaid.render(id, chart.trim()) + if (cancelled || !el) return + el.innerHTML = svg + const svgEl = el.querySelector('svg') + if (svgEl) { + svgEl.style.maxWidth = '100%' + svgEl.style.height = 'auto' + svgEl.style.display = 'block' + svgEl.style.margin = '0 auto' + } + } catch (err) { + console.error('StaticMermaidDiagram:', err) + } + } + + renderChart() + return () => { + cancelled = true + } + }, [chart, isDark]) + + return ( +
+
+
+ ) +} diff --git a/src/pages/accounts/index.mdx b/src/pages/accounts/index.mdx index 523c3965..4a0ac7c7 100644 --- a/src/pages/accounts/index.mdx +++ b/src/pages/accounts/index.mdx @@ -7,6 +7,7 @@ import { Cards, Card } from 'vocs' import * as Demo from '../../components/guides/Demo.tsx' import * as Step from '../../components/guides/steps' import IconGitHub from '~icons/simple-icons/github' +import { StaticMermaidDiagram } from '../../components/StaticMermaidDiagram' # Getting Started @@ -47,8 +48,7 @@ Your application interacts with the SDK through standard JSON-RPC methods (via [ An example of a high-level flow of an Application requesting for a user to perform an action with their Tempo Wallet is shown below: -```mermaid -sequenceDiagram +>S: result S-->>A: result -``` +`} /> ## Install diff --git a/src/pages/guide/node/validator-config-v2.mdx b/src/pages/guide/node/validator-config-v2.mdx index 94bb86a0..3e41d115 100644 --- a/src/pages/guide/node/validator-config-v2.mdx +++ b/src/pages/guide/node/validator-config-v2.mdx @@ -3,6 +3,8 @@ title: ValidatorConfig V2 description: Manage your validator with ValidatorConfig V2. Self-service key rotation, IP updates, and ownership transfer. --- +import { StaticMermaidDiagram } from '../../../components/StaticMermaidDiagram' + # ValidatorConfig V2 ValidatorConfig V2 ([TIP-1017](/protocol/tips/tip-1017)) is the precompile that manages consensus participants. This config was [activated on mainnet](https://explore.mainnet.tempo.xyz/receipt/0x4716147e3c2bf5c8d014b8c27d6e2af0042d5a5f29bdead256d6f33038702d64) after the T2 hardfork. @@ -11,12 +13,11 @@ ValidatorConfig V2 ([TIP-1017](/protocol/tips/tip-1017)) is the precompile that Once added, a validator immediately becomes a player in the next epoch. -```mermaid -flowchart TD +|epoch ends| B["Player
Receives signing shares"] B -->|"+1 epoch, receives share"| C["Player
Dealer"] C -->|"once fully synced"| D["Voter ↔ Proposer"] -``` +`} /> ## What changes for operators Validators can perform several operations themselves without coordinating with the Tempo team: