Helps adults who already speak German understand the Zurich German actually spoken around them. Runs at heidi.orangecat.ch (beta).
An adult who already commands German still cannot understand the Zurich German spoken around them, and the environment withdraws exposure exactly when it detects they need it: the Swiss switch to Hochdeutsch or English the moment they notice you struggling. The learner is denied the input because they are a learner. That trap is the reason a product is justified at all.
A source of exposure that does not withdraw. Raw exposure is not input, so the job is calibrated exposure at the learner's frontier, measured per learner and continuously — the thing software can do that a podcast cannot. Real recorded speakers, plus a deterministic variety gate, because a learner buying Zurich German cannot audit whether they were handed Bernese forms.
Help an adult who already has German understand the Zurich German actually spoken around them, deal with the messages and speech they meet in real life, and turn those encounters into learning.
Progress measured by how much of an unfamiliar Zurich speaker the learner understands — not by how much Heidi they have consumed.
The stack has not been written down yet.
Investigate this project with Loki →The full chat at «/chat» is the fleet's chat app (chatkit's ChatApp): your conversations sit in a drawer on a phone and beside the chat on a wide screen, with a search box and Today / Yesterday / Earlier; each has one «…» for Rename and Delete. New: a temporary conversation that is written nowhere, a message written while Heidi is answering waits above the box and goes when she is done, and the conversation you had open comes back after a reload. Signed out, the list is kept in this browser and nothing reaches the database; a conversation from the home box or the floating window carries over. Stop still throws away the half answer (it had not been checked for Zurich forms) and a failure still says why. The chips under an answer («Shorter», «Word by word») and «What does it mean?» on a selected word now put their sentence in the box to send, instead of sending it at once. Later the same day, with chatkit 0.19: the chips under an answer and «What does it mean?» send in one tap again. Each failed turn keeps its own reason, even with two in one conversation; Retry sends the pictures again; signed out, «This conversation is only in this browser» and sign-in sit above the list of conversations. Heidi's workarounds for 0.18 are gone: Stop discarding the half answer is now chatkit's stopKeepsPartial, the failure reason a per-turn ChatTurnError, and the dialect starter carries its language. A message you send in a saved conversation is shown once, not twice, and a question Heidi did not answer says so: before, it appeared twice with nothing under it and no way forward; now it gets the usual «try again» bubble with Retry. A double tap on Send no longer sends twice.
On «The most important words» the button that starts learning the next ten words is on a phone's first screen: it sits above the ten words there, not ten rows below them (on a wide screen it stays under the list). Choices that were ragged are now equal cells. In «Settings» the language and appearance choices were pills as wide as their words, wrapping three to a row with one left over; they are an even grid now (language: two columns, the last across; appearance: three equal cells). The footer's language list took half a column on a phone and broke «Schwiizerdütsch» and «Rumantsch» in the middle of the word; it takes the full width now. The returning learner's first screens lose what got in the way. On «My area» a learner who has already practised no longer gets the warm-up as a second dark button above «Practise now» (it is a quiet line; newcomers still get the card), and the empty «Continue» heading over «No conversations yet» is gone. On «Exercises» the note about where answers are kept moved below the start button, so «Let's go» is on the first screen of a phone instead of below it, and the warm-up line is not shown to someone who already practises. Heidi opens on an empty conversation with the box ready, and the last one is a row away. Nearly everyone who opens Heidi has something new to ask, and until now they landed in last week's conversation and had to find the way out. A conversation you were in less than half an hour ago stays as it was (ask, read a grammar page, come back); anything older is offered as one quiet row, «Your last conversation · date → Continue», never put back over the box and never deleted. Signed in, the row is your most recent saved conversation. One rule for the home page's Heidi box, the floating «Ask Heidi» and /chat (useRestoreDraft, STILL_GOING_MS in lib/domain/chat/draft.ts). One empty state for all three: the heading asks what you want to understand or say, instead of repeating «Ask Heidi» under a button that said it. The floating window shows the chat's own examples rather than three canned questions of its own, and its header icons are 44 px. Talking to Heidi has one name: the header, the dashboard and the floating button all say «Ask Heidi» (the header and dashboard used to say «Quick chat» for the same thing, and the dashboard showed its own button beside the floating one). On a phone the floating button slides away while you scroll down and returns when you scroll up, at the top and at the end of the page, so it stops covering the lines you are reading. An empty box has a Paste button: most first messages are a line someone else wrote and you just copied. One tap fills the box (it does not send), it only reads the clipboard when pressed, and it is not drawn where the browser does not allow reading it.
Your channels (/channels): follow YouTube channels you already watch — a channel link, a video link or the @handle, or one of the register's rows with what is spoken beside it. Heidi reads each channel's public feed hourly (no API key; loki's app-cron timer calls /api/cron/channels) and inline when you open the page, lists what is new since your last visit, and tells your phone about a new video if you turned notifications on (Web Push, per browser, from settings or the channels page). Following rings for the next video, never the back catalogue; nothing here ever reminds you to practise. One video (/channels/<id>): the listening lab's exercise on real media — a gist question before the first listen, three to five detail questions in Standard German after it, every wrong answer answered with the line that was said and a button that plays that second, then the transcript, tap a line to hear it. A count with its denominator, no score. The transcript is the platform's captions, labelled as the publisher's, as automatic («bei Mundart oft falsch») or as pasted; the questions are written by the model from it once per video, checked against the transcript before anybody sees them, and shared with every follower. Podcasts in the same loop: a podcast's RSS is read like a channel's feed, a publisher's page or an Apple Podcasts link resolves to it (Spotify publishes none and the form says so), every podcast and YouTube row on /listen has «Folgen», and an episode plays in the browser's own audio player with back-five-seconds and 0.75×. Its transcript is the publisher's where the feed links one; otherwise Heidi transcribes the opening minutes herself with the bridge recogniser, labelled as automatic with how far it reaches — never for a source the register calls dialect, where the recogniser would translate rather than transcribe. The house rule that nothing outside the packs names the variety now has a gate: lib/variety/leak.test.ts pins the count of such string literals per file to a baseline that may only fall. French is recorded as the next product, Ukrainian after it, with the six-step recipe in HEIDI.md §4. Fixed, found by the signed-in walkthrough: every signed-in page drew the signed-out state. @bitbaum/accountkit's getSession() returned a user without the actorId the pages key on, while the API routes, going through requireUser(), had it — so the dashboard and «Your channels» asked you to sign in while the follow itself worked. Fixed in accountkit 0.5.2 (bitbaum/accountkit#11), one reader for both; this release carries the pin. Said plainly where it bites: YouTube often refuses a server the captions. The screen then shows the three steps to copy the transcript from YouTube's own panel and paste it, and the result is labelled as pasted. What you answered stays in your browser (heidi.videos.v1); the follow is the one thing on the server, and all of it is on /privacy.
Heidi has its own accounts on @bitbaum/accountkit/auth (better-auth): email and password, passkeys and two-step codes, with «Continue with OrangeCat» as one option rather than the only way in. People whose first way in was OrangeCat keep the OrangeCat actor id as their id (legacyIdentityProvider), so every existing group, conversation and certificate stays theirs without a data migration. next-auth is gone; existing sessions end once.
The vocabulary search runs on @bitbaum/searchkit, the fleet's one search, instead of a hand-rolled substring match: one slip forgiven from four letters, two from eight, the start of a word while typing, accents and umlauts folded, both the dialect word and its German meaning searched. Results come best match first (the learning order breaks ties) with what matched marked; the «guessable» heading is not drawn while a query reorders the list. The dialect spelling rule stays ours, as searchkit's normalize: ie read as i, doubled letters as one (lib/domain/vocabulary/search.ts). On another writer's spelling plus one slip (schribe for schriibe, slipped) it is the difference between 0.24 and 0.91 found first. search.test.ts runs every generated one-slip misspelling of every word (~9,700) and holds success@1 ≥ 0.90, success@3 ≥ 0.96; the misses are short words the rule leaves at three letters (vill → vil) and slips that land on another listed word (cho for scho).
One row of buttons under an answer instead of three: suggested replies first, then the «What now?» moves and the «Learn from this» chips, de-duplicated and capped at five (lib/domain/chat/row.ts). The grammar link — the one button that opens a page instead of sending a message — keeps its place when the replies fill the row. Styled as chatkit's reply buttons; one role="group" named by chat.moves.title. Suggested replies: when Heidi asks something back or the next step is obvious, two to four replies sit under the latest answer, written by the model in the reader's language; one tap sends it like the composer. From @bitbaum/chatkit 0.6 (REPLIES_INSTRUCTION, extractReplies, ChatReplies). The block rides after the JSON answer and is taken out server-side, so it never reaches the stream, storage, copy or speech. Not in groups, where there is no one person whose voice a reply would be. A new scene under «Alltag i Züri»: «Mit einem Bünzli» — the word, and the neighbour it names. Fifteen lines you hear (the rule, the precedent, the warning, and what people say about a Bünzli) and seven you say back, in the order that actually ends the exchange: thank them, ask where it is written, say it will not happen again. Every line passes the Zurich gate; no native speaker has read it yet, and the page says so. «Bünzli» is in the vocabulary as casual slang, with its register, so a learner finds out it is affectionate about a third party and an insult to somebody's face.
The investor page is the door to Heidi's investor room on OrangeCat: the password leads into the room, where each investor can also have a link of their own. Its text and numbers are still written, and checked, in investors.ts, and the room is filled from it.
The home page has a «What Heidi solves» section: concrete moments for one person (group chat, lunch table, kindergarten, doctor, Gemeinde, the indirect no) and for a city (the switch to Standard German, care homes, workplaces, media, learners who cannot check what they are taught), each with what Heidi does today and a link to the page that does it. Every card's link is a route or a scene id, never a string; lib/config/problems.test.ts fails on a link to a page that does not exist or on copy missing in any of the seven languages.
Heidi's Zurich German is checked end to end: on the Swiss German site the explanation is judged too, not only the line to send. A refused answer is retried once with the refused forms named, and the cleaner answer is kept; a line that still fails is never offered as sendable and has no Copy button. The gate knows «stöh» and «göh» (Bern's plurals; Zurich says stönd, gönd) and double articles such as «d'Le Bilan». The model named under an answer is the one that served it, not the first link in the chain. Words from the learner's own question are no longer handed back as vocabulary. Heidi asks Gemini first for Zurich German, measured on Heidi's own prompts against the previous first model.
Practice asks «isch or hät?» A new question kind sorts four verbs by their auxiliary in the past («er [isch | hät] gange»), with a new grammar topic «isch gange, hät gmacht» in all seven languages. The trap it targets: sitting and standing take «si» («Ich bi ghocket»), where German in Germany says «hat gesessen». Ten boards, each mixing both auxiliaries; the investor page now says sixteen kinds of question. Pages say what is true today. Recounted on 1 October and corrected: investors (156 merged PRs not 119; 1047 tests in 130 files not 842 in 103; 2,482 practice questions not 1,876; 261 words not 226; streaks, weekly goal, Teams, certificates and sync moved from "next" to built), white paper (2,482 questions in both languages — EN said 1,761, DE 1876; 261 words and 20 grammar topics, not 226 and 19; the refusal register lives in the tests, not on the roadmap), organisations (care scenes hold 120 lines, not "around sixty"), contribute (390 lines, not 140), and settings/portal/about (what an account does now; saved words sync; images are free). Dead copy in lib/config/landing.ts and two unrendered dictionary keys removed. Every verb has its forms, and the modal verbs have a topic. Only 4 of 48 verbs had forms. Now 87 verbs (35 new: fahre, wohne, wele, wärde, iistiige, chündige…) show a table: ich, du, er, one plural row for mir/ihr/si, and the past with its auxiliary («isch gange», «hät gmacht»), each beside the German form. Every form passes the dialect check. New grammar topic «cha, mues, wott» for the modal verbs, in all seven languages, with its own test. Form questions went from 13 to 348, and the question pool from 2,028 to 2,482. The dialect check catches Standard German. It only knew other dialects' forms, so plain Standard German passed («Ich habe heute keine Zeit»). A leaks list in the Zurich pack now names about 100 Standard German words Zurich German never uses, plus the -ung ending, each with the Zurich form. It found and we fixed leaks on the site itself: «Sit wenn» → «Sit wänn», «Bis morn denn» → «dänn», «drei» → «drüü», «viel» → «vill», «neu» → «nöi». Words that are also Zurich (will = weil, gern, Achtung) stay allowed; German quoted in «…» in grammar notes is exempt. Every word has an example sentence. 121 of the 226 words opened with no sentence at all; each now has a short one written for Heidi, with its German, passed by the Zurich dialect check. The false friends get sentences that rule out the misleading reading («Mir müend springe, s Tram chunt»). The sentences also feed practice: the pool grows from 1,876 to 2,028 questions (119 more translations, 33 more «pick the sentence»). Listening lab designed (HEIDI.md), not built. Swiss German audio with a dialect transcript exists only under research or non-commercial licences, or behind learner podcasts' paywalls. The design records the sources, the watch-then-answer flow and the data model, and names what unblocks it: recordings by a native Zurich speaker of dialogues written from the scenes. «Learn these 10 words» now asks all ten. The session held eight questions balanced by kind, so two of the ten named words never came up. A session on named words now seats each word once first and grows to fit (at most 30); the usual mix fills any seats left. The vocabulary page says what to learn next. It used to print every word with up to seventeen scene links: 23,217 px on a phone, with no hint which word mattered or whether you knew it. It now opens with the ten words that pay off most and one button to practise exactly those, then the full list in the same order, at 5,532 px on a phone.
Tests for 17 of the 19 grammar topics. Before, only «Kein Präteritum» had enough markable questions for a test. 107 new «which sentence says exactly this?» questions cover the subjunctive, short pronouns, participles, verb order, direction words, articles before names, «wo» clauses, possession, the imperative and the indefinite article, each with its own lesson where the existing ones did not fit (wish, clitic, modal-past, direction, name-role). The practice pool grows from 1,761 to 1,876 questions. Ask Heidi why, from any question. Under each answer in practice and the warm-up, «Heidi fragen, warum» sends the question, the right answer and your own when it differed, and opens the chat over the exercise. Before, the only buttons there were about the word in general, and asking about your answer meant leaving the exercise. Fixed: questions that needed no Zurich German, and a card in English. «Which article?» is now asked only where the Zurich article differs from the German one (s Tram, s Billett, s Grosi, s Säckli, d Chilbi); for «s Ässe» it was enough to know «das Essen». The practice pool shrinks from 1,793 to 1,761 questions accordingly. Fixed: white in dark mode. The floating «Üben | Heidi fragen» pill and the header avatar were white slabs on dark pages, and the dimming behind dialogs, sheets and open menus was a white haze. They stay dark now, and the overlay audit fails on any light fixed element in dark mode. Fixed: menus did not show over the chat. On /chat the header's menus opened below the bottom of the screen and behind the conversation list, on a computer, in light and dark. A stylesheet rule made the header position: static there, which switched its z-index off and anchored its menus to the page; and its layer (30) tied with the conversation list, which comes later in the page. The header is positioned again, sits on its own layer (45) above anything a page uses, and a source test holds the scale. A new overlay audit opens every expandable control on six pages at two widths in light and dark and fails if a panel is missing, empty, covered or see-through; CI runs it after the build. «Mein Bereich» starts with what today asks of you. A «Heute» panel on top: the streak, how many questions and kept words are due, «Jetzt üben» and «Mit Heidi chatten», and the weekly goal. It replaces the streak card, the due count further down and the chat button at the end of the page. Questions come back before you forget them. Every question now has its own date to return: tomorrow after a first right answer, then after 3, 7, 16 and 35 days, and back to tomorrow after a miss. A sitting starts with the questions that are due, then brings new ones; a question you answered recently waits its turn. Before, a question answered right once never came back on purpose, and only the words you kept had a schedule. Practise in one tap, from any page. Bottom right, next to "Heidi fragen", there is now "Üben". One tap opens practice for the page you are on: on a situation its sentences and words, on a grammar topic that topic, anywhere else everything mixed. Closing goes back to the same page at the same scroll position. It used to take two taps and two scrolls: the button under a situation, then "Losgehen" below the fold on the practice page. Fixed: a "test" of two questions. A test on «Bsitz andersume» had two questions, whatever the timer said, because the topic has only two that can be marked. A scope now offers a test only with at least ten; below that it is practised. The situations and "everything" keep their tests. Fixed: questions you could answer without Zurich German. For «Annas Schwester kommt auch noch», the wrong answers named another person, dropped the sister or said "nicht", so matching the German found the right one. They now keep Anna and her sister and differ only in who comes, which is what «de Anna ihri» says. The same for «Herrn Meiers Zimmer» and the two «wo» questions, whose wrong answers now read «wo» as "where". The explanation for possession no longer talks about «wo» relative clauses; that is its own lesson now. Exercises get their own screen, like an app. Practice, the test and the warm-up no longer run as a box in the middle of a page. "Losgehen" opens a screen with only the exercise: a close button and a progress bar on top, the question in the middle, and the answer buttons in a bar at the bottom where the thumb is. No header, no footer, and nothing above the question that can push it around. The test's clock sits in the top bar. Fixed: on a phone the page jumped after every answer. The next question landed somewhere else on the screen and you had to scroll back. Above the question, the streak card and "What to work on" grew with each answer, and iOS Safari has no scroll anchoring, so the whole page moved. Practice, the test, the warm-up and the home-page chat now run full screen on a phone, like an app, and each new question starts at the top. "Minimise" puts the task back into the page with its answers or conversation intact; "Continue full screen" reopens it. Warm-up: how much Zurich German do you already understand? Eight lines from the everyday situations in two minutes, with no grade and no level. The lines get harder while you keep up and easier after a miss, and after each answer you see the word the line turns on. The result names the situations you already follow and the one worth starting with, and your answers feed practice so the first session starts at the gaps. A reader who also gets the between-the-lines questions is offered the site in Swiss German, once. Offered on the home page, on the dashboard until done and on the practice page; stored only in the browser. Heidi answers what you wrote last. After three answers about «Le Bilan», someone asked «Pire» and got a fourth «Le Bilan» sentence, twice: the model was handed the whole conversation as one list with nothing marking the question. The newest message now stands apart from the history, a lone word is a question about that word, and Heidi's past turns carry the sentence she actually wrote. Against the live model: 4 of 6 runs wrong before, none after. «New conversation» where you need it. On a phone it sits in a toolbar right above the chat instead of only inside the side menu; in the floating chat it is an icon beside expand and close instead of an underlined link that wrapped. The side menu now closes with Escape. Fixed: the phone keyboard pushed the chat's header off the screen. Both chats now size themselves to what the keyboard leaves visible, and the full-screen chat no longer opens the keyboard by itself on a phone. Swiss German, all the way through. Schwiizerdütsch is now first in the language menu, the footer and settings. With the site in Swiss German, Heidi explains in Züridütsch in saved conversations too. They used to keep the language they were started in, so a thread begun in German kept German explanations under «Erklärige uf Züridütsch». Suggestion labels («Wärmer», «Chürzer») and tones now come from the dictionary instead of appearing as the model's English («warmer», «shorter», «bridge»). The translation under a suggestion is in the reader's language, and a Swiss German reader gets none under a dialect line. Against the live model: English translations in 2 of 2 answers that carried translations before, in none of 4 after. Fixed: «Chat» showed the conversation you had just left. Signed in, the first message moves the address to the saved conversation without reloading, and the menu's «Chat» link then re-rendered that same page — the next message went into the old conversation. «Chat» now always opens an empty one.
Five new kinds of question — and a reason after every answer. Practice now also asks what somebody means, which reply fits, which sentence says exactly the German, what time or price was said, and what a word means in its sentence. The pool grew from 1,451 questions to 1,793, the objectively marked ones from 246 to 588. After each of these: the word the answer turned on, and one sentence on why — in all seven languages. (#143) A home page you can read in a minute. The home page now says what Heidi is in two columns — with a cow in front of the Alps beside it — and leads in through three steps: situations, practice, chat. About 250 words instead of 440. The menu opens on a click, shows every page with one line about it, and closes with Escape or a click outside; on a phone it opens as a full page with large tiles and the chat at the top. (#141) More grammar, more words — and a real rule for the -li. Seven new grammar topics, 62 new words (164 → 226 entries), and the -li topic now says how the form is built instead of that it "often means nothing small". Every new example passes the same Zurich check as the rest; practice has 1451 questions instead of 1233. (#142) The strongest model your key can use. Bring your own API key: paste it, Heidi checks it at once and lists the models it can reach, with the strongest preselected. Ten providers instead of five; the check no longer costs anything; when it fails you see what the provider says. The key stays only in your browser, as before. The dialects, documented from the literature. Every dialect page now shows forms that identify the region — each with its source — and links the region's grammars and dictionaries. Corrected: «het», «nid», «goht» and «stoht» are not markers of other regions; the atlas records them inside the canton of Zurich too. The «Which one is Zurich?» exercise now only asks about forms that are certainly not Zurich German.
A say belongs to Solon. The open votes and comments on the roadmap and changelog are gone again. A voice is a right someone holds — it comes through Solon, with an account and a seat in Heidi's organisation. The roadmap and the changelog are for reading again; pointing at anything on any page still goes through the feedback widget. What was submitted so far stays stored and is listed on the privacy page. Heidi for Teams. A group can now be a team: whoever created it chooses the focus and sees who is secure in which situations. Every member decides for themselves whether to show their progress, and can take it back at any time; what is shown is the standing per situation and the number of certificates — never which lines, or what anyone got wrong. Your progress on every device, and a certificate per situation. Signed in, you can switch on «Progress on every device» in settings: practice, streak and saved words are then the same on phone and laptop. Switched off — the default — everything stays in your browser. Whoever understands a situation securely can have a certificate issued for it, on its own page anyone with the link can check — without your name. Five new situations, and every one deeper. At the doctor's, at the municipal office, in the laundry room, at an apéro — and when «no» does not mean no. 19 situations with 390 lines in all, up from 14 with 140. Every translation is now also checked for Swiss Standard German: «das Velo», not «das Fahrrad». Have your say on the roadmap and on every change. Every roadmap item could be marked as needed or not and commented on, with no account needed; only what you wrote was stored, with a random key your browser kept. Withdrawn the same day — see «A say belongs to Solon» above. Where is this message from? On the dialect page you can paste a message you received. Heidi shows which forms come from where, what Zurich says instead, and what the words mean. Too little text says so — Heidi does not guess. Corrected: «nid» was labelled an eastern Swiss form; it is Bernese and central Swiss. Streaks and a weekly goal. Your area now shows how many days in a row you have practised, your best run, and a weekly goal you choose (1 to 7 days, 3 by default). One missed day is bridged automatically. All of it stays in your browser.
Two new exercise kinds, and typing that is deliberately not marked. pick — a real pack sentence with one word cut out and four real words offered. It exists because of a measurement rather than a hunch: the twenty-six function words produced six questions between them, since they carry no article and no paradigm and only the matching grid could see them. They are the words the vocabulary page argues buy the most comprehension. Function-word questions went 6 → 32; the pool went 195 → 221. What makes it markable is the German printed underneath: several options produce a grammatical sentence, and exactly one makes it mean that. The distractors are filtered so no candidate's own gloss appears in the bridge — without that rule the generator offers nüme against a line containing "nicht mehr" and marks a defensible answer wrong. Typing, in the two self-marked kinds. The reveal button used to be the only control, so the retrieval could be skipped entirely. There is now an optional field to write the answer in first, and nothing compares it to the pack's — the two are shown one above the other with no verdict. Grading typed dialect would mean judging spelling in a variety that has none. A bug found by the new tests rather than by looking: pick located its word case-insensitively and then asked blank() to remove it, which matched case-sensitively — so mir was found in «Mir händ …», reported present, and left standing in an item that claimed to have a gap. Blanking now folds case, which is also right on its own terms: a word at the start of a sentence is the same word. The reference section became navigable, and practice became addressable. /grammar was one document four screens long with every topic expanded; it is now an index grouped into two bands — the topics a sentence does not survive, and the ones it survives while you do not — with a page per topic at /grammar/<topic>. Each topic page carries what a section could not: a practice session about that topic alone, the scene lines where it actually occurs, and previous/next within its band. /vocabulary gained a filter over both languages, jump links, a scoped session per group, and the scenes each word is said in. Every scene now practises itself. The mechanism under all three is one thing: ?topic=, ?scene= and ?group= on /practice, filtering the existing pool. Nothing new is generated. It works because ItemSource already recorded where each question came from — the field built to make a wrong answer traceable turned out to make a pool addressable. Two new exercise kinds. match (four words, four meanings) and gaptext (a four-line passage from a scene with three words lifted out and offered back). The word bank is what makes a passage objectively markable in a variety with no settled spelling: the learner chooses rather than spells. A word-order exercise was considered and refused — this variety tolerates more than one order, and calling a valid alternative wrong would be the §6 failure in a new place. The system now knows what you keep getting wrong. A learner model in the browser, beside the kept words: a miss rate per topic, scene, word group and rule, which reorders every sitting so the weak half leads and names the two or three areas worth going back to. No number is ever shown — no percentage, no level, no streak. The diagnosis points at the material, never at the person. It is declared on /privacy and deletable from /settings, along with the seen-history, which had been undeclared since the exercises shipped. The first smoothing was wrong and a test caught it: missed/(asked+1) let one wrong answer out of one outrank a topic missed nine times out of twenty. The rate is now shrunk by sample size instead. Exercise kinds became modules. lib/domain/practice/kinds/, one file per kind plus a registry. Adding match had touched five places, one of which was a bug shipped in the same commit as the feature. The registry's contract test immediately found a second: two cloze items generated from one topic could share an id, so answering one marked the other asked and one of the two was never served again. And enough material that the questions stop repeating, which was the complaint and was arithmetic rather than scheduling. A second situations domain (everyday), the short words no correspondence rescues, and paradigms for si, gah and cho — each built only from forms the packs already publish. Practice items 67 → 195, scenes 6 → 14, lines 60 → 140, vocabulary 48 → 83, grammar topics 8 → 11. Article questions went 3 → 24. Two smaller corrections found on the way: the pack spelled Chunnsch in its showcase and Chunsch in a grammar example, which is exactly what the orthography note promises not to do; and four places still linked /grammar#<topic> after topics became pages — including the chat's own grammar button, the product's main loop.
Situations, and the sales claim that had nothing behind it. /situations is a third axis beside the variety and the locale: domain — where you need this. The first pack is care and nursing homes, six scenes from a shift (handover, the morning, pain, meals, the evening, visitors) with sixty lines, each carrying its Standard German, a direction (hear or say) and the grammar topic it turns on. Twenty-seven of them became practice items and the pack's question count went from forty to sixty-seven. The reason it was built now is a fault rather than a plan. /organisations had been telling care homes that Heidi "practises the sentences that are actually said on your ward" since the page shipped, while the product held forty-eight words and none of them was said on a ward. That is a claim with nothing behind it, on a live page, which is the one thing sectors.ts says in its own header it must never do. The sentence has been replaced with what is actually there, and the row now links to the scenes so a reader can judge them directly — a sector with proof links to it, one without shows no link, and the asymmetry is deliberate. Three things hold the content honest. Every line passes the deterministic variety gate at the generation threshold, so a Bernese form fails the build rather than reaching a learner who could not detect it — and that gate was proven by mutating a line to güet and watching it fail, not by the suite being green. Every line names the source that vouches for its lexis, while the arrangement stays ours, because example sentences have to be written for this product rather than lifted from resources that are non-commercial. And a pack declares whether a native speaker has read it: care says no, and the pages print that above the lines rather than below them. It flips when a named person has read every line. lib/situations/display.ts is very nearly the identity function and exists anyway, for the reason lib/variety/display.ts exists: the pull request that adds an English note to a scene is a reasonable pull request, and this is what stops it rendering in Russian.
Practice got a second attempt, and the page now argues for itself. A missed question used to reveal its answer and move on — a test with feedback, and never a second chance to produce the thing. It now comes back three questions later, once, labelled as the second attempt. Rawson & Dunlosky (2011) is the reason: the durable gain comes from retrieving something correctly more than once, not from having seen it; Butler & Roediger (2008) is the sharper case for the three multiple-choice item kinds, where choosing a lure can leave the learner holding it unless something corrects them. The gap of three questions is ours, and is written down as a judgement rather than a finding. The schedule hears the FIRST answer only. A word got right a minute after being revealed has not been retrieved, and grading it twice would advance the very interval the spacing exists to protect. The first version of the guard was wrong in a way reading it did not show: it refused to requeue an item already in the QUEUE, but a requeued item leaves the queue to become the current question, so a second miss put it back again. Found by driving a whole session wrong on purpose — eight questions became thirteen and would have kept going. The rule is now stated on what decides it, and a test pins the worst case at exactly twice the session size. /practice explains its own design, with the papers. Seven claims, each linking to the study behind it, on the page the design belongs to rather than on /method — the person reading has just been told they got something wrong and is entitled to know whether the thing telling them knows anything. The interleaving row states the meta-analysis's moderator instead of suppressing it, and the row about the three-question gap says the number is ours. Every citation carries the same guarantee the research page already had: a test refuses a row that cites nothing, and refuses a locale that cites something different for the same sentence. The dialect pages gained the structure they were describing. Three branches of Alemannic, each carrying the pair of forms that draws its line — Kind inside Low Alemannic, Chind outside — with the two areas that genuinely straddle a line carrying no branch at all rather than a tidier falsehood. The area pages also list where each dialect can be heard, from a register whose rows have carried an atlas id since the first commit without any page following the join. Essays, because the question people actually arrive with — why a country this small has this many dialects, and why the big neighbour lost its own — is an argument with sources rather than a form or a map. Typed blocks rather than Markdown, so a page cannot render a type size the design system does not have, and translation is explicitly not required: an essay exists in the languages somebody wrote it in, a reader in another is told which one they are getting, and the sitemap's hreflang names only real translations. Every page is now measured for overflow rather than screenshotted. pnpm run audit:responsive drives every route in every language at four phone widths in both themes, with a browser that has been used, and fails on an element that leaves the viewport or whose content is wider than itself. It was written after the third report of a page scrolling sideways on a phone, each of which had been fixed one page at a time. /portal with eleven ordinary kept words measured 516px wide in a 360px viewport. ---
Somewhere to open your mouth. Record yourself and see what the recording actually shows: time spent speaking, where the pauses were, and which words came from another dialect. No score for pronunciation — nobody can measure that honestly today, and a number would be a claim about a person. In Zurich German nothing is transcribed, on purpose: no system writes this dialect down reliably.
The spoken channel. Heidi reads an answer aloud, and says what she is speaking with every time: no browser anywhere ships a Zurich voice, de-CH is Swiss Standard German, and lib/voice/variety.ts refuses to report any synthesiser as dialect at all. The variety gate, which has judged generated text since #52, now judges sound. Corrections became a setting, with three rules above it that no setting can switch on: never spelling (Zurich German has no correct one), never pronunciation (the correction path receives no audio, so a score would be invented), and never the forms in a transcript — recognition writes Standard German whatever was said, so flagging them corrects the machine and bills it to the learner. Where to hear it (/listen): fifty Swiss podcasts, stations, channels, programmes, series and films, each labelled with what is actually spoken and what that label rests on. Half of Swiss broadcasting is Standard German by format, which is the trap a learner cannot see. Kept alive by a link sweep with three verdicts, because a Cloudflare challenge is not a dead link. The explanation arrives as it is written (#67) — the answer streams rather than appearing whole.
Heidi is on every page, with an account menu; the reference pages became places that hand work to the assistant rather than pages to read; /technology published what the field can actually do with this language, with the numbers (#66) The chat box got one accessible name, and a test that checks (#65) Dictation moved onto a chain through transcribe() in @bitbaum/ai-kit rather than one fetch at one vendor with one key (#63, #64) Signed in, the locale root IS the dashboard (#61); the reference pages folded into one panel (#60) The vocabulary that actually blocks a sentence (#59); what Swiss German is, and a page per dialect area (#58); every dialect area on a map that says which claim it is making (#57) A kept word comes back in a sentence it was not found in (#56) A middleware rewrite behind Caddy dialled TLS at an http socket and took the site down for signed-in visitors with every test green (#55) Grammar got a place an answer can link to (#54); any word became askable, not just the ones Heidi chose to gloss (#53) Swiss Standard German became an output with its own gate (#52) — the product had only ever produced the spoken half, which quietly set people up to send a chat message to an insurance company The answer proposes what to do next (#51); the dashboard asks you something rather than showing you a list (#50) Start means start (#49); the chat got a room of its own (#48) A shared group link can be opened by the person it was sent to (#47)
One renderer for every surface — groups had been storing full answers and rendering them as plain paragraphs since the day they shipped (#45) One label per control, not a visible one and a hidden twin (#46) The home fold became the tool rather than a picture of it (#44) ai-kit ^0.15.0 → ^1.8.0, and the cast that was waiting on it deleted (#42) The evidence became the argument rather than a page beside it (#40); citations you can follow (#39) The headline promises an order in every language, not just four (#38) MIT licence (#37); five things the site said that a person would not (#36)