Commit graph halkyon/apps/learn/AGENTS.md
Author SHA1 Message Date
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
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
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
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
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
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
891e9dfd75
perf(learn): a conversation deleted goes at once, with an Undo
Deleting a kept conversation asked first and then waited while its
turns were blanked and deleted. It now goes as a card or a note does:
ConversationDeletions, a DelayedDeletions opened with the person's
data, takes it off the history at once (the list leaves out what is
pending), offers Undo for a moment on the sheet (hasSnackBars), and
blanks and deletes it once that is over; a deletion that fails shows
the conversation again, as every DelayedDeletions does. The question
before deleting goes, as Undo replaces it.

Whats-New: A conversation with an assistant is deleted at once, and Undo brings it back.
Whats-New-De: Eine Unterhaltung mit einem Assistenten wird sofort gelöscht, und „Rückgängig“ holt sie zurück.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 11:48:40 +01:00
56f40d4d65
refactor(ui,learn,todo,train,meditate): the navigation bar's one look, the debug section gone
The developer decided on 8 Nov 2026, after trying the variants on the
phone: the wave in the colour of the screen in view, without Material's
ripple, the indicator kept and there at once, a 1.2 s wave, top corners
of 20 dp, the bar's own neutral surface. Learn's bar stays cobalt, the
colour its tabs wash in, rather than its icon's purple.

So the bar has one look: FlushNavigationBar.waveDuration and
cornerRadius are constants, and the debug section goes from the four
Settings screens with what it switched (NavigationBarDebugSection,
debugNavigationBarStyle, effectiveNavigationBarStyle, the style record
and the variants A, C and F, the speeds, the corners and the tinted
surface). Their tests go too; those stating the look kept stay. Learn's
sign-out helper no longer has to scroll past the section.

ADR 0030's amendment of D60, the halkyon_ui README and Learn's
AGENTS.md record the decision and the alternatives set aside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 11:48:39 +01:00
60d53ed262
feat(ui,learn): the navigation bar in the colour of the screen in view
The developer's review of 8 Nov 2026: the bar follows the screen in view
rather than one colour per app, so a Todo project of its own colour gives
the bar that colour while it shows. The wave, the chosen destination's
icon and label and its indicator take ScreenTint.color, the colour the
screen in view claims through its TopGradient; the frame's accent, the
app's colour, stays only for the moment before any screen claims one,
since ScreenTint itself starts at cobalt, which would be wrong for Todo,
Train and Meditate. Learn's tabs claim cobalt, their wash, so its bar is
cobalt there now, not purple.

A colour changing on the same tab (a project opening) passes into the
next over 300 ms, at once with animations off. A wave paints in that
colour as it goes: it starts in the colour of the screen being left, as
the arriving one claims its own only once it shows, a frame later, and
passes into it with the rest of the bar.

A person's colours may be light, so the chosen destination's colour is
made legible (readableTone, halkyon_ui): a palette colour starts from its
own text tone as Todo's dates take it (its outline in light mode, its
fill in dark), any other colour from itself, then darkens towards black
in light mode and lightens towards white in dark, only until it reads at
4.5:1 on the bar and on its indicator. Every palette colour is tested in
both themes, on the neutral and the tinted surface. This also lifts
Learn's label, which read at about 3:1.

The debug section gains Bar surface, neutral or tinted: tinted mixes 6 %
of the screen's colour into the bar's own surface in light mode and 8 %
in dark, changing with it. Neutral by default, and in the shipping style.

Whats-New: The navigation bar takes the colour of the screen you're on, such as a project's own.
Whats-New-De: Die Navigationsleiste nimmt die Farbe des Bildschirms an, auf dem du bist, etwa die eines Projekts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 11:48:38 +01:00
45fd6595e1
feat(ui,learn,todo,train,meditate): a wave in the app's colour over the navigation bar
The developer's choice of 7 Nov 2026 (the mockup's variant E): a tap on
another destination sends a radial glow in the app's colour over the bar
from that destination's centre, solid to 35 % of its radius and gone at
70 %, growing to cover the bar (cubic(.2, .7, .2, 1)) while it fades from
half its strength in dark mode and a third in light, over 1.2 s, behind
the destinations and clipped to the bar. None for the destination already
chosen, none with animations off, and none for a tab changed without a
tap (a link, back): the wave answers the finger. The destination chosen
takes the app's colour, its indicator tinted 22 % with it, without
Material's ripple or the indicator growing; the bar's top corners are
rounded, 20 dp, the screen showing through them.

Each app gives the frame its colour (NavigationFrame's accent, from
PaletteColor): Learn's purple, its icon's last colour, Todo's strawberry,
Train's carrot and Meditate's fern, their screens' wash.

For trying it on a phone, a debug build's Settings has a section
Navigation bar (debug), halkyon_ui's NavigationBarDebugSection, switching
live, in memory alone: the variant (A Material only, C the wave with
Material's fill, E without it, F without the indicator too), the wave's
time (0.7 to 2 s) and the corners (0 to 28 dp). Release builds never read
it and show shippingNavigationBarStyle, the debug section's start: to
remove it, delete the section from the four Settings screens and
debugNavigationBarStyle, and let effectiveNavigationBarStyle return the
shipping style.

Whats-New: Tapping a tab sends a soft wave in the app's colour across the navigation bar, now with rounded corners.
Whats-New-De: Ein Tipp auf einen Tab schickt eine sanfte Welle in der Farbe der App über die Navigationsleiste, jetzt mit abgerundeten Ecken.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 11:48:37 +01:00
05db5e2f45
feat(learn,todo,train,meditate,ui): the navigation bar flush with the foot, a fade above it
Below medium windows the frame's bar is Material 3 Expressive's again,
flush with the window's foot (decided 7 Nov 2026 by the developer): the
theme's NavigationBar across the whole width, 64 dp in its own colour,
running on beneath the system's bar as Material's does. Right above it a
fade 32 dp high, from the background of the screen in view (the
ScreenTint's, the theme's surface before a screen claims one) made
see-through to that background, so what scrolls on beneath the bar fades
out rather than being cut off; taps pass through it. The rail of wider
windows stays as it was.

halkyon_ui's FloatingNavigationBar becomes FlushNavigationBar, its side
margin, lift and rounded card gone. Its coverOf is now the whole room
content leaves at its end, the system's bar included (the body's bottom
padding: bar, fade and system's bar inside the frame, the system's bar
alone outside it), and floating buttons rise by buttonLiftOf, the same
without the system's bar, which a Scaffold keeps them clear of itself.
Lists, Todo's hour grid and the Plan's sheet keep using coverOf; the
Profile's list no longer adds the system's bar a second time.

Whats-New: The navigation bar sits at the bottom edge again, with a soft fade above it.
Whats-New-De: Die Navigationsleiste sitzt wieder am unteren Rand, mit einem sanften Übergang darüber.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 11:48:37 +01:00
648dfe4051
refactor(learn): the editor's general parts move to halkyon_editor (ADR 0033, D37)
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>
2026-11-07 21:49:48 +01:00
23d3bcb4ca
feat(app,learn): the device's appearance and language, halkyon_app's, as Learn's Settings show them
The developer asked for Todo, Train and Meditate to offer Learn's
Appearance and Language (ADR 0014's amendment of 4 Nov 2026). Their
model, store, cubit and card move into halkyon_app: Appearance and Look
(a BCP 47 tag, null for the device's language), FileLookStore keeping it
in the app's support folder as JSON written by rename (per device, not
synced, read before the first frame), LookCubit for the apps to provide
above InstanceGate, and LookSettingsCard / DeviceLookCard with the texts
moved from Learn's arbs (look*, the same words). Each app passes the
languages it speaks, named in their own language.

Learn keeps its stored choice where it was, among its DeviceSettings,
and feeds the shared card from them: no migration, so nobody's language
or appearance moves, and nothing changes on its screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-05 08:58:40 +01:00
a7638e5e54
feat(learn): Settings' dictionaries on this device, one row a language
The developer's review of 4 Nov 2026. "Japanese on this device" becomes
"Dictionaries on this device", with one row a language: its name at the
start, what the device holds or that the download failed beneath it, and
its action at the end (Download with its size, the progress, Remove),
built as a list of rows so Korean's and Chinese's line up beneath
later. Japanese's is the only one now. The help speaks of a language's
dictionaries in general, then of what Japanese's hold (MeCab's, JMdict).

Whats-New: Settings lists the dictionaries on this device by language, each with its own Download or Remove.
Whats-New-De: Die Einstellungen listen die Wörterbücher auf diesem Gerät nach Sprache, jede mit eigenem Herunterladen oder Entfernen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-05 08:58:39 +01:00
bda09a2959
feat(learn): Add material no longer offers flashcards from a text (ADR 0021, D56 amended)
The developer's review of 4 Nov 2026. The entry goes from a studyset's
Add material, and with it the sheet for pasting a text
(proposeCardsFromText, _TextSheet), its three texts and proposeCards'
fromStudyset, which only it set to false. A note's and a PDF page's
flashcards stay; their tests now drive the proposals sheet from a note.

Whats-New: Add material no longer offers Flashcards from a text; a note's or a PDF page's flashcards stay.
Whats-New-De: Material hinzufügen bietet keine Karteikarten aus einem Text mehr an; die aus einer Notiz oder einer PDF-Seite bleiben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-05 08:58:38 +01:00
f45ed98d52
feat(learn): one assistant field across the app, kept from tab to tab (ADR 0021, D69)
Each screen's button kept its own open state and its own draft, so the
question half written on the Library was gone on Home. ComposerCubit,
provided beside ConversationDockCubit, now holds whether the field is
open and the draft (text, files attached, parts chosen) for every
button. Switching tabs keeps it as it is (AppShell says so before
goBranch); going to another screen within a tab, or over the tabs, keeps
it open only while something is written or attached. Only the button of
the screen at the router's location unfolds, so no tab kept alive
behind holds a second field.

Whats-New: The question you are writing stays open as you switch tabs.
Whats-New-De: Die Frage, die du schreibst, bleibt offen, wenn du den Tab wechselst.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-05 08:58:38 +01:00
b4b27a1d0c
feat(learn,app): the field's row as one, its model by name in its provider's colour, the microphone dictating (ADR 0021, D69)
The developer's review of 4 Nov 2026. Below the question, × and + stand
at the start, who answers is centred and the conversations and the
microphone stand at the end, every button the same rounded square, the
microphone listening and Send too. The row above the text ("Only what I
choose · 2 parts") goes, as +'s sheet offers the same choice; a web
search gets + with that row alone. Who answers shows its model's name
alone ('Haiku 4.5', AssistantProvider.modelName: Anthropic's without
"Claude", others as they are), outlined in its provider's colour (D60).
The microphone is halkyon_app's Dictation: what is said goes where the
cursor stands, and it listens again after each pause until stopped. It
gives way to Send once anything could be sent, as Claude's field does.

Whats-New: The question's buttons are in one row: close and + on the left, who answers in the middle, Conversations and the microphone on the right.
Whats-New: What goes along is chosen under +, for the web search too.
Whats-New: Who answers shows the model's name, in its provider's colour.
Whats-New: The microphone keeps listening after a pause, writing where the cursor is, until you stop it.
Whats-New-De: Die Knöpfe der Frage stehen in einer Reihe: Schließen und + links, wer antwortet in der Mitte, Unterhaltungen und Mikrofon rechts.
Whats-New-De: Was mitgeht, wählst du unter +, auch für die Websuche.
Whats-New-De: Wer antwortet, zeigt den Namen des Modells, in der Farbe seines Anbieters.
Whats-New-De: Das Mikrofon hört nach einer Pause weiter zu und schreibt, wo der Cursor steht, bis du es stoppst.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-05 08:58:37 +01:00
603218ef96
refactor(app): the device's speech recognition moves from Learn into halkyon_app, with dictation into a text
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>
2026-11-04 09:31:58 +01:00
ac387b5250
feat(icons): every icon fades from cobalt into its app's backdrop; Learn shows its own inside the app; Train's dumbbell (ADR 0031, D5 to D7)
All checks were successful
CI / check (push) Successful in 41m0s
CI / deploy (push) Successful in 4m37s
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>
2026-11-02 23:12:20 +01:00
3ac1880ac8
build(deploy)!: the instance's compose file is compose.learn.yaml, beside compose.train.yaml and compose.todo.yaml
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>
2026-11-02 23:12:15 +01:00
5469ce791f
build(deploy): the server's checkout is ~deploy/halkyon, the repository's name
All checks were successful
CI / check (push) Successful in 40m10s
CI / deploy (push) Successful in 58s
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>
2026-11-02 18:56:39 +01:00
b7b0b937fe
feat(app): one Profile for every app, the person's own tab first, then each app's (ADR 0014)
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>
2026-11-02 11:11:11 +01:00
447e00c52a
feat(learn): the assistant's field laid out as Claude's, with files attached and questions spoken, and no Check (ADR 0021, D67)
The question's text stands above its buttons: + attaching files, who
answers as a chip (Effort now a submenu of it), the conversations kept,
and the microphone while nothing is written, which gives way to the
button sending. What goes along is one row above the text, opening a
sheet with how far the assistant looks and the parts to send. A
conversation's follow-up has the same + and microphone.

Files are read on the device: an image goes as itself to an assistant
that reads images, a PDF as its pages' text, a text file as its text,
each cut at 20,000 characters and labelled by its name after the
question.

Voice is the device's own speech recognition (speech_to_text: Android,
Apple, Windows; none on Linux), in the app's language, the microphone
asked for once tapped. The privacy page says that Android's recogniser
may send the sound to Google or the phone's maker.

The field's Check goes, with its LanguageTool sheet (amending ADR 0024);
the editors' underlining and Why? stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 08:46:37 +01:00
5641285d31
feat(learn): Explore by what a licence allows, a skeleton while the library loads, and an assistant's agreement per provider (ADR 0005, rule 12; ADR 0021, D66)
- Explore filters by what a studyset's licence lets a learner do: adapt
  it (no NoDerivatives) and use it commercially (no NonCommercial),
  which the instance reads from the release's licence
  (ExploreQuery.adaptable, commercial) and the app the same way
  (LicenseUse). An instance from before ignores both.
- While the studysets are read, the Library shows shimmering skeletons
  of their cards rather than a spinner: halkyon_ui's new Shimmer and
  SkeletonBox, still where the system asks for no animations.
- The scope's "Include private studysets" is offered only while the
  person has a private studyset.
- An agreement to what goes where is per provider (protocol and host):
  agreeing to one assistant agrees to every assistant of that provider,
  those granted later too, and the agreement sheet says so. No
  migration: it is written on every grant of the provider, so taking
  one back keeps it for the others, and read per provider from any of
  them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 05:19:18 +01:00
138c4dd46e
feat(learn): the assistant's button folded on Home too, the conversation at full height folding into it, and smaller fixes from the developer's list (ADR 0021, D64, D65)
- Home's assistant starts folded into its button at the lower left, as
  in the Library, where a field stood at its foot.
- The button has Material 3's floating action button shape (a rounded
  square) rather than a circle, its gradient ring kept.
- On a studyset below expanded windows it stands in one row with Add
  material and Study rather than above them.
- While studying, the ratings below the card make way while its field
  is open rather than standing under it.
- The conversation opens at the window's full height, its header above
  the list, so a drag on the header moves the sheet and a drag on the
  list scrolls it. Close ends the conversation; any other way out folds
  it into the button (ConversationDockCubit, above the router), its
  answer still coming, until any button opens it again or another
  conversation starts.
- An answer's Copy icon lines up with the answer's text.
- A sense's grammar notes run on after its glosses as one flowing text,
  so a short note such as "n" no longer takes a line of its own.
- A card's hints show their count in a grey badge beside the title, so
  it no longer reads as the hint's number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 05:19:18 +01:00
4898d0a04a
feat(learn): the assistant's conversation a dialog on the desktop and in expanded windows (Todo's ADR 0033, D11)
- halkyon_ui's sheetShowsAsDialog says whether showAdaptiveSheet shows
  a dialog here, for a sheet of its own that can't go through it.
- Learn's conversation, a half-height sheet dragged up, asks it: where
  sheets are dialogs it is a dialog up to 720 wide and as tall as the
  window, with no handle to drag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-01 18:20:58 +01:00
b908019969
feat(todo): dialogs on the desktop, the tabs reordered, a task's path and menu (ADR 0033, D11 to D14)
- halkyon_ui: showAdaptiveSheet shows a dialog on Linux, macOS and Windows
  at every width (bottom sheets stay on phones and tablets below expanded
  windows), so Learn and Train follow; SheetScaffold takes a heading in
  the title's place and may leave out its close button.
- Todo: the tabs are Inbox, Today, Upcoming, Browse, Today still opening;
  `serverpod start` launches the app on Linux, as it has no web build.
- A task's sheet shows where the task is (its project, an icon once tasks
  are above it, and the tasks above it, each opening what it names) and a
  ⋮ menu in place of its close button: when it was added (its UUID v7),
  Duplicate with its sub-tasks, hiding its done sub-tasks (synced in its
  payload), Close in the pane, Delete after asking. The description's icon
  stays by its first line.
- A row names its project and section at the end of its date's line; every
  menu entry has its icon.
- Learn: a studyset's path starts at the Library tab's icon, named by its
  tooltip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-01 16:53:38 +01:00
7727317c68
feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.

tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00
581cf1f1cd
feat(ui): tabs folding into the title, in Learn's and Train's Profile (ADR 0030, D60)
halkyon_ui's FoldingTabs: the tab row under the app bar's title at the
top, scrolling away with the content, the chosen tab's name then small
under the title, which, tapped, scrolls back up to the row, as Strava's.
Learn's Profile (Profile, Statistics) is built on it, and Train's
Profile in tabs: Progress (your body, the last 12 weeks, this year),
Days, and Plans where the device doesn't record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:12:11 +01:00
b45db6ea7c
feat(ui): every app's frame around its tabs, Learn's moved into halkyon_ui
NavigationFrame is Learn's AppShell without its router and its tabs: a
navigation rail from medium windows on with the app's icon on top,
leading to the first tab, a bar floating above the foot below, the
background of the screen in view around it, and a tick on every choice.
TabsStack is Learn's container of the tabs' navigators, every tab kept
alive and told whether it shows. Learn's shell is built on both, its
tests unchanged; Train's is next.

