Files
cv-site/PROJECT-MEMORY.md
T
juanatsap 949c9a0351 docs: Consolidate documentation into single doc/ folder
- Move docs/ contents to doc/ with proper numbering:
  - CONTACT-FORM-QUICKSTART.md → 17-CONTACT-FORM.md
  - SECURITY-AUDIT-REPORT.md → 18-SECURITY-AUDIT.md
  - SECURITY.md → 19-SECURITY-IMPLEMENTATION.md
- Delete duplicate/redundant files from docs/:
  - CMD-K-COMMAND-BAR.md (duplicate of 16-CMD-K-API.md)
  - CONTACT_FORM_IMPLEMENTATION.md (overlaps with quickstart)
  - SECURITY-IMPLEMENTATION-SUMMARY.md (summary of audit)
- Update doc/README.md with new document references
- Update test counts to 39 test files across all READMEs
- Update all "Last Updated" dates to 2025-12-01
- Add new API endpoints documentation (text, cmd-k, contact, toggles)
- Update PROJECT-MEMORY.md with new features and correct paths
2025-12-01 13:30:48 +00:00

24 KiB

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"

// ✅ 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:

-- 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):

<!-- Clean HTML - 9 navigation links use single function -->
<a href="#education" _="on click call scrollToSection(event, 'education')">
-- 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):

<!-- 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
// ✅ 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:

// ✅ 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:

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/
│   ├── config/                  # Configuration
│   ├── handlers/                # HTTP handlers
│   ├── middleware/              # HTTP middleware
│   ├── models/                  # Data models
│   ├── routes/                  # Route definitions
│   └── templates/               # Template utilities
├── 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)

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)

<!-- ✅ 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

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-01 Project Status: Production - Full feature set including CMD+K command palette and contact form Test Coverage: 39 test files, 100% core features + CMD+K, contact form, PDF generation 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

<!-- ❌ 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:

// 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

<!-- ✅ 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:

curl http://localhost:1999/text
curl http://localhost:1999/text?lang=es

8. CMD+K Command Palette (2025-12-01)

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

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

Tests:

  • tests/mjs/71-cmd-k-api-scroll.test.mjs
  • tests/mjs/72-cmd-k-button.test.mjs