diff --git a/CLAUDE.md b/CLAUDE.md index 5af0fb4..ecaa263 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,6 +7,45 @@ ## Code Quality Rules - NEVER fail silently - always log errors and make failures visible to users when appropriate. +- If a composable is used within only one Vue component, keep it in that component file - do NOT create a separate composable file. + +## WebR Documentation References + +Always reference the official WebR documentation when working with WebR features. Be eager to check and link to relevant sections: + +**Official WebR Documentation**: https://docs.r-wasm.org/webr/latest/ + +### Table of Contents with Links: +- [Getting Started](https://docs.r-wasm.org/webr/latest/getting-started.html) +- [Downloading WebR](https://docs.r-wasm.org/webr/latest/downloading.html) +- [Serving Pages with WebR](https://docs.r-wasm.org/webr/latest/serving.html) +- [Examples using WebR](https://docs.r-wasm.org/webr/latest/examples.html) +- [Worker Communication](https://docs.r-wasm.org/webr/latest/communication.html) +- [Evaluating R Code](https://docs.r-wasm.org/webr/latest/evaluating.html) ⭐ *Key for our composables* +- [Plotting](https://docs.r-wasm.org/webr/latest/plotting.html) ⭐ *Used in our ggplot2 demos* +- [Networking](https://docs.r-wasm.org/webr/latest/networking.html) +- [Working with R Objects](https://docs.r-wasm.org/webr/latest/objects.html) ⭐ *Critical for data handling* +- [Managing R Objects](https://docs.r-wasm.org/webr/latest/objects.html#managing-r-objects) +- [Converting to JavaScript](https://docs.r-wasm.org/webr/latest/convert-js.html) ⭐ *Used extensively* +- [Creating New R Objects](https://docs.r-wasm.org/webr/latest/objects.html#creating-new-r-objects) +- [Installing R Packages](https://docs.r-wasm.org/webr/latest/packages.html) ⭐ *For our library management* +- [Building R Packages](https://docs.r-wasm.org/webr/latest/building.html) +- [Mounting Filesystem Data](https://docs.r-wasm.org/webr/latest/mounting.html) ⭐ *For CSV uploads* +- [WebR API](https://docs.r-wasm.org/webr/latest/api.html) +- [R API](https://docs.r-wasm.org/webr/latest/api/r.html) +- [JavaScript API](https://docs.r-wasm.org/webr/latest/api/js.html) ⭐ *Main reference* + +### Key Principles: +- Always link to relevant WebR docs when implementing features +- Use WebR best practices as documented +- Reference specific API methods and their documentation +- Show developers this is a proper WebR showcase, not just a demo + +## Testing Philosophy + +- DON'T test implementation details like "finding messages with specific text" - these are non-tests +- DON'T test trivial functionality that just confirms code does what it was written to do +- DO test actual business logic and user-facing behavior that could realistically break ## Type Safety Requirements diff --git a/package.json b/package.json index d1f4dc9..460e51f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webr-ggplot2-demo", "private": true, - "version": "0.1.8", + "version": "0.2.1", "type": "module", "scripts": { "dev": "vite", diff --git a/simplification_ideas.md b/simplification_ideas.md deleted file mode 100644 index 7eb59d3..0000000 --- a/simplification_ideas.md +++ /dev/null @@ -1,234 +0,0 @@ -# Simplification Ideas for webr-ggplot2 - -This document outlines opportunities to simplify the codebase while maintaining all functionality, making it easier to use as a starting point for others. - -## Overview - -The goal is to reduce complexity by ~40% while keeping the same user experience. This will make the project more approachable for developers who want to: -- Understand how WebR works -- Build their own R-in-browser applications -- Deploy quickly without configuration overhead - -## 1. Dependency Reduction - -### Remove Unnecessary Packages -- `@monaco-editor/loader` - Monaco is imported directly, loader not needed -- `@rushstack/eslint-patch` - Only needed for complex monorepo setups -- `@tsconfig/node18` - Can use inline TypeScript configuration -- `@vue/tsconfig` - Provides minimal value over direct configuration -- All ESLint packages - While useful, adds complexity for a demo project -- All Prettier packages - Can be optional for those who want it -- `pnpm-workspace.yaml` - Single package project doesn't need workspace - -### Keep Only Essential Dependencies -- `vue` - Core framework -- `vite` - Build tool -- `typescript` - Type safety -- `webr` - R runtime in browser -- `monaco-editor` - Code editor -- `@vitejs/plugin-vue` - Vue support for Vite - -### Package Manager -- Keep using pnpm as specified in project requirements -- Remove hash from packageManager field for cleaner config - -## 2. Component Consolidation - -### Current Structure (6 components) -``` -components/ -├── CodeEditor.vue -├── ExampleSelector.vue -├── FileUpload.vue -├── LibrarySelector.vue -├── OutputDisplay.vue -└── WebRStatus.vue -``` - -### Simplified Structure (4 components) -``` -components/ -├── CodeEditor.vue # R code editor with examples dropdown -├── OutputPanel.vue # Merged console + plots display -├── StatusBar.vue # Merged WebR status + library info -└── FileUpload.vue # CSV upload (unchanged) -``` - -### Specific Merges -- **WebRStatus + LibrarySelector → StatusBar**: Single component for all status info -- **Console logic from App.vue → OutputPanel**: Centralize output handling -- **ExampleSelector → CodeEditor**: Examples are part of the editor experience - -## 3. Configuration Simplification - -### TypeScript Configuration -Replace 3 config files with single `tsconfig.json`: -```json -{ - "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "lib": ["ES2020", "DOM"], - "jsx": "preserve", - "moduleResolution": "node", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules"] -} -``` - -### Build Process -- Remove `vue-tsc` type checking from build (keep for dev if desired) -- Simplify scripts in package.json -- Minimal vite.config.ts focused only on CORS headers for WebR - -### Remove Configuration Files -- `.eslintrc.cjs` -- `.prettierrc.json` -- `tsconfig.app.json` -- `tsconfig.node.json` -- `pnpm-workspace.yaml` - -## 4. Code Pattern Improvements - -### Message Handling -- Simplify message types to just: "output", "error", "plot" -- Remove complex message filtering logic -- Centralize all console handling in OutputPanel - -### Type Safety -Replace `any` types with proper interfaces: -- WebR instance typing -- Message payload types -- File upload event types - -### Remove Workarounds -- Console.error override hack for WebR warnings -- Complex Monaco worker configuration -- Redundant state management between components - -### Library Management -- Remove toggle functionality for libraries -- Auto-install required packages (ggplot2, dplyr, ggrepel) on init -- Simpler status display - -## 5. CSS and Styling - -### Current Issues -- Duplicate styles across components -- Inconsistent spacing and colors -- Complex nested selectors - -### Improvements -- Create CSS variables for consistent theming -- Use utility classes for common patterns -- Consolidate similar styles -- Remove unused CSS - -## 6. Project Structure - -### Simplified Directory Layout -``` -webr-ggplot2/ -├── src/ -│ ├── components/ # 4 consolidated components -│ ├── composables/ # Single useWebR.ts -│ ├── data/ # examples.ts unchanged -│ ├── types/ # Proper TypeScript interfaces -│ ├── App.vue # Cleaner layout component -│ ├── main.ts # Minimal setup -│ └── style.css # Consolidated styles -├── public/ # Static assets -├── index.html # Entry point -├── package.json # Simplified dependencies -├── tsconfig.json # Single TS config -├── vite.config.ts # Minimal Vite config -└── README.md # Clear setup instructions -``` - -## 7. Setup and Deployment - -### Current Setup -```bash -# Install dependencies -pnpm install -# Run dev server -pnpm dev -# Build -pnpm build -``` - -### Simplified Setup -```bash -# Install and run -pnpm install -pnpm dev - -# Build -pnpm build -``` - -### README Improvements -- Quick start section at the top -- Clear explanation of what WebR is -- Simple deployment instructions -- Links to WebR and ggplot2 documentation - -## 8. Code Examples - -### Simplified useWebR Composable -- Remove redundant state -- Cleaner initialization -- Better error handling -- Typed WebR instance - -### Cleaner App.vue -- Remove console logic (moved to OutputPanel) -- Simpler layout structure -- Better responsive design -- Cleaner event handling - -## 9. Benefits of Simplification - -### For Users -- Faster setup (npm install && npm run dev) -- Easier to understand codebase -- Better starting point for customization -- Fewer things that can go wrong - -### For Maintainers -- Less dependencies to update -- Clearer code structure -- Easier to add features -- Better performance - -### For Deployment -- Smaller bundle size -- Faster builds -- Works out of the box -- No configuration needed - -## 10. Implementation Priority - -1. **High Priority** - - Remove unnecessary dependencies - - Consolidate TypeScript configuration - - Merge WebRStatus and LibrarySelector - -2. **Medium Priority** - - Consolidate console handling - - Simplify message types - - Fix TypeScript types - -3. **Low Priority** - - CSS improvements - - README updates - - Example refinements - -## Conclusion - -These simplifications will make webr-ggplot2 a cleaner, more approachable example of running R in the browser. The reduced complexity makes it an ideal starting point for developers wanting to build their own WebR-powered applications. \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 6321bf6..2668591 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,30 +6,19 @@ import ExampleSelector from './components/ExampleSelector.vue' import OutputDisplay from './components/OutputDisplay.vue' import WebRStatus from './components/WebRStatus.vue' import LibrarySelector from './components/LibrarySelector.vue' +import ConsoleOutput from './components/ConsoleOutput.vue' +import ConsoleToggle from './components/ConsoleToggle.vue' +import AppHeader from './components/AppHeader.vue' +import RunButton from './components/RunButton.vue' import { useWebR } from './composables/useWebR' import { examples } from './data/examples' -import { icons } from './data/icons' import type { RExample, CsvData } from './types' -// Constants -const GITHUB_REPO_OWNER = 'QuesmaOrg' -const GITHUB_REPO_NAME = 'demo-webr-ggplot' -const GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}` - -// Types -interface GitHubRepo { - stargazers_count: number - [key: string]: unknown -} - // Start with the first example (getting-started) const code = ref(examples[0].code) const lastExecutedCode = ref('') const hasChanges = computed(() => code.value !== lastExecutedCode.value) -// GitHub stars -const githubStars = ref(null) - // Current CSV data state const currentCsvData = ref(null) @@ -50,47 +39,8 @@ const { toggleLibrary, } = useWebR() -// Filter text messages for console display -const textMessages = computed(() => { - return messages.filter(message => message.type !== 'plot') -}) - -// Console state -const showConsole = ref(false) -const hasErrors = computed(() => { - return textMessages.value.some(msg => msg.type === 'error' || msg.type === 'stderr') -}) - -const hasWarnings = computed(() => { - return textMessages.value.some(msg => msg.type === 'warning') -}) - -// Group consecutive messages of the same type for cleaner display -const groupedMessages = computed(() => { - const groups: Array<{type: string, content: string, showLabel: boolean}> = [] - - for (const message of textMessages.value) { - const lastGroup = groups[groups.length - 1] - - // Clean up excessive line breaks from the message content - const cleanContent = message.content.replace(/\n\s*\n\s*\n/g, '\n').trim() - - if (lastGroup && lastGroup.type === message.type) { - // Same type as previous - append content with single line break - lastGroup.content += '\n' + cleanContent - } else { - // New type - create new group - const showLabel = message.type !== 'stdout' // Don't show label for stdout - groups.push({ - type: message.type, - content: cleanContent, - showLabel - }) - } - } - - return groups -}) +// Console component reference +const consoleRef = ref>() const runCode = async (): Promise => { if (code.value.trim()) { @@ -99,10 +49,7 @@ const runCode = async (): Promise => { lastExecutedCode.value = code.value // Auto-open console if no plots were generated - const hasPlots = messages.some(msg => msg.type === 'plot') - if (!hasPlots && messages.length > 0) { - showConsole.value = true - } + consoleRef.value?.autoOpenConsole() } } @@ -113,8 +60,6 @@ const handleFileUpload = async (csvData: CsvData): Promise => { const handleFileRemoved = (): void => { currentCsvData.value = null - // Clear any data-related variables in R - void executeCode('if (exists("data")) rm(data)') } const handleExampleSelect = async (example: RExample): Promise => { @@ -162,10 +107,7 @@ const handleExampleSelect = async (example: RExample): Promise => { lastExecutedCode.value = example.code // Auto-open console if no plots were generated - const hasPlots = messages.some(msg => msg.type === 'plot') - if (!hasPlots && messages.length > 0) { - showConsole.value = true - } + consoleRef.value?.autoOpenConsole() } } } catch (error) { @@ -184,32 +126,14 @@ const handleExampleSelect = async (example: RExample): Promise => { lastExecutedCode.value = example.code // Auto-open console if no plots were generated - const hasPlots = messages.some(msg => msg.type === 'plot') - if (!hasPlots && messages.length > 0) { - showConsole.value = true - } + consoleRef.value?.autoOpenConsole() } } } -// Fetch GitHub stars -const fetchGitHubStars = async (): Promise => { - try { - const response = await fetch(GITHUB_API_URL) - if (response.ok) { - const data = await response.json() as GitHubRepo - githubStars.value = data.stargazers_count - } - } catch (error) { - console.error('Failed to fetch GitHub stars:', error) - } -} - onMounted(async () => { // Initialize WebR first await initializeWebR('') - // Fetch GitHub stars - void fetchGitHubStars() // Execute the first example once WebR is ready if (isReady.value && examples.length > 0) { @@ -220,55 +144,7 @@ onMounted(async () => {