The spell engine's $me resolves to the logged-in viewer, but profile tabs
need 'Me' scope to mean the profile owner. Fixed by emitting the owner's
literal hex pubkey instead of $me in ProfileTabEditModal.
Also noted the $contacts behavioral change: profile tab 'Contacts' scope
now shows the viewer's contacts (not the owner's). This is intentional --
the old behavior fetched the owner's kind 3 event, but the spell engine
resolves $contacts from the viewer's follow list.
- SavedFeedFiltersEditor: removed the main SavedFeedFiltersEditor component
(unused after modals were rewritten), parseSelectedKinds, kindsToKindFilter,
getAuthorScope, and the local TabFilter type. Removed unused imports (Globe,
UserSearch, Input, Separator, useUserLists, useFollowPacks). The file now
only exports the reusable sub-components (buildKindOptions, KindPicker,
MultiKindPicker, ScopeToggle, ListPackPicker, AuthorChip, AuthorFilterDropdown).
- useProfileFeed: removed useTabFeed (120 lines) and TabFeedPage type. Profile
tabs now use useStreamPosts with spell events.
- Removed unused NostrFilter import from useProfileFeed.
Profile tabs (kind 16769) now store spell events instead of TabFilter objects.
Each tab tag contains a JSON-encoded kind:777 spell with all filter parameters,
runtime variables ($me, $contacts), and client hints in its tags. The old var
tag system is eliminated entirely.
Key changes:
- profileTabsEvent.ts: ProfileTab now has { label, spell } instead of
{ label, filter }. ProfileTabsData no longer has vars. Deleted TabFilter,
TabVarDef, resolvePointer, resolveFilter (~100 lines removed).
- ProfilePage.tsx: ProfileSavedFeedContent uses useStreamPosts with spell
instead of useResolveTabFilter + useTabFeed. Core tab spells built via
buildSpellTags. Removed profileVars, useResolveTabFilter, useTabFeed imports.
- ProfileTabEditModal.tsx: Produces spell events. Parses spell tags to seed form.
Uses $me for 'me' scope, $contacts for 'contacts' scope.
- SearchPage.tsx: handleSaveProfileTab builds spell events instead of TabFilter.
- Deleted useResolveTabFilter.ts (93 lines) — no longer needed anywhere.
- NIP.md: Updated kind 16769 documentation for spell-based tab format.
SavedFeed now stores a kind:777 spell event instead of TabFilter + TabVarDef.
Old saved feeds in encrypted settings silently drop (Zod schema rejects them).
Key changes:
- AppContext: SavedFeed type changed to { id, label, spell: NostrEvent, createdAt }
Removed TabFilter and TabVarDef types (kept in profileTabsEvent.ts for profile tabs)
- schemas.ts: SavedFeedSchema validates a NostrEvent in the spell field
- useSavedFeeds: addSavedFeed takes (label, spell) instead of (label, filter, vars)
- Feed.tsx SavedFeedContent: passes feed.spell to useStreamPosts, removed
useResolveTabFilter usage and all manual option extraction (~40 lines deleted)
- SearchPage handleSaveFeed: builds spell tags via buildSpellTags(), creates an
unsigned spell event, stores it. Profile tab save path kept as compat shim.
- FeedEditModal: produces spell events from UI state instead of TabFilter objects.
Parses spell tags to seed the form in edit mode.
- ContentSettings: adapted handlers to new (label, spell) signature
- SavedFeedFiltersEditor: TabFilter defined locally (no longer in AppContext)
useStreamPosts now accepts an optional 'spell' (kind:777 NostrEvent) in its
options. When provided, the spell is resolved internally — variables ($me,
$contacts), relative timestamps, client hints (media, language, sort, etc.)
— and drives the entire stream. All other option fields are ignored in spell
mode.
Key changes to useStreamPosts:
- Resolves spell via resolveSpell() using useCurrentUser + useFollowList
- Derives effectiveOptions/effectiveQuery from spell hints
- Routes initial query to Ditto relays when needsDittoRelay is true
- Merges spell extra filter fields (since, until, tag filters) into queries
- Existing callers unchanged (spell option is optional)
SpellRunPage now uses useStreamPosts with the spell option instead of manual
useQuery + nostr.query, gaining live streaming and the new-posts pill for free.
- Add client-hint tags to spell format: media, language, platform, sort,
include-replies. These instruct clients how to build NIP-50 search
extensions and apply client-side filters.
- Add SpellClientHints interface and needsDittoRelay flag to ResolvedSpell
for downstream consumers to know when Ditto relay routing is required.
- Move buildSpellTags() from useAIChatTools into spellEngine.ts as the
single source of truth for spell tag construction.
- Update AI tool schema and system prompt with new parameters and examples.
- Update SpellContent badge display for new tags.
- Document kind 777 in NIP.md.
The $follows variable resolver was querying kind 3 (contact list) with a
'#d' filter, but kind 3 is a replaceable event that has no d tag. This
caused the query to return zero results, so the follows list was never
resolved and the author filter was silently dropped.
Only include '#d' in the query filter for addressable events (30000-39999).
Also fix pre-existing lint error (unused useMemo import) and missing
aiModel field in TestApp.
- Extract MessageBubble, DorkThinking, ToolCallBadge to shared AIChat components
- Add MobileDorkSheet with chat messages, send/stop, and search toggle
- Add Dork toggle (<[o_o]>) to MobileSearchSheet input bar
- MobileBottomNav manages shared backdrop and both sheets via hidden prop
- Both sheets stay mounted to preserve state when toggling
- 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.