- 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>
Persist dismissed game IDs locally (SharedPreferences on Android,
java.util.prefs on Desktop) so completed games can be permanently
hidden from the chess lobby. Adds per-game dismiss button and
"Clear all" action when 2+ finished games exist.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Mark recentlyLoadedGames timestamp eagerly in refreshGame (before
async fetch) to prevent concurrent fetches for the same game
- Clear focused game in removeGameId when the removed game matches,
so finished games stop being re-polled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Dedup incoming events by ID (bounded LRU set of 500) to prevent
redundant processing when multiple relays deliver the same event
- Filter non-chess kind 30 events early (isStart=false, isMove=false)
- Guard handleGameAccepted with recentlyLoadedGames check to prevent
N concurrent relay fetches when N move events arrive for same game
- 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
Deduplicate chess notify() into shared notifyChessEvent() helper using BaseChessEvent
Remove addCompletedGameDirectly, extend moveToCompleted with optional liveState param
Hoist currentUser lookup to top of ChessLobbyContent, remove 4 duplicate remembers
Add missing imports in desktop ChessScreen, replace all FQN usages with short names
Remove redundant distinctBy in completed games UI (state already prevents duplicates)
- Filter user's own games from public "Live Games" list in lobby
- Fix broken addSpectatingGame filter (playerPubkey is always viewerPubkey)
- Add removeGame() to clean up stale entries when "Game not found"
- Make player avatars clickable to open profile on game screen
- Make completed game cards clickable to view final board state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds ability to leave a spectated game on both Android and Desktop:
- Added stopSpectating() to ChessLobbyLogic (removes from state + stops polling)
- Both ViewModels now delegate to logic.stopSpectating() instead of bypassing it
- LiveChessGameScreen accepts onLeaveSpectating callback shown in spectator info area
- Android: Leave Game button pops back stack and stops spectating
- Desktop: Leave Game button clears selectedGameId (returns to lobby) and stops spectating
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ChessPlayerChip.kt with three shared composables:
- ChessPlayerChip: single player avatar + name with active border
- ChessPlayerVsHeader: mirrored White vs Black header above the board
- OverlappingAvatars: compact overlapping circular avatars for list cards
Wire avatars into:
- LiveChessGameScreen: vs header with active player green border
- DesktopChessGameLayout: vs header above the chess board
- Android & Desktop lobby cards: overlapping avatars in ActiveGameCard,
ChallengeCard, and OutgoingChallengeCard avatar slots
Uses existing UserAvatar (coil3 AsyncImage + Robohash fallback) from
commons for KMP compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip adding games to spectatingGames when the user is a participant
(playerPubkey or opponentPubkey matches userPubkey) or when the game
is already finished.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire onGameEndDismiss callback so the "Continue" button on game end
overlay moves the game to the completed list. Add auto-detection of
finished games during polling refresh so games transition even if the
user navigates away without clicking the button. Discovered games that
are already finished now go straight to completed instead of appearing
in the active list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The board was non-interactive for participants during pending challenges
on Android because canMakeMoves factored in isPendingChallenge. Desktop
passed isSpectator directly without the pending check, so it worked.
Remove isPendingChallenge from the board interactivity gate to match
Desktop behavior. The header still shows "Challenge Pending" status,
and this also fixes a secondary issue where the isPendingChallenge flag
could get stuck when replaceGameState preserved the old state via its
open-challenge workaround.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>