- Extract tool definitions and message types to src/lib/aiChatTools.ts
- Extract system prompt to src/lib/aiChatSystemPrompt.ts
- Extract tool executor hook to src/hooks/useAIChatTools.ts
- Extract session logic to src/hooks/useAIChatSession.ts
- Page reduced from 1086 to 321 lines (render + sub-components only)
- Fix layout: remove overflow-hidden and nested scroll, align with main branch
The main element was using h-full min-h-0 which doesn't work because
the parent wrapper div has no fixed height. Restore the original
ai-chat-height class which uses calc(100dvh - ...) for an absolute
height constraint. Keep overflow-hidden to prevent outer page scroll.
CreditsBadge now sits left of the model dropdown in the controls row
instead of in the PageHeader, so it stays on the same line on small
screens. Added overflow-hidden to the main container so only the
ScrollArea chat pane scrolls, eliminating the extra space below the
input that the user could scroll to reveal.
search_users resolves names to Nostr pubkeys with a two-phase search:
contacts first (fetch kind:3, then kind:0 metadata matching the query),
then NIP-50 relay search as fallback. Returns up to 5 matches with
pubkey, name, display_name, nip05, and about.
The tool call loop now supports multiple rounds (up to 10): after each
tool execution, the follow-up request includes tools so the AI can
chain calls (e.g. search_users -> create_spell). The loop breaks when
the AI responds with plain text instead of another tool call.
Add getCredits() to useShakespeare hook (GET /v1/credits with NIP-98
auth). CreditsBadge component uses TanStack Query to fetch and
auto-refresh the balance every 30s, formatted as USD currency. Shown
in the PageHeader next to the AI Chat title.
The star button now compares sidebar items by decoded event ID instead
of nevent string equality, so it correctly detects spells added via
different paths (three-dot menu vs star button vs paste). When removing,
it uses the matched sidebar entry's actual string.
Also adds toast notifications for add/remove, and replaces the ghost
button hover with a star-fill hover effect using Tailwind group.
neventEncode({ id, author }) omits the kind, so when the sidebar
decodes it and the relay fetch hasn't completed (or fails), the kind
falls back to 1 (text note) showing the wrong icon. Including the
kind in the encoding lets the sidebar resolve the correct icon and
path immediately from the decode without waiting for the fetch.
Fixed in both SpellRunPage (star button) and NoteMoreMenu (three-dot
menu 'Add to sidebar').
Show a star icon in the spell summary card that adds/removes the
spell from the sidebar. Filled primary star when in sidebar, muted
outline when not. Uses the same nostr: URI pattern as NoteMoreMenu.
Messages survive page navigation and browser refresh. The session is
loaded from localStorage on mount and saved on every change. The clear
button removes the stored session. Timestamps are serialized as ISO
strings and NostrEvent objects serialize as plain JSON.
Teach Dork how to translate natural language into spell parameters:
runtime variables ($me, $contacts), relative timestamps, common
event kinds, tag filters vs NIP-50 search, and translation examples.
References NIP-A7 spell spec.
Add a generic nostrEvent field to DisplayMessage so any event published
by a tool can be rendered in the chat. Uses NoteCard with compact mode,
which automatically dispatches to the right content component for the
event kind (SpellContent for kind 777, etc.). Extensible to future
tools that publish other event kinds.
Build the tool execution handler for create_spell: converts structured
parameters into kind:777 tags, generates an ephemeral keypair (like
Shakespeare's NostrPublishEventsTool), signs and publishes the spell
event plus a minimal kind:0 profile to relays. The executor returns
the published NostrEvent on the DisplayMessage for inline rendering.
Also makes executeToolCall async and updates the tool call loop to
await each execution sequentially.
Define the OpenAI function calling schema that lets Dork create
kind:777 spell events from natural language requests. Parameters
map to the spell tag schema: name, kinds, authors, tag_filters,
since/until, limit, search, and relays. Includes examples and
documentation of runtime variables ($me, $contacts) and relative
timestamp syntax in the tool description.
The nevent encoding may not include a kind (e.g. nak v0.18.6 doesn't
encode it). Refactor EventSidebarContent to use the kind returned by
useNostrEventSidebar after fetching the event, so the wand icon and
/spells/run/ path resolve correctly regardless of nevent encoding.
Replace the bookmark-based spell tab system in Feed.tsx with sidebar
integration. Spell events (kind 777) added to the sidebar via nostr:
URIs now render with the wand icon, display the spell name, and
navigate to /spells/run/<nevent>. Active-route highlighting matches
the spell run page by comparing decoded event IDs.
Hatching ceremony: escalating haptics on each crack click (light → medium
→ heavy → success notification on hatch). Egg tap-to-wiggle in feeds and
posts: light impact on each user-initiated tap. Auto-wiggle intervals are
excluded to avoid unwanted vibration.
The popover emoji picker (both quick presets and full picker) was
publishing reactions internally without triggering haptic feedback.
Add impactLight() at the top of publishReaction() so every emoji
selection path gets tactile feedback.
Install @capacitor/haptics and add a centralized haptics utility
(src/lib/haptics.ts) that uses the native taptic engine on iOS/Android
and falls back to navigator.vibrate() on web.
Haptics added to:
- Switch component (covers 36+ toggle switches app-wide)
- PullToRefresh threshold (covers 15+ pages)
- MobileBottomNav tab taps
- ReactionButton (like/unlike, double-click heart)
- RepostMenu (repost/undo repost)
- ZapDialog button press + payment success (NWC and WebLN)
- FollowButton and ProfilePage follow toggle
- ComposeBox (post, voice message, and poll publish success)
- NoteMoreMenu (bookmark, pin, mute)
- VinesFeedPage reaction and repost buttons
- ProfileReactionButton and ExternalReactionButton
- NoteCard share button
- BlobbiRoomShell swipe navigation
Replaces raw navigator.vibrate() calls in GameControls and
SendAnimation with the new cross-platform haptics utility, fixing
haptic feedback on iOS where the Vibration API is not available.
Radix Dialog's DismissableLayer sets pointer-events: none on
document.body when the modal is open. Since the dropdowns are portaled
to document.body, they inherit this and silently swallow all mouse
events. Adding pointer-events-auto restores click delivery.
The autocomplete dropdowns are portaled to document.body to escape
overflow clipping, which places them outside the Radix Dialog DOM tree.
Clicks on them were treated as 'interact outside' the dialog, preventing
mouse selection of emoji and mention suggestions.
Add data-autocomplete-dropdown attribute to both dropdown containers and
check for it in handleInteractOutside to prevent modal dismissal.
Zap receipts embedded via nostr:nevent1 references were falling through
to the generic EmbeddedNoteCard, which rendered the raw JSON content of
the zap request. Add a dedicated EmbeddedZapCard that extracts and
displays the sender, amount, and message using the existing zap utility
functions. Forwards disableHoverCards to prevent nested hover cards.
- Add disableMediaEmbeds prop to NoteContent that suppresses images,
galleries, and video/audio inside embedded quotes while preserving
link preview cards and lightning invoices
- Render inline fallback links for nevent/naddr references when
disableNoteEmbeds is true, instead of returning null and leaving
invisible gaps in quoted text
- Restore tag-based title/description fallback for events with empty
content (articles, custom addressable kinds) so they don't render
blank cards
- Migrate EmbeddedProfileBadgesCard to useProfileUrl for consistent
profile link routing