Commit graph halkyon/apps
Author SHA1 Message Date
0594c4f3fe
feat(learn): shared studysets sealed with their space's latest key
Step 4 (c) of ADR 0044 (D20 to D22).

- `SharedSpaceKeys`: after each sync, opens the keys members' devices
  sealed to the person and seals those this device holds to newcomers,
  space by space, one failing holding back none of the others; keeps
  each space's latest key, counting only keys whose sealed ID names
  that space; makes a space's new key and shares it with its members.
- `Privacy`: a studyset in a shared space takes the space's latest key,
  after its own signed record and the key it was last seen sealed with,
  so a private studyset the instance claims is shared keeps its own
  key. An item last seen under a space's key is written with that
  space's latest (D22: earlier rows keep theirs). Writing is refused
  while no key of the space is open (`spaceKeyMissing`), and a shared
  studyset is never made readable nor given a key of the person's own
  (`shared`, D21). Sweeps keep to studysets with a signed record, which
  a shared one never has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 14:13:37 +01:00
92b87e218d
feat(learn): new rows of a shared studyset go in its space
ADR 0044, D24, step 4's routing. `SpaceRouter` answers the sync engine's
`newRowSpace`: a studyset's cards, notes, paths, PDFs and their
revisions go in the space the studyset is in, a topic in its parent's,
a studyset placed for a share (`placeIn`) in its space, and a file in
the space its key is for; everything else, reviews and tags among them
(D2, D25, D26), stays the person's own. Which studysets are shared is
read from the engine's grants and kept until a sync brings changes.

A shared space's key has an ID of its own, `SpaceKeyId`
(`h1.<space>.<ms>.<nonce>`), which `ContentKeyId` reads as none of its
own, so no `content_key` row passes for one.

No studyset is shared yet: until one is, every row goes in the person's
space as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 14:13:36 +01:00
358d983011
refactor(learn): own rows name shared content by its ID alone
ADR 0044, D26. The sync engine links no row of one space to a row of
another: a reference across spaces is blanked, on the device and on the
server alike (found by experiment, 11 Nov 2026). Reviews, card, note and
path tags, PDF readings and path answers are the person's own (D2, D25)
but will name cards, notes, paths, steps and PDFs of spaces they share,
so those six tables keep their columns without the relation. Every
gateway already deletes them as it deletes what they name.

The migration drops the nine foreign keys of those tables and adds the
three to `tag` again, which Serverpod numbers anew; no column or row
changes. The synced tables' hash covers foreign keys, so released apps
stop syncing with a server that has it until they update: deploy it
together with a release of Learn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 12:40:23 +01:00
b72cea1fe0
feat(base,app,keys): keys of shared spaces, sealed to each member
Step 3 of ADR 0044 (D3, D18, D19). A shared space's data keys are
sealed on a device to each member's public keys; the instance keeps
what devices sealed and never opens it.

- halkyon_keys: the safety code of two accounts' public keys
  (`safetyCode`), 60 digits, the same on both sides.
- halkyon_base: the register hands out accounts' public keys
  (`public-keys-of`, local and remote); the table `shared_space_key`
  and the endpoint `sharedSpaceKeys` (memberKeys, keysNeeded, seal,
  mine). Only the Owner and Editors seal, the first key sealed to a
  member stays, and a member's keys go with their membership. Every
  host's migration creates the table.
- halkyon_app: `ContentCipher` seals a key to another account and
  opens one sealed by another; `SharedKeys` shares a space's key with
  its members, seals keys to newcomers and opens the person's own,
  holding each member to the public keys `KeyPins` pinned on first
  sight (`key_pins.json` in the person's folder), a change refused
  until trusted anew.

Learn takes them into its `Privacy` in step 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 11:35:42 +01:00
d08d8ad545
feat(base,learn): spaces people share, kept by halkyon_base
ADR 0044, step 2 (D1, D2, D11, D13, and D15 to D17, decided today).
halkyon_base keeps the spaces several people share, for every app:
shared_space, its members with their roles (Owner, Editor, Viewer), and
invitations by handle waiting for someone with no account on the
service yet. The sharedSpace endpoint creates a space, lists its
members, invites by handle, changes roles, removes members, hands the
space on, leaves and deletes it.

Who may do what:
- Only people with an identity reach the endpoint.
- A space the caller isn't in can't be told from one that doesn't
  exist.
- Members are named by their membership, never by their account's ID.
- Only the Owner manages the members, unless the space lets members
  invite.
- The Owner's role passes only by handing the space on, and the Owner
  leaves only naming who takes it on.
- An invitation counts as an answer about a handle, as it tells whether
  one exists.
Every rule has a test asserting the refusal.

The module stays free of the sync engine: a host hands it a SpaceGrants,
and every call runs in the module's transaction, so the engine's grants
follow the roles or nothing changes. The Owner and an Editor write; a
Viewer reads. Learn hands over SyncSpaceGrants on OfflineSyncSpaces. Todo
hands over none yet, so its server refuses until its projects are
shared.

Deleting an account hands each space the person owns to the Editor who
joined first, else the Viewer who joined first. A space with no other
member goes. This runs before the host's own deletions. A person's first
sign-in on a service makes their waiting invitations memberships, in the
same transaction that makes them a person there.

Migrations: the module's and every host's (Learn, Todo, Train, Meditate)
create the three tables, their indexes and the two cascading foreign
keys to shared_space, and nothing else. The old server keeps working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 10:54:19 +01:00
d664115c46
feat(learn): confirm the notes and files a release takes
ADR 0044, D8 and D9, its first step: before publishing to Explore, the
person confirms the notes, PDFs and other files that go. Cards and
learning paths always go.

Publish's Continue leads to a step listing them by kind, none ticked at
first but those the latest release held. Each kind is ticked whole, and
an item's menu ticks everything but it or nothing but it. What isn't
ticked goes to the server as publish's leftOut. So does whatever the
sync before publishing brings that the person never saw.

The server leaves those out of the release and its counts, and leaves
out the files only they name. leftOut is optional: released apps send
none and publish everything, as before. publish.releasedMaterial names
the notes and files of the latest release, and none for a studyset not
the caller's, as for one never published.

Whats-New: Before publishing to Explore, you choose which notes and files go with the cards and learning paths.
Whats-New-De: Vor dem Veröffentlichen auf Explore wählst du, welche Notizen und Dateien mit den Karteikarten und Lernpfaden mitgehen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 10:06:38 +01:00
3ca4b4308d
feat(todo,app): files attached to tasks and comments
Halkyon's ADR 0043, D1, D3, D5 and D8: any file up to 100 MB, attached to
a task's description or to a comment, sealed as everything of Todo's.

Server: the attachment table, synced and sealed as the others, with the
sealed file's hash readable for the store; todoFiles hands halkyon_base
its rows, readable by their space's members, and takes sealed files
alone; the files no attachment names go daily. Its tests hold downloads
to the row's space, refuse a readable file and prune what no live row
names. Migration attachments: CREATE TABLE "attachment" with its indexes
and the foreign key to its space, cascading as every synced table's.

App: AttachmentRepository seals each file for its row, keeps it sealed
and opened in the person's folder, uploads it after each sync and fetches
it as it shows, both held back by Without files on mobile data but for a
file tapped. Under the description and a comment's text, a row of them:
pictures as thumbnails opening full-size, other files as chips opening in
the system's app, else saved; the paperclip beside the description and in the comment's
composer adds them, the composer holding them until it is sent. A task deleted takes its files
and its comments' along, a comment its own.

halkyon_app opens a file in the system's app (FileOpener): on Android
through its plugin and a FileProvider of its own class, sharing the
cache's halkyon_opened/ folder alone; on the desktop as a file: URI.

Whats-New: Attach files to a task's description or a comment with the paperclip: pictures show as thumbnails, other files open in their app.
Whats-New-De: Hänge mit der Büroklammer Dateien an die Beschreibung einer Aufgabe oder an einen Kommentar: Bilder erscheinen als Vorschau, andere Dateien öffnen sich in ihrer App.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 01:19:41 +01:00
5dd7575dd6
refactor(app,learn): people's files on the device shared in halkyon_app
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>
2026-11-11 01:19:40 +01:00
b7f0aaaec7
refactor(base,learn): people's files kept by halkyon_base
ADR 0043, D2, D4, D6 and D7, the first step towards Todo's attachments:
Learn's file store moves into the module, for every server that keeps
files, and Learn becomes its first host.

halkyon_base gets the store as Learn had it: uploads into the caller's
own space by SHA-256, downloads by a row the caller may read, each
person's room (FILE_LIMIT_IDENTIFIED_MB, FILE_LIMIT_INSTANCE_MB, the
operator's file_allowance, which moves into the module unchanged) and
the pruning of files no live row names. It stays free of the sync
engine: a host hands it a FileStore saying which of its rows name a file
and what it takes (HalkyonBase.files); without one the files endpoint
refuses. Its tests, with refusals, run on a stand-in host.

Learn hands it learnFiles: its asset rows, read by their space's members,
a readable file over 20 MB only as a PDF, up to 100 MB. Its app calls the
module; its own asset endpoint stays for released apps alone, forwarding
to the module in Learn's own models, whose names are on the wire. Its
file tests still pass through it, end to end.

Migrations: the module's creates file_allowance. Learn's server migration
holds version rows alone, as tables are matched by name (ADR 0014); its
device migration creates the module's empty table, as every module's.
Todo's, Train's and Meditate's create the empty table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 01:19:28 +01:00
59fb9c4d6d
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:46:29 +01:00
fd6647d752
feat(todo): a task done ticks on phones and chimes on desktops
The developer's request of 10 Nov 2026: completing a task should feel like
something. `CompletionFeedback`, held by `TodoDevice` and handed to each
person's `TodoPerson`, gives a light haptic tick on Android and iOS and, on
desktops, a short quiet chime of our own: two soft bell tones a fifth apart,
synthesized by `apps/todo/tool/completion_sound.dart` into
`assets/sounds/task_done.wav`, so it needs no one's licence. A task's check
and the selection's Complete call it once, however many tasks are done;
an undo is silent.

Settings' Tasks turns the chime off on a desktop, kept per device in
`<app support>/completion_feedback.json`; a phone is offered no switch. A
device that can't play loses only the sound. The player is `audioplayers`,
as Learn's sounds: on Linux it links GStreamer, so Todo's snap gains the
`audio-playback` plug and GStreamer's headers to build, as Learn's recipe has.

Whats-New: Completing a task now gives a light tap on phones and a short chime on computers, which Settings › Tasks turns off.
Whats-New-De: Eine erledigte Aufgabe gibt jetzt auf dem Handy ein leichtes Tippen und am Computer einen kurzen Klang, den du unter Einstellungen › Aufgaben ausschaltest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 23:07:58 +01:00
1cc27ef0d6
feat(learn): search ranks titles first; privacy changes show progress
- The Library's search lists titles starting with what is typed first,
  then titles holding it as a word, then secondary titles, rather than
  by the shelves' recency.
- The offer to make every studyset private is gone: each studyset's
  lock already says and changes it, and new ones are private.
- Making a studyset private or not from its sheet flips the switch as
  soon as the person agrees and says that everything in it is being
  written anew, rather than standing still for a minute.
- Jellyfin's password can be shown while typed.

Whats-New: Searching the library lists title matches first; changing a studyset's privacy shows its progress; Jellyfin's password can be shown.
Whats-New-De: Die Suche in der Bibliothek zeigt Treffer im Titel zuerst; das Ändern der Privatsphäre eines Lernsets zeigt den Fortschritt; das Jellyfin-Passwort lässt sich anzeigen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 23:07:57 +01:00
1781553f1d
perf(learn): local reads no longer wait on the instance
Every read of the person's content refreshes their keys, and a refresh
asked the instance whether the account has keys until it once answered
yes, which an account signing in with SimpleLogin alone never does. So
on a poor connection every read waited on a request: the library's
progress, a request per studyset in a row, took over five minutes, and
Home was slow too.

A device holding the keys already knows the account has them, so the
keys now open first and the instance is asked only while no other sign
says so (a device without the keys still asks at every refresh).

Whats-New: The library and Home open quickly again without a good connection.
Whats-New-De: Die Bibliothek und die Startseite öffnen sich ohne gute Verbindung wieder schnell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 23:07:52 +01:00
151f848360
feat(learn): Markdown files beside the PDFs, read formatted
A studyset's files take Markdown (.md, .markdown) as well as PDFs, in
the same synced rows with a kind (payload schema 3, written only for
Markdown, so older apps skip those rows). Upload a PDF becomes Upload a
file; a Markdown file must be UTF-8 text.

The reader opens one formatted and read-only through the editor's
Markdown reader, keeping fenced code, which that reader drops; the file
itself never changes, and its source text is a tap away, selectable too.
It has neither search, zoom nor screenshots. It reads in 100 parts by
its top-level blocks, so reading progress, deadlines and the cards and
notes tied to a place work as a PDF's pages do, shown as a share; its
outermost headings are its chapters.

Whats-New: Upload Markdown files beside your PDFs: they open formatted, with reading progress, deadlines and flashcards from what you select.
Whats-New-De: Lade Markdown-Dateien neben deine PDFs hoch: Sie öffnen sich formatiert, mit Lesefortschritt, Fristen und Karteikarten aus deiner Auswahl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 21:31:42 +01:00
690f69fa55
feat(learn): a learning path's Scroll view, chosen in the study settings
A path shows either one step at a time (Steps) or on one page (Scroll,
as Tomorrow University's): the steps one below the other up to the first
question not answered right, answered in place with a lock below it, the
page running on once it is right, Finish at its end. A text step is done
once its end was on screen. The outline of the steps (done ticked, the
one in view marked, those beyond the question locked, a tap scrolling
there) is a column from expanded windows on, a drawer from the bar below.

The choice is the person's study option (`StudyOptions.pathView`,
settings schema 15), in Settings › Study and the session settings.

Whats-New: Learning paths can show on one scrolling page with an outline beside them: choose Scroll under Settings › Study.
Whats-New-De: Lernpfade lassen sich auf einer scrollbaren Seite mit Gliederung daneben zeigen: unter Einstellungen › Lernen „Scrollen“ wählen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 21:31:37 +01:00
3257905536
feat(learn): furigana and pinyin over hints, from Tatoeba's transcriptions
The curator puts Tatoeba's readings on a dictionary's examples by their
Tatoeba ID (`withTatoebaReadings`, `curate readings`, and in `update`):
furigana over JMdict's Japanese from its jpn transcriptions, pinyin with
tone marks over Chinese from its cmn ones, each word over as many Han
characters as it has syllables. An example gets `ruby`, `[start, end,
reading]` each, and `rubyUnreviewed` where no one wrote or checked them
(MeCab's furigana, the automatic pinyin). JMdict's conversion goes to 3,
so instances convert again at once.

A word's example hints carry them as ruby marks, the word still in bold.
The hints sheet shows them from the start with a switch above
(Furigana, Pinyin), kept on the device (`DeviceSettings.hintReadings`),
and below a hint whose readings no one reviewed says so.

Whats-New: Example sentences in a word's hints now show furigana or pinyin over them, with a switch to hide them.
Whats-New-De: Beispielsätze in den Hinweisen eines Worts zeigen jetzt Furigana oder Pinyin darüber, mit einem Schalter zum Ausblenden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 20:28:57 +01:00
bed39dc2a2
feat(learn): a split of Topics, Introduction and Bibliography beside the material
As Tomorrow University's challenges have it (the developer's decisions of
10 Nov 2026): beside a studyset's material, one at a time, its topics,
its introduction (the description, whole; left out where there is none)
and its bibliography, read and changed in place. In the side column from
expanded windows on, in the topics' sheet below; it opens on Topics, at
every level. The description no longer stands above the material, nor
the bibliography's button at the foot of the topics. The pill between
the material's parts becomes PillSwitch, shared by both.

Whats-New: A studyset's topics, introduction and bibliography now share one panel beside its material, a tap apart.
Whats-New-De: Themen, Einführung und Literatur eines Lernsets teilen sich jetzt ein Feld neben dem Material, einen Tipp voneinander entfernt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 20:28:50 +01:00
a3b124ddcb
feat(learn): the library on shelves by recency, narrowed to a subject (ADR 0041, D8)
The library as Vaia's (the developer's decisions of 10 Nov 2026): the
starred on a shelf of their own, then those studied or changed in the
last 7 days, the last month and before, each under its heading, the
archive last. Recent is the later of a card's last rating
(StudyProgress.lastStudied) and the studyset's own last change. A
dropdown narrows the library to one of the subjects in use, kept on the
device (DeviceSettings.librarySubject). A card names its subject under
its title, with its icon, and a language course its language beside.

