···11+## Description
22+33+This file includes the backlog of features and fixes that need to be done.
44+Each should be addressed one at a time, and the item should be removed after implementation has been finished and verified.
55+66+---
77+88+## Features
99+1010+1. LARGE: complete record styling refactor that changes from table-style to more mobile-friendly style
1111+ - Likely a more "post-style" version that is closer to bsky posts
1212+ - To be done later down the line
1313+ - setting to use legacy table view
1414+1515+2. Settings menu (mostly tbd)
1616+ - Private mode -- don't show in community feed (records are still public via pds api though)
1717+ - Dev mode -- show did, copy did in profiles (remove "logged in as <did>" from home page)
1818+ - Toggle for table view vs future post-style view
1919+2020+## Fixes
2121+2222+- Loading columns for brews table doesn't match loaded column names
+68
internal/feed/service.go
···1313 "github.com/rs/zerolog/log"
1414)
15151616+// PublicFeedCacheTTL is the duration for which the public feed cache is valid.
1717+// This value can be adjusted based on desired freshness vs. performance tradeoff.
1818+// Consider values between 5-10 minutes for a good balance.
1919+const PublicFeedCacheTTL = 5 * time.Minute
2020+2121+// PublicFeedLimit is the number of items to show for unauthenticated users
2222+const PublicFeedLimit = 5
2323+1624// FeedItem represents an activity in the social feed with author info
1725type FeedItem struct {
1826 // Record type and data (only one will be non-nil)
···3038 TimeAgo string // "2 hours ago", "yesterday", etc.
3139}
32404141+// publicFeedCache holds cached feed items for unauthenticated users
4242+type publicFeedCache struct {
4343+ items []*FeedItem
4444+ expiresAt time.Time
4545+ mu sync.RWMutex
4646+}
4747+3348// Service fetches and aggregates brews from registered users
3449type Service struct {
3550 registry *Registry
3651 publicClient *atproto.PublicClient
5252+ cache *publicFeedCache
3753}
38543955// NewService creates a new feed service
···4157 return &Service{
4258 registry: registry,
4359 publicClient: atproto.NewPublicClient(),
6060+ cache: &publicFeedCache{},
4461 }
6262+}
6363+6464+// GetCachedPublicFeed returns cached feed items for unauthenticated users.
6565+// It returns up to PublicFeedLimit items from the cache, refreshing if expired.
6666+func (s *Service) GetCachedPublicFeed(ctx context.Context) ([]*FeedItem, error) {
6767+ s.cache.mu.RLock()
6868+ if time.Now().Before(s.cache.expiresAt) && len(s.cache.items) > 0 {
6969+ items := s.cache.items
7070+ s.cache.mu.RUnlock()
7171+ log.Debug().Int("item_count", len(items)).Msg("feed: returning cached public feed")
7272+ return items, nil
7373+ }
7474+ s.cache.mu.RUnlock()
7575+7676+ // Cache is expired or empty, refresh it
7777+ return s.refreshPublicFeedCache(ctx)
7878+}
7979+8080+// refreshPublicFeedCache fetches fresh feed items and updates the cache
8181+func (s *Service) refreshPublicFeedCache(ctx context.Context) ([]*FeedItem, error) {
8282+ s.cache.mu.Lock()
8383+ defer s.cache.mu.Unlock()
8484+8585+ // Double-check if another goroutine already refreshed the cache
8686+ if time.Now().Before(s.cache.expiresAt) && len(s.cache.items) > 0 {
8787+ return s.cache.items, nil
8888+ }
8989+9090+ log.Debug().Msg("feed: refreshing public feed cache")
9191+9292+ // Fetch fresh feed items (limited to PublicFeedLimit)
9393+ items, err := s.GetRecentRecords(ctx, PublicFeedLimit)
9494+ if err != nil {
9595+ // If we have stale data, return it rather than failing
9696+ if len(s.cache.items) > 0 {
9797+ log.Warn().Err(err).Msg("feed: failed to refresh cache, returning stale data")
9898+ return s.cache.items, nil
9999+ }
100100+ return nil, err
101101+ }
102102+103103+ // Update cache
104104+ s.cache.items = items
105105+ s.cache.expiresAt = time.Now().Add(PublicFeedCacheTTL)
106106+107107+ log.Debug().
108108+ Int("item_count", len(items)).
109109+ Time("expires_at", s.cache.expiresAt).
110110+ Msg("feed: updated public feed cache")
111111+112112+ return items, nil
45113}
4611447115// GetRecentRecords fetches recent activity (brews and other records) from all registered users
+12-1
internal/handlers/handlers.go
···156156// Community feed partial (loaded async via HTMX)
157157func (h *Handler) HandleFeedPartial(w http.ResponseWriter, r *http.Request) {
158158 var feedItems []*feed.FeedItem
159159+159160 if h.feedService != nil {
160160- feedItems, _ = h.feedService.GetRecentRecords(r.Context(), 20)
161161+ // Check if user is authenticated
162162+ _, err := atproto.GetAuthenticatedDID(r.Context())
163163+ isAuthenticated := err == nil
164164+165165+ if isAuthenticated {
166166+ // Authenticated users get the full feed (20 items), fetched fresh
167167+ feedItems, _ = h.feedService.GetRecentRecords(r.Context(), 20)
168168+ } else {
169169+ // Unauthenticated users get a limited feed from the cache
170170+ feedItems, _ = h.feedService.GetCachedPublicFeed(r.Context())
171171+ }
161172 }
162173163174 if err := bff.RenderFeedPartial(w, feedItems); err != nil {