Move stopPropagation from the messages wrapper div to individual message
bubbles, so tapping gaps between messages propagates to the outer onClose
handler.
Fetches the buddy's kind 0 metadata via useAuthor. When the buddy has a
profile picture, renders a shaped Avatar (matching the profile tab pattern)
with the buddy's name as the label. Falls back to the default Bot icon
with 'Buddy' text when no picture is set.
create_spell is a write operation (creates persistent feed for user to browse),
get_feed is a read operation (fetches content for the AI to summarize).
Added decision table, key signal words, and default-to-get_feed guidance.
- Swap Search (magnifying glass) for Bot icon in the buddy input bar
- Dork button no longer closes the sheet
- When streaming: clicking stops generation (like Escape)
- When idle: clicking advances the animation by one frame
Blockquote text was using the prose default color which is nearly
invisible on dark theme backgrounds. Override with muted-foreground
for the text and the theme border color for the left accent line.
- Add break-words to the prose container so long unbroken strings like
npub identifiers wrap instead of overflowing the bubble
- Set prose-code:text-primary so inline code uses the theme accent color
instead of the prose default (which was dark-on-dark in dark themes)
- Remove prose backtick decorations around inline code elements
The previous e.target===e.currentTarget approach didn't work because the
scroll container's padding and gap areas are still within the element's
box. Instead, put onClick={onClose} on the outer fixed wrapper and
stopPropagation on the two interactive children (message content wrapper
and input bar). Clicks on dead space (padding, gaps, empty scroll area)
now bubble up to the outer div and close the sheet.
- Bottom nav no longer hides when search/buddy sheets open, so the user
can see the active tab and switch between them
- Buddy sheet closes when tapping empty background area (outside messages
and input bar) via target===currentTarget check
- Search sheet positioned above the nav bar (bottom-mobile-nav) instead
of bottom-0 since the nav is now visible
- Removed duplicate backdrop from MobileSearchSheet (parent provides it)
The search sheet and buddy sheet were previously coupled — buddy was a
toggle mode inside the search sheet. Now they are fully independent:
- Search (Discover) tab opens the search sheet with no AI toggle
- Buddy (ai-chat) tab opens the buddy chat overlay directly
- Tapping one closes the other; backdrop closes both
- Removed buddyMode/onToggleBuddy plumbing from MobileSearchSheet
- Removed onToggleBuddy from MobileBuddySheet (close button instead)
- Removed the <[o_o]> toggle from the search input bar
ActivityCard was removed from NoteCard.tsx in e7a17d3f but the
PostDetailPage poll vote detail view still imported it, causing a
runtime crash when visiting profile pages. Inline the equivalent
article/flex layout directly.
The buddy AI now knows who it's talking to via a {{USER_IDENTITY}} block
in the system prompt containing the user's npub, hex pubkey, display name,
NIP-05, and bio. This lets the AI answer questions like 'who am I?' or
'what's my npub?' directly, and guides it to use fetch_event/get_feed for
deeper profile or post lookups.
Adds a new get_feed tool that lets the AI assistant read posts from any
feed source and summarize activity for the user. Supports:
- Named feeds: follows, global, ditto, and user's saved feeds
- Country feeds: NIP-73 geographic comments via iso3166 codes
- Ad-hoc queries: kinds, authors, search, hashtags via spell resolution
The system prompt is now dynamic, injecting saved feed labels so the
model knows which feeds are available. buildSystemPrompt accepts an
optional savedFeedLabels parameter for this.
Create NSecSigner from buddy's secret key for Blossom upload auth.
Falls back to user's signer when no buddy exists. Calls
BlossomUploader directly instead of going through useUploadFile hook,
using the same server config and timeout pattern.
publish_events: Publishes Nostr events signed by the buddy's key
(falls back to ephemeral when no buddy). Supports any event kind
with content and tags. Returns published events for inline display.
The agent only publishes when explicitly instructed by the user.
fetch_event: Fetches Nostr events by NIP-19 identifier (npub, note,
nevent, naddr, nprofile). Uses the existing nostr pool connection.
Allows the agent to read content the user references in chat.
Both tools adapted from Shakespeare's implementations to fit Ditto's
hook-based tool pattern. System prompt updated with documentation
for both new tools.
When a buddy is configured, create_spell and create_emoji_pack tools
now sign events with the buddy's persistent keypair. This gives the
buddy a consistent Nostr identity across all created content.
Falls back to ephemeral keys when no buddy exists (preserving current
behavior). Ephemeral key profiles ('Dork Spellcaster', 'Dork Emoji
Maker') are only published in the fallback path since the buddy
already has its own kind 0 profile.
Settings textarea was showing the resolved prompt (with 'Dork' and
empty soul) because SYSTEM_PROMPT called buildSystemPrompt() which
substituted placeholders immediately.
Fix: export DEFAULT_SYSTEM_PROMPT_TEMPLATE as the raw template string
with {{NAME}} and {{SOUL}} intact. Settings UI displays this so users
can see and edit the actual placeholders. Substitution only happens
at chat time in buildSystemPrompt().
Also removes 'When you use a tool, briefly describe what you created'
-- personality directives belong in the soul, not the base template.
The system prompt is now purely functional — identity and personality
are entirely governed by {{NAME}} and {{SOUL}} placeholders that get
replaced at runtime with the buddy's configured values.
Changes:
- Remove hardcoded 'You are Dork' intro and 'Be concise and friendly'
- Template starts with 'You are {{NAME}}' followed by {{SOUL}}
- Tool documentation reformatted with ## headers for clarity
- buildSystemPrompt() always does placeholder substitution (no
separate code path for custom vs default)
- Soul textarea style matches system prompt (monospace, text-xs)
- System prompt textarea now shows the built-in default prompt
instead of empty with placeholder, so users can directly edit it
- When text matches the default, stores empty string (no override)
- Reset button restores the default prompt text
- Remove Bot icon from Identity label
- Remove Server icon from MCP Servers label
Add aiSystemPrompt to AppConfig (interface, Zod schema, defaults)
for users to override the base system prompt template.
buildSystemPrompt() accepts optional customPrompt param. When set,
it replaces the entire default template while still substituting
{{NAME}} and {{SOUL}} placeholders.
Advanced Settings UI:
- System Prompt textarea below Soul (monospace, shows built-in
default as placeholder when empty)
- Documents {{NAME}} and {{SOUL}} placeholder usage
- Reset to default button when custom prompt is active
- Saves on blur to AppConfig (persisted in localStorage)
Buddy section in Advanced Settings now shows:
- Identity info (name, npub) when buddy exists
- Soul textarea: pre-populated from buddy event, saves on blur
via updateSoul mutation, re-encrypts and republishes kind 30078
- 'No buddy configured' hint when no buddy exists
Danger Zone now includes:
- Reset Buddy button: wipes localStorage nsec and publishes empty
kind 30078 event to overwrite on relays
Split AIChatPage into three views:
- Logged-out: login prompt
- No buddy: onboarding flow with Dork as static Clippy-like guide
- Buddy exists: full AI chat with buddy's name/soul in system prompt
Onboarding (useBuddyOnboarding):
- Scripted conversation state machine: intro -> name -> soul -> confirm
- Reuses existing MessageBubble components, no API calls
- Dork walks user through naming and describing their buddy's soul
- On confirmation, calls createBuddy() to generate keypair + publish
Chat mode (BuddyChatView):
- Passes buddyName and buddySoul to useAIChatSession
- Header shows buddy name instead of 'Dork'
- Empty state greetings use buddy name
- Input placeholder personalized to buddy name
Move buddy name into the NIP-44 encrypted content alongside nsec
and soul. This avoids needing an extra kind 0 fetch to display the
buddy name on the chat page. The agent's kind 0 profile may use
nicknames, but the identity event name is authoritative.
- BuddySecrets: add required 'name' field
- BuddyIdentity: add 'name' field
- createBuddy: include name in encrypted payload
- updateSoul: preserve existing name when re-encrypting
- decryptSecrets: validate all three required fields
- NIP.md: document name in encrypted content schema
Refactor aiChatSystemPrompt.ts: convert static SYSTEM_PROMPT const
into buildSystemPrompt(name?, soul?) function. When a buddy is
configured, the agent name and soul text are injected into the
template. Falls back to 'Dork' persona when no buddy exists.
Update useAIChatSession to accept optional buddyName/buddySoul
params and build the system prompt dynamically via useMemo.
Match create_spell pattern: sign with ephemeral keypair, publish a
profile for the key, and return nostrEvent so the emoji pack card
renders inline in the Dork chat.
Dork can now fetch web pages, download and upload images to Blossom,
and publish NIP-30 emoji packs. This enables the workflow:
'download these emojis from <url> and make an emoji set'
- fetch_page: fetches URL via CORS proxy, extracts image URLs from HTML
- upload_from_url: downloads images and uploads to Blossom servers
- create_emoji_pack: publishes kind 30030 emoji pack as the user
Dork can now connect to Streamable HTTP MCP servers to discover and
use external tools at runtime. This bridges the gap between dev-time
MCP configs (.mcp.json) and the production AI chat system.
- MCPClient class using @modelcontextprotocol/sdk for full spec support
- useMCPTools hook with TanStack Query caching (5-min stale time)
- MCP tools merged into Dork's tool array and routed through MCPClient
- Settings UI in Advanced Settings to add/remove MCP servers
- MCPServer type, Zod schema, and AppConfig integration