Skip to content

feat: add visualization support with charts and mermaid diagrams#89

Closed
honix wants to merge 2 commits into
mainfrom
claude/add-visualizations-yDAks
Closed

feat: add visualization support with charts and mermaid diagrams#89
honix wants to merge 2 commits into
mainfrom
claude/add-visualizations-yDAks

Conversation

@honix

@honix honix commented Jan 18, 2026

Copy link
Copy Markdown
Owner

Summary

This PR adds comprehensive visualization capabilities to Sviter, enabling users to embed interactive charts and diagrams directly in wiki pages.

Features Added

  • Chart Visualization: Embed charts from CSV data using image syntax

    • Syntax: ![chartType](file.chart.csv)
    • Supports: line, bar, pie, and area charts
    • Auto-detects chart type based on data structure
    • Chart type can be specified in alt text: ![bar](data.chart.csv)
  • Mermaid Diagrams: Create flowcharts, sequence diagrams, and more

    • Syntax: ```mermaid ... ``` code blocks
    • Full Mermaid.js support for various diagram types
    • Renders directly in page view

Implementation Details

  • Frontend Dependencies: Added recharts for charts and mermaid for diagrams
  • ProseMirror Integration:
    • Extended schema with mermaid and chart node types
    • Custom NodeViews for React-based rendering
    • Enhanced markdown parser to recognize visualization syntax
  • Components:
    • ChartViewer: Fetches CSV data and renders interactive charts
    • MermaidDiagram: Renders Mermaid diagrams with error handling
  • Demo Content: Added Visualizations-Demo.md and weekly-stats.chart.csv to wiki

Testing

Test the visualizations by viewing Visualizations-Demo.md in the wiki.


Note: This PR was generated with assistance from Claude Code.

Test plan

  • View Visualizations-Demo.md page and verify charts render correctly
  • Verify Mermaid diagrams display properly in various diagram types
  • Test chart interaction (hover tooltips, legends)
  • Verify charts work in both view and edit modes
  • Check error handling for missing/invalid CSV files
  • Verify mermaid syntax errors are displayed clearly

Add comprehensive visualization capabilities to Sviter:

- Install recharts and mermaid dependencies
- Create ChartViewer component for CSV data visualization
  - Supports line, bar, pie, and area charts
  - Auto-detects chart type from data
  - Fetches CSV data from wiki assets
- Create MermaidDiagram component for diagram rendering
- Extend ProseMirror schema with mermaid and chart nodes
- Add custom markdown parsing for visualization syntax:
  - Mermaid: ```mermaid ... ``` code blocks
  - Charts: ![type](file.chart.csv) image syntax
- Implement React-based NodeViews for rendering in ProseMirror
- Integrate NodeViews into both CollaborativeEditor and ProseMirrorEditor

Syntax:
- Charts: ![line](weekly-stats.chart.csv)
- Diagrams: ```mermaid ... ```
@claude

claude Bot commented Jan 18, 2026

Copy link
Copy Markdown

Claude finished @honix's task in 1m 58s —— View job


Code Review Complete ✓

I've completed a thorough review of the visualization feature PR. This is an excellent addition to Sviter that brings powerful chart and diagram capabilities! However, there are several security issues and bugs that should be addressed before merging.


🔴 Critical Issues (Must Fix)

1. Security: Mermaid securityLevel: 'loose'

  • File: MermaidDiagram.tsx:12
  • Impact: XSS vulnerability - allows arbitrary HTML/JavaScript in diagrams
  • Fix: Change to securityLevel: 'strict' or 'antiscript'

2. Security: CSV Injection Vulnerability

  • File: ChartViewer.tsx:39
  • Impact: Naive CSV parser doesn't handle quoted fields, escaped chars, or malicious content
  • Fix: Use a proper CSV library like papaparse or csv-parse

3. Bug: Placeholder Replacement Logic

  • File: conversion.ts:275
  • Impact: Can incorrectly replace content that contains placeholder text; doesn't handle multiple placeholders
  • Fix: Use exact matching instead of includes()

🟡 Important Issues (Should Fix)

4. Performance: Missing Request Cancellation

  • File: nodeViews.tsx:14
  • Impact: Race conditions and memory leaks when rapidly switching charts
  • Fix: Implement AbortController to cancel pending fetch requests

5. Bug: Incorrect Number Parsing

  • File: ChartViewer.tsx:53
  • Impact: Empty strings become 0 instead of being skipped, misleading users
  • Fix: Check for empty/whitespace before parsing: trimmed !== '' && !isNaN(num)

6. Bug: Regex State Issues

  • File: conversion.ts:231
  • Impact: Global regex with .exec() in loop can cause state persistence bugs
  • Fix: Use matchAll() or reset lastIndex after loop

🟢 Minor Issues (Nice to Have)

7. Code Quality: Deprecated substr()

  • File: MermaidDiagram.tsx:19
  • Fix: Use substring() or crypto.randomUUID() for better ID generation

8. Code Quality: Unused Parameters

  • File: nodeViews.tsx:75
  • Fix: Remove underscore prefix if not intentionally unused, or document why

✅ What's Done Well

  1. Great Architecture: Clean separation between components, node views, and schema
  2. Error Handling: Both components show user-friendly error messages
  3. Type Safety: Good TypeScript usage throughout
  4. Integration: Seamless ProseMirror integration with proper NodeView lifecycle
  5. User Experience: Loading states, responsive charts, helpful error messages
  6. Documentation: Clear comments explaining complex logic

📊 Test Coverage Concerns

The test plan in the PR description is comprehensive, but no automated tests were added. Consider adding:

  1. Unit tests for CSV parsing (especially edge cases)
  2. Unit tests for markdown preprocessing (mermaid/chart extraction)
  3. E2E tests for chart rendering and interaction
  4. Security tests for malicious CSV/mermaid content

🎯 Recommendations

Priority 1 (Before Merge)

  • Fix Mermaid security level
  • Implement proper CSV parsing
  • Fix placeholder replacement logic

Priority 2 (Soon After)

  • Add request cancellation for chart fetching
  • Fix number parsing edge cases
  • Fix regex state issues

Priority 3 (Future)

  • Add automated tests
  • Consider chart caching for performance
  • Add chart type auto-detection improvements (currently basic)

📝 Overall Assessment

Code Quality: 7/10 - Well-structured but needs security/bug fixes
Security: 4/10 - Critical issues with Mermaid and CSV parsing
Performance: 7/10 - Good overall, but missing fetch cancellation
Test Coverage: 3/10 - No automated tests added

Recommendation: 🟡 Request Changes - The feature is excellent and well-implemented, but the security issues must be addressed before merging. After fixing the critical issues, this will be a great addition to Sviter!


Next Steps

I've added inline comments on specific lines with detailed explanations and code suggestions. Please review each comment and let me know if you have questions about any of the recommendations.

'#a28dff',
];

function parseCSV(csv: string): Array<Record<string, any>> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Concern: CSV Parsing Vulnerability

The current CSV parser is overly simplistic and vulnerable to CSV injection attacks. It only splits by commas without proper escaping or quoted field handling.

Issues:

  1. Fields with commas inside quotes aren't handled (e.g., "City, State" would be split incorrectly)
  2. No handling of escaped quotes or newlines within fields
  3. Could lead to data corruption or XSS if CSV data contains malicious content

Recommendation: Use a proper CSV parsing library like papaparse or csv-parse that handles:

  • Quoted fields with commas
  • Escaped quotes
  • Multi-line fields
  • Different delimiters

Example fix:

import Papa from 'papaparse';

function parseCSV(csv: string): Array<Record<string, any>> {
  const result = Papa.parse(csv, {
    header: true,
    dynamicTyping: true, // Auto-converts numbers
    skipEmptyLines: true,
  });
  return result.data;
}

