diff --git a/src/actions/publish-spellbook.ts b/src/actions/publish-spellbook.ts
index 849f60d..0eeb01b 100644
--- a/src/actions/publish-spellbook.ts
+++ b/src/actions/publish-spellbook.ts
@@ -35,7 +35,9 @@ export interface PublishSpellbookOptions {
* @example
* ```typescript
* // Publish via ActionHub with proper side-effect handling
- * for await (const event of hub.exec(PublishSpellbook, options)) {
+ * const event = await lastValueFrom(hub.exec(PublishSpellbook, options));
+ * if (event) {
+ * await publishEvent(event);
* // Only mark as published AFTER successful relay publish
* await markSpellbookPublished(localId, event as SpellbookEvent);
* }
diff --git a/src/components/ReqViewer.tsx b/src/components/ReqViewer.tsx
index 69a0255..ed78813 100644
--- a/src/components/ReqViewer.tsx
+++ b/src/components/ReqViewer.tsx
@@ -71,7 +71,6 @@ import { SyntaxHighlight } from "@/components/SyntaxHighlight";
import { getConnectionIcon, getAuthIcon } from "@/lib/relay-status-utils";
import { resolveFilterAliases, getTagValues } from "@/lib/nostr-utils";
import { useNostrEvent } from "@/hooks/useNostrEvent";
-import { cn } from "@/lib/utils";
import { MemoizedCompactEventRow } from "./nostr/CompactEventRow";
import type { ViewMode } from "@/lib/req-parser";
diff --git a/src/components/SaveSpellbookDialog.tsx b/src/components/SaveSpellbookDialog.tsx
index 73b5afd..7035d15 100644
--- a/src/components/SaveSpellbookDialog.tsx
+++ b/src/components/SaveSpellbookDialog.tsx
@@ -19,10 +19,11 @@ import {
markSpellbookPublished,
} from "@/services/spellbook-storage";
import { PublishSpellbook } from "@/actions/publish-spellbook";
-import { hub } from "@/services/hub";
+import { hub, publishEvent } from "@/services/hub";
import { createSpellbook } from "@/lib/spellbook-manager";
import { Loader2, Save, Send } from "lucide-react";
import type { SpellbookEvent } from "@/types/spell";
+import { lastValueFrom } from "rxjs";
interface SaveSpellbookDialogProps {
open: boolean;
@@ -112,17 +113,22 @@ export function SaveSpellbookDialog({
// 4. Optionally publish
if (shouldPublish) {
const localId = existingSpellbook?.localId || localSpellbook.id;
- // Use hub.exec() to get the event and handle side effects after successful publish
- for await (const event of hub.exec(PublishSpellbook, {
- state,
- title,
- description,
- workspaceIds: selectedWorkspaces,
- content: localSpellbook.content,
- })) {
+ const event = await lastValueFrom(
+ hub.exec(PublishSpellbook, {
+ state,
+ title,
+ description,
+ workspaceIds: selectedWorkspaces,
+ content: localSpellbook.content,
+ }),
+ );
+
+ if (event) {
+ await publishEvent(event);
// Only mark as published AFTER successful relay publish
await markSpellbookPublished(localId, event as SpellbookEvent);
}
+
toast.success(
isUpdateMode
? "Spellbook updated and published to Nostr"
diff --git a/src/components/SpellbookDropdown.tsx b/src/components/SpellbookDropdown.tsx
index da2c29a..0394a43 100644
--- a/src/components/SpellbookDropdown.tsx
+++ b/src/components/SpellbookDropdown.tsx
@@ -34,16 +34,19 @@ import {
import { cn } from "@/lib/utils";
import { SaveSpellbookDialog } from "./SaveSpellbookDialog";
import { toast } from "sonner";
+import { UserName } from "./nostr/UserName";
/**
* Status indicator component for spellbook state
*/
function SpellbookStatus({
+ owner,
isOwner,
isPublished,
isLocal,
className,
}: {
+ owner?: string;
isOwner: boolean;
isPublished?: boolean;
isLocal?: boolean;
@@ -62,12 +65,12 @@ function SpellbookStatus({
you
- ) : (
+ ) : owner ? (
- other
+
- )}
+ ) : null}
•
{/* Storage status */}
{isPublished ? (
@@ -187,6 +190,7 @@ export function SpellbookDropdown() {
);
}, [localSpellbooks, networkEvents, activeAccount]);
+ const owner = activeSpellbook?.pubkey;
// Derived states for clearer UX
const isOwner = useMemo(() => {
if (!activeSpellbook) return false;
@@ -311,6 +315,7 @@ export function SpellbookDropdown() {
{activeSpellbook.title}
) : (
)}
diff --git a/src/components/SpellbooksViewer.tsx b/src/components/SpellbooksViewer.tsx
index 04ef4cb..0bc9bb8 100644
--- a/src/components/SpellbooksViewer.tsx
+++ b/src/components/SpellbooksViewer.tsx
@@ -35,7 +35,7 @@ import {
import type { LocalSpellbook } from "@/services/db";
import { PublishSpellbook } from "@/actions/publish-spellbook";
import { DeleteEventAction } from "@/actions/delete-event";
-import { hub } from "@/services/hub";
+import { hub, publishEvent } from "@/services/hub";
import { useGrimoire } from "@/core/state";
import { cn } from "@/lib/utils";
import { useReqTimeline } from "@/hooks/useReqTimeline";
@@ -44,6 +44,7 @@ import type { SpellbookEvent, ParsedSpellbook } from "@/types/spell";
import { SPELLBOOK_KIND } from "@/constants/kinds";
import { UserName } from "./nostr/UserName";
import { AGGREGATOR_RELAYS } from "@/services/loaders";
+import { lastValueFrom } from "rxjs";
interface SpellbookCardProps {
spellbook: LocalSpellbook;
@@ -380,16 +381,22 @@ export function SpellbooksViewer() {
const handlePublish = async (spellbook: LocalSpellbook) => {
try {
// Use hub.exec() to get the event and handle side effects after successful publish
- for await (const event of hub.exec(PublishSpellbook, {
- state,
- title: spellbook.title,
- description: spellbook.description,
- workspaceIds: Object.keys(spellbook.content.workspaces),
- content: spellbook.content,
- })) {
+ const event = await lastValueFrom(
+ hub.exec(PublishSpellbook, {
+ state,
+ title: spellbook.title,
+ description: spellbook.description,
+ workspaceIds: Object.keys(spellbook.content.workspaces),
+ content: spellbook.content,
+ }),
+ );
+
+ if (event) {
+ await publishEvent(event);
// Only mark as published AFTER successful relay publish
await markSpellbookPublished(spellbook.id, event as SpellbookEvent);
}
+
toast.success("Spellbook published");
} catch (error) {
toast.error(
diff --git a/src/components/nostr/compact/ReactionCompactPreview.tsx b/src/components/nostr/compact/ReactionCompactPreview.tsx
index 84a4293..c5307b1 100644
--- a/src/components/nostr/compact/ReactionCompactPreview.tsx
+++ b/src/components/nostr/compact/ReactionCompactPreview.tsx
@@ -2,7 +2,6 @@ import type { NostrEvent } from "@/types/nostr";
import { useMemo } from "react";
import { Heart, ThumbsUp, ThumbsDown, Flame, Smile } from "lucide-react";
import { useNostrEvent } from "@/hooks/useNostrEvent";
-import { getContentPreview } from "./index";
import { UserName } from "../UserName";
import { RichText } from "../RichText";
@@ -83,9 +82,6 @@ export function ReactionCompactPreview({ event }: { event: NostrEvent }) {
// Fetch the reacted event
const reactedEvent = useNostrEvent(eventPointer);
- // Get content preview
- const preview = reactedEvent ? getContentPreview(reactedEvent, 50) : null;
-
// Map common reactions to icons for compact display
const getReactionDisplay = (content: string) => {
switch (content) {
diff --git a/src/components/nostr/compact/RepostCompactPreview.tsx b/src/components/nostr/compact/RepostCompactPreview.tsx
index 3e12c0e..749857a 100644
--- a/src/components/nostr/compact/RepostCompactPreview.tsx
+++ b/src/components/nostr/compact/RepostCompactPreview.tsx
@@ -1,8 +1,6 @@
import type { NostrEvent } from "@/types/nostr";
import { useMemo } from "react";
-import { Repeat2 } from "lucide-react";
import { useNostrEvent } from "@/hooks/useNostrEvent";
-import { getContentPreview } from "./index";
import { UserName } from "../UserName";
import { RichText } from "../RichText";
diff --git a/src/components/nostr/kinds/RepositoryStateRenderer.tsx b/src/components/nostr/kinds/RepositoryStateRenderer.tsx
index 9aef180..2044bf5 100644
--- a/src/components/nostr/kinds/RepositoryStateRenderer.tsx
+++ b/src/components/nostr/kinds/RepositoryStateRenderer.tsx
@@ -10,7 +10,7 @@ import {
parseHeadBranch,
getRepositoryStateHead,
} from "@/lib/nip34-helpers";
-import { Label } from "@/components/ui/Label";
+import { Label } from "@/components/ui/label";
import { RepositoryLink } from "../RepositoryLink";
/**
diff --git a/src/components/ui/skeleton/InlineReplySkeleton.tsx b/src/components/ui/skeleton/InlineReplySkeleton.tsx
index b1a97d8..ea06200 100644
--- a/src/components/ui/skeleton/InlineReplySkeleton.tsx
+++ b/src/components/ui/skeleton/InlineReplySkeleton.tsx
@@ -3,7 +3,7 @@ import { Skeleton } from "./Skeleton";
export interface InlineReplySkeletonProps {
/** Icon to show on the left (Reply, MessageCircle, etc.) */
- icon: React.ReactNode;
+ icon?: React.ReactNode;
className?: string;
}
diff --git a/src/services/hub.ts b/src/services/hub.ts
index 9a07942..dcbc33c 100644
--- a/src/services/hub.ts
+++ b/src/services/hub.ts
@@ -13,7 +13,7 @@ import accountManager from "./accounts";
*
* @param event - The signed Nostr event to publish
*/
-async function publishEvent(event: NostrEvent): Promise {
+export async function publishEvent(event: NostrEvent): Promise {
// Try to get author's outbox relays from EventStore (kind 10002)
let relays = await relayListCache.getOutboxRelays(event.pubkey);