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:
@@ -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 ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user