Skip to content

Releases: SikamikanikoBG/patzer

Patzer v7.18.1

Choose a tag to compare

@github-actions github-actions released this 05 Oct 03:57

[7.18.1] — 2026-10-05

Moves land the instant you drop them

  • Fixed: a dropped piece jumped back to its square until the server
    answered.
    In a timed game the page redraws ten times a second for the
    clock, and every redraw put the board back to the last position the server
    had confirmed, so your move only appeared after a round trip. A drag still
    in progress was reset on the next tick too, and arrows you had drawn
    vanished. On bullet that felt like input lag. The board now only updates
    when something on it actually changes, and your move is on the board, with
    its sound, the moment you let go. The server's answer just confirms it, or
    puts the board back if it rejects the move.
  • Premoves right after your move. Until the server confirmed your move
    the board still thought it was your turn, so a quick premove was sent as a
    real move and rejected. It is now queued as a premove.

Merged contributions (thanks, @Fristail27)

  • Russian Learn, puzzles and opening trainer. Every lesson text (kid
    versions included), the tactic puzzles and the opening trainer are now in
    Russian, and the locale check covers Russian lesson texts like German ones.

Patzer v7.17.1

Choose a tag to compare

@github-actions github-actions released this 27 Sep 08:39
76cf521

[7.17.1] — 2026-09-27

Finished games no longer hang on "Saving game…"

  • Fixed: after a game against the bot, the game-over card spun on "Saving
    game…" forever.
    The game was saved — it just couldn't be reached from
    there. Since 7.14.0 the page closes the bot socket as soon as the game is
    over, and the server announced the end (game_over) a few milliseconds
    before confirming the save (game_saved), so the confirmation arrived on a
    socket that was already closed. In a real browser that happened every
    time. The server now saves first and puts the game's id on game_over
    itself, and the page closes the socket only once it knows the id.
  • Fixed: [classify-user-move] Error: engine not started in the server
    log at the end of a bot game — the analysis engine was shut down while the
    last move was still being classified.

Tests: a browser scenario that finishes five games in a row and opens each
one's review (0/5 on 7.17.0, 5/5 now), and a protocol check that game_over
carries the saved game's id.

Patzer v7.17.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 07:57
9c04999

[7.17.0] — 2026-09-27

The coach coaches

