Commit Graph
27 Commits
Author SHA1 Message Date
Claude 82d65bfa34 Rebuild the Verbs tab on the prefix rule, and add a verbs on/off switch
Five clusters, named as signed off, taught in the order that makes each one
cheapest to learn:

  Prepositions in Disguise  the free ones            25 verbs, one rule
  Aimed at Someone          the list you learn        9
  Facing Someone            the list you learn        5
  Upside Down Verbs         the subject swaps         6
  The Five Liars            where English misleads    5 pairs

The organising claim is that a verb beginning with a dative preposition takes
the dative, so 25 of the 45 dative verbs cost one rule rather than 25
memorisations. The UI makes that visible instead of stating it: the prefix is
printed in the dative colour wherever a verb appears, in the Lernen panel, in
the rule modal and in Charts. zuhören, beitreten, widersprechen.

The rule has a hole, and the lesson now says so. As a preposition, wider
governs the ACCUSATIVE (wider Erwarten), so widersprechen and widerstehen
being dative is not something the rule predicts. They carry a ruleExempt flag,
are marked ✱ everywhere they appear, and a test fails if a verb lands in the
prefix cluster whose prefix is not in DATIVE_PREFIXES without that flag —
otherwise the first lesson would be teaching something false.

Coverage: the earlier plan parked 19 prefix verbs in Charts as a reference
list. That was wrong — a list you read is not a verb you can produce — so
there are now two "Der Rest der Familie" lessons that drill them, and Daily
Practice draws from all 68 verbs. Lesson one is six exemplars covering six
different prefixes; nothing is taught twice and nothing is left untested,
which a test asserts by set equality.

Verbs on/off, defaulting to OFF. With it off every case in the app comes from
a preposition, so a question never asks two unknowns at once, and the Verbs
tab is hidden. On, the two tabs whose case source is a free choice go to
roughly half and half. Only those two tabs change, and it is worth being
precise about why: Präpositionen, Artikel, Adjektive and Komparativ lessons
are named after the preposition group or the ending table they drill, so there
is no verb-governed variant of "Dativ: mit, von, aus, bei" to mix in. The
sign-off board claimed ~50% for every tab; that was my overstatement, not a
decision, and the switch is implemented where it means something.

Pronouns needed new code for this, as flagged. Every pronoun question was
"Ich helfe ___", which leans on knowing helfen is dative, so with the switch
off that tab had nothing to generate. PRON_PREP_FRAMES adds nine
preposition-governed shapes (four accusative, five dative) — Er wartet auf
mich, Er wohnt bei mir — and a test checks each frame's preposition really
governs the case it claims. Pronoun questions still carry no noun on either
setting, so the guard that fixed the blank screen stays real.

Data: 29 new verbs. 25 prefix verbs across eight dative prefixes, ähneln,
gehorchen and dienen for Facing Someone, gelingen and leidtun for the flippers
(leidtun needed a `tail` field, since its clause closes with a fixed word).
45 dative verbs in total, which is where the "about 50" figure comes from once
prefix verbs are counted individually rather than folded into a rule.

Four things the corpus diff caught before they shipped: "I obey the child",
"I serve the child", "I succeed the girl", and — the real one — "The doctor
feels sorry for the doctor", because leidtun had people on both sides and
Arzt/Ärztin are two German nouns with one English word. A collision scan over
every verb line is now clean. The old test asserting the dative list "matches
the reference" is gone; it matched my own subset, not a reference, and it now
checks the cluster structure the lessons rely on and says in a comment why no
canonical list exists to check against.

Two things the browser pass caught that tests could not. The Lernen panel had
widersprechen among the six exemplars with its prefix coloured like the rest —
presenting the one verb the rule does not predict as proof of the rule, on the
same screen as the note saying it is an exception. It is now ausweichen, and a
test fails if a ruleExempt verb is ever used as an exemplar. And the prefix
colour means "this prefix is a preposition governing THIS case", so wider- now
prints in the accusative colour rather than the dative one: it really is a
preposition, it just governs the other case, which is the whole reason those
two verbs are exceptions.

