LWT 3.5.0: Graded reviews, Chinese word splitting, and schemas that survive
Reviews are graded the way Anki grades them, Chinese texts split into words, and an install whose schema came out incomplete stays usable instead of answering 500. If you run LWT in Docker, read the first section before upgrading — this release changes where the database lives.
Before you upgrade: the database moved to a named volume
docker-compose.yml used to bind-mount the database directory from the host (./lwt_db_data). On Windows that path is case-insensitive, so MariaDB forced lower_case_table_names=2, which its Linux build does not support, and every ALTER TABLE failed its tablespace rename — the cause of the failing clean installs in #275. The database now lives in a Docker named volume instead.
An existing install will look empty after upgrading. Nothing is deleted: your data is still sitting in ./lwt_db_data, it is simply no longer mounted. Dump it before you upgrade, and import it afterwards:
# before upgrading, against the running stack
docker exec lwt_db mariadb-dump -uroot -p"$DB_PASSWORD" --single-transaction \
--databases learning-with-texts > lwt-backup.sql
# after docker compose up -d
docker exec -i lwt_db mariadb -uroot -p"$DB_PASSWORD" < lwt-backup.sqlTake that dump regardless: this release also drops three columns from words, and there is no downgrade path once it has run.
Reviews are graded Again / Hard / Good / Easy
The review card's Wrong/Correct pair becomes the four grades FSRS needs, each showing when it would bring the term back — "3d", "2mo" — before you commit to it. Keys 1-4 grade, as in Anki; the arrow keys still work, and setting a status outright moved to Shift and the number keys.
All three correct grades still raise the term's status by one, exactly as Correct did, so your reading colours are unchanged. What differs is what the scheduler learns from the answer.
The queue follows that schedule now, picking and ordering by each term's due date rather than the Leitner score LWT has carried since the original. A term you have not graded keeps the schedule it already had — it falls due exactly where its status put it — so nothing floods or empties when you upgrade, and it moves onto real scheduling the first time you grade it. Two statuses come up a day earlier than before, where the old formula rounded before comparing.
With that, the legacy scoring is gone: WoTodayScore, WoTomorrowScore and WoRandom were a stored copy of a formula over a term's status and the date it changed, and a migration drops all three. Nothing is lost, because the values were derived rather than entered. The nightly recomputation across your whole vocabulary goes with them, and the vocabulary list's Score column becomes Due, counting days until the term returns.
Anki decks arrive already scheduled
A term LWT has scheduled now exports as a review card rather than a new one. It is due in Anki when it is due in LWT, and carries its interval, review count, lapse count, FSRS memory state and full review history, so Anki's own FSRS continues from LWT's estimate instead of starting the term over. Suspended terms keep their schedule behind the suspension, so unsuspending resumes rather than restarts.
Nothing flows the other way — re-importing a deck still updates only fields and suspension.
Chinese texts can be read
A Chinese language created from the built-in preset produced a text with no clickable words at all: nothing to look up, nothing to track. Three things had stacked up behind that, and the root of it was that the Parser Type setting in the language form was decorative — it was written to the database and no part of the parsing pipeline ever read it. Choosing "Jieba (Chinese)" or "MeCab Python" parsed exactly as before.
The setting is honoured now, and the menu lists the parsers from config/parsers.php, which it never did, so jieba and MeCab Python appear on an install that has them — the published Docker image among them. The Chinese and Japanese presets ask for a real tokenizer accordingly. Where that tokenizer is not installed the language falls back to character-by-character parsing, so the text stays readable either way.
Every language that exists today parses exactly as it did. That is worth stating plainly, because it was nearly not true: an earlier cut of this change re-tokenized every character-split language on upgrade, and a Chinese text went from 103 words to 122 while its saved terms went on matching the old split.
A text that parses into nothing says so
A language whose Word Characters setting does not match the script of its texts does not fail. It parses successfully into nothing — the text saves, opens, shows every character, and refuses to respond to any click, with nothing anywhere to explain why. The reading view and the check-text page now say what happened and link straight to the language's settings.
An incomplete schema stays usable
If a migration failed on your install, the release before this one would have made things worse rather than better. The review queue reads each term's due date from term_schedule, and a table named in SQL cannot be guarded the way a missing row can — MySQL rejects the whole statement at prepare time. So on a schema missing that table, the review page and the entire vocabulary list answered 500, where 3.4.2 had still been usable.
Both now fall back to the schedule a term's status implies, which on such an install is the same answer, since nothing there has been graded yet.
The reason installs were left in that state is fixed too. A failed migration was only reconsidered when an upgrade brought new migration files along — which on a fresh install can never happen, since the first run records every migration and there is never anything new afterwards. A failure stayed failed at one attempt, forever, until some later release happened to add a file. Failures are now retried on the following requests, up to three attempts, and an upgrade restores that budget so a migration that failed on a prerequisite a later one repairs still gets its chance.
Migration logging got the same attention: a healthy fresh install used to write ~178 Migration failed: lines while every one of its migrations succeeded, because legacy migrations rename tables a fresh install never had and those statements fail by design. They log as skipped now, and Migration failed: means it.
Adding a term on a large vocabulary
Opening the term editor read every term of the language into memory to look for similar ones. That is affordable for a vocabulary built by hand and fatal for one seeded from a dictionary import, where clicking a word spent a minute before dying on PHP's memory limit. Candidates are selected in the database now, so the work no longer grows with the size of your vocabulary. Suggestions themselves are unchanged.
Two improvements to what it suggests, while we were there. Similar terms follow word families rather than spelling alone, so bought and buy find each other despite sharing no letters. And for a compound like Geschwindigkeitsbegrenzung, suggestions are picked one at a time with the term shrinking to what is still unexplained after each pick, so the word covering the other half is no longer crowded out by a dozen near-identical siblings.
Upgrading is the usual docker compose pull && docker compose up -d --force-recreate — but read the first section first, and take the dump. Full detail in CHANGELOG.md.