summaryrefslogtreecommitdiffstats
path: root/news/phase3-api-and-apps/index.html
blob: 1f0feaba2f5323cc3b4cfa82f607f9e435fa223c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Phase 3: Memories in Your Hands — Tesseras</title>
    <meta name="description" content="Tesseras now has a Flutter app and an embedded Rust node — anyone can create and preserve memories from their phone.">
    <!-- Open Graph -->
    <meta property="og:type" content="article">
    <meta property="og:title" content="Phase 3: Memories in Your Hands">
    <meta property="og:description" content="Tesseras now has a Flutter app and an embedded Rust node — anyone can create and preserve memories from their phone.">
    <meta property="og:image" content="https://tesseras.net/images/social.jpg">
    <meta property="og:image:width" content="1200">
    <meta property="og:image:height" content="630">
    <meta property="og:site_name" content="Tesseras">
    <!-- Twitter Card -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="Phase 3: Memories in Your Hands">
    <meta name="twitter:description" content="Tesseras now has a Flutter app and an embedded Rust node — anyone can create and preserve memories from their phone.">
    <meta name="twitter:image" content="https://tesseras.net/images/social.jpg">
    <link rel="stylesheet" href="https://tesseras.net/style.css?h=21f0f32121928ee5c690">
    
        
            <link rel="alternate" type="application/atom+xml" title="Tesseras" href="https://tesseras.net/atom.xml">
        
    
    <link rel="icon" type="image/png" sizes="32x32" href="https://tesseras.net/images/favicon.png?h=be4e123a23393b1a027d">
    
</head>
<body>
    <header>
        <h1>
            <a href="https:&#x2F;&#x2F;tesseras.net/">
                <img src="https://tesseras.net/images/logo-64.png?h=c1b8d0c4c5f93b49d40b" alt="Tesseras" width="40" height="40" class="logo">
                Tesseras
            </a>
        </h1>
        <nav>
            
                <a href="https://tesseras.net/about/">About</a>
                <a href="https://tesseras.net/news/">News</a>
                <a href="https://tesseras.net/releases/">Releases</a>
                <a href="https://tesseras.net/faq/">FAQ</a>
                <a href="https://tesseras.net/subscriptions/">Subscriptions</a>
                <a href="https://tesseras.net/contact/">Contact</a>
            
        </nav>
        <nav class="lang-switch">
            
                <strong>English</strong> | <a href="/pt-br&#x2F;news&#x2F;phase3-api-and-apps&#x2F;">Português</a>
            
        </nav>
    </header>

    <main>
        
<article>
    <h2>Phase 3: Memories in Your Hands</h2>
    <p class="news-date">2026-02-14</p>
    <p>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.</p>
<h2 id="what-was-built">What was built</h2>
<p><strong>tesseras-embedded</strong> — A full P2P node that runs inside a mobile app. The
<code>EmbeddedNode</code> 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 (<code>Mutex&lt;Option&lt;EmbeddedNode&gt;&gt;</code>) 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.</p>
<p>Eleven FFI functions are exposed to Dart via flutter_rust_bridge: lifecycle
(<code>node_start</code>, <code>node_stop</code>, <code>node_is_running</code>), identity (<code>create_identity</code>,
<code>get_identity</code>), memories (<code>create_memory</code>, <code>get_timeline</code>, <code>get_memory</code>), and
network status (<code>get_network_stats</code>, <code>get_replication_status</code>). All types
crossing the FFI boundary are flat structs with only <code>String</code>, <code>Option&lt;String&gt;</code>,
<code>Vec&lt;String&gt;</code>, and primitives — no trait objects, no generics, no lifetimes.</p>
<p>Four adapter modules bridge core ports to concrete implementations:
<code>Blake3HasherAdapter</code>, <code>Ed25519SignerAdapter</code>/<code>Ed25519VerifierAdapter</code> for
cryptography, <code>DhtPortAdapter</code> for DHT operations, and
<code>ReplicationHandlerAdapter</code> for incoming fragment and attestation RPCs.</p>
<p>The <code>bundled-sqlite</code> 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.</p>
<p><strong>Flutter app</strong> — A Material Design 3 application with Riverpod state
management, targeting Android, iOS, Linux, macOS, and Windows from a single
codebase.</p>
<p>The <em>onboarding flow</em> 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.</p>
<p>The <em>timeline screen</em> 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
<em>memory creation screen</em>, which supports photo selection from gallery or camera
via <code>image_picker</code>, 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.</p>
<p>The <em>network screen</em> shows two cards: node status (peer count, DHT size,
bootstrap state, uptime) and replication health (total fragments, healthy
fragments, repairing fragments, replication factor). The <em>settings screen</em>
displays the user's identity — name, truncated node ID, truncated public key,
and creation date.</p>
<p>Three Riverpod providers manage state: <code>nodeProvider</code> starts the embedded node
on app launch using the app documents directory and stops it on dispose;
<code>identityProvider</code> loads the existing profile or creates a new one;
<code>timelineProvider</code> fetches the memory list with pagination.</p>
<p><strong>Testing</strong> — 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.</p>
<h2 id="architecture-decisions">Architecture decisions</h2>
<ul>
<li><strong>Embedded node, not client-server</strong>: 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.</li>
<li><strong>Synchronous FFI</strong>: all flutter_rust_bridge functions are marked
<code>#[frb(sync)]</code> 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.</li>
<li><strong>Global singleton</strong>: a <code>Mutex&lt;Option&lt;EmbeddedNode&gt;&gt;</code> 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.</li>
<li><strong>Flat FFI types</strong>: 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.</li>
<li><strong>Three-screen onboarding</strong>: 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.</li>
</ul>
<h2 id="what-comes-next">What comes next</h2>
<ul>
<li><strong>Phase 4: Resilience and Scale</strong> — 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</li>
<li><strong>Phase 5: Exploration and Culture</strong> — Public tessera browser by
era/location/theme/language, institutional curation, genealogy integration,
physical media export (M-DISC, microfilm, acid-free paper with QR)</li>
</ul>
<p>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.</p>

</article>

    </main>

    <footer>
        <p>&copy; 2026 Tesseras Project. <a href="/atom.xml">News Feed</a> · <a href="https://git.sr.ht/~ijanc/tesseras">Source</a></p>
    </footer>
</body>
</html>