Whats-New: The library groups your studysets by when you last used them, and narrows them to one subject.
Whats-New-De: Die Bibliothek ordnet deine Lernsets danach, wann du sie zuletzt genutzt hast, und zeigt auf Wunsch nur ein Fach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 19:21:00 +01:00
9868dc27c6
feat(learn): a studyset's subject, picked from a fixed list (ADR 0041)
A top studyset is filed under one of 44 subjects in six groups (the
developer's decisions of 9 and 10 Nov 2026): languages as one, computing
in five, Other / not sure last. Each has a Lucide icon and a default
colour, which picking it gives the studyset, its own to change from then
on. The studyset's sheet picks it (pickSubject); a sub-studyset shows its
top studyset's (subjectOf) and offers none. The payload keeps it as
`subject` (schema 8), so archives and published studysets carry it; an
ID this version doesn't know is kept and reads as none.

Whats-New: A studyset can now be filed under a subject, which gives it that subject's colour.
Whats-New-De: Ein Lernset lässt sich jetzt einem Fach zuordnen, das ihm dessen Farbe gibt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 19:20:49 +01:00
a37ca123b1
feat(todo): the open sidebar lists favourites and the project tree, folds kept
Todo's open sidebar is laid out as Todoist's (the developer's decisions
of 10 Nov 2026): the favourite projects, filters and labels, then the
projects as a tree with their open counts, sub-projects folding. Browse
and the sidebar share their rows (project_lists.dart) and their folds,
which the device now keeps per person (FoldedProjects,
folded_projects.json in the person's folder) instead of forgetting them
at each start.

Whats-New: On a wide screen, the open sidebar lists your favourites and projects; folded sub-projects stay folded.
Whats-New-De: Auf breiten Bildschirmen zeigt die geöffnete Seitenleiste deine Favoriten und Projekte; eingeklappte Unterprojekte bleiben eingeklappt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 18:04:14 +01:00
3f441cbc98
feat: the rail opens into a sidebar with labels from expanded windows on
Decided by the developer on 9 and 10 Nov 2026: phones keep the bar,
medium windows (600 to 840 dp) the rail alone, and from 840 dp a toggle
at the rail's foot opens it into a sidebar 256 dp wide, as Proton
Drive's: the app named beside its icon, each tab its label beside its
icon, the icons where the rail had them, so nothing moves under the
pointer. Its labels come in one after another, top to bottom, all at once
where animations are off. Open or closed is kept per device, in
halkyon_app's Look (look.json, `sidebar`), which Learn keeps in its
DeviceSettings (`sidebarOpen`), as it does the appearance and language.

NavigationFrame takes sidebarOpen and onSidebarChanged (no toggle without
it), and sidebarSections, what the app lists below the tabs, which Todo's
favourites and projects will be. The closed rail keeps Flutter's widget,
its toggle pinned at the foot (trailingAtBottom); the open one is the
frame's own, as the staggered labels need. Learn, Todo, Train and
Meditate keep the choice.

Whats-New: In wide windows, the button at the foot of the side rail opens it into a sidebar with names, and it stays open.
Whats-New-De: In breiten Fenstern öffnet der Knopf unten an der Seitenleiste sie mit Namen, und sie bleibt offen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 18:04:09 +01:00
99b75645ad
feat(learn): exporting shows its steps, counts and percent, with Abort
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>
2026-11-10 16:19:55 +01:00
7cba684513
feat(learn): an Anki deck's import shows its steps, counts and percent, with Abort
Importing an Anki deck showed one bar and "Importing…". It now says which
of its four steps it is in (copying files, writing flashcards, tagging,
bringing over the review history), how many of the step's items are done
and the percent of all of it, and offers Abort (decided 9 Nov 2026), as
the archive's import does.

The importer checks between files and batches of cards. Aborted, it
deletes what it wrote: a studyset it made for the deck for good with all
of it; importing into an existing studyset, the cards it wrote there and
the topics it made for subdecks, leaving what the studyset held before;
and the files' rows it made, not those the person had. Tags it made stay.
Once everything is written, Abort no longer undoes it.

Whats-New: Importing an Anki deck shows its steps and how far it has come, and Abort stops it, keeping nothing of it.
Whats-New-De: Der Import eines Anki-Decks 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>
2026-11-10 16:19:54 +01:00
5f7cb70c3c
feat(learn): an archive's import shows its steps, counts and percent, with Abort
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>
2026-11-10 16:19:54 +01:00
8502fb38f0
feat(todo): the weather: a forecast on days, tasks tied to it, the days that fit (ADR 0033, D40)
A task can need weather: dry, sunny, calm and a temperature range (not
freezing, warm, or from and to as typed), set from Weather in its
details (a chip beside Priority, then a row of its own once it needs
any) and kept in its sealed payload (`weather`, schema stays 1).
Where its day's forecast misses any of it, its row carries a mark and
its details say what is missing; nothing moves and its reminder stays.
Its date picker lists the coming days the forecast fits, at its time or
from 8:00 to 20:00 without one.

The forecast is for the place set in Settings' new Weather card, kept in
the sealed settings; else, for a task with a place, its first place. The
device's location is never read for it. `ForecastCubit` asks the
instance for each place (`InstanceWeather`, rounded to about a
kilometre) as it comes, as the app opens, hourly and on a pull, and
holds the answers in memory alone. With a place in Settings, each day's
heading in Today and Upcoming shows its sky and temperatures, the 3-day
grid its icon. Settings says where the instance gives no forecasts, and
credits Open-Meteo as its licence asks.

Whats-New: A task can need dry, sunny, calm or mild weather: it is marked when the forecast doesn't fit, and its date picker shows the days that do; days show the forecast for the place set in Settings.
Whats-New-De: Eine Aufgabe kann trockenes, sonniges, windstilles oder mildes Wetter brauchen: Sie wird markiert, wenn die Vorhersage nicht passt, und ihre Datumsauswahl zeigt die passenden Tage; die Tage zeigen die Vorhersage für den Ort aus den Einstellungen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 15:28:03 +01:00
a91aa98817
feat(todo): Todo's server answers a place's forecast (ADR 0033, D40)
The weather of D40 comes through the instance, as Train's workouts'
(ADR 0036, D1): Todo's server gets a `weather` endpoint, `offered` and
`forecast`, answering a place's hours for the next 16 days from
Open-Meteo for people signed in alone, the place rounded to about a
kilometre, nothing kept or logged, 30 requests a person a minute.
Refusals are `WeatherRefused` (off, invalid, tooOften, unreachable).
`WEATHER=off` and `OPEN_METEO_API_KEY` switch it as they switch Train's,
and production's compose file passes them to Todo's server too.

D40 records the developer's decisions of 10 Nov 2026 on the weather and
the agent's details (what dry, sunny, a temperature range and calm mean
over a task's hours).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 15:27:58 +01:00
85a022af98
feat(todo): a project linked to a calendar shows its events beside the tasks (ADR 0033, D39)
A project's editor takes a calendar's address (https:// or webcal://,
refused otherwise), and its events show read-only, marked in the
project's colour: in Today's list and Upcoming's agenda above each day's
tasks, as blocks in the hour grids (Today's Calendar, Upcoming's 3-day)
sharing the lanes with timed tasks and all-day ones under All day, in
the project's Calendar (its agenda and month, its own events alone), and
in saved filters of days. A tap shows what an event is: when, where,
and whose calendar. The grid's lane layout is generic for it
(`layOutItems`); a task's row's box is shared (`rowBox`).

Whats-New: Link a calendar to a project in its settings: its events show beside your tasks in Today, Upcoming and the project.
Whats-New-De: Verknüpfe einen Kalender in den Einstellungen eines Projekts: Seine Termine erscheinen neben deinen Aufgaben in Heute, Demnächst und im Projekt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:23:04 +01:00
63d50f77d4
feat(todo): the calendars linked to projects fetched by the device and kept for offline (ADR 0033, D39)
`CalendarCubit` follows the projects' calendar links: a calendar linked
is shown at once from the copy kept and fetched by the device over HTTPS
(`HttpCalendarFeeds`, `webcal://` as HTTPS), as the app opens, every hour
while it is open and when a list is pulled down; a fetch that fails, or
answers with something that is no calendar, keeps the last copy; a link
taken off, or its project gone, takes its events and its copy along. The
copies are files in the person's own folder (`calendars/<project>.ics`).
Nothing shows the events yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:23:03 +01:00
892a58f4fd
feat(todo): a calendar's feed read, and linked to a project in its payload (ADR 0033, D39)
The developer's decisions of 10 Nov 2026 for calendar feeds, recorded as
D39: a project links to a calendar's iCal feed, its events shown beside
tasks, read-only; each device fetches it, the address sealed in the
project's payload, the last copy kept on the device.

This lays the ground: `readIcal` reads a feed's events (title, start,
end or duration, all-day, place, cancelled, the dates a recurrence
leaves out and the occurrences changed on their own), on the device's
clock through `timezone`, and expands their recurrence with pub.dev's
`rrule` (Apache-2.0, the developer's choice); `ProjectContent` gains
`calendarUrl`, its payload staying schema 1. Nothing shows it yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:23:03 +01:00
7f643aa00e
feat(todo): the list scrolls to each task as quick add adds it, just above its bar
As Todoist's (the developer's review, 10 Nov 2026): the row of the task
added brings itself into view, just above quick add's bar on a phone,
within its list beside the dialog on the desktop; while quick add is
open, a screen's lists leave room at their end for the bar and the
keyboard, so that even the last task can rise above them, nothing in
view moving for it. A long list builds the rows near its view alone, so
the screen scrolls on down until the task's row is built.

Whats-New: Each task you add now scrolls into view, just above the add bar.
Whats-New-De: Jede neue Aufgabe rückt jetzt ins Bild, direkt über die Leiste zum Hinzufügen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:23:02 +01:00
607ac71233
feat(todo): a task's sub-tasks held and dragged into another order in its sheet
As a project's rows are (D16): a long press on a sub-task in the task's
sheet or pane lifts it, and dropped on the upper or lower half of
another it goes there, still the task's sub-task (the developer's
review, 10 Nov 2026).

