ワンクリックで
lvt-debug-rendering
Debug LiveTemplate rendering issues - template not updating, partial renders, action dispatch errors, WebSocket problems
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Debug LiveTemplate rendering issues - template not updating, partial renders, action dispatch errors, WebSocket problems
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Use when deploying LiveTemplate applications to production - covers Docker containerization, Fly.io deployment, Kubernetes setup, database persistence, and production best practices
Transform app to production-ready - adds authentication, deployment config, environment setup, and best practices
Use when deploying LiveTemplate applications to production - covers Docker containerization, Fly.io deployment, Kubernetes setup, database persistence, and production best practices
Rapid end-to-end workflow - creates app, adds resources, sets up development environment in one flow
Generate test apps, capture screenshots, analyze UI for issues, and recursively fix problems in kit templates
Use when adding database migrations to LiveTemplate apps - guides both auto-generated migrations (from lvt gen resource) and custom migrations (indexes, constraints, data transformations)
| name | lvt-debug-rendering |
| description | Debug LiveTemplate rendering issues - template not updating, partial renders, action dispatch errors, WebSocket problems |
| category | maintenance |
| version | 1.0.0 |
| keywords | ["lvt","livetemplate","lt"] |
Systematic debugging guide for LiveTemplate rendering issues. Covers the full rendering pipeline from server-side tree generation to client-side DOM patching, helping diagnose and fix issues where templates don't update, render incorrectly, or actions fail to dispatch.
This skill typically runs in existing LiveTemplate projects (.lvtrc exists).
Context Established By:
.lvtrc exists (most common scenario)lvt-assistant agentKeyword matching (case-insensitive): lvt, livetemplate, lt
With Context:
Without Context (needs keywords):
| Symptom | Likely Phase | First Check | Key Files |
|---|---|---|---|
| Template not updating after data change | Diff | Tree cache state | tree_compare.go, template.go |
| Partial/broken HTML | Build | Statics array alignment | parse.go, types.go |
| Action dispatch error | Dispatch | Method signature | dispatch.go |
| Range items not updating | Diff/Range | Key detection | range_ops.go, tree-renderer.ts |
| Empty response from server | Build | Tree generation errors | template.go |
| morphdom not applying changes | Client | Tree state merging | tree-renderer.ts |
| WebSocket message not received | Transport | Connection state | websocket.ts, mount.go |
| Statics missing on update | Registry | Client structure tracking | signature/ package |
LiveTemplate uses a 5-phase rendering pipeline:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Parse │ -> │ Build │ -> │ Diff │ -> │ Render │ -> │ Send │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │ │
Template TreeNode with Changed HTML string WebSocket/
-> AST statics/dynamics fields only (full render) HTTP
Phase 1: Parse - Template string -> AST -> Tree structure Phase 2: Build - AST + Data -> TreeNode (statics + dynamics) Phase 3: Diff - Compare oldTree vs newTree -> Minimal changes Phase 4: Render - TreeNode -> HTML (for full renders) Phase 5: Send - JSON over WebSocket or HTTP response
Symptoms:
window.__wsMessages shows messages but DOM unchangedInvestigation Checklist:
1. [ ] Verify action reached server
- Check server logs for action dispatch
- Look for DispatchError in logs
2. [ ] Verify state mutation
- Add logging in controller action method
- Confirm state changed before return
- Check: are you returning the new state?
3. [ ] Check tree generation
- Log buildTree() output
- Verify dynamics contain changed values
4. [ ] Check diff generation
- Log output of CompareTreesAndGetChangesWithPath()
- Verify changed fields appear in result
- Check if fingerprint changed
5. [ ] Check WebSocket delivery (Browser Console)
window.__wsMessages // See all messages
window.__lastWSMessage // Last message content
6. [ ] Check client-side merge
liveTemplateClient.getTreeState() // Current state
7. [ ] Check DOM update
- Inspect element in browser DevTools
- Check if morphdom applied changes
Common Root Causes:
| Cause | Detection | Fix |
|---|---|---|
| State mutation without returning new state | Logging shows state changed but not returned | Return new state from controller method |
| TreeNode fingerprint unchanged | Diff returns empty object | Ensure data actually differs |
| Client rangeState out of sync | Range items update wrong elements | Check range key consistency |
| morphdom key mismatch | Elements replaced instead of updated | Use consistent data-key or data-lvt-key |
Example Fix - State Not Returned:
// WRONG - state mutated but not returned
func (c *Counter) Increment(state CounterState, ctx *livetemplate.Context) (CounterState, error) {
state.Count++
return state, nil // Missing return was: return CounterState{}, nil
}
// CORRECT
func (c *Counter) Increment(state CounterState, ctx *livetemplate.Context) (CounterState, error) {
state.Count++
return state, nil
}
Symptoms:
Investigation Checklist:
1. [ ] Check statics array alignment
- len(statics) should equal len(dynamics) + 1
- Statics interleave with dynamics: s[0] + d[0] + s[1] + d[1] + s[2]
2. [ ] Inspect TreeNode structure
- JSON serialize tree for inspection
- Check for missing "s" key in nested nodes
- Verify numeric keys are sequential ("0", "1", "2"...)
3. [ ] Check template parsing
lvt parse app/resource/resource.tmpl
- Look for parse errors
- Check template action syntax
4. [ ] Check data type matches
- Verify template expects correct data structure
- Check for nil pointers in data
5. [ ] Client-side reconstruction (Browser Console)
// Get tree state
liveTemplateClient.getTreeState()
// Check if statics present
// First render should have "s" arrays
// Updates may omit statics (cached on client)
Common Root Causes:
| Cause | Detection | Fix |
|---|---|---|
| Nested TreeNode missing statics | First nested render shows as empty | Ensure registry marks structures as seen after first render |
| Range statics stripped prematurely | Range items missing HTML wrapper | Check client-side range caching |
| Dynamic key numbering gap | Tree has "0", "2" but no "1" | Fix tree building logic |
| Template composition not flattened | {{template}} references fail | Ensure templates are properly composed |
Debug: Serialize TreeNode (Server)
// Add to handler for debugging
import "encoding/json"
func debugTree(tree *TreeNode) {
data, _ := json.MarshalIndent(tree, "", " ")
log.Printf("Tree:\n%s", string(data))
}
Symptoms:
Investigation Checklist:
1. [ ] Check method signature
Controller+State pattern requires:
func(state State, ctx *Context) (State, error)
2. [ ] Check action name mapping
- Template: lvt-click="increment"
- Go: func (c *Counter) Increment(...)
- Note: snake_case in template -> PascalCase method
3. [ ] Check method visibility
- Method must be exported (uppercase first letter)
- Method must be on correct receiver type
4. [ ] Check DispatchError details
- Action name attempted
- Store type searched
- Available methods on type
Common Root Causes:
| Cause | Detection | Fix |
|---|---|---|
| Method not exported | DispatchError shows lowercase method | Capitalize method name |
| Wrong signature | Method found but not matching | Use exact (state State, ctx *Context) (State, error) |
| Action name typo | DispatchError shows misspelled action | Fix template attribute |
| Wrong receiver type | Method on value receiver, store uses pointer | Match receiver to store usage |
Example - DispatchError Output:
Error: method not found: action 'incremnt' not found on type *Counter
Available methods: Increment, Decrement, Reset
Fix: Correct typo in template from lvt-click="incremnt" to lvt-click="increment"
Example - Wrong Signature:
// WRONG - missing context parameter
func (c *Counter) Increment(state CounterState) (CounterState, error) {
...
}
// CORRECT
func (c *Counter) Increment(state CounterState, ctx *livetemplate.Context) (CounterState, error) {
...
}
internal/parse/)What happens: Template string -> AST -> Tree structure
Key functions:
Parse() - Main entry pointevaluatePipe() - Expression evaluationDebug points:
// Check parse result
tree, err := parse.Parse(templateString)
if err != nil {
log.Printf("Parse error: %v", err)
}
Common issues:
internal/build/)What happens: AST + Data -> TreeNode with statics/dynamics
Key types:
type TreeNode struct {
Statics []string // Static HTML fragments
Dynamics map[string]interface{} // Dynamic values at positions
Range *RangeData // Range metadata if present
Metadata *TreeMetadata // Additional metadata
}
Debug points:
// Log tree structure
tree := buildTree(ast, data)
treeJSON, _ := json.MarshalIndent(tree, "", " ")
log.Printf("Built tree:\n%s", string(treeJSON))
Common issues:
internal/diff/)What happens: oldTree vs newTree -> Minimal changes
Key functions:
CompareTreesAndGetChangesWithPath() - Main entry pointFindRangeConstructMatches() - Range item matchingDebug points:
// Log diff result
changes := diff.CompareTreesAndGetChangesWithPath(oldTree, newTree, path)
log.Printf("Old dynamics: %+v", oldTree.Dynamics)
log.Printf("New dynamics: %+v", newTree.Dynamics)
log.Printf("Changes: %+v", changes.Dynamics)
Range operations format:
{
"d": [
["u", "item-3", {"0": "updated value"}], // Update
["i", "item-2", "0", {"s": [...], "0": ...}], // Insert after
["r", "item-5"], // Remove
["o", ["id-1", "id-2", "id-3"]] // Reorder
]
}
Common issues:
internal/render/, internal/context/)What happens: Execute Go template, produce HTML
Key functions:
renderHTML() - Template executionMinifyHTML() - HTML minificationTemplate context available:
// In templates, available via .lvt namespace
.lvt.Error("fieldName") // Validation error for field
.lvt.HasError("fieldName") // Check if field has error
.lvt.AllErrors() // All validation errors
.lvt.Uploads("fieldName") // Access uploads
.lvt.DevMode // Development mode flag
Common issues:
internal/send/, internal/session/)What happens: TreeNode -> JSON -> WebSocket/HTTP
Key files:
mount.go - HTTP/WebSocket handlersession/registry.go - Connection managementWebSocket architecture:
Send() → Queue to sendChan → writePump goroutine → WebSocket.WriteMessage()
Metrics to check:
// Prometheus metrics available
wsBufferFull // Buffer overflow events
wsSlowClientCloses // Slow client disconnects
wsWriteErrors // WebSocket write failures
Common issues:
ErrClientTooSlow)ErrConnectionClosed)transport/websocket.ts)Debug variables (Browser Console):
// All received WebSocket messages
window.__wsMessages
// Last message received
window.__lastWSMessage
// Connection state: 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
liveTemplateClient.ws?.readyState
// Transport used for last message
window.__lvtSendPath // "websocket" | "http" | "http-fallback"
Connection issues:
// Check if WebSocket connected
if (liveTemplateClient.ws?.readyState !== 1) {
console.log("WebSocket not connected");
}
// Force reconnect (if stuck)
liveTemplateClient.ws?.close();
// Auto-reconnect should trigger
state/tree-renderer.ts)Key state:
treeState - Current tree structurerangeState - Range item trackingrangeIdKeys - ID-to-key mapping for rangesDebug (Browser Console):
// Get current tree state
liveTemplateClient.getTreeState()
// After an update, check what was applied
// Look for changes in dynamics
Key methods:
applyUpdate() - Entry point for server updatesdeepMergeTreeNodes() - Merge nested updatesapplyDifferentialOpsToRange() - Handle range operationsreconstructFromTree() - TreeNode -> HTML stringCommon issues:
rangeState out of sync with serverlivetemplate-client.ts)Uses morphdom for efficient DOM reconciliation.
Key callbacks:
onBeforeElUpdated - Focus preservation, skip equal nodesonNodeAdded - lvt-mounted lifecycle hookonBeforeNodeDiscarded - lvt-destroyed lifecycle hookDebug variables:
// Check if send was called
window.__lvtSendCalled
// Action name of last message
window.__lvtMessageAction
// Form submission tracking
window.__lvtSubmitListenerTriggered
window.__lvtActionFound
window.__lvtInWrapper
Common issues:
dom/event-delegation.ts)Supported attributes:
lvt-click, lvt-submit, lvt-change, lvt-inputlvt-keydown, lvt-keyup, lvt-focus, lvt-blurlvt-window-* for window-level eventslvt-click-away for outside clicksDebug:
// Add before event to trace handling
document.addEventListener('click', (e) => {
console.log('Click target:', e.target);
console.log('Has lvt-click:', e.target.hasAttribute?.('lvt-click'));
}, true);
// Add to handler for comprehensive debugging
import (
"encoding/json"
"log"
)
// Before tree building
log.Printf("State before: %+v", state)
// After tree building
func debugTree(name string, tree *TreeNode) {
data, _ := json.MarshalIndent(tree, "", " ")
log.Printf("%s tree:\n%s", name, string(data))
}
// Before/after diff
debugTree("Old", oldTree)
debugTree("New", newTree)
log.Printf("Changes: %+v", diff)
// Comprehensive message logging
window.addEventListener('message', (e) => {
console.log('Window message:', e.data);
});
// Trace all WebSocket messages
const origOnMessage = liveTemplateClient.ws.onmessage;
liveTemplateClient.ws.onmessage = (e) => {
console.log('WS message:', JSON.parse(e.data));
origOnMessage.call(liveTemplateClient.ws, e);
};
// Manual tree state inspection
JSON.stringify(liveTemplateClient.getTreeState(), null, 2);
// Force a re-render (testing)
liveTemplateClient.applyUpdate({"0": "test"});
// Access browser console logs
chromedp.ActionFunc(func(ctx context.Context) error {
chromedp.ListenTarget(ctx, func(ev interface{}) {
if msg, ok := ev.(*runtime.EventConsoleAPICalled); ok {
for _, arg := range msg.Args {
log.Printf("Console [%s]: %s", msg.Type, arg.Value)
}
}
})
return nil
})
// Get WebSocket state
var wsState int
chromedp.Evaluate(`liveTemplateClient.ws?.readyState || -1`, &wsState)
// Get tree state
var treeState map[string]interface{}
chromedp.Evaluate(`liveTemplateClient.getTreeState()`, &treeState)
// Get all WebSocket messages
var wsMessages []interface{}
chromedp.Evaluate(`window.__wsMessages || []`, &wsMessages)
| Cause | Symptom | Fix |
|---|---|---|
| Statics/dynamics mismatch | Partial HTML, missing content | Check tree building logic alignment |
| Nested range not embedded | Range items inline instead of nested | Check childTree.HasRange() branch |
| Key generator reuse | Wrong keys on reconnect | Reset keyGen per session |
| Cause | Symptom | Fix |
|---|---|---|
| Same fingerprint | No update sent | Verify data actually changed |
| Registry mistrack | Statics wrongly stripped | Check MarkSeen() calls |
| Range match failure | Full replace instead of diff | Check key extraction logic |
| Cause | Symptom | Fix |
|---|---|---|
| rangeState out of sync | Range operations fail | Initialize from first tree |
| morphdom key conflict | Wrong elements updated | Use consistent data-key |
| Tree merge incomplete | Old values persist | Check deepMergeTreeNodes() |
lvt:validate-templateslvt:troubleshootlvt:deploylvt:run-and-testThese are the key files and functions this skill references. If any change significantly, update the skill.
| File | Key Functions/Types |
|---|---|
template.go | buildTree(), ExecuteUpdates(), Clone() |
dispatch.go | DispatchError, DispatchWithState() |
internal/parse/parse.go | Parse(), evaluatePipe() |
internal/build/types.go | TreeNode, RangeData, TreeMetadata |
internal/diff/tree_compare.go | CompareTreesAndGetChangesWithPath() |
internal/diff/range_ops.go | Range differential operations |
internal/observe/metrics.go | actionsProcessed, treesBuilt, wsWriteErrors |
| File | Key Functions/Variables |
|---|---|
livetemplate-client.ts | updateDOM(), handleWebSocketPayload(), window.__wsMessages, window.__lvtSendPath |
state/tree-renderer.ts | applyUpdate(), reconstructFromTree(), deepMergeTreeNodes() |
transport/websocket.ts | WebSocketTransport, WebSocketManager, getReadyState() |
dom/event-delegation.ts | lvt-* attribute handlers |
Last Validated: 2024-12-20 against commit 696b1fd
Update Checklist:
When updating this skill, verify:
__wsMessages, __lvtSendPath, etc.)go test ./e2e -run TestDebugRenderingSkillReferencesMonitored Files:
Server: template.go, dispatch.go, internal/parse/parse.go, internal/build/types.go, internal/diff/tree_compare.go
Client: livetemplate-client.ts, state/tree-renderer.ts, transport/websocket.ts
Do:
window.__wsMessages to verify WebSocket deliveryrangeState for range update issuesDon't:
__wsMessages