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>
This commit is contained in:
parent
fd6647d752
commit
5d47efd7c5
3 changed files with 195 additions and 0 deletions
55
docs/adr/0042-mobile-data.md
Normal file
55
docs/adr/0042-mobile-data.md
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# ADR 0042: Sync held back on mobile data
|
||||||
|
|
||||||
|
- **Status:** decided 10 Nov 2026 by the developer: D1 is their own request, D2 to D5
|
||||||
|
the agent's recommendations, which they chose. Not built yet.
|
||||||
|
- **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.
|
||||||
58
docs/adr/0043-shared-file-store-todo-attachments.md
Normal file
58
docs/adr/0043-shared-file-store-todo-attachments.md
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# ADR 0043: One file store in halkyon_base; Todo's attachments
|
||||||
|
|
||||||
|
- **Status:** decided 10 Nov 2026 by the developer: D1 and D2 are their own requests,
|
||||||
|
D3 their choice over the agent's recommendation (20 MB), D4 and D5 the agent's
|
||||||
|
recommendations, which they chose. Not built yet.
|
||||||
|
- **Builds on:** ADR 0004 (Learn's assets, rule 5), ADR 0014 (the module), ADR 0017
|
||||||
|
(the account's keys), ADR 0019 (sealed files), ADR 0033 (Todo), ADR 0042 (mobile data).
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Learn keeps people's files (images, sounds, PDFs) in the server's private storage by
|
||||||
|
space and SHA-256 (`AssetEndpoint`, `file_limits.dart`, `file_pruning.dart`), counts
|
||||||
|
each person's room against `FILE_LIMIT_IDENTIFIED_MB` and the operator's allowances,
|
||||||
|
and seals a private studyset's files on the device (`HLKF`, ADR 0019). Todo has
|
||||||
|
descriptions and comments as rich text (ADR 0033, D36, D38) and no files. The developer
|
||||||
|
asked for attachments in both, now (10 Nov 2026).
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- **D1.** Todo gets attachments in a task's description and in comments: an image shows
|
||||||
|
inline, any other file as a chip with its name and size that opens in the system's
|
||||||
|
app (or saves, on a desktop).
|
||||||
|
- **D2.** The file store moves into **halkyon_base**: the endpoint (upload by hash into
|
||||||
|
the caller's own space, finish, usage, download by a row a member may read), the
|
||||||
|
limits, the room counted and the pruning. Learn's server uses the module's and drops
|
||||||
|
its own; the tables keep their names and data (a move with the version rows alone,
|
||||||
|
as ADR 0014's moves). The module asks each app, through a hook as `deletePersonData`,
|
||||||
|
which synced rows name a file, for download rights and the pruning: Learn's `asset`
|
||||||
|
rows, Todo's attachment rows.
|
||||||
|
- **D3.** Todo takes **any file up to 100 MB**, as Learn's PDFs. Every Todo file is
|
||||||
|
sealed (everything of Todo's is, ADR 0033, D5), so the server can't look into it and
|
||||||
|
checks only its size and the sealed header; Learn's rule for readable files (over 20
|
||||||
|
MB only a PDF) stays Learn's.
|
||||||
|
- **D4.** **Room per service**, Learn's rule: each server counts its own people's files
|
||||||
|
against its own `FILE_LIMIT_IDENTIFIED_MB` (500 MB by default), `FILE_LIMIT_INSTANCE_MB`
|
||||||
|
and allowances. The operator's grants screen (ADR 0021) grants a service's room on
|
||||||
|
that service.
|
||||||
|
- **D5.** A device fetches a file **when it is shown**, then keeps it in the person's
|
||||||
|
folder; offline, it has what was seen. On mobile data, *Without files* (ADR 0042)
|
||||||
|
holds back uploads and downloads alike; a file not fetched shows as its chip with
|
||||||
|
*Download*.
|
||||||
|
|
||||||
|
## Todo's rows
|
||||||
|
|
||||||
|
An `attachment` table, synced and sealed as every Todo table: the file's hash, sealed
|
||||||
|
size and kind (image or file), its name and the task or comment it belongs to, all in
|
||||||
|
the sealed payload but the hash, which the server needs to count and serve. The rich
|
||||||
|
text names the attachment by its row ID, as Learn's editor names an asset. Deleting a
|
||||||
|
task or a comment blanks its attachment rows (the payload first, ADR 0002, rule 2); the
|
||||||
|
module's pruning removes a file no row names any more.
|
||||||
|
|
||||||
|
## To work out when building
|
||||||
|
|
||||||
|
- The move of Learn's tables into the module: their migration SQL (version rows only,
|
||||||
|
or a rename where the module's naming differs), explained before it is applied.
|
||||||
|
- Whether the sealed header (`HLKF`) and Learn's `SealedFile` move to `halkyon_keys`
|
||||||
|
for Todo to share, which the recommendation is.
|
||||||
|
- The quota's text in Todo's Settings: Learn shows the room used in its Sync card.
|
||||||
82
docs/adr/0044-shared-spaces-and-collaboration.md
Normal file
82
docs/adr/0044-shared-spaces-and-collaboration.md
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
# ADR 0044: Shared spaces and collaboration, people and agents alike
|
||||||
|
|
||||||
|
- **Status:** decided 10 Nov 2026 by the developer: D8 is their own request, D9 and D10
|
||||||
|
their choices over the agent's recommendations (all ticked at first; agents editing
|
||||||
|
directly), D1 to D7 the agent's recommendations, which they chose. Not built yet; the
|
||||||
|
open questions below come first.
|
||||||
|
- **Builds on:** ADR 0005 (publishing, reports and suggestions), ADR 0014 (the module),
|
||||||
|
ADR 0016 (one account across an instance), ADR 0017 (key pairs), ADR 0019 (private
|
||||||
|
studysets), ADR 0021 (operators and assistants).
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
A studyset belongs to one person: their rows are in their personal space, a private
|
||||||
|
one sealed with a data key that only their own devices unwrap. The developer asked (10
|
||||||
|
Nov 2026) for Vaia's sharing (*Manage collaborators*: invite by email, an invite link,
|
||||||
|
the members and their roles; *Collaboration settings*: joining by link, collaborators
|
||||||
|
inviting others; *Share with the community*: choosing what material goes public) and
|
||||||
|
for collaboration with everyone, people and agents alike: *invite @Paul or @Claude to a
|
||||||
|
studyset*, Claude Code on halkyon.li's host working as an agent account, as Copilot or
|
||||||
|
Claude work on a GitHub repository.
|
||||||
|
|
||||||
|
The sync engine already has shared spaces (`OfflineSyncSpaces`: create, grant, revoke,
|
||||||
|
members), with two roles, `readOnly` and `readWrite`, which limit writes alone: a
|
||||||
|
member reads everything in the space.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- **D1.** **Shared spaces live in halkyon_base,** for every app: members, roles,
|
||||||
|
invitations, links, settings and the wrapped keys. Learn's studysets use them first,
|
||||||
|
Todo's projects later on the same tables.
|
||||||
|
- **D2.** **Roles: Owner, Editor, Viewer.** The Owner can do everything, delete the
|
||||||
|
studyset and hand it to another member; an Editor changes content; a Viewer reads and
|
||||||
|
studies, with their own reviews in their own space. Editor and Owner are the engine's
|
||||||
|
`readWrite`, a Viewer its `readOnly`; who is Owner is the module's own table, checked
|
||||||
|
by its endpoints.
|
||||||
|
- **D3.** **A private studyset's key is wrapped to each member's public key** (ADR
|
||||||
|
0017's key pairs) by a member's device that holds it, at its next sync after someone
|
||||||
|
joined. The instance never sees the key; reading waits for that sync. A member
|
||||||
|
removed gets no new content: the studyset rolls to a new key, as ADR 0019's rewrites.
|
||||||
|
- **D4.** **Invites by @handle or by email.** An @handle adds a person on the instance at
|
||||||
|
once (they see it in their Inbox and may leave). An email address gets a mail with a
|
||||||
|
link to join, which works after signing up; the address is never shown to the other
|
||||||
|
members.
|
||||||
|
- **D5.** **Joining by link is off at first;** someone joining by it is a Viewer, whom
|
||||||
|
the Owner can raise. *Collaborators may invite others* is off at first too.
|
||||||
|
- **D6.** **Agents are accounts of their own,** marked as agents, each owned by a person
|
||||||
|
who made it from their Profile; listed in the owner's Profile, deleted with them. An
|
||||||
|
agent has its own key pair, kept where it runs, signs in with a token the owner made
|
||||||
|
(never a password), and is invited like a person: *@claude*.
|
||||||
|
- **D7.** **An agent reaches the instance through a small CLI or MCP server** run beside
|
||||||
|
it (Claude Code on halkyon.li's host runs as its own user, without Docker): it signs
|
||||||
|
in with its token, syncs the spaces it is a member of, opens the sealed rows with its
|
||||||
|
own keys, and writes through the same endpoints an app uses.
|
||||||
|
- **D8.** **Share with the community** (publishing to Explore, ADR 0005) gets a step
|
||||||
|
confirming the material: every PDF, other file and note listed by kind.
|
||||||
|
- **D9.** That step's choice: **all unticked at first**, with per kind *tick all*, and
|
||||||
|
on any item *everything but this* and *nothing but this*. Cards and the learning path
|
||||||
|
always go.
|
||||||
|
- **D10.** **An agent suggests; a person accepts.** An agent is a Viewer in the engine's
|
||||||
|
sense and writes nothing into the studyset; its changes go as suggestions, which a
|
||||||
|
member with Editor's rights accepts or declines, each marked as by @claude. Learn's
|
||||||
|
reports and suggestions (ADR 0005, stage 4: `feedback_thread`, the author's
|
||||||
|
decisions, accepted suggestions applied by their item) are the start: they become
|
||||||
|
suggestions within a shared space too, not only on published releases.
|
||||||
|
|
||||||
|
## Open questions, before building
|
||||||
|
|
||||||
|
- **Moving rows between spaces.** A studyset's rows are in its owner's personal space;
|
||||||
|
sharing moves them into a new shared space. Row IDs are unique across a table and the
|
||||||
|
engine keeps a deleted row's tombstone until its space goes, so a row can't simply be
|
||||||
|
deleted in one space and inserted in another under the same ID. Either the engine
|
||||||
|
learns to move a row between spaces (a change to our fork), or sharing copies the tree
|
||||||
|
under new IDs and rewrites what names them (cards' reviews, tags, the learning path),
|
||||||
|
as ADR 0019's rewrites do with keys. To try against the fork first.
|
||||||
|
- **Whose reviews.** A Viewer's or Editor's schedule for a shared card stays in their own
|
||||||
|
space, keyed by the card's ID; the card's own review rows today assume one learner.
|
||||||
|
- **Handing on and leaving.** An Owner leaving must hand the studyset on first, or it
|
||||||
|
goes with them, as their account's deletion deletes their spaces.
|
||||||
|
- **Members across services.** On an instance with a register (ADR 0016), handles are
|
||||||
|
the register's; a member of a Learn studyset needs no Todo account and the reverse.
|
||||||
|
- **The agent's token:** made and revoked in the owner's Profile, scoped to the agent's
|
||||||
|
account, never shown again after making; whether it expires.
|
||||||
Loading…
Reference in a new issue