Whats-New: In a task's details, hold a sub-task to drag it into another order.
Whats-New-De: In den Details einer Aufgabe hältst du eine Unteraufgabe gedrückt, um sie in eine andere Reihenfolge zu ziehen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:23:02 +01:00
ca9ad6c69c
feat(todo): a task's title wraps in its sheet and pane, its words read as quick add's
The title runs over as many lines as it needs instead of trailing off,
and what it says is highlighted as in quick add and set as it is saved
(the developer's review, 10 Nov 2026): a day and its recurrence, the
priority, the deadline, labels, reminders and places added, the project
the task moves to; a word tapped, or a Backspace right after it, keeps
it as text. Enter still saves. Quick add's controller and reading of
its title are shared for it (title_tokens.dart).

Whats-New: In a task's details, a long title now wraps, and words like "every Monday" in it are recognised as in quick add.
Whats-New-De: In den Details einer Aufgabe bricht ein langer Titel jetzt um, und Wörter wie „jeden Montag“ darin werden wie beim schnellen Hinzufügen erkannt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:23:01 +01:00
f9cd30dd5e
fix(todo): the + is not dragged over a day's hours, in Today's Calendar and Upcoming's 3-day
There a tap, a click or a stretch dragged says when (the developer's
review, 10 Nov 2026); Upcoming's agenda and the lists keep dragging it.

Whats-New: In Calendar and 3-day, the + button no longer drags; tap the hours instead.
Whats-New-De: In Kalender und 3 Tage lässt sich der +-Knopf nicht mehr ziehen; tippe stattdessen auf die Stunden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:23:01 +01:00
8b862b2c1b
feat(todo): the hour grid leaves a strip free beside blocks; a stretch of time chosen by dragging
As Proton Calendar's (the developer's review, 10 Nov 2026): each day's
blocks leave 12 pixels free on its right, where a tap or a click still
adds a task beside them; a mouse dragged, or a finger held and dragged,
chooses a stretch of quarter hours, shown with its times, which quick
add opens with. A tap or a click still adds half an hour.

