Flutter Butler Hackathon 2026 Entry | Built with Flutter + Serverpod
A zero-knowledge personal vault with on-device AI. Capture thoughts on a freeform Canvas, organize them into a structured Binder, track life events in a Timeline - all with end-to-end encryption where even the server can't read your data.
Live Demo: vaultnbinder.serverpod.space | API: vaultnbinder.api.serverpod.space
| Problem | Our Solution |
|---|---|
| Cloud apps read your data | Zero-knowledge server - stores only encrypted blobs |
| AI assistants send data to the cloud | On-device AI - Gemma runs 100% locally |
| Important info scattered everywhere | Three integrated views - Canvas, Binder, Life Log |
| Generic app design | Heritage Archive - warm, premium aesthetic |
- Canvas: Padlet-like freeform workspace for brain-dumping Cards
- Binder: Structured "Where Everything Is" index with Records (Assets, Liabilities, Coverage, Contacts, etc.)
- Life Log: Timeline view for diary/life events (unified with Cards - any card can be logged)
- Unified Card System: Cards can optionally be added to Life Log with date, mood, location, and tags
- Context-Aware FAB: + button adapts to current view (Life Log adds logged cards, elsewhere quick capture)
- E2EE: End-to-end encryption - your data never leaves your device unencrypted
- Local-first: Works fully offline
- Private AI Assistant: On-device AI concierge with access to all your data (Gemma 3 Nano)
- Smart Tagging: AI-powered tag and category suggestions
- Summarization: Generate digests from notes and life log entries
- Just-in-Case Pack: Export for emergencies (redacted print + encrypted archive)
| Layer | Technology |
|---|---|
| Framework | Flutter (iOS, Android, macOS, Windows, Linux, Web) |
| State Management | Riverpod |
| Local Database | Drift + SQLCipher |
| Encryption | cryptography (Argon2id + XChaCha20-Poly1305) |
| Navigation | go_router |
| Backend | Serverpod Cloud (ciphertext-only sync) |
| Local AI | flutter_gemma (Gemma 3 Nano, bundled in assets) |
| Voice | speech_to_text (on-device STT) |
vaultnbinder/
├── lib/ # Flutter app source
├── vaultnbinder_server/ # Serverpod backend
├── vaultnbinder_client/ # Generated Serverpod client
├── android/ # Android platform
├── ios/ # iOS platform
├── macos/ # macOS platform
├── web/ # Web platform
├── windows/ # Windows platform
└── linux/ # Linux platform
lib/
├── main.dart # App entry point
├── bootstrap.dart # Initialization
├── core/ # Shared infrastructure
│ ├── errors/ # Error handling utilities
│ │ ├── app_exception.dart
│ │ ├── error_handler.dart
│ │ └── async_value_extensions.dart
│ ├── theme/ # App theming
│ └── widgets/ # Shared widgets
│ ├── app_lifecycle_observer.dart
│ ├── error_boundary.dart
│ └── splash_screen.dart
├── ai/ # Private AI Assistant
│ ├── domain/
│ │ └── entities/
│ │ ├── ai_entities.dart
│ │ └── assistant_entities.dart
│ ├── data/services/
│ │ ├── gemma_service.dart
│ │ ├── ai_assistant_service.dart
│ │ ├── tagging_service.dart
│ │ └── summarization_service.dart
│ └── presentation/
│ ├── providers/ai_providers.dart
│ └── screens/
│ ├── ai_assistant_screen.dart
│ └── ai_settings_screen.dart
├── encryption/ # E2EE layer
│ ├── data/services/
│ │ ├── crypto_service.dart
│ │ └── secure_storage_service.dart
│ └── providers/
├── database/ # Drift + SQLCipher
│ ├── database.dart # Main database with SQLCipher
│ ├── tables/ # Table definitions
│ │ ├── cards_table.dart
│ │ ├── records_table.dart
│ │ ├── attachments_table.dart
│ │ ├── life_log_entries_table.dart
│ │ ├── filing_suggestions_table.dart
│ │ ├── canvas_positions_table.dart
│ │ ├── card_record_links_table.dart
│ │ └── sync_metadata_table.dart
│ ├── daos/ # Data Access Objects
│ │ ├── cards_dao.dart
│ │ ├── records_dao.dart
│ │ ├── attachments_dao.dart
│ │ ├── life_log_dao.dart
│ │ ├── filing_suggestions_dao.dart
│ │ └── sync_dao.dart
│ └── providers/ # Riverpod providers
├── features/
│ ├── vault/ # Unlock/lock/setup
│ ├── canvas/ # Freeform board
│ │ └── presentation/
│ │ ├── screens/canvas_screen.dart
│ │ └── widgets/
│ │ ├── canvas_board.dart
│ │ └── canvas_card.dart
│ ├── binder/ # Structured records
│ │ ├── domain/
│ │ │ ├── entities/record_entity.dart
│ │ │ └── repositories/record_repository.dart
│ │ ├── data/
│ │ │ └── repositories/record_repository_impl.dart
│ │ └── presentation/
│ │ ├── providers/record_providers.dart
│ │ ├── screens/
│ │ │ ├── binder_screen.dart
│ │ │ ├── record_detail_screen.dart
│ │ │ ├── record_editor_screen.dart
│ │ │ └── link_cards_screen.dart
│ │ └── widgets/
│ │ ├── record_card.dart
│ │ └── category_chip.dart
│ ├── life_log/ # Timeline view
│ │ ├── domain/
│ │ │ ├── entities/life_log_entry.dart
│ │ │ └── repositories/life_log_repository.dart
│ │ ├── data/
│ │ │ └── repositories/life_log_repository_impl.dart
│ │ └── presentation/
│ │ ├── providers/life_log_providers.dart
│ │ ├── screens/
│ │ │ ├── life_log_screen.dart
│ │ │ ├── entry_detail_screen.dart
│ │ │ └── entry_editor_screen.dart
│ │ └── widgets/
│ │ ├── entry_card.dart
│ │ └── month_calendar.dart
│ ├── attachments/ # File attachments
│ │ ├── domain/
│ │ │ ├── entities/attachment_entity.dart
│ │ │ └── repositories/attachment_repository.dart
│ │ ├── data/
│ │ │ ├── repositories/attachment_repository_impl.dart
│ │ │ └── services/file_storage_service.dart
│ │ └── presentation/
│ │ ├── providers/attachment_providers.dart
│ │ └── widgets/
│ │ ├── attachment_picker.dart
│ │ └── attachment_list.dart
│ ├── cards/ # Card capture/edit
│ │ ├── domain/
│ │ │ ├── entities/card_entity.dart
│ │ │ └── repositories/card_repository.dart
│ │ ├── data/
│ │ │ └── repositories/card_repository_impl.dart
│ │ └── presentation/
│ │ ├── providers/card_providers.dart
│ │ ├── screens/card_editor_screen.dart
│ │ └── widgets/quick_capture_sheet.dart
│ ├── filing_tray/ # AI suggestions
│ ├── onboarding/ # User onboarding
│ │ └── presentation/
│ │ ├── providers/onboarding_providers.dart
│ │ ├── screens/onboarding_screen.dart
│ │ └── widgets/onboarding_page.dart
│ ├── settings/ # App settings
│ │ ├── domain/
│ │ │ └── entities/app_settings.dart
│ │ └── presentation/
│ │ ├── providers/settings_providers.dart
│ │ ├── screens/settings_screen.dart
│ │ └── widgets/
│ │ ├── settings_section.dart
│ │ └── settings_tile.dart
│ └── sync/ # Cloud sync
│ ├── domain/
│ │ └── entities/sync_entities.dart
│ ├── data/
│ │ └── services/
│ │ ├── sync_service.dart
│ │ ├── sync_orchestrator.dart
│ │ └── offline_queue_service.dart
│ └── presentation/
│ └── providers/sync_providers.dart
└── routing/ # go_router config
User Passcode
│
▼ Argon2id (64MB, 3 iterations)
┌────────────────────────┐
│ Vault Root Key (VRK) │ ← Stored in OS Keychain
└────────────────────────┘
│
├──► SQLCipher Key (DB encryption)
│
└──► Per-Object DEKs (envelope encryption)
├── Card DEKs
├── Record DEKs
└── Attachment DEKs
Key Security Properties:
- VRK never leaves device in plaintext
- Server is zero-knowledge (ciphertext only)
- Per-object DEKs limit blast radius
- XChaCha20-Poly1305 authenticated encryption
- Flutter SDK 3.24+
- Dart 3.24+
- Xcode 15+ (for iOS/macOS builds)
- Android Studio (for Android builds)
| Platform | Status |
|---|---|
| Android | ✅ Debug APK builds successfully |
| iOS | ✅ Builds (requires Xcode team configuration) |
| macOS | ✅ Supported |
| Web | ✅ Supported |
# Clone the repository
git clone <repo-url>
cd vaultnbinder
# Install dependencies
flutter pub get
# Run code generation
dart run build_runner build
# Run the app
flutter run# iOS
flutter run -d ios
# Android
flutter run -d android
# macOS
flutter run -d macos
# Web
flutter run -d chromeAfter modifying freezed classes or Drift tables:
dart run build_runner build --delete-conflicting-outputsflutter testProduction URLs:
| Service | URL |
|---|---|
| Web | https://vaultnbinder.serverpod.space/ |
| API | https://vaultnbinder.api.serverpod.space/ |
| Insights | https://vaultnbinder.insights.serverpod.space/ |
Deployment:
# Install Serverpod Cloud CLI (one-time)
dart pub global activate serverpod_cloud_cli
scloud version
# Login to Serverpod Cloud
scloud auth login
# First-time setup (from server directory)
cd vaultnbinder_server
scloud launch
# Subsequent deployments
scloud deploy
# Check deployment status
scloud deployment show# Start local Postgres via Docker
cd vaultnbinder_server
docker-compose up -d
# Run the server locally
dart run bin/main.dart
# Generate Serverpod code after model changes
serverpod generate- Project structure
- Dependencies configured
- CryptoService (Argon2id + XChaCha20-Poly1305)
- SecureStorageService
- Vault creation/unlock flow
- VaultStateProvider with Riverpod
- go_router with vault guard
- Vault Setup/Unlock screens
- Main app shell with navigation
- Drift schema implementation (8 tables)
- SQLCipher integration with VRK-derived key
- DAOs for Cards, Records, Attachments, LifeLog, FilingSuggestions, Sync
- Vault-gated database providers
- Card domain entities with freezed (CardModel, CanvasPositionModel)
- CardRepository with per-card DEK encryption
- Card providers with Riverpod (streams, notifiers, view state)
- Canvas board with pan/zoom gesture handling
- Draggable cards with position persistence
- Quick capture bottom sheet
- Full card editor screen with type selector
- Record domain entities with freezed sealed classes
- 8 record types: Asset, Liability, Coverage, Contact, Medical, Credential, Document, Custom
- Type-specific data classes (AssetData, LiabilityData, etc.)
- RecordRepository with per-record DEK encryption
- Record providers with Riverpod (streams, notifiers, filters)
- Binder screen with category filter chips and record counts
- Record detail screen with type-specific field display
- Record editor screen with dynamic forms per type
- Card↔Record linking screen with multi-select
- Attachment domain entities with file type detection
- Streaming file encryption (1MB chunks) with XChaCha20-Poly1305
- FileStorageService for encrypted file persistence
- AttachmentRepository with per-file DEK encryption
- Attachment providers with Riverpod
- Photo/file picker with camera, gallery, and file support
- Attachment list widget with preview support
- LifeLog domain entities (diary, event, milestone, memory types)
- LifeLogRepository with encrypted title/content/location
- LifeLog providers with date filtering
- Timeline screen with calendar navigation
- Entry editor with mood and tag support
- Entry detail screen
- Local full-text search
- Filing Tray UI
- Manual filing workflow
- flutter_gemma package setup
- GGUF model download UI
- GemmaService for inference
- Card analysis with JSON output
- FilingSuggestion parsing
- Redacted PDF export
- Encrypted archive export
- Import from archive
- Serverpod project setup
- Data models (Vault, Device, CipherBlob, CloudBackup)
- Sync endpoint with delta sync
- Device registration endpoint
- Backup endpoint
- SyncService for Serverpod communication
- OfflineQueueService with Drift tables
- SyncOrchestrator for coordinating sync
- Conflict detection and resolution
- Background sync support
- Settings screen with all configuration options
- Auto-lock timer with configurable timeout
- Biometrics configuration UI
- Onboarding flow for new users
- Error handling utilities (AppException, ErrorHandler)
- AsyncValue extensions for consistent error UI
- App lifecycle observer for auto-lock
- Theme switching (light/dark/system)
- Hide content in app switcher option
- Splash screen
- Gemma 3 Nano model bundled in app assets
- GemmaService with asset-based installation
- AIAssistantService with context injection from user data
- TaggingService for smart tag suggestions
- SummarizationService for note/life log digests
- AI Assistant chat screen with streaming responses
- AI button in main app bar
- AI settings screen with model management
- SemanticSearchService (natural language search) - planned
- TranscriptionService (voice to text) - planned
VaultNBinder features a distinctive "Heritage Archive" design system that avoids generic tech aesthetics. The design is inspired by archival libraries, premium stationery, and brass fixtures.
- Warm Security: Feel protected without feeling clinical
- Tactile Digital: Textures and depth that invite interaction
- Quiet Confidence: Premium without being pretentious
- Living Archive: Grows with you, tells your story
| Category | Light Mode | Dark Mode |
|---|---|---|
| Background | Archive Cream #FAF7F2 |
Deep Archive #0F0D0A |
| Text | Vault Black #1A1814 |
Cream Text #F5F0E8 |
| Primary Accent | Brass #C9A227 |
Aged Brass #D4B84B |
| Secondary | Ink Blue #2C3E50 |
Slate Blue #8BA5C2 |
| Cards | Parchment #F5F0E8 |
Night Parchment #1A1814 |
- Headlines: Source Serif 4 (elegant, editorial serif)
- Body/UI: DM Sans (geometric but warm sans-serif)
- Code/Credentials: JetBrains Mono
| Type | Color | Meaning |
|---|---|---|
| Asset | Moss Green #4A7C59 |
Growth, wealth |
| Liability | Muted Burgundy #8B4D57 |
Attention without alarm |
| Coverage | Slate Blue #4A6B8A |
Protection |
| Contact | Warm Taupe #7A6B5A |
Human connection |
| Medical | Dusty Lavender #6B5A7A |
Care |
| Credential | Amber #8A6B4A |
Value, access |
| Document | Sage #5A6B6B |
Neutral storage |
lib/core/theme/app_theme.dart- Full theme configurationlib/core/theme/design_tokens.dart- Colors, spacing, typography tokensDESIGN_SYSTEM.md- Complete design system documentation
| Decision | Choice | Rationale |
|---|---|---|
| Encryption | XChaCha20-Poly1305 | Modern AEAD, no nonce reuse issues |
| Key Derivation | Argon2id | Memory-hard, resistant to GPU attacks |
| State Management | Riverpod | Type-safe, testable, async-friendly |
| Database | Drift + SQLCipher | Reactive, type-safe, encrypted |
| On-Device AI | Gemma 3 Nano | Privacy-first, ~800MB model, runs offline |
| Design System | Heritage Archive | Warm, distinctive, avoids generic tech aesthetics |
| Typography | Google Fonts (Source Serif 4 + DM Sans) | Premium feel, excellent readability |
| Icons | Phosphor Icons | Consistent, flexible, warmer than Material |
The app includes a fully private, on-device AI assistant powered by Gemma 3 Nano:
┌─────────────────────────────────────────────────────────────┐
│ AI Assistant Architecture │
│ │
│ Assets: │
│ └─ models/gemma-3n-E2B-it-int4.task (~800MB) │
│ │
│ Services: │
│ ├─ GemmaService (model loading from asset) │
│ ├─ AIAssistantService (chat + context injection) │
│ ├─ TaggingService (smart tags) │
│ └─ SummarizationService (digests) │
│ │
│ Features: │
│ ├─ Context-aware chat (accesses cards/records) │
│ ├─ Smart tag suggestions for content │
│ ├─ Category detection for life log entries │
│ └─ Daily/weekly digest generation │
└─────────────────────────────────────────────────────────────┘
Privacy Guarantees:
- All AI processing runs 100% on-device
- No data sent to external servers
- Model bundled in app assets, no network required
- Context built from decrypted data in memory only
On iOS/Android, the model is loaded directly from bundled assets. On desktop (macOS/Windows/Linux), flutter_gemma doesn't support fromAsset(), so the model must be downloaded from the network.
Model Configuration:
The app fetches the model URL from the Serverpod backend via the model endpoint. Update the URL in vaultnbinder_server/lib/src/endpoints/model_endpoint.dart:
const modelUrl = String.fromEnvironment(
'AI_MODEL_URL',
defaultValue: 'https://storage.googleapis.com/vaultnbinder-models/gemma-3n-E2B-it-int4.task',
);Hosting Options:
-
Google Cloud Storage (Recommended)
# Create bucket gsutil mb gs://vaultnbinder-models # Upload model file (2.9GB) gsutil cp assets/models/gemma-3n-E2B-it-int4.task gs://vaultnbinder-models/ # Make publicly readable gsutil acl ch -u AllUsers:R gs://vaultnbinder-models/gemma-3n-E2B-it-int4.task # The URL will be: # https://storage.googleapis.com/vaultnbinder-models/gemma-3n-E2B-it-int4.task
-
AWS S3
# Create bucket and upload aws s3 cp assets/models/gemma-3n-E2B-it-int4.task s3://your-bucket/models/ # Enable public read access via bucket policy
-
Cloudflare R2
- Free egress, great for large file downloads
- Compatible with S3 API
After Uploading:
- Update the model URL in
model_endpoint.dart - Deploy to Serverpod Cloud:
cd vaultnbinder_server scloud deploy
Fallback Behavior:
If the server is unavailable, the app falls back to Google's 800MB Gemma 3 1B model:
https://storage.googleapis.com/mediapipe-assets/gemma3-1b-it-int4.task
Proprietary - All rights reserved
This is a private project. Contact the maintainer for contribution guidelines.