fix: enrich activity log entries with item titles and collection info (#1)

The activity list endpoint returned raw entries without item context,
causing the activity page to show bare "Created"/"Updated" verbs.
The dashboard already enriched entries via GetItem() lookups — now the
activity handler does the same, and the frontend reads top-level fields
with metadata fallback.
This commit is contained in:
xarmian
2026-03-28 10:51:36 -04:00
committed by GitHub
parent fbfaaa8c84
commit 52c8348361
4 changed files with 43 additions and 10 deletions
+5
View File
@@ -16,6 +16,11 @@ type Activity struct {
Source string `json:"source"`
Metadata string `json:"metadata,omitempty"` // JSON
CreatedAt time.Time `json:"created_at"`
// Enrichment fields — populated by handlers, not stored in DB
ItemTitle string `json:"item_title,omitempty"`
ItemSlug string `json:"item_slug,omitempty"`
CollectionSlug string `json:"collection_slug,omitempty"`
}
type ActivityListParams struct {
+23
View File
@@ -39,6 +39,9 @@ func (s *Server) handleListWorkspaceActivity(w http.ResponseWriter, r *http.Requ
activities = []models.Activity{}
}
// Enrich activities with item titles and collection info
s.enrichActivities(activities)
writeJSON(w, http.StatusOK, activities)
}
@@ -73,5 +76,25 @@ func (s *Server) handleListDocumentActivity(w http.ResponseWriter, r *http.Reque
activities = []models.Activity{}
}
// Enrich activities with item titles and collection info
s.enrichActivities(activities)
writeJSON(w, http.StatusOK, activities)
}
// enrichActivities populates ItemTitle, ItemSlug, and CollectionSlug
// on each activity by looking up the referenced item.
func (s *Server) enrichActivities(activities []models.Activity) {
for i := range activities {
if activities[i].DocumentID == "" {
continue
}
item, err := s.store.GetItem(activities[i].DocumentID)
if err != nil || item == nil {
continue
}
activities[i].ItemTitle = item.Title
activities[i].ItemSlug = item.Slug
activities[i].CollectionSlug = item.CollectionSlug
}
}
+3
View File
@@ -238,6 +238,9 @@ export interface Activity {
source: string;
metadata: string;
created_at: string;
item_title?: string;
item_slug?: string;
collection_slug?: string;
}
// ─── Dashboard ───────────────────────────────────────────────────────────────
@@ -88,10 +88,11 @@
}
}
// Client-side collection filter (metadata contains item_collection info)
// Client-side collection filter using enriched top-level field or metadata fallback
let filteredActivities = $derived.by(() => {
if (!filterCollection) return activities;
return activities.filter((a) => {
if (a.collection_slug) return a.collection_slug === filterCollection;
try {
const meta = JSON.parse(a.metadata);
return meta.collection_slug === filterCollection || meta.collection === filterCollection;
@@ -299,6 +300,9 @@
<div class="date-entries">
{#each group.items as activity (activity.id)}
{@const meta = parseMeta(activity.metadata)}
{@const itemTitle = activity.item_title || meta.item_title || meta.title}
{@const itemSlug = activity.item_slug || meta.item_slug}
{@const collSlug = activity.collection_slug || meta.collection_slug}
{@const src = getSourceLabel(activity.source, activity.actor)}
<div class="entry {borderClass(activity.source, activity.actor)}">
<span
@@ -310,18 +314,16 @@
<div class="entry-content">
<div class="entry-main">
<span class="entry-verb">{activityVerb(activity.action)}</span>
{#if meta.item_title && meta.item_slug && meta.collection_slug}
{#if itemTitle && itemSlug && collSlug}
<a
href="/{wsSlug}/{meta.collection_slug}/{meta.item_slug}"
class="entry-item-link">{meta.item_title}</a
href="/{wsSlug}/{collSlug}/{itemSlug}"
class="entry-item-link">{itemTitle}</a
>
{:else if meta.item_title}
<span class="entry-item-name">{meta.item_title}</span>
{:else if meta.title}
<span class="entry-item-name">{meta.title}</span>
{:else if itemTitle}
<span class="entry-item-name">{itemTitle}</span>
{/if}
{#if meta.collection_slug}
<span class="entry-collection">{meta.collection_slug}</span>
{#if collSlug}
<span class="entry-collection">{collSlug}</span>
{/if}
</div>
{#if meta.changes}