Skip to content

feat(playground): Multi-language Coding Playground with AST instrumentation & live visualizations#31

Merged
mnaimfaizy merged 10 commits intomainfrom
feature/playground
Apr 8, 2026
Merged

feat(playground): Multi-language Coding Playground with AST instrumentation & live visualizations#31
mnaimfaizy merged 10 commits intomainfrom
feature/playground

Conversation

@mnaimfaizy
Copy link
Copy Markdown
Owner

🎮 Coding Playground — New Feature

A fully in-browser, multi-language coding playground with AST-based instrumentation, timeline debugging, and pluggable data-structure visualization lenses. No server required — JavaScript, TypeScript, and Python all execute client-side.

✨ Highlights

  • Multi-language execution — JS/TS via sandboxed iframe, Python via Pyodide (WASM)
  • AST instrumentation — Acorn/Astring pipeline injects __snapshot() calls around every statement; Python uses sys.settrace wrapper
  • Timeline Player — Step forward/backward through execution snapshots, inspect variables and call stack at each point
  • 4 Visualization Lenses — Pluggable React Flow canvases: Data Structure (Array, LinkedList, Stack, Queue, HashTable), Event Loop, Heap & Stack, Stream
  • Space-themed dark UI — Standalone layout with animated starfield canvas, scoped dark theme (.playground-dark)
  • Security hardened — CSP-locked iframe, blocked network APIs, HTML entity escaping, prototype-pollution defense, rate-limited output

📸 Architecture

PlaygroundLayout (dark-scoped, standalone — no Header/Footer/Sidebar)
└── PlaygroundApp (orchestrator)
    ├── PlaygroundToolbar (language picker, run/stop, settings, examples)
    ├── MonacoEditor (code input)
    ├── SandboxFrame (JS/TS execution — hidden iframe)
    ├── usePyodide (Python execution — Pyodide WASM)
    ├── ConsoleOutput (rate-limited, sanitized output)
    ├── TimelinePlayer (snapshot stepper with variable inspector)
    └── VisualizationCanvas (React Flow lens selector + rendering)

🔧 Implementation Phases

Phase Description Commit
0 Foundation — types, routing, layout, dark theme, Vite config d8f118d
1 Core Editor & Execution — Monaco, SandboxFrame, Pyodide, console 58496ed
2 Code Instrumentation — JsInstrumenter, PythonInstrumenter, StateSnapshot, Timeline 2a5f41a
3 Visualization Engine — 4 React Flow lenses, AnimatedNode, LensSelector d710d6a
4 UI/UX Polish — PaneResizer, SettingsPanel, examples, keyboard shortcuts, starfield 14fe0b5
5 Security & Performance — sanitize.ts, CSP hardening, React.memo, Monaco optimization 32e4df6
6 Testing & Documentation — 59 tests, Developer Guide, README/copilot-instructions updates 9ce9d60

🧪 Test Coverage

59 new tests (211 total passing) across three tiers:

Unit Tests (55)

  • JsInstrumenter (11) — instrumentation, async code, error handling, source maps
  • PythonInstrumenter (14) — trace wrapper, frame filtering, snapshot parsing
  • sanitize (~25) — escapeHtml, sanitizeOutput, circular refs, prototype pollution, entry limits
  • codeTemplates (5) — template completeness and validity

Component Tests (55)

  • MonacoEditor (9) — rendering, language switching, onChange, loading
  • SandboxFrame (6) — iframe CSP, ref methods, cleanup
  • ConsoleOutput (11) — entry types, clear, ARIA live region, XSS escaping
  • TimelinePlayer (16) — step nav, play/pause, speed, variables, a11y
  • SpaceBackground (7) — canvas animation, reduced motion, cleanup
  • PlaygroundApp (6) — smoke tests with mocked subsystems

Integration Tests (19)

  • JS/Python instrumentation pipelines end-to-end
  • Sanitization + console output pipeline
  • StateSnapshot utilities (safeDeepClone, diffSnapshots)
  • Example snippets coverage

