Phase 3: Memories in Your Hands
2026-02-14
People can now hold their memories in their hands. Phase 3 delivers what the previous phases built toward: a mobile app where someone downloads Tesseras, creates an identity, takes a photo, and that memory enters the preservation network. No cloud accounts, no subscriptions, no company between you and your memories.
What was built
tesseras-embedded — A full P2P node that runs inside a mobile app. The
EmbeddedNode struct owns a Tokio runtime, SQLite database, QUIC transport,
Kademlia DHT engine, replication service, and tessera service — the same stack
as the desktop daemon, compiled into a shared library. A global singleton
pattern (Mutex<Option<EmbeddedNode>>) ensures one node per app lifecycle. On
start, it opens the database, runs migrations, loads or generates an Ed25519
identity with proof-of-work node ID, binds QUIC on an ephemeral port, wires up
DHT and replication, and spawns the repair loop. On stop, it sends a shutdown
signal and drains gracefully.
Eleven FFI functions are exposed to Dart via flutter_rust_bridge: lifecycle
(node_start, node_stop, node_is_running), identity (create_identity,
get_identity), memories (create_memory, get_timeline, get_memory), and
network status (get_network_stats, get_replication_status). All types
crossing the FFI boundary are flat structs with only String, Option<String>,
Vec<String>, and primitives — no trait objects, no generics, no lifetimes.
Four adapter modules bridge core ports to concrete implementations:
Blake3HasherAdapter, Ed25519SignerAdapter/Ed25519VerifierAdapter for
cryptography, DhtPortAdapter for DHT operations, and
ReplicationHandlerAdapter for incoming fragment and attestation RPCs.
The bundled-sqlite feature flag compiles SQLite from source, required for
Android and iOS where the system library may not be available. Cargokit
configuration passes this flag automatically in both debug and release builds.
Flutter app — A Material Design 3 application with Riverpod state management, targeting Android, iOS, Linux, macOS, and Windows from a single codebase.
The onboarding flow is three screens: a welcome screen explaining the project in one sentence ("Preserve your memories across millennia. No cloud. No company."), an identity creation screen that triggers Ed25519 keypair generation in Rust, and a confirmation screen showing the user's name and cryptographic identity.
The timeline screen displays memories in reverse chronological order with
image previews, context text, and chips for memory type and visibility.
Pull-to-refresh reloads from the Rust node. A floating action button opens the
memory creation screen, which supports photo selection from gallery or camera
via image_picker, optional context text, memory type and visibility dropdowns,
and comma-separated tags. Creating a memory calls the Rust FFI synchronously,
then returns to the timeline.
The network screen shows two cards: node status (peer count, DHT size, bootstrap state, uptime) and replication health (total fragments, healthy fragments, repairing fragments, replication factor). The settings screen displays the user's identity — name, truncated node ID, truncated public key, and creation date.
Three Riverpod providers manage state: nodeProvider starts the embedded node
on app launch using the app documents directory and stops it on dispose;
identityProvider loads the existing profile or creates a new one;
timelineProvider fetches the memory list with pagination.
Testing — 9 Rust unit tests in tesseras-embedded covering node lifecycle (start/stop without panic), identity persistence across restarts, restart cycles without SQLite corruption, network event streaming, stats retrieval, memory creation and timeline retrieval, and single memory lookup by hash. 2 Flutter tests: an integration test verifying Rust initialization and app startup, and a widget smoke test.
Architecture decisions
- Embedded node, not client-server: the phone runs the full P2P stack, not a thin client talking to a remote daemon. This means memories are preserved even without internet. Users with a Raspberry Pi or VPS can optionally connect the app to their daemon via GraphQL for higher availability, but it's not required.
- Synchronous FFI: all flutter_rust_bridge functions are marked
#[frb(sync)]and block on the internal Tokio runtime. This simplifies the Dart side (no async bridge complexity) while the Rust side handles concurrency internally. Flutter's UI thread stays responsive because Riverpod wraps calls in async providers. - Global singleton: a
Mutex<Option<EmbeddedNode>>global ensures the node lifecycle is predictable — one start, one stop, no races. Mobile platforms kill processes aggressively, so simplicity in lifecycle management is a feature. - Flat FFI types: no Rust abstractions leak across the FFI boundary. Every type is a plain struct with strings and numbers. This makes the auto-generated Dart bindings reliable and easy to debug.
- Three-screen onboarding: identity creation is the only required step. No email, no password, no server registration. The app generates a cryptographic identity locally and is ready to use.
What comes next
- Phase 4: Resilience and Scale — Advanced NAT traversal (STUN/TURN), Shamir's Secret Sharing for heirs, sealed tesseras with time-lock encryption, performance tuning, security audits, OS packaging for Alpine/Arch/Debian/FreeBSD/OpenBSD
- Phase 5: Exploration and Culture — Public tessera browser by era/location/theme/language, institutional curation, genealogy integration, physical media export (M-DISC, microfilm, acid-free paper with QR)
The infrastructure is complete. The network exists, replication works, and now anyone with a phone can participate. What remains is hardening what we have and opening it to the world.