PyRST Architecture

This document describes the system architecture, component breakdown, and technical design decisions for PyRST.

Overview

PyRST is a client-side web application that runs Python code in the browser using WebAssembly (via Pyodide). The application processes reStructuredText markup and converts it to HTML without any server-side processing.

High-Level Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Browser                              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                   User Interface                       │  │
│  │  ┌─────────────┐      ┌──────────────┐               │  │
│  │  │   Editor    │      │   Preview    │               │  │
│  │  │   Panel     │◄────►│    Panel     │               │  │
│  │  └─────────────┘      └──────────────┘               │  │
│  │  ┌──────────────────────────────────────┐            │  │
│  │  │         Toolbar & Controls            │            │  │
│  │  └──────────────────────────────────────┘            │  │
│  │  ┌──────────────────────────────────────┐            │  │
│  │  │         Error Panel                   │            │  │
│  │  └──────────────────────────────────────┘            │  │
│  └───────────────────────────────────────────────────────┘  │
│                           │                                  │
│  ┌────────────────────────▼──────────────────────────────┐  │
│  │              JavaScript Layer                         │  │
│  │  ┌──────────┐  ┌──────────┐  ┌────────────────────┐  │  │
│  │  │  app.js  │  │app_export│  │error-highlighting.js│ │  │
│  │  └──────────┘  └──────────┘  └────────────────────┘  │  │
│  └────────────────────────────────────────────────────────┘  │
│                           │                                  │
│  ┌────────────────────────▼──────────────────────────────┐  │
│  │            Pyodide (Python in WASM)                   │  │
│  │  ┌──────────────────────────────────────────────────┐ │  │
│  │  │               main.py                             │ │  │
│  │  │         (RST Processing Logic)                    │ │  │
│  │  └──────────────────────────────────────────────────┘ │  │
│  │  ┌──────────────────────────────────────────────────┐ │  │
│  │  │          docutils Package                         │ │  │
│  │  │      (RST Parser & HTML Generator)                │ │  │
│  │  └──────────────────────────────────────────────────┘ │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │              Browser Storage (localStorage)            │  │
│  │                (Panel sizes, preferences)              │  │
│  └────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

System Architecture

Frontend Layer (HTML/CSS/JS)

The frontend is built with vanilla JavaScript and Tailwind CSS, providing:

  • HTML Structure (index.html): Semantic layout with editor, preview, toolbar, and error panels

  • CSS Styling: Three CSS modules for different concerns:

    • editor.css: Editor-specific styles, toolbar, error panel

    • preview.css: RST HTML output styling (headings, tables, code blocks, admonitions)

    • resizable.css: Drag-to-resize divider styling

  • JavaScript Modules: Three main JS files:

    • app.js: Core initialization, toolbar, resizing, RST conversion

    • app_export.js: Export functionality (copy, download as RST/HTML/PDF)

    • error-highlighting.js: Error parsing, panel management, line highlighting

Python Layer (Pyodide/docutils)

The Python layer runs in WebAssembly via Pyodide:

  • main.py: Contains convert_rst() function that uses docutils to parse RST and generate HTML

  • docutils: Installed via micropip at runtime, provides the RST parser

  • Pyodide Bridge: JavaScript-Python interop using pyodide.runPythonAsync() and pyodide.globals

Storage Layer

  • LocalStorage: Persists panel sizes across sessions

  • No Server Storage: All document data stays in the browser

Component Breakdown

1. Editor Panel

Purpose: Text input area for writing RST

Components:

  • Textarea element with monospace font

  • Highlight overlay for error/warning line highlighting

  • Auto-sync with preview via input events

Files:

  • index.html (lines 77-158)

  • app.js (toolbar functions)

  • error-highlighting.js (line highlighting)

2. Preview Panel

Purpose: Rendered HTML output of RST

Components:

  • Preview div that displays docutils-generated HTML

  • Export buttons (copy/download RST, HTML, PDF)

  • Styled with comprehensive CSS for all RST elements

