# Render Hook Implementation Lessons ## Overview This document captures key lessons learned while implementing the checklist render hook for Hugo. These insights should help guide future render hook implementations. ## 1. Progressive Enhancement Pattern **Key Principle**: Preserve the original Markdown source in a `
` element for non-JS viewers and AI agents, then progressively enhance it with JavaScript for users with JS enabled.

**Implementation**:
```html
{{ .Inner | htmlEscape | safeHTML }}
{{ .Page.Store.Set "hasChecklist" true }} ``` **Benefits**: - AI agents can easily parse the raw Markdown source (e.g., via `.html.md` URLs) - Graceful degradation for users without JavaScript - SEO-friendly (content is in the HTML) - Easier to debug and maintain **Lesson**: Always preserve the source content in a way that's accessible without JavaScript. --- ## 2. Avoiding Duplicate JavaScript on Multi-Component Pages **Problem**: If a page has multiple checklists, the JavaScript would be loaded multiple times, causing inefficiency and potential conflicts. **Solution**: Use Hugo's page store pattern (like Mermaid does): 1. **Render hook sets a flag**: ```html {{ .Page.Store.Set "hasChecklist" true }} ``` 2. **Base template conditionally loads the script**: ```html {{ if .Page.Store.Get "hasChecklist" }} {{ end }} ``` 3. **JavaScript finds all instances and processes them**: ```javascript const checklists = document.querySelectorAll('pre.checklist-source'); checklists.forEach(pre => { /* process each */ }); ``` **Lesson**: Use page store flags to conditionally load resources only when needed, and design JavaScript to handle multiple instances on a single page. --- ## 3. Static Files vs Asset Pipeline **Problem**: Initially placed `checklist.js` in `assets/js/` but got 404 errors. **Solution**: Hugo's static JavaScript files must go in `static/js/`, not `assets/js/`. **Explanation**: - `assets/` directory: For CSS, images, and files processed by Hugo's asset pipeline - `static/` directory: For files served directly as-is (JavaScript, fonts, etc.) **Lesson**: Know the difference between Hugo's asset pipeline and static files. JavaScript typically goes in `static/js/`. --- ## 4. Security: Avoid innerHTML with Dynamic Content **Problem**: Using `innerHTML` with template literals containing dynamic IDs is an XSS vulnerability vector. **Bad**: ```javascript countersDiv.innerHTML = ``; ``` **Good**: ```javascript const label = document.createElement('label'); label.htmlFor = formId + '-count'; label.textContent = 'text'; countersDiv.appendChild(label); ``` **Additional Tips**: - Use `textContent` instead of `innerHTML` for text content - Use `document.createTextNode()` for text nodes - Use `document.createDocumentFragment()` for efficient DOM building - Use actual emoji characters instead of HTML entities (safer and cleaner) **Lesson**: Always use safe DOM methods. Even if the data source is controlled, defense-in-depth is important. --- ## 5. Code Block Attributes and Extraction **Pattern**: Hugo render hooks receive code block attributes via `.Attributes`. **Example**: ```markdown ```checklist {id="pyprodlist"} - [ ] Item 1 - [ ] Item 2 ``` ``` **Extraction in render hook**: ```html {{- $id := .Attributes.id | default "checklist" -}} ``` **Lesson**: Use `.Attributes` to extract custom parameters from code block headers. Provide sensible defaults. --- ## 6. Data Attributes for JavaScript Communication **Pattern**: Use `data-*` attributes to pass information from HTML to JavaScript. **Example**: ```html
...
``` **JavaScript access**: ```javascript const checklistId = pre.getAttribute('data-checklist-id'); ``` **Lesson**: Data attributes are the clean way to pass server-side data to client-side JavaScript. Avoid embedding data in class names or other hacks. --- ## 7. Markdown Parsing in JavaScript **Pattern**: Parse Markdown in JavaScript using regex patterns. **Example** (checklist items): ```javascript const linkMatch = item.match(/\[([^\]]+)\]\(([^\)]+)\)/); if (linkMatch) { const a = document.createElement('a'); a.href = linkMatch[2]; a.textContent = linkMatch[1]; } ``` **Lesson**: For simple Markdown patterns, regex is sufficient. For complex parsing, consider a lightweight Markdown parser library. --- ## 8. State Persistence with localStorage **Pattern**: Use `localStorage` to persist user interactions across page reloads. **Example**: ```javascript // Save state localStorage.setItem(formId, itemChoices); // Load state let itemString = localStorage.getItem(formId); if (itemString) { setCLItemsFromString(formId, itemString); } ``` **Considerations**: - Use unique keys (e.g., based on checklist ID) to avoid conflicts - Handle missing/corrupted data gracefully - Consider privacy implications **Lesson**: localStorage is useful for persisting user state, but always validate and handle edge cases. --- ## 9. Template Context Limitations **Problem**: Render hook context doesn't have access to `.Page.Store` directly in some Hugo versions. **Solution**: Use `.Page.Store` in the render hook template, not `.Store`. **Lesson**: Understand the context available in render hooks vs other templates. Test with your Hugo version. --- ## 10. Testing Multiple Instances **Pattern**: Always test with multiple instances of the component on the same page. **Why**: - ID conflicts can occur - Event handlers might interfere - localStorage keys might collide - Performance issues might only appear with multiple instances **Lesson**: Test with at least 2-3 instances of your component on the same page before considering it complete. --- ## 11. Consistency Across Similar Components **Pattern**: When implementing similar components (like checklists for multiple client libraries), use the same Markdown format and render hook. **Benefits**: - Easier maintenance - Consistent user experience - Easier for AI agents to parse - Reduces code duplication **Lesson**: Design render hooks to be reusable across similar content. Use consistent naming conventions and ID patterns. --- ## 12. Accessibility Considerations **Implemented**: - Proper `