Categories

Categories and filters

Categories group statements for display in the mobile apps. Unlike traditional tagging, categories use JSON-based filter rules to dynamically match statements.

Creating a category

Go to Categories in the sidebar and click Create Category. You’ll need:

  • App – Which app this category belongs to
  • Name – Internal name (e.g., “Pre-Party”)
  • Emoji – Displayed in the UI and optionally in the app
  • Localized title and description – Per-language text shown to users

Filter rules

The filter field is a JSON structure that defines which statements belong to the category. Filters can include field conditions that match against statement properties:

  • Match by mode (e.g., only “truth” statements)
  • Match by tags (e.g., statements tagged “party”)
  • Match by isActive status
  • Match by custom metadata fields

Multiple conditions combine with AND or OR logic.

Semantic search in filters – You can add a semantic condition that uses vector similarity to find statements matching a concept. The system pre-computes embeddings for each condition so category matching stays fast at query time.

Creating categories from statement filters

A quick way to create a category: set up filters on the All Statements tab, review the matching results, then click the “Create Category from Filters” button. This pre-fills the category form with the filter you already built.

Platform visibility

Each category has visibility flags:

  • Hidden on iOS – Category won’t appear in the iOS app
  • Hidden on Android – Category won’t appear in the Android app
  • Paid – Category is behind a paywall
  • Explicit – Category contains adult content

Testing categories

Click the test button on any category to see which statements its filter currently matches. This runs the filter against the full statement database and shows a preview of the results with a count.

Localized text

Categories support a title and description per locale. These are stored as LocalizedCategoryText entries. At minimum, provide English (en). Add other languages as needed for your supported locales.

How it works under the hood

Filter resolution happens at snapshot generation time, not live. When you create a snapshot and click Generate, each category’s filter is evaluated against the current statement database. The matching statement IDs are stored directly in the snapshot payload. This means:

  • Adding or editing statements after a snapshot is published has no effect on that snapshot
  • To include new or changed statements, you must create a new snapshot
  • The test button on categories shows what currently matches – this may differ from what’s in an existing snapshot

Semantic filters use vector similarity. If a filter includes a semantic condition, the system computes a vector embedding for the query text and finds statements within the similarity threshold using pgvector. These embeddings are pre-computed and stored on the filter so that repeated evaluations are fast.

Filter operators support matching on tags (CONTAINS_ANY, CONTAINS_ALL), mode (IS, IS_ONE_OF), metadata fields (including nested JSON paths), and existence checks (EXISTS, NOT_EXISTS). Conditions combine with AND or OR logic.