From 4f278ca5748b1c2f4ace4e2cff70acdedbedf04e Mon Sep 17 00:00:00 2001 From: Chad Miller Date: Tue, 30 Dec 2025 04:38:01 +0000 Subject: [PATCH] feat: add optimistic UI for following 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/components/organisms/grain-profile-header.js | 35 +++++++++++++++++++++++++++++------ 1 file(s) changed, 29 insertion(s)(+), 6 deletion(s)(-) diff --git a/src/components/organisms/grain-profile-header.js b/src/components/organisms/grain-profile-header.js --- a/src/components/organisms/grain-profile-header.js +++ b/src/components/organisms/grain-profile-header.js @@ -238,22 +238,45 @@ if (!this._user || this._followLoading || !this.profile) return; this._followLoading = true; + + // Store previous state for rollback + const previousState = { + viewerIsFollowing: this.profile.viewerIsFollowing, + viewerFollowUri: this.profile.viewerFollowUri, + followerCount: this.profile.followerCount || 0 + }; + + // Optimistic update - apply immediately + const newIsFollowing = !previousState.viewerIsFollowing; + this.profile = { + ...this.profile, + viewerIsFollowing: newIsFollowing, + viewerFollowUri: newIsFollowing ? this.profile.viewerFollowUri : null, + followerCount: previousState.followerCount + (newIsFollowing ? 1 : -1) + }; + try { const update = await mutations.toggleFollow( this.profile.handle, this.profile.did, - this.profile.viewerIsFollowing, - this.profile.viewerFollowUri, - this.profile.followerCount || 0 + previousState.viewerIsFollowing, + previousState.viewerFollowUri, + previousState.followerCount ); + // Update with real URI from server (needed for future unfollows) this.profile = { ...this.profile, - viewerIsFollowing: update.viewerIsFollowing, - viewerFollowUri: update.viewerFollowUri, - followerCount: update.followerCount + viewerFollowUri: update.viewerFollowUri }; } catch (err) { + // Rollback on failure console.error('Failed to toggle follow:', err); + this.profile = { + ...this.profile, + viewerIsFollowing: previousState.viewerIsFollowing, + viewerFollowUri: previousState.viewerFollowUri, + followerCount: previousState.followerCount + }; this.shadowRoot.querySelector('grain-toast')?.show('Failed to update'); } finally { this._followLoading = false; -- tangled.sh