Skip to content

Releases: vyanhursky/joven

v1.0.0b8

v1.0.0b8 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 19 Sep 04:10

Changed

  • The app no longer bundles epubcheck, and the download drops from 223 MB
    to 201 MB on macOS. The jar needs a JVM the app cannot ship, so for the readers
    the app exists for it was 32 MB that never ran — while the two Setup rows
    explaining its absence were the most confusing thing on the page. epubcheck
    remains a development and CI dependency, where it earns its place as the only
    gate that validates the output as an EPUB rather than as a diff of the input;
    a reader who wants the twelfth check installs it and Joven finds it on PATH.
  • doctor reports epubcheck in one row instead of two. A separate java row
    was right while the app shipped the jar and the JVM really was the missing
    piece. Now it named something the reader never asked for, next to a second row
    saying the same thing. Java appears only where it is genuinely what is missing:
    a jar someone configured, with no runtime to run it.

Fixed

  • The README said macOS ships a JVM. It ships a stub that prints "Unable to
    locate a Java Runtime" and exits 1.

v1.0.0b7

v1.0.0b7 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 18 Sep 23:35

Added

  • A Quit button, top right of the page. The packaged app has no other off
    switch on macOS — it is a console binary in a bundle with no Cocoa event loop,
    so it has no Dock menu, ignores Cmd-Q, and is invisible to System Events, which
    left Activity Monitor as the only way to stop it. Refuses while a job is
    running, unless you confirm.

Fixed

  • Launching Joven while it was already running died silently. Binding a taken
    port raised OSError from inside the server, and a Finder launch has no console
    to print it to: the reader got a two-second bounce in the Dock and nothing else,
    with the running instance invisible and the icon apparently broken. A second
    launch now notices the first, reopens the page at it, and exits. A port taken by
    something that is not Joven says so and names --port. Joven also stops setting
    SO_REUSEADDR on Windows, where it means "bind a port that is already in use"
    rather than Unix's "do not wait out TIME_WAIT" — two servers on 8770, and no way
    to tell which one answers.
  • The install guide said quitting the app stops it, which on macOS there was no
    way to do.

v1.0.0b6

v1.0.0b6 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 18 Sep 22:31

Added

  • A downloadable app — Joven-windows-x64.zip, Joven-macos-arm64.dmg,
    Joven-linux-x64.tar.gz on each release. Double-clicking it opens the browser
    workflow: no Python, no PATH, no terminal. It carries kepubify and
    epubcheck itself, so the only thing left to install is Ollama and its model.
    About 220 MB to download, ~260 ms to start. Unsigned, so Windows and macOS warn
    on first run — docs/install-app.md walks through it.
  • joven doctor — checks the model server, the model, kepubify, Java and
    epubcheck, and reports what each missing piece costs rather than only that it
    is missing. Exits non-zero only when something stops a book being annotated.
  • An icon on the Windows and macOS builds, drawn from the README's horse by
    packaging/make_icon.py so there is only ever one copy of the art. Small sizes
    get the strokes thickened first, because the horse is line work and shrinks to a
    smudge otherwise. Linux keeps none: a tarball has nowhere to put one, so the PNG
    ships in the bundle for whoever writes a .desktop file.
  • A Setup tab in the browser UI, showing the same checks, and the page now
    opens on it when something required is missing instead of on a drop zone that
    cannot work. Where the model was never pulled it offers a Pull model button;
    cancelling is safe, because Ollama keeps what it has and resumes.

Fixed

  • doctor reported Java and epubcheck as working on any Mac without a JDK.
    macOS ships a /usr/bin/java stub on every install: it resolves like a real
    JVM and exits 1 with "Unable to locate a Java Runtime". So doctor said
    ready, promised twelve integrity checks, and the render ended in 1 of 12 checks FAILED — the stub's message quoted at a reader who was told they did
    not need Java. Joven now starts the JVM before believing in it, and a Mac with
    no JDK gets the documented java — not found and eleven checks that pass.
  • The macOS disk image had nothing to drag the app to. An app launched from
    the read-only image cannot be approved — the first-run warning has a single
    Done button and no way past it — so the window now carries an
    Applications alias, and docs/install-app.md says to
    use it and gives the System Settings approval steps that current macOS
    actually requires.
  • The missing-Java hint named winget on every platform, including Linux and
    macOS.
  • setx JOVEN_EPUBCHECK_JAR does not affect the terminal it is typed in; the
    README now says so, and documents PowerShell's Activate.ps1 — the
    extensionless activate beside it is the bash script and silently does nothing.
  • A UI test asserted that a detect job was still running when its POST returned,
    which the stub backend could beat about one run in six on Linux.

