69012bb1ae
New test files: - config/config_test.go (100% coverage) - constants/constants_test.go (100% coverage) - httputil/response_test.go (100% coverage) - validation/rules_test.go (91.9% coverage) - middleware/logger_test.go, security_test.go, security_logger_test.go - handlers/errors_test.go Updated documentation: - doc/27-GO-TESTING.md: Complete testing guide - doc/00-GO-DOCUMENTATION-INDEX.md: Added testing section - doc/01-ARCHITECTURE.md: Updated package structure - doc/DECISIONS.md: Added ADR-004 caching decision - PROJECT-MEMORY.md: Added Go testing section
820 lines
27 KiB
Markdown
820 lines
27 KiB
Markdown
# 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`
|
|
|
|
---
|
|
|
|
### 2. Hyperscript Parser Limit (NO LONGER EXISTS ✅)
|
|
|
|
**✅ CONFIRMED: NO def statement limit with hyperscript 0.9.14+**
|
|
|
|
**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 (no limit)
|
|
|
|
**Historical Context (for reference only):**
|
|
- Hyperscript 0.9.12 had a hard 3 def limit (fixed in 0.9.14)
|
|
- Current version has NO def statement limit
|
|
- Functions can be freely organized across multiple files
|
|
|
|
**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()')`
|
|
|
|
**Current Best Practice:** Organize hyperscript functions by category in separate files
|
|
|
|
```
|
|
static/hyperscript/
|
|
├── toggles._hs # Toggle functions (CV length, icons, theme)
|
|
├── hover-sync._hs # Hover synchronization functions
|
|
├── 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.)
|
|
```
|
|
|
|
**Migration in progress:** Moving functions from `cv-functions.js` back to hyperscript
|
|
|
|
**Test that verifies no limit:** `tests/mjs/9-hyperscript-def-limit.test.mjs`
|
|
|
|
**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"
|
|
|
|
---
|
|
|
|
### 3. Toggle Synchronization (WORKING) + Hover Sync (FIXED)
|
|
|
|
**Toggle checkboxes WORK perfectly ✅**
|
|
|
|
Every toggle checkbox exists in TWO places and stays synchronized:
|
|
1. Action bar (desktop) - `#lengthToggle`, `#iconToggle`, `#themeToggle`
|
|
2. Hamburger menu (mobile) - `#lengthToggleMenu`, `#iconToggleMenu`, `#themeToggleMenu`
|
|
|
|
**Toggle behavior (WORKING):**
|
|
- ✅ 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;
|
|
}
|
|
}
|
|
```
|
|
|
|
**✅ HOVER SYNC - PDF/Print buttons NOW WORKING**
|
|
|
|
**Action buttons that sync hover states:**
|
|
1. Action bar (desktop):
|
|
- `#action-bar-pdf-btn` - Download PDF button
|
|
- `.action-bar-print-btn` - Print Friendly button
|
|
2. Hamburger menu (mobile):
|
|
- `.menu-pdf-btn` - PDF button (templates/partials/navigation/hamburger-menu.html:178)
|
|
- `.menu-print-btn` - Print button (templates/partials/navigation/hamburger-menu.html:186)
|
|
|
|
**Working hover sync functions:**
|
|
- `syncPdfHover(isHovering)` - Syncs PDF button hover between locations ✅
|
|
- `syncPrintHover(isHovering)` - Syncs Print button hover between locations ✅
|
|
|
|
**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
|
|
|
|
**Files involved:**
|
|
- `templates/partials/navigation/action-buttons.html` (lines 9-10, 18-19)
|
|
- `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)
|
|
|
|
**Test coverage:** `tests/mjs/8-hover-sync.test.mjs` ✅
|
|
|
|
**Test that enforces toggle sync:** `tests/mjs/1-toggles.test.mjs` ✅
|
|
|
|
---
|
|
|
|
### 4. Real-Time Rendering + Persistence (CRITICAL)
|
|
|
|
**ALL UI changes MUST happen in BOTH DOM and localStorage**
|
|
|
|
**BOTH are required - not mutually exclusive:**
|
|
|
|
```javascript
|
|
// ✅ CORRECT - DOM update AND localStorage
|
|
paper.classList.add('show-icons'); // Visual change (instant feedback)
|
|
localStorage.setItem('cv-icons', 'true'); // Persistence (survives reload)
|
|
|
|
// ❌ WRONG - Only DOM (lost on page reload)
|
|
paper.classList.add('show-icons');
|
|
|
|
// ❌ WRONG - Only storage (no visual feedback)
|
|
localStorage.setItem('cv-icons', 'true');
|
|
```
|
|
|
|
**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
|
|
|
|
**Test that enforces this:** `tests/mjs/1-toggles.test.mjs` (verifies both DOM and localStorage)
|
|
|
|
---
|
|
|
|
### 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
|
|
├── 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
|
|
└── 9-hyperscript-def-limit.test.mjs # Def statement limit verification
|
|
```
|
|
|
|
**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
|
|
|
|
**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
|
|
|
|
**Why HTMX + Hyperscript:**
|
|
- Server-driven UI (hypermedia pattern)
|
|
- Minimal JavaScript
|
|
- Progressive enhancement
|
|
- Real-time updates capabilities
|
|
|
|
### File Organization
|
|
|
|
```
|
|
cv/
|
|
├── main.go # Server entry point (v1.1.0)
|
|
├── go.mod, go.sum # Go dependencies
|
|
├── internal/
|
|
│ ├── cache/ # Application-level data caching (95.7% coverage)
|
|
│ ├── config/ # Configuration (100% coverage)
|
|
│ ├── constants/ # Project-wide constants (100% coverage)
|
|
│ ├── email/ # Email service - SMTP (58% coverage)
|
|
│ ├── fileutil/ # File path utilities (88.9% coverage)
|
|
│ ├── handlers/ # HTTP handlers (62.9% coverage)
|
|
│ ├── httputil/ # HTTP response helpers (100% coverage)
|
|
│ ├── middleware/ # HTTP middleware (87.5% coverage)
|
|
│ ├── models/ # Data models (cv: 83.3%, ui: 85.7%)
|
|
│ ├── pdf/ # PDF generation (requires Chrome)
|
|
│ ├── routes/ # Route definitions
|
|
│ ├── templates/ # Template utilities
|
|
│ └── validation/ # Input validation (91.9% coverage)
|
|
├── static/
|
|
│ ├── js/
|
|
│ │ └── cv-functions.js # Global functions (toggles, keyboard, hover sync)
|
|
│ ├── css/ # Stylesheets
|
|
│ ├── hyperscript/
|
|
│ │ └── functions._hs # Hyperscript functions (if any)
|
|
│ ├── images/ # Static images
|
|
│ └── pdf/ # PDF files
|
|
├── templates/
|
|
│ ├── index.html # Main page template
|
|
│ ├── cv-content.html # CV content
|
|
│ ├── language-switch.html # Language switcher
|
|
│ └── partials/
|
|
│ ├── 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
|
|
└── tests/
|
|
└── mjs/ # Systematic test suite (Playwright + Bun runner)
|
|
```
|
|
|
|
**Critical files:**
|
|
- `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
|
|
|
|
---
|
|
|
|
## 🐛 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
|
|
- `doc/HYPERSCRIPT-RULES.md` - Parser limits and workarounds
|
|
- `~/.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
|
|
- `doc/MODERN-WEB-TECHNIQUES.md` - Architecture decisions
|
|
|
|
### 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
|
|
**Lesson:** Having toggles in two or more places (action bar + menu) means every change must update all locations. Forgetting this causes desync bugs.
|
|
|
|
**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
|
|
4. Then fix the bug, using the debug agent and the htmx expert
|
|
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-12-06
|
|
**Project Status:** Production - Full feature set including CMD+K command palette and contact form
|
|
**Test Coverage:**
|
|
- **Frontend (Playwright):** 44 test files, 100% core features
|
|
- **Backend (Go):** 12 test files, ~75% average coverage
|
|
- 100%: config, constants, httputil
|
|
- 90%+: cache (95.7%), validation (91.9%)
|
|
- 80%+: middleware (87.5%), fileutil (88.9%), models
|
|
- See `doc/27-GO-TESTING.md` for full details
|
|
**Critical Memory Files:** This file + `~/.claude/cv-icons-migration.md`
|
|
|
|
---
|
|
|
|
### 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:** 39 test files (comprehensive coverage)
|
|
|
|
**Master test runner:** `tests/run-all.mjs` (auto-discovers numbered tests)
|
|
|
|
---
|
|
|
|
### 6. Contact Form (2025-12-01)
|
|
|
|
**Secure contact form with comprehensive security middleware chain:**
|
|
|
|
**Security Features:**
|
|
- **BrowserOnly middleware** - Blocks curl/Postman/bots (requires HX-Request header)
|
|
- **Rate limiting** - 5 submissions per hour per IP
|
|
- **CSRF protection** - Token validation against session
|
|
|
|
**Files:**
|
|
- `internal/handlers/cv_contact.go` - Contact form handler
|
|
- `internal/middleware/browser_only.go` - Browser validation middleware
|
|
- `internal/middleware/contact_rate_limit.go` - Rate limiting
|
|
- `templates/partials/modals/contact-modal.html` - Contact form UI
|
|
|
|
**Documentation:**
|
|
- `doc/17-CONTACT-FORM.md` - Quick start guide
|
|
- `doc/18-SECURITY-AUDIT.md` - Security audit including contact form
|
|
- `doc/19-SECURITY-IMPLEMENTATION.md` - Security controls documentation
|
|
|
|
**Tests:** `tests/mjs/73-contact-form.test.mjs`
|
|
|
|
---
|
|
|
|
### 7. Plain Text CV (2025-12-01)
|
|
|
|
**CLI-friendly plain text output for curl, wget, lynx, w3m:**
|
|
|
|
**Features:**
|
|
- Auto-detected via User-Agent header
|
|
- 80-character line width
|
|
- Unicode/emoji support with proper centering
|
|
- Useful for AI assistants reading CV content
|
|
|
|
**Files:**
|
|
- `internal/handlers/cv_text.go` - Plain text handler
|
|
- `templates/cv-text.txt` - Plain text template
|
|
|
|
**Usage:**
|
|
```bash
|
|
curl http://localhost:1999/text
|
|
curl http://localhost:1999/text?lang=es
|
|
```
|
|
|
|
---
|
|
|
|
### 8. CMD+K Command Palette (2025-12-01, updated 2025-12-04)
|
|
|
|
**ninja-keys integration for quick navigation:**
|
|
|
|
**Features:**
|
|
- Dynamic entries from CV data (experiences, projects, courses)
|
|
- Scroll-to-section functionality
|
|
- Language-aware responses
|
|
- 1-hour cache headers
|
|
- **Search bar button** (2025-12-04): macOS Spotlight-style search bar in action bar
|
|
- Integrated in dark action bar with semi-transparent styling
|
|
- Shows keyboard shortcut indicators (⌘ K) as individual kbd elements
|
|
- Replaces old simple search button with more discoverable design
|
|
- CSS: `.search-bar-btn`, `.search-bar-icon`, `.search-bar-text`, `.search-bar-keys`
|
|
- Responsive: kbd keys hidden on mobile (<900px)
|
|
|
|
**Files:**
|
|
- `internal/handlers/cv_cmdk.go` - CMD+K API handler
|
|
- `static/js/ninja-keys-init.js` - Frontend initialization
|
|
- `doc/16-CMD-K-API.md` - API documentation
|
|
- `templates/partials/navigation/action-buttons.html` - Search bar button HTML
|
|
- `static/css/04-interactive/_buttons.css` - Search bar button styles
|
|
|
|
**Tests:**
|
|
- `tests/mjs/71-cmd-k-api-scroll.test.mjs`
|
|
- `tests/mjs/72-cmd-k-button.test.mjs` - Tests search bar styling, kbd elements, icon, click behavior
|
|
|
|
---
|
|
|
|
### 9. Go Backend Testing (2025-12-06)
|
|
|
|
**Comprehensive Go test suite with ~75% average coverage:**
|
|
|
|
**Commands:**
|
|
```bash
|
|
# Run all Go tests
|
|
go test ./internal/...
|
|
|
|
# Run with coverage
|
|
go test -cover ./internal/...
|
|
|
|
# Generate HTML coverage report
|
|
go test -coverprofile=coverage.out ./internal/...
|
|
go tool cover -html=coverage.out -o coverage.html
|
|
```
|
|
|
|
**Coverage by Package:**
|
|
| Package | Coverage | Notes |
|
|
|---------|----------|-------|
|
|
| config | 100% | Configuration loading |
|
|
| constants | 100% | All constants validated |
|
|
| httputil | 100% | Response helpers |
|
|
| cache | 95.7% | Application-level data caching |
|
|
| validation | 91.9% | Input validation rules |
|
|
| middleware | 87.5% | Security, rate limiting, preferences |
|
|
| fileutil | 88.9% | File path utilities |
|
|
| models/ui | 85.7% | UI configuration models |
|
|
| models/cv | 83.3% | CV data models |
|
|
| handlers | 62.9% | HTTP handlers (PDF needs Chrome) |
|
|
| email | 58.0% | Requires SMTP connection |
|
|
|
|
**Test Files:**
|
|
- `internal/cache/data_cache_test.go`
|
|
- `internal/config/config_test.go`
|
|
- `internal/constants/constants_test.go`
|
|
- `internal/email/email_test.go`
|
|
- `internal/handlers/errors_test.go`
|
|
- `internal/httputil/response_test.go`
|
|
- `internal/middleware/csrf_test.go`
|
|
- `internal/middleware/logger_test.go`
|
|
- `internal/middleware/contact_rate_limit_test.go`
|
|
- `internal/middleware/security_logger_test.go`
|
|
- `internal/validation/rules_test.go`
|
|
|
|
**Documentation:** `doc/27-GO-TESTING.md`
|
|
|