docs(adr): mobile data, one file store, shared spaces
The developer's decisions of 10 Nov 2026 on their evening's requests:
- ADR 0042: every app holds its sync back on a metered connection, as the
OS says, and asks in a quiet banner (Sync now, Without files, Not now);
an answer holds until the connection changes; Settings › Sync chooses
Ask, Without files or Everything.
- ADR 0043: Learn's file store moves into halkyon_base, room counted per
service; Todo gets sealed attachments in descriptions and comments, any
file up to 100 MB, fetched when shown.
- ADR 0044: shared spaces in halkyon_base for every app, Learn's studysets
first: Owner, Editor and Viewer, keys wrapped to members' key pairs,
invites by handle or email, links off at first, the material to publish
chosen item by item, and agents as accounts of their own whose changes
are suggestions a person accepts. Its open questions come before
building, first how rows move between spaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 23:07:01 +01:00
|
|
|
|
# ADR 0042: Sync held back on mobile data
|
|
|
|
|
|
|
|
|
|
|
|
- **Status:** decided 10 Nov 2026 by the developer: D1 is their own request, D2 to D5
|
feat(app,ui): sync waits on mobile data and asks first
The developer's request of 10 Nov 2026, decided in ADR 0042: on mobile
data every app holds its sync back, rows and files alike, and asks.
halkyon_app becomes an Android plugin, its first native code, registered
in every app by Flutter: HalkyonAppPlugin.java says which network the
device is on and whether it is metered as Android has it (no
NET_CAPABILITY_NOT_METERED: cellular, a phone's hotspot, Wi-Fi marked
metered). Java, so it applies no Kotlin Gradle Plugin.
MobileDataGate, one per device, holds the setting (Ask first, Sync
without files, Sync everything; kept in <app support>/mobile_data.json),
the network, and the person's answer, which holds until the device moves
to another network or off a metered one. SyncService asks it before each
sync: held back, nothing goes, the status counts what waits and asks; an
answer or Wi-Fi syncs at once, and syncing now while held back is Sync
now. Each app's frame shows the banner above every tab, in halkyon_ui's
new NavigationFrame.banner slot (On mobile data · 3 changes waiting, with
Not now, Without files and Sync now); the Sync card in Settings chooses
the setting on a phone and says when files wait for Wi-Fi. Learn's files
neither go up nor come down while held back.
Whats-New: On mobile data the app no longer syncs by itself: a banner asks first, and Settings › Sync chooses whether to ask, sync without files, or sync everything.
Whats-New-De: Mit mobilen Daten synchronisiert die App nicht mehr von selbst: Ein Hinweis fragt zuerst, und unter Einstellungen › Synchronisierung wählst du, ob sie fragt, ohne Dateien oder alles synchronisiert.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 23:38:17 +01:00
|
|
|
|
the agent's recommendations, which they chose. Built 10 Nov 2026 (below).
|
docs(adr): mobile data, one file store, shared spaces
The developer's decisions of 10 Nov 2026 on their evening's requests:
- ADR 0042: every app holds its sync back on a metered connection, as the
OS says, and asks in a quiet banner (Sync now, Without files, Not now);
an answer holds until the connection changes; Settings › Sync chooses
Ask, Without files or Everything.
- ADR 0043: Learn's file store moves into halkyon_base, room counted per
service; Todo gets sealed attachments in descriptions and comments, any
file up to 100 MB, fetched when shown.
- ADR 0044: shared spaces in halkyon_base for every app, Learn's studysets
first: Owner, Editor and Viewer, keys wrapped to members' key pairs,
invites by handle or email, links off at first, the material to publish
chosen item by item, and agents as accounts of their own whose changes
are suggestions a person accepts. Its open questions come before
building, first how rows move between spaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 23:07:01 +01:00
|
|
|
|
- **Builds on:** ADR 0014 (halkyon_app, every app's shell, with its `SyncService`).
|
|
|
|
|
|
|
|
|
|
|
|
## Context
|
|
|
|
|
|
|
|
|
|
|
|
Every Halkyon app syncs whenever something changes and on a timer, on whatever network
|
|
|
|
|
|
the device has. Learn's library holds tens of thousands of rows and hundreds of
|
|
|
|
|
|
megabytes of PDFs; a first sync, or an import on another device, can take a phone's
|
|
|
|
|
|
monthly data on the go. The developer asked (10 Nov 2026): *"It should hold back
|
|
|
|
|
|
everything, asking the user whether one wants to sync. Then, give also the option to
|
|
|
|
|
|
exclude files, images etc."*
|
|
|
|
|
|
|
|
|
|
|
|
## Decisions
|
|
|
|
|
|
|
|
|
|
|
|
- **D1.** On mobile data, every app holds back its sync, rows and files alike, and asks
|
|
|
|
|
|
first. Built once, in halkyon_app, around its `SyncService`; nothing an app does on
|
|
|
|
|
|
the device waits on it (local-first as before: what is written is kept and sent
|
|
|
|
|
|
later).
|
|
|
|
|
|
- **D2.** *Mobile data* is a **metered** connection as the operating system says: on
|
|
|
|
|
|
Android its `NET_CAPABILITY_NOT_METERED` missing, which takes in cellular and also a
|
|
|
|
|
|
phone's hotspot or a Wi-Fi network marked metered; read by a few lines of Kotlin in
|
|
|
|
|
|
halkyon_app's plugin code (`connectivity_plus` tells only the kind of network, which
|
|
|
|
|
|
misses the hotspot). iOS: `NWPath.isExpensive`, once the iOS builds ship. Desktops
|
|
|
|
|
|
are never metered.
|
|
|
|
|
|
- **D3.** It asks with a **quiet banner** above the lists, never a dialog: *On mobile
|
|
|
|
|
|
data · 12 changes waiting* with *Sync now*, *Without files* and *Not now*. *Not now*
|
|
|
|
|
|
hides it until something new waits. Pulling a list down to sync is an explicit ask
|
|
|
|
|
|
too, and syncs as *Sync now* would.
|
|
|
|
|
|
- **D4.** An answer **holds until the connection changes**: while the device stays on
|
|
|
|
|
|
this metered network; asked again after Wi-Fi came and went, or when the app starts
|
|
|
|
|
|
again on mobile data.
|
|
|
|
|
|
- **D5.** Settings › Sync has *On mobile data*: **Ask** (the default), **Without files**,
|
|
|
|
|
|
**Everything**. No *Never*: *Not now* holds back already. A device setting (as the
|
|
|
|
|
|
look), not synced: a tablet on Wi-Fi alone and a phone differ.
|
|
|
|
|
|
|
|
|
|
|
|
## What "without files" means
|
|
|
|
|
|
|
|
|
|
|
|
Rows sync; files neither go up nor come down. Each app names its files to halkyon_app
|
|
|
|
|
|
as a queue of its own (Learn's asset uploads and downloads, its PDFs and Jellyfin
|
|
|
|
|
|
covers; Todo's attachments, ADR 0043; Train's maps' tiles stay as they are, fetched
|
|
|
|
|
|
when shown), which `SyncService` pauses and resumes. What is waiting shows in Sync's
|
|
|
|
|
|
card (*3 files waiting for Wi-Fi*).
|
|
|
|
|
|
|
|
|
|
|
|
## To work out when building
|
|
|
|
|
|
|
|
|
|
|
|
- Where the banner sits in each app's frame: halkyon_ui's `NavigationFrame` has no
|
|
|
|
|
|
place for one above every tab yet.
|
|
|
|
|
|
- The sync engine's stream: whether held back means not opening it at all (the
|
|
|
|
|
|
recommendation: nothing goes over the network, not even the handshake) or opening it
|
|
|
|
|
|
and sending nothing.
|
|
|
|
|
|
- Learn's offline reads (b578fcb9) must not wait on a sync held back: the tests of
|
|
|
|
|
|
`ContentKeys` and the gateways already run without one.
|
feat(app,ui): sync waits on mobile data and asks first
The developer's request of 10 Nov 2026, decided in ADR 0042: on mobile
data every app holds its sync back, rows and files alike, and asks.
halkyon_app becomes an Android plugin, its first native code, registered
in every app by Flutter: HalkyonAppPlugin.java says which network the
device is on and whether it is metered as Android has it (no
NET_CAPABILITY_NOT_METERED: cellular, a phone's hotspot, Wi-Fi marked
metered). Java, so it applies no Kotlin Gradle Plugin.
MobileDataGate, one per device, holds the setting (Ask first, Sync
without files, Sync everything; kept in <app support>/mobile_data.json),
the network, and the person's answer, which holds until the device moves
to another network or off a metered one. SyncService asks it before each
sync: held back, nothing goes, the status counts what waits and asks; an
answer or Wi-Fi syncs at once, and syncing now while held back is Sync
now. Each app's frame shows the banner above every tab, in halkyon_ui's
new NavigationFrame.banner slot (On mobile data · 3 changes waiting, with
Not now, Without files and Sync now); the Sync card in Settings chooses
the setting on a phone and says when files wait for Wi-Fi. Learn's files
neither go up nor come down while held back.
Whats-New: On mobile data the app no longer syncs by itself: a banner asks first, and Settings › Sync chooses whether to ask, sync without files, or sync everything.
Whats-New-De: Mit mobilen Daten synchronisiert die App nicht mehr von selbst: Ein Hinweis fragt zuerst, und unter Einstellungen › Synchronisierung wählst du, ob sie fragt, ohne Dateien oder alles synchronisiert.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 23:38:17 +01:00
|
|
|
|
|
|
|
|
|
|
## As built (10 Nov 2026)
|
|
|
|
|
|
|
|
|
|
|
|
The developer chose two more things while it was built: the native code as halkyon_app's
|
|
|
|
|
|
own Android plugin, not a Kotlin file in each app, and the banner in the frame, above
|
|
|
|
|
|
every tab.
|
|
|
|
|
|
|
|
|
|
|
|
- **halkyon_app's plugin** (`android/`, `HalkyonAppPlugin.java`): the first native code of
|
|
|
|
|
|
a shared package, registered in every app by Flutter. Java, so it applies no Kotlin
|
|
|
|
|
|
Gradle Plugin. It answers the default network on `app.halkyon/network` and sends each
|
|
|
|
|
|
change on `app.halkyon/network/changes`: its handle as `id`, and `metered` where
|
|
|
|
|
|
`NET_CAPABILITY_NOT_METERED` is missing (unless temporarily not metered, Android 11 on).
|
|
|
|
|
|
It declares `ACCESS_NETWORK_STATE`. Dart: `AndroidNetworkWatch`, `NeverMetered` on other
|
|
|
|
|
|
platforms, `deviceNetworkWatch()`.
|
|
|
|
|
|
- **`MobileDataGate`** (the device's, one for every person's sync): the setting, kept in
|
|
|
|
|
|
`<app support>/mobile_data.json`; the network; the answer, bound to the network's `id`
|
|
|
|
|
|
and dropped when the device moves to another network or off a metered one; `allowance`
|
|
|
|
|
|
(`nothing`, `rows`, `everything`) and `filesAllowed`. Nothing syncs until it knows the
|
|
|
|
|
|
network, or for 3 s at most.
|
|
|
|
|
|
- **`SyncService(mobileData:)`**: a trigger while nothing is allowed syncs nothing; the
|
|
|
|
|
|
status says `heldBack`, `asking` (until *Not now*, and again once another change
|
|
|
|
|
|
waits), `filesHeld` and the setting (`mobileData`, null on a desktop). `syncNow` while
|
|
|
|
|
|
held back answers *Sync now*. The gate allowing more syncs at once.
|
|
|
|
|
|
- **The banner**: `mobileDataBannerOf(context)` in each app's `NavigationFrame.banner`
|
|
|
|
|
|
(halkyon_ui: above the tabs, their screens without the top inset, which the banner keeps
|
|
|
|
|
|
clear of itself). **Settings**: the Sync card's *On mobile data* field and its lines
|
|
|
|
|
|
*Held back on mobile data* and *Files wait for Wi-Fi*.
|
|
|
|
|
|
- **Files**: Learn's `AssetRepository(filesAllowed:)` uploads nothing and downloads
|
|
|
|
|
|
nothing while files are held, as without a connection; what waits goes after the next
|
|
|
|
|
|
sync that may carry it. Todo's attachments (ADR 0043) will do the same.
|
|
|
|
|
|
- **Not built**: the count of files waiting in the Sync card; iOS (`NWPath.isExpensive`).
|
|
|
|
|
|
|