v1.0.0b5

v1.0.0b5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Sep 23:20

Added

  • joven ui — the whole workflow in one local page: drop an EPUB, detect with
    live progress and a Cancel that keeps every answer already given, review with
    span editing, render and verify with one button, download the KEPUB, browse the
    decision trace. Books live under ~/.joven/books/<sha256>/ with their sidecar,
    trace and outputs, so the command line can pick up where the page left off.
    See docs/browser-ui.md.
  • joven strip, and render refuses a book that already carries Joven
    footnotes — rendering onto one doubled every marker while looking plausible.
    inspect shows the marker count; detect warns and carries on.
  • A progress bar on detect: paragraphs, escalations, footnotes, last model
    latency, time left. Hidden when stderr is not a terminal; --quiet turns it off.
  • detect --workers N. Paragraphs are scanned concurrently and finished in
    book order, so the trace, --resume and the sidecar are identical at any worker
    count. Ollama needs OLLAMA_NUM_PARALLEL to match or the requests queue.
  • detect --backend openai for any server speaking the OpenAI chat protocol —
    llama.cpp, LM Studio, vLLM — with the same prompt, few-shots and JSON contract.
    Strict json_schema and the enable_thinking switch are learned from the
    server's first refusal rather than assumed.
  • Configuration. joven.toml, a user config file and JOVEN_* variables, in
    the order flag › environment › project file › user file › default. joven config
    shows every value and its source. Unknown keys and wrong types are errors.
    JOVEN_EPUBCHECK_JAR is unchanged.
  • joven reject, reset, status, diff, and detect --href / --range.
  • detect --ollama-url, also JOVEN_OLLAMA_URL.
  • Review page: j / k move between cards, Ctrl-Enter saves an edit, and the
    review API accepts new spans.

Fixed

  • The review page kept keyboard focus after a decision; the next a used to hit
    the first card on the page.
  • render and review warn when a sidecar was detected from a different file.

Infrastructure

  • mypy runs in CI with lxml-stubs.
  • A GitHub release is created from every tag, with the wheel and sdist attached
    and this file's section as the notes. A tag with no section fails before the
    PyPI upload.

Measured

A Vintage edition of The Crossing (160,262 words, 13,878 segments) on an RTX
4070 Ti SUPER with qwen3:8b, Ollama at its defaults:

one worker --workers 4
escalated to the LLM 3,046 (22%) 3,046
footnotes 735 734
trace order — identical
wall clock 15.2 min 13.4 min

The small gain is Ollama queueing the requests (OLLAMA_NUM_PARALLEL unset).
Cities of the Plain, 91,326 words, through the browser UI end to end: 2,272
escalations, 343 footnotes, 9 minutes with two workers, all 12 checks passed.

v1.0.0b4

v1.0.0b4 Pre-release
Pre-release

Choose a tag to compare

@vyanhursky vyanhursky released this 05 Sep 22:35

Windows is a supported platform. Everything here was found by running the tool
on Windows 11 for the first time — the suite, the guard scripts, and a whole novel
through detect, render and verify. The README had said "Windows is
untested", and untested turned out to mean four bugs, one of them silent.

Fixed

  • External tools are run by resolved path, not by bare name. shutil.which
    honours PATHEXT, so on Windows it resolves epubcheck.CMD — but
    CreateProcess can only start a .exe, so subprocess.run(["epubcheck", …])
    raised FileNotFoundError for a tool that was installed and on PATH.
    epubcheck_available() returned True and the run then died, which is the worst
    shape a dependency check can take. It affects every .cmd/.bat launcher,
    which is how epubcheck and most JVM tools arrive on Windows. Three integration
    tests failed on it.

  • epubcheck is found without a launcher at all. The official distribution is a
    zip holding epubcheck.jar and nothing else — no .bat, no .exe — so on
    Windows there was never anything for PATH to find. This was the silent one:
    epubcheck was reported missing, and verify reported it SKIPPED and passed,
    so the single external gate on the output stopped running while the run still
    looked clean. JOVEN_EPUBCHECK_JAR now points at the jar and it runs under
    java -jar.

  • The review server answers a refused POST instead of resetting the
    connection.
    do_POST sent 404 and 415 without reading the body it was
    refusing. Unread bytes left in the socket make Windows reset rather than close,
    so the client got WinError 10053 where the status code should have been — the
    refusal arriving as a dropped connection rather than as a 415. Whether the reset
    beat the response out of the buffer was a race, so this also flaked rather than
    failing honestly. The content-type refusal that stops a page in another tab
    writing to the sidecar is unchanged; it just says so now.

  • Output is UTF-8 even when redirected. A redirected stream on Windows falls
    back to the locale encoding, cp1252 on a stock install, so matríz written to a
    file came back as matr?z. Worse, joven add echoes the passage it matched, and
    typer.echo given a character outside cp1252 raises UnicodeEncodeError rather
    than degrading — after the sidecar has already been written. Accented Spanish is
    safe, since cp1252 covers it, and The Crossing contains no character outside
    it; that was luck rather than safety.

Documentation

  • The install guide covers the Windows route, and calls out the epubcheck jar step
    specifically, because skipping it does not fail loudly — it reports SKIPPED
    and passes.
  • Development covers Scripts\ rather than bin/, and the reinstall-while-running
    trap: Windows cannot replace an open file, so pip install -e . during a long
    detect leaves a half-uninstalled package and a venv that looks fine until it
    raises ModuleNotFoundError.
  • A .gitattributes settles line endings in the repository rather than per clone.
    Not cosmetic here: the load-bearing invariant compares bytes, so a stray \r in
    a tracked fixture would look like the renderer corrupting the book.

Infrastructure

  • CI runs the suite on windows-latest at the Python floor and ceiling, five jobs
    in total. The Windows epubcheck step deliberately creates no launcher, so CI
    exercises the jar route a real user takes rather than a wrapper invented for its
    own convenience.

Measured

