fix(community): support new c-prefix handle format

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>

Changed files
+29 -17
lib
+20 -16
lib/utils/community_handle_utils.dart
··· 1 1 /// Utility functions for community handle formatting and resolution. 2 2 /// 3 3 /// Coves communities use atProto handles in the format: 4 - /// - DNS format: `gaming.community.coves.social` 4 + /// - DNS format (new): `c-gaming.coves.social` 5 + /// - DNS format (legacy): `gaming.community.coves.social` 5 6 /// - Display format: `!gaming@coves.social` 6 7 class CommunityHandleUtils { 7 8 /// Converts a DNS-style community handle to display format 8 9 /// 9 - /// Transforms `gaming.community.coves.social` → `!gaming@coves.social` 10 - /// by removing the `.community.` segment 10 + /// Supports both formats: 11 + /// - New: `c-gaming.coves.social` → `!gaming@coves.social` 12 + /// - Legacy: `gaming.community.coves.social` → `!gaming@coves.social` 11 13 /// 12 - /// Returns null if the handle is null or doesn't contain `.community.` 14 + /// Returns null if the handle is null or doesn't match expected formats 13 15 static String? formatHandleForDisplay(String? handle) { 14 16 if (handle == null || handle.isEmpty) { 15 17 return null; 16 18 } 17 19 18 - // Expected format: name.community.instance.domain 19 - // e.g., gaming.community.coves.social 20 20 final parts = handle.split('.'); 21 21 22 - // Must have at least 4 parts: [name, community, instance, domain] 23 - if (parts.length < 4 || parts[1] != 'community') { 24 - return null; 22 + // New format: c-name.instance.domain (e.g., c-gaming.coves.social) 23 + if (parts.length >= 3 && parts[0].startsWith('c-')) { 24 + final communityName = parts[0].substring(2); // Remove 'c-' prefix 25 + final instanceDomain = parts.sublist(1).join('.'); 26 + return '!$communityName@$instanceDomain'; 25 27 } 26 28 27 - // Extract community name (first part) 28 - final communityName = parts[0]; 29 - 30 - // Extract instance domain (everything after .community.) 31 - final instanceDomain = parts.sublist(2).join('.'); 29 + // Legacy format: name.community.instance.domain 30 + // e.g., gaming.community.coves.social 31 + if (parts.length >= 4 && parts[1] == 'community') { 32 + final communityName = parts[0]; 33 + final instanceDomain = parts.sublist(2).join('.'); 34 + return '!$communityName@$instanceDomain'; 35 + } 32 36 33 - // Format as !name@instance 34 - return '!$communityName@$instanceDomain'; 37 + // Unknown format - return null 38 + return null; 35 39 } 36 40 37 41 /// Converts a display-style community handle to DNS format
+9 -1
lib/widgets/post_card.dart
··· 262 262 /// Builds the community handle with styled parts (name + instance) 263 263 Widget _buildCommunityHandle(CommunityRef community) { 264 264 final displayHandle = 265 - CommunityHandleUtils.formatHandleForDisplay(community.handle)!; 265 + CommunityHandleUtils.formatHandleForDisplay(community.handle); 266 + 267 + // Fallback to raw handle or name if formatting fails 268 + if (displayHandle == null || !displayHandle.contains('@')) { 269 + return Text( 270 + community.handle ?? community.name, 271 + style: const TextStyle(color: AppColors.communityName, fontSize: 14), 272 + ); 273 + } 266 274 267 275 // Split the handle into community name and instance 268 276 // Format: !gaming@coves.social