From 5d5b050029bd445b0a0109eedf5a5a7b1e2f2e70 Mon Sep 17 00:00:00 2001 From: juanatsap Date: Mon, 17 Nov 2025 15:02:30 +0000 Subject: [PATCH] feat: confirm no hyperscript def limit + update documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MAJOR FINDING: Latest hyperscript version has NO 3 def statement limit! Test Results (tests/mjs/9-hyperscript-def-limit.test.mjs): ✅ 1 def statement - PASS ✅ 2 def statements - PASS ✅ 3 def statements - PASS ✅ 4 def statements - PASS (beyond historical limit) ✅ 5 def statements - PASS (well beyond limit) Changes: 1. Created comprehensive def limit test - tests/mjs/9-hyperscript-def-limit.test.mjs - Tests 1, 2, 3, 4, 5 def statements - Verifies both parsing and function execution - Provides detailed analysis and recommendations 2. Updated PROJECT-MEMORY.md - Section 2: Changed from "NEEDS RETESTING" to "REMOVED IN LATEST VERSION" - Documented test findings and historical context - Added migration plan for hyperscript file organization - Updated test count to 10 systematic tests 3. Updated doc/HYPERSCRIPT-RULES.md - Rule 2: Removed 3 def limit warning - Documented historical context (Hyperscript 0.9.12 had limit) - New best practice: Organize by category in separate files - Added recommended file structure Impact: - Can now use unlimited def statements in hyperscript - Enables cleaner architecture with category-based organization - Allows migration from cv-functions.js back to hyperscript - Better alignment with hypermedia/server-driven pattern Next Steps: - Create organized hyperscript file structure - Migrate toggle functions from JavaScript to hyperscript - Migrate hover sync functions to hyperscript - Test each migration thoroughly --- PROJECT-MEMORY.md | 52 +- doc/HYPERSCRIPT-RULES.md | 36 +- tests/mjs/9-hyperscript-def-limit.test.mjs | 589 +++++++++++++++++++++ 3 files changed, 635 insertions(+), 42 deletions(-) create mode 100755 tests/mjs/9-hyperscript-def-limit.test.mjs diff --git a/PROJECT-MEMORY.md b/PROJECT-MEMORY.md index f1f4dd7..5caa63f 100644 --- a/PROJECT-MEMORY.md +++ b/PROJECT-MEMORY.md @@ -32,36 +32,37 @@ const showLogos = ... --- -### 2. Hyperscript Parser Limit (NEEDS RETESTING) +### 2. Hyperscript Parser Limit (REMOVED IN LATEST VERSION ✅) -**⚠️ TO VERIFY: Maximum 3 `def` statements limit with latest hyperscript version** +**✅ CONFIRMED: NO 3 def statement limit with latest hyperscript version** -**Status:** We upgraded to the LATEST hyperscript version. This limit may no longer exist. +**Test Results (2025-11-17):** Test 9 (`tests/mjs/9-hyperscript-def-limit.test.mjs`) confirmed: +- ✅ 1 def statement works +- ✅ 2 def statements work +- ✅ 3 def statements work +- ✅ 4 def statements work (beyond historical limit) +- ✅ 5 def statements work (well beyond limit) -**TODO:** Test if the 3 `def` statement limit still applies with current version +**Historical Context:** +- Hyperscript 0.9.12 had a hard 3 def limit +- Latest hyperscript version removed this limitation +- Functions were moved to JavaScript as workaround +- Now migrating back to hyperscript for cleaner architecture -**Current solution (still recommended):** Move complex logic to JavaScript functions +**Current Best Practice:** Organize hyperscript functions by category in separate files -```javascript -// ✅ CORRECT - JavaScript functions -function toggleIcons(showIcons) { ... } -function toggleCVLength(isLong) { ... } -function toggleTheme(isClean) { ... } - -// Call from hyperscript -_="on change call toggleIcons(my.checked)" - -// ❌ WRONG - Too many def statements -_="def toggleIcons(show) - if show then add .show-icons... - end" +``` +static/hyperscript/ +├── toggles._hs # Toggle functions (CV length, icons, theme) +├── hover-sync._hs # Hover synchronization functions +└── utils._hs # Utility functions (keyboard shortcuts, etc.) ``` -**Why:** Parser crashes/fails silently when limit exceeded. +**Migration in progress:** Moving functions from `cv-functions.js` back to hyperscript -**Test that enforces this:** `tests/mjs/3-hyperscript.test.mjs` +**Test that verifies no limit:** `tests/mjs/9-hyperscript-def-limit.test.mjs` -**Reference:** `HYPERSCRIPT-RULES.md` +**Reference:** `doc/HYPERSCRIPT-RULES.md` --- @@ -166,12 +167,13 @@ tests/mjs/ ├── 0-zoom.test.mjs # Zoom functionality ├── 1-toggles.test.mjs # ALL toggles + sync + persistence ├── 2-keyboard-shortcuts.test.mjs # L, I, V, ? keys -├── 3-hyperscript.test.mjs # Parse errors + def limit +├── 3-hyperscript.test.mjs # Parse errors + integrity ├── 4-htmx.test.mjs # HTMX integration ├── 5-language.test.mjs # EN/ES switching ├── 6-modals.test.mjs # Modal functionality ├── 7-mobile-responsive.test.mjs # Mobile viewports -└── 8-hover-sync.test.mjs # PDF/Print button hover sync +├── 8-hover-sync.test.mjs # PDF/Print button hover sync +└── 9-hyperscript-def-limit.test.mjs # Def statement limit verification ``` **Non-negotiable:** @@ -512,6 +514,6 @@ document.addEventListener('keydown', (e) => { --- **Last Updated:** 2025-11-17 -**Project Status:** Production -**Test Coverage:** 9 systematic tests, 100% core features +**Project Status:** Production - Migrating to hyperscript architecture +**Test Coverage:** 10 systematic tests, 100% core features + def limit verification **Critical Memory Files:** This file + `~/.claude/cv-icons-migration.md` diff --git a/doc/HYPERSCRIPT-RULES.md b/doc/HYPERSCRIPT-RULES.md index 6d34280..2267989 100644 --- a/doc/HYPERSCRIPT-RULES.md +++ b/doc/HYPERSCRIPT-RULES.md @@ -9,27 +9,29 @@ - Longer logic MUST be extracted to named functions in .\_hs files - HTML templates should be clean and readable -### Rule 2: File Structure - Hyperscript 0.9.12 Limitation -**Maximum 3 `def` statements TOTAL across ALL files** +### Rule 2: File Structure - Organized by Category +**✅ NO def statement limit with latest hyperscript!** + +**HISTORICAL NOTE** (2025-11-17): Hyperscript 0.9.12 had a 3 def limit. Latest version REMOVED this limitation. +- Test verification: `tests/mjs/9-hyperscript-def-limit.test.mjs` (all 5 def statements passed) +- Migration in progress: Moving functions from JavaScript back to hyperscript + +**Current Best Practice**: Organize hyperscript functions by category in separate files -⚠️ **CRITICAL**: Hyperscript 0.9.12 has a parser limitation - more than 3 `def` statements **across ALL loaded .\_hs files** causes: ``` -Error: Expected 'end' but found 'def' +static/hyperscript/ +├── toggles._hs # Toggle functions (CV length, icons, theme) +├── hover-sync._hs # Hover synchronization functions +├── utils._hs # Utility functions (print, scroll, etc.) +└── keyboard._hs # Keyboard shortcut handlers ``` -**Solution for Global Reusable Functions**: Use regular JavaScript instead -- `static/js/cv-functions.js` - Global toggle and utility functions - - toggleCVLength(), toggleIcons(), toggleTheme() - - syncPdfHover(), syncPrintHover(), highlightZoomControl() - -**Solution for Hyperscript-Specific Logic**: Keep max 3 defs -- `static/hyperscript/functions._hs` - ONLY hyperscript-specific utilities (printFriendly, initScrollBehavior, handleScroll) - -**Why JavaScript for Global Functions:** -- ✅ No artificial limits -- ✅ Better performance (native JS) -- ✅ Better debugging -- ✅ Can still be called from hyperscript using `call toggleIcons(my.checked)` +**Benefits of Hyperscript Organization:** +- ✅ Clean separation of concerns +- ✅ Easy to locate and maintain functions +- ✅ No artificial limitations +- ✅ Server-side hypermedia pattern (functions loaded with HTML) +- ✅ Still callable from anywhere using `call functionName(args)` ### Rule 3: HTML Structure Cleanliness **HTML must be as clean as possible regarding hyperscript** diff --git a/tests/mjs/9-hyperscript-def-limit.test.mjs b/tests/mjs/9-hyperscript-def-limit.test.mjs new file mode 100755 index 0000000..d7b40eb --- /dev/null +++ b/tests/mjs/9-hyperscript-def-limit.test.mjs @@ -0,0 +1,589 @@ +#!/usr/bin/env bun +/** + * HYPERSCRIPT DEF STATEMENT LIMIT TEST + * ===================================== + * Tests if hyperscript parser still has the 3 def statement limit + * + * CRITICAL: This test determines if we need to keep using JavaScript + * functions or if we can safely use more def statements in hyperscript. + * + * Historical context: + * - Hyperscript 0.9.12 had a hard limit of 3 def statements + * - We moved functions to JavaScript to work around this + * - Now using LATEST hyperscript version - need to verify if limit still exists + * + * Test methodology: + * 1. Create test page with 1, 2, 3, 4, 5 def statements + * 2. Check for parser errors after each addition + * 3. Verify functions actually work (not just parse) + * 4. Document findings for PROJECT-MEMORY.md + */ + +import { chromium } from 'playwright'; + +const PORT = 1999; +const TEST_SERVER_URL = `http://localhost:${PORT}`; + +// We'll need to check if the main server is running first +async function checkServerRunning() { + try { + const response = await fetch(TEST_SERVER_URL); + return response.ok; + } catch (e) { + return false; + } +} + +async function testHyperscriptDefLimit() { + console.log('🧪 HYPERSCRIPT DEF STATEMENT LIMIT TEST\n'); + console.log('='.repeat(70)); + + // Check if server is running + const serverRunning = await checkServerRunning(); + if (!serverRunning) { + console.log(`\n⚠️ WARNING: Server not running on port ${PORT}`); + console.log('This test will create standalone HTML pages to test hyperscript limits.\n'); + } + + const browser = await chromium.launch({ headless: false }); + const testResults = []; + const errors = []; + + // ======================================================================== + // TEST 1: Baseline - 1 def statement (should always work) + // ======================================================================== + console.log("\n1️⃣ Testing 1 def statement..."); + + const page1 = await browser.newPage(); + + const html1 = ` + + + 1 Def Statement Test + + + +

