Skip to content

v0.4.0: natural reading (sentence-aware chunking, number/currency reading) + Report a problem - #1

Merged
harshmathurx merged 22 commits into
mainfrom
v0.4-natural-reading
Oct 7, 2026
Merged

harshmathurx merged 22 commits into
mainfrom
v0.4-natural-reading

Conversation

@harshmathurx

Copy link
Copy Markdown
Owner

Lector now reads in whole sentences and says numbers the way a person would.

Why

  • Chunking was cutting phrases. Chrome's sentence splitter never breaks before a lowercase word, so all-lowercase posts (common on X) became one giant "sentence". The chunker then cut it at arbitrary words, e.g. "…the more exposure | i got talking…". Kokoro restarts its intonation at every cut, which is what sounded off.
  • Numbers and symbols were read literally. "$2.5M" was read as "dollar two point five em".

No LLM. Lector doesn't load one. Adding one would cost a 350–500 MB download, seconds to minutes before the first word, and a model that can rewrite words, which breaks the page highlighting. Rules are instant and testable.

What changed

  • Chunker (shared/chunker.ts)
    • Its own sentence pass handles lowercase text, using sbd's abbreviation list (MIT), initials and 1/ thread numbering.
    • Segments are whole sentences up to 420 chars; short neighbours are joined while they stay under 140 chars.
    • Over-long sentences break at a scored point: ; : — first, then commas, then clause words, then a space. It avoids short leftover fragments and never breaks inside a number, currency amount or unit.
    • Pauses depend on the punctuation (?/!, ., : ; —, ,, or none).
    • The short first segment for a fast start is kept.
  • Normalizer (shared/normalize.ts), rules ported from misaki (Apache-2.0), plus number-to-words (MIT, about 1.3 KB). It covers:
    • currency with magnitudes ($ £ € ₹ ¥, k/M/B/bn/cr/lakh), including "forty billion dollar valuation"
    • percentages and percent ranges, YoY and similar
    • ranges (phone numbers are left alone), ordinals, fractions, multipliers
    • units, with "5m" read as metres or million by context
    • ISO and slash dates, and times
    • Roman numerals in context ("World War II", but "Type C" stays a letter)
    • initialisms Kokoro mangles, versions, @Handles and #hashtags, X 1/ numbering
    • Output is checked to stay stable under kokoro-js's own normalizer.
  • Engine guard (engine.worker.ts): a segment over 500 phoneme tokens is split and its audio joined. kokoro-js used to silently cut off the end.
  • Extraction (content.ts): Draft.js blocks (X Articles) are treated as paragraphs. When Readability's copy of a block maps to a live wrapper, the live paragraphs are read instead. Output on paulgraham.com and Wikipedia is identical to main.
  • Report a problem in Settings: opens a pre-filled GitHub issue containing only the Lector and Chrome versions, OS, engine, voice quality, voice, speed and last error. It never includes the page address or article text, and needs no new permissions.
  • Version 0.4.0; README, CLAUDE.md and THIRD_PARTY_NOTICES updated.

Verification

  • tsc is clean and bun test passes (205 tests). Your screenshot paragraphs are fixtures, and each review finding has a test.

  • scripts/e2e.mjs with real Kokoro passes every check on WebGPU and NO_GPU at the final tip.

  • That run caught a release blocker the unit tests couldn't: the guard called .call on the tokenizer, which doesn't have one. Fixed in 8d5b337.

  • The guard fired on a real page (726 tokens) and the full 41.8 s of audio was produced, so nothing was truncated.

  • Bench, warm runs, one run each, so a rough comparison only:

    Backend Time to first audio, main → this branch Speed (RTF) Stalls
    WebGPU 2.44 s → 1.46 s unchanged 0
    WASM 5.48 s → 2.20 s unchanged 0
  • axe finds zero violations on the Settings sheet.

  • A high-effort code review found 10 issues, all fixed.

Needs a human

  • Listening by ear. Old and new audio for the screenshot paragraphs and a number-heavy paragraph is in the session scratchpad (verify/ab/).
  • A real X Article. The extraction fix was only tested on fixtures, because X needs a login.
  • Phonemes not checked. "ay M" for a.m. wasn't checked at the phoneme level.

🤖 Generated with Claude Code

harshmathurx and others added 22 commits October 7, 2026 13:19
Wrap the kokoro-js tokenizer to detect >500 phoneme tokens before
inference, then split near the middle (; : , space) and join the audio
with a 60ms gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Treat Draft.js blocks as paragraph blocks, separate block boundaries in
paragraph text, and split live blocks at <br><br> runs. Adds an extraction
check script and HTML fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wikipedia)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts to chunker.test.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d punctuation pauses

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, money, units, dates, symbols

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… read as 'slash'; tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cr = crore, yr = year

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; bump version

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opens a prefilled GitHub issue with Lector/browser/engine details. Never
includes the page URL, title or text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en a copy's leaf maps to a wrapper

Reverts the <br><br> splitting (paragraphs lost their element, breaking Alt+click /
listen-from-here) and the block-spacing change (broke relocate() after SPA re-renders).
Extraction on paulgraham.com and Wikipedia is identical to main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hains, scores, meters

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…double tokenization in length guard

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e caught every segment failing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@harshmathurx
harshmathurx merged commit f49c46f into main Oct 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant