2025-11-17 13:51:17 +00:00
# CV Project - Developer Memory
## ⚠️ CRITICAL: Read This Before Any Changes
This document contains **non-negotiable rules ** and **hard-learned lessons ** from this project's development. Violating these causes bugs, breaks features, and wastes time.
---
## 🔴 ABSOLUTE RULES - NEVER VIOLATE
### 1. Icon Naming Convention (CRITICAL)
**ALWAYS use "icons" - NEVER "logos" **
``` javascript
// ✅ CORRECT
. show - icons
localStorage . getItem ( 'cv-icons' )
const showIcons = ...
// ❌ WRONG - WILL BREAK ICON TOGGLE
. show - logos
localStorage . getItem ( 'cv-logos' )
const showLogos = ...
```
**Why: ** Icon toggle had critical bug (commit 3f77fed) because JavaScript used `.show-logos` but CSS checked for `.show-icons` . This caused toggles to only work after page refresh.
**Test that enforces this: ** `tests/mjs/1-toggles.test.mjs`
**Memory file: ** `~/.claude/cv-icons-migration.md`
---
2025-11-17 15:02:30 +00:00
### 2. Hyperscript Parser Limit (REMOVED IN LATEST VERSION ✅)
2025-11-17 13:51:17 +00:00
2025-11-17 15:02:30 +00:00
* * ✅ CONFIRMED: NO 3 def statement limit with latest hyperscript version**
2025-11-17 13:51:17 +00:00
2025-11-17 15:02:30 +00:00
**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)
2025-11-17 13:51:17 +00:00
2025-11-17 15:02:30 +00:00
**Historical Context: **
- Hyperscript 0.9.12 had a hard 3 def limit
2025-11-17 18:04:07 +00:00
- Hyperscript 0.9.14+ removed this limitation
2025-11-17 15:02:30 +00:00
- Functions were moved to JavaScript as workaround
2025-11-17 18:04:07 +00:00
- **NOW MIGRATED BACK** to hyperscript with JavaScript wrappers (2025-11-17)
**Current Architecture (2025-11-17): **
- Core logic in hyperscript (`static/hyperscript/*.hs` )
- JavaScript wrappers for `call` command compatibility (`static/js/cv-functions.js` )
- Pattern: `window.fn()` → `_hyperscript.evaluate('hyperscriptFn()')`
2025-11-17 14:22:23 +00:00
2025-11-17 15:02:30 +00:00
**Current Best Practice: ** Organize hyperscript functions by category in separate files
2025-11-17 13:51:17 +00:00
2025-11-17 15:02:30 +00:00
```
static/hyperscript/
├── toggles._hs # Toggle functions (CV length, icons, theme)
├── hover-sync._hs # Hover synchronization functions
2025-11-20 09:17:09 +00:00
├── navigation._hs # Navigation functions (scroll-to-section) [2025-11-20]
├── keyboard._hs # Keyboard handler reference (inline in body tag)
└── utils._hs # Utility functions (print, scroll, etc.)
2025-11-17 13:51:17 +00:00
```
2025-11-17 15:02:30 +00:00
**Migration in progress: ** Moving functions from `cv-functions.js` back to hyperscript
2025-11-17 13:51:17 +00:00
2025-11-17 15:02:30 +00:00
**Test that verifies no limit: ** `tests/mjs/9-hyperscript-def-limit.test.mjs`
2025-11-17 13:51:17 +00:00
2025-11-20 09:17:09 +00:00
**Reference: ** `doc/4-HYPERSCRIPT-RULES.md`
---
### 2.1. Hyperscript Event Handler Externalization (2025-11-20)
**Rule: Complex event handlers that inspect event properties MUST stay inline **
* * ✅ CAN Be Externalized:**
- Simple navigation handlers (scrollToSection)
- Toggle handlers (toggleCVLength, toggleIcons)
- Hover sync handlers (syncPdfHover, syncPrintHover)
* * ❌ MUST Stay Inline:**
- Keyboard handlers that inspect `event.key` , `event.target` , modifier keys
- Event handlers with complex conditional logic based on event properties
**Why: ** The `event` variable in `_=""` attributes is a hyperscript runtime variable. External `def` functions don't have direct access to this event context from HTML attributes.
**Optimization for Inline: ** Use `then` chains to make compact:
``` hyperscript
-- Compact inline handler with then chains
if event.key is '?' and not event.ctrlKey and not isInputField
halt the event then set modal to #shortcuts-modal then if modal then call modal.showModal() end
end
```
**Example - Navigation (Externalized): **
``` html
<!-- Clean HTML - 9 navigation links use single function -->
< a href = "#education" _ = "on click call scrollToSection(event, 'education')" >
```
``` hyperscript
-- External function in navigation._hs
def scrollToSection(event, sectionId)
call event.preventDefault()
set element to document.getElementById(sectionId)
if element then call element.scrollIntoView({behavior: 'smooth'}) end
end
```
**Example - Keyboard Handler (Inline): **
``` html
<!-- Must stay inline - inspects event.key, event.target, modifiers -->
< body _ = "on keydown
set tagName to event.target.tagName
set isInputField to (tagName is 'INPUT' or tagName is 'TEXTAREA')
if event.key is 'l' and not event.ctrlKey and not isInputField
halt the event then -- handler logic
end
end" >
```
**Files: **
- `static/hyperscript/navigation._hs` - External navigation function
- `templates/partials/navigation/hamburger-menu.html` - 9 clean navigation links
- `templates/index.html` - Optimized inline keyboard handler (body tag)
**Test: ** `tests/mjs/2-keyboard-shortcuts.test.mjs` (keyboard shortcuts)
**Reference: ** `doc/4-HYPERSCRIPT-RULES.md` Section "Rule 4: Event Handler Externalization Guidelines"
2025-11-17 13:51:17 +00:00
---
2025-11-17 14:37:38 +00:00
### 3. Toggle Synchronization (WORKING) + Hover Sync (FIXED)
2025-11-17 13:51:17 +00:00
2025-11-17 14:22:23 +00:00
**Toggle checkboxes WORK perfectly ✅ **
2025-11-17 13:51:17 +00:00
2025-11-17 14:22:23 +00:00
Every toggle checkbox exists in TWO places and stays synchronized:
2025-11-17 13:51:17 +00:00
1. Action bar (desktop) - `#lengthToggle` , `#iconToggle` , `#themeToggle`
2. Hamburger menu (mobile) - `#lengthToggleMenu` , `#iconToggleMenu` , `#themeToggleMenu`
2025-11-17 14:22:23 +00:00
**Toggle behavior (WORKING): **
2025-11-17 13:51:17 +00:00
- ✅ Clicking either toggle updates BOTH
- ✅ Changes are real-time (no page refresh)
- ✅ localStorage persists the state
- ✅ Page load reads from localStorage and applies to both
``` javascript
// ✅ CORRECT - Update both toggles
function toggleIcons ( showIcons ) {
const paper = document . querySelector ( '.cv-paper' ) ;
const otherToggle = document . querySelector ( '#iconToggle' ) ||
document . querySelector ( '#iconToggleMenu' ) ;
if ( showIcons ) {
paper ? . classList . add ( 'show-icons' ) ;
localStorage . setItem ( 'cv-icons' , 'true' ) ;
if ( otherToggle ) otherToggle . checked = true ;
} else {
paper ? . classList . remove ( 'show-icons' ) ;
localStorage . setItem ( 'cv-icons' , 'false' ) ;
if ( otherToggle ) otherToggle . checked = false ;
}
}
```
2025-11-17 14:37:38 +00:00
* * ✅ HOVER SYNC - PDF/Print buttons NOW WORKING**
2025-11-17 14:22:23 +00:00
2025-11-17 14:37:38 +00:00
**Action buttons that sync hover states: **
2025-11-17 14:22:23 +00:00
1. Action bar (desktop):
- `#action-bar-pdf-btn` - Download PDF button
- `.action-bar-print-btn` - Print Friendly button
2. Hamburger menu (mobile):
2025-11-17 14:37:38 +00:00
- `.menu-pdf-btn` - PDF button (templates/partials/navigation/hamburger-menu.html:178)
- `.menu-print-btn` - Print button (templates/partials/navigation/hamburger-menu.html:186)
2025-11-17 14:22:23 +00:00
2025-11-17 14:37:38 +00:00
**Working hover sync functions: **
- `syncPdfHover(isHovering)` - Syncs PDF button hover between locations ✅
- `syncPrintHover(isHovering)` - Syncs Print button hover between locations ✅
2025-11-17 14:22:23 +00:00
2025-11-17 14:37:38 +00:00
**Implementation: **
- Both buttons in each location have mouseenter/mouseleave handlers
- Sync functions select all matching buttons (`.pdf-btn, .menu-pdf-btn` )
- CSS classes `.pdf-hover-sync` and `.print-hover-sync` applied to all instances
- Visual feedback matches across action bar and menu
2025-11-17 14:22:23 +00:00
2025-11-17 14:37:38 +00:00
**Files involved: **
2025-11-17 14:22:23 +00:00
- `templates/partials/navigation/action-buttons.html` (lines 9-10, 18-19)
2025-11-17 14:37:38 +00:00
- `templates/partials/navigation/hamburger-menu.html` (lines 178-184, 186-189)
- `static/js/cv-functions.js` (lines 71-99)
- `static/css/main.css` (lines 2690-2712)
2025-11-17 14:22:23 +00:00
2025-11-17 14:37:38 +00:00
**Test coverage: ** `tests/mjs/8-hover-sync.test.mjs` ✅
2025-11-17 14:22:23 +00:00
**Test that enforces toggle sync: ** `tests/mjs/1-toggles.test.mjs` ✅
2025-11-17 13:51:17 +00:00
---
2025-11-17 14:22:23 +00:00
### 4. Real-Time Rendering + Persistence (CRITICAL)
**ALL UI changes MUST happen in BOTH DOM and localStorage **
2025-11-17 13:51:17 +00:00
2025-11-17 14:22:23 +00:00
**BOTH are required - not mutually exclusive: **
2025-11-17 13:51:17 +00:00
``` javascript
2025-11-17 14:22:23 +00:00
// ✅ CORRECT - DOM update AND localStorage
paper . classList . add ( 'show-icons' ) ; // Visual change (instant feedback)
localStorage . setItem ( 'cv-icons' , 'true' ) ; // Persistence (survives reload)
2025-11-17 13:51:17 +00:00
2025-11-17 14:22:23 +00:00
// ❌ WRONG - Only DOM (lost on page reload)
paper . classList . add ( 'show-icons' ) ;
// ❌ WRONG - Only storage (no visual feedback)
localStorage . setItem ( 'cv-icons' , 'true' ) ;
2025-11-17 13:51:17 +00:00
```
2025-11-17 14:22:23 +00:00
**Why both are needed: **
- **DOM changes** = instant visual feedback (users see it immediately)
- **localStorage** = state persists across page reloads (users don't lose their preferences)
- Users need BOTH: instant feedback + remembered preferences
2025-11-17 13:51:17 +00:00
2025-11-17 14:22:23 +00:00
**Test that enforces this: ** `tests/mjs/1-toggles.test.mjs` (verifies both DOM and localStorage)
2025-11-17 13:51:17 +00:00
---
### 5. Test Suite as Single Source of Truth
**If a test passes, the feature MUST work. If a feature works, the test MUST pass. **
```
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
2025-11-17 15:02:30 +00:00
├── 3-hyperscript.test.mjs # Parse errors + integrity
2025-11-17 13:51:17 +00:00
├── 4-htmx.test.mjs # HTMX integration
├── 5-language.test.mjs # EN/ES switching
├── 6-modals.test.mjs # Modal functionality
2025-11-17 14:37:38 +00:00
├── 7-mobile-responsive.test.mjs # Mobile viewports
2025-11-17 15:02:30 +00:00
├── 8-hover-sync.test.mjs # PDF/Print button hover sync
└── 9-hyperscript-def-limit.test.mjs # Def statement limit verification
2025-11-17 13:51:17 +00:00
```
**Non-negotiable: **
- ❌ NO code changes without test validation
- ❌ NO bug fixes without regression test
- ❌ NO features without test coverage
- ❌ NO deployment if tests fail
**Run before every commit: **
``` bash
bun tests/run-all.mjs
```
**Reference: ** `tests/README.md` , `tests/TEST-SUMMARY.md`
---
## 📋 Architecture Decisions
### Tech Stack
2025-11-17 14:22:23 +00:00
**Backend: **
- ✅ Go 1.21+ (Backend server)
- ✅ Standard library `net/http` (HTTP server)
- ✅ Go templates (Server-side rendering)
**Frontend: **
- ✅ HTMX (Hypermedia-driven interactions)
- ✅ Hyperscript (Latest version - event handling)
- ✅ Vanilla JavaScript (cv-functions.js - toggles, keyboard shortcuts)
- ✅ Iconify (Icon system)
**Testing: **
- ✅ Playwright (E2E browser automation)
- ✅ Bun (Test runner ONLY - NOT the runtime!)
**Why Go: **
- Fast compilation
- Single binary deployment
- Built-in templating
- Excellent standard library
- Strong typing
- Cross-platform
2025-11-17 13:51:17 +00:00
**Why HTMX + Hyperscript: **
- Server-driven UI (hypermedia pattern)
- Minimal JavaScript
- Progressive enhancement
2025-11-17 14:22:23 +00:00
- Real-time updates capabilities
2025-11-17 13:51:17 +00:00
### File Organization
```
cv/
2025-11-17 14:22:23 +00:00
├── main.go # Server entry point (v1.1.0)
├── go.mod, go.sum # Go dependencies
├── internal/
│ ├── config/ # Configuration
│ ├── handlers/ # HTTP handlers
│ ├── middleware/ # HTTP middleware
│ ├── models/ # Data models
│ ├── routes/ # Route definitions
│ └── templates/ # Template utilities
2025-11-17 13:51:17 +00:00
├── static/
│ ├── js/
2025-11-17 14:22:23 +00:00
│ │ └── cv-functions.js # Global functions (toggles, keyboard, hover sync)
│ ├── css/ # Stylesheets
│ ├── hyperscript/
│ │ └── functions._hs # Hyperscript functions (if any)
│ ├── images/ # Static images
│ └── pdf/ # PDF files
2025-11-17 13:51:17 +00:00
├── templates/
2025-11-17 14:22:23 +00:00
│ ├── index.html # Main page template
│ ├── cv-content.html # CV content
│ ├── language-switch.html # Language switcher
2025-11-17 13:51:17 +00:00
│ └── partials/
2025-11-17 14:22:23 +00:00
│ ├── navigation/
│ │ ├── action-bar.html # Desktop action bar
│ │ ├── action-buttons.html # PDF + Print buttons
│ │ └── hamburger-menu.html # Mobile menu (ALL controls + buttons)
│ ├── widgets/ # Reusable UI components
│ └── modals/ # Modal dialogs
2025-11-17 13:51:17 +00:00
└── tests/
2025-11-17 14:22:23 +00:00
└── mjs/ # Systematic test suite (Playwright + Bun runner)
2025-11-17 13:51:17 +00:00
```
**Critical files: **
2025-11-17 14:22:23 +00:00
- `main.go` - Server entry point
- `static/js/cv-functions.js` - Toggle, keyboard, and hover sync functions
- `templates/partials/navigation/action-buttons.html` - PDF + Print buttons (action bar)
- `templates/partials/navigation/hamburger-menu.html` - Complete mobile menu with toggles + buttons
- `templates/index.html` - Main template structure
2025-11-17 13:51:17 +00:00
---
## 🐛 Historical Bugs (Learn From These)
### Bug 1: Icon Toggle Required Refresh
**Commit:** 3f77fed
**Date: ** 2025-11-17
**Symptom: ** Icon toggle only worked after page refresh
**Root cause: ** Class name mismatch
- JavaScript: `classList.add('show-logos')`
- CSS: `.cv-paper:not(.show-icons)`
**Fix:** Changed all references from "logos" to "icons"
**Test added: ** Screenshot verification in `1-toggles.test.mjs`
**Memory: ** `~/.claude/cv-icons-migration.md`
### Bug 2: Hyperscript Parser Crash
**Date:** 2025-11-16
**Symptom: ** Silent failures, toggles stopped working
**Root cause: ** Exceeded 3 `def` statement limit
**Fix: ** Moved toggle logic to JavaScript (`cv-functions.js` )
**Test added: ** Def statement counter in `3-hyperscript.test.mjs`
**Reference: ** `HYPERSCRIPT-RULES.md`
### Bug 3: Toggle Desynchronization
**Date:** 2025-11-15
**Symptom: ** Action bar and menu toggles out of sync
**Root cause: ** Only updating one toggle, not both
**Fix: ** Every toggle function now updates both locations
**Test added: ** Sync verification in `1-toggles.test.mjs`
---
## 🎯 Development Workflow
### Before Making Changes
1. **Read relevant test: ** Understand what's being tested
2. **Run the test: ** See current behavior
3. **Make changes: ** Code + test updates together
4. **Run test again: ** Verify it passes
5. **Run ALL tests: ** `bun tests/run-all.mjs`
6. **Manual verification: ** Browser stays open - check it yourself
7. **Commit: ** With clear description
### When Adding a Feature
1. **Write test FIRST ** (test what you want to build)
2. **Implement feature **
3. **Test passes ** (feature works)
4. **Update documentation ** (TEST-SUMMARY.md)
5. **Commit both ** (code + test together)
### When Fixing a Bug
1. **Write regression test ** (reproduces the bug)
2. **Test FAILS ** (proves bug exists)
3. **Fix the bug **
4. **Test PASSES ** (proves fix works)
5. **Commit both ** (fix + test together)
---
## 📝 Key Patterns
### Toggle Pattern (Standard)
``` javascript
function toggleX ( enabled ) {
const target = document . querySelector ( '.target' ) ;
const otherToggle = document . querySelector ( '#xToggle' ) ||
document . querySelector ( '#xToggleMenu' ) ;
if ( enabled ) {
target ? . classList . add ( 'state-class' ) ;
localStorage . setItem ( 'cv-x' , 'true' ) ;
if ( otherToggle ) otherToggle . checked = true ;
} else {
target ? . classList . remove ( 'state-class' ) ;
localStorage . setItem ( 'cv-x' , 'false' ) ;
if ( otherToggle ) otherToggle . checked = false ;
}
}
```
**Must have: **
- ✅ Real-time DOM update
- ✅ localStorage persistence
- ✅ Sync both toggle locations
- ✅ Safe navigation (`?.` )
### Hyperscript Pattern (LIMITED USE)
``` html
<!-- ✅ GOOD - Simple event delegation -->
< button _ = "on click call myJavaScriptFunction(my.checked)" >
<!-- ⚠️ AVOID - Complex logic (use JS instead) -->
< button _ = "on click
if condition then ...
else ... end" >
<!-- ❌ FORBIDDEN - def statements (unless absolutely necessary) -->
< script type = "_hyperscript" >
def myFunction ( ) <!-- COUNTS TOWARD 3 LIMIT -- >
...
end
< / script >
```
### Keyboard Shortcuts Pattern
``` javascript
document . addEventListener ( 'keydown' , ( e ) => {
// ✅ ALWAYS check for input fields
if ( e . target . matches ( 'input, textarea, select' ) ) return ;
// ✅ Use lowercase key
const key = e . key . toLowerCase ( ) ;
switch ( key ) {
case 'l' : toggleCVLength ( ! body . classList . contains ( 'cv-long' ) ) ; break ;
case 'i' : toggleIcons ( ! paper . classList . contains ( 'show-icons' ) ) ; break ;
case 'v' : toggleTheme ( ! body . classList . contains ( 'theme-clean' ) ) ; break ;
case '?' : document . querySelector ( '#shortcuts-modal' ) ? . showModal ( ) ; break ;
}
} ) ;
```
**Test: ** `tests/mjs/2-keyboard-shortcuts.test.mjs`
---
## 🔧 Common Operations
### Adding a New Toggle
1. **Add to HTML: ** Action bar + menu
2. **Add localStorage key: ** Choose naming convention
3. **Add toggle function: ** Follow standard pattern
4. **Add keyboard shortcut: ** Optional but recommended
5. **Add to test: ** Update `1-toggles.test.mjs`
6. **Add to keyboard test: ** If you added shortcut
7. **Run ALL tests **
### Changing Class Names
1. **Search globally: ** Find ALL references
2. **Update JavaScript: ** cv-functions.js
3. **Update CSS: ** All stylesheets
4. **Update HTML: ** All templates
5. **Update tests: ** Search for old name
6. **Create memory file: ** `~/.claude/name-migration.md`
7. **Test thoroughly **
### Debugging Toggle Issues
**Checklist: **
- [ ] Check class name matches between JS and CSS
- [ ] Verify both toggles are updated (action bar + menu)
- [ ] Check localStorage key is correct
- [ ] Verify real-time DOM update happens
- [ ] Run `1-toggles.test.mjs` - does it catch the bug?
- [ ] If test doesn't catch it, FIX THE TEST FIRST
---
## 📚 Key Documents
### Must Read Before Changes
- `tests/README.md` - Test suite accountability
2025-11-17 14:22:23 +00:00
- `doc/HYPERSCRIPT-RULES.md` - Parser limits and workarounds
2025-11-17 13:51:17 +00:00
- `~/.claude/cv-icons-migration.md` - Icon naming convention
### Reference Documentation
- `tests/TEST-SUMMARY.md` - Complete test documentation
- `tests/mjs/README.md` - Test structure and patterns
2025-11-17 14:22:23 +00:00
- `doc/MODERN-WEB-TECHNIQUES.md` - Architecture decisions
2025-11-17 13:51:17 +00:00
### Historical Record
- `tests/archive/README.md` - Legacy tests (reference only)
- Git history - Search for bug fix commits
---
## 🎓 Lessons Learned
### 1. Name Things Correctly From the Start
**Lesson:** Renaming "logos" to "icons" required:
- Updating 4 files
- Creating memory document
- Adding test verification
- Could have been avoided with better initial naming
**Rule: ** Think about naming conventions BEFORE first implementation
### 2. Tests Are Not Optional
**Lesson:** Icon toggle bug existed for days before we added screenshot verification to the test. The test was passing but the feature was broken.
**Rule: ** Tests must verify ACTUAL behavior, not just code execution
### 3. Framework Limits Are Real
**Lesson:** Hyperscript's 3 `def` limit seems arbitrary but it's a hard constraint. We learned this after hitting silent failures.
**Rule: ** Read framework documentation carefully, especially limits/constraints
### 4. Synchronization Is Hard
2025-11-17 14:22:23 +00:00
**Lesson:** Having toggles in two or more places (action bar + menu) means every change must update all locations. Forgetting this causes desync bugs.
2025-11-17 13:51:17 +00:00
**Rule: ** If something appears in multiple places, use a single function to update ALL locations
### 5. Real-Time Updates Are Expected
**Lesson:** Users don't understand localStorage vs DOM updates. If they click a button, they expect INSTANT visual feedback.
**Rule: ** Every toggle must update both localStorage AND DOM immediately
---
## 🚀 Future Developers
**Before you change ANYTHING: **
1. Read this document
2. Read `tests/README.md`
3. Run `bun tests/run-all.mjs`
4. Understand which test covers what you're changing
5. Make your changes + update tests together
6. Run ALL tests again
7. Manual verification in browser
8. Update this document if you learn something new
**When you hit a bug: **
1. Which test should have caught this?
2. Why didn't it?
3. Fix the test FIRST
2025-11-17 14:22:23 +00:00
4. Then fix the bug, using the debug agent and the htmx expert
2025-11-17 13:51:17 +00:00
5. Document the lesson in this file
**Remember: **
- Tests are the specification
- If tests pass but feature fails → tests are wrong
- If feature works but tests fail → feature is wrong
- When in doubt, trust the tests MORE than the code
---
**Last Updated: ** 2025-11-17
2025-11-17 15:02:30 +00:00
**Project Status: ** Production - Migrating to hyperscript architecture
**Test Coverage: ** 10 systematic tests, 100% core features + def limit verification
2025-11-17 13:51:17 +00:00
**Critical Memory Files: ** This file + `~/.claude/cv-icons-migration.md`
2025-11-17 18:04:07 +00:00
---
### 3. Hyperscript-JavaScript Interoperability (CRITICAL - 2025-11-17)
**Rule: Hyperscript `call` in attributes requires global JavaScript scope **
``` html
<!-- ❌ DOESN'T WORK - Hyperscript def not in window -->
< button _ = "on click call hyperscriptFunction()" > Click< / button >
<!-- ✅ WORKS - JavaScript wrapper exposes to window -->
window.functionName = () => _hyperscript.evaluate('hyperscriptFunction()');
```
**Why this matters: **
- Hyperscript docs say "global hyperscript functions can be called from JavaScript" ✅ TRUE
- BUT the reverse (`call` in `_=""` attributes) requires functions in `window` object
- Hyperscript `def` functions are NOT automatically exposed to window
- Templates use `_="on mouseenter call syncPdfHover(true)"` syntax
**Solution - Wrapper Pattern: **
``` javascript
// static/js/cv-functions.js
function syncPdfHover ( show ) {
if ( typeof _hyperscript !== 'undefined' ) {
_hyperscript . evaluate ( 'syncPdfHover(' + show + ')' ) ;
}
}
window . syncPdfHover = syncPdfHover ;
```
**Files: **
- Implementation: `static/hyperscript/*.hs` (toggles._hs, hover-sync._hs)
- Wrappers: `static/js/cv-functions.js`
- Test: `tests/mjs/8-hover-sync.test.mjs`
**Bug History: ** Hover sync broke when JavaScript functions were deleted during hyperscript migration. Restored as thin wrappers (commit 491aa66).
---
### 4. Zoom Architecture (CRITICAL - 2025-11-17)
**Rule: Only CV content inside #zoom-wrapper, NOT UI chrome **
``` html
<!-- ✅ CORRECT Structure -->
< div id = "zoom-wrapper" >
< div class = "cv-container" > CV Content< / div >
< / div >
{{template "page-footer" .}} <!-- OUTSIDE zoom - wrapper -->
```
**What gets zoomed (INSIDE #zoom-wrapper): **
- ✅ CV paper (.cv-container)
- ✅ CV content (.cv-paper)
**What does NOT get zoomed (OUTSIDE #zoom-wrapper): **
- ✅ Footer
- ✅ Action bar
- ✅ Hamburger menu
- ✅ Fixed buttons (PDF, print, zoom toggle, etc.)
**Zoom Range: ** 25% - 300% (updated from 175% on 2025-11-17)
**localStorage Keys: **
- `cv-zoom` - Current zoom level (25-300)
- `cv-zoom-visible` - Whether zoom control is shown (true/false)
- `cv-zoom-position` - Draggable position of zoom control
**Critical Bug Fixed (commit 52e97f1): **
- Footer was INSIDE zoom-wrapper → got zoomed with content
- Moved footer OUTSIDE zoom-wrapper → stays normal size
- Test: `tests/mjs/11-zoom-ui-exclusion.test.mjs`
**Critical Bug Fixed (commit 35a836a): **
- Zoom persistence broken - set wrong element's value
- zoom-control.html:10 was `set my value` (div) instead of `set #zoom-slider's value`
- Test: `tests/mjs/10-zoom-persistence.test.mjs`
---
### 5. Test Maintenance (REQUIRED - 2025-11-17)
**Rule: Update tests/TEST-SUMMARY.md every time you add a test **
When adding new test files:
1. Create test file: `tests/mjs/{N}-{feature}.test.mjs`
2. Update `tests/TEST-SUMMARY.md` with test description
3. Update test count at bottom of TEST-SUMMARY.md
4. Add to New Tests section with date
**Current Test Count: ** 12 active (0-11), 60+ archived
**Master test runner: ** `tests/run-all.mjs` (auto-discovers numbered tests)