mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

Development Guide#

Quick Start#

flutter pub get
just objectbox-setup # once per machine (macOS/Linux)
just gen
flutter run

Common Tasks#

Use just for common workflows:

Command Description
just format Run dart format
just lint Run flutter analyze
just objectbox-setup Install pinned ObjectBox native runtime (macOS/Linux)
just objectbox-check Verify ObjectBox native runtime is present
just test Run the full flutter test suite
just gen Run build_runner for code generation
just check Format, lint, and test in sequence

Architecture#

Stack#

Layer Technology
Framework Flutter (Material 3)
State management flutter_bloc (Bloc + Cubit)
Primary database Drift (SQLite) — source of truth
Vector store ObjectBox — embeddings for semantic search
Networking bluesky / atproto packages + Dio
Navigation go_router (StatefulShellRoute)
Code generation build_runner, freezed, drift_dev, objectbox_generator

Feature Module Layout#

Each feature follows this convention:

features/<feature>/
  bloc/       # Bloc or Cubit + State + Event classes
  cubit/      # Lighter-weight cubits where appropriate
  data/       # Repository classes; API + database interactions
  presentation/
    <feature>_screen.dart
    widgets/

Database#

Drift (primary store)#

Powered by Drift (SQLite). All migrations live in lib/core/database/app_database.dart.

Current schema version: 15

Table Version Purpose
accounts 1 Auth sessions (DID, tokens, handle, service URL)
cached_profiles 2 Profile metadata cache
cached_posts 2 Post content cache for offline display
settings 1 Key-value application configuration
saved_feeds 3 User's pinned and saved feeds
cached_feed_pages 12 First-page feed cache for cold-start UX
search_history 4 Persistent local search query history
drafts 5 Offline-first post drafts with media
saved_posts 6 Locally saved posts (save type: local or cloud bookmark)
labeler_cache 9 Cached labeler service policies
liked_posts 15 Locally persisted liked posts for semantic search

Adding a migration#

  1. Add or modify table definition in lib/core/database/tables.dart.
  2. Bump schemaVersion in AppDatabase.
  3. Add if (from < N) { ... } block in MigrationStrategy.onUpgrade.
  4. Run just gen to regenerate app_database.g.dart.

ObjectBox (vector store)#

ObjectBox runs as a secondary store alongside Drift. It holds only embedding vectors and the metadata needed to join back to Drift.

The EmbeddedPost entity stores:

  • postUri (unique AT-URI)
  • accountDid
  • source ('saved' or 'liked')
  • indexedText — text used to produce the embedding
  • embedding — 384-dimensional float32 vector (HNSW cosine index)
  • embeddedAt

After modifying EmbeddedPost, run just gen to regenerate objectbox.g.dart and objectbox-model.json.

Running unit tests (ObjectBox native library)#

ObjectBox requires a platform native library to run flutter test locally. Lazurite standardizes this setup through a pinned installer script.

Supported local platforms: macOS and Linux.

Run this once per machine:

just objectbox-setup

just test and just test-quiet intentionally run a preflight check (just objectbox-check) and fail fast with setup instructions if the runtime is missing.

On-device vector search over saved and liked posts using:

  • Model: all-MiniLM-L6-v2 quantized INT8 TFLite model (384D output, ~25 MB)
  • Tokenizer: WordPiece, max 256 tokens, bundled as Flutter assets
  • Inference: Long-lived background Isolate via EmbeddingService
  • Storage: ObjectBox with HNSW cosine index on the embedding vector

The EmbeddingService.isAvailable flag gates all semantic search UI entry points. On initialization failure the feature degrades gracefully to unavailable.

Liked Posts#

Liked posts are synced from the Bluesky API and persisted locally in the liked_posts Drift table. This feeds the semantic search pipeline.

LikedPostsRepository (lib/features/feed/data/liked_posts_repository.dart):

Method Description
syncLikes(accountDid) Paginate getActorLikes until a known URI is hit or 1000-post cap is reached
evicts oldest on overflow
getLikedPosts(accountDid, {limit, offset}) Paginated query ordered by likedAt DESC
removeLike(accountDid, postUri) Delete a single entry

Testing#

Run the full suite:

just test
# or with failure-only reporter (recommended for large suites):
just test-quiet

Coverage target: >99% (±5% acceptable). All new code must have tests. Widget tests use NativeDatabase.memory() for Drift and mocktail for API mocks.

Code Generation#

Run after any change to:

  • Drift table definitions (tables.dart)
  • ObjectBox entities (embedded_post.dart)
  • freezed models
  • json_serializable classes
just gen
# or:
dart run build_runner build --delete-conflicting-outputs

Routes#

Path Description
/login Authentication gateway
/ Home feed tab
/search Search tab
/profile Current user profile tab
/settings Global settings
/compose Root-level modal for new posts

References#