Skip to content

Conversation

@addiinnocent
Copy link
Collaborator

Summary

  • Use .put().once() pattern to ensure data persists to localStorage (round-trip writes)
  • Implement dual-store pattern: primary location + sphere index for efficient querying
  • Fix real-time listener with .map().once() to avoid duplicate load events
  • Manage loading state: wait for initial data before hiding spinner
  • Automatically reload comments when sphere changes using watchEffect
  • Add comprehensive Gun.js persistence patterns to documentation

Technical

Key Patterns Implemented:

  • Round-trip writes: .put().once() verifies data persists to storage (not just queued)
  • Dual-store indexing: Data stored at comment/{id} (primary) and sphere/{sphereId}/comments/{id} (queryable index)
  • Listener management: .map().once() prevents duplicate events, Promise wrapper controls loading state
  • Reactive sphere changes: watchEffect in InfoView automatically reloads comments when navigating between spheres

Architecture:

  • Comments now properly persist across page reloads
  • Index structure enables efficient querying by sphere
  • Listener fires for new items after initial load completes
  • Storage debugging helpers verify Gun.js persistence

Testing

  • Comments persist after page reload
  • Comments appear on navigation to different spheres
  • Real-time sync between multiple tabs
  • Nested replies and vote tallying working
  • Loading spinner shows only while data is loading

Documentation

  • Updated docs/BEST-PRACTISES.md with Gun.js persistence patterns
  • Added commentProvider() documentation to docs/DEVELOPMENT.md
  • Documented dual-store pattern and .map().once() listener pattern
  • Added examples of round-trip writes vs .then() anti-pattern

Breaking Changes

None

addiinnocent and others added 25 commits July 15, 2025 15:00
- 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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants