E2E coverage audit

What the browser automation in e2e/ walks through, set against every production route and data-testid hook, and what to do about the gaps. Refreshed 2026-10-22 and re-counted at the merge of main on 2026-10-23 (the first snapshot was PR #117, 2026-10-17, hand-written: git show c955cd1e:docs/e2e-audit.md). How the suite works: Testing locally.

The numbers below the line are generated: python3 scripts/e2e-audit.py --write refreshes them in place. Re-run it before acting on a count.

Findings

  • 33 % of the keyless tests never run in CI. The exact-spec job in .github/workflows/smoke.yml passes a fixed list of 18 files (specs: specs/exact.spec.ts …); the smoke job passes the folder specs/smoke. A spec file not on the list runs only when someone runs scripts/e2e.sh by hand. Missing today, 15 files and 79 tests, none of them needing a key: ai-off, avatar-colour, course-report, dev-payments, editor-errors, home, hosted-ai, lessons, profile-autosave, prompt-limits, review-ai-changes, review-and-revise, revise, share, subscription. That is most of the course editor (lessons, revise, review), the home page, sharing, profile autosave and the paid plans. create joined the list in #422, docs and course-copy since, one file at a time: new spec files keep landing outside it. CLAUDE.md says “the exact and smoke e2e specs run on every PR”, which reads as all of them.
  • The learner’s paths are covered, in CI. The smoke specs play every mode, question type, Sprint exercise kind and game on the test course, right and wrong, as a fresh account, and check /api/progress after; Spanish lesson 1 and French get the same in each mode.
  • Nothing with a model in the loop runs unattended. The 14 Jev journeys, 2 judged specs, 4 personas (scripts/e2e-explore.sh) and the course playthroughs need TYPESAFE_API_KEY and no workflow has a schedule. plagiarism.yml already reads that secret.
  • Routes. Of 137 production routes, 3 are reached by no flow: POST /login/code/resend, POST /login/code/cancel, POST /profile/two-factor/code. The script lists nine more that are reached in ways a grep cannot see: GET /theme.css and GET /courses/themes.css (stylesheet links the pages load; custom-themes.spec.ts checks the first), POST /profile/google/confirm (a hidden form submitted by profile-delete-google, in google-signin.spec.ts), GET /profile/export/download (the link in the export e-mail, in smoke/export.spec.ts), /courses/shared/{token} (from the invitation e-mail, in share.spec.ts, local only), POST /api/courses/{id}/lessons (the editor’s autosave, the URL built by concatenation in static/courses.js), /pictures/{*path} (a course’s SVG pictures), /play/conjugation/play (drill-setup-start), /auth/google/callback (the dev Google stand-in redirects there).
  • Testids. 69 % of the hooks are referenced. The learner-facing gaps are the home page’s submenu links (home-play-*, home-ai-*, home-guide-next), the settings of Numbers by ear, Listening and Date Dash (rate, range, replay, reveal), the tutor’s provider picker and no-key state, and the Matching leaderboard. Most of the rest are -form wrappers, dialogs’ close buttons and the dev tools.

Flows by area

The per-test list is below the line. In short:

AreaSpecs (CI unless marked)
Sign-up, sign-in, e-mail, 2FAsmoke/verify-email, google-signin, mail, two-factor, authenticator, terms, i18n
Learning, every mode and gamesmoke/happy-paths, smoke/complete-test-course, smoke/test-course-games, smoke/wrong-answers, smoke/wrong-answers-games, smoke/start-level, smoke/french, smoke/daily-goal, smoke/motivator*, modals, lesson-tables
Home pagehome (local only)
Profileprofile-autosave, avatar-colour, ai-off (all local only); custom-themes, profile-nav; smoke/privacy, smoke/export (export, delete)
Plans and paymentssubscription, dev-payments, hosted-ai (all local only)
Generating a courseexact, pathways, dev-ai, create; prompt-limits (local only)
Editing a courseeditor-autosave, forms, course-copy; lessons, editor-errors, course-report, revise, review-ai-changes, review-and-revise (local only)
Sharingshare (local only)
Docs, self-hosting, layoutsmoke/self-host, smoke/phone-width, docs
Jev, with a keyjourneys (14 goals from e2e/journeys.ts), judged (2), explore (a persona from e2e/personas/), record (drafts a spec)

Journeys (e2e/journeys.ts): open a lesson, find the generate form, start Sprint on a phone (impatient), find “ser vs estar” (classroom), visit every nav section, share a course, add a lesson with AI, leave an AI lesson out in the review, edit a lesson as a form, add a lesson to a unit by hand, name a new course, turn off AI features, start at A2, change the level.

Personas (e2e/personas/): accurate (reads everything, Study mode, patience 20), classroom (wants one topic now, 10), impatient (a teenager on a phone in Sprint, 6), testing-bot (sees only markup, 12).

Recommendations

  1. Run every keyless spec in CI. Pass specs (the whole folder) to the exact-spec job instead of a list; the Jev specs already skip themselves without a key (--grep-invert @jev stays). Expect some of the 79 to fail at first: they have not run since they were written. Fix or delete each, then the list can never go stale again.
  2. Make the docs say what CI runs. One line in CLAUDE.md and docs/testing.md: CI runs every file under e2e/specs/ except the Jev ones (after 1), or, until then, only the files listed in smoke.yml.
  3. Cover the three unreached routes with exact specs: resend and cancel on /login/code, and the profile’s two-factor code change.
  4. Spec the learner surfaces with no testid referenced: home submenu links, game settings (rate, range, replay), tutor without a key, the Matching leaderboard.
  5. Schedule the model-driven checks: a nightly workflow that runs scripts/e2e-jev.sh and one persona when TYPESAFE_API_KEY is set, reporting, not gating.

Generated numbers

WhatCount
Spec files91 (71 run in CI)
Tests387: 287 run in CI, 89 keyless but local only, 11 need a Jev key
Production routes (src/routes/mod.rs, no /dev)182: 103 named by an e2e file, 66 more reached through a page, 13 not reached
data-testid hooks (templates, static JS)1630, 1284 referenced (78 %)
Jev journeys / personas14 / 4

Spec files

FileTestsCINeeds a key
specs/accessibility.spec.ts7yes
specs/ai-off.spec.ts2no
specs/authenticator.spec.ts4yes
specs/avatar-colour.spec.ts3no
specs/consent.spec.ts2yes
specs/content-check.spec.ts3yes
specs/content-reports.spec.ts1yes
specs/content-review.spec.ts2yes
specs/course-card-stats.spec.ts2yes
specs/course-copy.spec.ts5yes
specs/course-report.spec.ts3no
specs/course-table.spec.ts6yes
specs/course-translate.spec.ts2yes
specs/create.spec.ts11yes
specs/custom-themes.spec.ts6yes
specs/dev-ai.spec.ts11yes
specs/dev-payments.spec.ts1no
specs/docs.spec.ts8yes
specs/editor-autosave.spec.ts5yes
specs/editor-errors.spec.ts4no
specs/editor-fixes.spec.ts3no
specs/email-owner.spec.ts2yes
specs/exact.spec.ts29yes
specs/explore.spec.ts3noyes
specs/forms.spec.ts4yes
specs/google-signin.spec.ts11yes
specs/home.spec.ts4no
specs/hosted-ai.spec.ts6no
specs/i18n.spec.ts2yes
specs/journeys.spec.ts2noyes
specs/judged.spec.ts3noyes
specs/lesson-conversations.spec.ts1yes
specs/lesson-duplicate.spec.ts1yes
specs/lesson-editor.spec.ts10yes
specs/lesson-header.spec.ts1yes
specs/lesson-tables.spec.ts2yes
specs/lessons.spec.ts17no
specs/mail.spec.ts7yes
specs/mistakes.spec.ts1yes
specs/modals.spec.ts5yes
specs/moderation.spec.ts4yes
specs/owner-resets.spec.ts3yes
specs/pathways.spec.ts7yes
specs/privacy-requests.spec.ts2yes
specs/profile-autosave.spec.ts8no
specs/profile-nav.spec.ts1yes
specs/prompt-limits.spec.ts5no
specs/record.spec.ts3noyes
specs/release.spec.ts2yes
specs/review-ai-changes.spec.ts14no
specs/review-and-revise.spec.ts4no
specs/revise.spec.ts10no
specs/share-edges.spec.ts9yes
specs/share-sign-in.spec.ts3yes
specs/share.spec.ts2no
specs/smoke/complete-test-course.spec.ts3yes
specs/smoke/daily-goal.spec.ts1yes
specs/smoke/drill-first-set.spec.ts2yes
specs/smoke/export.spec.ts2yes
specs/smoke/french.spec.ts6yes
specs/smoke/happy-paths.spec.ts10yes
specs/smoke/mini.spec.ts6yes
specs/smoke/motivator-reward.spec.ts1yes
specs/smoke/motivator-skc.spec.ts1yes
specs/smoke/motivator-social.spec.ts1yes
specs/smoke/motivator-xp.spec.ts1yes
specs/smoke/motivator.spec.ts1yes
specs/smoke/password-reset.spec.ts2yes
specs/smoke/phone-width.spec.ts2yes
specs/smoke/placement.spec.ts2yes
specs/smoke/practice-empty-check.spec.ts1yes
specs/smoke/practice-keys.spec.ts2yes
specs/smoke/practice-nothing-yet.spec.ts2yes
specs/smoke/practice-result-order.spec.ts1yes
specs/smoke/practice-summary.spec.ts2yes
specs/smoke/privacy.spec.ts3yes
specs/smoke/self-host.spec.ts3yes
specs/smoke/skc-shop.spec.ts2yes
specs/smoke/sprint-check-error.spec.ts2yes
specs/smoke/sprint-ladder.spec.ts2yes
specs/smoke/sprint-sort.spec.ts2yes
specs/smoke/start-level.spec.ts4yes
specs/smoke/test-course-games.spec.ts7yes
specs/smoke/verify-email.spec.ts3yes
specs/smoke/wrong-answers-games.spec.ts5yes
specs/smoke/wrong-answers.spec.ts7yes
specs/subscription.spec.ts3no
specs/term-flag.spec.ts3yes
specs/terms.spec.ts5yes
specs/two-factor.spec.ts4yes
specs/word-list.spec.ts3yes

Routes reached only through a page

No e2e file names them; a spec drives the form, link (its testid) or script that calls them.

  • POST /api/practice/finish: static/quiz.js
  • POST /api/listening/check: static/listening.js
  • POST /api/listening/finish: static/listening.js
  • POST /courses/{id}/file/delete: course-edit-file-delete-form
  • POST /courses/{id}/cancel: course-ai-modal-cancel-form, course-edit-cancel-form, courses-cancel
  • POST /courses/{id}/undo: course-undo-form
  • POST /courses/{id}/redo: course-undo-form
  • POST /courses/{id}/report: content-report-form
  • POST /courses/{id}/share/remove: course-share-remove-form
  • GET /cookies: footer-cookies
  • GET POST /legal/copyright: copyright-form, terms-copyright-link
  • GET POST /terms/accept: terms-accept-check
  • POST /terms/dismiss: terms-notice-dismiss
  • GET POST /password/change: password-change-form
  • POST /api/sprint/check: static/sprint.js
  • POST /api/sprint/finish: static/sprint.js
  • POST /api/guide/step: static/guide.js
  • POST /api/guide/restart: static/guide.js
  • POST /api/games/matching/start: static/matching.js
  • POST /api/games/matching/finish: static/matching.js
  • POST /api/games/conjugation/deal: static/conjugation.js
  • POST /api/games/conjugation/check: static/conjugation.js
  • POST /api/games/conjugation/finish: static/conjugation.js
  • POST /api/games/numbers/finish: static/numbers.js
  • POST /api/games/dates/finish: static/dates.js
  • POST /api/vocabulary/review: static/vocab.js
  • GET POST /login/code: login-code-input
  • POST /login/code/resend: login-code-cancel
  • GET /login/code/status: login-code
  • POST /login/code/cancel: login-code-cancel
  • POST /waitlist: waitlist-form{{ suffix }}
  • POST /logout: account-deletion-logout, nav-logout-form, password-change-logout, terms-gate-logout
  • POST /account/deletion/cancel: account-deletion-cancel
  • POST /profile/password: profile-password-form
  • POST /profile/sessions/end-others: profile-sign-out-others-form
  • POST /profile/google/link: profile-google-link-form
  • POST /profile/google/unlink: profile-google-unlink-form
  • POST /profile/api-keys/remove: profile-api-key-remove-form
  • POST /profile/ai: static/profile.js, consent-ai-form
  • POST /profile/own-keys: static/profile.js, profile-own-keys-form
  • POST /profile/export: profile-export-form
  • GET POST /profile/export/download: profile-export-download-form
  • POST /profile/delete: profile-delete-form
  • POST /profile/verify-email/resend: verify-email-resend
  • POST /profile/phone/send: phone-locked-dialog
  • POST /profile/phone/verify: phone-verify-form
  • POST /profile/two-factor/totp/confirm: two-factor-totp-code
  • POST /superuser/release/invite: release-invite
  • POST /superuser/invites: superuser-invite-form
  • POST /superuser/invites/revoke: superuser-invite-revoke
  • POST /superuser/terms-announce: superuser-terms-form
  • POST /superuser/reports/forward: superuser-report-forward
  • POST /superuser/reports/resolve: superuser-report-resolve
  • POST /superuser/courses/put-back: superuser-put-back-form
  • POST /superuser/courses/override: superuser-override-form
  • POST /superuser/courses/clear-override: superuser-clear-override-form
  • POST /superuser/courses/delete: superuser-course-delete-form
  • POST /superuser/courses/email-owner: superuser-email-owner-form
  • POST /superuser/unsuspend: superuser-account-unsuspend
  • POST /superuser/privacy/export: superuser-privacy-export-form
  • POST /superuser/privacy/forget: superuser-privacy-forget-form
  • POST /superuser/unblock-ip: superuser-unblock
  • POST /api/lesson-check: static/lesson-check.js
  • POST /billing/cancel: cancel-form
  • POST /billing/checkout: plans-form
  • POST /billing/portal: profile-billing-portal-form

Routes no e2e flow reaches

Not named by an e2e file, and no driven form, link or script calls them.

  • GET /courses/themes.css
  • POST /api/courses/{id}/preview-answer
  • GET /pictures/{*path}
  • GET /theme.css
  • GET /badges/{file}
  • GET /waitlist/confirm
  • GET /account/deletion
  • GET /auth/google/callback
  • POST /profile/google/confirm: called from templates/profile.html
  • POST /profile/two-factor/code: called from templates/profile.html
  • POST /superuser/release/switch: called from templates/superuser_release.html
  • POST /superuser/release/invite-next: called from templates/superuser_release.html
  • GET /manifest.webmanifest: called from templates/base.html, templates/sprint.html, templates/sprint_play.html, templates/tutor.html

Testids per template

FileHooksReferencedNot referenced
templates/dev_ai.html4214dev-ai-base, dev-ai-clear, dev-ai-clear-inject, dev-ai-detail, dev-ai-fixture, dev-ai-fixture-edited, dev-ai-fixture-open, dev-ai-fixtures, dev-ai-fixtures-info, dev-ai-fixtures-info-dialog, dev-ai-hold-info, dev-ai-hold-info-dialog (+16)
templates/home.html5125home-ai-api-keys, home-ai-lessons, home-ai-open, home-ai-recommended, home-guide-next, home-guide-open, home-play-dates, home-play-drill, home-play-flashcards, home-play-matching, home-play-numbers, home-play-open (+14)
templates/superuser_release.html246release-back, release-counts, release-empty, release-error, release-inbox, release-info, release-info-dialog, release-invite-next, release-invite-next-form, release-invite-next-n, release-list-card, release-next (+6)
templates/dev_payments_state.html214dev-payments-cancel-at-end, dev-payments-customer, dev-payments-customers, dev-payments-mark-paid, dev-payments-pay-next, dev-payments-payment, dev-payments-payment-status, dev-payments-payments, dev-payments-refund, dev-payments-resume, dev-payments-session, dev-payments-session-open (+5)
templates/numbers.html237numbers-again, numbers-card, numbers-form, numbers-max, numbers-min, numbers-novoice, numbers-range, numbers-rate, numbers-replay, numbers-reveal, numbers-settings, numbers-setup (+4)
templates/dev_box.html2612dev-box-info, dev-box-info-dialog, dev-box-off, dev-inbox-clear, dev-inbox-filter, dev-inbox-off, dev-inbox-to, dev-mail-providers, dev-mail-send-form, dev-phone-clear, dev-phone-off, dev-phone-on (+2)
templates/profile.html185172email-no-mail-close, email-no-mail-dialog, email-unverified-close, two-factor-confirm-code, two-factor-confirm-form, two-factor-confirm-google, two-factor-confirm-google-done, two-factor-confirm-method, two-factor-confirm-send, two-factor-confirm-sent, two-factor-confirm-with-google, two-factor-totp-error (+1)
templates/listening_play.html196listen, listen-empty, listen-form, listen-novoice, listen-rate, listen-replay, listen-reveal, listen-setup, listen-slow, listen-status, listen-stop, listen-unsupported (+1)
templates/dev_payments.html110dev-payments-api-base, dev-payments-clear, dev-payments-info, dev-payments-info-dialog, dev-payments-log, dev-payments-mode, dev-payments-notice, dev-payments-off, dev-payments-reset, dev-payments-state, dev-payments-webhook-url
templates/dev_inbox_message.html113dev-inbox-back, dev-inbox-headers, dev-inbox-link, dev-inbox-message, dev-inbox-message-archive, dev-inbox-message-delete, dev-inbox-raw, dev-inbox-text
templates/dev_jobs.html102dev-jobs-attempts, dev-jobs-empty, dev-jobs-error, dev-jobs-queue-fail, dev-jobs-row, dev-jobs-run-now, dev-jobs-state, dev-jobs-table
templates/dev_mail_message.html124dev-mail-back, dev-mail-detail, dev-mail-error, dev-mail-headers, dev-mail-html, dev-mail-message-archive, dev-mail-message-delete, dev-mail-text
templates/dev_text_message.html157dev-text-back, dev-text-compose-error, dev-text-compose-form, dev-text-error, dev-text-headers, dev-text-thread, {{ prefix() }}-message-archive, {{ prefix() }}-message-delete
templates/lesson.html4032lesson-check-form, lesson-head, lesson-mastery, lesson-pass, lesson-practise, lesson-prerequisite, lesson-read-form, lesson-test
templates/dates.html169dates-again, dates-card, dates-ear, dates-hud, dates-replay, dates-score, dates-setup
templates/dev_payments_entry.html81dev-payments-back, dev-payments-entry-detail, dev-payments-entry-request, dev-payments-entry-resend, dev-payments-entry-response, dev-payments-entry-result, dev-payments-entry-what
templates/conjugation_setup.html137drill-setup-count, drill-setup-empty, drill-setup-form, drill-setup-hide, drill-setup-locked-lesson, drill-setup-locked-list
templates/matching.html115matching-again, matching-board-area, matching-leaderboard, matching-leaderboard-row, matching-status, matching-timer
templates/dev_ai_held.html94dev-ai-answer-error, dev-ai-answer-form, dev-ai-held-empty, dev-ai-reply, dev-ai-request-state
templates/dev_ai_call.html83dev-ai-back, dev-ai-call-detail, dev-ai-call-headers, dev-ai-call-purpose, dev-ai-call-result
templates/lookup.html105lookup-back, lookup-collected, lookup-hits, lookup-more, lookup-term
templates/register.html116register-dev, register-dev-login, register-form, register-invite, register-owner-note
templates/reset.html138reset-again, reset-ask-form, reset-choose-form, reset-error, reset-login
templates/tutor.html127tutor-add-key, tutor-exit, tutor-form, tutor-provider-form, tutor-score
templates/dev_authenticator.html51dev-authenticator-add-form, dev-authenticator-info, dev-authenticator-info-dialog, dev-authenticator-list
templates/dev_payments_checkout.html106dev-checkout-closed, dev-checkout-email, dev-checkout-form, dev-checkout-product
templates/dev_payments_log.html51dev-payments-empty, dev-payments-entry-open, dev-payments-entry-status, dev-payments-resend
templates/dev_payments_portal.html84dev-portal-cancel, dev-portal-cancel-now, dev-portal-empty, dev-portal-update-card
templates/docs.html95docs, docs-llms, docs-nav, docs-page-link
templates/lessons.html95lessons-bar, lessons-item-practise, lessons-list, lessons-shown
templates/listening.html40listening-lesson, listening-lesson-mastery, listening-lesson-play, listening-mixed
templates/login.html1612login-dev, login-dev-forget-google-form, login-dev-login, login-privacy-link
static/tutor.js106tutor-buy-credits, tutor-out-of-ai, tutor-own-key, tutor-upgrade
static/devbox.js1714dev-jobs, dev-text-message, login-code-device
templates/dev_ai_fixture.html74dev-ai-fixture-form, dev-ai-fixture-key, dev-ai-fixture-save
templates/conjugation.html129drill-again, drill-again-misses, drill-form
templates/generate_form.html2219generate-instructions, generate-kind, generate-depth
templates/google_confirm.html63google-confirm, google-confirm-cancel, google-confirm-form
templates/guide.html63guide-continue, guide-stages, guide-three-star
templates/landing.html41landing-course, landing-privacy, landing-terms
templates/register_closed.html30register-closed, register-closed-login, register-closed-message
templates/terms.html52terms-privacy-link, terms-raw, terms-version
templates/verify_email.html74verify-email, verify-email-continue, verify-email-resend-form
templates/dev_ai_rows.html53dev-ai-call-outcome, dev-ai-empty
templates/games.html31games-list, games-play
templates/lang_picker.html31lang-picker-form, lang-picker-submit
templates/login_code.html97login-code-form, login-code-notice
templates/login_code_device_rows.html53login-code-device-code, login-code-device-empty
templates/map.html53map-lesson-link, map-practice
templates/matching_pick.html20matching-board, matching-board-play
templates/terms_accept.html64terms-accept-error, terms-accept-form
templates/terms_notice.html53terms-notice-change, terms-notice-form
templates/dev_authenticator_rows.html76dev-authenticator-account
templates/error.html54error-message
templates/goal_message.html10goal-message
templates/history.html43history-games-scroll
static/lessons.js32lessons-to-top
static/login_code.js21login-code-resend
templates/login_code_device.html21login-code-device-off
templates/mistakes.html98mistakes-scroll
templates/vocabulary.html43vocabulary-study-all
templates/vocabulary_lesson.html43vocabulary-study
templates/account_deletion.html66
templates/ai_form.html1212
templates/ai_out.html44
static/autosave.js33
templates/base.html1717
static/profile.js3737
templates/content_report_dialog.html77
templates/course_shared_dot.html44
templates/copyright.html1313
static/courses.js2525
templates/ai_job_modal.html1414
templates/course_edit.html2121
templates/course_file.html66
templates/lessons_modes.html11
static/lesson-editor.js33
templates/lessons_panel.html5858
templates/lessons_row.html77
static/word-list.js1010
templates/undo_revision.html55
templates/course_report.html77
templates/course_review_modal.html2727
templates/course_share_modal.html1313
templates/courses.html4141
templates/course_copy.html22
templates/course_settings.html1919
static/course-settings.js88
static/placement.js44
templates/course_wizard.html33
templates/course_stat.html88
templates/course_translate.html1111
templates/dev_box_kinds.html55
static/conjugation.js33
templates/flag_matches.html66
templates/vocab_study.html88
static/lesson-check.js11
templates/lesson_pager.html33
templates/masked_word.html11
templates/nav_play.html1212
templates/nav_study.html1111
templates/pager.html44
templates/password_change.html99
templates/placement_test.html1010
templates/proxy_signin.html44
templates/quiz.html1111
static/quiz.js22
templates/register_sent.html22
static/say.js11
templates/self_host.html1515
templates/shop.html1010
templates/sprint.html2424
static/sprint.js55
templates/sprint_play.html1313
templates/stage.html1212
static/guide.js11
templates/superuser.html8686
templates/superuser_courses.html7878
templates/superuser_privacy.html1919
templates/waitlist.html1010
templates/dev_box_rows.html55
templates/dev_google.html22
templates/pick_tabs.html22
templates/terms_check.html55
templates/ai_switch.html44

Every test, by file

Titles as Playwright lists them (from the source: run npm ci in e2e/ for loops expanded).

  • specs/accessibility.spec.ts (7, CI): pages fit a phone at 200 % text size (#342) · the root font size follows the browser’s text size (#342) · the system’s reduce-motion setting stops animations and transitions (#342) · the first Tab reaches a skip link that jumps to the content (#342) · /sprint has one page heading (#342) · status pills and links in text have 4.5:1 contrast (#342) · inline code keeps its size with the % root font size (#342)
  • specs/ai-off.spec.ts (2, local only): the sign-up box hides every AI feature, and the profile turns them back on · the profile’s switch is a negative, hides the AI features at once, and asks first when a key would be deleted
  • specs/authenticator.spec.ts (4, CI): a pasted otpauth link is added, shows the RFC 6238 code with a countdown, copies it, and is removed (#291) · the next code shows by itself when the period runs out, and a link’s settings are honoured · a scan link opens the form filled in, a setup key works too, and a bad key says why · when the list goes empty, an empty add box offers the example account again (#291)
  • specs/avatar-colour.spec.ts (3, local only): a colour is picked in the course’s settings and the corner wears it in the course · a theme picked in the settings adds scenes of its world, offered only while it is picked · a theme brings its own colours for the same slot
  • specs/consent.spec.ts (2, CI): the consent card shows each consent, and Do Not Show Me AI Features hides them and shows them again across a reload · with a key on file the card’s AI switch asks first, and the key line goes
  • specs/content-check.spec.ts (3, CI): a course is scanned before it is shared; a high score the AI review passes blocks nothing · the nightly check waits in the job table for the next midnight (#1038)
  • specs/content-reports.spec.ts (1, CI): a shared course is reported, a copyright notice is sent, and the superuser forwards and resolves them
  • specs/content-review.spec.ts (2, CI): a harmful course is flagged with a short report; an override note is followed until new harm comes
  • specs/course-card-stats.spec.ts (2, CI): a course card shows passed and mastered as a bar and chips; a chip opens its stat in a modal and its name fits a phone (#684) · a chip’s modal fetches what is next as it opens: the next lesson to pass and each unit, the lessons to practise, and opens the course at one (#900)
  • specs/course-copy.spec.ts (5, CI): the copy icon in Spanish’s settings opens Copy of Spanish in the editor, with every file, and lists it · copies count against the account’s course bytes: past the limit the copy is refused with a notice · no copy or translate icon in the settings of a course shared with you; copying or translating it, or another user’s private course, is a 404 · no copy icon while a course is being generated, nor after it stopped; a POST says why · at phone width the copy icon sits beside the X, inside the dialog and clear of its title
  • specs/course-report.spec.ts (3, local only): the report shows which features each lesson uses · the report fits a phone · the report follows a save
  • specs/course-table.spec.ts (6, CI): a flagged course: search, the reason, override with a note, clear the override · more than five matches show as a count that opens them all · take down and put back from the course’s dialog · delete a course after a warning: gone from the table and the owner’s list · suspend a course’s owner from its dialog: their session ends
  • specs/course-translate.spec.ts (2, CI): Copy and translate to Español makes a translated copy; the original stays as it was · the dialog does not offer the language a course is already explained in
  • specs/create.spec.ts (11, CI): create, close the dialog, name the course and its lessons, then generate into it · editing a lesson’s content takes Generate away · a refused generate form comes back open with what was typed · the old pages lead to the new flow · the dialog asks which provider only when two or more keys are saved · the progress modal can’t be dismissed while it runs; stopped, the course waits on the list with Resume · a generation that fails before its outline: Retry on the card, the dialog filled in, and a retry writes the same course · a stopped course can be deleted from its card · a stopped course’s red ! does not widen the course list on a phone (#675) · a stopped course’s red ! tip stays on a phone screen while it shows (#864) · the old status page leads to the editor
  • specs/custom-themes.spec.ts (6, CI): wear a theme you made in a course, see it everywhere in it, then delete it · a theme you made is invisible to everyone else · a made theme is checked like a theme file, within the account’s limits · the course settings with your themes fit a phone · a course wears a theme of its own; outside a course the app wears Default (#565, #577) · a themed example’s mark reads right for a theme title that starts with The (#952)
  • specs/dev-ai.spec.ts (11, CI): a course generated through the dev AI provider shows in /dev/ai; limits, a timeout and an edited fixture reach the learner · the wire API refuses unknown keys and answers the OpenAI format; the platform key has its own profile · Manual replies: a request waits, folded at the bottom of /dev/ai, for the answer typed there; an ERROR: answer is the provider’s error · leaving the tutor while its first turn waits on the model does not lock the tutor (#659) · a learner who leaves the tutor mid-turn twice has one call with the model, not two (#848) · coming back while the tutor is still answering, Cancel lets go of the old reply and starts again (#890) · a slow reply gets a Cancel after ten seconds, and the learner can carry on (#890) · in another language, coming back while the tutor is still answering also offers Cancel (#890) · coming back to the tutor in the same tab after a failed first turn starts again (#932) · each profile’s keys are copy buttons in their provider’s colours
  • specs/dev-payments.spec.ts (1, local only): subscribe through the fake checkout, fail a renewal, cancel; a tampered webhook signature is refused
  • specs/docs.spec.ts (8, CI): the docs index is reachable logged out, with user and developer pages apart · every user page is served as HTML and as raw Markdown · /llms.txt lists the course format and its schema, and both are served · no docs page scrolls sideways at phone width · a long bare link in docs prose wraps at phone width · every in-app link on every docs page opens, and its #heading exists · a docs page’s heading is on the first screen, the page list after it · on a desktop the page list stays beside the page
  • specs/editor-autosave.spec.ts (5, CI): typing in a lesson’s JSON saves 3 s after the last keystroke, or when the drawer loses focus · a hidden tab sends what waits at once · what does not validate turns the circle red, says why, and is not stored but stays on the page · a save the server fails is tried again, and a page behind another tab is told to reload · an AI job locks the editor: what was typed is saved first, then nothing is saved until it ends
  • specs/editor-errors.spec.ts (4, local only): a parse error, an unknown prerequisite and a duplicate id each say what, where and how to fix it · a problem in another lesson opens that lesson’s drawer · the file page words a parse error as the editor does · Undo takes back changes that cannot be saved, after saving what can (#288)
  • specs/editor-fixes.spec.ts (3, local only): the file page’s path box checks the path and logs no console error (#273) · on a phone, Course settings and + Add unit do not overlap (#274) · closing a rename box by clicking elsewhere leaves focus where the click went (#275)
  • specs/email-owner.spec.ts (2, CI): e-mail a flagged course’s owner from its dialog, with an edit
  • specs/exact.spec.ts (29, CI): generate a course from the fixtures and open it · revise a generated course with feedback, then undo it · the lesson table: move, rename, delete, add, edit JSON, each saved as it is made, then undo each page’s edits · a course someone makes stays private to them · a failed generation explains itself in the dialog; its card keeps the raw detail behind a red ! · a key the provider does not know is refused by the provider, not by the app · the course list fills a desktop row and stacks on a phone (#428) · filter the lessons list, and say when nothing matches · look a word up from the lessons filter: its meaning and the lesson that teaches it (#348) · lessons show one unit at a time, with a tab per unit and page links for a long unit (#837) · the verb drill setup shows one set at a time, and its locked lessons a page at a time (#837) · the verb drill draws the person slots with profile pictures, the learner’s own for yo (#879) · pass a lesson’s reading check · a lesson’s tappable words look tappable and take the keyboard (#403) · tapping a word shows its meaning in a bubble that fits the screen · finish a Sprint mode session · Sprint home fits a phone in every app language (#670) · on a phone the History table scrolls in its box, not the page (#424) · on a phone the Vocabulary term table scrolls in its box, not the page (#933) · on a phone the Study and Play mode bars take two rows and scroll sideways (#423) · the modes’ old paths redirect, keeping the query string · the mode bars link to Study’s Lessons, not to the /study redirect · Free has no Skrongle AI: no usage card, no own-keys pill, the tutor offers only the saved keys · the dev toggle switches the plan on the profile · the profile has no Dev section for anyone; the seeded keys answer generate and the tutor · the profile’s e-mail field: a Verified check, and an (i) dialog with the name others see · Start asks for a course’s settings before it opens, then keeps them behind its gear; the password changes in a dialog · a page that does not exist is a 404 inside the normal layout, with ways back (#397) · the site installs as an app: every layout links a manifest that opens at the home page (#345)
  • specs/explore.spec.ts (3, needs a key): explore ${replayPath ? “replay” : who}
  • specs/forms.spec.ts (4, CI): Forms mode: add a word and a question in the lesson editor, see them as JSON, and find them in the file as saved · the JSON switch: a drawer reopened after a flip follows it, and quick flips end where they point · a new lesson is a whole form before it is saved · Forms mode edits the course settings too, and fits a phone
  • specs/google-signin.spec.ts (11, CI): a new learner signs up with Google, confirms the Terms, and signs in again · a learner connects a Google account with the password, nobody else can take it, and unlinks it (#360) · Google with a password account’s address: refused while unconfirmed, then joins that account once confirmed · with two-factor sign-in on, Google joins the account only once the code is entered (#677) · registering with a password for a Google-only address makes nothing and mails its owner · a Google-only account confirms with Google before its deletion is scheduled (#376, #970) · signing out everywhere else ends the learner’s other sessions (#375) · the ${error} button: the sign-in fails and comes back to the login page with the reason · the register page has the same buttons, each leading to its message · the buttons fit a phone screen · the timeout button: a Google that never answers fails the sign-in after the app’s own timeout
  • specs/home.spec.ts (4, local only): the home page has a map row and the Guide as a mode of its own · each mode’s explanation sits behind its (i) · the home page fits a phone · the map row and the Guide card are plain links
  • specs/hosted-ai.spec.ts (6, local only): a Plus learner with no key tutors and generates on Skrongle AI; the month’s bar goes down · the platform key’s limit reaches the learner as a plain sentence · a plan with no Skrongle AI (Free) is not offered it, and the tutor card says why · Basic, the CA$3 plan, has no Skrongle AI either: the tutor and the generator want an own key (#931) · credit packs are not sold without Stripe: the checkout answers 400 · only a superuser sees everyone’s Skrongle AI spend today; a learner sees their own bar
  • specs/i18n.spec.ts (2, CI): a visitor picks Spanish on the register page, the account keeps it, the profile changes it back · the browser’s language is used before any pick, and an unknown one falls back to English
  • specs/journeys.spec.ts (2, needs a key): journey ${journey.id}
  • specs/judged.spec.ts (3, needs a key): a lesson page teaches a topic and ends with a check · a failed generation explains itself in plain words
  • specs/lesson-conversations.spec.ts (1, CI): a lesson with a conversation shows both speakers’ lines
  • specs/lesson-duplicate.spec.ts (1, CI): duplicate a lesson: the copy sits below the original, with new ids, and the course still loads
  • specs/lesson-editor.spec.ts (10, CI): the outline lists every section of the lesson, opens its items, and the preview knows which modes show them · a multiple-choice question: the right answer is picked, a new option grows from the dashed box, the type switch reshapes the form, and it all lands in the file · checks appear under the field as you type, in the outline, and from the server · Try it: the preview grades a question with the course’s own checker, in Study and in Sprint · Sprint exercises: buckets and items, the wrong word by a click, tiles and distractors, played in the preview · words, sentences, conversations and tables have their own forms · items are added and deleted from the outline, the Guide’s steps are ordered by hand, and the lesson’s prerequisites are picked · on a phone the three columns are tabs, and nothing overflows · the gear’s Language features switch hides what only a language course fills, and is kept in the course settings · the gear’s Levels switch holds the Placement test switch, which adds or takes out placement.json under the course settings (#1106)
  • specs/lesson-header.spec.ts (1, CI): on a phone the lesson text starts under 420 px down a 390x844 screen: one main button, the rest under More ways, the details behind an (i) (#999)
  • specs/lesson-tables.spec.ts (2, CI): the test course’s lessons with tables fit a phone · Spanish paradigm tables scroll in their box, not the page
  • specs/lessons.spec.ts (17, local only): the server refuses a table it cannot use, and changes nothing · the last lesson cannot be deleted, and Escape keeps a lesson’s name · a stopped run keeps its Resume, and has no editor until it is finished · while an AI lesson is being written, every way to change the course is refused · three saves at once leave a course that loads · a folded unit hides its lessons and still takes new ones · Add unit: named in place, saved in the settings, it takes lessons, an empty one goes at once and one with lessons asks first (#232) · course settings are the first row: pinned, kept, and their units reshape the table as they are typed · add-lesson refusals: short brief, and a reply that cannot be used leaves the course alone · Use AI: Only add appends a lesson each time; Allow overall changes can replace the whole course · a hand-made course can be changed overall too · the download is every file by path, with a format version · Delete this course is a red button that warns first · without JavaScript the table still lists the lessons, and Edit opens one · the lesson table fits a phone · a course with progress warns before a delete, and the table is for the owner’s own courses only · a lesson page links the lessons before and after it in course order, below the lesson only, and neither past the ends (#402, #999)
  • specs/mail.spec.ts (7, CI): a new account’s confirmation e-mail opens in the dev inbox, and its link confirms the address · the outbox’s test message arrives in the inbox and opens from the outbox · the dev mailbox is a mail client’s table: bold until opened, archive strikes out and sinks, delete removes, all without a reload (#223) · a superuser resets another account’s e-mail verification, and the new link confirms it again · only a superuser sees the Superuser page · the Text section works like the e-mail one, styled like a messaging app (#225) · the dev mailbox needs no sign-in: signed out and as a learner, every section and its JSON open
  • specs/mistakes.spec.ts (1, CI): a wrong quiz answer shows on the mistakes list from the course home, and a right one clears it (#338)
  • specs/modals.spec.ts (5, CI): a Sprint mode stop’s sheet closes on a tap outside it and on Escape (#233) · profile dialogs close on a click outside; one with something typed in its form stays (#226) · the Generate dialog closes on a click outside until a wish is typed; a running job’s box stays (#226) · the /dev/jobs (i) opens its dialog; OK and a click outside close it (#872) · without JavaScript a server-opened dialog opens in view at ${viewport.width}px (#1057)
  • specs/moderation.spec.ts (4, CI): a course taken down shows its owner why, and comes back when put back · a suspended account is signed out and can’t sign in until the suspension is lifted · a superuser deletes an account after a warning; the address can register again · a superuser blocks an address range and removes the block; others get 404
  • specs/owner-resets.spec.ts (3, CI): a superuser sets a temporary password; the learner signs in with it and must choose a new one · a superuser turns off another account’s two-factor; others get 404 · reloading after a temporary password is set does not set another
  • specs/pathways.spec.ts (7, CI): switch course: Spanish, then a generated course, then back · generate from notes and uploaded files, with no wish · cancel a slow generation, then resume it to a ready course · resume a run that stopped on a lesson: the resumed job keeps what was written · saving into a stopped run keeps it resumable, and Resume keeps the edit (#81) · the editor opened between the outline and lesson 1 still follows the job to its end · remove an API key: the profile says no key, and generating no longer offers it
  • specs/privacy-requests.spec.ts (2, CI): a superuser sends an account its data; the link goes to that account’s address · a superuser forgets an address with no account, not one with an account
  • specs/profile-autosave.spec.ts (8, local only): the green checks come a moment after the save, fade after 5 s, and come back with the next edit · names save a second after typing stops, in one request, and the heading follows · a field that needs attention turns red at once, is not sent, and leaving asks first · the server refusing a field marks it red with the server’s reason · without a connection the circle turns red, leaving asks, and it saves once back online · the phone number wears a +1 and punctuates the digits as they are typed (#496) · the profile has no picture and no theme: both are a course’s settings · API keys: folded away, a pasted key saves at once, a typed one 3 s after typing stops
  • specs/profile-nav.spec.ts (1, CI): the profile nav marks the section in view
  • specs/prompt-limits.spec.ts (5, local only): the Generate dialog counts the wish and the notes, red outside 20 to the maximum · the server refuses Generate prompts outside the limits, in the same words · Use AI counts its text, red outside 20 to the maximum, and the server agrees · a Generate form that is too short is refused in place, calmly · short notes are refused in place too, and the notes box is the one marked
  • specs/record.spec.ts (3, needs a key): record ${id}
  • specs/release.spec.ts (2, CI): a person joins the waitlist, confirms, is invited and signs up
  • specs/review-ai-changes.spec.ts (14, local only): a generated course reviews as new lessons, listed like the lesson table · Only add shows the new lesson as one column; what was there keeps no red · Use AI runs on the course page: the modal comes up in place, holds the page, then the review opens · a refused Use AI request comes back on the same page, text kept · a revision shows red and green side by side, and later hand edits do not change it · leaving out a line and a lesson: Accept writes only the rest, and the review remembers · editing a line in place as text: it turns blue and stays in, Undo/Redo step through edits, Accept writes them · after a lesson-table edit, leaving out part of a lesson still goes in (#280) · after Undo of the AI run the review says so and offers no Accept (#281) · the review labels lines in plain words, not JSON keys (#282) · Don’t show this automatically next time: the review waits for its button · Review changes is a button next to Submit · a course nobody used AI on has no review button · without JavaScript the review is a page whose boxes Accept sends
  • specs/review-and-revise.spec.ts (4, local only): while a revision runs the modal covers the locked workspace; closing the review shows the new lessons · a lesson edited by hand survives a revision that keeps it · undo puts back the files from before the revision and drops later hand edits · a hand edit that breaks the course rules does not block a revision of a course that loads again
  • specs/revise.spec.ts (10, local only): revise a course twice in a row, and undo goes back one step · undo and redo icons right after a revision, from the course page the review closes onto (#231) · Undo and Redo: rows the step adds fade in, rows it takes away fade out, the rest stay still; none under reduced motion (#251) · a generated course is changed with Only add or Allow overall changes, not generated again · feedback that is too short is refused on the form and kept · a plan that removes a lesson drops it, keeps the others’ progress, and undo brings it back · a rewritten lesson keeps its progress · a plan the AI cannot give leaves the course alone and offers neither undo nor resume · while a revision runs the course still opens but its files are locked; cancel releases it · two revisions asked for at the same moment start only one job
  • specs/share-edges.spec.ts (9, CI): a used link turns a second account away; the invited address, confirmed later, has the course with or without the link · unknown, odd and padded tokens: a 404 for the link, the usual sign-in pages · removed and invited again: a new link, the old one dead, the learner’s progress back · an old link after the share was removed says it no longer works · every mode opens on a shared course with the learner’s own progress, kept when the owner edits the lesson · the share removed mid-session: the next page goes to the course list, the API answers 400 · the share removed mid-Sprint: the page goes to the course list instead of ‘try again’ · the owner deletes the course, or the whole account: the course leaves the learner’s list and session · the invitation is in the owner’s language; the learner’s pages stay in theirs
  • specs/share-sign-in.spec.ts (3, CI): a share link survives the two-factor code step, and cancelling it keeps the link · a share link survives Google sign-up’s confirm step · a share link survives a Google sign-in with two-factor on
  • specs/share.spec.ts (2, local only): an invited address registers from the link and has the course read only, until the owner removes the share · a shared course’s circle tip wraps inside a phone screen while it shows, even with a long owner address (#954)
  • specs/smoke/complete-test-course.spec.ts (3, CI): K: read and practise every lesson · D: every lesson’s session, every exercise kind, the review mix and lightning · G: every Guide stage, in order
  • specs/smoke/daily-goal.spec.ts (1, CI): every mode fills one daily goal and resumes where the learner left off
  • specs/smoke/drill-first-set.spec.ts (2, CI): the drill opens on the first set with unlocked verbs, not Present · Start drill works without JavaScript
  • specs/smoke/export.spec.ts (2, CI): a learner asks for their data, gets the link by e-mail and downloads the JSON once
  • specs/smoke/french.spec.ts (6, CI): the course is on /courses, offers its four levels, and starts at greetings · Study: read greetings, pass its check, practise it; numbers’ numeric questions grade · Sprint: play greetings’ session right at phone width · Play: play the first stage · Realm: the theme’s French material comes first and the course’s French praise is used · games: the verb drill (être, avoir) and Date Dash
  • specs/smoke/happy-paths.spec.ts (10, CI): 1. a new learner sees lesson 1 as next · 1b. a stage read in Study but never played says Start in the Guide, not Try again (#417) · 1c. a Guide stage played to the end under 70 % says Try again, not Continue past its last step (#417) · 2. Study: read lesson 1, pass its check, practise it: passed everywhere · 3. Sprint: play lesson 1’s session right: passed everywhere · 4. Play: play stage 1: passed everywhere · 5. a lesson passed in one mode is passed in the other two · 6. progress survives logging out and back in · 7. a lesson whose every answer was right is not listed as needing practice (#681) · 8. a Guide stage’s quick checks mix their options, so the right one is not always first (#962)
  • specs/smoke/mini.spec.ts (6, CI): a learner registers, signs in again and passes a lesson with nothing configured · there is no dev mailbox and no dev phone · the server says it is a Skrongle mini · the profile has no Subscription, no phone field and no red ! on the e-mail · sharing shows the link once, and a reload goes back to the dialog without inviting again
  • specs/smoke/motivator-reward.spec.ts (1, CI): every mode’s end screen shows the worn motivator’s reward line
  • specs/smoke/motivator-skc.spec.ts (1, CI): Skrongle Cash draws the hero and reward line, and keeps counting while another motivator is worn
  • specs/smoke/motivator-social.spec.ts (1, CI): Social compares the learner with the course’s other learners, anonymously
  • specs/smoke/motivator-xp.spec.ts (1, CI): XP counts what was earned under any motivator and levels the learner up
  • specs/smoke/motivator.spec.ts (1, CI): the motivator worn draws the course home, and switching keeps progress
  • specs/smoke/password-reset.spec.ts (2, CI): registering a taken address looks the same and mails its owner · a forgotten password is reset from the login page through the mailed link
  • specs/smoke/phone-width.spec.ts (2, CI): the main pages and a lesson in each mode fit a 390 px phone · the course editor fits a 390 px phone
  • specs/smoke/placement.spec.ts (2, CI): the placement test suggests the first unit with a miss, shows a report card, and the course starts there · the marking is the server’s: answers come back with each unit’s tally and no question carries its answer
  • specs/smoke/practice-empty-check.spec.ts (1, CI): Check with no answer asks for one inline, on a choice and on a typed question
  • specs/smoke/practice-keys.spec.ts (2, CI): number keys pick an option and Enter checks, then advances · digits typed into a fill-in answer stay in the text box
  • specs/smoke/practice-nothing-yet.spec.ts (2, CI): a fresh learner’s practice shows Read next and no counter · a round with questions still shows the counter
  • specs/smoke/practice-result-order.spec.ts (1, CI): the result box sits above Next, and Hint goes once the answer is graded
  • specs/smoke/practice-summary.spec.ts (2, CI): the mastery change is shown as (+N), and left out when nothing changed · a round too short to count says what it needs the answers for
  • specs/smoke/privacy.spec.ts (3, CI): the privacy policy is served to visitors · deleting the account signs out and waits 14 days; signing in can cancel it (#970) · the AI forms say which provider gets the notes
  • specs/smoke/self-host.spec.ts (3, CI): the self-hosting guide is linked from the docs and readable on a phone · the guide is only linked from the docs, not the landing page or the footer · the self-hosting FAQ is linked from the technical guide and points at the diagnostics
  • specs/smoke/skc-shop.spec.ts (2, CI): the shop sells a badge once, keeps it across motivators and draws it in the worn theme · without JavaScript a refused buy says why
  • specs/smoke/sprint-check-error.spec.ts (2, CI): a failing check shows one error line, replaced on retry, cleared on success · a typed answer stays editable after a failed check (issue #672)
  • specs/smoke/sprint-ladder.spec.ts (2, CI): fast taps all land on the rungs, a filled rung gives its form back, all right grades right · one form on a wrong rung grades wrong, shows the key, and History agrees with its ✗
  • specs/smoke/sprint-sort.spec.ts (2, CI): a wrong bucket shakes the word and keeps it; the sort checks itself, wrong, and History says why · a clean sort, by the keyboard: Enter on the right bucket drops the word, and the sort checks itself right
  • specs/smoke/start-level.spec.ts (4, CI): start Spanish at A2: the A1 lessons are skipped and never offered · change level mid-course (#333): progress stays, every mode moves to the chosen level · the test course offers each of its units as a level · a course without levels never asks for one
  • specs/smoke/test-course-games.spec.ts (7, CI): every game on /play is played here · K Listening: a lesson’s round, then a mixed round · Vocabulary: collect words in Sprint mode, then the vocabulary page and the flashcards · Word Match: the lesson board and the all-lessons board · the paradigm drill, Numbers by ear, Date Dash (each drill) · Tutor: a tutored lesson over the fixtures, one tapped and one typed answer · Theme: the worn theme’s material comes first, and nothing is missing without it
  • specs/smoke/verify-email.spec.ts (3, CI): a new learner confirms their e-mail address from the link · sending the e-mail again replaces the first link
  • specs/smoke/wrong-answers-games.spec.ts (5, CI): Guide: a stage of wrong quick checks isn’t passed and the next stays locked; a replay passes it · Word Match: wrong tries are counted and keep the lesson from passing · the games: a wrong answer is marked wrong and History counts it · flashcards: a card marked not known comes back in the missed deck · Tutor: a wrong answer gets a wrong verdict
  • specs/smoke/wrong-answers.spec.ts (7, CI): K: every question type, answered wrong, is marked wrong and doesn’t pass · K: one lesson answered wrong reads as "1 lesson needs practice" on the course home and map · K: the 70 % line, 3 of 5 doesn’t pass and 4 of 5 does · near misses are wrong, and the answers they nearly are stay right · a reading check with one wrong answer fails, the lesson stays unread, and a retry passes · D: every exercise kind, answered wrong, is marked wrong, and no lesson passes on wrong answers · D: the session’s score is the answers given, on the screen, in History and in progress
  • specs/subscription.spec.ts (3, local only): Plus: bought through Checkout, shown with its renewal date, the AI bar and the own-keys pill; cancelled, it lasts the month · Basic: no Skrongle AI card or pill, an Upgrade to Plus button that opens the Billing Portal, and the chooser marks Basic as the current plan · dev login ?plan= takes basic, plus and the old pro (now Plus) through the dev Checkout; anything else names the plans
  • specs/term-flag.spec.ts (3, CI): a course with a listed word can’t start or be shared until the word is gone · more than five matches show as a count that opens the list, ten to a page
  • specs/terms.spec.ts (5, CI): the terms page is public and says when it was last updated · registration needs the terms checkbox, and records the acceptance · a material change: the notice cannot be dismissed and blocks the API until accepted · a minor change: the notice shows what changed and Continue closes it for good · the profile’s read link shows the terms in a dialog and closes back to the profile (#152)
  • specs/two-factor.spec.ts (4, CI): a phone number is verified with a texted code, and text-message sign-in then works (#292) · e-mail sign-in codes are not offered until the address is verified; then the code comes by e-mail and a wrong one is refused (#292) · an authenticator app is added through the dev authenticator and its codes sign in, each once (#292) · a number that replied STOP gets its sign-in code by e-mail, and START plus a new code turns texts back on (#969)
  • specs/word-list.spec.ts (3, CI): a pasted word list previews, flags what it can’t read, and becomes a lesson that opens with the words · a paste past the cap is refused in words, not with a body-limit error · the button follows the Language features switch