New to Claude Skills? Learn how to install them →

vitorpamplona on GitHub

Feed Patterns

Free

Streamline feed management in Amethyst applications.

Get this skill

Free · Opens the source repo

What Feed Patterns does

The Feed Patterns skill provides a structured approach to managing feeds in Amethyst applications, which include various types of lists such as home feeds, profile feeds, and notifications. It introduces developers to the underlying architecture of feed composition and data access, specifically focusing on the FeedFilter and FeedViewModel abstractions. This skill is essential for anyone looking to add new feeds or modify existing ones, as it covers the necessary classes and patterns for effective feed management.

When implementing a feed, users will learn how to define filters that determine which notes are included in a feed. The skill explains the importance of reactive state management, detailing how FeedViewModel interacts with LocalCache to ensure that the UI reflects the current state of the data. This is particularly useful for debugging scenarios where feeds may not update as expected after changes in user interactions, such as muting or following accounts.

The documentation also guides users through the process of extending existing ViewModels or creating new filters based on specific requirements. It emphasizes the differences between various filter types, such as AdditiveFeedFilter for incremental updates and AdditiveComplexFeedFilter for handling different item types in feeds. By understanding these patterns, developers can create efficient and responsive feeds tailored to their application's needs.

Overall, this skill is targeted at developers working with Amethyst who need to implement or enhance feed functionalities. It provides the foundational knowledge necessary to navigate the complexities of feed management within the framework, ensuring that users can build robust and dynamic user interfaces.

When to use it

Use this skill when adding new feed screens or modifying existing feeds in Amethyst applications.

When not to use it

This skill may not be suitable for projects outside the Amethyst framework or for those not dealing with feed management.

What you can build with it

Adding a New Feed Screen

When you need to implement a new screen that displays a list of notes, this skill provides the necessary patterns and classes to do so.

Modifying Existing Feed Logic

If you want to change the filtering or ordering logic of an existing feed, this skill offers guidance on how to adjust the relevant filters.

Debugging Feed Updates

Use this skill to troubleshoot issues where a feed does not reflect changes after user actions, helping you identify potential problems in the data flow.

How to install Feed Patterns

View source

1. Install with the skills CLI

npx skills add vitorpamplona/amethyst/feed-patterns --agent claude-code

2. Or install it manually

Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.

Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs

Inside SKILL.md

Written by vitorpamplona

Feed Patterns

Amethyst's "feed" abstraction is: a FeedFilter that decides which notes belong in a list, plus a FeedViewModel that exposes the current state reactively to the UI. Every scrollable list — home, profile, hashtag, bookmarks, notifications, DMs — is a variant of this.

When to Use This Skill

  • Adding a new screen that shows a list of notes.
  • Modifying an existing feed's filtering / ordering / inclusion rules.
  • Investigating why a feed doesn't update after a mute/follow/bookmark change.
  • Deciding whether to extend a ViewModel or write a new filter.
  • Understanding the Android ⇄ Desktop sharing boundary for feeds.

Architecture

┌─────────────────────────────────────────────────────────────┐
│ commons/.../viewmodels/  (shared, KMP)                      │
│   FeedViewModel  ◄── ListChangeFeedViewModel                │
│                 ◄── ChatroomFeedViewModel                   │
│                 ◄── MarmotGroupFeedViewModel                │
│                                                             │
│                                                             │
│ commons/.../ui/feeds/  (shared, KMP)                        │
│   IFeedFilter / FeedFilter<T>  (abstract base)              │
│   IAdditiveFeedFilter / AdditiveFeedFilter<T>               │
│   ChangesFlowFilter                                         │
│   FeedContentState, FeedState — the flow the UI collects    │
└─────────────────────────────────────────────────────────────┘
              ▲
              │ uses
              │
┌─────────────────────────────────────────────────────────────┐
│ amethyst/.../ui/dal/  (Android-only additions)              │
│   AdditiveComplexFeedFilter<T, U>                           │
│   FilterByListParams                                        │
│   DefaultFeedOrder (Note/Event/Card comparators)            │
│   (FeedFilters.kt & ChangesFlowFilter.kt here are just      │
│    back-compat typealiases re-exporting commons)            │
│                                                             │
│   Concrete feeds: HomeNewThreadFeedFilter,                  │
│   HashtagFeedFilter, NotificationFeedFilter, … live in      │
│   feature folders under ui/screen/loggedIn/*/dal/           │
└─────────────────────────────────────────────────────────────┘
              ▲
              │ reads
              │
┌─────────────────────────────────────────────────────────────┐
│ model/LocalCache.kt + account.<feature>.flow                │
└─────────────────────────────────────────────────────────────┘

Key Files

Shared (commons)

commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/:

  • FeedViewModel.ktabstract class FeedViewModel(localFilter, cacheProvider). Holds a FeedContentState, subscribes to invalidation signals (from Account flows and LocalCacheFlow), re-runs the filter, and emits a new FeedState for the UI.
  • ListChangeFeedViewModel.kt — specialization for feeds whose membership changes frequently (e.g. bookmarks).
  • ChatroomFeedViewModel.kt — DM thread feed.
  • MarmotGroupFeedViewModel.kt — NIP-29 / marmot group feed.
  • LiveStreamTopZappersViewModel.kt, SearchBarState.kt, ChatNewMessageState.kt — narrower, non-feed states that share the plumbing.

Shared filter bases (commons)

commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/feeds/:

  • FeedFilter.ktabstract class FeedFilter<T> : IFeedFilter<T>. Has feed(): List<T> (the sync query against the cache), feedKey(): String (identity used to cache), limit(), and loadTop().
  • AdditiveFeedFilter.ktabstract class AdditiveFeedFilter<T> : FeedFilter<T>(), IAdditiveFeedFilter<T>. Adds incremental updates (the "additive" part): updateListWith(oldList, newItems) runs applyFilter(newItems) and grafts accepted items onto the existing list (re-sort + take(limit())) without recomputing everything.
  • ChangesFlowFilter.kt — wraps a filter with a coarse "state changed" signal so the ViewModel knows to re-query.
  • FeedContentState.kt / FeedState.kt — the reactive state the UI collects.

Android DAL (additions on top)

amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/:

  • AdditiveComplexFeedFilter.ktabstract class AdditiveComplexFeedFilter<T, U> : FeedFilter<T>(): like AdditiveFeedFilter but the incoming items (Set<U>) are a different type than the list rows (T).
  • FilterByListParams.kt — common parameters (top-nav filter, exclude muted, since/until) shared across many filters.
  • DefaultFeedOrder.kt — standard comparators (createdAt desc + id tiebreaker for stable paging) for Note, Event, and Card.
  • FeedFilters.kt / ChangesFlowFilter.kt — back-compat typealiases re-exporting the commons classes; don't add logic here.

Concrete filters (Home, Hashtag, Profile, Bookmark, Notifications, Communities, etc.) live in feature dal/ subfolders under amethyst/.../ui/screen/loggedIn/*/ — each extends FeedFilter, AdditiveFeedFilter, or AdditiveComplexFeedFilter. Desktop has its own in desktopApp/.../feeds/DesktopFeedFilters.kt.

Adding a New Feed

  1. Define the filter. Extend AdditiveFeedFilter<Note> (or plain FeedFilter<Note> if additivity doesn't matter; AdditiveComplexFeedFilter<T, U> if incoming items differ in type from list rows). Implement:
    • feedKey() — stable identity (e.g. hashtag name, account pubkey).
    • feed() — synchronous scan over LocalCache / Account state producing an ordered list.
    • limit() — pagination hint.
    • If additive: applyFilter(collection: Set<Note>): Set<Note> and sort(collection: Set<Note>): List<Note>.
  2. Pick or write a ViewModel. If the feed's membership shifts often (bookmarks, notifications), extend ListChangeFeedViewModel. Otherwise FeedViewModel.
  3. Wire invalidation. The ViewModel must observe the right Account flows + LocalCacheFlow so it re-queries when state changes.
  4. Render. In the composable, collect viewModel.feedState.feedContent and render with a LazyColumn { items(..., key = { it.id }) { NoteCompose(it) } }.
  5. Subscribe to relays. Most feeds also need a Subscribable to fetch historical events. See the relay-client skill.

Filter Sharing (Android vs Desktop)

  • The filter base classes (FeedFilter, AdditiveFeedFilter, ChangesFlowFilter) and feed state (FeedContentState) are in commons/.../ui/feeds/shared. ViewModels are in commons/.../viewmodels/shared.
  • The concrete filters are platform-local: Android's in amethyst/.../ui/screen/loggedIn/*/dal/, Desktop's in desktopApp/.../feeds/. amethyst/.../ui/dal/ keeps Android-only helpers (AdditiveComplexFeedFilter, FilterByListParams, DefaultFeedOrder) plus back-compat typealiases.
  • When porting a feed, share the concrete filter only if both platforms need identical inclusion rules.

Gotchas

  • Never scan LocalCache from a composable. Always go through a FeedFilter + FeedViewModel, which does it on a background dispatcher and debounces invalidation.
  • feedKey() is used as a cache key. Two different semantic feeds must produce different keys, otherwise their state cross-contaminates.
  • Additive updates must stay consistent with the full recompute. If applyFilter accepts a note that feed() wouldn't include, UX drifts.
  • Paging isn't free — use limit() and since/until in FilterByListParams rather than trimming a giant scan.
  • Notifications feed is special — it inspects the follow/mute state (account.kind3FollowList.flow, account.hiddenUsers) and LocalCache deletions to hide muted/deleted content; always run through the FilterByListParams exclusion paths rather than filtering post-hoc.

References

  • references/feed-filter-composition.md — step-by-step for adding a feed.
  • references/viewmodel-base-classes.md — inheritance graph for the FeedViewModel family.
  • Complements: account-state (where the data lives), relay-client (how to subscribe), compose-expert (how to render).

Frequently asked questions about Feed Patterns

Similar skills