The rail's icon is drawn from 120 px at most in memory, as Learn's was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 23:52:39 +01:00
0ec15d2fb2
fix(learn): nothing of Learn's goes to Android's backup or to another phone (ADR 0019, D9)
Some checks failed
CI / check (push) Has been cancelled
CI / deploy (push) Has been cancelled
The device keeps private studysets open in its database, which Android's
backup (Google's, or Seedvault where turned on) and a transfer to a new
phone took open. allowBackup and fullBackupContent are off, and from
Android 12 on the data extraction rules exclude every folder from the
transfer too, which allowBackup alone no longer stops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 16:46:23 +01:00
ece9ce9697
feat: the apps' icons as one family, drawn by one tool (ADR 0031)
tool/app_icons.py at the root replaces Learn's and Todo's scripts: each
app's Lucide glyph (lucide-static 1.50.0) drawn by librsvg, fading from
cobalt through the colours ADR 0031 names, then every platform's
launcher icon through flutter_launcher_icons, Windows' .ico with every
size, and Learn's snap icon.

Learn's icon becomes the graduation cap, its window's on Linux too, with
Lucide's notice in its licence list; the kingfisher stays the emblem on
the rail and the launch screen. Todo's becomes list-todo, Train's bike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:45 +01:00
3e24bf8d9d
docs(deploy): deploy's manual key is pinned to redeploy.sh too
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-29 00:17:11 +01:00
8b5bdfed8c
feat(learn): choosing how much an assistant thinks, and the assistants by provider in the field
Halkyon Learn's ADR 0021, D60 and D63:

- A chip after the scope's offers the efforts the operator allows for the
  assistant asked, its own marked; the one picked is kept per assistant on
  the device (DeviceSettings.askEfforts) and asked at, also by a
  conversation continued from the history. One the operator no longer
  allows counts as none.
- Every question asks for its effort's ceiling on output, as the thinking
  counts towards it (8,192 to 65,536 tokens; 16,384 at the model's own),
  where it asked for 4,096; the flashcards proposed, Explain and Why? ask
  at the assistant's own effort, a person's own key at none and 4,096.
- The field's picker lists the assistants under their providers' names,
  each in its colour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:21:54 +01:00
19dd4d9c4e
fix(learn): the instance asks LanguageTool for its languages again a minute after it didn't answer
Switching the sidecar on learn.halkyon.eu's server recreated the server
beside LanguageTool, whose start takes a minute: the server's first look
at the languages failed, and it looked again only an hour later, as
after one that worked. Until then instance.info() named no languages,
and an app that asked meanwhile keeps that answer, so its editors check
nothing until it starts anew. A look LanguageTool doesn't answer is now
tried again a minute later, by a timer, until it answers, the languages
known before kept meanwhile; one that worked still counts for an hour.

The server's tests take fake_async (^1.3.3, as Learn's app, already in
the lock) for the timer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:21:52 +01:00
c23c0c80a1
docs(adr): ADR 0024, LanguageTool as a sidecar
The developer's decisions of 26 Oct (D1-D5) and the calls made
building it; notes in ADR 0017 (point 10) and ADR 0021 (D54) on what
it amends; Learn's AGENTS.md on the sidecar and the checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:17:11 +01:00
6e63312bfa
docs: the assistant answering in Learn, as step 3 built it
Learn's AGENTS.md describes the field with the assistants answering in it
(ADR 0021, step 3): who answers, the scope, the conversation sheet and its
lookups, images, private studysets' warnings, kept conversations and the
tasks; and the testing trap of awaiting a finished subscription's cancel.
The shared packages' READMEs name providerTermsOf and onThisMachine.
Learn's README no longer says there is no end-to-end encryption, which
private studysets changed (ADR 0019).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:17:09 +01:00
36899cdc3b
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.

AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.

Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:17:07 +01:00
dbae802915
feat(base,learn,todo): the operator's assistants, and the instance's privacy page (ADR 0021, step 2)
The server half of the assistants (points 4 to 8, D27 to D46): the
operator sets language models in deploy/assistants.env, grants them to
people with limits, and the module forwards their questions with the
operator's key, counting what each used, never what was asked.

- assistantsFrom: ASSISTANTS, ASSISTANT_ID_KEY, ASSISTANT_<ID>_NAME,
  _PROTOCOL (anthropic, openai, openai-responses), _BASE_URL, _MODEL,
  _KEY, _IMAGES, _PRICE_INPUT and _PRICE_OUTPUT (per million tokens),
  ASSISTANT_CURRENCY, ASSISTANT_OWN_KEYS; anything unusable stops the
  server as it starts, naming the variable.
- The assistant endpoint (identified): offered, agree (D40), ask as a
  Serverpod stream (D35): the text as the model writes it, then the
  whole turn, sent once the request is counted; figures, the caller's
  own. Checked in order: at most 2 questions on their way per person
  (tooManyAtOnce), the grant, the agreement, limits in the pricing's
  unit now (limitOutdated), the month's and the day's limits (with when
  they reset, UTC), the question (malformed, imagesNotRead, tooLarge
  past 400,000 characters, an image over 3,932,160 bytes or more than
  20 images); output capped at 16,384 tokens. Each request carries the
  person's opaque ID (HMAC-SHA256 under ASSISTANT_ID_KEY, D30). Once
  sent, the provider's reply is read to its end even if the app
  cancels, and counted on a session of its own; a provider's failure is
  logged by kind and status, told as providerKey, providerBusy (with
  its wait), providerRefused or providerFailed.
- The assistantOperator endpoint (identified and operator, on the
  register): setUp, grants, grant (assistantNotSet, invalidLimit,
  notFound), revoke, figures of a UTC month.
- Tables assistant_grant and assistant_figures (per person, assistant
  and UTC day), in the module's, Learn's and Todo's migrations. The
  daily pruning keeps this month and the two before
  (forgetOldAssistantFigures, D41); deletePerson deletes both.
  A member offers no assistant (D43).
- instance.basics() gains assistantOwnKeys.
- /privacy (D39, addPrivacyPageRoute, on the register's web server,
  before the profile pages' route): made from the instance's settings,
  English or German, no JavaScript: who may sign up, where the data and
  backups are, what the module keeps (the account, sessions, IP
  addresses, the server's log), the host's privacyPagePoints (Learn's:
  studysets synced, private ones encrypted on the devices with what
  stays readable, what is published, dictionary lookups, the learner
  count), each assistant with its model and host, what goes and what is
  counted, and whether personal keys are allowed. Never a key.

Tests: 70 on the assistants (grants, limits, refusals in order,
streaming, cancelling, counting, concurrency, the operator's denials,
deletion, pruning), 19 on the settings, 8 on the privacy page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:17:06 +01:00
31b9c17850
feat(app,learn,todo): the operator's "This instance" card, screen and badge (ADR 0021, step 1)
Whoever operates the instance approves, refuses and suspends accounts
and names other operators in the app (D21 to D24, D34):

- halkyon_app: InstanceOperatorRepository on
  ServerpodInstanceOperatorGateway (the module's instanceOperator
  endpoint; refusals as OperatorFailed: notFound, isOperator,
  lastOperator, notAllowed for 401 and 403, failed), in AppDependencies.
  OperatingCubit counts the accounts that wait: asked as the person's
  app opens, after every change, and every 5 minutes only while they
  operate the instance, so nobody else's app keeps asking.
  InstanceOperatorCard (or its tiles) and WaitingAccountsBadge show
  nothing at all for anyone else. InstanceOperatorScreen, laid out as
  Settings: the accounts that wait, each showing its four words to read
  out before Approve (D33), Refuse for new ones after asking; who
  operates, Take back after asking, the last always kept; Name an
  operator by handle; Find an account by the start of its handle, with
  Suspend after asking and Name as operator. English and German texts
  ("Admin" as the role word in German).
- Learn: the card right after the Sync card in Settings, which the
  cogs' count leads to as their dot leads to Sync; the count on the
  Settings cogs of Home and Profile.
- Todo: the tiles in the account sheet and the count on its button.
  Todo's server is a member, whose `operating()` is always null, so
  nothing shows there until it holds the register.

Tests: the gateway's mapping, the repository, both cubits with their
denials, the card, the badge and the screen; Learn's Settings shows the
card and the count to an operator and neither to anyone else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:17:05 +01:00
c48d423619
docs(adr): ADR 0021 accepted, the assistant's boundaries (D7–D30)
The developer's decisions of 25 Oct, reviewing ADR 0021 whole:

- What an assistant may see and do (D7–D12): the scope of each question
  (the parts ticked, the studyset, its tree, the library), looked up
  through read-only tools the app answers on the device; private
  studysets out of a wider scope unless switched on, and a warning
  whenever one's content would go to a remote model, Lumo included, as
  no provider keeps a question confidential while its model answers;
  changes only as drafts; progress never; text first, an image when
  ticked or asked for.
- What it keeps, does and where it lives (D13–D18): conversations kept
  sealed with the person's own key; four features (questions about what
  is open, cards from text, explaining a miss, checking a written
  answer); grants and limits in halkyon_base, the adapters in a package
  of their own (halkyon_llm, BSD-3-Clause); the proposal's points 4–8
  and 10 as written; the warning once per studyset and assistant; built
  for v0.21.0.
- Sign-ups and operators (D19–D26): SIGN_UP open, approval or closed;
  the first operator by SQL, others named in the app; suspending an
  approved account; a wait screen with a matching code the operator
  checks with the person; 14 days, 10 waiting.
- What people are told and what never goes (D27–D30): the app and a
  privacy page of each instance's own; others' posts only with their
  author's switch, a copy's licence being its agreement; an opaque ID
  per person for the provider.

ADR 0022 and Learn's AGENTS.md now point to it as accepted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:17:04 +01:00
4e33133733
feat(base,app)!: GitHub signs nobody in any more (ADR 0020, D36)
Every account signs in with SimpleLogin or a password now, so GitHub
goes from the module, the shared app package, Learn and Todo, ahead of
ADR 0017's GITHUB_SIGN_IN_ENDS.

- halkyon_base: no `gitHubSignIn` endpoint, GitHub identity provider or
  GitHub refusals; no `githubClientId`/`githubClientSecret`, no
  `GITHUB_SIGN_IN_ENDS` and no `forgetGitHubAccounts`; the register
  loses `account-with-github`. `SignInWays.gitHub`, the basics'
  `gitHubClientId` and `gitHubSignInEnds`, and `PasswordRefusal`'s
  `signInAgain` and `noAddress` go. A first password takes a code
  mailed to the person's own address; an account without one sets none
  (`wrongCredentials`).
- Learn's server: `instance.info()` without `gitHubClientId`, the
  pruning without GitHub, and `minimumAppBuild` 24, as builds up to 23
  (v0.20.0) require `SignInWays.gitHub`: the server deploys with the
  release of build 24.
- halkyon_app: no GitHub button or problems on the start screen, no
  password offered after a GitHub sign-in, and `ServerpodAuthGateway`
  without the browser half only GitHub used (`(client, base:)`). The
  account section offers a first password only to an account with an
  address. `LoopbackWebAuth` and the apps' schemes stay, SimpleLogin's
  ways back.
- Learn's Profile lists no GitHub; the texts go (English, German).
- Serverpod's `serverpod_auth_idp_github_account` stays, as its
  migrations make it; a row goes with its account, and ADR 0020's D36
  gives operators the SQL to delete those left.
- deploy/: compose.yaml and .env.example without GitHub. The READMEs,
  AGENTS.md files and ADR 0017 amended.

BREAKING CHANGE: builds up to 23 can't read the instance's answers
about an account's ways in; minimumAppBuild is 24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:16:39 +01:00
87671208df
feat(base)!: no anonymous accounts on the server (ADR 0020, D25)
All checks were successful
CI / check (push) Successful in 30m13s
CI / deploy (push) Has been skipped
The server's half of D25: every account has a way in, and the anonymous
accounts left from before go with the daily pruning.

- The module signs nobody in anonymously: `anonymousIdp` and
  `gitHubUpgrade` are gone, and so is linking GitHub at the register.
- A password is set only by someone whose address is theirs (a
  `firstPassword` code) or who signs in with GitHub alone (a fresh
  sign-in and a `signUp` code); an anonymous account is refused
  `wrongCredentials` before any code is used.
- SimpleLogin refuses to link onto a caller without `identified`
  (`wrongCaller`), the member's endpoint too, so nothing turns an
  anonymous account into one with a way in.
- `pruneAnonymousPeople` deletes every anonymous account left, however
  new, never one identified. Learn and Todo schedule it as
  `prune-accounts` and cancel the old `prune-abandoned-anonymous-people`
  entry once.
- Learn: someone without `identified` keeps no files;
  `FILE_LIMIT_ANONYMOUS_MB` is gone. `minimumAppBuild` is 23, as builds
  up to 22 start with an anonymous account.
- Learn's sync tests make their person and session by SQL in the
  development database, as Serverpod stores sessions.
- Docs, store listings and ADRs 0010, 0014, 0016, 0017 and 0019 amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 16:14:42 +02:00
5b9df73756
feat(app)!: no anonymous accounts on the device (ADR 0020, D4, D25)
Every account has a way in, so the apps make no anonymous account any
more. The start screen shows whenever nobody is signed in, a first
start included, and offers SimpleLogin, a password and GitHub for those
who have it, with no start without an account. An instance out of reach
is said so, with Try again, and one offering no way to create an account
too. An anonymous account's session left from before is deleted as the
app starts (D25), falling back to signing out on the device.

What only anonymous accounts needed goes: signing in anonymously, the
signed-out mark, adding GitHub to an account and switching to a GitHub
account from the Profile tab (with its Cancel, the only one), an
anonymous device's own keys, the warning that signing out deletes the
account, and the texts about keeping progress. Settings says on which
instance the account is signed in. Learn's publishing, reports and
suggestions and its learner count no longer ask whether the person has
an identity: everyone signed in has one.

The server still offers anonymous sign-in until the next commit, so
older apps keep working until v0.20.0 raises the minimum build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 16:14:37 +02:00
fad12b4411
feat(app): adding SimpleLogin to an account (ADR 0020, D21, D23)
All checks were successful
CI / check (push) Successful in 32m34s
CI / deploy (push) Has been skipped
halkyon_app reads where the device's person stands with SimpleLogin
(nothing to show, linked, or missing) and shows it in SimpleLoginSection,
which each app places above the password part: Add SimpleLogin, once
added one more way in. GitHub is added to nobody where SimpleLogin is
offered, and Learn's Profile offers an anonymous person SimpleLogin
alone there (D23).

No cut-off day, reminder or banner (D24): with the developer the only
person on learn.halkyon.eu, the cut-off is the operator taking GitHub's
OAuth app out of the deploy's settings once that account has SimpleLogin.
ADR 0020 records it and drops step 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 13:00:28 +02:00