···77import {
88 ComDeckbelcherCollectionList,
99 ComDeckbelcherDeckList,
1010+ ComDeckbelcherSocialComment,
1011 ComDeckbelcherSocialLike,
1212+ ComDeckbelcherSocialReply,
1113} from "./lexicons/index";
12141315const CONSTELLATION_BASE = "https://constellation.microcosm.blue";
···2325export const LIKE_NSID = getCollectionFromSchema(
2426 ComDeckbelcherSocialLike.mainSchema,
2527);
2828+export const COMMENT_NSID = getCollectionFromSchema(
2929+ ComDeckbelcherSocialComment.mainSchema,
3030+);
3131+export const REPLY_NSID = getCollectionFromSchema(
3232+ ComDeckbelcherSocialReply.mainSchema,
3333+);
26342735// Path constants for DeckBelcher collections
2836// Constellation includes $type in paths for union array elements
···3543// Like paths (subject is a union but NOT an array, so no [$type] notation)
3644export const LIKE_CARD_PATH = ".subject.ref.oracleUri";
3745export const LIKE_RECORD_PATH = ".subject.ref.uri";
4646+4747+// Comment paths (top-level comments, subject is union)
4848+// Query comments on a card (global card discussion)
4949+export const COMMENT_CARD_PATH = ".subject.ref.oracleUri";
5050+// Query comments on a record (deck, collection, etc)
5151+export const COMMENT_RECORD_PATH = ".subject.ref.uri";
5252+5353+// Reply paths (threaded replies)
5454+// Query by root to get all replies in a thread (for thread expansion)
5555+export const REPLY_ROOT_PATH = ".root.uri";
5656+// Query by parent to get direct replies to a specific comment/reply
5757+export const REPLY_PARENT_PATH = ".parent.uri";
38583959export interface BacklinkRecord {
4060 did: string;
+116
src/lib/constellation-queries.ts
···1414 COLLECTION_LIST_CARD_PATH,
1515 COLLECTION_LIST_DECK_PATH,
1616 COLLECTION_LIST_NSID,
1717+ COMMENT_CARD_PATH,
1818+ COMMENT_NSID,
1919+ COMMENT_RECORD_PATH,
1720 DECK_LIST_CARD_PATH,
1821 DECK_LIST_NSID,
1922 getBacklinks,
···2124 LIKE_CARD_PATH,
2225 LIKE_NSID,
2326 LIKE_RECORD_PATH,
2727+ REPLY_NSID,
2828+ REPLY_PARENT_PATH,
2929+ REPLY_ROOT_PATH,
2430} from "./constellation-client";
2531import type { OracleUri } from "./scryfall-types";
2632import { useAuth } from "./useAuth";
···394400 : null,
395401 ] as const);
396402}
403403+404404+// ============================================================================
405405+// Comment Queries
406406+// ============================================================================
407407+408408+function getCommentPathForItemType(itemType: SocialItemType): string {
409409+ return itemType === "card" ? COMMENT_CARD_PATH : COMMENT_RECORD_PATH;
410410+}
411411+412412+/**
413413+ * Query options for getting top-level comment count for an item
414414+ */
415415+export function itemCommentCountQueryOptions<T extends SocialItemType>(
416416+ itemUri: T extends "card" ? CardItemUri : DeckItemUri,
417417+ itemType: T,
418418+) {
419419+ return queryOptions({
420420+ queryKey: ["constellation", "commentCount", itemUri] as const,
421421+ queryFn: async (): Promise<number> => {
422422+ const result = await getLinksCount({
423423+ target: itemUri,
424424+ collection: COMMENT_NSID,
425425+ path: getCommentPathForItemType(itemType),
426426+ });
427427+428428+ if (!result.success) {
429429+ throw result.error;
430430+ }
431431+432432+ return result.data.total;
433433+ },
434434+ staleTime: 60 * 1000,
435435+ });
436436+}
437437+438438+/**
439439+ * Infinite query for top-level comments on an item (card or deck/collection)
440440+ */
441441+export function itemCommentsQueryOptions<T extends SocialItemType>(
442442+ itemUri: T extends "card" ? CardItemUri : DeckItemUri,
443443+ itemType: T,
444444+) {
445445+ return infiniteQueryOptions({
446446+ queryKey: ["constellation", "comments", itemUri] as const,
447447+ queryFn: async ({ pageParam }) => {
448448+ const result = await getBacklinks({
449449+ subject: itemUri,
450450+ source: buildSource(COMMENT_NSID, getCommentPathForItemType(itemType)),
451451+ limit: 25,
452452+ cursor: pageParam,
453453+ });
454454+ if (!result.success) throw result.error;
455455+ return result.data;
456456+ },
457457+ initialPageParam: undefined as string | undefined,
458458+ getNextPageParam: (lastPage) => lastPage.cursor ?? undefined,
459459+ staleTime: 60 * 1000,
460460+ });
461461+}
462462+463463+// ============================================================================
464464+// Reply Queries (for threading)
465465+// ============================================================================
466466+467467+/**
468468+ * Infinite query for all replies in a thread (by root comment URI).
469469+ * Use this to expand an entire thread - returns all replies regardless of depth.
470470+ */
471471+export function threadRepliesQueryOptions(rootCommentUri: string) {
472472+ return infiniteQueryOptions({
473473+ queryKey: ["constellation", "threadReplies", rootCommentUri] as const,
474474+ queryFn: async ({ pageParam }) => {
475475+ const result = await getBacklinks({
476476+ subject: rootCommentUri,
477477+ source: buildSource(REPLY_NSID, REPLY_ROOT_PATH),
478478+ limit: 50,
479479+ cursor: pageParam,
480480+ });
481481+ if (!result.success) throw result.error;
482482+ return result.data;
483483+ },
484484+ initialPageParam: undefined as string | undefined,
485485+ getNextPageParam: (lastPage) => lastPage.cursor ?? undefined,
486486+ staleTime: 60 * 1000,
487487+ });
488488+}
489489+490490+/**
491491+ * Query options for getting direct reply count for any comment or reply.
492492+ * Use this to show "X replies" on a specific comment.
493493+ */
494494+export function directReplyCountQueryOptions(commentOrReplyUri: string) {
495495+ return queryOptions({
496496+ queryKey: ["constellation", "directReplyCount", commentOrReplyUri] as const,
497497+ queryFn: async (): Promise<number> => {
498498+ const result = await getLinksCount({
499499+ target: commentOrReplyUri,
500500+ collection: REPLY_NSID,
501501+ path: REPLY_PARENT_PATH,
502502+ });
503503+504504+ if (!result.success) {
505505+ throw result.error;
506506+ }
507507+508508+ return result.data.total;
509509+ },
510510+ staleTime: 60 * 1000,
511511+ });
512512+}
+2
src/lib/lexicons/index.ts
···55export * as ComDeckbelcherDefs from "./types/com/deckbelcher/defs.js";
66export * as ComDeckbelcherRichtext from "./types/com/deckbelcher/richtext.js";
77export * as ComDeckbelcherRichtextFacet from "./types/com/deckbelcher/richtext/facet.js";
88+export * as ComDeckbelcherSocialComment from "./types/com/deckbelcher/social/comment.js";
89export * as ComDeckbelcherSocialLike from "./types/com/deckbelcher/social/like.js";
1010+export * as ComDeckbelcherSocialReply from "./types/com/deckbelcher/social/reply.js";