fix: implement deterministic ID generation for consistent content injection

- Replace random UUID suffix with deterministic hash-based signature
- Use element DOM path, sibling position, and content preview for uniqueness
- Ensure same elements always get the same content ID across enhancements
- Fix critical bug where content updates weren't being injected into static files

This resolves the enhance button workflow: API content updates → enhance trigger → static file injection.
This commit is contained in:
2025-09-20 17:53:04 +02:00
parent 6f682372b5
commit 1b5c673466

View File

@@ -1,11 +1,11 @@
package engine package engine
import ( import (
"crypto/sha256"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/google/uuid"
"golang.org/x/net/html" "golang.org/x/net/html"
) )
@@ -23,7 +23,7 @@ func NewIDGenerator() *IDGenerator {
} }
} }
// Generate creates a content ID for an HTML element using lightweight hierarchical approach // Generate creates a content ID for an HTML element using deterministic approach
func (g *IDGenerator) Generate(node *html.Node, filePath string) string { func (g *IDGenerator) Generate(node *html.Node, filePath string) string {
// 1. File context (minimal) // 1. File context (minimal)
fileName := g.getFileName(filePath) fileName := g.getFileName(filePath)
@@ -35,12 +35,19 @@ func (g *IDGenerator) Generate(node *html.Node, filePath string) string {
// 3. Build readable prefix (deterministic, no runtime counting) // 3. Build readable prefix (deterministic, no runtime counting)
prefix := g.buildDeterministicPrefix(fileName, tag, primaryClass) prefix := g.buildDeterministicPrefix(fileName, tag, primaryClass)
// 5. Add UUID-based suffix for guaranteed uniqueness // 4. Create deterministic suffix based on element characteristics
uuidSuffix := uuid.New().String()[:6] // Use first 6 chars of UUID signature := g.createDeterministicSignature(node, filePath)
finalID := fmt.Sprintf("%s-%s", prefix, uuidSuffix) finalID := fmt.Sprintf("%s-%s", prefix, signature)
// Ensure uniqueness within this session
counter := 1
originalID := finalID
for g.usedIDs[finalID] {
finalID = fmt.Sprintf("%s-%d", originalID, counter)
counter++
}
// Ensure uniqueness (should be guaranteed by hash, but safety check)
g.usedIDs[finalID] = true g.usedIDs[finalID] = true
return finalID return finalID
@@ -111,9 +118,42 @@ func (g *IDGenerator) buildPrefix(fileName, tag, primaryClass string, index int)
return strings.Join(parts, "-") return strings.Join(parts, "-")
} }
// createSignature creates a unique signature for collision resistance (DEPRECATED - using UUID now) // createDeterministicSignature creates a deterministic signature for element identification
func (g *IDGenerator) createDeterministicSignature(node *html.Node, filePath string) string {
// Build signature from stable characteristics
var sigParts []string
// 1. DOM path (simplified, max 3 levels)
domPath := g.getSimpleDOMPath(node)
if domPath != "" {
sigParts = append(sigParts, domPath)
}
// 2. Sibling position
siblingIndex := g.getSiblingIndex(node)
sigParts = append(sigParts, fmt.Sprintf("pos%d", siblingIndex))
// 3. Content preview (first few chars for uniqueness)
contentPreview := g.getContentPreview(node)
if contentPreview != "" {
// Use first 20 chars for signature
if len(contentPreview) > 20 {
contentPreview = contentPreview[:20]
}
sigParts = append(sigParts, contentPreview)
}
// 4. Create hash of combined signature
combined := strings.Join(sigParts, "|")
hash := sha256.Sum256([]byte(combined))
// Use first 6 characters of hash for short, deterministic suffix
return fmt.Sprintf("%x", hash)[:6]
}
// createSignature creates a unique signature for collision resistance (DEPRECATED - using deterministic now)
func (g *IDGenerator) createSignature(node *html.Node, filePath string) string { func (g *IDGenerator) createSignature(node *html.Node, filePath string) string {
// This method is kept for compatibility but not used in UUID-based generation // This method is kept for compatibility but not used in deterministic generation
return "" return ""
} }