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:
2026-07-12 09:16:03 +02:00
parent c8b479d565
commit e3cbaee534
2 changed files with 12 additions and 7 deletions

View File

@@ -8,6 +8,7 @@ import (
"strconv"
"github.com/dtoro/oikos/internal/httpapi/gen"
"github.com/google/uuid"
)
// 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)
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_headline('english', ke.content, plainto_tsquery('english', $1),
'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{}
for rows.Next() {
var id uuid.UUID
var slug, eType, title, source string
var tags []string
var rank float32
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
}
@@ -149,6 +151,7 @@ func (s *Server) SearchKnowledge(ctx context.Context, request gen.SearchKnowledg
}
items = append(items, gen.KnowledgeHit{
Id: id,
Slug: slug,
Title: title,
Type: hitType,
@@ -172,7 +175,7 @@ func (s *Server) GetEntityKnowledge(ctx context.Context, request gen.GetEntityKn
entitySlug := request.EntityId
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
JOIN entities e ON e.id = ke.entity_id
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.type IN ('documents', 'about')
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
JOIN entities e ON e.id = ke.entity_id
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
WHERE r.valid_to IS NULL
AND r.type = 'procedure-for'
ORDER BY 1`,
ORDER BY 2`,
entitySlug)
if err != nil {
return nil, err
@@ -200,10 +203,11 @@ func (s *Server) GetEntityKnowledge(ctx context.Context, request gen.GetEntityKn
items := []gen.KnowledgeHit{}
for rows.Next() {
var id uuid.UUID
var slug, eType, title, source 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
}
@@ -216,6 +220,7 @@ func (s *Server) GetEntityKnowledge(ctx context.Context, request gen.GetEntityKn
}
items = append(items, gen.KnowledgeHit{
Id: id,
Slug: slug,
Title: title,
Type: hitType,

View File

@@ -115,7 +115,7 @@
<p class="text-sm text-muted-foreground">{results.length} result{results.length === 1 ? '' : 's'} for "{query}"</p>
<ScrollArea class="flex-1">
<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.Header>
<div class="flex items-center gap-2">