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
2026-07-05 12:48:27 +02:00
2026-07-05 12:48:27 +02:00

Deklinationstrainer

A daily-practice German declension trainer. Self-hosted so your progress actually saves.

Deploy to Coolify

One-time setup

  1. Push these files to a git repo (GitHub / Gitea / whatever Coolify is connected to).

  2. In Coolify, create a new Application:

    • Source: your git repo
    • Build pack: Nixpacks (auto-detects Vite) or Static
    • Build command: npm run build
    • Publish directory: dist
    • Port: not needed for static output (Coolify serves it directly)
  3. Add a domain (e.g. deklination.yourdomain.com). Coolify provisions HTTPS via Caddy/Traefik automatically.

  4. Deploy. Visit the URL on any device.

Local dev

npm install
npm run dev    # http://localhost:5173
npm run build  # outputs to ./dist

On iPhone

Open the URL in Safari, tap Share → Add to Home Screen. It opens fullscreen like a native app, and progress saves in localStorage across reboots, sessions, and app closes.

Storage

The app tries window.storage first (Claude artifact environment), then falls back to localStorage. When self-hosted, only localStorage runs, which iOS Safari persists reliably.

To wipe progress: open browser devtools → Application → Local Storage → delete the gd-v10 key. Or on iPhone: Settings → Safari → Advanced → Website Data → search your domain → delete.

Stripe checkout

Beug uses public Stripe Checkout or Payment Link URLs, supplied through environment variables. Do not put Stripe secret keys in this Vite app.

Payments are behind a launch-mode flag:

VITE_PAYMENTS_ENABLED=false

When VITE_PAYMENTS_ENABLED is absent or false, the app runs in Reddit/free mode: all lessons are usable, paid rows are not blurred, and the paywall/pricing screen is hidden. The Stripe URLs can stay configured in the environment so billing can be switched back on later with one env change.

To turn payments back on, set:

VITE_PAYMENTS_ENABLED=true

Create one product in Stripe, for example Beug Premium, with three prices:

  • Weekly subscription: $2.99 every week
  • Yearly subscription: $9.99 every year
  • Lifetime access: $19.99 one-time payment

Create a Payment Link or Checkout link for each price. Set the success redirect URL to:

https://beug.george1.dev/?checkout=success

Set the cancel URL to:

https://beug.george1.dev/

Then add these public URLs in the deployment environment:

VITE_STRIPE_YEARLY_URL=https://buy.stripe.com/...
VITE_STRIPE_LIFETIME_URL=https://buy.stripe.com/...
VITE_STRIPE_WEEKLY_URL=https://buy.stripe.com/...

The current v1 unlock is intentionally simple: after Stripe redirects back with ?checkout=success, Beug stores the unlock in this browser's localStorage. This avoids accounts for the tiny-app version, but it is a soft paywall. A stricter version needs a small backend, Stripe webhooks, and account or email-based entitlement lookup.

Future prompt to re-enable payments

Turn payments back on for Beug. Set VITE_PAYMENTS_ENABLED=true in Coolify, keep the three existing Stripe Payment Link env vars, redeploy the app, and verify that unpaid users see the soft paid lesson blur, that charts remain visible, and that tapping a paid lesson opens the pricing screen with Weekly, Yearly, and Lifetime plans.
S
Description
No description provided
Readme
4 MiB
Languages
JavaScript 99.7%
HTML 0.2%