The coach used to narrate: it named the move, repeated its label and stopped.
It saw nothing but the one move — not why it was bad, not what was better,
not the player's own games. Worse, it could contradict the engine: in
discussion #37 Game Review called 3…Nf6?? (which allows 4.Qxf7#) a blunder
while the coach called it a great move. That wasn't a small model's fault —
a 27B model did the same, three times out of three, because the prompt gave
it nothing to explain and an example sentence that praised development.

  • Fixed: the coach can no longer contradict the engine. Every answer is
    checked against the verdict before it is shown. Praise for a mistake gets
    one retry with a correction, and if the model does it again the player gets
    a plain answer built from the facts instead. The prompt now puts the
    verdict first, forbids praise for mistakes outright, and the example
    sentences that small models copied word for word are gone in every
    language. (#37)
  • New: why a move is good or bad. The server works out what a move
    allows, leaves hanging, wins, forks, stops or threatens (chess.js), and
    the opponent's best answer as a short line (Stockfish) — "this lets the
    queen take on f7 with checkmate", not "a blunder".
  • New: the better idea, and alternatives. The best move and the other
    moves that are nearly as good, each with what it achieves ("the pawn to g6:
    it stops the checkmate threat and attacks the queen").
  • New: the coach remembers your games. Your recurring mistake types over
    the last 30 analysed games (allowing a quick mate, leaving a piece
    unprotected, missing a mate, a win or a tactic), your weakest phase, your
    best finds, and positions you got wrong in the opening trainer — brought
    up when the move on the board is the same kind.
  • New: a rule to remember and a next step. Each mistake ends with a rule
    of thumb for that kind of error, and the coach links the matching Learn
    lesson and your puzzles.
  • New: hints that coach. A hint now knows the position — a mate threat,
    pieces under attack, a loose enemy piece, undeveloped pieces, an uncastled
    king, and what the engine's best move achieves — and points you there
    without naming the move.
  • Play gets the same coach as Game Review. Play never sent an evaluation
    or engine line; the coach now runs its own short engine search, so a move
    in a live game is explained as fully as one in Game Review. The in-game
    auto-coach uses the same pipeline and guard.
  • Fixed: Bulgarian and Russian "better move" read as praise. Found while
    testing the guard against every verdict in every language.
  • The coach panel no longer shows a server error message as if it were
    coaching, and keeps paragraph breaks.

Tests: 46 new — tactics detection, the coaching facts, the guard in all five
languages (including every verdict phrase), the player memory (and its speed:
30 games in under a second on a real account, down from 17.8 s during
development), and the #37 regression end to end through the real route with a
fake LLM that praises the blunder. Against the 7.16 code that suite fails.

Patzer v7.16.1

Choose a tag to compare

@github-actions github-actions released this 27 Sep 05:25
a8f9cb2

[7.16.1] — 2026-09-27

vLLM works again, and CI now runs the regression suites

  • Fixed: the vLLM coach broke in 7.15.0. Adding DeepSeek moved vLLM's
    model list from /v1/models to /models, which vLLM doesn't serve — so
    Test in Admin → System failed on every existing vLLM setup. Adding
    /v1 to the URL made the model list work but broke everything else
    (/v1/v1/chat/completions): Test ALL models, the coach and Game Review.
    vLLM's routes are back under /v1 of the bare server URL, and a URL that
    already ends in /v1 works too, so nobody has to change their setting back.
    Ollama and DeepSeek are unchanged.
  • New: a regression suite for the LLM providers
    (server/test/llm-providers.test.ts, 33 tests). Fake Ollama, vLLM and
    DeepSeek servers over real HTTP, each serving only the routes the real one
    has; every entry point (model list, model test, coach stream, JSON review,
    the Admin → System buttons) for every provider and every way of writing the
    URL. Against the 7.16.0 code it fails 10 tests — exactly the two symptoms
    above.
  • CI runs the regression suites on every PR: test:e2e, test:resume
    and the two real-browser suites test:ui and test:resume-ui, a boot
    check of the Docker image, and — when lesson content changes —
    verify:lessons. CONTRIBUTING and the PR template now ask for a
    regression test that fails without the fix with every bug fix.

Patzer v7.16.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 04:45

[7.16.0] — 2026-09-27

A Learn section, Russian, and a round of fixes

Patzer can now teach chess, not just review it: 54 interactive lessons from
"how the knight moves" to rook endgames, in English and German. It also speaks
Russian now, its fifth language. Thank you to
@eric-gpu (Learn, opening-trainer follow-ups,
Docker coach setup, two security and hardening fixes — #46–#51, #53, #54) and to
our newest contributor @Fristail27 (Russian —
#52).

Both the Learn section and the opening trainer are marked beta: they work,
but their texts are still being refined. That is also why Russian shows the
trainer and Learn screens in English for now; they get translated once the
wording settles.

Added

  • Learn section (beta). A new Learn page with 54 interactive lessons in four
    levels — New to chess, Beginner, Intermediate, Advanced: how the pieces
    move and every rule, how moves are written down, basic checkmates, forks,
    pins and the other tactics, mating patterns, opening principles, pawn and
    rook endgames. A lesson is a short explanation with animated examples, then
    tasks: collect stars, tap squares, find the move (Patzer answers), play an
    ending out against Stockfish, or answer a question. A wrong move gets a
    reason where the position shows one (stalemate, a check that isn't mate);
    "Show the solution" draws the arrows and you play the moves yourself; the
    explanation can be looked up again during the tasks. Each lesson earns one
    to three stars, stars add up to levels, and there are three new
    achievements. Progress is stored per profile on the server, an unfinished
    lesson resumes where you left it, kid mode shows the same lessons in
    simpler words, and a Home tile suggests the next lesson. The tactic, mate
    and endgame tasks are puzzles from the Lichess puzzle database (CC0). Every
    task is checked twice: by the test suite (legal moves, real mates, star
    tasks solvable in the stated number of moves) and by
    npm run verify:lessons, which asks Stockfish whether each answer really is
    the one clearly best move. Lesson texts in English and German. Marked beta
    while it is being refined. (Roadmap #7; #49, #50, #51)
  • Russian (ru). UI, setup wizard, coach persona and prompts, move
    narration and Game Review prose, with all four Russian plural forms (one /
    few / many / other). A key-parity test keeps ru.json complete; the
    beta trainer and Learn texts (plus a few strings from this same release) are
    listed as pending and fall back to English until they're translated.
    (#20, #52)

Changed

  • Opening trainer is marked beta. It works, but it is still being
    refined, so the Trainer tab carries a small "Beta" badge. (#48)
  • Opening trainer: easier first steps, fairer misses. A new line starts
    with "Do you know this line already?": watch it first (arrows on every
    move, nothing counted) or practise right away. One wrong try no longer
    lands in the review — the second one (or "Show me the move") does. "Practice
    this line" from your repertoire stops before one of your own moves that the
    analysis calls a mistake in most of your games. The review queue turns over
    at your own midnight instead of UTC's, and a move you no longer play can be
    removed from it. Built-in line names are translated. In the review, a
    first wrong try gets a hint and one more go; after the answer it moves on
    by itself. (#48)

Fixed

  • Connecting the AI coach from Docker. The setup wizard rejected
    http://host.docker.internal:11434 — the very address the README
    recommends — as invalid_url, and a failed test only said "unreachable" or
    "fetch failed". Now that address is accepted, and a failed test in the setup
    wizard and Admin → System explains the usual cause: inside Docker
    localhost is the container itself, and Ollama has to listen on the network
    (OLLAMA_HOST=0.0.0.0). docker-compose.yml has an opt-in (commented-out)
    line that maps host.docker.internal on Linux too, and the README
    troubleshooting entry spells out both steps. (#37, #53)
  • German admin texts for DeepSeek and the hosted engine. The DeepSeek
    fields and the chess-api.com note in Admin → System were hard-coded in
    English; they now come from the locale files, with German translations,
    and the German coach description mentions DeepSeek like the English one. (#54)
  • Lichess import: one broken game no longer fails the whole import. A
    line of the export that is not a whole game is skipped instead of answering
    500, and an account that already has an import waiting gets "an import is
    already running" instead of queueing more requests behind it. (#47)

Security

  • The GitHub star count no longer sends the page address to GitHub. The
    star badge also shows on the sign-up and password-reset pages, whose URLs
    carry an invite code or a reset token; the request to api.github.com now
    goes out without a Referer. On top of that the server now sends
    Referrer-Policy: strict-origin-when-cross-origin instead of
    no-referrer-when-downgrade, so no link or request to another site
    carries a page's path and query any more. (#46)

Patzer v7.15.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 12:56

[7.15.0] — 2026-09-26

German, Lichess, invites, an opening trainer — and two new contributors

The biggest community release so far: seven pull requests from two first-time
contributors. Thank you, @eric-gpu (German,
Lichess import, invite-only sign-up, both sound sets and the opening trainer —
#39, #40, #41, #42, #43, #45) and @aminghuf
(DeepSeek, the opt-in hosted engine and automatic Chess.com sync — #44).

Nothing changes unless you ask for it: the new sounds default to Classic,
sign-up keeps whatever mode you had, and Game Review stays on the bundled
Stockfish. The one thing that runs on its own is the Chess.com sync below — it
only touches profiles that already have a Chess.com username.

Added

  • German (de). The whole app — UI, setup wizard, coach persona and hard
    rules, move narration, Game Review prose — in German. The coach prompt is
    tuned for small local models: it asks for the informal "du" without using
    it as a form of address (mistral:7b otherwise ended most answers' first
    sentence with "…, du!"), and it has no example sentences, which such
    models copied word for word — "Solide Entwicklung" even for a blunder.
    German is also the first
    coach language whose piece names need an article and a grammatical case
    ("der Springer zieht", but "schlägt den Bauern", "du hast einen Turm
    mehr"), so move and material phrasing decline properly instead of gluing
    bare nouns together. (#18)

  • Soft check and game-end sounds. A new Check & game-end sounds choice
    under Settings → Sound & assistance: Classic keeps the original bells
    (and stays the default), Soft swaps those two for marimba-style wooden
    bars that sit closer to the wood-knock move sounds — two short rising notes
    for check, and the same G → E → C cadence as before on marimba with a faint
    bell underneath for game end. Still fully synthesized, no audio files, with
    a preview button next to each option. (#36)

  • Real-board move sounds. A second choice under Settings → Sound &
    assistance
    , Move sounds: Classic keeps the synthesized wood knocks
    (default), Real board plays recordings of real pieces set down on a
    wooden board for moves, captures and castling (the move click twice, king
    then rook). Check, promotion and game end keep their sounds and still follow
    the check & game-end choice. The two clicks (~30 KB each) are cut from a CC0
    recording on Freesound and only load once Real board is picked; until
    they have loaded — or if they can't — moves fall back to the synthesized
    knocks. (#36)

  • Import games from Lichess. Next to the Chess.com importer: set a
    Lichess username in Settings and Import from Lichess appears in Game
    Review. Uses Lichess's public export API (no token), sends a real
    User-Agent, makes one request at a time from the server and tells you to
    wait a minute instead of retrying when Lichess answers 429. Standard chess
    only — variants, aborted and unfinished games are skipped — and importing
    twice doesn't duplicate anything. Correspondence games land in the same
    daily pool as chess.com's. Import errors (unknown player, Lichess down)
    are shown now instead of failing silently, for Chess.com too. (#28)

  • Invite-only sign-up. Admin → System now has three answers to "who can
    create an account": anyone, invite only, nobody. Admin → Users gets an
    Invites list: each invite is a code and a /signup?invite=… link, limited
    to a number of accounts and a lifetime (one account, one week unless you
    pick otherwise), and can preset the language and coach level of the
    accounts made with it — so a coach can hand a class a "Spanish, beginner"
    link. Copy the link again later, withdraw it, and see who signed up with
    it. The sign-up page checks an invite as soon as it's entered, so a dead
    link is reported before anyone fills in the form. Existing installs keep
    what they had: on becomes "anyone", off becomes "nobody". (#26)

  • Opening trainer. The Openings page has a second tab, Trainer. Pick a
    line — one of 16 built-in main lines for White or Black, or any position in
    your own repertoire tree ("Practice this line", continued the way most of
    your games went on) — and Patzer plays the other side while you find your
    moves. A wrong move is taken back so you can try again; "Show me the move"
    draws the arrow. Every move you miss is stored on your profile and comes
    back in a daily review queue until you've got it right on three different
    days. (Roadmap #6)

  • DeepSeek as a coach provider. A third tab next to Ollama and vLLM in
    Admin → System, for machines too small to run a model. The API key comes
    from DEEPSEEK_API_KEY (wins, Docker-secret friendly) or the Admin field,
    and is never sent back to the browser — only whether one is set. Ollama and
    vLLM stay the local-first default. (#44)

  • Optional hosted analysis engine. ENGINE_BACKEND=chessapi sends Game
    Review positions to chess-api.com (Stockfish 18 NNUE) instead of the bundled
    binary — meant for a Pi/NAS or a public try-it instance. Off by default:
    local stays the default and nothing leaves your machine unless you set it.
    On an API failure it retries once, then finishes that game locally, so one
    review never mixes two engines. (#44)

  • Automatic Chess.com sync and review. Profiles with a Chess.com username
    get their new games pulled in the background (every 15 minutes by default,
    CHESSCOM_SYNC_MINUTES, 0 turns the timer off), the newest ones analyzed
    (at most three per tick), and — when an LLM is configured — the AI Game
    Review written without a click. Finished bot and friend games get their
    review the same way. A failed review never blocks the import; it is retried
    on the next sweep. (#44)

Changed

  • games.source accepts lichess. SQLite can't alter a CHECK
    constraint, so existing installs rebuild the games table once on startup:
    in one transaction, with foreign keys off for the swap so no analysis,
    puzzle or rating history is cascade-deleted, and with the row count and
    foreign_key_check verified before it commits. If anything goes wrong it
    rolls back, logs, and the server starts as before without the Lichess
    import.

Fixed

  • Admin → System's Save button didn't save the sign-up settings. It only
    saved the coach and engine fields; the sign-up switches were saved by
    "Save email settings" further down, so changing them and pressing Save
    quietly did nothing. Save now saves the whole page.
  • English text on translated screens. Around 140 strings were hard-coded
    in components (Settings descriptions, the greeting, phase names, the game
    list, key moments, admin pages…) or used a translation key that existed in
    no locale file, so they showed English in every language. They are locale
    keys now. Achievement titles and weekly plan goals come from the server in
    English; they are now translated in the browser from their id/kind, with
    the server text as fallback. Bulgarian and Spanish fall back to English for
    the new keys, exactly as before, until someone translates them.
  • Language pickers showed every language in the current one. After
    switching to Bulgarian, the Settings select read "Немски" for German — no
    way back for someone who can't read Cyrillic. Settings, the setup wizard
    and Admin → Users now list each language in itself (English, Български,
    Español, Deutsch), like the command palette already did.

Patzer v7.14.0

Choose a tag to compare

@github-actions github-actions released this 23 Sep 03:50

[7.14.0] — 2026-09-23

Your game is still there when you come back

Switching to another app mid-game and coming back used to lose the game. Not
"lose the connection" — lose the game. This release fixes that for bot games and
for games against a friend, which failed the same way for two different reasons.

  • Fixed: a game against the bot was never written down until it ended. It
    lived entirely inside the WebSocket that created it, so closing the tab — or a
    phone freezing the browser while you answered a message — destroyed it with no
    trace. Every move is now snapshotted server-side, and reconnecting picks the
    game up mid-position. It survives the tab closing, a reload, and a server
    restart. Your clock is not charged for the time you were away: PvP charges
    it because a human is waiting, but the bot is a process that wasn't even
    running, and flagging you for closing a tab would punish the exact thing this
    release is about. Bot games are unrated, so there's nothing to protect.
  • Fixed: the page never reconnected. onclose was literally
    /* ignore */ — so after a phone sleep the board still looked alive, the clock
    kept counting down, and nothing you did reached the server. The socket now
    retries with a backoff, and retries immediately on the events that actually
    fire when a phone wakes up. This is what was killing longer games with a
    friend: the server kept those games correctly all along, but the browser had
    quietly stopped talking to it.
  • New: "Continue where you left off." Until now there was no route back into
    an unfinished game at all — a friend game could only be reached if you still
    had its URL. The Play screen now lists the bot game you left and every
    unfinished game with a friend, with how far in you are and which colour you're
    playing. One button to resume, one to discard.
  • New: the clock measures real time. It used to count interval ticks rather
    than elapsed time, and browsers throttle timers in background tabs. It now
    measures real time, and freezes while the socket is down instead of counting
    down a game you aren't connected to.
  • Fixed: the GitHub star count from 7.13.0 never appeared. The page's own
    Content-Security-Policy blocked the request and logged a violation on every
    page load. Found by the new browser test.

Two new test suites, because "it reconnects" is not something to take on trust:
npm run test:resume drives the protocol against a real server (kill the socket,
reconnect, keep playing), and npm run test:resume-ui does it in a real browser,
including pulling the network out from under a live game.

Patzer v7.13.0

Choose a tag to compare

@SikamikanikoBG SikamikanikoBG released this 20 Sep 06:14

[7.13.0] — 2026-09-20

Star on GitHub link

Patzer is free and self-hosted, so there is exactly one thing it can ask for in
return. This release adds a Star on GitHub link — in three places that stay
out of the way, and nowhere near the board.

  • In the footer, beside the version chip. On every page, at the same size
    as the version, with the repo's live star count next to it. Deliberately not
    in the top bar: that bar already measures itself and collapses nine nav pills
    to icons when the labels don't fit a laptop, so a star button up there would
    have cost somebody a destination.
  • At the bottom of the changelog. Having just read what the last few
    releases handed you for free is the one moment to ask.
  • Under the login card. That is where someone sizing Patzer up for their
    own box lands — and until now nothing in the product said where the source
    lives. The word github appeared exactly once in the whole web app, in a
    help string on the admin page.

The count comes from GitHub's public API once a day per browser and is cached in
localStorage. If the box has no internet, the call fails or you are
rate-limited, the link still works and simply shows no number. Click it once and
it goes muted — Starred — and stops nudging on that device for good. The
browser cannot actually check whether you starred without your GitHub
credentials, which Patzer will never ask for, so it takes the click at its word.

Bulgarian and Spanish strings included. Five new tests cover the cache, the
offline path and the once-only nudge; npm test is at 100.

Patzer v7.12.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 13:52

[7.12.0] — 2026-09-19

Premove queues, an instant board, and a clock you can read at a glance

Three changes aimed squarely at playing fast.

  • New: queue up to six premoves. chessground holds exactly one premove, so
    the queue is Patzer's own. While the opponent is thinking you can keep
    playing: the board shows the position as if every queued move had been
    made — your pieces are already sitting where you put them, with an arrow per
    move and the move list under the board — which is the trace you actually want
    rather than a single ghost arrow. Each move is re-checked against reality the
    instant the opponent moves: if it's still legal it's sent immediately, if it
    isn't, the whole queue is dropped rather than half-played. Escape cancels, so
    does clicking the chip.

    The edge cases it handles, because a premove queue is mostly edge cases: a
    queued move that gives check ends the queue (the position after it isn't one
    you can plan from); the queue clears on takeback, on game over, and whenever
    the server rejects anything; premoves are ignored while you're browsing back
    through the game; promotion works (the picker runs against the queued
    position, not the live one); and a premove skips the blunder warning, because
    the point of premoving is speed.

  • Removed: the piece slide animation. It was 200ms of dead time on every
    move — the board knew the position before you could see it, which reads as
    lag on a fast time control. Pieces now appear where they are.

  • The clock grows under 30 seconds. The side actually on the clock doubles
    in size below 30s (18px → 36px) so it's readable without looking away from
    the board, and keeps the red pulse it already had below 10s. The idle side
    stays small — a big number on a clock that isn't running is just noise.

Patzer v7.11.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 12:46

[7.11.0] — 2026-09-19

It tells you when it's out of date, and the header stops falling off the screen

Four community pull requests landed today — thanks to @Vaibhavs25, who
picked up four of the open issues in one sitting.

  • New: Patzer notices when a newer release is out. A self-hosted app can't
    update itself and shouldn't try, but it can stop you discovering six months
    later that the thing you wanted was fixed in July. Patzer now asks GitHub at
    most once every six hours whether there's a newer tag, and shows one quiet
    line at the top of the page when there is, with a link to the release notes
    and a reminder to pull the image. Dismiss it and that version never nags you
    again. It sends nothing about you or your games — it's an anonymous read of a
    public page — a failed check is silent, and you can switch the whole thing
    off in Admin → System or with UPDATE_CHECK=0.
  • Fixed: the header overflowed the window. With nine nav items, an admin
    account and the language toggle, the top bar wrapped onto two lines and
    pushed the ES button and the user chip off the right-hand edge — on a
    1440px laptop, which is most laptops. Two things were wrong: the bar was
    capped at 1280px wide no matter how big your monitor was, so the nav never
    had room even at 1920; and the pills overflowed their container instead of
    giving way, so they painted over the user menu. The bar now uses the width
    it has, and the nav measures itself — when the labels don't fit, the pills
    become icons with tooltips, so every destination stays one click away instead
    of some being hidden. Measuring rather than picking a breakpoint matters
    because an admin carries two more items than a regular profile and Bulgarian
    labels are wider than English ones: any fixed width would be wrong for
    somebody. Users and System also moved into an Admin dropdown. (#22, PR by
    @Vaibhavs25, finished off afterwards)
  • Fixed: the coach's mute button didn't always mute. Two separate bugs,
    both in the gap between "you press mute" and "sound comes out". The panel
    read a stale copy of the mute flag when a reply finished streaming, so muting
    mid-answer still read the answer aloud. And the speech helper has two
    deferred paths — a one-tick delay to dodge a Chrome bug, and a wait for the
    OS voice list, which on Windows is usually empty on first use — neither of
    which was cancellable, so a mute landing in either window was simply ignored
    and the coach carried on talking. Both now check a generation counter that
    cancel() bumps. Covered by tests that fail against the old behaviour.
  • New: the phone moves sheet is draggable. Swipe up on the Moves button
    to open it, drag the handle down to dismiss, with the sheet following your
    finger. Tapping still works, the move list inside still scrolls, and
    prefers-reduced-motion disables the gesture rather than just shortening the
    animation. (#23, PR by @Vaibhavs25)

Also: the Spanish/Bulgarian strings for the new UI, and npm test is now at 89
tests.