Skip to content

Commit 2e4ff7a

Browse files
committed
fix(mobile): keep Mermaid labels that look like CSS intact
nativeMermaidSvg resolved var(...) and color-mix(...) across the whole SVG string, so a label such as `var(--brand)` was rewritten or deleted, and an unmatched `var(` could swallow the rest of the SVG. Substitute only inside tag attribute values; pele escapes label text, so labels live in text nodes. Model: Claude Opus 5.5 (claude-opus-5-5) via Claude Code in T3 Code
1 parent 058ed89 commit 2e4ff7a

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

‎apps/mobile/src/lib/mermaidDiagrams.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,33 @@ describe("pele SVG for react-native-svg", () => {
7373
expect(svg).toContain('stroke="#888888"');
7474
});
7575

76+
it("leaves label text that looks like CSS untouched", () => {
77+
const svg = nativeMermaidSvg(
78+
render(
79+
'flowchart TD\n A["var(--brand) > x"] --> B{"OK? var(--pele-fg"}\n B --> C["color-mix(in srgb, var(--a) 50%, var(--b))"]',
80+
).svg,
81+
palette,
82+
);
83+
expect(svg).toContain(">var(--brand) &gt; x</text>");
84+
expect(svg).toContain(">OK? var(--pele-fg</text>");
85+
expect(svg).toContain(">color-mix(in srgb, var(--a)</tspan>");
86+
expect(svg).toContain(">50%, var(--b))</tspan>");
87+
expect(svg.replace(/<(text|tspan)[^>]*>[^<]*<\/\1>/g, "")).not.toMatch(/var\(|color-mix/);
88+
});
89+
90+
it("resolves every attribute, including font weights, across diagram types", () => {
91+
for (const source of [
92+
"classDiagram\n A <|-- B",
93+
"gantt\n title T\n section S\n x :a1, 2024-01-01, 3d",
94+
"timeline\n title T\n 2020: a",
95+
]) {
96+
const markup = nativeMermaidSvg(render(source).svg, palette)
97+
.match(/<[^>]*>/g)!
98+
.join("");
99+
expect(markup).not.toMatch(/var\(|color-mix|--_/);
100+
}
101+
});
102+
76103
it("keeps Pele's fallback for colors the theme does not set", () => {
77104
const svg = nativeMermaidSvg(render('pie\n "Dogs" : 3\n "Cats" : 1').svg, palette);
78105
expect(svg).toContain('fill="#4c78a8"');

‎apps/mobile/src/lib/mermaidSvg.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ export interface MermaidPalette {
1212
// Only a nested flowchart cluster's title stroke uses color-mix; its first color is close enough.
1313
const COLOR_MIX = /color-mix\(in srgb,\s*(var\(--[\w-]+\))\s+[\d.]+%,\s*var\(--[\w-]+\)\)/g;
1414

15+
// Pele escapes `<`, `>`, and `&` in label text and attribute values, so `<...>` is always markup.
16+
const MARKUP_TAG = /<[^>]*>/g;
17+
// Labels are text nodes, so substituting only inside attribute values leaves a label that
18+
// happens to read `var(--x)` or `color-mix(...)` untouched.
19+
const ATTRIBUTE = /(\s[\w:-]+=")([^"]*)"/g;
20+
1521
function resolveCssVariables(text: string, variables: ReadonlyMap<string, string>, depth = 0) {
1622
let resolved = "";
1723
let index = 0;
@@ -63,5 +69,11 @@ export function nativeMermaidSvg(svg: string, palette: MermaidPalette): string {
6369
}
6470
const fontSize = /(?:^|;)\s*font:\s*([\d.]+)px/.exec(style)?.[1] ?? "16";
6571
const nativeRoot = `${root.replace(/\sstyle="[^"]*"/, "")} font-size="${fontSize}" color="var(--_fg)"`;
66-
return resolveCssVariables((nativeRoot + svg.slice(rootEnd)).replace(COLOR_MIX, "$1"), variables);
72+
return (nativeRoot + svg.slice(rootEnd)).replace(MARKUP_TAG, (tag) =>
73+
tag.replace(
74+
ATTRIBUTE,
75+
(_, open: string, value: string) =>
76+
`${open}${resolveCssVariables(value.replace(COLOR_MIX, "$1"), variables)}"`,
77+
),
78+
);
6779
}

0 commit comments

Comments
 (0)