219 tests pass. Every level in every tab renders and answers on both switch
settings — 57 rows with verbs on, 45 with verbs off, no page errors — driven
by clicking through the real UI rather than calling the generator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-10-14 10:00:13 +00:00
georgeandClaude Opus 5 d74f341d43 Add the Comparison tab: Komparativ, Superlativ and am besten
New tab, six lessons plus Daily Practice. The organising idea is that
comparison adds no endings at all: it builds a new STEM (gut -> besser ->
best) and then takes the same three adjective tables on top. The feedback
and the rule panel are written around that, and the declension levels reuse
ADJ_ENDINGS unchanged.

  Die Steigerung            build the comparative and the am-superlative
  Umlaut?                   alt -> aelter, but laut -> lauter
  am besten oder das beste? which superlative shape the slot wants
  Komparativ deklinieren    besser- plus the usual ending
  Superlativ deklinieren    best- plus the usual ending
  Alles gemischt            both degrees, every table

The point the rule panel leads with, and the one that makes "am besten"
stop being a special case: am = an + dem, so it is a DATIVE, and the weak
dative ending is -en for every gender. "am besten" is not an exception to
the tables, it is already in them, frozen. The level that tests it turns
purely on whether a noun follows the gap, which is the actual rule.

Data: comp/sup stems on all 24 adjectives, an umlaut flag (the thing worth
drilling - alt/jung/lang/kalt/warm/gross take one, laut/rot/blau/neu do
not), and the five irregulars that do not decline normally (viel, gern,
hoch, nah, bald) in their own list used only by the form-building level.

English comparison is a lookup table rather than a rule, because a rule
produces "badest", "beautifulest" and "blueest" - all three of which the
first run actually generated before the table went in.

Two constraints built into the generators: a superlative never takes an
ein-word ("ein bester Mann" is not said, since a superlative implies
uniqueness), and the wrong answers are always the same stem with another
ending, or the positive degree - so both halves of the job are tested. The
English mistakes "mehr interessant" and "am meisten interessant" appear as
distractors in the form level and never as the answer.

Also fixed while here: the adjective carrier-frame draw could repeat a
frame back-to-back when the strong class had only one eligible frame. It
now steps past a repeated frame as well as a repeated preposition. The two
anti-repeat tests that asserted this absolutely were flaky - they are now
measured bounds with a comment saying why an absolute claim is false.

195 tests pass, stable over four consecutive runs; all 38 levels across the
seven tabs render in a browser with no page errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-10-14 07:00:31 +00:00
georgeandClaude Opus 5 b96f3d3d0e Fix blank screen on the pronoun levels
Opening Singular Pronomen rendered nothing at all. The console said
"Cannot read properties of undefined (reading 'w')".

My regression, from the commit that added the "why Regens?" explainer. That
button is rendered on the screen shared by the article, contraction,
possessive AND pronoun questions, and its visibility test calls
nounForm(q.noun, q.cas) — but pronoun questions have no noun. The read threw,
React unmounted the tree, and the whole page went blank rather than losing
just that one line.

Guarded the access. Then swept all 32 levels across the six tabs in a real
browser: every one renders with no page errors.

Also wrote the contract down, since this is the second field a screen has
dereferenced without checking. engine.test.js now asserts which question
types carry a noun and which deliberately do not, that pronoun questions stay
noun-less (so the guard keeps being necessary rather than quietly redundant),
and that every question of every type has an answer and a blank to fill. A
new question type that lands on a shared screen without the fields that
screen reads is now a test failure instead of a blank page.

170 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-10-05 06:16:28 +00:00
georgeandClaude Opus 5 eae889c52b Add the Verbs tab, and stop Daily rounds repeating a preposition
The possessive and pronoun levels already build sentences with no
preposition in them - "Ich kenne ___ Mann", "Ich helfe ___ Mann" - where
the case is decided purely by the verb. Six verbs were doing that silently
across seven levels (sehen, kennen, lieben, hoeren as accusative, helfen as
dative, geben as both) and nothing in the app had ever taught or tested it.

The framing the whole section rests on: there is no accusative list to
learn. Accusative is the default for hundreds of transitive verbs, so you
learn the dative exceptions - the same shape as the preposition lists the
app already teaches.

39 verbs of data, in four groups:
- accusative foils, including the ones English makes feel dative
  (fragen, anrufen, treffen, unterstuetzen, moegen)
- 12 dative verbs: helfen, danken, antworten, glauben, folgen, gratulieren,
  vertrauen, verzeihen, begegnen, zuhoeren, raten, schaden
- 4 that flip subject and object against English: gefallen, schmecken,
  fehlen, gehoeren
- 8 two-object verbs, all covered by one rule: person Dativ, thing Akkusativ

Six lessons plus Daily Practice: Welcher Fall? / Die Fallen / Gefallen & Co.
/ Dativ + Akkusativ / Im Satz / Alles gemischt. The tab sits second, right
after Prepositions, so case government is learned before the forms that
depend on it.

Near-synonyms that disagree are modelled as a `trap` link, and a test
asserts every trap points at a real verb taking the other case and that the
link is symmetric - so fragen/antworten, treffen/begegnen, anrufen/zuhoeren,
unterstuetzen/helfen and moegen/gefallen cannot drift apart.

The inverted verbs carry an explicit English reordering rule rather than a
literal gloss, so "Das Fenster gefaellt der Aerztin" renders as "The doctor
likes the window" and never "The window pleases the doctor". Tested.

Verb sentences are enumerated into corpus.txt like everything else, keyed on
verb x noun, which is what caught "I lend the teacher the bed" and "I
recommend the girl the cupboard" before they shipped - two-object verbs now
name the things that can actually be handed over, and the English uses the
"to the person" order.

167 tests pass.

Known gap: a handful of verbs take the genitive (gedenken, beduerfen,
beschuldigen). They are formal and rare, so the rule panel mentions them and
the app does not drill them.

Daily rounds could serve the same preposition twice, even back-to-back

Reported as "für" twice in a row in the Prepositions daily. Measured before
the fix: 14% of prep-daily rounds had a back-to-back repeat and 51% repeated
some preposition.

A Daily round mixes several lessons, and each one was handed its own
newState(). The anti-repeat memory therefore only spanned one lesson's slice
of the round, so level 8 drawing "für" and level 10 drawing "für" could not
see each other. drawBag also only avoided the previous item when the bag
refilled, not on every draw.

- newState(shared) lets several lesson states run off one memory, and the
  Daily hands the same one to every lesson it mixes.
- drawBag now skips anything already used this cycle, and never repeats the
  previous item, checked on every draw rather than only at a refill.
- Kontraktionen drew through a different bag ('contr') and so bypassed the
  preposition memory entirely, letting "im" follow "in". It now consults and
  records the shared memory.
- Adjective frames were chosen with a plain pick(), so a Daily could repeat
  a whole carrier sentence. They are dealt from the shared memory too.

Rejected draws are put BACK into the bag rather than discarded. The first
version burned them, which skewed the Kontraktionen deal 1.5x toward the
forms whose preposition is not shared with a sibling form; reinsertion holds
the spread at 1.04x.

After: every Daily and every preposition-drilling level is at 0% for both
back-to-back and within-round repeats. Two cases remain non-zero and are
arithmetic rather than bugs - a round is 10 questions, so Wechsel im Kontext
(9 prepositions) must repeat one, and the Stark adjective level has exactly
one prepositional carrier frame. The adjective tab is held to frame variety
rather than preposition variety, since there the preposition is scenery and
the ending is the question.

Tests pin all of it: no Daily may repeat a preposition, no level may repeat
one back-to-back where it has a choice, and no Daily may repeat a carrier
sentence back-to-back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-10-05 06:12:27 +00:00
georgeandClaude Opus 5 74054338f4 Possessive levels showed the stem where every other level shows the gender
On the possessive levels the second line read "unser- (our)" — the stem —
while articles, contractions and adjectives all put the noun and its gender
there. The gender was still available, but demoted into the third line as a
tail on the case hint, so the thing you actually need was the least visible
thing on screen.

The stem was telling you nothing either way: possessive answer options are
generated to share the question's stem, so all four already start with
unser-/ihr-/euer-. It was spending the most prominent hint slot on the one
fact the options could not hide.

Articles, contractions, possessives and adjectives now render the same
second line — noun, gender-coloured article, English gloss — under the same
`gender` toggle, so hiding it on one level hides it on all of them. The
duplicate gender tail is gone from the case line, and the possessive-only
hint and its toggle are removed.

Pronoun questions keep their own hint. They have no noun at all, and which
pronoun is meant is genuinely not recoverable from the sentence, so that is
the right hint for them rather than an inconsistency.

Wechsel im Kontext keeps its preposition hint for the same reason: the
answer there is a case, not a declined form, so the noun's gender is not
part of the question and the preposition's English gloss is what helps.

