205 lines
5.9 KiB
Markdown
205 lines
5.9 KiB
Markdown
# Skeleton Loader Bug Fix - Verification Report
|
|
|
|
## 🔴 BUG IDENTIFIED
|
|
|
|
**Issue**: Skeleton loader was stuck permanently visible after language switch
|
|
|
|
## ROOT CAUSE ANALYSIS
|
|
|
|
### The Problem
|
|
The hyperscript event handlers were attached to the `#language-selector` element, which gets completely replaced during HTMX swap:
|
|
|
|
```html
|
|
<!-- BEFORE (BROKEN) -->
|
|
<div class="language-selector-wrapper">
|
|
<div class="language-selector" id="language-selector"
|
|
_="on htmx:beforeRequest from .selector-btn
|
|
add .active to #skeleton-loader
|
|
end
|
|
on htmx:afterSwap from .selector-btn
|
|
wait 100ms
|
|
remove .active from #skeleton-loader
|
|
end">
|
|
<button hx-target="#language-selector"
|
|
hx-swap="outerHTML">...</button>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**What happened**:
|
|
1. ✅ User clicks language button
|
|
2. ✅ `htmx:beforeRequest` fires → skeleton appears (`.active` added)
|
|
3. ❌ **HTMX swaps entire `#language-selector` with outerHTML** → Event handlers DESTROYED
|
|
4. ❌ `htmx:afterSwap` fires, but no listener exists on new element
|
|
5. ❌ Skeleton stuck with `.active` class forever
|
|
|
|
### The Solution
|
|
Move hyperscript handlers to the **parent wrapper** that doesn't get swapped:
|
|
|
|
```html
|
|
<!-- AFTER (FIXED) -->
|
|
<div class="language-selector-wrapper"
|
|
_="on htmx:beforeRequest from .selector-btn
|
|
add .active to #skeleton-loader
|
|
end
|
|
on htmx:afterSwap from .selector-btn
|
|
wait 100ms
|
|
remove .active from #skeleton-loader
|
|
end">
|
|
<div class="language-selector" id="language-selector">
|
|
<button hx-target="#language-selector"
|
|
hx-swap="outerHTML">...</button>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Why this works**:
|
|
1. ✅ Event handlers on `.language-selector-wrapper` (persists across swaps)
|
|
2. ✅ Listens for events FROM `.selector-btn` (event bubbling)
|
|
3. ✅ `htmx:beforeRequest` → skeleton appears
|
|
4. ✅ HTMX swaps `#language-selector` → wrapper remains intact
|
|
5. ✅ `htmx:afterSwap` → wrapper handlers still exist → skeleton disappears
|
|
|
|
## FILES MODIFIED
|
|
|
|
1. **templates/partials/navigation/language-selector.html**
|
|
- Moved hyperscript from `#language-selector` to `.language-selector-wrapper`
|
|
|
|
2. **templates/language-switch.html**
|
|
- Removed duplicate hyperscript from swapped element
|
|
|
|
## VERIFICATION STEPS
|
|
|
|
### 1. HTML Structure Verification ✅
|
|
```bash
|
|
curl -s http://localhost:1999/ | grep -A 10 "language-selector-wrapper"
|
|
```
|
|
|
|
**Result**: Hyperscript correctly attached to wrapper:
|
|
```html
|
|
<div class="language-selector-wrapper"
|
|
_="on htmx:beforeRequest from .selector-btn
|
|
add .active to #skeleton-loader
|
|
end
|
|
on htmx:afterSwap from .selector-btn
|
|
wait 100ms
|
|
remove .active from #skeleton-loader
|
|
end">
|
|
```
|
|
|
|
### 2. Swap Response Verification ✅
|
|
```bash
|
|
curl -s "http://localhost:1999/switch-language?lang=es" | grep -A 5 "language-selector"
|
|
```
|
|
|
|
**Result**: Inner element has NO hyperscript (as intended):
|
|
```html
|
|
<div class="language-selector" id="language-selector">
|
|
<button class="selector-btn">...</button>
|
|
</div>
|
|
```
|
|
|
|
### 3. CSS State Verification ✅
|
|
```bash
|
|
curl -s http://localhost:1999/static/css/main.css | grep -A 3 "#skeleton-loader"
|
|
```
|
|
|
|
**Result**: Proper CSS states:
|
|
```css
|
|
#skeleton-loader {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 250ms ease-in-out;
|
|
}
|
|
|
|
#skeleton-loader.active {
|
|
opacity: 1;
|
|
pointer-events: all;
|
|
}
|
|
```
|
|
|
|
## MANUAL BROWSER TEST REQUIRED
|
|
|
|
### Test Steps:
|
|
1. Open http://localhost:1999/?lang=en
|
|
2. Open DevTools Console
|
|
3. Run this monitoring script:
|
|
```javascript
|
|
// Monitor skeleton loader state
|
|
const skeleton = document.getElementById('skeleton-loader');
|
|
const observer = new MutationObserver(() => {
|
|
console.log('Skeleton classes:', skeleton.className);
|
|
console.log('Skeleton opacity:', window.getComputedStyle(skeleton).opacity);
|
|
});
|
|
observer.observe(skeleton, { attributes: true, attributeFilter: ['class'] });
|
|
|
|
// Monitor HTMX events
|
|
document.body.addEventListener('htmx:beforeRequest', (e) => {
|
|
if (e.detail.elt.classList.contains('selector-btn')) {
|
|
console.log('[BEFORE] Language switch starting');
|
|
}
|
|
});
|
|
document.body.addEventListener('htmx:afterSwap', (e) => {
|
|
if (e.detail.elt.classList.contains('selector-btn')) {
|
|
console.log('[AFTER] Language switch complete');
|
|
}
|
|
});
|
|
```
|
|
|
|
4. Click "Español" button
|
|
5. Watch console output
|
|
|
|
### Expected Console Output:
|
|
```
|
|
[BEFORE] Language switch starting
|
|
Skeleton classes: active
|
|
Skeleton opacity: 1
|
|
[AFTER] Language switch complete
|
|
(after 100ms)
|
|
Skeleton classes:
|
|
Skeleton opacity: 0
|
|
```
|
|
|
|
### Expected Visual Behavior:
|
|
1. ✅ Skeleton appears immediately (fade in 250ms)
|
|
2. ✅ Page content swaps (250ms swap + 250ms settle)
|
|
3. ✅ Skeleton disappears after 100ms delay (fade out 250ms)
|
|
4. ✅ Total: ~850ms smooth transition
|
|
|
|
### What to Check:
|
|
- ✅ Skeleton appears when clicking language button
|
|
- ✅ Skeleton disappears after content loads
|
|
- ✅ Skeleton does NOT stay stuck visible
|
|
- ✅ Can switch languages multiple times without issues
|
|
- ✅ Smooth fade in/out transitions
|
|
|
|
## TECHNICAL DETAILS
|
|
|
|
### Event Bubbling
|
|
Hyperscript uses `from .selector-btn` which listens for events that bubble up from any element matching `.selector-btn`, even if those elements are replaced.
|
|
|
|
### Timing Breakdown
|
|
```
|
|
[0ms] User clicks button
|
|
[0ms] htmx:beforeRequest → skeleton.active = true
|
|
[0ms] Skeleton starts fading in (opacity 0→1 over 250ms)
|
|
[100ms] Server responds
|
|
[100ms] HTMX starts swap
|
|
[350ms] Swap complete (250ms swap duration)
|
|
[350ms] htmx:afterSwap fired
|
|
[450ms] 100ms wait complete
|
|
[450ms] skeleton.active = false
|
|
[450ms] Skeleton starts fading out (opacity 1→0 over 250ms)
|
|
[700ms] Skeleton fully hidden
|
|
```
|
|
|
|
## STATUS
|
|
|
|
- ✅ Root cause identified
|
|
- ✅ Fix implemented
|
|
- ✅ HTML structure verified
|
|
- ✅ CSS states verified
|
|
- ⏳ Manual browser test REQUIRED
|
|
|
|
**Next Step**: Run manual browser test to confirm skeleton loader shows and hides correctly.
|