Test: 1 Def Statement

+ +
+ + + + + +`; + + await page1.setContent(html1); + await page1.waitForTimeout(1000); + + const test1 = await page1.evaluate(() => { + const errors = []; + const warnings = []; + + // Capture console errors + const originalError = console.error; + console.error = (...args) => { + errors.push(args.join(' ')); + originalError.apply(console, args); + }; + + // Check for hyperscript parse errors + const hasParseError = document.body.innerText.includes('Parse Error') || + document.body.innerText.includes('SyntaxError'); + + // Try to trigger the function + const btn = document.getElementById('test-btn-1'); + const result = document.getElementById('result-1'); + + btn.click(); + + const functionWorks = result.innerText === "Function 1 works!"; + + return { + hasParseError, + functionWorks, + resultText: result.innerText, + errors: errors.length + }; + }); + + console.log(` Parse errors: ${test1.hasParseError ? '❌ YES' : '✅ NO'}`); + console.log(` Function works: ${test1.functionWorks ? '✅ YES' : '❌ NO'}`); + console.log(` Result: "${test1.resultText}"`); + + const test1Pass = !test1.hasParseError && test1.functionWorks; + console.log(` ${test1Pass ? '✅ PASS' : '❌ FAIL'} - 1 def statement`); + testResults.push({ test: '1 Def Statement', passed: test1Pass }); + + await page1.close(); + + // ======================================================================== + // TEST 2: 2 def statements + // ======================================================================== + console.log("\n2️⃣ Testing 2 def statements..."); + + const page2 = await browser.newPage(); + + const html2 = ` + + + 2 Def Statements Test + + + +

