From 3e23c606a5a4e695cf32371834d530ef1616f7ee Mon Sep 17 00:00:00 2001 From: Piotr Migdal Date: Sat, 2 Aug 2025 22:23:58 +0200 Subject: [PATCH 1/7] Add testing philosophy to CLAUDE.md - define non-tests to avoid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 5af0fb4..8328197 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,12 @@ - NEVER fail silently - always log errors and make failures visible to users when appropriate. +## 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 - AVOID using `any` types to mask type checking - this defeats the purpose of TypeScript From 86b7bd68edf7f0717efb7f56a4549ccdfd98590b Mon Sep 17 00:00:00 2001 From: Piotr Migdal Date: Sat, 2 Aug 2025 22:38:46 +0200 Subject: [PATCH 2/7] Remove simplification_ideas.md (not implemented) (v0.1.9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- package.json | 2 +- simplification_ideas.md | 234 ---------------------------------------- 2 files changed, 1 insertion(+), 235 deletions(-) delete mode 100644 simplification_ideas.md diff --git a/package.json b/package.json index d1f4dc9..7e02fc3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webr-ggplot2-demo", "private": true, - "version": "0.1.8", + "version": "0.1.9", "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 From 3758c22a779c41944e6b7523f55777aaad56aa71 Mon Sep 17 00:00:00 2001 From: Piotr Migdal Date: Sat, 2 Aug 2025 22:55:29 +0200 Subject: [PATCH 3/7] Extract ConsoleOutput and ConsoleToggle components to reduce App.vue complexity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create ConsoleOutput.vue for overlay display functionality - Create ConsoleToggle.vue for toggle button with clean interface - Remove confusing toggleOnly/overlayOnly props abstraction - Clear separation: toggle shows state, output manages overlay - Extract all console-related logic and styling from App.vue - Reduce App.vue by ~100 lines with better separation of concerns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 1 + package.json | 2 +- src/App.vue | 270 ++----------------------------- src/components/ConsoleOutput.vue | 232 ++++++++++++++++++++++++++ src/components/ConsoleToggle.vue | 89 ++++++++++ 5 files changed, 339 insertions(+), 255 deletions(-) create mode 100644 src/components/ConsoleOutput.vue create mode 100644 src/components/ConsoleToggle.vue diff --git a/CLAUDE.md b/CLAUDE.md index 8328197..c359756 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,6 +7,7 @@ ## 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. ## Testing Philosophy diff --git a/package.json b/package.json index 7e02fc3..f50b072 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webr-ggplot2-demo", "private": true, - "version": "0.1.9", + "version": "0.1.10", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.vue b/src/App.vue index 6321bf6..88a4202 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,6 +6,8 @@ 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 { useWebR } from './composables/useWebR' import { examples } from './data/examples' import { icons } from './data/icons' @@ -50,47 +52,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 +62,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() } } @@ -162,10 +122,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,10 +141,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() } } } @@ -306,44 +260,10 @@ onMounted(async () => { :messages="messages" :is-loading="isLoading" /> - -
-
-
- Console Output - -
-
-
- {{ group.type.toUpperCase() }}: -
{{ group.content }}
- {{ group.content }} -
-
-
-
+ @@ -366,17 +286,11 @@ onMounted(async () => {
- +
@@ -558,119 +472,6 @@ onMounted(async () => { position: relative; } -.console-overlay { - position: absolute; - bottom: 0; - left: 0; - right: 0; - background: white; - border: 1px solid #e5e7eb; - border-radius: 6px 6px 0 0; - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); - z-index: 10; - max-height: 60%; -} - -.console-content { - display: flex; - flex-direction: column; - height: 100%; -} - -.console-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem 1rem; - border-bottom: 1px solid #e5e7eb; - background: #f9fafb; -} - -.console-title { - font-weight: 600; - font-size: 0.875rem; - color: #374151; -} - -.console-close { - background: none; - border: none; - font-size: 1.25rem; - cursor: pointer; - color: #6b7280; - padding: 0; - width: 1.5rem; - height: 1.5rem; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; -} - -.console-close:hover { - background: #e5e7eb; - color: #374151; -} - -.console-messages { - flex: 1; - overflow-y: auto; - padding: 0.75rem; - min-height: 120px; - max-height: 200px; -} - -.console-message { - display: flex; - gap: 0.5rem; - margin-bottom: 0.5rem; - font-size: 0.75rem; - line-height: 1.4; -} - -.console-message.no-label { - gap: 0; -} - -.console-message:last-child { - margin-bottom: 0; -} - -.message-label { - font-weight: 600; - text-transform: uppercase; - color: #6b7280; - min-width: 3rem; - flex-shrink: 0; -} - -.console-message.success .message-label { - color: #059669; -} - -.console-message.error .message-label { - color: #dc2626; -} - -.console-message.stderr .message-label { - color: #d97706; -} - -.console-message.info .message-label { - color: #2563eb; -} - -.console-message.warning .message-label { - color: #d97706; -} - -.message-text { - flex: 1; - margin: 0; - font-family: 'Courier New', monospace; - color: #374151; - white-space: pre-wrap; -} /* Bottom bar styling */ .bottom-bar { @@ -708,45 +509,6 @@ onMounted(async () => { align-items: center; } -.console-toggle { - background: #f3f4f6; - border: 1px solid #d1d5db; - border-radius: 6px; - padding: 0.5rem 0.75rem; - font-size: 0.875rem; - cursor: pointer; - transition: all 0.3s ease; - display: flex; - align-items: center; - gap: 0.5rem; - color: #6b7280; -} - -.console-toggle:hover { - background: #e5e7eb; - border-color: #9ca3af; -} - -.console-toggle.has-errors { - background: #fef2f2; - border-color: #fecaca; - color: #dc2626; -} - -.console-toggle.has-warnings { - background: #fefbf2; - border-color: #fed7aa; - color: #d97706; -} - -.toggle-arrow { - font-size: 0.75rem; - transition: transform 0.3s ease; -} - -.toggle-arrow.open { - transform: rotate(180deg); -} @media (max-width: 768px) { diff --git a/src/components/ConsoleOutput.vue b/src/components/ConsoleOutput.vue new file mode 100644 index 0000000..440f204 --- /dev/null +++ b/src/components/ConsoleOutput.vue @@ -0,0 +1,232 @@ + + + + + \ No newline at end of file diff --git a/src/components/ConsoleToggle.vue b/src/components/ConsoleToggle.vue new file mode 100644 index 0000000..f971f86 --- /dev/null +++ b/src/components/ConsoleToggle.vue @@ -0,0 +1,89 @@ + + + + + \ No newline at end of file From 9b51375ffe2ea24281ba6edf90c24137ffccfc6d Mon Sep 17 00:00:00 2001 From: Piotr Migdal Date: Sat, 2 Aug 2025 23:15:18 +0200 Subject: [PATCH 4/7] Extract AppHeader and RunButton components for better architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create AppHeader.vue with title, subtitle, and GitHub link functionality - Create RunButton.vue with dynamic state and version display - Remove 445 lines from App.vue (57% reduction: 776→331 lines) - Clean 2-level component hierarchy with focused responsibilities - Each component handles its own state and styling Components now extracted: - AppHeader: GitHub API, stars, title display - RunButton: execution state, version info, dynamic styling - ConsoleOutput: message overlay display - ConsoleToggle: console toggle button with error states 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- package.json | 2 +- src/App.vue | 226 ++--------------------------------- src/components/AppHeader.vue | 175 +++++++++++++++++++++++++++ src/components/RunButton.vue | 90 ++++++++++++++ 4 files changed, 276 insertions(+), 217 deletions(-) create mode 100644 src/components/AppHeader.vue create mode 100644 src/components/RunButton.vue diff --git a/package.json b/package.json index f50b072..bfe9fb4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webr-ggplot2-demo", "private": true, - "version": "0.1.10", + "version": "0.1.11", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.vue b/src/App.vue index 88a4202..20672db 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,30 +8,17 @@ 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) @@ -146,24 +133,9 @@ const handleExampleSelect = async (example: RExample): Promise => { } } -// 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) { @@ -174,55 +146,7 @@ onMounted(async () => {