142 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-10-02 07:04:51 +00:00
georgeandClaude Opus 5 e448045e52 ein-Wörter was quietly dropping three of the nine dative prepositions
Reported as "not a single dative in the ein-Wörter section". The engine did
still produce dative questions, but only from 6 of the 9 dative
prepositions, so the dative was thinner than the lesson claimed and leant
on Wechsel preps landing on dative rather than on dative preps themselves.

The indefOnly frame filter was throwing three away:

- bei — a regression from the "bei der Schwester" fix. That commit added
  `!f.enPossS` to the filter, which killed bei's only non-possessive frame
  outright. "bei einem Arzt" is perfectly good German and reads as "at a
  doctor's" in English, so there was never a reason to exclude it. von and
  zu survived but lost their person frames the same way, halving them.

- nach, seit — excluded as "definite idioms" because their frames carry an
  enDet override ("nach der Schule" is "after school", not "after the
  school"). But "nach einer Woche" and "seit einer Woche" are textbook
  German; the engine's own PREPS entry for seit uses "seit einer Woche" as
  its example.

The enDet exclusion existed to stop "während einer Woche", the sentence
reported a while back, so it could not simply be deleted. Frames can now
name `indefNouns`: the nouns that DO work once an ein-word is in front,
overriding the blanket noIndef flag for that frame only. nach and seit
re-admit Woche; während re-admits nothing, so "während einer Woche" stays
impossible. seit also gets enPrepIndef, because "seit einer Woche" is "FOR
a week" in English, not "since a week".

trotz stays out, but honestly: it only governs uncountables here ("trotz
eines Wetters" is not German), so it is removed from level 7's preps list
rather than left advertised and unreachable — the same treatment zu got in
the Dativ lesson.

The guard that should have caught this: a new test asserts that every level
generates every preposition it lists. A prep that can never be asked about
is now a test failure, not a silent hole. Two older tests that asserted the
buggy behaviour (that nach/seit never appear, that no noIndef noun ever
appears) are rewritten to assert the actual rule instead of being loosened.

Dative coverage in ein-Wörter: 6/9 prepositions before, 9/9 after. Case mix
47% dative / 32% accusative / 21% genitive. A round of 10 with no dative at
all went from 0.2% to 0.06%.

142 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-10-01 06:39:45 +00:00
georgeandClaude Opus 5 b9a4e16892 Add the Adjectives section, and explain why the noun itself changes
Two gaps, both visible from one screenshot of the Genitiv level.

1. Adjective endings were half-present and entirely untested. STRONG and
   WEAK existed in engine.js with correct data, but only as the S/W columns
   of a reference chart — nothing generated them, nothing tested them, and
   the MIXED table (the one "in seinem eigenen Kontext" actually needs) did
   not exist at all. There was not a single adjective in the codebase.

2. Nothing explained why "Regen" becomes "Regens" after trotz.

New Adjectives tab, six lessons plus Daily Practice:

  Schwach          after der/diese/jede — only -e and -en
  Gemischt         after ein/kein/mein — weak plus three exceptions
  Stark            no determiner — the adjective carries the case
  Welche Tabelle?  der-word, ein-word or nothing? (the meta-skill)
  Nach Präposition the full chain: preposition -> case -> table
  Alles gemischt   all three tables, including Genitiv

The three tables are built on one idea: the case has to be signalled once
in the noun phrase, and whoever can show it does. MIXED is therefore not
hand-typed — it is DERIVED from POSS_ENDINGS as "weak, except in the three
slots where the ein-word has no ending of its own, where it borrows the
strong one". A test asserts that relationship as well as all 48 cells, so
the rule cannot silently rot.

Two info panels:
- "Which ending table?" — the one-signal idea, the three tables, plus the
  two traps: strong genitive masc/neut is -en not -es (the noun already
  carries the -(e)s), and predicate adjectives take no ending at all.
- "Why Regens?" — appears wherever the case changes the noun itself:
  the -es/-s split, that feminines and plurals never change, the dative
  plural -n, and a note on the N-declension nouns Beug does not have yet.

Notable decisions:
- Nominative is now tested, for adjectives only. der gute Mann vs ein
  guter Mann is the clearest illustration of the whole weak/mixed split and
  it exists only in the nominative, so the app-wide "no nominative" rule is
  explicitly scoped to non-adjective levels rather than quietly dropped.
- The tab bar measured 369px of content in a 350px bar with four tabs, so
  a fifth could not fit. The row now scrolls instead of squeezing labels.
- Adjectives are gated like Possessives and Pronouns.

The corpus caught its keep again. Adjective sentences are enumerated into
corpus.txt keyed on the semantic pairing (frame x adjective x noun), which
keeps it at ~2000 lines instead of exploding over every determiner and
case. Reading it surfaced, and this commit fixes: "I live in the old lake"
(places split into buildings/settlements/landscape), "I play with the blue
sofa", "I like the bad kitchen" (frames that assert liking now exclude
negative adjectives), "I buy the good time" and "I cook with the good
money" (pools narrowed to what you can buy and cook with), grosser/kleiner
of a person rendering as "big/small" rather than "tall/short", and
"in dem alten Hotel" failing to fuse into "im alten Hotel".

130 tests pass; 12000 generated questions across all 30 levels.

Known gap, stated in the rule panel: Beug has no plural nouns, so the
plural column of all three tables is shown for reference but not drilled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-09-22 06:06:59 +00:00
georgeandClaude Opus 5 0dfc2955db Enumerate every possible sentence, review it, and lock it with a snapshot
"Wir wohnen bei der Schwester. / We live at the sister." was two bugs at
once, and both were the same shape: a template applied to a pairing nobody
had ever read.

Root cause: sentences are built combinatorially (frames x verbs x nouns x
subjects). The space is ~2900 sentences and only a sample had ever been
looked at, so every fix so far has been whack-a-mole on whichever bad one
happened to surface.

The fix is structural, not another special case:

- composeFrame() is now pure, and enumerateCorpus() walks every frame,
  verb, noun and case for every consumer (fill-the-article, ein-Woerter,
  possessive, Wechsel im Kontext, fused). src/corpus.txt is that full
  enumeration, checked in and reviewed.
- Two tests make it foolproof rather than decorative:
    * the snapshot test fails if generation drifts from corpus.txt, so no
      new sentence can reach a learner unreviewed. Regenerate with
      `npm run corpus` and the diff IS the review.
    * a coverage test runs the real generators over every sentence level
      and asserts each question it produces has a key in the corpus — so
      the file is provably the live output, not a parallel implementation.
  Two more assert no un-fused contraction and no German leaking into an
  English line.

Bugs the audit then found and fixed:

- zu/von/bei + a person means "at/to/from their PLACE". English needs the
  -'s ("at the doctor's"), and with a bare definite article German only
  allows roles you visit (beim Arzt), not family — "bei der Schwester" is
  wrong, "bei meiner Schwester" is right, so family frames are now
  possessive-only. Added Aerztin/Lehrerin/Freundin so the role case has a
  non-fusing feminine form.
- "zu dem Arzt", "in dem Haus": a fill-the-article sentence may no longer
  show a two-word form German obligatorily fuses. Those pairings belong to
  the Kontraktionen questions, which already teach them. "zu" therefore
  leaves the Dativ lesson entirely, the way "bis" already had.
- mein Mann / meine Frau mean my husband / my wife, not my man / my woman.
- "mein Maedchen" is not idiomatic and was reachable from the possessive
  templates; the coverage test caught it.
- You cannot drive into a kitchen, a cellar or a library: the "in" motion
  frame is split into drivable places and on-foot-only places.
- "stellt den Stuhl an die Wand" is "against the wall", not "to the wall";
  haengen/schreiben/kleben take "on"; wohnen am See is "by the lake".

Also: questions now carry the verb and case they were built from, the
article redraw is depth-bounded so a data mistake degrades to one odd
question instead of hanging, and level 3's advertised prep list matches
what it can actually generate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-09-18 07:24:29 +00:00
georgeandClaude Opus 5 1d5402edc7 Kontraktionen: ask it backwards, as a two-tap word cloud
"hinter + das = ?" with options am / im / uebers / hinters was no test at
all — only one option could start with "hinter", so you never had to know
which article was fused in. The whole level was answerable on the prefix.

Flipped it. The fused form is given and you take it apart:

- The form is shown large ("im"), and you pick the preposition from a word
  cloud, then the article from a second one. Graded on both parts.
- The preposition step is a real question for the truncated forms — im vs
  am, vom, zum/zur — which are exactly the ones that get mixed up.
- The article step is the point: -s means das, -m means dem, -r means der.
  Distractors are always real articles (das/dem/der/die/den), never whole
  contraction forms, so elimination on shape no longer works.
- Feedback names which half was wrong and states the ending rule, then
  highlights the article in the definite-article table.
- The chosen preposition chip is tappable to change before committing;
  backspace does the same on desktop. On reveal the line resolves to the
  correct pair.

Tests: the old flaw is pinned shut (nothing on offer is ever a contraction
form), both choice sets are checked, and a data test asserts the -s/-m/-r
ending predicts the article for all 16 contractions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-09-17 07:05:58 +00:00
georgeandClaude Opus 5 047454f23f Wechsel im Kontext: blank the article, make the verb decide, add the rule
The level showed the article already in the sentence ("unter dem Stuhl"),
so it was answerable by recognising "dem" — it never tested wohin/wo at
all. And the frame pool was tiny: one cat, one lamp, eight nouns.

- The article is now blanked. The only evidence is the verb and whether it
  has a direct object, which is the actual skill. The solved sentence is
  revealed on answer, contracted where German really contracts (ins Regal,
  am Bahnhof), with a note naming the fusion.
- Frames rebuilt around what decides the case: 27 new nouns, 18 new verbs,
  and every frame one-way so no verb+noun pairing licenses both readings.
- Motion verbs now appear on the DATIVE side on purpose — schwimmen im See,
  tanzen in der Kueche, spielen auf der Strasse — because "is it moving?" is
  the wrong test and that is the main thing learners get wrong.
- The put/be pairs are drilled properly: legen/liegen, stellen/stehen,
  setzen/sitzen, plus haengen and stecken, which keep the same present tense
  either way so only the direct object tells you.
- ankommen added as the lexical exception: Dativ despite obvious arrival.
- New "Wohin oder wo?" info panel: the rule, the before-and-after test, the
  movement trap, the verb-pair table and the exception. Reachable from the
  practice screen, from the answer feedback, and from Charts.
- Feedback now explains the specific sentence rather than restating theory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-09-17 06:55:26 +00:00
george f39db3cb86 One-tap grids everywhere, hideable translation/hints, cleaner top and bottom bars
- Possessives, pronouns, and Kontraktionen switch from typed input to the
  same 4-choice tap grid as articles. Possessive choices all share the
  question's stem; pronoun choices lead with the pronoun's own case forms.
- The translation line is hideable everywhere (shows by default, hidden
  state persists like the other hints, revealed again on a wrong answer).
  The possessive and pronoun hint lines get hide toggles too.
- Header: Charts moves out; One-tap becomes a pill — plain grey when off,
  green outlined when on. More margin below the header across the board.
- Bottom bar: progress dots centered with 'n/10 · level name' underneath,
  outlined Charts button (capital C) pinned bottom-right.
- Touch devices hide the 1-4/enter/esc keyboard chips; answer buttons keep
  their full size via min-heights so tap targets stay big.
2026-07-21 07:27:32 +00:00
george 1444814062 Fix ungrammatical ein-word sentences and the a/the translation mismatch
The ein-Wörter level reused sentence frames written for definite articles
and blindly swapped in the ein/eine table, producing non-German like
'trotz eines Wetters' and 'während einer Woche' while the English still
said 'the'.

- Nouns gain a noIndef flag (mass nouns: Wetter, Regen, Musik, Arbeit;
  definite-idiom temporal/kinship nouns: Nacht, Woche, Vater, Mutter,
  Bruder, Schwester, Tochter). They are never paired with an ein-word.
- Frames with a custom English determiner (nach → 'after school',
  seit → 'since that week') are definite idioms and are skipped for
  ein-word questions.
- genArticleQ redraws prepositions whose frames are entirely ruled out
  (trotz has only mass nouns), instead of generating a bad sentence.
- Indefinite translations now use a/an to match the German article;
  definite levels keep 'the'.
- Tests pin all of this down (75 passing).
2026-07-21 07:15:45 +00:00
george c4c28f46ed Daily Practice per section: mixed drills, stricter unlock, tick-and-skip one-tap
- One-tap mode no longer shows the answer when you're right: a green tick
  flashes and it skips straight to the next question. Wrong answers still
  show the full correction and now wait for an explicit Next tap.
- Every tab (Prepositions, Articles, Possessives, Pronouns) gets its own
  pinned Daily Practice card. Sessions mix questions from every lesson in
  the tab via a shuffle-bag over lesson ids.
- Daily Practice is locked until every lesson in its section has been
  passed (one full round at 70%); the locked card shows x/y progress.
- Finishing a daily session offers 'Continue: <section> Daily Practice'
  cycling to the next tab with an unlocked daily, with play again and
  back to menu as secondary options.
2026-07-21 07:09:40 +00:00
fightme-cmdandClaude Fable 5 80f7e0eca2 chore: verify cloud push -> auto-deploy pipeline
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 10:02:55 +02:00
george f567555876 Feedback: show the full declension table, not one row
Every answer card now renders the complete 4×4 declension grid instead
of a single case row. The correct cell is highlighted green; on a wrong
answer, the cell matching what you typed is highlighted red, so you see
both your pick and the right one in context.

- FillFeedback (articles + possessives): full table, correct + wrong
  highlighting within the noun's gender column.
- ChunkFeedback + ContractionFeedback: contraction questions now show
  the definite-article table too, not just the fused answer.
- PronounFeedback: correct cell green, your wrong pick red.
2026-07-17 07:42:56 +00:00
george aa781b72bb Add pinned Daily Practice card + one-tap mode
- Daily Practice (all 31 prepositions) is pinned to the top of the
  Prepositions tab once the "Alle Präpositionen" lesson is passed
  (>=70% over a full round).
- One-tap mode: after answering, the app flashes right/wrong and
  auto-advances. Always on in Daily Practice; a persisted "One-tap"
  toggle in the practice header enables it for any other level.
2026-07-17 07:23:36 +00:00
george cc19ea09e0 Add Daily Practice level (all 31 prepositions)
New pseudo-level (id 10.5) that drills all 31 prepositions. App.jsx
pins it to the top of the Prepositions tab once the "Alle
Präpositionen" lesson is completed, and defaults it to one-tap mode.
2026-07-17 07:17:13 +00:00
fightme-cmdandClaude Opus 4.8 cd0b6c800d Remove nominative testing, add hideable hints and week streak dots
- Remove the Nominativ level and drop nominative-case testing from mixed/
  possessive/pronoun levels (nominative kept only in reference charts)
- Add persistent hide/show toggles on the two giveaway hint lines in
  practice (noun gender, case/preposition info); state persists in localStorage
- De-spoil "Wechsel im Kontext": remove motion/location hint from answer
  buttons; put sentence translation + preposition meaning behind a toggle
- Add a 7-day practice-history dot strip beside the streak counter
- Extend engine + tests (61 vitest tests)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:36:08 +02:00
fightme-cmdandClaude Fable 5 d30e213104 Add testable question engine, streak fixes, mobile case grid
- New question-generation engine (shuffle-bag preposition selection,
  per-preposition sentence frames, genitive noun forms) in src/engine.js
- Streak fixes: local-date handling, lapse display
- Footer copy: "Get in touch"
- 2x2 mobile case grid
- Vitest suite (60 tests) covering the engine

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 14:12:42 +02:00
fightme-cmd d30350e7e2 Address feature request emails 2026-07-05 13:08:27 +02:00
fightme-cmd 6805aa5314 Hide footer during practice 2026-07-05 13:01:01 +02:00
fightme-cmd 44fb800b4b Omit payment links when disabled 2026-07-05 12:51:57 +02:00
fightme-cmd 49dc1de769 Add payment launch mode flag 2026-07-05 12:48:27 +02:00
fightme-cmd 28e8ade05f Refine paywall presentation and homepage copy 2026-07-05 10:36:59 +02:00
fightme-cmd 49f78ef6b0 Add premium paywall and core German drills 2026-07-05 10:31:53 +02:00
fightme-cmdandClaude Opus 4.8 960f63dc21 Add Dockerfile + nginx for Coolify deployment
Multi-stage build (node -> nginx) serving the Vite static output on
port 80, matching the dockerfile build pack used for other apps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:35:06 +02:00
fightme-cmdandClaude Opus 4.8 a198219bc0 Initial commit: Deklinationstrainer German declension trainer
Vite + React app for daily German declension practice with
localStorage-backed progress. Includes Coolify deploy instructions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:00:57 +02:00