-
Notifications
You must be signed in to change notification settings - Fork 2
fix: implement Gun.js comments persistence with proper round-trip writes #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
addiinnocent
wants to merge
25
commits into
main
Choose a base branch
from
feat/ipfs-signed-upload-urls
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- Updated .claude/settings and .gitignore - Modified sphere InfoView and server index.html - Added server application files and configuration - Added compiled assets from build
- Fix isAuthenticated reactive computation to properly track login state - Implement proper Gun.js subscription cleanup with .off() instead of callbacks - Store Gun references (userRef, profilesRef) instead of trying to store unsubscribe functions - Fix logout to properly unsubscribe from user and profiles before clearing state - Guard against writing null values to Gun.js during logout - Add 500ms sync delay between register and login to ensure credentials are written - Remove hardcoded test emails from PasskeyView and RegisterView - Store registration email in sessionStorage to pass to PasskeyView - Fix missing useUser() import in SettingsView - Add console logging for logout flow debugging This fixes issues where: - Logging out and back in would show profiles from previous account - Logout button wouldn't work due to incorrect unsubscribe handling - Multiple logins without logout would stack subscriptions
- Resolve authentication state pollution and subscription leaks - Update all composables with proper lifecycle management - Add Gun.js ready state utilities for initialisation - Implement Pinata integration for IPFS functionality - Fix router and plugin loading in production - Update server configuration and environment setup
Implement a threaded comment system with the following features:
- Nested replies (Reddit-style threading with max 5 levels)
- Upvote/downvote voting system
- Real-time P2P synchronisation via Gun.js
- Public comments (no local/global space concept)
- Authentication required for posting and voting
New files:
- /types/Comment.ts - TypeScript types for Comment, Vote, and CommentWithVotes
- /client/src/composables/commentProvider.ts - Gun.js integration and business logic
- /client/src/components/forms/CommentForm.vue - Form for creating comments/replies
- /client/src/components/CommentItem.vue - Single comment display with voting
- /client/src/components/CommentList.vue - Recursive list rendering
Modified files:
- /types/index.ts - Export Comment types
- /client/src/views/sphere/InfoView.vue - Integrated thread section into sphere pages
Gun.js namespaces:
- comment/{id} - Primary comment storage
- vote/{id} - Vote storage
- sphere/{sphereId}/comments - Index of all comments for a sphere
- comment/{parentId}/replies - Index of replies to a comment
- vote_index/{profileId}/{commentId} - User's vote lookup
Features:
- Vote toggle: clicking the same vote button removes the vote
- Vote change: clicking a different vote button updates the vote
- Real-time updates via Gun.js listeners
- Depth-based indentation (max 5 levels)
- Author profile lookup
- Relative timestamps (e.g., "2 hours ago")
- Empty state messages
Use commentProvider() return value directly instead of calling useComment() in the same component. Provider/inject is meant for parent-child relationships, so the parent component should use the returned composable directly.
Move useProfile() call to the top level of commentProvider() instead of inside individual functions. In Vue 3 Composition API, composables must be called during setup, not inside regular functions.
Instead of using references via .set(node), directly store comment data
in sphere/{sphereId}/comments/{id}. This ensures the listener can properly
pick up the data when stored. Also added console logging for debugging.
Add newly created comments directly to the local comments.value array for instant UI feedback while Gun.js syncs the data across peers. This ensures comments appear immediately without waiting for the listener callback to fire.
Two major fixes: 1. Persistence: Load existing comments from Gun.js on page load using .once() before setting up the listener. This ensures comments persist when you reload the page. 2. Design: Remove nested cards following DESIGN.md guidelines - Changed CommentItem from Card-based to list item with left border - Simple, clean visual hierarchy without nested boxes - Better spacing and typography - Improved dark mode support The comment section now properly persists comments across page reloads and follows the TribeLike design system.
Major fixes for persistence:
1. Wait for Gun.js acknowledgements: Use callback functions in .put() to ensure
data is actually written to Gun.js before resolving. This ensures comments
are saved to localStorage/IndexedDB.
2. Proper async/await: Wrap Gun.js operations in Promises to ensure proper
sequencing and error handling.
3. Enhanced logging: Added detailed console logs throughout the save and load
process to help debug persistence issues:
- When creating comments
- When saving to Gun.js
- When loading from Gun.js
- When adding to local state
4. Improved loading: Added { wait: 1000 } option to .once() to wait for data
from Gun.js persistent storage.
Comments should now persist across page reloads, navigations, and app restarts.
Since Gun.js persistence might not be working reliably in the browser,
added dual-layer persistence:
1. Primary: Gun.js (for P2P sync)
2. Backup: localStorage with two keys:
- gundb_comment_{id}: Full comment data
- gundb_sphere_comments_{sphereId}: List of comment IDs
Loading logic:
1. First checks localStorage backup for comment IDs
2. Falls back to Gun.js if localStorage is empty
3. For each comment ID, loads from Gun.js, then falls back to localStorage
This ensures comments persist even if Gun.js doesn't properly save to
IndexedDB/localStorage. Comments will survive page reloads and navigations.
Removed all localStorage dependencies. Comments now persist purely using Gun.js.
**SAVE (createComment):**
1. Save full comment at: gun.get('comment/{id}').put(comment)
2. Add to sphere index at: gun.get('sphere/{sphereId}/comments/{id}').put(comment)
3. If reply, add to parent at: gun.get('comment/{parentId}/replies/{id}').put(comment)
**RETRIEVE (loadComments):**
1. Query Gun.js for comment IDs at: gun.get('sphere/{sphereId}/comments').once()
2. For each ID, load full comment from: gun.get('comment/{commentId}').once()
3. Filter out Gun.js metadata (keys starting with '_')
4. Load vote counts and user votes for each comment
**DATA PATHS:**
- comment/{commentId} → Full comment data
- sphere/{sphereId}/comments/{commentId} → Index of comments in sphere
- comment/{parentId}/replies/{commentId} → Replies to a comment
- vote/{voteId} → Vote data
- vote_index/{profileId}/{commentId} → User's vote lookup
All comments now persist purely in Gun.js with proper Gun.js callback handlers.
CRITICAL FIX:
- Use .then() on .put() to wait for Gun.js to actually persist to storage
- This ensures data is written to localStorage BEFORE we resolve
- Previous implementation was fire-and-forget, data never persisted
GUN.JS SAVE FLOW (createComment):
1. gun.get('comment/{id}').put(comment).then(() => {
2. gun.get('sphere/{sphereId}/comments/{id}').put(comment).then(() => {
3. gun.get('comment/{parentId}/replies/{id}').put(comment) [if reply]
4. Add to local state
5. Resolve promise
6. })
7. })
GUN.JS LOAD FLOW (loadComments):
1. Query gun.get('sphere/{sphereId}/comments').once()
2. Extract comment IDs from returned object
3. For each ID, query gun.get('comment/{id}').once()
4. Load full comment data and add to state
IMPROVED LOGGING:
- Shows exact Gun.js paths being queried
- Shows data types and keys returned from Gun.js
- Clear ✓/✗/⚠️ indicators for debugging
- Full path: toplocs_v{VERSION}/{path}
This should finally persist comments to Gun.js storage.
- Use .put().once() pattern to ensure data persists to localStorage - Implement dual-store pattern: primary location + sphere index for efficient querying - Fix real-time listener with .map().once() to avoid duplicate events - Manage loading state: wait for initial data before hiding spinner - Reload comments when sphere changes using watchEffect - Add comprehensive Gun.js patterns to BEST-PRACTISES documentation - Update DEVELOPMENT.md with comments system architecture
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
.put().once()pattern to ensure data persists to localStorage (round-trip writes).map().once()to avoid duplicate load eventswatchEffectTechnical
Key Patterns Implemented:
.put().once()verifies data persists to storage (not just queued)comment/{id}(primary) andsphere/{sphereId}/comments/{id}(queryable index).map().once()prevents duplicate events, Promise wrapper controls loading statewatchEffectin InfoView automatically reloads comments when navigating between spheresArchitecture:
Testing
Documentation
docs/BEST-PRACTISES.mdwith Gun.js persistence patternscommentProvider()documentation todocs/DEVELOPMENT.md.map().once()listener pattern.then()anti-patternBreaking Changes
None