🔒 Security

  • Sandboxed iframe: sandbox="allow-scripts", CSP default-src 'none'; script-src 'unsafe-inline'
  • Blocked APIs: fetch, XMLHttpRequest, WebSocket, EventSource, importScripts, eval, Function
  • Output sanitization: HTML entity escaping, prototype-pollution defense (__proto__, constructor, prototype)
  • Rate limiting: Console entries capped at 500, strings truncated at 50 KB
  • Recursive depth limits: safeDeepClone caps at 10 levels, sanitizeOutput at 5

📁 New Files

64 files changed+10,444 / -180

Key additions under src/features/playground/:

  • PlaygroundApp.tsx — Root orchestrator
  • components/editor/ — MonacoEditor, EditorTabs, LanguagePicker
  • components/execution/ — SandboxFrame, ConsoleOutput, ExecutionController, PythonRunner
  • components/instrumentation/ — TimelinePlayer
  • components/layout/ — PlaygroundLayout, PlaygroundToolbar, PaneResizer, SettingsPanel
  • components/theme/ — SpaceBackground, tokens, playground-theme.css
  • components/visualizations/ — VisualizationCanvas, LensSelector, 4 lenses, AnimatedNode
  • hooks/ — useMonaco, usePaneLayout, usePlaygroundState, usePyodide, useSandbox, useTimeline
  • instrumentation/ — JsInstrumenter, PythonInstrumenter, StateSnapshot
  • services/ — instrumentCode, pyodide-loader
  • utils/ — codeTemplates, exampleSnippets, sanitize

Documentation:

  • docs/Playground-Developer-Guide.md — How to add lenses, languages, snippets, theme changes
  • docs/Playground-Implementation-Plan.md — Full 7-phase plan (all complete)

📖 Developer Guide

See docs/Playground-Developer-Guide.md for:

  • How to add a new visualization lens
  • How to add a new language
  • How to modify the dark theme
  • How to add example snippets
  • Security considerations for contributors
  • Performance budget and monitoring

Dependencies Added

Package Version Purpose
acorn 8.14.1 JS AST parsing for instrumentation
astring 1.9.0 AST-to-source code generation
@xyflow/react 12.8.2 React Flow for visualization lenses

All 211 tests passing. Build succeeds. No lint errors.

- Add playground feature directory structure under src/features/playground/
- Create core TypeScript types (PlaygroundLanguage, VisualizationLens,
  ExecutionState, StateSnapshot, SandboxMessage, ConsoleEntry, etc.)
- Add default code templates for JavaScript, TypeScript, and Python
- Restructure App.tsx routing so /playground renders without Header,
  Sidebar, Footer, or Breadcrumbs (standalone full-screen layout)
- Add Playground link with Rocket icon to Header (desktop + mobile),
  opens in new tab via target=_blank
- Create space-themed dark layout shell with:
  - Design tokens (deep space palette, electric blue accents)
  - Scoped CSS under [data-theme=playground] with scrollbar, focus,
    and selection overrides
  - Animated starfield canvas with parallax layers, respects
    prefers-reduced-motion
  - PlaygroundLayout with translucent glassmorphism panes
  - BackToSiteLink with close-tab-if-opener pattern
- Add PlaygroundApp with three-pane placeholder layout and toolbar
- Add PlaygroundEntry with ErrorBoundary + SEO meta tags
- Update vite.config.ts manualChunks for vendor-monaco, vendor-xyflow
- Build verified: playground chunk at ~5.5 KB, CSS scoped separately
Step 1.1 — Monaco Editor Integration:
- Install @monaco-editor/react@4.7.0 + monaco-editor@0.53.0 (0 audit vulns)
- Create useMonaco hook with custom space dark theme, Ctrl+Enter/Ctrl+S
- Create MonacoEditor wrapper with loading skeleton and aria-label
- Create LanguagePicker (JS/TS/Python) with radio group accessibility
- Create EditorTabs with filename display and reset button

Step 1.2 — JavaScript/TypeScript Sandboxed Execution:
- Create SandboxFrame with sandbox='allow-scripts', nonce-based CSP
- Block network APIs (fetch/XHR/WebSocket/EventSource) inside iframe
- Create useSandbox hook with execute/terminate, 50KB input validation
- Create ConsoleOutput with color-coded entries, auto-scroll, role=log
- Create ExecutionController with Run/Stop/Reset and state indicators
- 10s execution timeout with proper error handling