A full run on Windows 11, against a different edition of The Crossing (149,995
words to the Knopf edition's 151,865) — RTX 4070 Ti SUPER, 32 GB, qwen3:8b:

macOS, Knopf edition Windows, this edition
escalated to the LLM 2,556 (21%) 2,544 (21%)
footnotes produced 726 731
integrity checks 12 of 12 12 of 12
wall clock 73 min 12.2 min

The escalation rate matches to within a percentage point and the footnote count to
within five on a book five thousand words shorter, which is the evidence that the
port changed behaviour nowhere. The wall clock is a GPU, not a platform: 0.3 s per
escalated call against 1.5 s on an M-series laptop.

v1.0.0b3

v1.0.0b3 Pre-release
Pre-release

Choose a tag to compare

@vyanhursky vyanhursky released this 05 Sep 22:35

Everything here was found by running the tool against two books it had never
seen — All the Pretty Horses and Suttree — which is the first time it has been
asked to work on anything but the novel it was written for.

Fixed

  • The EPUB 2→3 upgrade no longer breaks a valid NCX. read_package took the
    first dc:identifier and ignored package/@unique-identifier, the attribute
    whose only job is to say which identifier is the book's identity.
    sync_ncx_identifier then wrote that wrong value into the legacy NCX — so All
    the Pretty Horses
    , whose OPF lists an ISBN first and its real identity second,
    came out with an epubcheck NCX-001 error it did not have going in. Books with a
    single dc:identifier (The Crossing, Suttree) cannot show the difference,
    which is why one book was not enough to catch it.

  • Latin is no longer annotated as Spanish. A two-language detector cannot answer
    "neither": asked about Latin it must choose, and it does not choose English.
    Suttree — a novel with no Spanish in it — produced Stabat Mater Dolorosa. at
    SPANISH 0.94, above the accept threshold, which skips adjudication entirely and
    becomes a confident, wrong footnote.

    The obvious fix is wrong. Adding Latin to the detector dilutes the distribution the
    thresholds are tuned against: Dieciseis. drops from 1.00 to 0.54 and stops being
    accepted, and it is a documented Tier-1 strength that the LLM gets wrong. So the
    primary detector is untouched, and Latin is asked about separately as a veto on the
    accept path — the one place where being wrong is unrecoverable. The dialogue tag
    is stripped before the test
    , which is what makes it safe: Respóndele, he said.
    ranks Latin 0.64 / Spanish 0.34 with the attribution attached and 0.00 / 1.00
    without it. That is the same tag distorting a third measurement, for the same
    reason it distorted the other two.

    Measured over 1,094 known-Spanish passages from two books: zero wrongly vetoed.

  • The model is told that Latin is not Spanish. The veto guards only the accept
    path. Two of Suttree's three Latin passages escalated instead, and the model,
    asked directly, agreed they were Spanish and translated them. The prompt and its
    few-shot examples now cover this.

  • --resume checks the Tier-1 verdict before reusing an answer. Tier 1's two
    outcomes call different prompts — an accept gets translate ("this is Spanish,
    render it"), the band gets adjudicate ("is this Spanish at all?") — and a
    translate-only answer always says yes. Resuming across a Tier-1 change would
    therefore hand a "yes" to a segment the new code wants adjudicated, quietly
    reinstating the footnote the change existed to prevent. The Latin veto is exactly
    such a change.

Documentation

  • The install guide covers uv itself and the ~/.local/bin PATH step. Following
    the previous instructions on a clean machine got you joven: command not found.
  • Results now cover three books, including the control run and the precision figure
    it produced.

Measured

Re-running the Suttree control after these fixes: six false positives down to
two
, in 177,257 words. One Latin passage was stopped by the Tier-1 veto, two by
the prompt, and Vag. went with them. Escalation rate and wall clock are unchanged
(2,548 calls, 63 minutes), so nothing was traded for it.

Still open

  • No suh. → "No sir." — dialect English that the similarity veto misses at a 0.67
    ratio, just under the 0.75 threshold.
  • Ay. — genuinely ambiguous out of context; English here, Spanish elsewhere.

v1.0.0b2

v1.0.0b2 Pre-release
Pre-release

Choose a tag to compare

@vyanhursky vyanhursky released this 05 Sep 22:35

Fixed

  • Books using HTML named entities no longer fail to parse. XML defines five
    entity names; XHTML in the wild uses the full HTML set, so any book containing
    &nbsp;, &mdash; or &rsquo; was refused with Entity 'nbsp' not defined
    and a traceback. The Knopf edition of The Crossing happens to use none, which
    is why one book was enough to hide this. Named entities are now resolved before
    parsing, on both sides of the text-preservation comparison so the invariant is
    unaffected.
  • Font obfuscation is no longer misreported as DRM. META-INF/encryption.xml
    is how the IDPF and Adobe font-obfuscation schemes declare themselves as well as
    how real DRM does, and unencumbered trade EPUBs carry it routinely. Refusing on
    the file's presence rejected those books with advice to strip DRM that was never
    applied. The check now reads the encryption algorithm and refuses only genuine
    encryption, naming the resources it cannot read.
  • An unparseable document reports an error and exits, instead of raising a
    traceback out of inspect, detect or add.

Added

  • Releases publish to PyPI from a tag, via Trusted Publishing — no API token
    in repository secrets. The install path becomes uv tool install joven-ebook-annotator instead of a venv and an editable checkout. A guard fails
    the build when a tag disagrees with the version in pyproject.toml, because
    PyPI will not let a version number be reused. See
    docs/releasing.md.
  • joven detect --resume TRACE. A full run is 73 minutes and the sidecar was
    written only at the end, so an interruption at minute 70 lost all of it. The
    trace is now flushed per record and can be replayed: every model answer it holds
    is reused and only unreached segments cost anything. Tier 1 and the suppression
    gates still run over the whole book, so a resumed run reflects the current code;
    recorded errors are retried rather than inherited.