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();