Skip to main content

Overview

Teak provides multiple organization strategies: favorites for quick access, tags for categorization, type filters for browsing, and visual filters for aesthetic discovery.

Favorites

Mark important cards for instant access.

Marking as Favorite

import { api } from "@teak/convex";
import { useMutation } from "convex/react";

const updateCard = useMutation(api.cards.updateCard);

await updateCard({
  cardId: "abc123",
  isFavorited: true
});

Querying Favorites

import { api } from "@teak/convex";
import { useQuery } from "convex/react";

const favorites = useQuery(api.card.getCards.getCards, {
  favoritesOnly: true,
  limit: 50
});
Favorites use the optimized by_user_favorites_deleted index for fast queries.

Tags

Organize cards with custom labels.

User Tags vs AI Tags

Teak maintains two separate tag systems:
FeatureUser TagsAI Tags
SourceManual inputAI-generated
FieldtagsaiTags
EditableYesNo (regenerate card)
SearchFull-text searchFull-text search

Adding Tags

// At creation
await createCard({
  content: "Design inspiration",
  tags: ["ui", "dark-mode", "inspiration"]
});

// Update existing card
await updateCard({
  cardId: "abc123",
  tags: ["ui", "dark-mode", "inspiration", "new-tag"]
});

Searching by Tags

Tags are searchable via the full-text search system:
const results = useQuery(api.card.getCards.searchCards, {
  searchQuery: "dark-mode", // Matches tags containing "dark-mode"
  limit: 50
});
Tag searches use partial matching. Searching “dark” will find cards tagged “dark-mode”.

Type Filters

Filter by card type to focus on specific content.

Single Type

const images = useQuery(api.card.getCards.searchCards, {
  types: ["image"],
  limit: 50
});
Single-type filters use the optimized by_user_type_deleted index.

Multiple Types

const media = useQuery(api.card.getCards.searchCards, {
  types: ["image", "video", "audio"],
  limit: 50
});

All Available Types

import { CARD_TYPES } from "@teak/convex/shared/constants";

// ["text", "link", "image", "video", "audio", "document", "palette", "quote"]
console.log(CARD_TYPES);

Visual Filters

Discover cards by aesthetic style and color.

Visual Styles

Teak extracts 13 visual style facets from images and videos:
StyleDescription
abstractAbstract art, non-representational
cinematicFilm-like, dramatic composition
darkDark mode, low-key lighting
illustrativeHand-drawn, illustrated
minimalMinimalist, simple composition
monochromeBlack and white, grayscale
moodyAtmospheric, dramatic
pastelSoft, muted colors
photographicRealistic, photo-like
retroVintage computing, synthwave
surrealDreamlike, surreal
vintageAged, antique aesthetic
vibrantHighly saturated, colorful

Color Hues

Filter by dominant color hue:
HueCoverage
redRed tones
orangeOrange tones
yellowYellow tones
greenGreen tones
tealTeal tones
cyanCyan tones
blueBlue tones
purplePurple, violet, indigo
pinkPink, magenta, fuchsia
brownBrown tones
neutralGray, monochrome

Exact Colors

Filter by specific hex codes:
const results = useQuery(api.card.getCards.searchCards, {
  hexFilters: ["#FF6B35", "#F7931E"],
  limit: 50
});
Hex filters match palette cards containing those exact colors.

Combined Visual Filters

const aestheticImages = useQuery(api.card.getCards.searchCards, {
  types: ["image"],
  styleFilters: ["minimal", "monochrome"],
  hueFilters: ["neutral"],
  limit: 50
});
Visual filters use dedicated facet indexes for performance. See packages/convex/card/visualFilters.ts.

Time Ranges

Filter by creation date:
const recentCards = useQuery(api.card.getCards.searchCards, {
  createdAtRange: {
    start: Date.now() - 7 * 24 * 60 * 60 * 1000, // 7 days ago
    end: Date.now()
  },
  limit: 50
});
Time range queries use the by_created index for efficient filtering.

Trash

Cards can be soft-deleted and recovered:

Move to Trash

await updateCard({
  cardId: "abc123",
  isDeleted: true
});

View Trash

const trashedCards = useQuery(api.card.getCards.searchCards, {
  showTrashOnly: true,
  limit: 50
});

Restore from Trash

await updateCard({
  cardId: "abc123",
  isDeleted: false // or undefined
});
Permanent deletion is not yet implemented. All deletions are soft-deletes.

Combining Filters

All filters can be combined for powerful queries:
const results = useQuery(api.card.getCards.searchCards, {
  searchQuery: "design inspiration",
  types: ["image", "link"],
  favoritesOnly: true,
  styleFilters: ["minimal", "vibrant"],
  hueFilters: ["blue", "purple"],
  createdAtRange: {
    start: Date.now() - 30 * 24 * 60 * 60 * 1000,
    end: Date.now()
  },
  limit: 50
});

Performance Tips

1

Use Specific Indexes

Single-type and favorites-only queries use optimized compound indexes:
  • by_user_type_deleted for single type
  • by_user_favorites_deleted for favorites
  • by_created for time ranges
2

Paginate Large Results

const { results, loadMore, status } = usePaginatedQuery(
  api.card.getCards.searchCardsPaginated,
  { /* filters */ },
  { initialNumItems: 50 }
);
3

Visual Filters with Type Filter

Combine visual filters with type filters to reduce search space:
{
  types: ["image"], // Narrow down first
  styleFilters: ["minimal"]
}

Source Reference

  • Query logic: packages/convex/card/getCards.ts:45-747
  • Visual filters: packages/convex/card/visualFilters.ts
  • Constants: packages/convex/shared/constants.ts:20-227

Build docs developers (and LLMs) love