diff --git a/go-backend/internal/models/notification.go b/go-backend/internal/models/notification.go index 150e3c9..e0df6de 100644 --- a/go-backend/internal/models/notification.go +++ b/go-backend/internal/models/notification.go @@ -33,17 +33,18 @@ const ( ) type Notification struct { - ID uuid.UUID `json:"id" db:"id"` - UserID uuid.UUID `json:"user_id" db:"user_id"` - Type string `json:"type" db:"type"` - ActorID uuid.UUID `json:"actor_id" db:"actor_id"` - PostID *uuid.UUID `json:"post_id,omitempty" db:"post_id"` - CommentID *uuid.UUID `json:"comment_id,omitempty" db:"comment_id"` - IsRead bool `json:"is_read" db:"is_read"` - CreatedAt time.Time `json:"created_at" db:"created_at"` - Metadata json.RawMessage `json:"metadata" db:"metadata"` - GroupKey *string `json:"group_key,omitempty" db:"group_key"` - Priority string `json:"priority" db:"priority"` + ID uuid.UUID `json:"id" db:"id"` + UserID uuid.UUID `json:"user_id" db:"user_id"` + Type string `json:"type" db:"type"` + ActorID uuid.UUID `json:"actor_id" db:"actor_id"` + PostID *uuid.UUID `json:"post_id,omitempty" db:"post_id"` + CommentID *uuid.UUID `json:"comment_id,omitempty" db:"comment_id"` + IsRead bool `json:"is_read" db:"is_read"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + ArchivedAt *time.Time `json:"archived_at,omitempty" db:"archived_at"` + Metadata json.RawMessage `json:"metadata" db:"metadata"` + GroupKey *string `json:"group_key,omitempty" db:"group_key"` + Priority string `json:"priority" db:"priority"` // Joined fields for display ActorHandle string `json:"actor_handle" db:"actor_handle"` diff --git a/go-backend/internal/repository/notification_repository.go b/go-backend/internal/repository/notification_repository.go index 042e6f9..d09d58d 100644 --- a/go-backend/internal/repository/notification_repository.go +++ b/go-backend/internal/repository/notification_repository.go @@ -107,12 +107,12 @@ func (r *NotificationRepository) CreateNotification(ctx context.Context, notif * func (r *NotificationRepository) GetNotifications(ctx context.Context, userID string, limit, offset int, includeArchived bool) ([]models.Notification, error) { whereClause := "WHERE n.user_id = $1::uuid AND n.archived_at IS NULL" if includeArchived { - whereClause = "WHERE n.user_id = $1::uuid" + whereClause = "WHERE n.user_id = $1::uuid AND n.archived_at IS NOT NULL" } query := ` SELECT - n.id, n.user_id, n.type, n.actor_id, n.post_id, n.comment_id, n.is_read, n.created_at, n.metadata, + n.id, n.user_id, n.type, n.actor_id, n.post_id, n.comment_id, n.is_read, n.created_at, n.archived_at, n.metadata, COALESCE(n.group_key, '') as group_key, COALESCE(n.priority, 'normal') as priority, pr.handle, pr.display_name, COALESCE(pr.avatar_url, ''), @@ -138,7 +138,7 @@ func (r *NotificationRepository) GetNotifications(ctx context.Context, userID st var postImageURL *string var postBody *string err := rows.Scan( - &n.ID, &n.UserID, &n.Type, &n.ActorID, &n.PostID, &n.CommentID, &n.IsRead, &n.CreatedAt, &n.Metadata, + &n.ID, &n.UserID, &n.Type, &n.ActorID, &n.PostID, &n.CommentID, &n.IsRead, &n.CreatedAt, &n.ArchivedAt, &n.Metadata, &groupKey, &n.Priority, &n.ActorHandle, &n.ActorDisplayName, &n.ActorAvatarURL, &postImageURL, &postBody, @@ -160,7 +160,7 @@ func (r *NotificationRepository) GetNotifications(ctx context.Context, userID st func (r *NotificationRepository) GetGroupedNotifications(ctx context.Context, userID string, limit, offset int, includeArchived bool) ([]models.Notification, error) { whereClause := "WHERE n.user_id = $1::uuid AND n.archived_at IS NULL" if includeArchived { - whereClause = "WHERE n.user_id = $1::uuid" + whereClause = "WHERE n.user_id = $1::uuid AND n.archived_at IS NOT NULL" } query := ` @@ -180,7 +180,7 @@ func (r *NotificationRepository) GetGroupedNotifications(ctx context.Context, us ` + whereClause + ` ) SELECT - id, user_id, type, actor_id, post_id, comment_id, is_read, created_at, metadata, + id, user_id, type, actor_id, post_id, comment_id, is_read, created_at, archived_at, metadata, COALESCE(group_key, '') as group_key, COALESCE(priority, 'normal') as priority, actor_handle, actor_display_name, actor_avatar_url, @@ -203,7 +203,7 @@ func (r *NotificationRepository) GetGroupedNotifications(ctx context.Context, us var n models.Notification var groupKey string err := rows.Scan( - &n.ID, &n.UserID, &n.Type, &n.ActorID, &n.PostID, &n.CommentID, &n.IsRead, &n.CreatedAt, &n.Metadata, + &n.ID, &n.UserID, &n.Type, &n.ActorID, &n.PostID, &n.CommentID, &n.IsRead, &n.CreatedAt, &n.ArchivedAt, &n.Metadata, &groupKey, &n.Priority, &n.ActorHandle, &n.ActorDisplayName, &n.ActorAvatarURL, &n.PostImageURL, &n.PostBody, diff --git a/sojorn_app/lib/screens/notifications/notifications_screen.dart b/sojorn_app/lib/screens/notifications/notifications_screen.dart index a980e31..9fe45bd 100644 --- a/sojorn_app/lib/screens/notifications/notifications_screen.dart +++ b/sojorn_app/lib/screens/notifications/notifications_screen.dart @@ -10,6 +10,9 @@ import '../../widgets/app_scaffold.dart'; import '../../widgets/media/signed_media_image.dart'; import '../profile/viewable_profile_screen.dart'; import '../post/post_detail_screen.dart'; +import '../search/search_screen.dart'; +import '../discover/discover_screen.dart'; +import '../secure_chat/secure_chat_full_screen.dart'; import 'package:go_router/go_router.dart'; import '../../services/notification_service.dart'; @@ -51,6 +54,7 @@ class _NotificationsScreenState extends ConsumerState { void dispose() { _authSub?.cancel(); _tabController?.dispose(); + NotificationService.instance.refreshBadge(); super.dispose(); } @@ -108,28 +112,18 @@ class _NotificationsScreenState extends ConsumerState { ); if (mounted) { - // If showing Active, filter out anything archived - final filtered = !showArchived - ? notifications - .where((item) => - item.archivedAt == null && - !_locallyArchivedIds.contains(item.id)) - .toList() - // If showing Archived, only show archived items - : notifications - .where((item) => - item.archivedAt != null || - _locallyArchivedIds.contains(item.id)) - .toList(); + // Backend handles active vs archived filtering + final filtered = notifications + .where((item) => !_locallyArchivedIds.contains(item.id)) + .toList(); - final fetchedCount = notifications.length; setState(() { if (refresh) { _notifications = filtered; } else { _notifications.addAll(filtered); } - _hasMore = fetchedCount == 20; + _hasMore = notifications.length == 20; }); } } catch (e) { @@ -358,6 +352,25 @@ class _NotificationsScreenState extends ConsumerState { } } + void _navigateHome() { + Navigator.of(context).pop(); + } + + void _navigateSearch() { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const DiscoverScreen()), + ); + } + + void _navigateChat() { + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (_) => const SecureChatFullScreen(), + fullscreenDialog: true, + ), + ); + } + @override Widget build(BuildContext context) { final canArchiveAll = _activeTabIndex == 0 && _notifications.isNotEmpty; @@ -365,11 +378,8 @@ class _NotificationsScreenState extends ConsumerState { return DefaultTabController( length: 2, child: AppScaffold( - title: 'Notifications', - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.of(context).pop(), - ), + title: 'Activity', + leading: const SizedBox.shrink(), actions: [ if (canArchiveAll) TextButton( @@ -400,6 +410,7 @@ class _NotificationsScreenState extends ConsumerState { Tab(text: 'Archived'), ], ), + bottomNavigationBar: _buildBottomNav(), body: _error != null ? _ErrorState( message: _error!, @@ -414,7 +425,6 @@ class _NotificationsScreenState extends ConsumerState { padding: const EdgeInsets.symmetric(vertical: 8), itemBuilder: (context, index) { if (index == _notifications.length) { - // Load more indicator if (!_isLoading) { _loadNotifications(); } @@ -428,7 +438,6 @@ class _NotificationsScreenState extends ConsumerState { final notification = _notifications[index]; if (_activeTabIndex == 0) { - // Swipe to archive in Active tab return Dismissible( key: Key('notif_${notification.id}'), direction: DismissDirection.endToStart, @@ -466,7 +475,6 @@ class _NotificationsScreenState extends ConsumerState { ), ); } else { - // No swipe to archive in Archived tab return _NotificationItem( notification: notification, onTap: notification.type == NotificationType.follow_request @@ -480,6 +488,86 @@ class _NotificationsScreenState extends ConsumerState { ), ); } + + Widget _buildBottomNav() { + return Container( + decoration: BoxDecoration( + color: AppTheme.scaffoldBg, + border: Border( + top: BorderSide( + color: AppTheme.egyptianBlue.withOpacity(0.1), + width: 0.5, + ), + ), + ), + child: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildNavItem( + icon: Icons.home_outlined, + label: 'Home', + onTap: _navigateHome, + ), + _buildNavItem( + icon: Icons.search, + label: 'Discover', + onTap: _navigateSearch, + ), + _buildNavItem( + icon: Icons.notifications, + label: 'Activity', + isActive: true, + onTap: () {}, + ), + _buildNavItem( + icon: Icons.chat_bubble_outline, + label: 'Chat', + onTap: _navigateChat, + ), + ], + ), + ), + ), + ); + } + + Widget _buildNavItem({ + required IconData icon, + required String label, + required VoidCallback onTap, + bool isActive = false, + }) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + color: isActive ? AppTheme.navyBlue : Colors.grey, + size: 26, + ), + const SizedBox(height: 2), + Text( + label, + style: TextStyle( + fontSize: 10, + color: isActive ? AppTheme.navyBlue : Colors.grey, + fontWeight: isActive ? FontWeight.w600 : FontWeight.normal, + ), + ), + ], + ), + ), + ); + } } /// Individual notification item widget diff --git a/sojorn_docs/TODO.md b/sojorn_docs/TODO.md index 0dc2f5a..13477fe 100644 --- a/sojorn_docs/TODO.md +++ b/sojorn_docs/TODO.md @@ -1,311 +1,229 @@ -# Sojorn Development TODO List +# Sojorn Development TODO -**Last Updated**: January 30, 2026 -**Status**: Ready for 100% Go Backend Migration -**Estimated Effort**: ~12 hours total +**Last Updated**: February 6, 2026 --- -## 🎯 **High Priority Tasks** (Critical for 100% Go Backend) +## 🎯 High Priority — Feature Work -### **1. Implement Beacon Voting Endpoints** ⚡ -**Location**: `go-backend/internal/handlers/post_handler.go` -**Status**: Not Started -**Effort**: 2 hours +### 1. Finalize AI Moderation — Image & Video +**Status**: In Progress +Text moderation is live (OpenAI Moderation API). Image and video moderation not yet implemented. -**Tasks:** -- [ ] Add `VouchBeacon` handler method -- [ ] Add `ReportBeacon` handler method -- [ ] Add `RemoveBeaconVote` handler method -- [ ] Implement database operations for beacon votes -- [ ] Add proper error handling and validation +- [ ] Add image moderation to post creation flow (send image to OpenAI or Vision API) +- [ ] Add video moderation via thumbnail extraction (grab first frame or key frame) +- [ ] Run extracted thumbnail through same image moderation pipeline +- [ ] Flag content that exceeds thresholds into `moderation_flags` table +- [ ] Wire into existing Three Poisons scoring (Hate, Greed, Delusion) +- [ ] Add admin queue visibility for image/video flags -**Implementation Details:** -```go -func (h *PostHandler) VouchBeacon(c *gin.Context) { - // Get user ID from context - // Get beacon ID from params - // Check if user already voted - // Add vote to database - // Update beacon confidence score - // Return success response -} -``` - -### **2. Add Beacon Voting Routes** ⚡ -**Location**: `go-backend/cmd/api/main.go` -**Status**: Not Started -**Effort**: 30 minutes - -**Tasks:** -- [ ] Add `POST /beacons/:id/vouch` route -- [ ] Add `POST /beacons/:id/report` route -- [ ] Add `DELETE /beacons/:id/vouch` route -- [ ] Ensure proper middleware (auth, rate limiting) - -**Routes to Add:** -```go -authorized.POST("/beacons/:id/vouch", postHandler.VouchBeacon) -authorized.POST("/beacons/:id/report", postHandler.ReportBeacon) -authorized.DELETE("/beacons/:id/vouch", postHandler.RemoveBeaconVote) -``` - -### **3. Update Flutter Beacon API** ⚡ -**Location**: `sojorn_app/lib/services/api_service.dart` -**Status**: Not Started -**Effort**: 1 hour - -**Tasks:** -- [ ] Replace empty `vouchBeacon()` method -- [ ] Replace empty `reportBeacon()` method -- [ ] Replace empty `removeBeaconVote()` method -- [ ] Add proper error handling -- [ ] Update method signatures to match Go API - -**Current State:** -```dart -// These methods are empty stubs that need implementation: -Future vouchBeacon(String beaconId) async { - // Migrate to Go API - EMPTY STUB -} - -Future reportBeacon(String beaconId) async { - // Migrate to Go API - EMPTY STUB -} - -Future removeBeaconVote(String beaconId) async { - // Migrate to Go API - EMPTY STUB -} -``` +**Backend**: `go-backend/internal/handlers/post_handler.go` (CreatePost flow) +**Key decision**: Use OpenAI Vision API for images, ffmpeg thumbnail extraction for video on the server side --- -## ⚠️ **Medium Priority Tasks** (Cleanup & Technical Debt) +### 2. Quips — Complete Video Recorder & Editor Overhaul +**Status**: Needs major work +Current recorder is basic. Goal: TikTok/Instagram-level recording and editing experience. -### **4. Remove Supabase Function Proxy** -**Location**: `go-backend/internal/handlers/function_proxy.go` -**Status**: Not Started -**Effort**: 1 hour +- [ ] Multi-segment recording with pause/resume +- [ ] Speed control (0.5x, 1x, 2x, 3x) +- [ ] Filters and effects (color grading, beauty mode) +- [ ] Text overlays with timing and positioning +- [ ] Music/audio overlay from library or device +- [ ] Trim and reorder clips +- [ ] Transitions between segments +- [ ] Preview before posting +- [ ] Progress indicator during upload +- [ ] Thumbnail selection for posted quip -**Tasks:** -- [ ] Delete `function_proxy.go` file -- [ ] Remove function proxy handler instantiation -- [ ] Remove `/functions/:name` route from `main.go` -- [ ] Remove related environment variables -- [ ] Test that no functionality is broken - -**Files to Remove:** -- `go-backend/internal/handlers/function_proxy.go` -- `go-backend/cmd/supabase-migrate/` (entire directory) - -### **5. Clean Up Supabase Dependencies** -**Location**: Multiple files -**Status**: Not Started -**Effort**: 2 hours - -**Tasks:** -- [ ] Remove `SupabaseID` field from `internal/models/user.go` -- [ ] Remove Supabase environment variables from `.env.example` -- [ ] Update configuration struct in `internal/config/config.go` -- [ ] Remove any remaining Supabase imports -- [ ] Update middleware comments that reference Supabase - -**Environment Variables to Remove:** -```bash -# SUPABASE_URL -# SUPABASE_KEY -# SUPABASE_SERVICE_ROLE_KEY -``` - -### **6. Update Outdated TODO Comments** -**Location**: `sojorn_app/lib/services/api_service.dart` -**Status**: Not Started -**Effort**: 30 minutes - -**Tasks:** -- [ ] Remove comment "Beacon voting still Supabase RPC or migrate?" -- [ ] Remove comment "Summary didn't mention beacon voting endpoint" -- [ ] Update any other misleading Supabase references -- [ ] Ensure all comments reflect current Go backend state - -### **7. End-to-End Testing** -**Location**: Entire application -**Status**: Not Started -**Effort**: 2 hours - -**Tasks:** -- [ ] Test beacon voting flow end-to-end -- [ ] Verify all core features work without Supabase -- [ ] Test media uploads to R2 -- [ ] Test E2EE chat functionality -- [ ] Test push notifications -- [ ] Performance testing -- [ ] Security verification +**Frontend**: `sojorn_app/lib/screens/quips/create/` +**Packages to evaluate**: `camera`, `ffmpeg_kit_flutter`, `video_editor` --- -## 🔧 **Low Priority Tasks** (UI Polish & Code Cleanup) +### 3. Beacon Page Overhaul — Local Safety & Social Awareness +**Status**: Basic beacon system exists (post, vouch, report). Needs full redesign. +Vision: Citizen + Nextdoor but focused on social awareness over fear-mongering. -### **8. Fix Video Comments TODO** -**Location**: `sojorn_app/lib/widgets/video_comments_sheet.dart` -**Status**: Not Started -**Effort**: 1 hour +- [ ] Redesign beacon feed as a local safety dashboard +- [ ] Map view with clustered pins (incidents, community alerts, mutual aid) +- [ ] Beacon categories: Safety Alert, Community Need, Lost & Found, Event, Mutual Aid +- [ ] Verified/official source badges for local orgs +- [ ] "How to help" action items on each beacon (donate, volunteer, share) +- [ ] Tone guidelines — auto-moderate fear-bait and rage-bait language +- [ ] Neighborhood/radius filtering +- [ ] Push notifications for nearby beacons (opt-in) +- [ ] Confidence scoring visible to users (vouch/report ratio) +- [ ] Resolution status (active → resolved → archived) -**Tasks:** -- [ ] Replace simulated API call with real `publishComment()` call -- [ ] Remove "TODO: Implement actual comment posting" comment -- [ ] Test comment posting functionality -- [ ] Ensure proper error handling - -**Current State:** -```dart -// TODO: Implement actual comment posting -await Future.delayed(const Duration(seconds: 1)); // Simulate API call -``` - -**Should Become:** -```dart -await ApiService.instance.publishComment(postId: widget.postId, body: comment); -``` - -### **9. Implement Comment Reply Features** -**Location**: `sojorn_app/lib/widgets/threaded_comment_widget.dart` -**Status**: Not Started -**Effort**: 2 hours - -**Tasks:** -- [ ] Implement actual reply submission in `_submitReply()` -- [ ] Add reply UI components -- [ ] Connect to backend reply API -- [ ] Remove "TODO: Implement actual reply submission" comment - -### **10. Add Post Options Menu** -**Location**: `sojorn_app/lib/widgets/post_with_video_widget.dart` -**Status**: Not Started -**Effort**: 1 hour - -**Tasks:** -- [ ] Implement post options menu functionality -- [ ] Add menu items (edit, delete, report, etc.) -- [ ] Remove "TODO: Show post options" comment -- [ ] Connect to backend APIs - -### **11. Fix Profile Navigation** -**Location**: `sojorn_app/lib/widgets/sojorn_rich_text.dart` -**Status**: Not Started -**Effort**: 1 hour - -**Tasks:** -- [ ] Implement profile navigation from mentions -- [ ] Remove "TODO: Implement profile navigation" comment -- [ ] Add proper navigation logic -- [ ] Test mention navigation - -### **12. Clean Up Debug Code** -**Location**: `sojorn_app/lib/services/simple_e2ee_service.dart` -**Status**: Not Started -**Effort**: 1 hour - -**Tasks:** -- [ ] Remove `_FORCE_KEY_ROTATION` debug flag -- [ ] Remove debug print statements -- [ ] Remove force reset methods for 208-bit key bug -- [ ] Clean up any remaining debug code +**Backend**: `go-backend/internal/handlers/post_handler.go` (beacon endpoints) +**Frontend**: `sojorn_app/lib/screens/beacons/` --- -## ✅ **Already Completed Features** (For Reference) +## ⚠️ Medium Priority — Core Features -### **Core Functionality** -- ✅ User authentication (JWT-based) -- ✅ Post creation, editing, deletion -- ✅ Feed and post retrieval -- ✅ Image/video uploads to R2 -- ✅ **Comment system** (fully implemented) +### 4. User Profile Customization — Modular Widget System +**Status**: Basic profiles exist. Needs a modular, personalized approach. +Vision: New-age MySpace — users pick and arrange profile widgets to make it their own, without chaos. + +**Core Architecture:** +- Profile is a grid/stack of draggable **widgets** the user can add, remove, and reorder +- Each widget is a self-contained component with a fixed max size and style boundary +- Widgets render inside a consistent design system (can't break the layout or go full HTML) +- Profile data stored as a JSON `profile_layout` column: ordered list of widget types + config + +**Standard Fields (always present):** +- [ ] Avatar + display name + handle (non-removable header) +- [ ] Bio (rich text, links, emoji) +- [ ] Pronouns field +- [ ] Location (optional, city-level) + +**Widget Catalog (user picks and arranges):** +- [ ] **Pinned Posts** — Pin up to 3 posts to the top of your profile +- [ ] **Music Widget** — Currently listening / favorite track (Spotify/Apple Music embed or manual) +- [ ] **Photo Grid** — Mini gallery (3-6 featured photos from uploads) +- [ ] **Social Links** — Icons row for external links (site, GitHub, IG, etc.) +- [ ] **Causes I Care About** — Tag-style badges (environment, mutual aid, arts, etc.) +- [ ] **Featured Friends** — Highlight 3-6 people (like MySpace Top 8 but chill) +- [ ] **Stats Widget** — Post count, follower count, member since (opt-in) +- [ ] **Quote Widget** — A single styled quote / motto +- [ ] **Beacon Activity** — Recent community contributions +- [ ] **Custom Text Block** — Markdown-rendered freeform section + +**Theming (constrained but expressive):** +- [ ] Accent color picker (applies to profile header, widget borders, link color) +- [ ] Light/dark/auto profile theme (independent of app theme) +- [ ] Banner image (behind header area) +- [ ] Profile badges (verified, early adopter, community helper — system-assigned) + +**Implementation:** +- [ ] Backend: `profile_layout JSONB` column on `profiles` table +- [ ] Backend: `PUT /profile/layout` endpoint to save widget arrangement +- [ ] Frontend: `ProfileWidgetRenderer` that reads layout JSON and renders widget stack +- [ ] Frontend: `ProfileEditor` with drag-to-reorder and add/remove widget catalog +- [ ] Widget sandboxing — each widget has max height, no custom CSS/HTML injection +- [ ] Default layout for new users (bio + social links + pinned posts) + +--- + +### 5. Blocking System +**Status**: Basic block exists. Import/export not implemented. + +- [ ] Verify block prevents: seeing posts, DMs, mentions, search results, follow +- [ ] Block list management screen (view, unblock) +- [ ] Export block list as JSON/CSV +- [ ] Import block list from JSON/CSV +- [ ] Import block list from other platforms (Twitter/X format, Mastodon format) +- [ ] Blocked users cannot see your profile or posts +- [ ] Silent block (user doesn't know they're blocked) + +**Frontend**: `sojorn_app/lib/screens/profile/blocked_users_screen.dart` +**Backend**: `go-backend/internal/handlers/user_handler.go` + +--- + +### 6. E2EE Chat Stability & Sync +**Status**: X3DH implementation works but key sync across devices is fragile. + +- [ ] Audit key recovery flow — ensure it reliably recovers from MAC errors +- [ ] Device-to-device key sync without storing plaintext on server +- [ ] QR code key verification between users +- [ ] "Encrypted with old keys" messages should offer re-request option +- [ ] Clean up `forceResetBrokenKeys()` dead code in `simple_e2ee_service.dart` +- [ ] Ensure cloud backup/restore cycle works end-to-end +- [ ] Add key fingerprint display in chat settings +- [ ] Rate limit key recovery to prevent loops + +--- + +### 7. Repost / Boost Feature +**Status**: Not started. +A "repost" action that amplifies content to your followers without quote-posting. + +- [ ] Repost button on posts (share to your followers' feeds) +- [ ] Repost count displayed on posts +- [ ] Reposted-by attribution in feed ("@user reposted") +- [ ] Undo repost +- [ ] Backend: `reposts` table (user_id, post_id, created_at) +- [ ] Feed algorithm weights reposts into feed ranking + +--- + +### 8. Algorithm Refactor — Promote Good, Discourage Anger +**Status**: Basic algorithm exists in `algorithm_config` table. Needs philosophical overhaul. +Core principle: **Show users what they love, not what they hate.** + +- [ ] Engagement scoring that weights positive interactions (save, repost, thoughtful reply) over rage-clicks +- [ ] De-rank content with high negative-reaction ratios +- [ ] "Cooling period" — delay viral anger content by 30min before amplifying +- [ ] Boost content tagged as good news, community, mutual aid, creativity +- [ ] User-controllable feed preferences ("show me more of X, less of Y") +- [ ] Diversity injection — prevent echo chambers by mixing in adjacent-interest content +- [ ] Transparency: show users why a post appeared in their feed +- [ ] Admin algorithm tuning panel (already built in admin dashboard) +- [ ] A/B testing framework for algorithm changes + +--- + +## 🔧 Low Priority — Polish & Cleanup + +### 9. Remaining Code TODOs +Small scattered items across the codebase: + +- [ ] `sojorn_rich_text.dart` — Implement profile navigation from @mentions +- [ ] `post_with_video_widget.dart` — Implement post options menu (edit, delete, report) +- [ ] `video_player_with_comments.dart` — Implement "more options" button +- [ ] `sojorn_swipeable_post.dart` — Wire up allowChain setting when API supports it +- [ ] `reading_post_card.dart` — Implement share functionality + +### 10. Legacy Cleanup +- [ ] Delete `go-backend/cmd/supabase-migrate/` directory (dead migration tool) +- [ ] Update 2 stale Supabase comments in `go-backend/internal/middleware/auth.go` +- [ ] Remove `forceResetBrokenKeys()` from `simple_e2ee_service.dart` + +--- + +## ✅ Completed + +### Core Platform (shipped) +- ✅ Go backend — 100% migrated from Supabase +- ✅ User auth (JWT, refresh tokens, email verification) +- ✅ Posts (create, edit, delete, visibility, chains) +- ✅ Comments (threaded, with replies) +- ✅ Feed algorithm (basic version) +- ✅ Image/video uploads to Cloudflare R2 - ✅ Follow/unfollow system -- ✅ E2EE chat (X3DH implementation) -- ✅ Push notifications (FCM) -- ✅ Search functionality +- ✅ Search (users, posts, hashtags) - ✅ Categories and user settings -- ✅ Chain posts functionality +- ✅ Chain posts +- ✅ Beacon voting (vouch, report, remove vote) +- ✅ E2EE chat (X3DH, key backup/restore) +- ✅ Push notifications (FCM) +- ✅ Quips (basic video recording and feed) -### **Infrastructure** -- ✅ Go backend API with all core endpoints -- ✅ PostgreSQL database with proper schema -- ✅ Cloudflare R2 integration for media -- ✅ Nginx reverse proxy -- ✅ SSL/TLS configuration +### Admin & Moderation (shipped) +- ✅ Admin panel (Next.js dashboard, users, posts, moderation queue, appeals) +- ✅ OpenAI text moderation (auto-flag on post/comment creation) +- ✅ Three Poisons scoring (Hate, Greed, Delusion) +- ✅ Ban/suspend system with content jailing +- ✅ Email notifications (ban, suspend, restore, content removal) +- ✅ Moderation queue with dismiss/action/ban workflows +- ✅ User violation tracking and history +- ✅ IP-based ban evasion detection + +### Infrastructure (shipped) +- ✅ PostgreSQL with full schema +- ✅ Cloudflare R2 media storage +- ✅ Nginx reverse proxy + SSL/TLS - ✅ Systemd service management +- ✅ GeoIP for location features +- ✅ Automated deploy scripts ---- - -## 📊 **Current Status Analysis** - -### **What's Working Better Than Expected** -- **Comment System**: Fully implemented with Go backend (TODO was outdated) -- **Media Uploads**: Direct to R2, no Supabase dependency -- **Chain Posts**: Complete implementation -- **E2EE Chat**: Production-ready X3DH system - -### **What Actually Needs Work** -- **Beacon Voting**: Only 3 missing endpoints (core feature) -- **Supabase Cleanup**: Mostly removing legacy code -- **UI Polish**: Fixing outdated TODO comments - -### **Key Insight** -The codebase is **90% complete** for Go backend migration. Most TODO comments are outdated and refer to features that are already implemented. The beacon voting system is the only critical missing piece. - ---- - -## 🚀 **Implementation Plan** - -### **Day 1: Beacon Voting (4 hours)** -1. Implement beacon voting handlers (2h) -2. Add API routes (30m) -3. Update Flutter API methods (1h) -4. Test beacon voting flow (30m) - -### **Day 2: Supabase Cleanup (3 hours)** -1. Remove function proxy (1h) -2. Clean up dependencies (2h) - -### **Day 3: UI Polish (3 hours)** -1. Fix video comments TODO (1h) -2. Implement reply features (2h) - -### **Day 4: Final Polish & Testing (2 hours)** -1. Add post options menu (1h) -2. End-to-end testing (1h) - ---- - -## 🎯 **Success Criteria** - -### **100% Go Backend Functionality** -- [ ] All features work without Supabase -- [ ] Beacon voting system operational -- [ ] No Supabase code or dependencies -- [ ] All TODO comments resolved or updated -- [ ] End-to-end testing passes - -### **Code Quality** -- [ ] No debug code in production -- [ ] No outdated comments -- [ ] Clean, maintainable code -- [ ] Proper error handling -- [ ] Security best practices - ---- - -## 📝 **Notes** - -- **Most TODO comments are outdated** - features are already implemented -- **Beacon voting is the only critical missing feature** -- **Supabase cleanup is mostly removing legacy code** -- **UI polish items are nice-to-have, not blocking** - -**Total estimated effort: ~12 hours to reach 100% Go backend functionality** - ---- - -**Next Steps**: Start with high-priority beacon voting implementation, as it's the only critical missing feature for complete Go backend functionality. +### Recent Fixes (Feb 2026) +- ✅ Notification badge count clears on archive +- ✅ Notification UI → full page (was slide-up dialog) +- ✅ Moderation queue constraint fix (dismissed/actioned statuses) +- ✅ Debug print cleanup (190+ statements removed) +- ✅ Run scripts cleanup (run_dev, run_web, run_web_chrome)