headers.forEach((header, index) => {
const value = values[index];
// Try to parse as number, otherwise keep as string
row[header] = isNaN(Number(value)) ? value : Number(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Incorrect number parsing

Using isNaN(Number(value)) has issues:

  • Number("") returns 0, not NaN - empty strings become 0
  • Number(" ") returns 0 - whitespace becomes 0
  • Number("123abc") returns NaN correctly

This means empty cells will show as 0 in charts instead of being skipped, which could mislead users.

Better approach:

const trimmed = value.trim();
const num = Number(trimmed);
row[header] = trimmed !== '' && !isNaN(num) ? num : value;

mermaid.initialize({
startOnLoad: false,
theme: 'default',
securityLevel: 'loose',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Risk: securityLevel: 'loose'

Setting securityLevel: 'loose' disables security features and could expose the app to XSS attacks through malicious mermaid diagrams.

From Mermaid docs:

  • 'loose': Allows all HTML in labels
  • 'strict': Disallows HTML and JavaScript
  • 'antiscript': Sanitizes HTML

Recommendation: Use 'strict' or 'antiscript' instead:

mermaid.initialize({
  startOnLoad: false,
  theme: 'default',
  securityLevel: 'strict', // or 'antiscript'
  fontFamily: 'inherit',
});

If you need HTML in labels, use 'antiscript' which sanitizes but allows safe HTML.

export function MermaidDiagram({ chart }: MermaidDiagramProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [error, setError] = useState<string | null>(null);
const [id] = useState(() => `mermaid-${Math.random().toString(36).substr(2, 9)}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality: ID generation

Using Math.random().toString(36).substr(2, 9) is deprecated - substr is replaced by substring.

Also, consider using a more reliable ID generator to avoid potential collisions:

const [id] = useState(() => `mermaid-${crypto.randomUUID()}`);

Or if you need shorter IDs:

const [id] = useState(() => `mermaid-${Math.random().toString(36).substring(2, 11)}`);

Comment thread frontend/src/editor/nodeViews.tsx Outdated
/**
* Helper to fetch CSV data from the wiki
*/
async function fetchCSVData(src: string, currentPagePath: string | null): Promise<string> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance Concern: Missing request cancellation

The fetchCSVData function doesn't support aborting ongoing requests. If a user rapidly navigates between pages or updates chart sources, this could lead to:

  1. Race conditions (older requests completing after newer ones)
  2. Memory leaks from orphaned promises
  3. Unnecessary network traffic

Recommendation: Use AbortController:

async function fetchCSVData(
  src: string, 
  currentPagePath: string | null, 
  signal?: AbortSignal
): Promise<string> {
  try {
    const resolvedPath = resolvePath(src, currentPagePath);
    const url = `${getApiUrl()}/api/assets/${encodeURIComponent(resolvedPath).replace(/%2F/g, '/')}`;

    const response = await fetch(url, { signal });
    if (!response.ok) {
      throw new Error(`Failed to fetch: ${response.statusText}`);
    }

    return await response.text();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error('Request cancelled');
    }
    console.error('Error fetching CSV data:', error);
    throw error;
  }
}

Then in ChartNodeView, track and cancel the request on update/destroy.

private root: Root;
private currentPagePath: string | null;

constructor(node: ProseMirrorNode, _view: EditorView, _getPos: () => number | undefined, currentPagePath: string | null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Unused parameters with underscore prefix

Parameters _view and _getPos are prefixed with underscore but the constructor signature doesn't match ProseMirror's NodeView interface correctly. The third parameter should be getPos, not a function that returns number | undefined.

Correct signature:

constructor(
  node: ProseMirrorNode, 
  view: EditorView, 
  getPos: boolean | (() => number | undefined),
  currentPagePath: string | null
) {

You're currently not using view or getPos, but if you need them later (e.g., for handling clicks), you should store them without the underscore prefix.

let processed = markdown;

// Extract mermaid blocks
const mermaidRegex = /```mermaid\n([\s\S]*?)```/g;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Regex state persistence issue

Using regex .exec() in a while loop with a global flag can cause issues because the regex maintains state. If the regex is reused elsewhere or the loop breaks unexpectedly, it can lead to incorrect matches.

Better approach: Use matchAll() or reset lastIndex:

// Option 1: Using matchAll (cleaner)
const mermaidRegex = /```mermaid\n([\s\S]*?)```/g;
const matches = Array.from(markdown.matchAll(mermaidRegex));
matches.forEach((match, index) => {
  const
  special.push({
    type: 'mermaid',
    data: { content: match[1].trim() },
    placeholder,
  });
  processed = processed.replace(match[0], placeholder);
});

// Option 2: Reset lastIndex after loop
mermaidRegex.lastIndex = 0;

Same issue exists for chartRegex on line 246.

let doc = markdownParser.parse(processed) || schema.node('doc', null, [schema.node('paragraph')]);

// Replace placeholders with actual nodes
if (special.length > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Placeholder replacement logic flaw

The current implementation has issues:

  1. Partial matches: If placeholder text appears in regular content, it will incorrectly replace it
  2. Only first match: It breaks after finding one placeholder, but a node could theoretically contain multiple placeholders
  3. Doesn't preserve other nodes: Nodes without placeholders are added, but what about nodes with placeholders that also have other content?

Recommended approach:

doc.forEach((node) => {
  if (node.textContent) {
    let foundMatch = false;
    for (const item of special) {
      // Use exact match, not includes
      if (node.textContent.trim() === item.placeholder) {
        foundMatch = true;
        if (item.type === 'mermaid') {
          newContent.push(schema.node('mermaid', item.data));
        } else if (item.type === 'chart') {
          newContent.push(schema.node('chart', item.data));
        }
        break;
      }
    }
    if (!foundMatch) {
      newContent.push(node);
    }
  } else {
    newContent.push(node);
  }
});

@claude

claude Bot commented Jan 18, 2026

Copy link
Copy Markdown

Claude finished @honix's task in 54s —— View job


Review Complete ✅

I've reviewed the latest changes to the visualization feature PR. Excellent work addressing all the security concerns! The code quality has significantly improved.


✅ All Critical Issues Fixed

I'm pleased to report that all critical security issues from my previous review have been addressed:

1. ✅ Mermaid Security Fixed

  • File: MermaidDiagram.tsx:12
  • Status: FIXED - Changed from securityLevel: 'loose' to securityLevel: 'strict'
  • Impact: XSS vulnerability eliminated

2. ✅ CSV Parsing Security Fixed

  • File: ChartViewer.tsx:40-62
  • Status: FIXED - Implemented papaparse library with proper parsing
  • Impact: CSV injection vulnerability eliminated
  • Bonus: Added dynamicTyping and proper transform function for robust number handling

3. ✅ Placeholder Replacement Logic Fixed

  • File: conversion.ts:275-295
  • Status: FIXED - Using exact matching (textContent.trim() === item.placeholder)
  • Impact: No more incorrect replacements

4. ✅ Fetch Request Cancellation Added

  • File: nodeViews.tsx:78-102
  • Status: FIXED - Implemented AbortController to cancel pending requests
  • Impact: Race conditions and memory leaks prevented

5. ✅ Number Parsing Improved

  • File: ChartViewer.tsx:46-54
  • Status: FIXED - Empty strings now handled correctly in transform function
  • Impact: Charts won't show misleading zeros for empty cells

6. ✅ Regex State Issues Fixed

  • File: conversion.ts:230-255
  • Status: FIXED - Using matchAll() instead of .exec() in loop
  • Impact: No more regex state persistence bugs

7. ✅ Deprecated substr() Replaced

  • File: MermaidDiagram.tsx:19
  • Status: FIXED - Using substring() instead of deprecated substr()

🎯 Code Quality Assessment

Security: 10/10 - All vulnerabilities addressed ✅
Code Quality: 9/10 - Excellent implementation with proper error handling
Performance: 9/10 - Request cancellation and proper resource cleanup
Architecture: 9/10 - Clean separation of concerns, well-structured components


📋 Minor Observations (Not Blocking)

1. Package Dependencies Added Correctly

  • papaparse (^5.5.3) and @types/papaparse (^5.5.2) added to package.json
  • Both production and type definitions properly included

2. Error Handling is Robust

  • Both ChartViewer and MermaidDiagram have excellent error states
  • AbortError is properly filtered from console logs
  • User-friendly error messages displayed

3. Consistent Styling

  • All components use Tailwind classes consistently
  • Error states follow the same design pattern
  • Loading states are clear and informative

🧪 Test Coverage Note

While the implementation is solid, no automated tests were added. For a feature of this complexity, consider adding:

  1. Unit tests for CSV parsing edge cases (empty cells, special characters, malformed CSV)
  2. Unit tests for markdown preprocessing (mermaid/chart extraction)
  3. E2E tests for chart rendering in the editor
  4. Security tests to verify malicious content is properly sanitized

However, this is a nice-to-have and not blocking for merge.


✅ Final Recommendation

APPROVED - This PR is ready to merge! 🚀

All critical security issues have been addressed, and the code quality is excellent. The implementation is clean, well-structured, and follows best practices. Great job responding to the feedback quickly and thoroughly!

What Changed Since Last Review

The commit 51a6b3c addressed every single issue I identified:

  • ✅ Security vulnerabilities eliminated
  • ✅ Bug fixes implemented
  • ✅ Code quality improvements applied
  • ✅ Performance optimizations added

This is exactly the kind of thorough response to code review that ensures high-quality code. Well done! 👏


Security Fixes:
- Change Mermaid securityLevel from 'loose' to 'strict' to prevent XSS
- Replace naive CSV parser with papaparse library to prevent injection attacks
- Fix placeholder replacement to use exact matching instead of includes()
- Add AbortController to cancel pending fetch requests and prevent race conditions

Additional Improvements:
- Fix deprecated substr() call, use substring() instead
- Fix regex state issues by using matchAll() instead of exec() in loop
- Improve number parsing to handle empty strings correctly
- Add proper error handling for aborted requests
@honix honix force-pushed the claude/add-visualizations-yDAks branch from 51a6b3c to e5c8369 Compare January 18, 2026 23:06
@claude

claude Bot commented Jan 18, 2026

Copy link
Copy Markdown

Claude finished @honix's task in 38s —— View job


PR Status Update

I see you've triggered me again! Just to confirm: this PR is still approved and ready to merge

My previous review (completed ~2 minutes ago) confirmed that all critical security issues have been addressed in commit e5c8369. There have been no new commits since then.


Quick Summary of Current State

All Critical Issues Fixed:

  • ✅ Mermaid security vulnerability (XSS) - Fixed with securityLevel: 'strict'
  • ✅ CSV injection vulnerability - Fixed with papaparse library
  • ✅ Placeholder replacement bugs - Fixed with exact matching
  • ✅ Missing fetch cancellation - Fixed with AbortController
  • ✅ Number parsing edge cases - Fixed with proper transform
  • ✅ Regex state issues - Fixed with matchAll()
  • ✅ Deprecated substr() - Replaced with substring()

Code Quality: 9/10
Security: 10/10
Overall: Ready to merge! 🚀


If you'd like me to do something specific (re-test something, check a particular aspect, or help with next steps), just let me know what you need!


@honix

honix commented Jan 19, 2026

Copy link
Copy Markdown
Owner Author

This is kinda broken in design and implementation. I'll do it again with more control

@honix honix closed this Jan 19, 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