Files:

  • index.html (lines 188-227)

  • preview.css (all RST element styling)

  • app_export.js (export functionality)

3. Toolbar

Purpose: Quick access to common RST formatting

Components:

  • Header buttons (H1, H2, H3)

  • Text formatting (bold, italic, code)

  • List buttons (bullet, numbered)

  • Link/image insertion

  • Block elements (code block, quote, table)

Files:

  • index.html (lines 88-148)

  • app.js (lines 117-346)

Key Functions:

  • addHeading(level): Adds RST heading with underline

  • wrapText(prefix, suffix): Wraps selection with markers

  • insertText(text): Inserts at cursor position

  • insertBulletList(), insertNumberedList(): Smart list insertion

  • insertLink(), insertImage(): Link/image insertion

  • insertCodeBlock(), insertBlockquote(), insertTable(): Block insertion

4. Error Panel

Purpose: Display RST syntax errors and warnings

Components:

  • Error list with clickable items

  • Error/warning counts

  • Collapsible panel

  • Line jump functionality

Files:

  • index.html (lines 160-181)

  • error-highlighting.js

Key Functions:

  • parseSystemMessages(html): Extracts errors from docutils system messages

  • updateErrorPanel(errors): Updates UI with error list

  • highlightEditorLines(errors): Highlights error lines in editor

  • jumpToLine(lineNumber): Scrolls editor to specific line

5. Resizable Panels

Purpose: Adjustable split between editor and preview

Components:

  • Draggable divider

  • Panel flex sizing

  • LocalStorage persistence

Files:

  • app.js (lines 12-111)

  • resizable.css

Key Functions:

  • initResizablePanels(): Sets up drag handlers

  • loadPanelSizes(): Restores saved sizes

  • savePanelSizes(): Persists to localStorage

6. Pyodide Integration

Purpose: Run Python/docutils in browser

Components:

  • Pyodide loader

  • micropip for package installation

  • Python-JavaScript bridge

Files:

  • app.js (lines 351-467)

  • main.py

Key Functions:

  • initPyodide(): Loads Pyodide, installs docutils, initializes editor

  • convertRST(rstText): Calls Python to convert RST to HTML

  • convert_rst(rst_text): Python function using docutils

Data Flow

Initialization Flow

1. Page Load
   ↓
2. DOMContentLoaded Event
   ↓
3. initPyodide()
   ├─ Load Pyodide from CDN
   ├─ Install micropip
   ├─ Install docutils
   ├─ Load main.py
   ├─ Load sample.rst
   ├─ Initialize editor with sample
   ├─ Set up event listeners
   └─ Initialize toolbar, export, resizing
   ↓
4. Ready to Use

Editing Flow

1. User types in editor
   ↓
2. 'input' event fired
   ↓
3. Debounced convertRST() called (300ms delay)
   ↓
4. JavaScript → Python bridge
   ├─ Set rst_input in Python globals
   ├─ Run publish_string() via docutils
   └─ Return HTML string
   ↓
5. Parse HTML for system messages
   ↓
6. Update preview panel with HTML
   ↓
7. Update error panel with errors
   ↓
8. Highlight error lines in editor

Export Flow

1. User clicks export button
   ↓
2. Export function called
   ├─ Copy RST → clipboard.writeText(editor.value)
   ├─ Copy HTML → clipboard.writeText(preview.innerHTML)
   ├─ Download RST → Blob → download link
   ├─ Download HTML → Full HTML with CSS → download
   └─ PDF → window.print()
   ↓
3. Show toast notification

File Structure

pyrst/
├── src/pyrst/
│   ├── index.html              # Main HTML structure
│   ├── app.js                  # Core app logic, toolbar, resizing
│   ├── app_export.js           # Export functions, toast notifications
│   ├── error-highlighting.js   # Error parsing and highlighting
│   ├── main.py                 # Python RST conversion logic
│   ├── sample.rst              # Sample RST content
│   ├── __init__.py             # Empty Python package marker
│   └── css/
│       ├── editor.css          # Editor, toolbar, error panel styles
│       ├── preview.css         # RST HTML output styles
│       └── resizable.css       # Resizable divider styles
├── docs/                       # Documentation
├── tests/                      # Test files
├── pyproject.toml              # Python project metadata
├── Makefile                    # Build commands
├── README.md                   # Project README
└── uv.lock                     # Dependency lock file