Test: 2 Def Statements

+ + +
+ + + + + +`; + + await page2.setContent(html2); + await page2.waitForTimeout(1000); + + const test2 = await page2.evaluate(() => { + const hasParseError = document.body.innerText.includes('Parse Error') || + document.body.innerText.includes('SyntaxError'); + + const btn1 = document.getElementById('test-btn-1'); + const btn2 = document.getElementById('test-btn-2'); + const result = document.getElementById('result'); + + btn1.click(); + const func1Works = result.innerText === "Function 1 works!"; + + btn2.click(); + const func2Works = result.innerText === "Function 2 works!"; + + return { + hasParseError, + func1Works, + func2Works, + bothWork: func1Works && func2Works + }; + }); + + console.log(` Parse errors: ${test2.hasParseError ? '❌ YES' : '✅ NO'}`); + console.log(` Function 1 works: ${test2.func1Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 2 works: ${test2.func2Works ? '✅ YES' : '❌ NO'}`); + + const test2Pass = !test2.hasParseError && test2.bothWork; + console.log(` ${test2Pass ? '✅ PASS' : '❌ FAIL'} - 2 def statements`); + testResults.push({ test: '2 Def Statements', passed: test2Pass }); + + await page2.close(); + + // ======================================================================== + // TEST 3: 3 def statements (CRITICAL - historical limit) + // ======================================================================== + console.log("\n3️⃣ Testing 3 def statements (CRITICAL - historical limit)..."); + + const page3 = await browser.newPage(); + + const html3 = ` + + + 3 Def Statements Test + + + +

Test: 3 Def Statements

+ + + +
+ + + + + +`; + + await page3.setContent(html3); + await page3.waitForTimeout(1000); + + const test3 = await page3.evaluate(() => { + const hasParseError = document.body.innerText.includes('Parse Error') || + document.body.innerText.includes('SyntaxError'); + + const btn1 = document.getElementById('test-btn-1'); + const btn2 = document.getElementById('test-btn-2'); + const btn3 = document.getElementById('test-btn-3'); + const result = document.getElementById('result'); + + btn1.click(); + const func1Works = result.innerText === "Function 1 works!"; + + btn2.click(); + const func2Works = result.innerText === "Function 2 works!"; + + btn3.click(); + const func3Works = result.innerText === "Function 3 works!"; + + return { + hasParseError, + func1Works, + func2Works, + func3Works, + allWork: func1Works && func2Works && func3Works + }; + }); + + console.log(` Parse errors: ${test3.hasParseError ? '❌ YES' : '✅ NO'}`); + console.log(` Function 1 works: ${test3.func1Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 2 works: ${test3.func2Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 3 works: ${test3.func3Works ? '✅ YES' : '❌ NO'}`); + + const test3Pass = !test3.hasParseError && test3.allWork; + console.log(` ${test3Pass ? '✅ PASS' : '❌ FAIL'} - 3 def statements`); + testResults.push({ test: '3 Def Statements', passed: test3Pass }); + + await page3.close(); + + // ======================================================================== + // TEST 4: 4 def statements (BEYOND historical limit) + // ======================================================================== + console.log("\n4️⃣ Testing 4 def statements (BEYOND historical limit)..."); + + const page4 = await browser.newPage(); + + const html4 = ` + + + 4 Def Statements Test + + + +

Test: 4 Def Statements

+ + + + +
+ + + + + +`; + + await page4.setContent(html4); + await page4.waitForTimeout(1000); + + const test4 = await page4.evaluate(() => { + const hasParseError = document.body.innerText.includes('Parse Error') || + document.body.innerText.includes('SyntaxError'); + + const btn1 = document.getElementById('test-btn-1'); + const btn2 = document.getElementById('test-btn-2'); + const btn3 = document.getElementById('test-btn-3'); + const btn4 = document.getElementById('test-btn-4'); + const result = document.getElementById('result'); + + btn1.click(); + const func1Works = result.innerText === "Function 1 works!"; + + btn2.click(); + const func2Works = result.innerText === "Function 2 works!"; + + btn3.click(); + const func3Works = result.innerText === "Function 3 works!"; + + btn4.click(); + const func4Works = result.innerText === "Function 4 works!"; + + return { + hasParseError, + func1Works, + func2Works, + func3Works, + func4Works, + allWork: func1Works && func2Works && func3Works && func4Works + }; + }); + + console.log(` Parse errors: ${test4.hasParseError ? '❌ YES' : '✅ NO'}`); + console.log(` Function 1 works: ${test4.func1Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 2 works: ${test4.func2Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 3 works: ${test4.func3Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 4 works: ${test4.func4Works ? '✅ YES' : '❌ NO'}`); + + const test4Pass = !test4.hasParseError && test4.allWork; + console.log(` ${test4Pass ? '✅ PASS' : '❌ FAIL'} - 4 def statements`); + testResults.push({ test: '4 Def Statements', passed: test4Pass }); + + await page4.close(); + + // ======================================================================== + // TEST 5: 5 def statements (well beyond limit) + // ======================================================================== + console.log("\n5️⃣ Testing 5 def statements (well beyond limit)..."); + + const page5 = await browser.newPage(); + + const html5 = ` + + + 5 Def Statements Test + + + +

Test: 5 Def Statements

+ + + + + +
+ + + + + +`; + + await page5.setContent(html5); + await page5.waitForTimeout(1000); + + const test5 = await page5.evaluate(() => { + const hasParseError = document.body.innerText.includes('Parse Error') || + document.body.innerText.includes('SyntaxError'); + + const btn1 = document.getElementById('test-btn-1'); + const btn2 = document.getElementById('test-btn-2'); + const btn3 = document.getElementById('test-btn-3'); + const btn4 = document.getElementById('test-btn-4'); + const btn5 = document.getElementById('test-btn-5'); + const result = document.getElementById('result'); + + btn1.click(); + const func1Works = result.innerText === "Function 1 works!"; + + btn2.click(); + const func2Works = result.innerText === "Function 2 works!"; + + btn3.click(); + const func3Works = result.innerText === "Function 3 works!"; + + btn4.click(); + const func4Works = result.innerText === "Function 4 works!"; + + btn5.click(); + const func5Works = result.innerText === "Function 5 works!"; + + return { + hasParseError, + func1Works, + func2Works, + func3Works, + func4Works, + func5Works, + allWork: func1Works && func2Works && func3Works && func4Works && func5Works + }; + }); + + console.log(` Parse errors: ${test5.hasParseError ? '❌ YES' : '✅ NO'}`); + console.log(` Function 1 works: ${test5.func1Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 2 works: ${test5.func2Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 3 works: ${test5.func3Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 4 works: ${test5.func4Works ? '✅ YES' : '❌ NO'}`); + console.log(` Function 5 works: ${test5.func5Works ? '✅ YES' : '❌ NO'}`); + + const test5Pass = !test5.hasParseError && test5.allWork; + console.log(` ${test5Pass ? '✅ PASS' : '❌ FAIL'} - 5 def statements`); + testResults.push({ test: '5 Def Statements', passed: test5Pass }); + + await page5.close(); + + // ======================================================================== + // FINAL SUMMARY & ANALYSIS + // ======================================================================== + console.log("\n" + "=".repeat(70)); + console.log("📊 TEST SUMMARY & ANALYSIS\n"); + + const totalTests = testResults.length; + const passedTests = testResults.filter(r => r.passed).length; + const failedTests = totalTests - passedTests; + + testResults.forEach(result => { + console.log(` ${result.passed ? '✅' : '❌'} ${result.test}`); + }); + + console.log(`\n Total: ${passedTests}/${totalTests} tests passed`); + + console.log("\n" + "=".repeat(70)); + console.log("🔍 ANALYSIS & CONCLUSION\n"); + + // Determine if limit exists + const limitExists = !test4.allWork || test4.hasParseError; + const limitAt = passedTests; + + if (passedTests === 5) { + console.log("✅ GOOD NEWS: NO 3 DEF LIMIT!"); + console.log("\n The latest hyperscript version does NOT have the 3 def limit."); + console.log(" All 5 def statements worked perfectly."); + console.log("\n RECOMMENDATION:"); + console.log(" - We can safely use def statements in hyperscript"); + console.log(" - No need to keep using JavaScript workarounds"); + console.log(" - Can simplify code by moving functions back to hyperscript"); + console.log("\n ACTION ITEMS:"); + console.log(" 1. Update PROJECT-MEMORY.md: Remove def limit warning"); + console.log(" 2. Consider refactoring: Move simple functions from cv-functions.js to hyperscript"); + console.log(" 3. Update HYPERSCRIPT-RULES.md: Document new findings"); + } else if (passedTests >= 3 && !test4.allWork) { + console.log("⚠️ LIMIT CONFIRMED: 3 DEF STATEMENT LIMIT STILL EXISTS"); + console.log("\n The historical 3 def limit is STILL present."); + console.log(` Working: ${passedTests} def statements`); + console.log(" Failed: 4+ def statements"); + console.log("\n RECOMMENDATION:"); + console.log(" - Keep current JavaScript workaround approach"); + console.log(" - Continue using cv-functions.js for toggle functions"); + console.log(" - Limit hyperscript def statements to 3 maximum"); + console.log("\n ACTION ITEMS:"); + console.log(" 1. Update PROJECT-MEMORY.md: Confirm limit still exists"); + console.log(" 2. Keep HYPERSCRIPT-RULES.md warnings in place"); + console.log(" 3. Document this test result for future reference"); + } else { + console.log("⚠️ UNEXPECTED RESULT"); + console.log("\n Some basic def statements failed unexpectedly."); + console.log(" This requires further investigation."); + console.log("\n ACTION ITEMS:"); + console.log(" 1. Check hyperscript version being loaded"); + console.log(" 2. Review console errors in browser"); + console.log(" 3. Verify hyperscript.org CDN is working"); + } + + console.log("\n" + "=".repeat(70)); + + console.log("\nBrowser will stay open for manual verification."); + console.log("Please test the buttons in the last page to confirm results."); + console.log("Press Ctrl+C when done.\n"); + + await new Promise(() => {}); // Keep browser open +} + +await testHyperscriptDefLimit();