commits
Prevents users from being trapped in the app when their atProto profile
is misconfigured or not found. Adds optional secondary action support
to FullScreenError widget.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Integrate the new social.coves.actor.getComments backend endpoint
into the profile page's Comments tab:
- Add getActorComments() method to CovesApiService
- Add CommentsState model with immutable list (List.unmodifiable)
- Add ActorCommentsResponse model with proper documentation
- Add loadComments/loadMoreComments/retryComments to UserProfileProvider
- Wire up lazy loading in profile screen (loads on first tab switch)
- Display comments using existing CommentCard widget
- Handle pagination, loading states, and errors
- Properly handle 404 as "User not found" (not empty state)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add banner image support with gradient fallback
- Add avatar drop shadow for depth
- Display handle and DID (with QR icon) instead of display name
- Add "Joined" date with calendar icon
- Add tabbed content bar with icons (Posts, Comments, Likes)
- Implement frosted glass effect on collapsed header
- Show Memberships stat instead of Communities/Reputation
- Add UserProfile model and UserProfileProvider
- Add TappableAuthor widget for navigating to profiles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tapping the feed tab while already on the feed screen now scrolls the
current feed to the top, matching common social media app behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add hasMore prop to FeedPage widget to track pagination state
- Show "You're all caught up!" message with checkmark when feed ends
- Add _shouldShowFooter getter for cleaner footer logic
- Prevents confusing bounce behavior when reaching end of feed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Adjust post detail text sizing to match feed (title 16px, text 14px)
- Remove bold styling from title and author handle
- Reduce author avatar size from 24px to 20px
- Keep external embed images at same height as feed view (180px)
- Add sources section for megathread posts with clickable source links
- Add EmbedSource model with URI validation and security checks
- Add SourceLinkBar widget matching ExternalLinkBar styling
- Improve ValueKey comment clarity in feed_page.dart
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add external link embed support (link cards with thumbnail, title, description)
- Add BlueskyExternalEmbed model with domain extraction
- Improve quoted posts: show handle, timestamp, media placeholder
- Handle unavailable quoted posts (blocked, deleted, detached)
- Add official Bluesky SVG icons (reply, repost, like, logo)
- Match Bluesky's dim theme colors exactly
- Remove text truncation for posts (Bluesky has 300 char limit)
- Add formatCount and formatFullDateTime utilities
- Add comprehensive tests for Bluesky post models (67 tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add UI components to render Bluesky crosspost embeds in feeds with
Bluesky-styled post cards.
New files:
- lib/models/bluesky_post.dart: Data models for BlueskyPostEmbed,
BlueskyPostResult with URL helpers and robust JSON validation
- lib/constants/bluesky_colors.dart: Bluesky brand color palette
- lib/widgets/bluesky_action_bar.dart: Disabled action bar showing
engagement counts (view-only)
- lib/widgets/bluesky_post_card.dart: Main card widget with avatar,
author info, text, media placeholder, quote posts, and action bar
Changes:
- lib/models/post.dart: Add BlueskyPostEmbed parsing for
social.coves.embed.post type, fix nullable text field handling
- lib/widgets/post_card.dart: Conditionally render BlueskyPostCard
when embed is present
- lib/services/coves_api_service.dart: Add catch blocks for parsing
errors to prevent silent failures
Features:
- 42px circular avatar (tappable → opens Bluesky profile)
- Author name, handle, and relative timestamp
- Post text with max 6 lines
- Media placeholder with "View on Bluesky" link
- Nested quote post support (1 level)
- Disabled action bar with reply/repost/like counts
- "View on Bluesky" footer link
- Graceful unavailable post handling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add CreateCommunityResponse model with defensive JSON parsing
- Add createCommunity() API method to CovesApiService
- Transform CommunitiesScreen to admin-aware with creation form
- Admin check via handle (coves.social, alex.local.coves.dev)
- Backend enforces actual permissions via DID allowlist
- DNS-valid name validation with regex
- Proper listener cleanup to prevent memory leaks
- Cached API service instance per screen lifecycle
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update formatHandleForDisplay to handle both formats:
- New: c-gaming.coves.social → !gaming@coves.social
- Legacy: gaming.community.coves.social → !gaming@coves.social
Add null fallback in PostCard._buildCommunityHandle to prevent
crashes when handle format is unrecognized.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
User-specific Claude Code permissions should not be committed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add atproto provider logos to login and landing screens
- Add errorBuilder to all SVG assets for graceful degradation
- Improve sign-in error messages with user-friendly text
- Replace hardcoded colors with AppColors constants
- Extract help dialog to dedicated method for cleaner code
- Add keyboard dismiss on tap outside input field
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Multi-feed state management with security hardening:
- FeedState model with sentinel copyWith pattern for nullable field clearing
- MultiFeedProvider with per-feed state and cross-session security guards
- FeedPage widget with pull-to-refresh on empty state
- FeedScreen auth sync and lazy feed loading
- Comprehensive tests for new architecture
Fixes:
- copyWith can now clear nullable fields (cursor, error, lastRefreshTime)
- Cross-session data leaks prevented via DID comparison
- Empty feed states are now refreshable
- PageController syncs with auth state on sign-out
- For You tab loads on first access after sign-in
Addresses code quality checks from CODE_QUALITY_GUIDE.md:
- Use cascade notation where appropriate (cascade_invocations)
- Put control body on separate line (always_put_control_body_on_new_line)
- Break long string to stay under 80 chars (lines_longer_than_80_chars)
- Apply dart format to all changed files
All files now pass:
- dart format --output=none --set-exit-if-changed
- flutter analyze (0 issues)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates provider registration to use MultiFeedProvider instead of
FeedProvider. The new provider is initialized with:
- CovesApiService for API calls
- AuthProvider for auth state and session identity
- VoteProvider for vote state initialization from feed responses
Also includes pubspec.lock updates from dependency resolution.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates widget tests for the new MultiFeedProvider architecture:
- FakeMultiFeedProvider replaces FakeFeedProvider
- Supports per-feed state management (FeedType parameter)
- Uses sentinel-compatible copyWith for state mutations
- Tests cover both authenticated and unauthenticated flows
- Tests for PageView swipe navigation when authenticated
- Tests for single-feed display when not authenticated
Removes orphaned test/providers/feed_provider_test.dart that
referenced the deleted FeedProvider.
Updates test/widget_test.dart counter test with provider setup.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Refactors FeedScreen to work with MultiFeedProvider and adds several
UX and reliability improvements:
PageView for feed switching:
- Authenticated users can swipe between Discover and For You
- Unauthenticated users see only Discover (no PageView)
- Per-feed ScrollControllers with position restoration
Auth state synchronization:
- Listens to AuthProvider changes
- Jumps PageController to page 0 on sign-out to match provider state
- Prevents tab/page mismatch after re-authentication
Lazy feed loading (_ensureFeedLoaded):
- Triggers initial load when switching to an unloaded feed
- Handles case where user signs in after app start and taps For You
- Called from both tab tap and swipe navigation
This fixes issues where:
- For You tab showed empty state after signing in mid-session
- PageController stayed on page 1 after sign-out while provider
switched to Discover, causing misalignment on re-auth
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extracts feed rendering logic into a reusable FeedPage widget that
handles all feed states:
- Loading: Centered CircularProgressIndicator
- Error: User-friendly message with Retry button
- Empty: Contextual message based on auth state
- Posts: ListView.builder with pagination support
Key improvements:
- Empty state now wrapped in RefreshIndicator with CustomScrollView
and SliverFillRemaining, allowing pull-to-refresh when feed is empty
- Error messages are user-friendly (maps technical errors to readable text)
- Loading more indicator at list bottom during pagination
- Proper scroll controller integration for infinite scroll
This fixes an issue where unauthenticated users with an empty Discover
feed had no way to retry without restarting the app.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replaces FeedProvider with MultiFeedProvider that manages separate
state for Discover and For You feeds. Key improvements:
Architecture:
- Per-feed state management using Map<FeedType, FeedState>
- Centralized _fetchFeed helper eliminates code duplication
- Auth-aware feed switching (For You requires authentication)
- Minute-based time updates for relative timestamps
Security (cross-session data leak prevention):
- Captures session DID before fetch to detect auth changes
- Discards For You responses if session changed during fetch
- Guards both success and error paths to prevent stale data
- Removes feed state entirely on session change (not copyWith)
This prevents scenarios where:
- User signs out during fetch → old data reappears
- User A signs out, User B signs in → User A's feed shown to B
- Fetch errors after sign-out → stale posts restored
Removes the old single-feed FeedProvider.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduces FeedState as an immutable state container for per-feed data
(Discover and For You feeds). Key features:
- Holds posts, cursor, loading states, error, scroll position, and
last refresh time
- Uses sentinel pattern for copyWith to distinguish "not provided"
from "explicitly set to null" for nullable fields (cursor, error,
lastRefreshTime)
- Enables proper clearing of fields on refresh/error recovery
The sentinel pattern fixes a bug where nullable fields couldn't be
cleared back to null through copyWith - critical for pagination
cursor handling when reaching the end of a feed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move provider initialization from postFrameCallback to didChangeDependencies
for synchronous access before first build. Create ScrollController with
initialScrollOffset set to cached position, eliminating the visible flash
from loading → content at top → jump to cached position.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
View.of(context) was being called in didChangeMetrics() during widget
deactivation, causing "Looking up a deactivated widget's ancestor"
errors. The context becomes invalid before mounted becomes false.
Fixed by caching the FlutterView reference in didChangeDependencies()
for both _ReplyScreenState and _ReplyToolbarState, then using the
cached reference in didChangeMetrics().
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add create post functionality with community picker:
Features:
- Community models for API responses
- listCommunities and createPost API endpoints
- Community picker screen with search and pagination
- Create post screen with URL validation and input limits
- Optimistic post navigation support
Tests:
- 76 tests covering models, API, and widget behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Comprehensive test coverage for community features:
Unit tests:
- CommunitiesResponse, CommunityView JSON parsing
- CreatePostResponse, ExternalEmbedInput serialization
- SelfLabels, SelfLabel const construction and serialization
- CovesApiService.listCommunities with pagination/errors
- CovesApiService.createPost with validation/errors
Widget tests:
- CreatePostScreen UI elements and form validation
- Character limits enforcement (title: 300, content: 10000)
- NSFW toggle, URL field show/hide behavior
Logic tests:
- Community search filtering (name, displayName, description)
- Member count formatting (K/M suffixes)
- Description line building with separators
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add isOptimistic flag to PostDetailScreen to skip initial comment
loading for newly created posts that haven't been indexed yet.
Also adds AlwaysScrollableScrollPhysics for consistent pull-to-refresh.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Create post screen improvements:
- Community picker integration
- URL validation (http/https only)
- Input length limits from backend lexicon (title: 300, content: 10000)
- NSFW toggle with self-labels
- Language selection dropdown
- Navigate to feed after successful post creation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Full-screen community selection interface for post creation:
- Search with debounced client-side filtering
- Infinite scroll pagination
- Cached avatar images with error fallback
- Proper authenticated API service with disposal
- Member/subscriber count formatting (K/M suffixes)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add CovesApiService methods for community operations:
- listCommunities(): Fetch communities with pagination and sorting
- createPost(): Create posts with title, content, embeds, and labels
Both methods include proper error handling and debug logging.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add data models for community-related API operations:
- CommunitiesResponse and CommunityView for listing communities
- CommunityViewerState for subscription/membership status
- CreatePostRequest and CreatePostResponse for post creation
- ExternalEmbedInput for link embeds
- SelfLabels and SelfLabel for content labels (NSFW, etc.)
All models support const constructors for better performance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add per-post CommentsProvider caching with LRU eviction for instant
back-navigation, scroll position restoration, and draft text preservation.
Updates feed tests to reflect the new feed type tab behavior:
- Default feed is now Discover (not timeline)
- Authenticated users see both Discover and For You tabs
- Unauthenticated users see only Discover tab
- Updated test descriptions for clarity
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates tests to work with the new CommentsProvider constructor that
requires postUri and postCid parameters.
Changes:
- CommentsProvider tests: pass postUri/postCid in constructor, remove
parameters from loadComments calls
- Add MockCommentsProvider helper for widget tests
- Update FocusedThreadScreen tests to provide commentsProvider parameter
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates PostDetailScreen, ReplyScreen, and FocusedThreadScreen to use
the new CommentsProviderCache for instant back-navigation and state preservation.
PostDetailScreen:
- Acquires provider from cache with reference counting
- Restores scroll position when returning to cached comments
- Background refresh if data is stale (>5 min)
- Handles sign-out by navigating back to feed
ReplyScreen:
- Requires CommentsProvider parameter for draft access
- Saves draft text on cancel, restores on reopen
- Per-parent-URI drafts (separate drafts for different reply contexts)
- Auto-closes on sign-out
FocusedThreadScreen:
- Passes CommentsProvider to children for consistent draft/vote state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduces a caching layer for CommentsProvider instances to enable instant
back-navigation when returning to previously viewed posts.
Key changes:
- Add CommentsProviderCache with LRU eviction (15 posts max)
- Refactor CommentsProvider to be immutable per post (postUri/postCid in constructor)
- Add reference counting to prevent evicting in-use providers
- Add scroll position and draft text preservation to CommentsProvider
- Add staleness tracking for background refresh of cached data
- Wire up cache in main.dart with sign-out cleanup
The cache automatically disposes providers when evicted and clears all
providers on sign-out for privacy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New features:
- FocusedThreadScreen for viewing deep comment threads
- "Read X more replies" link at maxDepth navigates to focused view
- Ancestors shown flat above anchor, replies threaded below
- Auto-scroll to anchor comment on open
Performance & code quality:
- Fix O(n²) descendant counting - only compute when needed at maxDepth
- Extract threading colors to shared kThreadingColors constant
- Remove unused Consumer<VoteProvider> wrapper
- Extract StatusBarOverlay reusable widget
Tests:
- Add unit tests for countDescendants
- Add widget tests for CommentThread max-depth behavior
- Add widget tests for FocusedThreadScreen rendering
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add support for --dart-define=ENV=dev as a convenience shorthand
alongside the existing ENVIRONMENT override. Maps 'dev'/'local' to
local environment and 'prod'/'production' to production.
Also remove macos Flutter config files from tracking (already in gitignore).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Animation improvements:
- Increase collapse duration to 350ms expand / 280ms collapse
- Add combined fade + size + slide transitions
- Content slides down from parent on expand, up on collapse
- Add ClipRect to prevent overflow on nested threads
- Badge now uses scale + opacity animation with easeOutBack bounce
Compact collapsed state:
- Hide comment content when collapsed (only show avatar + username)
- Move "+X hidden" badge to right side, simplified to "+X"
- Reduce padding in collapsed state
PR review fixes:
- Return unmodifiable Set from collapsedComments getter
- Add accessibility Semantics with collapse/expand hints
- Add 6 unit tests for collapse state management
Also includes dart format fixes across touched files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Podfile.lock for reproducible CocoaPods builds
- Update Xcode project and workspace settings
- Update AppFrameworkInfo.plist
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Podfile for iOS native dependencies
- Update xcconfig files to include CocoaPods integration
- Ignore macOS directory (not targeting this platform)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add collapsed comment tracking to CommentsProvider with toggleCollapsed()
- Add long-press gesture on CommentCard with haptic feedback
- Show "+N hidden" badge when thread is collapsed (depth-aware positioning)
- Animate collapse/expand with AnimatedSwitcher + SizeTransition (200ms)
- Only build replies widget when not collapsed (optimization)
- Wire up collapse state in PostDetailScreen
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add FeedType enum (discover/forYou) with feed switching in FeedProvider
- Replace AppBar with transparent gradient header overlay
- Add Discover/For You tabs with underline indicator (auth-gated)
- Rename Search tab to Communities with Workspaces icon
- Use IndexedStack to preserve screen state on tab switch
- Add accessibility labels and extract magic numbers to constants
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Comment creation feature with backend integration:
- CommentService for API calls
- Shared auth interceptor (401 retry)
- Input validation (10k chars, emoji-aware)
- Tap-to-reply UI for nested comments
- 22 new tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
CommentService tests (10 tests):
- Successful comment creation
- Auth failure when no session
- Network error handling
- 401 response handling
- Invalid responses (null data, missing uri, empty uri)
- Server error handling
- Nested reply request format
CommentsProvider.createComment tests (12 tests):
- Validation: empty content, whitespace-only, exceeds limit
- Emoji counting with grapheme clusters
- Error states: no post loaded, no CommentService
- Top-level comment (reply to post)
- Nested comment (reply to comment)
- Content trimming
- Refresh after success
- Exception propagation
- Boundary testing at max length
All 52 tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Pass postCid to loadComments for reply reference support
- Add onReplyTap callback to CommentThread and CommentCard
- Tapping reply icon on a comment navigates to ReplyScreen
- ReplyScreen receives parent comment for nested replies
- Show "Replying to @handle" context in reply screen
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add CommentService dependency to CommentsProvider
- Add createComment() method supporting both post and comment replies
- Store postCid alongside postUri for proper reply references
- Add input validation: 10k char limit using grapheme clusters
- Proper emoji counting (🎉 = 1 char, not 2)
- Wire up CommentService in main.dart
Reply reference logic:
- Reply to post: root=post, parent=post
- Reply to comment: root=post, parent=comment
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create auth_interceptor.dart with reusable createAuthInterceptor()
- Handles 401 responses by refreshing token and retrying once
- Sign out user if refresh fails to prevent infinite loops
- Refactor VoteService to use shared interceptor
- CommentService already uses it (from previous commit)
Eliminates ~130 lines of duplicated interceptor code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add CommentService with createComment() method that calls backend API
- Backend handles PDS writes via OAuth/DPoP (sealed token architecture)
- Add ValidationException to api_exceptions for client-side validation
- Add characters package for proper Unicode grapheme cluster counting
The comment creation flow:
Mobile → Coves Backend (sealed token) → User's PDS (DPoP)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update mock providers to remove VoteService dependency
- Add ViewerState to test fixtures for feed and comments
- Update FeedProvider tests for vote state initialization
- Update CommentsProvider tests for recursive vote state init
- Update VoteProvider tests for extractRkeyFromUri utility
- Remove obsolete vote service test methods (deleteVote, getUserVotes)
- Add generated mock files for auth service tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
main.dart:
- Remove voteService parameter from FeedProvider and CommentsProvider
CommentsProvider:
- Remove VoteService dependency - vote state from response viewer data
- Add _initializeCommentVoteState helper for recursive vote initialization
- On refresh: initialize all comments (server data is truth)
- On pagination: only initialize new comments (preserve optimistic state)
This completes the migration from VoteService.getUserVotes() to using
viewer state from API responses for both feed posts and comments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove VoteService dependency - vote state comes from feed response
- Replace getUserVotes + loadInitialVotes with per-post setInitialVoteState
- Use viewer.vote and viewer.voteUri from backend response
- Call setInitialVoteState for ALL posts to handle cross-device vote removal
This fixes the bug where votes would disappear on feed refresh:
1. Backend now populates viewer state from PDS cache
2. Feed provider initializes VoteProvider state from viewer data
3. Score adjustments are cleared to prevent double-counting
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Vote Service:
- Remove deleteVote method - backend handles toggle logic
- Remove getUserVotes - vote state now comes from feed viewer data
- Remove unused ExistingVote and VoteInfo classes
- Handle empty uri/cid response as successful toggle-off
- Use shared extractRkeyFromUri utility
Vote Provider:
- Remove existingVoteRkey/Direction params from createVote call
- Remove loadInitialVotes - replaced by setInitialVoteState per-post
- Add extractRkeyFromUri static utility to VoteState
- Clear score adjustments in setInitialVoteState to prevent double-counting
This aligns with the backend's vote cache approach where viewer state
is populated from PDS on each request rather than relying on the
eventually-consistent AppView index.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add ViewerState class to represent the viewer's relationship with posts:
- vote: direction ("up", "down", or null)
- voteUri: AT-URI of the vote record
- saved: bookmark status
- tags: user-applied tags
This enables the feed to include viewer-specific state from the backend,
allowing proper initialization of vote UI state on feed refresh.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major OAuth architecture rework - delegate complexity to backend.
Key changes:
- Add CovesSession model for simplified sealed tokens
- Add CovesAuthService for backend-delegated OAuth
- Update OAuth config for private-use URI scheme (RFC 8252)
- Add automatic token refresh on 401 responses
- Remove atproto_oauth_flutter package (~14K lines)
The backend now handles all OAuth complexity (DPoP, PKCE, token exchange)
and returns opaque sealed tokens that the client simply stores and sends.
- Remove unused imports in test files
- Add assertion on unused variable in singleton test
- Clean up redaction test file (remove unused mocks)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Auto-format all Dart files per CODE_QUALITY_GUIDE.md standards.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update feed_screen_test.dart for the new OAuth patterns.
iOS changes:
- Add Runner.entitlements for associated domains
- Enable Universal Links (applinks:coves.social)
- Reference entitlements in Xcode project
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove the client-side OAuth implementation now that auth is delegated
to the Coves backend. This eliminates ~14,000 lines of complex OAuth
code that handled:
Removed oauth_service.dart:
- Complex OAuthSession management
- Client-side token refresh
- DPoP key generation and proof signing
- PKCE code verifier/challenge generation
Removed atproto_oauth_flutter package:
- DPoP implementation (fetch_dpop.dart)
- Identity resolution (did/handle resolvers)
- OAuth server discovery and metadata
- Token exchange and refresh logic
- Cryptographic key management
- Session state persistence
The backend now handles all of this complexity, returning opaque
sealed tokens that the client simply stores and sends.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Wire up VoteService with the new session getter and auth callbacks
for the backend-delegated OAuth architecture.
Key changes:
- VoteService now uses sessionGetter instead of direct OAuthSession
- Add tokenRefresher callback for automatic 401 recovery
- Add signOutHandler callback for failed refresh cleanup
- Remove OAuthService initialization (deleted)
The new flow ensures votes go through the Coves backend which
has the DPoP keys needed to write to user PDSs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update FeedProvider and CommentsProvider to work with the simplified
session model and backend-proxied auth flow.
Key changes:
- Use VoteService callback pattern instead of OAuthSession
- Remove direct PDS URL handling
- Simplify test mocks to match new API
Provider updates:
- FeedProvider: Use token getter instead of session getter
- CommentsProvider: Same simplification
Test updates:
- Update mocks to use CovesSession instead of OAuthSession
- Remove PDS URL getter mocks
- Simplify vote service setup in tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Integrate the new social.coves.actor.getComments backend endpoint
into the profile page's Comments tab:
- Add getActorComments() method to CovesApiService
- Add CommentsState model with immutable list (List.unmodifiable)
- Add ActorCommentsResponse model with proper documentation
- Add loadComments/loadMoreComments/retryComments to UserProfileProvider
- Wire up lazy loading in profile screen (loads on first tab switch)
- Display comments using existing CommentCard widget
- Handle pagination, loading states, and errors
- Properly handle 404 as "User not found" (not empty state)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add banner image support with gradient fallback
- Add avatar drop shadow for depth
- Display handle and DID (with QR icon) instead of display name
- Add "Joined" date with calendar icon
- Add tabbed content bar with icons (Posts, Comments, Likes)
- Implement frosted glass effect on collapsed header
- Show Memberships stat instead of Communities/Reputation
- Add UserProfile model and UserProfileProvider
- Add TappableAuthor widget for navigating to profiles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add hasMore prop to FeedPage widget to track pagination state
- Show "You're all caught up!" message with checkmark when feed ends
- Add _shouldShowFooter getter for cleaner footer logic
- Prevents confusing bounce behavior when reaching end of feed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Adjust post detail text sizing to match feed (title 16px, text 14px)
- Remove bold styling from title and author handle
- Reduce author avatar size from 24px to 20px
- Keep external embed images at same height as feed view (180px)
- Add sources section for megathread posts with clickable source links
- Add EmbedSource model with URI validation and security checks
- Add SourceLinkBar widget matching ExternalLinkBar styling
- Improve ValueKey comment clarity in feed_page.dart
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add external link embed support (link cards with thumbnail, title, description)
- Add BlueskyExternalEmbed model with domain extraction
- Improve quoted posts: show handle, timestamp, media placeholder
- Handle unavailable quoted posts (blocked, deleted, detached)
- Add official Bluesky SVG icons (reply, repost, like, logo)
- Match Bluesky's dim theme colors exactly
- Remove text truncation for posts (Bluesky has 300 char limit)
- Add formatCount and formatFullDateTime utilities
- Add comprehensive tests for Bluesky post models (67 tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add UI components to render Bluesky crosspost embeds in feeds with
Bluesky-styled post cards.
New files:
- lib/models/bluesky_post.dart: Data models for BlueskyPostEmbed,
BlueskyPostResult with URL helpers and robust JSON validation
- lib/constants/bluesky_colors.dart: Bluesky brand color palette
- lib/widgets/bluesky_action_bar.dart: Disabled action bar showing
engagement counts (view-only)
- lib/widgets/bluesky_post_card.dart: Main card widget with avatar,
author info, text, media placeholder, quote posts, and action bar
Changes:
- lib/models/post.dart: Add BlueskyPostEmbed parsing for
social.coves.embed.post type, fix nullable text field handling
- lib/widgets/post_card.dart: Conditionally render BlueskyPostCard
when embed is present
- lib/services/coves_api_service.dart: Add catch blocks for parsing
errors to prevent silent failures
Features:
- 42px circular avatar (tappable → opens Bluesky profile)
- Author name, handle, and relative timestamp
- Post text with max 6 lines
- Media placeholder with "View on Bluesky" link
- Nested quote post support (1 level)
- Disabled action bar with reply/repost/like counts
- "View on Bluesky" footer link
- Graceful unavailable post handling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add CreateCommunityResponse model with defensive JSON parsing
- Add createCommunity() API method to CovesApiService
- Transform CommunitiesScreen to admin-aware with creation form
- Admin check via handle (coves.social, alex.local.coves.dev)
- Backend enforces actual permissions via DID allowlist
- DNS-valid name validation with regex
- Proper listener cleanup to prevent memory leaks
- Cached API service instance per screen lifecycle
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update formatHandleForDisplay to handle both formats:
- New: c-gaming.coves.social → !gaming@coves.social
- Legacy: gaming.community.coves.social → !gaming@coves.social
Add null fallback in PostCard._buildCommunityHandle to prevent
crashes when handle format is unrecognized.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add atproto provider logos to login and landing screens
- Add errorBuilder to all SVG assets for graceful degradation
- Improve sign-in error messages with user-friendly text
- Replace hardcoded colors with AppColors constants
- Extract help dialog to dedicated method for cleaner code
- Add keyboard dismiss on tap outside input field
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Multi-feed state management with security hardening:
- FeedState model with sentinel copyWith pattern for nullable field clearing
- MultiFeedProvider with per-feed state and cross-session security guards
- FeedPage widget with pull-to-refresh on empty state
- FeedScreen auth sync and lazy feed loading
- Comprehensive tests for new architecture
Fixes:
- copyWith can now clear nullable fields (cursor, error, lastRefreshTime)
- Cross-session data leaks prevented via DID comparison
- Empty feed states are now refreshable
- PageController syncs with auth state on sign-out
- For You tab loads on first access after sign-in
Addresses code quality checks from CODE_QUALITY_GUIDE.md:
- Use cascade notation where appropriate (cascade_invocations)
- Put control body on separate line (always_put_control_body_on_new_line)
- Break long string to stay under 80 chars (lines_longer_than_80_chars)
- Apply dart format to all changed files
All files now pass:
- dart format --output=none --set-exit-if-changed
- flutter analyze (0 issues)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates provider registration to use MultiFeedProvider instead of
FeedProvider. The new provider is initialized with:
- CovesApiService for API calls
- AuthProvider for auth state and session identity
- VoteProvider for vote state initialization from feed responses
Also includes pubspec.lock updates from dependency resolution.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates widget tests for the new MultiFeedProvider architecture:
- FakeMultiFeedProvider replaces FakeFeedProvider
- Supports per-feed state management (FeedType parameter)
- Uses sentinel-compatible copyWith for state mutations
- Tests cover both authenticated and unauthenticated flows
- Tests for PageView swipe navigation when authenticated
- Tests for single-feed display when not authenticated
Removes orphaned test/providers/feed_provider_test.dart that
referenced the deleted FeedProvider.
Updates test/widget_test.dart counter test with provider setup.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Refactors FeedScreen to work with MultiFeedProvider and adds several
UX and reliability improvements:
PageView for feed switching:
- Authenticated users can swipe between Discover and For You
- Unauthenticated users see only Discover (no PageView)
- Per-feed ScrollControllers with position restoration
Auth state synchronization:
- Listens to AuthProvider changes
- Jumps PageController to page 0 on sign-out to match provider state
- Prevents tab/page mismatch after re-authentication
Lazy feed loading (_ensureFeedLoaded):
- Triggers initial load when switching to an unloaded feed
- Handles case where user signs in after app start and taps For You
- Called from both tab tap and swipe navigation
This fixes issues where:
- For You tab showed empty state after signing in mid-session
- PageController stayed on page 1 after sign-out while provider
switched to Discover, causing misalignment on re-auth
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extracts feed rendering logic into a reusable FeedPage widget that
handles all feed states:
- Loading: Centered CircularProgressIndicator
- Error: User-friendly message with Retry button
- Empty: Contextual message based on auth state
- Posts: ListView.builder with pagination support
Key improvements:
- Empty state now wrapped in RefreshIndicator with CustomScrollView
and SliverFillRemaining, allowing pull-to-refresh when feed is empty
- Error messages are user-friendly (maps technical errors to readable text)
- Loading more indicator at list bottom during pagination
- Proper scroll controller integration for infinite scroll
This fixes an issue where unauthenticated users with an empty Discover
feed had no way to retry without restarting the app.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replaces FeedProvider with MultiFeedProvider that manages separate
state for Discover and For You feeds. Key improvements:
Architecture:
- Per-feed state management using Map<FeedType, FeedState>
- Centralized _fetchFeed helper eliminates code duplication
- Auth-aware feed switching (For You requires authentication)
- Minute-based time updates for relative timestamps
Security (cross-session data leak prevention):
- Captures session DID before fetch to detect auth changes
- Discards For You responses if session changed during fetch
- Guards both success and error paths to prevent stale data
- Removes feed state entirely on session change (not copyWith)
This prevents scenarios where:
- User signs out during fetch → old data reappears
- User A signs out, User B signs in → User A's feed shown to B
- Fetch errors after sign-out → stale posts restored
Removes the old single-feed FeedProvider.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduces FeedState as an immutable state container for per-feed data
(Discover and For You feeds). Key features:
- Holds posts, cursor, loading states, error, scroll position, and
last refresh time
- Uses sentinel pattern for copyWith to distinguish "not provided"
from "explicitly set to null" for nullable fields (cursor, error,
lastRefreshTime)
- Enables proper clearing of fields on refresh/error recovery
The sentinel pattern fixes a bug where nullable fields couldn't be
cleared back to null through copyWith - critical for pagination
cursor handling when reaching the end of a feed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move provider initialization from postFrameCallback to didChangeDependencies
for synchronous access before first build. Create ScrollController with
initialScrollOffset set to cached position, eliminating the visible flash
from loading → content at top → jump to cached position.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
View.of(context) was being called in didChangeMetrics() during widget
deactivation, causing "Looking up a deactivated widget's ancestor"
errors. The context becomes invalid before mounted becomes false.
Fixed by caching the FlutterView reference in didChangeDependencies()
for both _ReplyScreenState and _ReplyToolbarState, then using the
cached reference in didChangeMetrics().
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add create post functionality with community picker:
Features:
- Community models for API responses
- listCommunities and createPost API endpoints
- Community picker screen with search and pagination
- Create post screen with URL validation and input limits
- Optimistic post navigation support
Tests:
- 76 tests covering models, API, and widget behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Comprehensive test coverage for community features:
Unit tests:
- CommunitiesResponse, CommunityView JSON parsing
- CreatePostResponse, ExternalEmbedInput serialization
- SelfLabels, SelfLabel const construction and serialization
- CovesApiService.listCommunities with pagination/errors
- CovesApiService.createPost with validation/errors
Widget tests:
- CreatePostScreen UI elements and form validation
- Character limits enforcement (title: 300, content: 10000)
- NSFW toggle, URL field show/hide behavior
Logic tests:
- Community search filtering (name, displayName, description)
- Member count formatting (K/M suffixes)
- Description line building with separators
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add isOptimistic flag to PostDetailScreen to skip initial comment
loading for newly created posts that haven't been indexed yet.
Also adds AlwaysScrollableScrollPhysics for consistent pull-to-refresh.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Create post screen improvements:
- Community picker integration
- URL validation (http/https only)
- Input length limits from backend lexicon (title: 300, content: 10000)
- NSFW toggle with self-labels
- Language selection dropdown
- Navigate to feed after successful post creation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Full-screen community selection interface for post creation:
- Search with debounced client-side filtering
- Infinite scroll pagination
- Cached avatar images with error fallback
- Proper authenticated API service with disposal
- Member/subscriber count formatting (K/M suffixes)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add CovesApiService methods for community operations:
- listCommunities(): Fetch communities with pagination and sorting
- createPost(): Create posts with title, content, embeds, and labels
Both methods include proper error handling and debug logging.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add data models for community-related API operations:
- CommunitiesResponse and CommunityView for listing communities
- CommunityViewerState for subscription/membership status
- CreatePostRequest and CreatePostResponse for post creation
- ExternalEmbedInput for link embeds
- SelfLabels and SelfLabel for content labels (NSFW, etc.)
All models support const constructors for better performance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updates feed tests to reflect the new feed type tab behavior:
- Default feed is now Discover (not timeline)
- Authenticated users see both Discover and For You tabs
- Unauthenticated users see only Discover tab
- Updated test descriptions for clarity
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates tests to work with the new CommentsProvider constructor that
requires postUri and postCid parameters.
Changes:
- CommentsProvider tests: pass postUri/postCid in constructor, remove
parameters from loadComments calls
- Add MockCommentsProvider helper for widget tests
- Update FocusedThreadScreen tests to provide commentsProvider parameter
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates PostDetailScreen, ReplyScreen, and FocusedThreadScreen to use
the new CommentsProviderCache for instant back-navigation and state preservation.
PostDetailScreen:
- Acquires provider from cache with reference counting
- Restores scroll position when returning to cached comments
- Background refresh if data is stale (>5 min)
- Handles sign-out by navigating back to feed
ReplyScreen:
- Requires CommentsProvider parameter for draft access
- Saves draft text on cancel, restores on reopen
- Per-parent-URI drafts (separate drafts for different reply contexts)
- Auto-closes on sign-out
FocusedThreadScreen:
- Passes CommentsProvider to children for consistent draft/vote state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduces a caching layer for CommentsProvider instances to enable instant
back-navigation when returning to previously viewed posts.
Key changes:
- Add CommentsProviderCache with LRU eviction (15 posts max)
- Refactor CommentsProvider to be immutable per post (postUri/postCid in constructor)
- Add reference counting to prevent evicting in-use providers
- Add scroll position and draft text preservation to CommentsProvider
- Add staleness tracking for background refresh of cached data
- Wire up cache in main.dart with sign-out cleanup
The cache automatically disposes providers when evicted and clears all
providers on sign-out for privacy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New features:
- FocusedThreadScreen for viewing deep comment threads
- "Read X more replies" link at maxDepth navigates to focused view
- Ancestors shown flat above anchor, replies threaded below
- Auto-scroll to anchor comment on open
Performance & code quality:
- Fix O(n²) descendant counting - only compute when needed at maxDepth
- Extract threading colors to shared kThreadingColors constant
- Remove unused Consumer<VoteProvider> wrapper
- Extract StatusBarOverlay reusable widget
Tests:
- Add unit tests for countDescendants
- Add widget tests for CommentThread max-depth behavior
- Add widget tests for FocusedThreadScreen rendering
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add support for --dart-define=ENV=dev as a convenience shorthand
alongside the existing ENVIRONMENT override. Maps 'dev'/'local' to
local environment and 'prod'/'production' to production.
Also remove macos Flutter config files from tracking (already in gitignore).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Animation improvements:
- Increase collapse duration to 350ms expand / 280ms collapse
- Add combined fade + size + slide transitions
- Content slides down from parent on expand, up on collapse
- Add ClipRect to prevent overflow on nested threads
- Badge now uses scale + opacity animation with easeOutBack bounce
Compact collapsed state:
- Hide comment content when collapsed (only show avatar + username)
- Move "+X hidden" badge to right side, simplified to "+X"
- Reduce padding in collapsed state
PR review fixes:
- Return unmodifiable Set from collapsedComments getter
- Add accessibility Semantics with collapse/expand hints
- Add 6 unit tests for collapse state management
Also includes dart format fixes across touched files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add collapsed comment tracking to CommentsProvider with toggleCollapsed()
- Add long-press gesture on CommentCard with haptic feedback
- Show "+N hidden" badge when thread is collapsed (depth-aware positioning)
- Animate collapse/expand with AnimatedSwitcher + SizeTransition (200ms)
- Only build replies widget when not collapsed (optimization)
- Wire up collapse state in PostDetailScreen
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add FeedType enum (discover/forYou) with feed switching in FeedProvider
- Replace AppBar with transparent gradient header overlay
- Add Discover/For You tabs with underline indicator (auth-gated)
- Rename Search tab to Communities with Workspaces icon
- Use IndexedStack to preserve screen state on tab switch
- Add accessibility labels and extract magic numbers to constants
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Comment creation feature with backend integration:
- CommentService for API calls
- Shared auth interceptor (401 retry)
- Input validation (10k chars, emoji-aware)
- Tap-to-reply UI for nested comments
- 22 new tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
CommentService tests (10 tests):
- Successful comment creation
- Auth failure when no session
- Network error handling
- 401 response handling
- Invalid responses (null data, missing uri, empty uri)
- Server error handling
- Nested reply request format
CommentsProvider.createComment tests (12 tests):
- Validation: empty content, whitespace-only, exceeds limit
- Emoji counting with grapheme clusters
- Error states: no post loaded, no CommentService
- Top-level comment (reply to post)
- Nested comment (reply to comment)
- Content trimming
- Refresh after success
- Exception propagation
- Boundary testing at max length
All 52 tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Pass postCid to loadComments for reply reference support
- Add onReplyTap callback to CommentThread and CommentCard
- Tapping reply icon on a comment navigates to ReplyScreen
- ReplyScreen receives parent comment for nested replies
- Show "Replying to @handle" context in reply screen
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add CommentService dependency to CommentsProvider
- Add createComment() method supporting both post and comment replies
- Store postCid alongside postUri for proper reply references
- Add input validation: 10k char limit using grapheme clusters
- Proper emoji counting (🎉 = 1 char, not 2)
- Wire up CommentService in main.dart
Reply reference logic:
- Reply to post: root=post, parent=post
- Reply to comment: root=post, parent=comment
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create auth_interceptor.dart with reusable createAuthInterceptor()
- Handles 401 responses by refreshing token and retrying once
- Sign out user if refresh fails to prevent infinite loops
- Refactor VoteService to use shared interceptor
- CommentService already uses it (from previous commit)
Eliminates ~130 lines of duplicated interceptor code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add CommentService with createComment() method that calls backend API
- Backend handles PDS writes via OAuth/DPoP (sealed token architecture)
- Add ValidationException to api_exceptions for client-side validation
- Add characters package for proper Unicode grapheme cluster counting
The comment creation flow:
Mobile → Coves Backend (sealed token) → User's PDS (DPoP)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update mock providers to remove VoteService dependency
- Add ViewerState to test fixtures for feed and comments
- Update FeedProvider tests for vote state initialization
- Update CommentsProvider tests for recursive vote state init
- Update VoteProvider tests for extractRkeyFromUri utility
- Remove obsolete vote service test methods (deleteVote, getUserVotes)
- Add generated mock files for auth service tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
main.dart:
- Remove voteService parameter from FeedProvider and CommentsProvider
CommentsProvider:
- Remove VoteService dependency - vote state from response viewer data
- Add _initializeCommentVoteState helper for recursive vote initialization
- On refresh: initialize all comments (server data is truth)
- On pagination: only initialize new comments (preserve optimistic state)
This completes the migration from VoteService.getUserVotes() to using
viewer state from API responses for both feed posts and comments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove VoteService dependency - vote state comes from feed response
- Replace getUserVotes + loadInitialVotes with per-post setInitialVoteState
- Use viewer.vote and viewer.voteUri from backend response
- Call setInitialVoteState for ALL posts to handle cross-device vote removal
This fixes the bug where votes would disappear on feed refresh:
1. Backend now populates viewer state from PDS cache
2. Feed provider initializes VoteProvider state from viewer data
3. Score adjustments are cleared to prevent double-counting
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Vote Service:
- Remove deleteVote method - backend handles toggle logic
- Remove getUserVotes - vote state now comes from feed viewer data
- Remove unused ExistingVote and VoteInfo classes
- Handle empty uri/cid response as successful toggle-off
- Use shared extractRkeyFromUri utility
Vote Provider:
- Remove existingVoteRkey/Direction params from createVote call
- Remove loadInitialVotes - replaced by setInitialVoteState per-post
- Add extractRkeyFromUri static utility to VoteState
- Clear score adjustments in setInitialVoteState to prevent double-counting
This aligns with the backend's vote cache approach where viewer state
is populated from PDS on each request rather than relying on the
eventually-consistent AppView index.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add ViewerState class to represent the viewer's relationship with posts:
- vote: direction ("up", "down", or null)
- voteUri: AT-URI of the vote record
- saved: bookmark status
- tags: user-applied tags
This enables the feed to include viewer-specific state from the backend,
allowing proper initialization of vote UI state on feed refresh.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major OAuth architecture rework - delegate complexity to backend.
Key changes:
- Add CovesSession model for simplified sealed tokens
- Add CovesAuthService for backend-delegated OAuth
- Update OAuth config for private-use URI scheme (RFC 8252)
- Add automatic token refresh on 401 responses
- Remove atproto_oauth_flutter package (~14K lines)
The backend now handles all OAuth complexity (DPoP, PKCE, token exchange)
and returns opaque sealed tokens that the client simply stores and sends.
Update feed_screen_test.dart for the new OAuth patterns.
iOS changes:
- Add Runner.entitlements for associated domains
- Enable Universal Links (applinks:coves.social)
- Reference entitlements in Xcode project
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove the client-side OAuth implementation now that auth is delegated
to the Coves backend. This eliminates ~14,000 lines of complex OAuth
code that handled:
Removed oauth_service.dart:
- Complex OAuthSession management
- Client-side token refresh
- DPoP key generation and proof signing
- PKCE code verifier/challenge generation
Removed atproto_oauth_flutter package:
- DPoP implementation (fetch_dpop.dart)
- Identity resolution (did/handle resolvers)
- OAuth server discovery and metadata
- Token exchange and refresh logic
- Cryptographic key management
- Session state persistence
The backend now handles all of this complexity, returning opaque
sealed tokens that the client simply stores and sends.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Wire up VoteService with the new session getter and auth callbacks
for the backend-delegated OAuth architecture.
Key changes:
- VoteService now uses sessionGetter instead of direct OAuthSession
- Add tokenRefresher callback for automatic 401 recovery
- Add signOutHandler callback for failed refresh cleanup
- Remove OAuthService initialization (deleted)
The new flow ensures votes go through the Coves backend which
has the DPoP keys needed to write to user PDSs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update FeedProvider and CommentsProvider to work with the simplified
session model and backend-proxied auth flow.
Key changes:
- Use VoteService callback pattern instead of OAuthSession
- Remove direct PDS URL handling
- Simplify test mocks to match new API
Provider updates:
- FeedProvider: Use token getter instead of session getter
- CommentsProvider: Same simplification
Test updates:
- Update mocks to use CovesSession instead of OAuthSession
- Remove PDS URL getter mocks
- Simplify vote service setup in tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>