Technology Choices

Why Pyodide?

Chosen: Pyodide enables running Python directly in the browser via WebAssembly.

Alternatives Considered:

  • Pure JavaScript RST parser (none mature enough)

  • Server-side processing (defeats “no server” goal)

Benefits:

  • Use mature docutils library without modification

  • No server infrastructure needed

  • Instant local processing

  • Privacy: no data leaves browser

Tradeoffs:

  • Initial load time (10-30 seconds for Pyodide + docutils)

  • Larger bundle size (~10MB)

  • Requires WASM-capable browser

Why Vanilla JavaScript?

Chosen: No framework, pure JavaScript

Alternatives Considered:

  • React, Vue, Svelte

Benefits:

  • Zero build step (just serve static files)

  • Smaller bundle size

  • Faster page load

  • Easier debugging

  • No framework lock-in

Tradeoffs:

  • Manual DOM manipulation

  • More verbose code for complex UIs

Why Tailwind CSS via CDN?

Chosen: Tailwind CSS loaded from CDN

Alternatives Considered:

  • Custom CSS only

  • Bootstrap

  • Compiled Tailwind

Benefits:

  • Rapid UI development

  • Consistent design system

  • No build step required

  • Responsive utilities built-in

Tradeoffs:

  • Larger CSS payload (CDN version includes all classes)

  • Utility-first approach (verbose HTML)

Why docutils?

Chosen: Official RST implementation

Alternatives Considered:

  • Custom parser

  • Markdown (different syntax)

Benefits:

  • Standard RST parser used by Sphinx, Python docs

  • Comprehensive directive/role support

  • Well-tested and maintained

  • System messages for error reporting

Tradeoffs:

  • Python-only (requires Pyodide)

  • Larger than minimal parsers

Why localStorage?

Chosen: Browser localStorage for persistence

Alternatives Considered:

  • IndexedDB

  • No persistence

  • Server-side storage

Benefits:

  • Simple API

  • Synchronous access

  • Sufficient for small data (panel sizes)

  • No setup required

Tradeoffs:

  • Limited storage (5-10MB)

  • Cleared on cache clear

  • Same-origin only

Performance Considerations

Initial Load Optimization

  • Pyodide loaded from CDN (cached by browser)

  • Loading screen with progress updates

  • Async initialization with await/async

Runtime Optimization

  • Debounced RST conversion (300ms delay)

  • Efficient DOM updates (innerHTML for preview)

  • CSS containment for panels

Memory Management

  • Single Pyodide instance (not recreated)

  • Minimal Python globals

  • DOM cleanup on error panel updates

Security Considerations

Client-Side Only

  • No server means no server-side vulnerabilities

  • User data never transmitted

  • XSS protection via proper HTML escaping (docutils handles this)

Content Security

  • No eval() usage

  • Safe clipboard API usage

  • Blob URLs properly revoked after download

Future Architecture Improvements

Potential Enhancements

  1. Web Workers: Move Pyodide to web worker for better UI responsiveness

  2. Virtual Scrolling: For very long documents

  3. Code Splitting: Lazy-load Pyodide only when needed

  4. Progressive Web App: Offline support, installable

  5. Syntax Highlighting: CodeMirror or Monaco for editor

  6. Dark Mode: Theme switching

  7. Plugin System: Custom directives/roles

Scalability

Current architecture handles:

  • Documents up to ~10,000 lines (tested)

  • Multiple open sessions (separate browser tabs)

  • Concurrent editing (localStorage sync)

Limitations:

  • Very large documents (>50,000 lines) may cause lag

  • Mobile browsers with limited memory

  • Older browsers without WASM support


Last Updated: 2025-11-15