diff --git a/web/src/routes/[handle]/[repo]/issues/[aturi]/+page.svelte b/web/src/routes/[handle]/[repo]/issues/[aturi]/+page.svelte
index 521e7bac..03fa33c6 100644
--- a/web/src/routes/[handle]/[repo]/issues/[aturi]/+page.svelte
+++ b/web/src/routes/[handle]/[repo]/issues/[aturi]/+page.svelte
@@ -5,6 +5,7 @@
import IssueLabelPanel from "$lib/components/repo/issues/IssueLabelPanel.svelte";
import CommentList from "$lib/components/comment/CommentList.svelte";
import CommentBox from "$lib/components/comment/CommentBox.svelte";
+ import Reactions from "$lib/components/reaction/Reactions.svelte";
import SignupPrompt from "$lib/components/ui/SignupPrompt.svelte";
import TabPanel from "$lib/components/ui/TabPanel.svelte";
import IssueForm from "$lib/components/repo/issues/IssueForm.svelte";
@@ -143,6 +144,12 @@
actions={isAuthor ? issueActions : undefined}
/>
+
{#if deleteError}
diff --git a/web/src/routes/[handle]/[repo]/issues/[aturi]/+page.ts b/web/src/routes/[handle]/[repo]/issues/[aturi]/+page.ts
index e7fea91b..dcf87dff 100644
--- a/web/src/routes/[handle]/[repo]/issues/[aturi]/+page.ts
+++ b/web/src/routes/[handle]/[repo]/issues/[aturi]/+page.ts
@@ -1,10 +1,18 @@
import { error } from "@sveltejs/kit";
import { createBobbinClient } from "$lib/api/client";
import { IdentityCache } from "$lib/api/identity";
-import { getIssue, listComments, listIssueStates } from "$lib/api/records";
+import {
+ getIssue,
+ listComments,
+ listIssueStates,
+ listReactions,
+ type RecordView,
+ type ReactionRecord
+} from "$lib/api/records";
import { didFromUri, rkeyFromUri } from "$lib/api/uri";
import { renderMarkup } from "$lib/markup";
import { buildCommentThreads, type ThreadInput } from "$lib/components/comment/comments";
+import { buildReactions, type ReactionGroup } from "$lib/components/reaction/reactions";
import type { PageLoad } from "./$types";
export const load: PageLoad = async (event) => {
@@ -34,11 +42,42 @@ export const load: PageLoad = async (event) => {
const latest = states?.items[0]?.value.state;
const state = latest?.endsWith(".closed") ? ("closed" as const) : ("open" as const);
-
const body = record.value.body ?? "";
const bodyHtml = body ? await renderMarkup(body, markupOpts) : null;
-
+ const viewerDid = parent.auth?.did;
const commentItems = comments?.items ?? [];
+
+ // reactions: one listReactions per subject (the issue itself + each comment).
+ // bobbin has no batch-by-subject query and enrich can't break counts down by
+ // emoji, so we fan out and group client-side. empty subjects come back empty.
+ const subjects = [record.uri, ...commentItems.map((item) => item.uri)];
+ const reactionPages = await Promise.all(
+ subjects.map((subject) =>
+ listReactions(ctx, subject, { order: "asc", limit: 100 }).catch(() => null)
+ )
+ );
+ const reactionsBySubject = new Map[]>();
+ subjects.forEach((subject, i) => reactionsBySubject.set(subject, reactionPages[i]?.items ?? []));
+
+ // resolve every reactor's handle once for the whole page (IdentityCache dedups)
+ const reactorDids = new Set();
+ for (const items of reactionsBySubject.values()) {
+ for (const item of items) reactorDids.add(didFromUri(item.uri));
+ }
+ const reactorHandles = new Map();
+ await Promise.all(
+ [...reactorDids].map(async (did) => {
+ const doc = await identities.resolve(did).catch(() => null);
+ reactorHandles.set(did, doc?.handle ?? did);
+ })
+ );
+ const reactionsFor = (subject: string): ReactionGroup[] =>
+ buildReactions(
+ reactionsBySubject.get(subject) ?? [],
+ viewerDid,
+ (did) => reactorHandles.get(did) ?? did
+ );
+
const threadInputs: ThreadInput[] = await Promise.all(
commentItems.map(async (item): Promise => {
const commentDid = didFromUri(item.uri);
@@ -56,7 +95,8 @@ export const load: PageLoad = async (event) => {
authorHandle: doc?.handle ?? commentDid,
createdAt: item.value.createdAt,
body: commentBody,
- bodyHtml: commentBodyHtml
+ bodyHtml: commentBodyHtml,
+ reactions: reactionsFor(item.uri)
},
replyTo: item.value.replyTo?.uri ?? null,
replyToCid: item.value.replyTo?.cid
@@ -75,7 +115,8 @@ export const load: PageLoad = async (event) => {
state,
authorDid,
authorHandle: author?.handle ?? authorDid,
- createdAt: record.value.createdAt
+ createdAt: record.value.createdAt,
+ reactions: reactionsFor(record.uri)
},
comments: buildCommentThreads(threadInputs),
markup: markupOpts