extremely claude-assisted go game based on atproto! working on cleaning up and giving a more unique design, still has a bit of a slop vibe to it.

Add toggle to view all reactions at once

- Added "All Reactions" toggle button in reactions panel
- Shows all reactions across all moves when enabled, sorted by date
- Displays move number badge on each reaction in "all" view
- Hides the "Add Reaction" button in "all" view since it needs a selected move

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+96 -10
+1 -1
TODOS.md
··· 1 1 Features not yet implemented 2 2 - [x] currently reactions are being saved to PDS using the move number rather than the rKey, this causes a mismatch and prevents them from loading properly in the app since they dont match with the constellation RKEY for moves. (FIXED: reactions now use move AT URIs) 3 - - [ ] allow viewing all reactions at once rather than just for a given move 3 + - [x] allow viewing all reactions at once rather than just for a given move 4 4 - [ ] add url parameters to navigate to a specific move URI in a game rather than the current move. 5 5 - [ ] swap board view from to more scalable and robust tenuki library https://github.com/aprescott/tenuki?tab=readme-ov-file, which also includes a scoring engine 6 6 - [ ] fix the scoring logic upon completion of a game to use tenuki's scoring engine and pre-populate scores based on these, still allow changes if the owner disagrees but suggesting a score makes it much easier than counting manually.
+95 -9
src/routes/game/[id]/+page.svelte
··· 34 34 let reactionStars = $state<number | undefined>(undefined); 35 35 let isSubmittingReaction = $state(false); 36 36 let reactionHandles = $state<Record<string, string>>({}); 37 + let showAllReactions = $state(false); 37 38 38 39 // Client-side loaded data with granular loading states 39 40 let loadingGame = $state(true); ··· 119 120 const moveUri = getMoveUri(selectedMove); 120 121 console.log(`Selected move ${moveUri}`); 121 122 return reactions.get(moveUri) || []; 123 + }); 124 + 125 + // Get all reactions across all moves, sorted by creation time 126 + const allReactions = $derived(() => { 127 + const all: Array<ReactionWithAuthor & { moveNumber?: number }> = []; 128 + for (const [moveUri, reacts] of reactions) { 129 + // Find the move number for this URI 130 + const move = moves.find(m => getMoveUri(m) === moveUri); 131 + for (const r of reacts) { 132 + all.push({ ...r, moveNumber: move?.moveNumber }); 133 + } 134 + } 135 + // Sort by creation time, newest first 136 + all.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); 137 + return all; 138 + }); 139 + 140 + // Get the reactions to display based on toggle 141 + const displayedReactions = $derived(() => { 142 + if (showAllReactions) { 143 + return allReactions(); 144 + } 145 + return selectedMoveReactions(); 122 146 }); 123 147 124 148 // Count reactions per move for badges ··· 826 850 <div class="reactions-header"> 827 851 <h3> 828 852 Reactions 829 - {#if selectedMove} 853 + {#if !showAllReactions && selectedMove} 830 854 <span class="reactions-move-ref"> 831 855 Move #{selectedMove.moveNumber} 832 856 </span> 833 857 {/if} 834 858 </h3> 835 - {#if data.session && selectedMove} 859 + <div class="reactions-header-actions"> 836 860 <button 837 - class="add-reaction-btn" 838 - onclick={() => showReactionForm = !showReactionForm} 861 + class="toggle-all-btn" 862 + class:active={showAllReactions} 863 + onclick={() => showAllReactions = !showAllReactions} 839 864 > 840 - {showReactionForm ? 'Cancel' : '+ Add Reaction'} 865 + {showAllReactions ? 'Selected Move' : 'All Reactions'} 841 866 </button> 842 - {/if} 867 + {#if data.session && selectedMove && !showAllReactions} 868 + <button 869 + class="add-reaction-btn" 870 + onclick={() => showReactionForm = !showReactionForm} 871 + > 872 + {showReactionForm ? 'Cancel' : '+ Add Reaction'} 873 + </button> 874 + {/if} 875 + </div> 843 876 </div> 844 877 845 878 {#if showReactionForm && data.session && selectedMove} ··· 881 914 <div class="reactions-list"> 882 915 {#if loadingReactions} 883 916 <p class="no-reactions loading-text">Loading reactions...</p> 884 - {:else if selectedMoveReactions().length === 0} 885 - <p class="no-reactions">No reactions yet. {data.session ? 'Be the first to comment!' : 'Login to add a reaction.'}</p> 917 + {:else if displayedReactions().length === 0} 918 + <p class="no-reactions"> 919 + {#if showAllReactions} 920 + No reactions on any moves yet. {data.session ? 'Select a move and add the first reaction!' : 'Login to add a reaction.'} 921 + {:else} 922 + No reactions yet. {data.session ? 'Be the first to comment!' : 'Login to add a reaction.'} 923 + {/if} 924 + </p> 886 925 {:else} 887 - {#each selectedMoveReactions() as reaction} 926 + {#each displayedReactions() as reaction} 888 927 <div class="reaction-item"> 889 928 <div class="reaction-item-header"> 929 + {#if showAllReactions && reaction.moveNumber} 930 + <span class="reaction-move-badge">#{reaction.moveNumber}</span> 931 + {/if} 890 932 {#if reaction.emoji} 891 933 <span class="reaction-item-emoji">{reaction.emoji}</span> 892 934 {/if} ··· 1680 1722 justify-content: space-between; 1681 1723 align-items: center; 1682 1724 margin-bottom: 1rem; 1725 + flex-wrap: wrap; 1726 + gap: 0.5rem; 1683 1727 } 1684 1728 1685 1729 .reactions-header h3 { ··· 1697 1741 color: var(--sky-gray); 1698 1742 } 1699 1743 1744 + .reactions-header-actions { 1745 + display: flex; 1746 + gap: 0.5rem; 1747 + align-items: center; 1748 + } 1749 + 1750 + .toggle-all-btn { 1751 + padding: 0.375rem 0.75rem; 1752 + background: var(--sky-cloud); 1753 + color: var(--sky-slate); 1754 + border: 1px solid var(--sky-blue-pale); 1755 + border-radius: 0.375rem; 1756 + font-size: 0.8rem; 1757 + font-weight: 500; 1758 + cursor: pointer; 1759 + transition: all 0.2s; 1760 + } 1761 + 1762 + .toggle-all-btn:hover { 1763 + background: var(--sky-blue-pale); 1764 + border-color: var(--sky-apricot); 1765 + } 1766 + 1767 + .toggle-all-btn.active { 1768 + background: linear-gradient(135deg, var(--sky-apricot-dark) 0%, var(--sky-apricot) 100%); 1769 + color: white; 1770 + border-color: var(--sky-apricot-dark); 1771 + } 1772 + 1700 1773 .add-reaction-btn { 1701 1774 padding: 0.375rem 0.75rem; 1702 1775 background: var(--sky-cloud); ··· 1712 1785 .add-reaction-btn:hover { 1713 1786 background: var(--sky-blue-pale); 1714 1787 border-color: var(--sky-apricot); 1788 + } 1789 + 1790 + .reaction-move-badge { 1791 + display: inline-flex; 1792 + align-items: center; 1793 + justify-content: center; 1794 + padding: 0.125rem 0.375rem; 1795 + background: var(--sky-apricot-light); 1796 + color: var(--sky-apricot-dark); 1797 + border-radius: 0.25rem; 1798 + font-size: 0.75rem; 1799 + font-weight: 600; 1800 + margin-right: 0.25rem; 1715 1801 } 1716 1802 1717 1803 /* Reaction form */