fix(knowledge): populate hit id in search results
Search hits and entity-knowledge hits never selected an id column, so
every KnowledgeHit.Id defaulted to the zero UUID. The frontend's keyed
{#each results as hit (hit.id)} then had all-duplicate keys, which
silently broke Svelte 5's if-block branch swap for the results panel —
search would set searched=true (Clear button appeared) but the view
never switched away from "Recently learned". Select e.id in both
queries and key the each block on hit.slug (guaranteed unique) instead.
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/dtoro/oikos/internal/httpapi/gen"
|
"github.com/dtoro/oikos/internal/httpapi/gen"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// serveRecentKnowledge backs the Knowledge page's "what the system knows / has
|
// serveRecentKnowledge backs the Knowledge page's "what the system knows / has
|
||||||
@@ -111,7 +112,7 @@ func (s *Server) SearchKnowledge(ctx context.Context, request gen.SearchKnowledg
|
|||||||
limit := clampLimit(request.Params.Limit)
|
limit := clampLimit(request.Params.Limit)
|
||||||
|
|
||||||
rows, err := s.pool.Query(ctx, `
|
rows, err := s.pool.Query(ctx, `
|
||||||
SELECT e.slug, COALESCE(et.name,''), ke.title, ke.source, ke.tags,
|
SELECT e.id, e.slug, COALESCE(et.name,''), ke.title, ke.source, ke.tags,
|
||||||
ts_rank(ke.search, plainto_tsquery('english', $1)) AS rank,
|
ts_rank(ke.search, plainto_tsquery('english', $1)) AS rank,
|
||||||
ts_headline('english', ke.content, plainto_tsquery('english', $1),
|
ts_headline('english', ke.content, plainto_tsquery('english', $1),
|
||||||
'MaxWords=40, MinWords=15, ShortWord=3, MaxFragments=3,
|
'MaxWords=40, MinWords=15, ShortWord=3, MaxFragments=3,
|
||||||
@@ -131,12 +132,13 @@ func (s *Server) SearchKnowledge(ctx context.Context, request gen.SearchKnowledg
|
|||||||
items := []gen.KnowledgeHit{}
|
items := []gen.KnowledgeHit{}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
|
var id uuid.UUID
|
||||||
var slug, eType, title, source string
|
var slug, eType, title, source string
|
||||||
var tags []string
|
var tags []string
|
||||||
var rank float32
|
var rank float32
|
||||||
var snippet *string
|
var snippet *string
|
||||||
|
|
||||||
if err := rows.Scan(&slug, &eType, &title, &source, &tags, &rank, &snippet); err != nil {
|
if err := rows.Scan(&id, &slug, &eType, &title, &source, &tags, &rank, &snippet); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +151,7 @@ func (s *Server) SearchKnowledge(ctx context.Context, request gen.SearchKnowledg
|
|||||||
}
|
}
|
||||||
|
|
||||||
items = append(items, gen.KnowledgeHit{
|
items = append(items, gen.KnowledgeHit{
|
||||||
|
Id: id,
|
||||||
Slug: slug,
|
Slug: slug,
|
||||||
Title: title,
|
Title: title,
|
||||||
Type: hitType,
|
Type: hitType,
|
||||||
@@ -172,7 +175,7 @@ func (s *Server) GetEntityKnowledge(ctx context.Context, request gen.GetEntityKn
|
|||||||
entitySlug := request.EntityId
|
entitySlug := request.EntityId
|
||||||
|
|
||||||
rows, err := s.pool.Query(ctx, `
|
rows, err := s.pool.Query(ctx, `
|
||||||
SELECT e.slug, COALESCE(et.name,''), ke.title, ke.source, ke.tags
|
SELECT e.id, e.slug, COALESCE(et.name,''), ke.title, ke.source, ke.tags
|
||||||
FROM knowledge_entities ke
|
FROM knowledge_entities ke
|
||||||
JOIN entities e ON e.id = ke.entity_id
|
JOIN entities e ON e.id = ke.entity_id
|
||||||
JOIN entity_types et ON et.name = e.type
|
JOIN entity_types et ON et.name = e.type
|
||||||
@@ -182,7 +185,7 @@ func (s *Server) GetEntityKnowledge(ctx context.Context, request gen.GetEntityKn
|
|||||||
AND r.valid_to IS NULL
|
AND r.valid_to IS NULL
|
||||||
AND r.type IN ('documents', 'about')
|
AND r.type IN ('documents', 'about')
|
||||||
UNION
|
UNION
|
||||||
SELECT e.slug, COALESCE(et.name,''), ke.title, ke.source, ke.tags
|
SELECT e.id, e.slug, COALESCE(et.name,''), ke.title, ke.source, ke.tags
|
||||||
FROM knowledge_entities ke
|
FROM knowledge_entities ke
|
||||||
JOIN entities e ON e.id = ke.entity_id
|
JOIN entities e ON e.id = ke.entity_id
|
||||||
JOIN entity_types et ON et.name = e.type
|
JOIN entity_types et ON et.name = e.type
|
||||||
@@ -191,7 +194,7 @@ func (s *Server) GetEntityKnowledge(ctx context.Context, request gen.GetEntityKn
|
|||||||
JOIN entities ent ON ent.type = target_type.name AND ent.slug = $1
|
JOIN entities ent ON ent.type = target_type.name AND ent.slug = $1
|
||||||
WHERE r.valid_to IS NULL
|
WHERE r.valid_to IS NULL
|
||||||
AND r.type = 'procedure-for'
|
AND r.type = 'procedure-for'
|
||||||
ORDER BY 1`,
|
ORDER BY 2`,
|
||||||
entitySlug)
|
entitySlug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -200,10 +203,11 @@ func (s *Server) GetEntityKnowledge(ctx context.Context, request gen.GetEntityKn
|
|||||||
|
|
||||||
items := []gen.KnowledgeHit{}
|
items := []gen.KnowledgeHit{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
|
var id uuid.UUID
|
||||||
var slug, eType, title, source string
|
var slug, eType, title, source string
|
||||||
var tags []string
|
var tags []string
|
||||||
|
|
||||||
if err := rows.Scan(&slug, &eType, &title, &source, &tags); err != nil {
|
if err := rows.Scan(&id, &slug, &eType, &title, &source, &tags); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +220,7 @@ func (s *Server) GetEntityKnowledge(ctx context.Context, request gen.GetEntityKn
|
|||||||
}
|
}
|
||||||
|
|
||||||
items = append(items, gen.KnowledgeHit{
|
items = append(items, gen.KnowledgeHit{
|
||||||
|
Id: id,
|
||||||
Slug: slug,
|
Slug: slug,
|
||||||
Title: title,
|
Title: title,
|
||||||
Type: hitType,
|
Type: hitType,
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
<p class="text-sm text-muted-foreground">{results.length} result{results.length === 1 ? '' : 's'} for "{query}"</p>
|
<p class="text-sm text-muted-foreground">{results.length} result{results.length === 1 ? '' : 's'} for "{query}"</p>
|
||||||
<ScrollArea class="flex-1">
|
<ScrollArea class="flex-1">
|
||||||
<div class="flex flex-col gap-3 pr-4">
|
<div class="flex flex-col gap-3 pr-4">
|
||||||
{#each results as hit (hit.id)}
|
{#each results as hit (hit.slug)}
|
||||||
<Card.Root class="transition-colors hover:bg-muted/50">
|
<Card.Root class="transition-colors hover:bg-muted/50">
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user