Whats-New: In Calendar and 3-day, drag across the hours to add a task for that long; tasks leave room beside them to add another.
Whats-New-De: In Kalender und 3 Tage ziehst du über die Stunden, um eine Aufgabe für so lange hinzuzufügen; neben Aufgaben bleibt Platz für eine weitere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:23:00 +01:00
c5eceda383
feat(ui): on a phone, the floating button sits just above the navigation bar, its screen's fade under it
Each tab's screen paints the bar's fade beneath its own button
(FrameFade as its bottomNavigationBar, extendBody), so the button stands
16 pixels above the bar, as Material's, instead of above the fade; the
frame keeps its own fade for screens without one, and gets it back while
a page is pushed over the screen.

Whats-New: On a phone, the + button now sits just above the navigation bar.
Whats-New-De: Auf dem Handy sitzt der +-Knopf jetzt direkt über der Navigationsleiste.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:23:00 +01:00
f47f6b23b5
fix(learn): a tab whose material is in its topics says so in the middle, as an empty tab does
The tab saying that its material is in the studyset's topics stood at the
top of the room below the tabs, while an empty tab's message stands in
its middle (SliverCentered), so switching between them moved the message
up and down (the developer's review, 9 Nov 2026). It stands in the middle
too now.

Whats-New: A studyset's tab whose material is in its topics now says so in the middle, as empty tabs do.
Whats-New-De: Ein Tab eines Lernsets, dessen Material in seinen Themen liegt, sagt das jetzt in der Mitte, wie leere Tabs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:22:59 +01:00
cc7511f060
feat(ui): in dark mode, filled and floating buttons in cobalt itself, white on it
In dark mode the primary colour is cobalt lightened (#AEB9FF), readable as
text and thin marks on the dark surfaces, and filled buttons took it: Todo's
send button glared bright beside everything else; floating buttons took
Material's dark primary container. Both are cobalt itself now, white on it,
as in light mode (decision 6 of the developer's list, 9 Nov 2026); links,
tabs and switches keep the lighter tone. Filled icon buttons take no theme
of their own in Flutter (the icon buttons' theme is the plain ones' too),
so they take filledIconButtonStyle: Learn's study and send buttons,
Todo's send buttons and Locate me, Meditate's stop dictating.

Whats-New: In dark mode, filled buttons and the + button are cobalt with a white icon, no longer a bright light blue.
Whats-New-De: Im dunklen Modus sind gefüllte Schaltflächen und die +-Schaltfläche kobaltblau mit weißem Symbol statt grell hellblau.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:22:59 +01:00
9e1442d89e
feat(todo): quick add's keys, sub-projects' colours, a task's menu on a right click, and the completed tasks deleted at once
The developer's list of 9 Nov 2026, Todo's part:

- Quick add: the down arrow and Tab move into the description, with its
  cursor (halkyon_editor's fix); Escape closes the projects or labels
  suggested first, then closes quick add, what was typed discarded; the
  project's chip shows the project's colour.
- A project without a colour of its own shows its parent's, the nearest
  ancestor's with one (colorOfProject, TodoCubit.colorOf), wherever a
  project's colour shows; the project editor's "Same colour as its parent"
  keeps it so, and the parent picker shows each project in its colour. A
  project of its own always has one.
- The task pane's x closes it, always; Close left the ⋮ menu.
- A task's row opens its menu on a right click: Mark as done, Date, Move
  to, Labels, Priority, Deadline, Duplicate and Delete, the selection's
  actions for that one task.
- Delete completed tasks, in the ⋮ of the Inbox, Today, Upcoming and a
  project, with Undo: the tasks leave the list at once and are deleted
  once the snack bar goes without its Undo (halkyon_ui's showUndoSnackBar
  gains onKept).

Whats-New: Quick add: the arrow keys and Tab reach the description, and Escape closes it without saving.
Whats-New-De: Schnell hinzufügen: Pfeiltasten und Tab erreichen die Beschreibung, und Escape schließt es, ohne zu speichern.
Whats-New: Sub-projects take their parent's colour unless they have their own.
Whats-New-De: Unterprojekte übernehmen die Farbe ihres übergeordneten Projekts, wenn sie keine eigene haben.
Whats-New: A right click on a task opens its menu, with Move to and more; Delete completed tasks clears a list, with Undo.
Whats-New-De: Ein Rechtsklick auf eine Aufgabe öffnet ihr Menü, mit Verschieben nach und mehr; Erledigte Aufgaben löschen räumt eine Liste auf, mit Rückgängig.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:22:57 +01:00
560de2ed93
fix(ui): on a phone, a snack bar stands above the screen's floating button
Below medium windows the frame's scaffold showed the screens' snack bars,
under the floating button of the screen's own scaffold, so a tap on a
snack bar's Undo hit the button instead (found 9 Nov 2026, Todo's Undo
after deleting the completed tasks). The screens get a messenger of their
own there, and their scaffolds lift the snack bars above their buttons, as
Material places them. From medium windows on the frame's scaffold still
shows them, once: a messenger shows a snack bar on each of its scaffolds
not inside another, as two panes side by side are. As the window grows
past a phone's, the phone's snack bars go, since a scaffold whose
messenger changes keeps the one it showed for good.

Whats-New: On a phone, a message's Undo is no longer hidden under the + button.
Whats-New-De: Auf dem Handy liegt das Rückgängig einer Meldung nicht mehr unter der +-Schaltfläche.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:22:56 +01:00
3bccf127d0
feat(learn): PDFs up to 100 MB
The books of the old app's library run past the 50 MB a PDF could have
(decided 2026-10-03); the server and the app take up to 100 MB now. Other
files keep their own, smaller limit.

Whats-New: PDFs can now be up to 100 MB.
Whats-New-De: PDFs dürfen jetzt bis zu 100 MB groß sein.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:22:55 +01:00
551140621b
feat(learn,train,todo,meditate): the icons on a white tile again, coloured by the system's theme on Android and iOS (ADR 0031, D9, D10)
D10 returns every icon to D1's white tile, D8's dark one undone, the
gradients as D5 gives them (cobalt holds about 5.7 : 1 against white).

D9 adds the variants the systems colour by their theme, all from the same
glyph at the same place, drawn by tool/app_icons.py and written into the
projects by flutter_launcher_icons: Android's monochrome layer, which
themed icons tint (Android 13 and later), and iOS's dark and tinted icons
(iOS 18 and later), the dark one keeping D8's lift of 30 % towards white
on iOS's own dark background. macOS needs an Icon Composer file made on a
Mac and keeps the plain icon until then.

Whats-New: The app's icon is white again, and takes the system's colours where Android or iOS themes its icons.
Whats-New-De: Das App-Symbol ist wieder weiß und nimmt die Farben des Systems an, wo Android oder iOS seine Symbole einfärbt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-10 14:22:49 +01:00
0cd329566f
fix(sync): a new device's first sync of a large library goes through
Some checks failed
CI / check (push) Has been cancelled
A device that had never synced a space collected everything it lacked from
the server, which read each change's domain row with a statement of its
own and sent nothing before all were read. On halkyon.li, a new device's
first sync of a library of 56,621 rows (8,190 cards, 20,961 reviews) kept
the server's one thread busy about 100 s; the client, hearing nothing for
40 s, not even the pongs to its pings, closed the stream, and every attempt
five minutes later started over, so that device never synced.

The fork of serverpod_offline_sync moves to the signed 741e334 on its
branch halkyon/batched-row-reads (stacked on halkyon/batch-inserts), which
reads those rows 500 to a statement per table and space in the same
snapshot: 120,194 changes took 51.5 to 53.6 s to collect on embedded
PostgreSQL before, 16.9 to 17.1 s after, the same bytes on the wire.

Whats-New: A new device now gets through its first sync of a large library instead of stopping and starting over.
Whats-New-De: Ein neues Gerät schafft jetzt seinen ersten Abgleich auch einer großen Bibliothek, statt abzubrechen und von vorn anzufangen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-09 15:50:29 +01:00
c8d67b1eb7
build(ci,deploy)!: the runner at home in a nightly window, deploys by hand (ADR 0039, D6, D13, D16)
Some checks failed
CI / check (push) Has been cancelled
The CI runner moves to halkyon.li's host at home and runs from 02:00 to
05:00 alone: a timer starts it, its unit's RuntimeMaxSec ends it, and
night.sh rests the instance's heavy sidecars around it as deploy, while
Caddy, PostgreSQL and the apps' servers keep serving. Jobs pushed during the
day wait in Forgejo's queue.

Its sandbox fits the new host: its folder on the HDD, the rest of /data
hidden, containerd's store out of reach, no private IPv4 network and no
IPv6 at all (the home network's IPv6 addresses are public ones under a
changing prefix, on the internet's own interface), 5 GB of memory with swap
beyond. The Sandbox step checks the tunnel, the home router and IPv6.

BREAKING CHANGE: CI deploys nothing any more: the deploy job goes with
learn.halkyon.eu's server, and halkyon.li deploys by hand with redeploy.sh
once a commit's night's run passed. Jobs run on the runner labelled
balthasar; sahaquiel's runner picks up none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:59:40 +01:00
fd9c2dfe76
feat(meditate,deploy): Meditate beside the instance, and every server's public address from .env
Meditate's deploy step (ADR 0038, step 7; ADR 0039, D14), as Todo's: the
server's Dockerfile with the library's check compiled beside it, its
production config on its own database and role, compose.meditate.yaml as a
member of the register, its site sending /library/* to the web server and
the rest to the API server, the operator's library read-only in a compose
file of its own, and redeploy.sh, backup.sh and the image replay taking it
in.

Every server's public address now comes from .env (Serverpod's
SERVERPOD_API_SERVER_PUBLIC_HOST and _WEB_SERVER_, which win over the
config file): Train, Todo and Meditate from their domains, Learn from
LEARN_PUBLIC_HOST, unset learn.halkyon.eu as before. Without it a server on
halkyon.li would name halkyon.eu in its SimpleLogin callback, profile pages
and privacy page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:59:39 +01:00
1aa633fdc7
chore(release): train-v0.5.0
All checks were successful
CI / check (push) Successful in 52m2s
CI / deploy (push) Successful in 1m5s
Build 5.

Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 14:38:00 +01:00
7c68f8309e
chore(release): todo-v0.4.0
Some checks failed
CI / check (push) Has been cancelled
CI / deploy (push) Has been cancelled
Build 4.

Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 14:27:25 +01:00
f76268ff37
chore(release): learn-v0.26.0
Some checks failed
CI / check (push) Has been cancelled
CI / deploy (push) Has been cancelled
Build 29.

Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 14:02:57 +01:00
1a3034fd31
feat(learn,todo,train,meditate): the apps' icons on a dark tile (ADR 0031, D8)
All checks were successful
CI / check (push) Successful in 50m47s
CI / deploy (push) Successful in 4m49s
Each icon's Lucide glyph now sits on the apps' dark background (#131520)
instead of white, its cobalt-to-colour gradient lifted 30 % towards white
so the glyph keeps above 4.5 : 1 against the tile. tool/app_icons.py
holds the tile and the lift; the pubspecs give Android's adaptive icon
and iOS's the same background. Every platform's icon is regenerated.

Whats-New: The app's icon now sits on a dark background.
Whats-New-De: Das App-Symbol hat jetzt einen dunklen Hintergrund.
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:48:43 +01:00
c36bd83c6a
feat(learn,todo,meditate): dictate through the phone's voice input app (ADR 0038, D18)
On Android the microphone now opens the app handling
RECOGNIZE_SPEECH (FUTO Voice Input, Google's), the default the person
chose in Android, and puts the text it returns where the cursor stood,
as Dictation does; cancelled, nothing. Only where none is installed, or
it can't be opened, does speech_to_text listen in line as before.

halkyon_app: VoiceInputApp (one listen, an answer once closed) on the
channel app.halkyon/voice_input, which DeviceSpeechInput asks first at
every listen, answering SpeechStart.finished so that Dictation doesn't
listen again; speech_to_text isn't touched on that route, so our app
asks for no microphone. SpeechInput.listen and Dictation.start take the
prompt the voice input app shows (the field's hint).

Each app: VoiceInput.kt (queryIntentActivities, startActivityForResult,
the first of EXTRA_RESULTS) attached in MainActivity, which hands it
activity results, and RECOGNIZE_SPEECH among the manifest's queries.
The privacy pages of Learn and Meditate say so.

Our own Whisper and Meditate's dream recordings are dropped (D18).

Whats-New: The microphone now dictates through your phone's voice input app, such as FUTO Voice Input.
Whats-New-De: Das Mikrofon diktiert jetzt über die Spracheingabe-App deines Telefons, etwa FUTO Voice Input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 11:48:42 +01:00
4940230ef1
perf(learn): a rated card gives way to the next one at once
Rating a card while studying waited for its review to be written, then
for the card's schedule to be replayed, before the next card showed.
StudySessionBloc now starts keeping the review and shows what comes
next at once: the card's new place in the round is worked out from its
reviews with the new one counted as if kept already
(ReviewRepository.schedulesAsStudied's pending, each review counted
once should it be kept by then).

Should keeping it fail, the step leaves the session's Undo, the rating
leaves its accuracy, and the card comes back where it stood, saying so
(withRatingFailed), as a failed rating did before. An Undo of a rating
still being kept waits for it and takes it back once kept; one that
couldn't be kept has nothing to take back, and the Undo has brought
the card back already.

Whats-New: While studying, the next flashcard shows the moment you rate one.
Whats-New-De: Beim Lernen kommt die nächste Karteikarte, sobald du eine bewertet hast.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 11:48:42 +01:00