fix: systematic element matching bug in enhancement pipeline
- Problem: Element ID collisions between similar elements (logo h1 vs hero h1) causing content to be injected into wrong elements - Root cause: Enhancer used naive tag+class matching instead of parser's sophisticated semantic analysis for element identification Systematic solution: - Enhanced parser architecture with exported utilities (GetClasses, ContainsClass) - Added FindElementInDocument() with content-based semantic matching - Replaced naive findAndInjectNodes() with parser-based element matching - Removed code duplication between parser and enhancer packages Backend improvements: - Moved ID generation to backend for single source of truth - Added ElementContext struct for frontend-backend communication - Updated API handlers to support context-based content ID generation Frontend improvements: - Enhanced getElementMetadata() to extract semantic context - Updated save flow to handle both enhanced and non-enhanced elements - Improved API client to use backend-generated content IDs Result: - Unique content IDs: navbar-logo-200530 vs hero-title-a1de7b - Precise element matching using content validation - Single source of truth for DOM utilities in parser package - Eliminated 40+ lines of duplicate code while fixing core bug
This commit is contained in:
@@ -17,6 +17,8 @@ import (
|
||||
"github.com/insertr/insertr/internal/db"
|
||||
"github.com/insertr/insertr/internal/db/postgresql"
|
||||
"github.com/insertr/insertr/internal/db/sqlite"
|
||||
"github.com/insertr/insertr/internal/parser"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// ContentHandler handles all content-related HTTP requests
|
||||
@@ -232,6 +234,16 @@ func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request) {
|
||||
siteID = "default" // final fallback
|
||||
}
|
||||
|
||||
// Determine content ID - use provided ID or generate from element context
|
||||
contentID := req.ID
|
||||
if contentID == "" {
|
||||
if req.ElementContext == nil {
|
||||
http.Error(w, "Either ID or element_context required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
contentID = h.generateContentID(req.ElementContext)
|
||||
}
|
||||
|
||||
// Extract user from request using authentication service
|
||||
userInfo, authErr := h.authService.ExtractUserFromRequest(r)
|
||||
if authErr != nil {
|
||||
@@ -246,7 +258,7 @@ func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request) {
|
||||
switch h.database.GetDBType() {
|
||||
case "sqlite3":
|
||||
content, err = h.database.GetSQLiteQueries().CreateContent(context.Background(), sqlite.CreateContentParams{
|
||||
ID: req.ID,
|
||||
ID: contentID,
|
||||
SiteID: siteID,
|
||||
Value: req.Value,
|
||||
Type: req.Type,
|
||||
@@ -254,7 +266,7 @@ func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
case "postgresql":
|
||||
content, err = h.database.GetPostgreSQLQueries().CreateContent(context.Background(), postgresql.CreateContentParams{
|
||||
ID: req.ID,
|
||||
ID: contentID,
|
||||
SiteID: siteID,
|
||||
Value: req.Value,
|
||||
Type: req.Type,
|
||||
@@ -728,3 +740,41 @@ func (h *ContentHandler) versionMatches(version interface{}, contentID, siteID s
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// generateContentID creates a content ID from element context using the parser
|
||||
func (h *ContentHandler) generateContentID(ctx *ElementContext) string {
|
||||
// Create virtual node for existing parser ID generation
|
||||
virtualNode := &html.Node{
|
||||
Type: html.ElementNode,
|
||||
Data: ctx.Tag,
|
||||
Attr: []html.Attribute{
|
||||
{Key: "class", Val: strings.Join(ctx.Classes, " ")},
|
||||
},
|
||||
}
|
||||
|
||||
// Add parent context as a virtual parent node if provided
|
||||
if ctx.ParentContext != "" && ctx.ParentContext != "content" {
|
||||
parentNode := &html.Node{
|
||||
Type: html.ElementNode,
|
||||
Data: "section",
|
||||
Attr: []html.Attribute{
|
||||
{Key: "class", Val: ctx.ParentContext},
|
||||
},
|
||||
}
|
||||
parentNode.AppendChild(virtualNode)
|
||||
virtualNode.Parent = parentNode
|
||||
}
|
||||
|
||||
// Add text content for hash generation
|
||||
if ctx.OriginalContent != "" {
|
||||
textNode := &html.Node{
|
||||
Type: html.TextNode,
|
||||
Data: ctx.OriginalContent,
|
||||
}
|
||||
virtualNode.AppendChild(textNode)
|
||||
}
|
||||
|
||||
// Use existing parser ID generator
|
||||
idGenerator := parser.NewIDGenerator()
|
||||
return idGenerator.Generate(virtualNode)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user