From 2f8f214f503badb56b9c93416bcca4101f058b9e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Jan 2026 22:15:56 +0000 Subject: [PATCH] fix: improve spell UI - scrolling, execution control, and parameter markers Three improvements to spell UI based on user feedback: 1. Fix tabs scroll issue - Change TabsContent overflow-hidden to overflow-auto in ProfileViewer, EventDetailViewer, and RelayViewer. This allows scrolling to content and clicking tabs when the visible area is small. 2. Prevent execution of parameterized spells without defaults - Add validation logic to check if spell has parameters and default values. Non-executable spells show as disabled (muted text, cursor-not-allowed, opacity-60) with helpful tooltips explaining where to use them (profile/event/relay viewer). 3. Add visual parameter type markers - Display Badge components showing the parameter type ($pubkey, $event, $relay) with User icon for $pubkey type. Applied to SpellRenderer (compact and detail views) and SpellsViewer cards. Files modified: - ProfileViewer.tsx - tabs scroll fix - EventDetailViewer.tsx - tabs scroll fix - RelayViewer.tsx - tabs scroll fix - SpellRenderer.tsx - parameter validation and badges - SpellsViewer.tsx - parameter validation and badges --- src/components/EventDetailViewer.tsx | 2 +- src/components/ProfileViewer.tsx | 2 +- src/components/RelayViewer.tsx | 2 +- src/components/SpellsViewer.tsx | 41 +++++-- src/components/nostr/kinds/SpellRenderer.tsx | 122 ++++++++++++++----- 5 files changed, 129 insertions(+), 40 deletions(-) diff --git a/src/components/EventDetailViewer.tsx b/src/components/EventDetailViewer.tsx index dcc4c61..d319bff 100644 --- a/src/components/EventDetailViewer.tsx +++ b/src/components/EventDetailViewer.tsx @@ -169,7 +169,7 @@ function SpellTabContent({ return ( {!appliedFilter ? (
diff --git a/src/components/ProfileViewer.tsx b/src/components/ProfileViewer.tsx index e5c12d9..6b8a77a 100644 --- a/src/components/ProfileViewer.tsx +++ b/src/components/ProfileViewer.tsx @@ -257,7 +257,7 @@ function SpellTabContent({ return ( {!appliedFilter ? (
diff --git a/src/components/RelayViewer.tsx b/src/components/RelayViewer.tsx index 59a3e93..3863780 100644 --- a/src/components/RelayViewer.tsx +++ b/src/components/RelayViewer.tsx @@ -139,7 +139,7 @@ function SpellTabContent({ return ( {!appliedFilter ? (
diff --git a/src/components/SpellsViewer.tsx b/src/components/SpellsViewer.tsx index 0c32096..61302e2 100644 --- a/src/components/SpellsViewer.tsx +++ b/src/components/SpellsViewer.tsx @@ -11,6 +11,7 @@ import { Archive, WandSparkles as Wand, BookUp, + User, } from "lucide-react"; import { useLiveQuery } from "dexie-react-hooks"; import db from "@/services/db"; @@ -63,6 +64,12 @@ function SpellCard({ spell, onDelete, onPublish }: SpellCardProps) { } }, [spell.command]); + // Check if spell has parameters but no defaults + const hasParameters = !!spell.parameterType; + const hasDefault = + spell.parameterDefault && spell.parameterDefault.length > 0; + const canExecute = !hasParameters || hasDefault; + const handlePublish = async () => { setIsPublishing(true); try { @@ -112,6 +119,17 @@ function SpellCard({ spell, onDelete, onPublish }: SpellCardProps) { > {displayName} + {hasParameters && ( + + {spell.parameterType === "$pubkey" && ( + + )} + {spell.parameterType} + + )}
{spell.deletedAt ? ( @@ -139,13 +157,22 @@ function SpellCard({ spell, onDelete, onPublish }: SpellCardProps) {
- - {spell.command} - + {canExecute ? ( + + {spell.command} + + ) : ( +
+ {spell.command} +
+ )}
{kinds.map((kind) => ( 0; + const canExecute = !hasParameters || hasDefault; + return (
- {/* Title */} - {spell.name && ( - - {spell.name} - - )} + {/* Title with parameter marker */} +
+ {spell.name && ( + + {spell.name} + + )} + {hasParameters && spell.parameter && ( + + {spell.parameter.type === "$pubkey" && ( + + )} + {spell.parameter.type} + + )} +
{/* Description */} {spell.description && ( @@ -172,13 +188,22 @@ export function SpellRenderer({ event }: BaseEventProps) {

)} - {/* Command Preview */} - - {spell.command} - + {/* Command Preview - only executable if has defaults or no parameters */} + {canExecute ? ( + + {spell.command} + + ) : ( +
+ {spell.command} +
+ )} {/* Kind Badges */} {spell.filter.kinds && spell.filter.kinds.length > 0 && ( @@ -220,6 +245,12 @@ export function SpellDetailRenderer({ event }: BaseEventProps) { try { const spell = decodeSpell(event as SpellEvent); + // Check if spell has parameters but no defaults + const hasParameters = !!spell.parameter; + const hasDefault = + spell.parameter?.default && spell.parameter.default.length > 0; + const canExecute = !hasParameters || hasDefault; + // Create a display filter that includes since/until even in relative format const displayFilter = { ...spell.filter }; @@ -237,14 +268,27 @@ export function SpellDetailRenderer({ event }: BaseEventProps) { return (
- {spell.name && ( - - {spell.name} - - )} +
+ {spell.name && ( + + {spell.name} + + )} + {hasParameters && spell.parameter && ( + + {spell.parameter.type === "$pubkey" && ( + + )} + {spell.parameter.type} + {hasDefault && ( + (has default) + )} + + )} +
{spell.description && (

{spell.description}

)} @@ -254,12 +298,30 @@ export function SpellDetailRenderer({ event }: BaseEventProps) {

Command

- - {spell.command} - + {canExecute ? ( + + {spell.command} + + ) : ( +
+
+ {spell.command} +
+

+ 💡 This spell requires a {spell.parameter?.type} to run. Use it + from a{" "} + {spell.parameter?.type === "$pubkey" + ? "profile" + : spell.parameter?.type === "$event" + ? "event" + : "relay"}{" "} + viewer to execute. +

+
+ )}
{spell.filter.kinds && spell.filter.kinds.length > 0 && (