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#
- Add or modify table definition in
lib/core/database/tables.dart. - Bump
schemaVersioninAppDatabase. - Add
if (from < N) { ... }block inMigrationStrategy.onUpgrade. - Run
just gento regenerateapp_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)accountDidsource('saved'or'liked')indexedText— text used to produce the embeddingembedding— 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.
Semantic Search#
On-device vector search over saved and liked posts using:
- Model:
all-MiniLM-L6-v2quantized INT8 TFLite model (384D output, ~25 MB) - Tokenizer: WordPiece, max 256 tokens, bundled as Flutter assets
- Inference: Long-lived background
IsolateviaEmbeddingService - 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) freezedmodelsjson_serializableclasses
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 |