- Share DesktopHighlightStore and DesktopDraftStore at app level instead
of per-column to fix cross-deck reactivity and draft persistence
- Replace broken clipboard-based highlight creation with
LocalContextMenuRepresentation that piggybacks on Copy to get selected text
- Render highlights as highlight:// links instead of bold/italic markers
- Add collapsible highlights panel in article reader with edit/delete/publish
- Publish highlights to relays as NIP-84 events via HighlightPublishAction
- Add Reads tab (kind 30023) and Highlights tab (kind 9802) to profile
- Rewrite MarkdownToolbar with MarkdownEditorState for selection-aware
toggle, Material icons, and keyboard shortcuts (Cmd+B/I/E/K)
- Wire DraftsScreen "New Draft" button to ArticleEditorScreen navigation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wire Article, Editor, Drafts into DeckColumnType + DeckColumnContainer
- Add Drafts to sidebar nav, Add Column dialog, and Window menu
- Add article navigation (onNavigateToArticle) in SinglePaneLayout
- Add NoteActionsRow to ReadsScreen LongFormCards (zaps, reactions, replies)
- Add Cmd+/Cmd- zoom in ArticleReaderScreen via fontScale on RenderMarkdown
- Add kind 30023 to ProfileSubscription for long-form in user profiles
- Add fontScale param to RenderMarkdown using scaled LocalDensity
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests cover the StringIndexOutOfBoundsException in Url.getPart() that
occurred when readEnd() trimmed trailing characters (e.g. ':') from a
detected URL but urlMarker indices remained pointing past the trimmed
string's end.
- UrlMarkerTest: three cases testing PORT/QUERY index at or beyond
string length (the boundary conditions fixed by minOf clamping and
the startIndex >= length guard)
- UrlsDetectorTest: regression for "今北産業" (no crash, 0 URLs) and
for a bare "example.com:" host
- UrlParserTest: end-to-end regression for "今北産業" and "example.com:"
https://claude.ai/code/session_013rJ9iYndYVJgpYK2VazxL2
jester.nyo.dev uses offchain.pub as one of its relays. Adding it gives
us 2 shared relays (relay.damus.io + offchain.pub) instead of 1,
improving challenge visibility between Amethyst and Jester clients.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ChessEngine.makeMove() now normalizes SAN notation before passing to
chesslib, fixing games against jester.nyo.dev and other clients that
use different notation:
- Castling: 0-0 → O-O, 0-0-0 → O-O-O (zeros to letters)
- Annotations: strips !, ?, !!, ??, !?, ?! suffixes
Previously, chesslib rejected 0-0/0-0-0 causing ChessStateReconstructor
to mark games as desynced and skip remaining moves. Games appeared
permanently stuck, even after app restart.
Also fixes discoverUserGames() silently dropping games classified as
spectator — now adds them to spectatingGames instead.
23 new interop tests covering SAN variants, reconstruction, spectator
detection, and missing start event handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NIP-04 encryption for sending DMs is now deprecated. All new messages
are sent using NIP-17 (gift-wrapped sealed messages). Reading NIP-04
messages remains supported for backward compatibility.
When a recipient lacks both a DM relay list (kind 10050) and NIP-65
inbox relays, the send button is disabled and a warning is shown
explaining that messages cannot be delivered.
Changes:
- ChatNewMessageState: Remove nip17/requiresNip17 toggles, add
recipientsMissingDmRelays state, always send via NIP-17
- ChatNewMessageViewModel: Always use NIP-17, remove NIP-04 send
paths, add recipient relay status checking
- ToggleNip17Button -> Nip17Indicator: Replace toggle with static
NIP-17 indicator (always on)
- PrivateMessageEditFieldRow: Show warning when recipients lack
DM relay lists, hide message input
- ChatFileSender: sendAll() always uses NIP-17
- Desktop ChatPane: Remove NIP-17 toggle, show relay warning
- ChatroomView: Reactively check recipient DM relay availability
https://claude.ai/code/session_01T7QhUW9cZogk4DxDXbbbJJ
Add full send + receive encrypted media support in desktop DM chat:
- Paperclip attach button and drag-and-drop in ChatPane (NIP-17 mode only)
- AES-GCM encryption before upload to Blossom server
- ChatMessageEncryptedFileHeaderEvent (kind 15) wrapped in GiftWrap
- sendNip17EncryptedFile() added to IAccount interface and implementations
- DesktopUploadOrchestrator.uploadEncrypted() with proper encrypted hash
- DesktopBlossomClient ByteArray upload overload for encrypted blobs
- LRU cache in EncryptedMediaService to avoid re-downloading
- Error handling: retry on failure, disable send during upload
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add optional r/p/e/a/emoji tags to StatusEvent.create() for full spec compliance
- Add GENERAL and MUSIC type constants to StatusEvent
- Filter expired statuses in UserStatusCache.addStatus() and add removeExpired()
- Add p tag (profile reference) display in DisplayStatusInner
- Add NIP-30 custom emoji rendering in status display via CreateTextWithEmoji
- Allow status type parameter in UserStatusAction.create()
https://claude.ai/code/session_0117asp4mDiR65dxbAGKi2hg
Replace raw MutableSet with synchronized EventDeduplicator class.
Remove dual dedup in addNoteResults — trackRelayEvent now gates all
event processing. SearchScreen callbacks skip dupes early via return
value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pulsating green heart icon shows NIP-46 signer connection status in both
DeckSidebar and SinglePaneLayout NavigationRail. Tooltip displays last
ping time. SignerConnectionState moved to commons for KMP sharing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace boolean isSearching with counter-based activeSubscriptionCount
so loading state stays true until both subs (people + notes) complete
- Add indeterminate LinearProgressIndicator overlaid on search bar bottom
border with matching 12dp rounding, replacing "Searching relays..." text
- Send search REQs to all configured relays (relayStatuses) instead of
only connected ones — fixes NIP-50 relays never receiving search queries
- Move clearResults/startSearching out of remember() into LaunchedEffect
to avoid side effects during composition
- Fix history saving partial queries by replacing LaunchedEffect with
snapshotFlow to properly observe isSearching transitions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract DateUtils (isLeapYear, dateToUnix, timestampToDate) to deduplicate
identical implementations in QueryParser and QuerySerializer
- Move SavedSearch data class from desktopApp to commons/search
- Replace local formatTimestamp with existing toTimeAgo from commons/util
- Remove dead code: resolveAuthorName, _authorSuggestions (never called)
- Remove dead code: createOrFilters (never wired into SearchScreen)
- Remove unused ICacheProvider dependency from AdvancedSearchBarState
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 4: Desktop UI composables for advanced search.
- Rewrite SearchScreen.kt to use AdvancedSearchBarState with TextFieldValue
- Add AdvancedSearchPanel with kind presets, author chips, date range, hashtags, language
- Add SearchResultsList with sticky headers for People/Notes/Articles/Other sections
- Make QueryParser.parseDateToTimestamp and QuerySerializer.timestampToDate public
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3: State holder with MutableStateFlow<SearchQuery> as SSOT,
sourceOfChange discriminator to prevent parse/serialize loops,
debounced query for subscriptions, results management, author
name resolution via cache autocomplete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 of advanced search. Recursive descent parser for operators
(from:, kind:, since:, until:, lang:, domain:, #tag, -exclude, OR),
serializer for bidirectional sync, kind alias registry with pseudo-kind
support. 68 unit tests all passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>