Commit graph halkyon/halkyon_learn_flutter/test/feature
Author SHA1 Message Date
e1032caf7e
fix(dictionary): a Greek letter found opens no empty sheet of strokes
Tapping a letter of the Greek alphabet among the dictionary's results
opened the character sheet, which could only say that there's no stroke
order for it. A letter of an alphabet has no strokes drawn, so the
dictionary now offers the tap only for characters of a script whose
strokes are drawn (Han characters, kana, Hangul), as a studied
character's answer does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 18:23:53 +02:00
fd783df649
fix(study): a Japanese word's romaji reading is not typed as pinyin
Any reading in Latin letters counted as pinyin, whatever the studyset's
language, so a Japanese word read in romaji (東京 as Tōkyō or toukyou,
common in imported decks), which was never typed for having no kana, was
typed with pinyin's tone numbers and hint, and tokyo was marked wrong.

A reading is now pinyin only when the studied studyset's fronts are in
Chinese (zh, in any script or region), its own language or inherited: the
session reads it as each round starts, the card the same languages its
screen is set in. A Japanese studyset studies as it did before pinyin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 18:23:52 +02:00
d3063e82e1
fix(dictionary): a German separated verb is a verb, never a joined noun or particle
A clause ending in a separable particle joined each word before it to the
particle and offered whatever the dictionary found, so "Er holt mich ab."
offered aber, "Er schläft ein." einer and "Er hört dem Lehrer zu." zudem.
Now only an entry that is a verb is offered, and a capitalised word after
the clause's first is taken for a noun and never joined.

The particles of fernsehen, hochheben, durchlesen, unterbringen,
übersetzen and bereitstellen were missing, as were the spoken rein, raus
and runter, so "Ich sehe heute Abend fern." now finds fernsehen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 18:23:52 +02:00
21b71a4413
feat(explore): cards as the library's, in its colour, with a description of their own
Explore lists its studysets in a grid as the library does: each card in
the studyset's colour along the top, its title under its secondary
title, the start of its description and what it holds, then, quieter,
its author's handle, the release and the licence, and a compact Add.
A tap opens the summary, which shows the colour too.

The Publish sheet has a Description on Explore, starting from the
latest release's, else the studyset's own, and sent with the release:
a studyset may introduce itself to strangers while its own description
stays empty in the library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 18:23:52 +02:00
31eed85aff
fix(card): a search for blue no longer finds blunt
The card browser looked for a query also with every ae, oe and ue made
a, o and u, so that maedchen found Mädchen: in any language, so blue
found blunt, Spanish que found aquí, and ue nearly everything. A card's
umlauts are written out on its side instead, beside its folded text,
and a query is matched as typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 18:23:51 +02:00
4eb2640be6
fix(study): a map question names none of Japan's parts before it is answered
The chips above Japan's map (Hokkaidō, Tōhoku, Kantō …) showed while a
prefecture's question was answered: Hokkaidō's and Okinawa's chip was
the answer itself, and the others named the region the prefecture lies
in. A study card now offers them only once checked; tapping the map
still opens the part tapped. The world's continents keep their chips:
shortcuts no question asks for. Writing a question offers them all, as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 18:23:51 +02:00
dd6c567ad0
feat(studyset): prepare for an exam, with plans, a date and the pace to it
A studyset plans for exams: each plan names what an exam covers, the
topics it takes whole (with their flashcards, learning paths and PDFs)
and the tags whose flashcards it takes from anywhere in the studyset.
The author's plans are published with the studyset (payload schema 7,
`examPlans`); copying a release and taking a newer one point their
topics at the copy's own studysets. Plans are also offered from level
tags: JLPT N5 to N1, HSK 1 to 7-9 and KRDict's levels, each taking the
levels below it, as the exams do.

The person chooses a plan and the exam's day (`exam`), which stays
theirs: the server leaves it out of a release, as a PDF's deadline.
The study plan then shows the days left, how many of the plan's
flashcards were started, its paths' steps done and its PDFs' pages
read, and the pace that gets everything new learnt a week before the
exam (by the exam when two weeks or fewer are left): new flashcards a
day, steps a week, pages a day. Study for the exam studies only what
the plan covers, its new cards at that pace (counted as the day began)
and every review due.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 14:35:36 +02:00
31e4d7d64b
feat(studyset): an empty tab says its material is in the topics, and leads there
People testing the app looked for the material of a studyset that groups
topics in its own empty tabs, which said "No flashcards yet" and how to
add some. A tab with none of its own while the topics hold some now says
so: "The flashcards are in its topics", each topic holding some with how
much (everything inside it counted), opening it; for flashcards Browse
all of them in one list; and, quieter, how to add some here after all.
Opened for the first time, such a studyset shows the first tab its topics
fill. The tabs' counts stay the studyset's own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 14:35:36 +02:00
8dde0fa42a
feat(assistant): a question opens in the AI assistant chosen, Proton's Lumo by default
Questions asked from the app went to Claude alone. Settings › Assistant
now chooses where they open (decided 2026-10-14): Lumo, the default, by
its guest chat with the question in the address's fragment (Lumo's
`prefill`, which needs no account and reaches no server; the address for
one's own account loses the question when nobody is signed in), ChatGPT,
Claude, Mistral, Perplexity, Duck.ai, Grok, Kagi Assistant, HuggingChat,
or an address of one's own with %s. Each link fills the question in for
the person to send (Perplexity answers at once, Grok asks first); Gemini,
which has no such link, and Copilot, whose stopped working, are left out.
The field names the assistant ("Ask Lumo…"). A device that sent questions
to Claude goes on asking an assistant, now Lumo. Connecting Claude to read
studysets stays as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 14:35:35 +02:00
e7f3abdb8d
fix(dictionary): a Hanja reads 물 수, and each radical's form shows once
Korean names a Hanja by its meaning, then its sound; simplified Chinese
shares some radicals' forms with Japanese (麦), which showed twice, and
writes 黑 as itself. A test can read a language's strokes beforehand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 14:35:35 +02:00
93c33ce079
feat(dictionary): a German verb is found with its particle at the clause's end
"Ich rufe dich morgen an": where a clause ends in a separable particle,
each word before it is also asked for with the particle joined on, as a
subordinate clause writes the verb (anrufe), which Wiktionary lists among
anrufen's forms. The verb is offered beside its stem and its particle,
as the longer words running over a Japanese word are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 14:35:35 +02:00
87bebd1953
feat(card): the card browser finds and sorts words of any language as typed
Search folded case and katakana only, for Japanese. It now also leaves out
accents, tone marks and umlauts (strasse finds Straße, maedchen and
madchen Mädchen, σπιτι σπίτι, xue xi xué xí), and A–Z sorts by the same
bare letters, so Ärger stands among the a's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 14:35:34 +02:00
5479b973fc
feat(dictionary): characters of Chinese, Korean and Greek, written by hand; pinyin typed
Parity with Japanese's kanji and kana:

- The characters' tab is named by its language (Hanzi, Hangul & Hanja,
  Alphabet), its groups too: HSK levels and the characters in common use
  (Hong Kong's grades for traditional ones); Hangul and the Hanja taught
  in schools; Greek's letters. Radicals for Chinese and Korean in their
  own forms (讠 for 言 in simplified Chinese), none for Greek.
- A character's readings are named as its language names them: On and
  Kun, Pinyin, Eum and Hun (음, 훈); a letter means its sound and name.
- How characters are written comes by language: Chinese from Make Me a
  Hanzi (its medians in KanjiVG's square, Arphic Public License, a data
  file beside the app, never in cards), the Hangul letters drawn by the
  curator; a Korean word is written letter by letter (닭: ㄷ ㅏ ㄺ). Words
  of alphabets (German, Greek) aren't written by hand.
- A Chinese word's pinyin is typed in a session, tone numbers turning
  into marks (ni3 → nǐ); KRDict's levels (초급, 중급, 고급) tag Korean
  words; a Korean word's pronunciation shows in brackets ([가치]).
- Smaller: Look up in the composer keeps a noun's article; the Watch
  filter names its level list; hints and placeholders by language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 14:35:34 +02:00
2c4d760eac
feat(dictionary): a Chinese word's row says its HSK level
As a Japanese word's says its JLPT level: HSK 1 … HSK 6, HSK 7–9. The
levels' scheme (`WordLevels`, `levelOf`) moves from the Watch screen's
filter into the dictionary's model, which both name levels with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 09:39:31 +02:00
a2441ecdf2
feat(dictionary): German, Greek, English, Chinese and Korean words
A studyset in German, Greek, English, Chinese or Korean finds its
dictionary as one in Japanese does (ADR 0008), and its subtitles are cut
into words by its language (`WordCutting`): Chinese by its longest
words as they stand, the longer words over each cut offered as for
Japanese; Korean at its spaces, a word the dictionary lacks as it stands
taken without the particle it ends with (집에서 is 집, 아이 stays a
child); every other language at its spaces, a word written exactly so
first (the noun Essen before the verb essen). A studyset finds the
dictionary of its language tag, else of its language alone (zh for
zh-Hans).

A Wiktionary word shows its genders, principal forms, IPA, romanisation
and Hanja; a German or Greek noun of one gender goes onto its card with
its article (das Haus, το σπίτι); its tag is its word class, named in
full (Noun, Adjective, Preposition), as Wiktionary's adj and adv would
read as JMdict's codes. Articles, pronouns, prepositions and
conjunctions are left out of a video's words. Chinese words show their
pinyin over each character on the Watch screen, carry their HSK level
as a tag (HSK-1 … HSK-7-9), and the dictionary adds a level whole; the
Watch screen filters by HSK levels for Chinese, JLPT's for Japanese, and
by none for a language without. A reading is typed in a session only
when it is kana, so pinyin isn't. Words are looked up on the web on
Jisho in Japanese, on Wiktionary at the language's section otherwise,
and a search's hint speaks of kana and romaji only in Japanese.

`DICTIONARY_LANGUAGES` in deploy/.env names the languages an instance
keeps (unset: de,el,en,ko,zh; empty: none).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 09:39:30 +02:00
5683a1f33e
test(tag): the tag filter has room beside four material tabs
The test font draws every letter as a full square, so at 1,200 wide the
four tabs left the flashcards' filter folded into the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 09:39:29 +02:00
f8336ada90
test(studyset): every empty material tab is opened, as each has one
Since paths moved out of the Study plan, every empty section the test
looks at is a tab of Material, so the check for one without a tab is
always true, which the analyser refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 09:39:29 +02:00
ed34503cda
feat(studyset): an empty material tab shows no count
With four tabs, Paths 5, Flashcards 12, Notes 0 and Files 0 don't fit a
phone, and Files scrolls out of sight. An empty tab says so itself once
opened, so its count goes (decided 2026-10-14, after the trial's
screenshots); the four tabs then fit as long as a few are empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 09:39:28 +02:00
c3f7de9607
feat(studyset): learning paths are material, the study plan refers to them
A learning path is material too, as its flashcards, notes and files are,
and the study plan only refers to it (decided 2026-10-13), as it will to
an exam's date. Material's tabs are now Paths, Flashcards, Notes and
Files, the paths first as the way through the rest, with Reorder beside
them; opened here for the first time, Material shows the first tab with
something in it. Study plan keeps how many paths are finished and Next
up, which names the next step of each path under way ("Step 3 of 6: が"),
or offers to start the first path not finished when none is, and lists
how far every path has come, its steps done in a bar, each opening the
path. The path list's state now knows each path's steps done and next
step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 09:39:23 +02:00
107160cac4
fix(jellyfin): a tap on someone a server shows names them, the password asked
Jellyfin v10.11 and later no longer say which accounts have a password
(HasPassword is always true, jellyfin/jellyfin#14950), so the demo's
account was listed as "With a password" and only named in the form.
Every listed account is now named in the form with the password field
focused, which says to leave it empty for an account without one, as
Jellyfin's own apps do; the line under each account goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 01:55:12 +02:00
48ac84d92c
feat(jellyfin): any stretch of a line's characters chosen by hand
The cut takes the longest word and knows no grammar, so 今日は reads as
こんにちは where it is 今日 and は. Any stretch of a line's characters can
now be chosen by hand: dragged across with a mouse, Shift+clicked from
the word or stretch chosen, or on a touch screen held on a character and
dragged on. It is looked up as one word, as it stands or as a form of
one, in one small request (the instance has the line's text already);
while it is chosen the line reads it as that word (きょう over 今日), and
what the dictionary lacks as one word is offered as a search on Jisho.

The line's words move into line_words.dart, out of the Watch screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:33:51 +02:00
85506ac7eb
feat(jellyfin): the instance suggests a Jellyfin server, signed in to with a tap
An instance now names a Jellyfin server for its people to learn from
(instance.info().jellyfinUrl): its operator's JELLYFIN_URL (`none`: none),
else https://jellyfin. on the domain registered for its public host, as
learn.halkyon.eu suggests jellyfin.halkyon.eu, once that answers as a
Jellyfin server. The registered domain comes from Mozilla's Public Suffix
List, kept in the server's data folder, so an instance at example.co.uk
never names someone else's jellyfin.co.uk. The instance looks as it
starts and again hourly when asked; the app never reaches for a guessed
address.

In the app, Add material offers From Jellyfin as soon as the instance
suggests a server, before anyone signed in to one. The sign-in fills the
server in and finds it by itself, and lists the people the server shows
on its login screen: one without a password, as a demo's, signs in with
a tap; one with a password is named in the form, which asks for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:33:50 +02:00
d552f8bea0
feat(jellyfin): From Jellyfin once signed in, the panel in the glow's colour, adding the words listed
All checks were successful
CI / check (push) Successful in 16m0s
CI / deploy (push) Successful in 1m13s
Add material's entry is now From Jellyfin (Aus Jellyfin), with a play
icon rather than a TV, as it plays music too, and shows only once the
device is signed in to a server in Settings, as the assistant shows only
once switched on; Settings says where it then is. The screen it opens is
called Jellyfin, and its search finds music too.

The words' panel takes the glow's colour as the scene changes: each copy
the glow takes (every 3 s) gives its average, no lighter than 35 % so the
text stays readable, as strong as the glow and fading into the next as
it does. In the light theme, which has no glow, the panel is tinted from
it while the pointer is over it. What lights up in the panel takes a
scheme made from the same colour; the episode's thumbnail is no longer
used.

While the studyset holds none of the video's words, the study button
adds the words listed (Add 11 words, as the filter shows them), tagged
as a word added alone is; afterwards it studies them, and its menu adds
the new ones listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:50 +02:00
c6e7a53dd6
feat(tag): one switch hides the tags from Jellyfin while studying
Every tag the app makes for where words were met on Jellyfin (the show
and the episode, the artist and the song) now says so (`origin` in tag
schema 2, written only for such a tag), in place of a tag hidden one by
one. Manage tags, where there are such tags, has one switch, Hide tags
from Jellyfin while studying (`hideJellyfinTags`, settings schema 9,
following the person): with it on, cards being studied leave them out of
their row at once, and Tags and Manage tags list them faint. They still
filter and narrow a session. The per-tag eye button is gone.

A word added while watching no longer takes the line it was met in as
its first hint: its hints are the dictionary's examples alone. Tatoeba's
sentences are checked, a subtitle line may be wrong or mistyped, a word
met again in a better line wouldn't get it, and every episode's line
would pile up on a common word.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:50 +02:00
cc777d6b06
fix(study): cramming again after turning front and back
A session begun anew (turned to the other side, its settings changed, or
tried again) kept the cards it had crammed before, so a cram of a few
cards turned round said at once there was nothing to study. It now
crams every card again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:50 +02:00
22f5a4fcd1
feat(jellyfin): songs and their lyrics, read as subtitles are
A music library's artists and albums open as shows and seasons do, and
can be tied to a studyset. A song plays as sound alone (`/Audio/{id}/
stream`, as stored) with its lyrics as Jellyfin keeps them (`/Audio/{id}/
Lyrics`: an LRC file beside it, or its tags), cut into the dictionary's
words as a video's subtitles are, with no translation. Timed lyrics
follow the song: the line sung large and bright among the others faded,
scrolled to as it comes, a line tapped played from its start; untimed
ones show whole. A song without lyrics shows greyed and doesn't open
(`HasLyrics`), as there is nothing in it to read. Its cover stands where
the picture would, glowing as a video does, and its words are tagged
with the artist, shown, and "artist · title", hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:50 +02:00
436dc884b6
feat(jellyfin): an episode's own tag, and studying an episode's words
A word added from an episode gets the show's tag and the episode's own ("あかね噺 S1 E1"), hidden on cards. The words panel's Study button studies the studyset's cards of the video's words, on their schedules or crammed (each once): it pauses the video and first gives the episode's tag to the cards of its words the studyset held before, then opens the session on that tag. A study focus can now ask for cramming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:49 +02:00
68945b2224
feat(tag): a tag can be hidden on cards being studied
A tag's eye in Manage tags hides it on cards being studied, or shows it again: for the many tags naming where a word was met (an episode, a song), which would crowd a common word's card. A hidden tag still filters, narrows a study session and shows where tags are edited. Tag schema 2 adds 'hidden', written only for a hidden tag, so older apps go on reading every other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:49 +02:00
747d73b867
fix(jellyfin): Add and Jisho on the word's own line, entries unfold after 0.6 s
The Watch screen's entries carry Jisho's link and Add at the end of the headword's line, aligned right, rather than beside the senses; and an entry unfolds over the list once the pointer has rested on it for 0.6 s, as 0.3 s opened entries the pointer only passed over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:49 +02:00
8fb5e105c9
fix(dictionary): a long kana word stands, and a word offers the longer ones
Cutting a line into words, kana that is no common word gave way to a
shorter common one whatever its length, so もうろく (耄碌, senility) was
taken as もう and ろく. The rule now holds only for kana of up to three
characters (が and まだ still win over がま, a cattail, and だ); a longer
kana word stands even when uncommon, as ichi.moe's segmenter weighs long
kana words by their length. And as no cut is always right, each word now
knows the longer words running over it into its neighbours, worked out
from the same lookup, which the Watch screen's panel offers beneath its
entries ("Longer words here"), each to add with the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:49 +02:00
fbe53c3441
feat(jellyfin): the words' panel unfolds senses, shows progress, filters
The panel beside the video lists each entry's senses one to a line, as
Jisho does (and as every dictionary list in the app now does), folded
to two lines; an entry unfolds over the list, not in it, after a moment
under the pointer or on a tap, in the video's colour, so nothing moves
under the pointer and words of many senses (上げる has 25) crowd out
nothing. Each entry links to the word on Jisho.org, opened only when
tapped. A word the studyset holds shows how far its card is learnt:
New, Learning, Due, or Learned with when it comes again. The video's
words go through a filter in place of New only: new to the studyset,
held or all, JLPT levels, and word classes. The dictionary's sources
stay in the panel's lower right corner, and the player's controls hide
after 2 seconds rather than 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:49 +02:00
48a9564231
feat(jellyfin): the player as players are, with the settings under a cog
The Watch screen's player takes the shape people know from players,
YouTube's first: the picture set in from every side with rounded
corners, so the glow of its colours shows all around it, and over it
play or pause in the middle with 10 seconds back and on beside it, and
along its foot where the video is (0:05 ──── 23:47) and a cog. The
controls fade out while it plays and the pointer rests, and come back as
it moves, on a tap with a finger, on a jump and while paused. The cog
opens a panel inside the picture, so nothing runs off the window: Pause
after each line, the subtitles, the translation and the sound, each
track named by its language in the app's words, and the keyboard
shortcuts. The buttons for the line before, again and the next sit
beside the subtitles they act on, no longer looking like a player's
skip to the next video. Keys are YouTube's (Space or K, J and L, the
arrows, 0 to 9, Home, End) with Shift and the arrows for lines, R, P and
Esc; ? lists them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:48 +02:00
96cbd912de
fix(ui): a window resized across 600 dp keeps every screen as it was
Crossing from compact to medium windows or back built screens anew,
losing what they held: a study session started over, a card being
written lost its text, a video began again. The frame around a screen
over the tabs (FramedScreen) was added above it only from medium windows
on, and the snack bars' width (FittedSnackBars) put a Theme above the
app's navigator only there, so the screens below sat at another depth
after the change. The snack bars now always keep their Theme, changing
only the width in it, and the framed screen keeps its screen under a
global key, which moves it whole between the two places.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:48 +02:00
da51ef73cb
feat(jellyfin): watch from one's own Jellyfin server and learn the words of its subtitles
Animelon's way of learning from video, from the person's own Jellyfin
server: connected per device (Settings, or a studyset's Add material ›
Watch from Jellyfin) with a name and password or a Quick Connect code,
over HTTPS only (plain HTTP only to a server on the same machine); the
token stays in the system's keyring. Shows, films and videos tied to a
studyset follow the person in their settings as the server's and the
item's ids alone (settings schema 9), named by the server when shown.

The Watch screen plays the video (video_player on Android, media_kit's
libmpv on Linux and Windows) with the line in the studyset's front
language cut into the dictionary's words with furigana, and the back
language's line beneath; a word tapped pauses the video and becomes a
card with the line and its translation as its first hint, tagged with
the show. The video's words are listed, most frequent first. The sound is
the track in the studyset's language, whichever the file marks default,
and the menu switches it; the subtitles read are a whole track, never a
forced one (which only covers what the sound doesn't say), and captions
for the hard of hearing only without plain ones. The line and its
translation take the video's width in a box of a fixed height, so the
video keeps its size; in the dark theme the video's colours glow around
it, as YouTube's ambient mode, and the words' panel takes the colour of
the episode's picture under the pointer.

A line is cut into words with one request: DictionaryEndpoint.withKeys
looks up every stretch of it that may be a word, as it stands and as
every word it may be a conjugation of, in the new dictionary_key table
(one row per key, indexed; migration dictionary-keys adds the table and
its index, nothing else), which the loader fills beside the entries.
The instance gets the text of each line looked up and keeps none of it,
as the Watch screen says.

Linux builds need libmpv-dev, and the app links libmpv: the snap stages
Ubuntu's libmpv2, makes the update-alternatives links for BLAS and LAPACK
that ffmpeg's speech recognition needs (staging never runs install
scripts), and removes what the GNOME runtime and core24 already hold, as
older copies of cairo, pango and librsvg hid the runtime's from its GTK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:48 +02:00
61bf7b2fa0
feat(citation): sources are books and articles, websites or YouTube videos
A source was a book's or an article's parts, however a website or a
video had to be squeezed into them. Adding one to a bibliography or a
step now asks what it is, and its form asks for what cites it: a book
or an article its parts as before; a website the page's link, title,
the site's name, authors and year; a YouTube video its link (checked to
be one), title, channel and year. Websites and videos need their link,
marked as required. A video is cited as APA cites one ("[Video]", a
part of YouTube) and plays in the app where it can, else on YouTube.

The kind is kept in the source's JSON, left out for a book or an
article; a kind this app doesn't know reads as a work. The studyset's
payload goes to schema 6 and a step's to schema 4 for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:48 +02:00
1c280157aa
feat(ui): required fields say so with a stronger outline and "Required"
Only a studyset's title was marked, with an asterisk; the other fields
nothing can be saved without said nothing until Save stayed grey. Every
required field now has, while empty, its outline (or its line) stronger
in the primary colour and "Required" below it, before its own helper
text: a studyset's and a path's title (in the sheet and the editor, in
place of its error), a tag's name, a PDF's new name, a YouTube video's
link, a link's address, the handle, an instance's address and a custom
search engine's address. Once written, an empty line keeps its place,
so nothing below moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:47 +02:00
b7e5b695b9
feat(studyset): every menu of a studyset offers all of its options
The library's card offered six options, a topic three, the studyset's
own screen all of them. Each now offers the same, from one list: edit,
move, study options, import and export content, reset progress, archive
or unarchive, delete, more info, and for a copy reporting to its author
and its reports and suggestions. Importing runs where the studyset's
blocs are, so from the library or a topic it opens the studyset's
screen, which asks what to import as it opens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:47 +02:00
b70d8e4ed2
feat(studyset): reset progress asks what starts afresh: flashcards, PDFs, paths
Resetting a studyset's progress started its flashcards afresh only. It
now asks what of it starts afresh, with everything inside it, naming
only what has progress and how much: its flashcards, reading its PDFs
(those read some way) and its learning paths (those with a step done),
each ticked to untick. One Undo takes all of it back: reading a PDF
afresh is undone now too, its fresh starts deleted by the moment they
were written, as the paths' are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:47 +02:00
fcee3c2670
feat(pdf): cards beside a PDF are written in the composer's own editor
The sidebar wrote a front and a back only. It now holds the composer's
editor with a bloc of its own: every kind of card (multiple choice,
vocabulary with Look up, matching, a map, a character), tags, hints and
the explanation, pinning, duplicating and deleting. A new card is tied
to the page shown, which it can be moved to the current one, and waits
for its chapter's tag by name until it is created; text sent from the
pages goes into its front or its answer, as its kind keeps one. Create
saves it once it is complete, as the composer does; going back asks
before discarding what was written.

The screenshot of the pages has a button at the end of the format bar,
outside its scrolling, where an editor's place is for it
(InsertableBlock.inFormatBar); the Insert menu still lists it.

A new card no longer shows twice in the composer when its stored copy
arrives while it is saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:47 +02:00
72cbec52c2
fix(pdf): the divider beside a PDF follows the pointer, and keeps its place
Dragging it moved the sidebar by each event's step from the width last
built, so whatever arrived between two frames was lost: the sidebar
trailed the pointer, which soon left the 10 dp strip and dropped the
drag. Each move also built every editor of the sidebar anew, its blocks
being a new list on every build. Now the width follows from where the
drag started, the blocks are kept while they stay the same, the handle
is 16 dp with a grip under the pointer, and the resize cursor holds
over the pages while dragging.

The sidebar's share of the room is kept as a device setting once a drag
ends, so every PDF opens with it, in any window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:46 +02:00
797354ef00
fix(pdf): the files' Sort sits beside their tab, folding with Reorder
The files' Sort button stood in a row of its own above the files, out
of line with their rows' ⋮ buttons, while Reorder sat beside the tabs.
Both now sit at the end of the tabs' row (PdfListToolbar), folding into
a ⋮ menu where room runs short as the flashcards' tools do: Reorder's
word first, then both into the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:46 +02:00
44cd1327d2
fix(dictionary): its sources in a short line, the full credits a tap away
The foot of the dictionary named each source with its makers and
licence, three lines of small print. It now names the sources alone in
a line ("Words from JMdict, JLPT vocabulary lists, Tatoeba"); tapping
it or its ⓘ shows who made each, under which licence, with its link,
which is still where the entries show, as the licences ask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:45 +02:00
7c7debd36f
fix(studyset): pulled past its end on Android, a part's box stretches as a whole
Android's stretch drew the list out while the box framing it stayed put,
so the text moved inside a frame that didn't. The boxes around a
studyset's parts and its topics now stretch as a whole, their frame
with them (SectionCard's `scrolls`), the list's own stretch off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:45 +02:00
5455453d20
fix(card): a word's grammar notes grey and slanted in the browser and the dictionary too
A session's answer set each sense's grammar notes (noun, used as a
suffix, …) after its glosses, small, grey and slanted, their meaning on
hover; the card browser listed the glosses alone, and the dictionary's
lists ran the first glosses together. Both now show the senses as the
answer does (VocabularySensesView, out of the study screen into the
card's widgets), the dictionary's rows as one paragraph cut short at two
lines (vocabularySensesSpan).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:44 +02:00
664a966574
fix(card): the browser's title says how many cards, sub-studysets on a line below
The card browser's title ran "Japanese — flashcards (incl.
sub-studysets)". The sub-studysets now go on a small line of their own
below the title, and the title has how many cards there are beside it,
in the badge the material's tabs show their counts in (CountBadge, now
shared).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:44 +02:00
468810c6f4
fix(studyset): the material's tools fold into the menu rather than cutting the tabs off
Where the row beside the material's tabs ran short, Browse, the filter
and the menu kept their room and the tabs were cut off ("Fil…"). The
tools now fold one by one, from the widest to the narrowest that leaves
the tabs their whole width: Browse loses its words first, then the
filter goes into the ⋮ menu, then Browse does too, as the studyset's own
actions fold into icons. A row of its own (CollapsingRow) measures the
tabs and each way of showing the tools and shows the first that fits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:44 +02:00
cf6d58aae7
feat(studyset): the material in tabs, each saying how many it holds
The Material part stacked the flashcards, notes and files as sections,
the first three of each behind Show all, as Vaia stacks them. With
hundreds of cards that kept the list a tap away, repeated each list's
tools and pushed the files far down. They are tabs again, Flashcards,
Notes and Files, each with its count, each list whole with its tools at
the end of the tabs' row; Progress and the Study plan stay. The tab is
kept as the part the studyset was left on, while the material shows,
and opened for the first time it opens on a tab with something in it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:43 +02:00
ceb7ca1a1e
fix(studyset): Progress is always one of a studyset's parts
Progress joined the switch only once the studyset's reviews were read
and showed something studied, so the switch grew from two parts to
three after a moment. It is now always there, saying there is nothing
learnt yet where nothing was studied, as in Vaia; the switch never
changes as a studyset opens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:43 +02:00
959014b4a1
feat(profile): the profile saves as it is written, the handle chosen in a dialog that checks it
Changing the profile meant a Save button at the foot of the form, which
the floating navigation bar half covered on a phone. A field now saves
when it is left or Enter is pressed in it, and a link when it is
removed; "Saving…" and "Saved" show beside the title. The form keeps
what is being written as saves come back, where it used to start afresh
and lose focus after each one.

The handle is chosen in a dialog that asks the instance while it is
typed whether it is free (`ProfileEndpoint.checkHandle`, which tells
nothing `byHandle` doesn't), and choosing the first makes the profile
with what was written. Saves go one at a time, the latest of those
waiting next, and a handle chosen holds for every save after it, so a
field's save can't take it back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:43 +02:00
b46dfe22b1
feat(profile): statistics beside the profile, a review counting a minute at most
The Profile tab has two tabs, Profile and Statistics, the latter for
anonymous people too: the time studied in all, per day and the most in
one day, and by days, weeks or months the study time and the ratings as
bars, a tap on a bar saying what it holds. The app draws the charts
itself, as the Progress part's sparkline, rather than taking a chart
package.

A review's time was however long its card was shown: one left on the
screen over lunch counted as an hour of study. It now counts at most a
minute, as Anki does by default, here and in a studyset's Progress; the
stored durations stay as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-13 21:21:43 +02:00