diff --git a/build/render_hook_docs/AI_RENDER_HOOK_LESSONS.md b/build/render_hook_docs/AI_RENDER_HOOK_LESSONS.md new file mode 100644 index 0000000000..49fddd83b4 --- /dev/null +++ b/build/render_hook_docs/AI_RENDER_HOOK_LESSONS.md @@ -0,0 +1,431 @@ +# 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 `