As Todoist's (the developer's review of 3 Nov 2026, ADR 0033, D22): each
layout a phone's outline with its picture, its name below on one line (no
"Calenda/r" split across two), a radio button marking the one in use; the
switches, the sorting and the filters each a group in a rounded box under a
plain heading.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
From the developer's review of 3 Nov 2026 (ADR 0033, D19 to D21):
- Today's tab shows today's day in its calendar, Browse the menu's lines,
Display's button Lucide's square-menu.
- A task's row stands in a rounded box, the rows of a list a group rounded
at its ends; the box is the row's own surface, so a chosen row's colour no
longer shows under Upcoming's pinned week strip as the list scrolls. Its
project and section end the line of its date at the row's right edge.
- Today has at most two groups: what is overdue, folding under its heading,
and today's, headed "Tuesday, 3 Nov" ("Dienstag, 3. Nov."), without a
"· Today" after it; Upcoming's days take the same form.
- Upcoming's way back to today shows whenever another day is picked.
- Today, Upcoming, the Inbox and every project pulled down sync now, an
empty list too, as Learn's.
- A project's screen washes in its own colour, as a studyset's in Learn.
- "Add section" in place of "New section".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The developer's review of 3 Nov 2026: a studyset is made private, or
non-private, in Edit alone. The lock's sheet still says what its privacy
means, offers publishing one that isn't private, and finishes one only
partly private.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
What explains a setting or a section, rather than what is the person's
own, moves behind halkyon_ui's HelpButton, beside its title through the
new ExplainedTitle (the developer's review of 3 Nov 2026): in Learn's
settings (citations, the Pomodoro timer, grammar, the web search,
Jellyfin, the device's dictionaries, counting one's learning), the study
settings, export's progress, the Jellyfin tags and the study button's
back first; in halkyon_app, setting up a password, adding SimpleLogin,
own keys not allowed, the profile page's link back and the operator's
grant sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Beside the field's microphone, a button opens voice mode over the whole
screen: a glow rising from below, a big microphone, and +, who answers,
the speaker and End in one row. It listens with the device's recogniser,
asks what was heard in one kept conversation, reads the answer aloud
with the device's speech (flutter_tts, its Markdown left out) and
listens again. On Linux there is none, as there is no microphone; where
nothing reads aloud, answers are shown as text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Following the developer's review of 3 Nov 2026 (ADR 0021, D67):
- + below the assistant's field, and below a conversation's follow-up,
opens "Add to your question" as Claude's "Add to session": three big
tiles side by side, Camera (where the device takes photos) and Photos
for an assistant reading images, and Files; below them, in the field, a
row leading to what goes along, showing how far the assistant looks and
how many parts are chosen.
- Camera and Photos through image_picker (flutter.dev's own): the camera's
own app and the system's photo picker, no permission asked on Android, a
file dialog on the desktop, which has no Camera; photos made at most
2048 pixels a side, read on the device like any file attached.
- What goes along is chosen in rounded rows, as Claude's Permission, in
place of radio buttons and chips: how far it looks with a tick, private
studysets with a switch, the parts each with a tick, a card's image with
its thumbnail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The developer's review of 3 Nov 2026: Make private and Make non-private
leave a studyset's options (its card's menu, its screen's); a top-level
studyset's Edit has a Visibility section instead, its switch making it
private, or non-private, at once once the person agreed in the dialog as
before (ADR 0019), not on Save, and what that means behind the question
mark. A topic's Edit offers none, its privacy being its tree's. The lock on
a studyset's card still explains its privacy and keeps its own button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Following the developer's review of 3 Nov 2026 (ADR 0021, D67):
- Unfolded in the Library, the field spans the screen between its margins,
as on Home and a studyset: it fell short on the right, by the padding
around the two folded buttons, which now stands only around those.
- Who answers stands right before the buttons at the right, which no longer
shift as its name changes (Claude Haiku, Claude Opus): it fills the space
left, aligned to its end, rather than a share of it.
- Every button of the field and of a conversation's follow-up is boxed, a
tile a shade above the field with rounded corners, as Claude's apps have
them; who answers too.
- On a studyset with a licence, the unfolded field takes its usual place
over the licence's line rather than squeezing in above it; folded, its
row still keeps clear of the line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
- A workout with a track: its map to the foot of the screen under a sheet
with its name; once the sheet settles, the map fits the track anew, eased,
into what is left free; pulled up, the round buttons over the map fold
into an app bar whose chevron lowers the sheet again. From expanded
windows on, the map beside the rest. The page stays in Home but opens
over the shell, without the floating bar.
- What the map's source sees moves behind a question mark.
- The map's credit on the app's theme: flutter_map's, built on
flutter/material, never saw it (material_ui's Theme is a class of its
own) and drew the theme's text on white; map_tiles read that Theme too,
and so never chose the instance's dark map (D50).
- Places named in the app's language (name:<language>) where OpenFreeMap's
styles ask for the English name.
- The route planner's hint behind a question mark; a new route's map opens
at zoom 14 where the phone was last known to be, else where the newest
workout with a track started; its details beside the map from expanded
windows on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
HelpButton takes an action, a label and what it does, shown beside OK where
there is more to read elsewhere, as what a map's source keeps (Train's ADR
0030, D63).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
HelpButton, beside a section's or a setting's title: the explanation opens
in a small dialog, under its title where given, rather than standing in the
everyday view (the developer's review of 3 Nov 2026). FieldInfoButton's
tooltip stays for a field's short hint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
A phone without a camera whose authenticator doesn't open otpauth: had no
way but typing the key. It is copied again, the key alone (ADR 0017
amended, the developer's decision of 3 Nov 2026): on Android through each
app's SensitiveClipboard.kt, marked EXTRA_IS_SENSITIVE, so Gboard and the
system show no preview and keep it out of their histories; elsewhere through
Flutter's clipboard. Emptied 60 seconds later if the clipboard still holds
it. The recovery phrase and codes stay never copied.
The code's field counted "/7", taking an optional space for a seventh
character: it now takes six digits alone, a pasted space dropped, and counts
nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
leancode_lint's avoid_context_read_in_build, which only the root's analysis
runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Explore shows the instance's library below Meditate's practices, where it
has one: its courses and items by category (Meditate's eleven, named in
English and German, and the operator's own) and by the same search; an
instance without one shows nothing more. An item's page: its recordings
to choose, Play, a favourite (kept in the sealed settings under
library/<id>), keeping it on the device and removing it, and for a sound,
a silent sitting with it looped; its credit and licence. The player
plays, pauses and seeks; played to its end it is kept as a sitting, and
as its course's lesson done (lesson_done, sealed and synced). Settings
lists the library's credits beside About; Today shows its favourites.
The last catalogue is kept in the person's folder for offline; files are
fetched with the catalogue's access, asked for again shortly before it
ends. ADR 0038, D16.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
An access to the library's files now holds from the hour begun, so
everyone given one within the same hour gets the same and a fetch can't
be told apart by account. The privacy page says what the library means
for people: signed in alone, fetched naming no account, what they
listened to sealed. ADR 0038, D16.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
An operator's folder (MEDITATE_LIBRARY_DIR) of recordings, pictures and a
YAML manifest, library.yaml (commented sample: library.example.yaml), read
as the server starts and every ten minutes, held in memory. Items without
a licence or credit, files outside the folder, unknown categories and
courses pointing at items not served are refused and logged; a manifest
that doesn't read keeps the library served before. bin/check_library.dart
checks a folder before deploying.
The catalogue goes to signed-in people alone (LibraryEndpoint), with a
12-hour access the server signs; the web server's /library/<served name>
serves the files to that access only, by ranges (relic's StaticHandler).
No request path reaches the file system.
lesson_done: a lesson done, sealed and synced, a row each; the migration
creates the table with its spaceId index and the cascading foreign key to
the sync engine's spaces, as dream's did. ADR 0038, D16.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The editor dictates where the cursor stands with halkyon_app's Dictation,
showing it listens and the words heard, listening again after each pause
until stopped or three silent listens, in the app's language. No
microphone where the device has no recogniser (Linux). On Android it asks
for recognition on the device; the privacy page says what each platform
does. Android's, iOS's and macOS's microphone declarations as Learn's.
ADR 0038, D15.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Learn's `SpeechInput` and `DeviceSpeechInput` (ADR 0021, D67) are
halkyon_app's now, and its `FakeSpeechInput` is in `testing.dart`, so
Meditate's dream journal (ADR 0038, D15) listens through the same code.
Learn imports them from there and behaves as before: its composer and
its tests are unchanged but for the imports, and `DeviceSpeechInput()`
asks for what it asked before.
`DeviceSpeechInput` takes three options, off by default:
- `preferOnDevice`: on Android, recognition on the device where it can
(Android 12 on), else offline; should that fail before a word is
heard (not a pause), it listens as Android otherwise does from then
on.
- `pauseFor`: how long a pause may last before the recogniser stops.
- `listenFor`: how long one listen may last at most.
`Dictation` writes what is heard where the cursor stands
(`DictationInsertion`: in place of a selection, set apart by a space),
shows the words so far, and listens again after each pause while it is
on, stopping by itself after three listens running heard nothing.
speech_to_text moves from Learn's pubspec to halkyon_app's, the lock
unchanged. Todo, Train and Meditate link the plugin now; their
generated plugin registrants (macOS, Windows) say so. An app declares
the microphone itself, and Todo and Train declare none, so nothing
there can listen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
ADR 0038, D14. A dream (`Dream`) holds the night it belongs to (the
morning's date, today's by default), an optional title, its text, the
marks lucid, nightmare and recurring, a clarity of 1 to 5, the mood on
waking and free labels, and when it was written; a row each in `dream`,
sealed for its row with the person's key as the sittings
(`DreamRepository` on `SealedRows`, `SyncDreamGateway`), followed by
`JournalCubit` for every screen.
- The Journal, between Explore and Profile: dreams by month, the
newest first, searched over title, text and labels as Explore
searches, filtered by marks, `+` above the bar; empty, it says that
entries are sealed on the device before they sync.
- A dream's screen over the tabs: a new one with its text first and
focused, the rest folded below; a kept one to read, with Edit and
Delete (asked first); leaving one written but not saved asks first.
- Today, from 4 to 12 o'clock while last night has no dream, a gentle
card leading to a new one; Settings' Journal card switches it off
(`dreamPrompt` in the sealed settings, schema 1 kept).
- Profile's Practice: this month's dreams and the lucid ones, once the
journal holds one.
- English and German texts; What's new 0.1.0 names the journal; the
account's deletion says it takes the dreams too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The developer's request of 3 Nov 2026: a dream journal, sealed and
synced as the sittings, with Today's morning card and its switch in
Settings. What an entry holds beyond the request, its own `dream`
table, the fourth tab between Explore and Profile and why, the
Journal's list, search and filters, the editor and Profile's two
figures are the agent's recommendations, to be confirmed. The `dreams`
migration's SQL is explained under Migrations; it is step 5, the
release moving to step 6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The dream journal's table (ADR 0038, D14): `dream`, in the sync engine's
database, a row a dream, its content sealed on the device (key ID,
payload, nonce), so the instance sees only that a dream exists, its size
and when it changed. The same rules as the sittings: the sync engine's
connector refuses a call without a session, each row lives in its
person's space, and deleting the person deletes their dreams with it
(the deletion test now writes and counts a dream too, and leaves
another person's).
The migration `20261103130213716-dreams`:
- `CREATE OR REPLACE FUNCTION gen_random_uuid_v7()`: the generator
repeats the UUID v7 function every migration; replacing it with the
same body changes nothing.
- `CREATE TABLE "dream"`: `id` a UUID v7 by default, `spaceId` the
person's sync space, `keyId` the key it is sealed with, `payload` the
sealed bytes (not null), `nonce` the sealing's nonce.
- `CREATE INDEX "dream_space_idx"` on `spaceId`, so deleting a space
finds its dreams without scanning the table.
- The foreign key from `spaceId` to `offline_sync_spaces("id")` with
`ON DELETE CASCADE`: deleting the space deletes its dreams.
- The version rows of every module in `serverpod_migrations`.
A new, empty table: nothing existing changes and no data moves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
halkyon_meditate_flutter on halkyon_app and halkyon_ui (ADR 0038): the
first start, sign-in and account as every Halkyon app's, a bar below
medium windows and a rail with the icon from medium on, fern washes,
Settings in cards (Sync, the operator's, Sitting, Practice, the
account, What's new, About, the version), English and German.
- Today: a greeting by the hour, Sit now at 5, 10, 20 minutes and the
timer's own length, the practice of the day, the week with the days
in a row, favourites, recently sat (D9).
- Explore: six practices of Halkyon's own, written for the app, found
by a search without case or accents, by goal and by kind; each page
with how it goes, its lengths, its heart and Start (D4, D10).
- The sitting, over the tabs: its time from the clock, a ring counting
down, the cue of the moment, Pause and End, the bells at the start,
between and at the end, the screen kept on; ended early, kept from a
minute on where the person says so (D5, D6).
- Profile: every Halkyon app's, then the person's practice: minutes,
sittings, days, the week and every sitting kept, to delete (D8).
- Settings and sittings sealed with the person's key and synced (D7).
The bells are synthesised by apps/meditate/tool/bells.py, the icon is
Lucide's sprout from cobalt through pacific into fern
(tool/app_icons.py). Releases and the deploy are a later step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
halkyon_meditate_server and its generated client, copied from Todo's
and stripped to the module, the register and the sync engine (ADR 0038,
D1, D7): content_key, meditate_settings and sitting, synced and sealed,
each indexing spaceId and deleted with the person's space; two
migrations, the initial one and sittings (purely additive, the ADR
explains the SQL); ports 8280 to 8282 and PostgreSQL 8290 (D13).
Tests: the module's basics naming `meditate`, the sync engine refusing
calls without a session, the register's modes, and a person's deletion
taking their rows alone.
halkyon_base's forwarding page knows Meditate's ways back
(meditate-loopback on 43826, meditate-scheme app.halkyon.meditate),
which halkyon_app's repository names among the apps' ports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Proposed by the agent on 3 Nov 2026, the developer having asked to go on
without their interjection: three tabs (Today, Explore, Profile) on
Todo's and Train's frame, fern washes, Halkyon's own practices as text
cues with a silent timer, bells synthesised for the app, a sitting over
the tabs, sealed and synced settings and sittings, the figures worked
out on the device, development ports of its own. Every decision is the
agent's recommendation, to be confirmed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
planReminders plans each reminder at a time of its own of an open task,
whatever its date, under an ID of its own; the notification says
"Reminder" for a task without a time. The task's sheet always has its
reminders' row: those before its time where it has one, those at a time
of their own each with its remove button and "Remind at a time" (a day,
then a time), the permission asked as a first one is chosen. The date
sheet sets a duration once there is a time (none, 15 minutes to 4
hours, as Todoist's), a time cleared taking it with it. Recorded as
ADR 0033, D18 (the developer's, 3 Nov 2026).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
`on` (German `am`) before a day is taken with it, as `at` before a time;
`for 2h`, `for 1h30m`, `for 90 min`, `für 1,5 Std.` set the date's
duration where there is a time, and stay text without one; every `!30m`
counts, each once, sorted, a task without a date due at the latest; and
`!1pm`, `!13:00`, `!tomorrow 9am`, `!Monday 8:30` are reminders at a time
of their own, a time without a day on the task's day, else the next to
come. Todoist's newsletter's four examples parse whole (ADR 0033, D18).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
ReminderTime is a day and minutes after midnight, the wall clock without
a zone as TaskDate's; a task's reminderTimes are written as
[{"day", "time"}] and left out where there are none, so older payloads
read with none. The payload's schema stays 1 (as D17's view): what a
newer version wrote at its top that this one doesn't read is kept in
TaskContent.unknown and written back beneath the fields it knows, so a
task changed here keeps it (ADR 0033, D18).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
snapcraft builds in LXD with the project mounted at /root/project and refuses
any --output naming a folder of this machine: the root's own build/release/
for Learn's snap (found releasing Learn 0.23.0) as the clone itself for Todo's
and Train's (Todo 0.1.0, both 3 Nov 2026). Every snap now packs into its
project and is moved into the release's folder from there, checked with
snapcraft 9.2.1 at the root. A failure after none of several apps was done
now says "none" rather than nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The release tool and tool/with_signing_keys.sh take several apps, each with its version
after a colon or none, and release them one after another, each as alone, stopping at
the first that fails; the key script fetches every named app's key.
The pre-push hook skips tool/check.dart when a push brings release commits alone: the
release tool's chore(release) subject, an app's pubspec changed in its version line and
its texts of what's new, nothing else (tool/release_push.dart). Signatures are checked as
before, and CI still runs the checks. A release no longer costs a run of the checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Built as Todo's in a clone of its own, from apps/train/snap/, for train.halkyon.eu; its
listing says the desktop shows, plans and keeps the statistics, and records nothing.
The snap tests now hold every snap built in a clone of its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
snapcraft builds in LXD with the project mounted at /root/project and passes --output
on unchanged: naming the clone itself, it took the clone for a file in the folder above,
outside the project, and refused it again. Without --output it packs into the clone
(checked with snapcraft 9.2.1 in Todo's clone), from where the release tool moves it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
snapcraft packs nowhere outside the project it builds, so Todo 0.1.0's release stopped
after its snap was built ("Cannot create packages outside of the project tree").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The link carried the deploy that switched the server's COMPOSE_FILE over (2 Nov 2026).
BREAKING CHANGE: an instance whose deploy/.env still names compose.yaml, or sets no
COMPOSE_FILE, must name compose.learn.yaml first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Each icon now ends in the colour its app's screens wash down from: Learn cobalt into
purple, Todo through violet into strawberry (Learn's former gradient), Train unchanged
into carrot, Meditate's to come through pacific into fern (Todo's former). Train's glyph
is Lucide's dumbbell, Meditate's will be the sprout.
Learn shows its graduation cap atop the rail, on the launch screen and on halkyon_app's
screens, as Todo and Train show theirs; the kingfisher leaves the app and stays the
F-Droid repository's icon.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Still the password of the instance's PostgreSQL superuser, halkyon_learn, which Learn's
server signs in as; only its name in deploy/.env changes. PostgreSQL's image still takes
it as POSTGRES_PASSWORD, its own variable.
BREAKING CHANGE: an instance's deploy/.env names it LEARN_POSTGRES_PASSWORD.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
No file has Compose's default name any more, so .env's COMPOSE_FILE is always set,
naming compose.learn.yaml first (.env.example sets it). compose.yaml stays as a link to
compose.learn.yaml for the one deploy that switches learn.halkyon.eu's .env over: the
deploy's merge runs with the COMPOSE_FILE from before. A later commit removes it.
BREAKING CHANGE: an instance's deploy/.env must set COMPOSE_FILE, starting with
compose.learn.yaml, before the link goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The folder kept its name from before the monorepo (ADR 0015), though
Train and Todo deploy from it too. redeploy.sh, the backup's units and
the operator's steps name ~deploy/halkyon; the Compose project keeps the
name halkyon-learn, as Docker names its volumes after it.
The server moves after this is deployed: the running redeploy.sh is read
whole before it runs, so the deploy of this commit still works in the
old folder; then the folder is moved, the pinned commands in deploy's
authorized_keys and the backup's units changed, and the containers
recreated on the new paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
SESSION_KEY_HASH_PEPPER went to Learn's server alone, beside
TRAIN_SESSION_KEY_HASH_PEPPER and TODO_SESSION_KEY_HASH_PEPPER. The
pepper's value stays: changed, everyone would be signed out of Learn.
POSTGRES_PASSWORD keeps its name, as it is the instance's PostgreSQL
superuser's, not Learn's alone.
BREAKING CHANGE: an instance's deploy/.env must set
LEARN_SESSION_KEY_HASH_PEPPER (SESSION_KEY_HASH_PEPPER's value) before
this is deployed; without it Compose stops at once, and the deploy fails
before anything is switched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
DOMAIN named the domain of Learn's site alone (Caddy's), beside the
prefixed domains of the other apps' compose files.
BREAKING CHANGE: an instance's deploy/.env must set LEARN_DOMAIN (DOMAIN's
value) before this is deployed; without it Compose stops at once, and the
deploy fails before anything is switched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The certificate of the key Todo's releases are signed with (alias
halkyon-todo), made in memory and kept in Proton Pass alone; the release
tool releases no APK signed otherwise.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
deploy/compose.todo.yaml adds Todo's server at todo.halkyon.eu, a member
of Learn's register, its database halkyon_todo in the instance's
PostgreSQL, its image built as Train's. redeploy.sh and backup.sh go over
every app server .env switches on (train, todo). Each compose file names
its server to the register in a variable of its own
(INSTANCE_MEMBER_TRAIN, INSTANCE_MEMBER_TODO), which halkyon_base adds to
INSTANCE_MEMBERS, each address once, as a second compose file's default
would have replaced Train's.
apps/todo/release.dart configures the release tool for Todo's APK and
snap: a key of its own (pinned once made; until then nothing is
released), the F-Droid listing (Time, NonFreeNet), and the snap from
apps/todo/snap/, which the tool builds in a clone of the repository at the
tag with that folder in place of snap/, as snapcraft builds one recipe a
folder and the root's is Learn's. What's new for 0.1.0 in English and
German, now shown. Android's backup and transfer are off, as Train's
(D53): the reminders scheduled keep their tasks' titles unsealed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
The band's screen shows whether the Loop detects training by itself and
switches it, with the shortest session it keeps (10, 15, 20 or 30
minutes), so a session nobody recorded still comes in afterwards as a
workout (ADR 0030, D39). Read on each connection and after the band's
setup through the SDK's getUserDeviceSettings, set with
setAutomaticTrainingDetectionSettings; its sensitivity stays as the band
has it, as the SDK documents no range for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Settings' Moving more, on Android: a reminder to get up after an idle
time, and a suggested walk or run, each off until turned on (then every
30 minutes, weekdays 9:00 to 18:00, not 12:00 to 13:00, each value the
person's), turning on asking for the activity and notification
permissions.
A WorkManager job every 15 minutes reads the phone's step counter and
keeps two hours of readings. It reminds where fewer than 50 steps came
over the idle time, all of it within the hours and outside the break,
on the person's days, never while Train records or the phone is in a
car (Android's car mode, or a car's Bluetooth audio or hands-free). It
suggests a walk or a run where the last span averaged a walking or
running cadence, once an hour; tapped, the Record sheet for it opens.
The decisions are pure Kotlin (Movement.kt), tested on the JVM; the
notifications' words are the app's, written into the job's
configuration as the person's app opens.
Polar's SDK sets neither the band's inactivity alert nor tells of one
without an all-day connection, hence the phone's steps (ADR 0037).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>