Step 1.3 — Python Execution via Pyodide:
- Create pyodide-loader service (CDN singleton, retry-safe)
- Create usePyodide hook with lazy load, stdout/stderr capture, timeout
- Create PythonRunner loading/error state component

Wiring:
- Create usePlaygroundState for language/code/execution state management
- Wire all components into PlaygroundApp three-pane layout
- Language switching preserves per-language code edits
- Global keyboard shortcuts (Escape=stop, Ctrl+L=clear console)
…pture

Step 2.1 — JavaScript/TypeScript Instrumentation:
- Create instrumentCode.ts: acorn AST parsing + astring code generation
  Injects __tracker__ calls at statements, function entry/exit, variable captures
- Create JsInstrumenter.ts: higher-level wrapper, snapshot reporting via postMessage
- Create StateSnapshot.ts: deep clone, diff utility, formatValue helpers
- Update SandboxFrame.tsx: handle 'timeline' message type, return snapshots
- Update useSandbox.ts: expose snapshots/clearSnapshots from execution results

Step 2.2 — Python Instrumentation:
- Create PythonInstrumenter.ts: sys.settrace() wrapper, base64-encoded user code
  Captures line numbers, local variables, call stack per frame
- Update usePyodide.ts: add runPythonInstrumented() with trace function wrapping

Step 2.3 — Timeline Player & Playback Controls:
- Create useTimeline.ts: step navigation, auto-play with speed control (0.5-4x)
  Respects prefers-reduced-motion, interval-based auto-advance
- Create TimelinePlayer.tsx: timeline slider, playback buttons (First/Prev/Play/Next/Last)
  Speed dropdown, variables panel with diff highlighting, call stack panel
  Keyboard shortcuts (Left/Right=step, Space=play/pause, Home/End=first/last)
- Wire into PlaygroundApp.tsx: instrumented mode toggle, timeline in center pane
  Line highlighting in Monaco editor via decorations API
- Add CSS for pg-highlight-line and pg-highlight-glyph decorations
- Create instrumentation/index.ts barrel export
Add interactive visualization system with lens-based architecture:

New components:
- LensSelector: Toolbar radio group with auto-suggest based on snapshot data
- VisualizationCanvas: Lazy-loaded container rendering the active lens
- EventLoopLens: SVG-based Call Stack, Microtask Queue, Task Queue visualization
- HeapStackLens: React Flow graph with stack frames and heap object nodes
- DataStructureLens: Auto-detects arrays, linked lists, trees from variables
- StreamLens: SVG Node.js stream pipeline with backpressure indicators
- AnimatedNode: Shared React Flow custom node with animation states

Dependencies:
- Add @xyflow/react@12.10.2 for graph-based visualizations
- Import @xyflow/react CSS in index.css

Bug fixes:
- Fix stale closure in handleRun: execute/run now return results directly
- Fix race condition: sandbox iframe reads __tracker__ directly in result
  message instead of relying on separate timeline postMessage
- Fix __tracker__ scoping: use window.__tracker__ so it's accessible from
  sandbox iframe's setTimeout callback after new Function() executes

Modified:
- PlaygroundApp: Wire lens selector and visualization canvas into layout
- SandboxFrame: Include snapshots in result message, remove timeline handler
- useSandbox/usePyodide: Return {error, snapshots} from execute/run
- JsInstrumenter: Remove appended timeline postMessage code
- instrumentCode: Use window.__tracker__ for global accessibility
Step 4.1 — Three-Pane Resizable Layout:
- PaneResizer: draggable divider with pointer events, keyboard arrow keys,
  double-click reset, ARIA separator role, grip dots on hover
- usePaneLayout hook: manages two divider fractions with localStorage
  persistence, min 15% pane width enforcement
- PlaygroundApp: flex layout with explicit width percentages replacing
  static grid-cols-3

Step 4.2 — Toolbar & Settings:
- PlaygroundToolbar: extracted toolbar with BackToSiteLink, LanguagePicker,
  Trace toggle, LensSelector, Examples dropdown with language filtering,
  Settings gear icon, ExecutionController
- exampleSnippets: 7 examples (Event Loop, BST, Promise Chain, Bubble Sort,
  Closures, Python Collections, Linked List) with language/lens metadata
- SettingsPanel: font size slider (10-22px), word wrap toggle, execution
  timeout slider (1-30s), animations toggle, localStorage persistence
- MonacoEditor: added fontSize and wordWrap prop overrides

Step 4.3 — Space Background Refinement:
- Shooting stars spawning every ~15s with gradient tails
- 30fps cap via FRAME_INTERVAL_MS
- Tab visibility pause (document.hidden)
- DPI fix: ctx.setTransform() instead of cumulative ctx.scale()
- will-change: transform for GPU acceleration

Step 4.4 — Keyboard Shortcuts & Accessibility:
- Ctrl+Enter: run code
- Escape: close settings or stop execution
- Ctrl+L: clear console
- Arrow keys: timeline stepping (when not in editor)
- Space: play/pause timeline (when not in editor)
…tion

Security:
- Add sanitize.ts with HTML escaping, entry limiting, and binary detection
- Harden SandboxFrame: block network APIs (fetch, XMLHttpRequest, WebSocket,
  importScripts, sendBeacon, window.open) with Object.defineProperty
- Override window.parent/top/opener to prevent sandbox escape
- Add rate limiting (1 exec/sec) and input validation to useSandbox/usePyodide
- Inject Python security preamble blocking urllib, requests, subprocess, etc.
- Escape console output HTML in ConsoleOutput to prevent XSS

Performance:
- Disable heavy Monaco features (codeLens, colorDecorators, inlayHints, etc.)
- Wrap all 4 visualization lenses and AnimatedNode in React.memo
- Cap DataStructureLens at 200 nodes with edge filtering
- Adaptive star count in SpaceBackground based on navigator.hardwareConcurrency

Bug fixes:
- Remove duplicate keydown handler from TimelinePlayer that blocked Space in
  Monaco editor (PlaygroundApp already handles Arrow/Space/Home/End shortcuts)
- Add z-index to toolbar wrapper so Examples dropdown renders above panels
Unit tests:
- JsInstrumenter (11): instrumentation, async, error handling, source maps
- PythonInstrumenter (14): trace wrapper, frame filtering, snapshot parsing
- sanitize (~25): escapeHtml, sanitizeOutput, circular refs, prototype pollution
- codeTemplates (5): template completeness and validity

Component tests:
- MonacoEditor (9): rendering, language switching, onChange, loading
- SandboxFrame (6): iframe CSP, ref methods, cleanup
- ConsoleOutput (11): entry types, clear, ARIA live region, XSS escaping
- TimelinePlayer (16): step nav, play/pause, speed, variables, a11y
- SpaceBackground (7): canvas animation, reduced motion, cleanup
- PlaygroundApp (6): smoke test with mocked subsystems

Integration tests (19):
- JS/Python instrumentation pipelines end-to-end
- Sanitization + console output pipeline (XSS, entry limits)
- StateSnapshot utilities (safeDeepClone, diffSnapshots)
- Example snippets coverage (languages, uniqueness, valid lenses)

Documentation:
- Create docs/Playground-Developer-Guide.md (lenses, languages, theme,
  snippets, security, performance, testing)
- Update README.md with Playground architecture and dependencies
- Update copilot-instructions.md with Playground module guidelines
- Mark Phase 6 complete in Playground-Implementation-Plan.md

All 211 tests passing (59 new + 152 existing).
@mnaimfaizy mnaimfaizy self-assigned this Apr 8, 2026
@mnaimfaizy mnaimfaizy added documentation Improvements or additions to documentation enhancement New feature or request feature New Feature labels Apr 8, 2026
@mnaimfaizy mnaimfaizy merged commit c3873f9 into main Apr 8, 2026
9 checks passed
@mnaimfaizy mnaimfaizy deleted the feature/playground branch April 8, 2026 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request feature New Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant