Halkyon's ADR 0044, step 6c (D7, D49 to D52). apps/learn/halkyon_learn_agent
is one executable, `halkyon-agent --instance URL sign-in | sync | mcp`:
- sign-in keeps the token read from stdin, signs in with it, makes the
agent's key pair on its first sign-in, keeps it before setting the
public halves on the register (agents.setKeys), and syncs;
- it opens its data as the app's _openPersonData does, on
halkyon_learn_core and halkyon_content: its own SQLite database, the
sync's space router, its keys and those of the spaces shared with it,
and the repositories of studysets, cards, notes, paths, files and
feedback;
- mcp serves ten tools over stdio (dart_mcp): list, read and search the
studysets shared with it, suggest a change or a new card or note,
report, list a studyset's suggestions and reply. Each syncs first and
a suggestion again at once; a refusal is the tool's error;
- its files live in $XDG_CONFIG_HOME/halkyon-agent/<host>, 0700 and
0600, refused when others may read them (D50);
- tool/install.sh builds it with `dart build cli` (libsodium and SQLite
bundled) and copies it to its user on the host, the build before kept
(D52).
The synced gateways of learning paths and files, and the fakes of the
core's gateways, move into halkyon_learn_core (its testing.dart), which
the app's tests and the agent's share.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
ADR 0044's D49 (amended in building it): what reads and seals Learn's
content moves out of the Flutter packages, so Learn's agent, which runs
without Flutter, shares it rather than repeating the privacy rules.
packages/halkyon_content (new, pure Dart) takes from halkyon_app the
envelope and its cipher, SealedFile, UnlockedAccountKeys, shared
spaces' gateway with SharedKeys and KeyPins, FileCache and
FileServerGateway, with their fakes and tests; halkyon_app exports it,
so no app changes. Its rich_text.dart takes RichContent from
halkyon_editor, which exports it. newAccountKeys makes an agent's key
pair. halkyon_core takes DelayedDeletions and gains SyncRequests, what a
synced gateway asks of the sync, which SyncService implements.
apps/learn/halkyon_learn_core (new, pure Dart) takes Learn's privacy,
sync, payloads and the models, codecs, gateways and repositories of
studysets, cards, notes, tags and feedback, with what they reach of
files, learning paths, citations and study options; the app imports them
from there. A subject's icon, colour and name stay in the app
(SubjectLook); what only the app shows (progress, exam coverage, the
card browser, deletions' waits) stays too. Nothing changes for people
using the apps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Step 4 (d) of ADR 0044 (D14, D23, D27, D28), without its screens yet.
- `StudysetSharing`: refuses a studyset with a publication in its tree
and a device that can't seal, before anything is made; makes the
space, readies it, exports the tree with its progress, imports it
under new IDs with its top placed in the space, and deletes the old
one for good once the copy is whole. Failing or aborted, the space
is deleted and the studyset stays as it was.
- `SyncSpaceSetup`: syncs until the person's membership of the new
space came, then makes its first key and seals it to them.
- `Privacy` writes a top studyset placed in a space with that space's
key; a studyset can be inserted under an ID chosen beforehand.
- The archive's export and import take `moved`, keeping each item's
link to Explore and each card's origin.
- An import that fails midway takes back what it wrote, as an aborted
one did.
Whats-New: An import that fails partway no longer leaves part of the studyset behind.
Whats-New-De: Ein Import, der unterwegs scheitert, lässt kein halbes Lernset mehr zurück.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
ADR 0043's recommendation, for Todo's attachments: what Learn had for its
files on the device moves into halkyon_app, unchanged but for its names,
and Learn uses it from there.
- FileCache, DirectoryFileCache (Learn's AssetFileCache): the files by
their SHA-256 in a folder of the person's, and which wait to be
uploaded.
- FileServerGateway, ServerpodFileServerGateway (Learn's
AssetServerGateway): halkyon_base's files endpoint, taking the module's
caller rather than Learn's client; FileLimitReached, FileRoom.
- Their fakes in halkyon_app's testing: MemoryFileCache,
FakeFileServerGateway, sha256Of.
- SealedFile: the format every app seals a file in (HLKF, version 1),
sealing with halkyon_app's ContentCipher; Learn's Privacy seals and
opens its files with it, for the same context as before, so every file
sealed so far opens as it did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Exporting a studyset said "Exporting…" in a snack bar until the place to
keep it was asked. It now shows a sheet with its steps (reading the
content, gathering files for an archive, packing), how many of the step's
items are done and the percent of all of it, and Abort, which keeps
nothing (decided 9 Nov 2026); then asks where to keep the file as before.
Both exporters check between studysets, and the archive's between files;
packing runs on its isolate to its end, its result dropped once aborted.
ExportCubit runs an export's work behind the sheet (runExport), which
says how it ended: made, failed (a file missing named as before) or
aborted.
Whats-New: Exporting shows its steps and how far it has come, and Abort stops it.
Whats-New-De: Der Export zeigt seine Schritte und wie weit er ist, und „Abbrechen“ beendet ihn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Importing a Halkyon archive showed one bar. It now says which of its three
steps it is in (copying files; writing studysets, flashcards and notes;
bringing over the progress), how many of the step's items are done and the
percent of all of it, and offers Abort (decided 9 Nov 2026).
The copier checks between items, never leaving one half written, and
keeps a trail of what it wrote: the top studyset once made, and the files'
rows it made (not those the person had, found before each file is added).
Aborted, the importer deletes the top studyset for good with everything
inside it, as Delete now does (the reviews, answers and pages read going
with their cards, steps and PDFs), and those rows; the tags it made stay,
as names the person may well use anyway. Once all of it is written, Abort
no longer undoes it.
WorkProgress, WorkStages and AbortSignal (core/work/) and
WorkProgressView (core/widget/) are shared with the Anki import and the
exports, which follow.
Whats-New: Importing an archive shows its steps and how far it has come, and Abort stops it, keeping nothing of it.
Whats-New-De: Der Import eines Archivs zeigt seine Schritte und wie weit er ist, und „Abbrechen“ beendet ihn, ohne etwas davon zu behalten.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Todo stores editor documents too, so the document, the editor, its view,
the format bar, links, the clipboard, scripts and EditorBlocks move, with
their strings and tests, to a shared package on the same appflowy_editor
fork. What is Learn's alone stays in Learn on top of it, and what the
editor took from it directly is inverted: EditorBlocks gains textMarks,
formatActions and onEditorClosed, RichContentEditor a wrapEditor, and an
InsertableBlock's name takes the BuildContext. Learn's screens use
LearnContentEditor and LearnContentView, which add Learn's blocks and the
grammar check. Learn behaves as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The public profile was already one per person for every service of an
instance (the register, ADR 0016); its app side was Learn's alone. It
moves into halkyon_app: the model, the repository on the module's
endpoints, the form, the avatar drawn on the device and the sheet with
someone's profile. They take the Public prefix (PublicProfileRepository,
BasePublicProfileGateway), as Train's own ProfileRepository holds the
body's figures. AppDependencies gains profiles and files, the picker the
avatar is chosen with, which moves out of Learn too.
HalkyonProfile is every app's Profile screen: the person's own tab first,
the same in every app (the account and the ways they sign in, the app's
own cards, then the public profile), then the app's own tabs, folding into
the title. Learn adds its inbox and Statistics. Train adds Progress, Days
and, where nothing records, Plans, and so opens on the person's tab, no
longer on Progress. Todo, which has no Profile tab, opens it from a row at
the top of Browse, where Todoist keeps the account.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
- `AssetRepository` seals a file of a private studyset with its tree's key
for its asset row before hashing it (`AssetSealing`, which `Privacy`
does): the row's hash and size and the file uploaded are the sealed
file's, the upload says it is encrypted, and the row's payload, sealed
too, carries the file name and the hash of what was sealed (asset
schema 2). A download is checked against the row's hash, opened for its
row and checked against what was sealed. This device keeps the sealed
file to upload and the opened one, by its own hash, for what reads files
by path.
- The same file is a row per key: in a private and a readable studyset, or
two private trees, it is two.
- Every place adding a file names its studyset: the editors' pictures and
sounds (`StudysetFiles`, which every screen of a studyset and the PDF
reader set), PDFs and screenshots, Anki imports, copies and their updates
(a copy's top studyset is made first, so its files take its key), and
accepted suggestions (by their item's tree).
- Making a tree private or not, or moving a topic into another tree, moves
its files: each under another key is added anew under the tree's, the
content names the new row (whole `asset:` values and a PDF's file, never
text), and the old row is blanked and deleted once nothing else names it.
- Tests: a private studyset's file never uploads readable; a sealed file
moved to another row, or under a key not open here, doesn't open; a
changed file is refused by its hash; one row per key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Its server, client, app and curator, and the tools that write its
assets (maps, Anki fixtures, icons), as they are; paths are fixed in
the commits that follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>