Vibecode LingQ Premium
track this build5 phases, 15 steps, beginner friendly0%The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For LingQ Premium, import permitted text and audio, mark known words, and review vocabulary in context. The hard boundary is content library, import pipeline, multi-device sync, community, tutors, and polish, plus content, pedagogy, and network.
You are building a lean indie version of LingQ Premium. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # LingQ Premium · indie build Import text and optional aligned audio, read with unknown words highlighted, look a word up and save it with its sentence as context, and review saved words on a spaced schedule that treats a word met in reading as a review, with progress measured in words you have actually seen. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, installable and offline capable | reading and reviewing happen on a phone in gaps, often without signal | | Storage | IndexedDB locally with export, no account required | your reading history is yours and there is no reason for it to need a server | | Tokenisation | Intl.Segmenter for word segmentation | the browser already knows word boundaries in many languages, including ones where spaces do not mark them | | Scheduling | An open spaced repetition algorithm you can read | a schedule you cannot inspect is one you cannot trust when it says you have forgotten something | | Audio | Optional aligned audio with sentence-level timings | listening while reading is the most useful feature and needs alignment rather than just playback | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the build. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Texts you are entitled to use, in your target language** · free with openly licensed material - Why: The whole build works on your material, and importing something you may not use is both the wrong thing and legally your problem rather than a platform's. - Get it: Openly licensed texts, books you have bought in a format you can read, or your own writing. Record the source for each. - Verify: At least three texts with recorded licence notes - [ ] **A dictionary file for your target language** · free with an open dictionary - Why: Lookups should work offline and without a service. A licensed dictionary file makes the tool self-contained. - Get it: Use an openly licensed bilingual dictionary in a format you can parse, and record its licence. - Verify: A lookup returns a gloss with no network access - [ ] **A sample in a language without spaces between words** · free - Why: Phase 1's segmentation check is meaningless without one, and a naive tokeniser looks correct until it meets one. - Get it: A short openly licensed passage in Japanese, Chinese or Thai. You do not need to read it to check that segmentation produced words rather than one long token. - Verify: Segmentation produces plausible word boundaries in it - [ ] **A phone to install it on** · free - Why: Reading and reviewing happen in gaps, away from a desk, often with no signal. Desktop-only testing will produce a tool you do not use. - Get it: Serve over HTTPS with a tunnel and install to the home screen. - Verify: It installs and works with networking disabled ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The licensed content library. - Tutors and community. - A curriculum or accredited instruction. - Multi-device sync. - content library, import pipeline, multi-device sync, community, tutors, and polish - licensed course catalog - expert curriculum - human feedback - large learner network and certification If one of those is essential to you, that is the reason to keep paying for LingQ Premium, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · LingQ Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a reader that turns text you own into language study, tracking known words and scheduling review to replace LingQ Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Import only material you are entitled to use. The library is most of what the subscription buys, and building a tool that helps someone copy a paywalled catalogue is not what this is. Your own books, openly licensed texts and material you have bought are the input. This also teaches nothing and certifies nothing. It tracks what you have seen, schedules what you should see again, and shows progress. Saying that plainly is more useful than implying a curriculum that does not exist. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, installable and offline capable · reading and reviewing happen on a phone in gaps, often without signal - Storage: IndexedDB locally with export, no account required · your reading history is yours and there is no reason for it to need a server - Tokenisation: Intl.Segmenter for word segmentation · the browser already knows word boundaries in many languages, including ones where spaces do not mark them - Scheduling: An open spaced repetition algorithm you can read · a schedule you cannot inspect is one you cannot trust when it says you have forgotten something - Audio: Optional aligned audio with sentence-level timings · listening while reading is the most useful feature and needs alignment rather than just playback ### Data model (create this before Phase 1) - texts(id, title, language, source, imported_at, word_count, licence_note) - sentences(id, text_id, ordinal, content, audio_start_ms, audio_end_ms) - tokens(id, sentence_id, ordinal, surface, lemma) · lemma where you have a lemmatiser, surface otherwise - lexemes(id, language, form, status, first_seen_at, last_seen_at, seen_count) · status is unknown, learning, known or ignored - meanings(id, lexeme_id, gloss, note, source) · your own note, or one you looked up - contexts(lexeme_id, sentence_id, saved_at) · the sentence you met it in, which is what makes review work - reviews(id, lexeme_id, at, grade, next_due_at, interval_days, ease) - A word met while reading counts as exposure and updates the schedule, because reading is the practice and reviewing is the backstop. ### Phase 1 · Import and segment text properly Build: Import plain text, Markdown or EPUB. Split into sentences and tokenise into words with Intl.Segmenter for the target language. Record where the text came from and its licence note. Verify segmentation on a language that does not separate words with spaces. Trap: Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. Done when: A text imports and segments into sentences and words. Segmentation is correct for a language without spaces between words, verified by inspecting a sentence. The licence note is recorded and required at import. An EPUB imports with chapter structure preserved. Do not build yet: No reading interface, lookups or review. ### Phase 2 · Reading with status visible Build: Render the text with each word coloured by its lexeme status. Unknown words are prominent, known words are plain. Tapping a word opens it. Reading a page marks the words on it as seen, which is exposure rather than knowledge. Trap: Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. Done when: A page renders with unknown words visibly marked and known words looking like normal text. Marking a word known changes its appearance everywhere it appears, including in other texts. Reading a page increments the seen count for its words. A text of fifty thousand words scrolls smoothly. Do not build yet: No lookups, saving or review yet. ### Phase 3 · Lookup and saving with context Build: Tapping a word offers a lookup, from a dictionary file you supply or a service you configure. Saving stores your own gloss and the sentence it came from. A word saved twice accumulates contexts rather than duplicating. Trap: A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. Done when: Saving a word stores your gloss and the sentence. Meeting the same word in another text adds a second context rather than a second lexeme. A lookup failure still allows saving with your own note. Review later shows the context sentence rather than the word alone. Do not build yet: No scheduling yet. ### Phase 4 · Review you can inspect Build: Implement an open spaced repetition algorithm you can read and explain. Grading a review updates the interval. Meeting a word while reading counts as a light review and extends the interval, so the schedule reflects what you are actually doing. Trap: A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. Done when: Grading a review updates the interval according to the documented algorithm, verified against a hand calculation. Meeting a word while reading extends its interval by less than a graded review would. The due queue is ordered by due date and is capped per session. The algorithm's parameters are visible and adjustable. Do not build yet: No audio or progress yet. ### Phase 5 · Audio aligned to sentences Build: Import audio alongside a text and align it at sentence level, either from timings you supply or by transcribing and matching. Tapping a sentence plays that sentence. Playback highlights the current sentence. Trap: Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. Done when: Tapping a sentence plays that sentence. The highlight stays with the audio at the end of a long chapter, verified by listening. A text imported without audio behaves normally with playback disabled. Alignment can be corrected by hand for a sentence that drifted. Do not build yet: Progress and export come last. ### Phase 6 · Progress, export and honest claims Build: Progress as words known, words met and texts read, with gaps left as gaps. Export everything as JSON and the lexicon as CSV. Then write plainly what the numbers mean: words met in reading, not proficiency and not a level. Trap: A known word count invites conversion into a proficiency level, and any such claim is unfounded: a word marked known in a reader is a word you recognised in context, which is a long way from being able to use it. Report what was measured and refuse to imply the rest. Done when: Progress charts render with unlogged days as gaps. Export and re-import reproduces the lexicon and review schedule exactly, verified by checksum. No screen shows a proficiency level or a claim about ability. The documentation states what a known word count does and does not mean. Do not build yet: Nothing after this. ### Out of scope (and why) - The content library, which is licensed material and is most of what the subscription buys. - Tutors and the community, which are people rather than software. - A curriculum or any claim to accredited instruction. This is a reading tool with a review schedule. - Multi-device sync, which needs accounts and a server, and which the export partly substitutes for. ### README must contain - That only material you are entitled to use should be imported, and that a licence note is required - That this teaches nothing and certifies nothing, and what the word counts actually measure - That word segmentation uses the browser's locale-aware segmenter, and why splitting on spaces fails - That saved words always keep their context sentence - That reading counts as exposure and affects the review schedule ===== AGENTS.md ===== # Agent instructions · LingQ Premium indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, installable and offline capable, IndexedDB locally with export, no account required, Intl.Segmenter for word segmentation, An open spaced repetition algorithm you can read, Optional aligned audio with sentence-level timings. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. ===== BUILD_PLAN.md ===== # Build plan · LingQ Premium Import text and optional aligned audio, read with unknown words highlighted, look a word up and save it with its sentence as context, and review saved words on a spaced schedule that treats a word met in reading as a review, with progress measured in words you have actually seen. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Import and segment text properly A text you own becomes sentences and words, correctly, in your target language. ### Steps 1. Import several formats Plain text, Markdown and EPUB. EPUB is structured already, which makes it the easiest good source. Files: `src/import/` 2. Segment with Intl.Segmenter Sentence and word granularity, with the target locale. Test on a non-spaced language. Files: `src/segment.ts` 3. Require a licence note at import One line saying where this came from and why you may use it. It is a habit worth enforcing. Files: `src/import/index.ts` ### Done when - [ ] A text segments into sentences and words - [ ] Segmentation is correct for a language without spaces - [ ] A licence note is required and stored at import - [ ] An EPUB preserves chapter structure ### Watch out - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. ## Phase 2 · Reading with status visible Opening a text shows at a glance what you know and what you do not. ### Steps 1. Render by status Unknown prominent, learning subtle, known plain. Readability first. Files: `src/reader.ts` 2. Share status across texts A lexeme is per language, not per text. Learning a word in one book means knowing it in the next. Files: `src/lexemes.ts` 3. Count exposure while reading Seen count and last seen. Reading is the practice, so it must register. Files: `src/exposure.ts` ### Done when - [ ] Known words look like ordinary text - [ ] Marking a word known updates it across texts - [ ] Reading increments seen counts - [ ] A fifty thousand word text scrolls smoothly ### Watch out - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. ## Phase 3 · Lookup and saving with context Saving a word keeps the sentence you met it in, because that is what makes it learnable. ### Steps 1. Support a local dictionary file A file you supply, so the tool works offline and without a service. A configured service is an addition rather than a requirement. Files: `src/dictionary.ts` 2. Store gloss and context together Your own words plus the sentence. Both, always. Files: `src/save.ts` 3. Accumulate contexts One lexeme, several contexts. This is what makes a word feel learned rather than memorised. Files: `src/contexts.ts` ### Done when - [ ] Saving stores a gloss and the context sentence - [ ] The same word in a second text adds a context, not a lexeme - [ ] A failed lookup still allows saving - [ ] Review shows the context sentence ### Watch out - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. ## Phase 4 · Review you can inspect Words come back on a schedule, and reading counts towards it. ### Steps 1. Implement an algorithm you can explain Documented, with the parameters visible. A schedule you cannot inspect is one you will stop trusting. Files: `src/srs.ts`, `docs/algorithm.md` 2. Let reading count as exposure A smaller interval extension than a graded review. This is what keeps the queue honest. Files: `src/srs.ts` 3. Cap the session A daily cap you set. An unbounded queue after a week away is why people stop. Files: `src/review.ts` ### Done when - [ ] Interval updates match a hand calculation - [ ] Reading exposure extends intervals by less than a graded review - [ ] The due queue is capped per session - [ ] Algorithm parameters are visible and adjustable ### Watch out - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. ## Phase 5 · Audio aligned to sentences You can listen while reading, and tapping a sentence plays it. ### Steps 1. Support supplied timings first If you have them, alignment is solved. Automatic matching is the fallback rather than the default. Files: `src/audio/align.ts` 2. Match in chunks when transcribing Anchor on matches through the text rather than assuming a constant speaking rate. Files: `src/audio/align.ts` 3. Verify at the end, not the start Drift accumulates. The first chapter always looks fine. ### Done when - [ ] Tapping a sentence plays that sentence - [ ] Alignment holds at the end of a long chapter - [ ] A text without audio behaves normally - [ ] A drifted sentence can be corrected by hand ### Watch out - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. ## Not in this build - The licensed content library. - Tutors and community. - A curriculum or accredited instruction. - Multi-device sync. ## After v1, if you want it - Lemmatisation for a language with heavy inflection, which collapses many forms into one lexeme - A difficulty estimate per text based on your known words, so you can pick what to read next - Sentence mining export to an existing flashcard tool, since some people already have one ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The language being studied. Drives segmentation, so it must be a locale the segmenter recognises. TARGET_LANGUAGE=ja # Required. Local dictionary file. Bundled so lookups work offline. DICTIONARY_PATH=/dict/ja-en.json # Required. Maximum reviews per session. An uncapped queue after time away is why people abandon this practice. REVIEW_DAILY_CAP=60 # Required. How much a word met while reading extends its interval, relative to a graded review. EXPOSURE_INTERVAL_FACTOR=0.3 # Required. How often to prompt for an export, because browser storage can be evicted. EXPORT_REMINDER_DAYS=21
You are building a lean indie version of LingQ Premium. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # LingQ Premium · indie build Import text and optional aligned audio, read with unknown words highlighted, look a word up and save it with its sentence as context, and review saved words on a spaced schedule that treats a word met in reading as a review, with progress measured in words you have actually seen. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, installable and offline capable | reading and reviewing happen on a phone in gaps, often without signal | | Storage | IndexedDB locally with export, no account required | your reading history is yours and there is no reason for it to need a server | | Tokenisation | Intl.Segmenter for word segmentation | the browser already knows word boundaries in many languages, including ones where spaces do not mark them | | Scheduling | An open spaced repetition algorithm you can read | a schedule you cannot inspect is one you cannot trust when it says you have forgotten something | | Audio | Optional aligned audio with sentence-level timings | listening while reading is the most useful feature and needs alignment rather than just playback | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the build. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Texts you are entitled to use, in your target language** · free with openly licensed material - Why: The whole build works on your material, and importing something you may not use is both the wrong thing and legally your problem rather than a platform's. - Get it: Openly licensed texts, books you have bought in a format you can read, or your own writing. Record the source for each. - Verify: At least three texts with recorded licence notes - [ ] **A dictionary file for your target language** · free with an open dictionary - Why: Lookups should work offline and without a service. A licensed dictionary file makes the tool self-contained. - Get it: Use an openly licensed bilingual dictionary in a format you can parse, and record its licence. - Verify: A lookup returns a gloss with no network access - [ ] **A sample in a language without spaces between words** · free - Why: Phase 1's segmentation check is meaningless without one, and a naive tokeniser looks correct until it meets one. - Get it: A short openly licensed passage in Japanese, Chinese or Thai. You do not need to read it to check that segmentation produced words rather than one long token. - Verify: Segmentation produces plausible word boundaries in it - [ ] **A phone to install it on** · free - Why: Reading and reviewing happen in gaps, away from a desk, often with no signal. Desktop-only testing will produce a tool you do not use. - Get it: Serve over HTTPS with a tunnel and install to the home screen. - Verify: It installs and works with networking disabled ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The licensed content library. - Tutors and community. - A curriculum or accredited instruction. - Multi-device sync. - content library, import pipeline, multi-device sync, community, tutors, and polish - licensed course catalog - expert curriculum - human feedback - large learner network and certification If one of those is essential to you, that is the reason to keep paying for LingQ Premium, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · LingQ Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a reader that turns text you own into language study, tracking known words and scheduling review to replace LingQ Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Import only material you are entitled to use. The library is most of what the subscription buys, and building a tool that helps someone copy a paywalled catalogue is not what this is. Your own books, openly licensed texts and material you have bought are the input. This also teaches nothing and certifies nothing. It tracks what you have seen, schedules what you should see again, and shows progress. Saying that plainly is more useful than implying a curriculum that does not exist. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, installable and offline capable · reading and reviewing happen on a phone in gaps, often without signal - Storage: IndexedDB locally with export, no account required · your reading history is yours and there is no reason for it to need a server - Tokenisation: Intl.Segmenter for word segmentation · the browser already knows word boundaries in many languages, including ones where spaces do not mark them - Scheduling: An open spaced repetition algorithm you can read · a schedule you cannot inspect is one you cannot trust when it says you have forgotten something - Audio: Optional aligned audio with sentence-level timings · listening while reading is the most useful feature and needs alignment rather than just playback ### Data model (create this before Phase 1) - texts(id, title, language, source, imported_at, word_count, licence_note) - sentences(id, text_id, ordinal, content, audio_start_ms, audio_end_ms) - tokens(id, sentence_id, ordinal, surface, lemma) · lemma where you have a lemmatiser, surface otherwise - lexemes(id, language, form, status, first_seen_at, last_seen_at, seen_count) · status is unknown, learning, known or ignored - meanings(id, lexeme_id, gloss, note, source) · your own note, or one you looked up - contexts(lexeme_id, sentence_id, saved_at) · the sentence you met it in, which is what makes review work - reviews(id, lexeme_id, at, grade, next_due_at, interval_days, ease) - A word met while reading counts as exposure and updates the schedule, because reading is the practice and reviewing is the backstop. ### Phase 1 · Import and segment text properly Build: Import plain text, Markdown or EPUB. Split into sentences and tokenise into words with Intl.Segmenter for the target language. Record where the text came from and its licence note. Verify segmentation on a language that does not separate words with spaces. Trap: Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. Done when: A text imports and segments into sentences and words. Segmentation is correct for a language without spaces between words, verified by inspecting a sentence. The licence note is recorded and required at import. An EPUB imports with chapter structure preserved. Do not build yet: No reading interface, lookups or review. ### Phase 2 · Reading with status visible Build: Render the text with each word coloured by its lexeme status. Unknown words are prominent, known words are plain. Tapping a word opens it. Reading a page marks the words on it as seen, which is exposure rather than knowledge. Trap: Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. Done when: A page renders with unknown words visibly marked and known words looking like normal text. Marking a word known changes its appearance everywhere it appears, including in other texts. Reading a page increments the seen count for its words. A text of fifty thousand words scrolls smoothly. Do not build yet: No lookups, saving or review yet. ### Phase 3 · Lookup and saving with context Build: Tapping a word offers a lookup, from a dictionary file you supply or a service you configure. Saving stores your own gloss and the sentence it came from. A word saved twice accumulates contexts rather than duplicating. Trap: A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. Done when: Saving a word stores your gloss and the sentence. Meeting the same word in another text adds a second context rather than a second lexeme. A lookup failure still allows saving with your own note. Review later shows the context sentence rather than the word alone. Do not build yet: No scheduling yet. ### Phase 4 · Review you can inspect Build: Implement an open spaced repetition algorithm you can read and explain. Grading a review updates the interval. Meeting a word while reading counts as a light review and extends the interval, so the schedule reflects what you are actually doing. Trap: A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. Done when: Grading a review updates the interval according to the documented algorithm, verified against a hand calculation. Meeting a word while reading extends its interval by less than a graded review would. The due queue is ordered by due date and is capped per session. The algorithm's parameters are visible and adjustable. Do not build yet: No audio or progress yet. ### Phase 5 · Audio aligned to sentences Build: Import audio alongside a text and align it at sentence level, either from timings you supply or by transcribing and matching. Tapping a sentence plays that sentence. Playback highlights the current sentence. Trap: Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. Done when: Tapping a sentence plays that sentence. The highlight stays with the audio at the end of a long chapter, verified by listening. A text imported without audio behaves normally with playback disabled. Alignment can be corrected by hand for a sentence that drifted. Do not build yet: Progress and export come last. ### Phase 6 · Progress, export and honest claims Build: Progress as words known, words met and texts read, with gaps left as gaps. Export everything as JSON and the lexicon as CSV. Then write plainly what the numbers mean: words met in reading, not proficiency and not a level. Trap: A known word count invites conversion into a proficiency level, and any such claim is unfounded: a word marked known in a reader is a word you recognised in context, which is a long way from being able to use it. Report what was measured and refuse to imply the rest. Done when: Progress charts render with unlogged days as gaps. Export and re-import reproduces the lexicon and review schedule exactly, verified by checksum. No screen shows a proficiency level or a claim about ability. The documentation states what a known word count does and does not mean. Do not build yet: Nothing after this. ### Out of scope (and why) - The content library, which is licensed material and is most of what the subscription buys. - Tutors and the community, which are people rather than software. - A curriculum or any claim to accredited instruction. This is a reading tool with a review schedule. - Multi-device sync, which needs accounts and a server, and which the export partly substitutes for. ### README must contain - That only material you are entitled to use should be imported, and that a licence note is required - That this teaches nothing and certifies nothing, and what the word counts actually measure - That word segmentation uses the browser's locale-aware segmenter, and why splitting on spaces fails - That saved words always keep their context sentence - That reading counts as exposure and affects the review schedule ===== AGENTS.md ===== # Agent instructions · LingQ Premium indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, installable and offline capable, IndexedDB locally with export, no account required, Intl.Segmenter for word segmentation, An open spaced repetition algorithm you can read, Optional aligned audio with sentence-level timings. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. ===== BUILD_PLAN.md ===== # Build plan · LingQ Premium Import text and optional aligned audio, read with unknown words highlighted, look a word up and save it with its sentence as context, and review saved words on a spaced schedule that treats a word met in reading as a review, with progress measured in words you have actually seen. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Import and segment text properly A text you own becomes sentences and words, correctly, in your target language. ### Steps 1. Import several formats Plain text, Markdown and EPUB. EPUB is structured already, which makes it the easiest good source. Files: `src/import/` 2. Segment with Intl.Segmenter Sentence and word granularity, with the target locale. Test on a non-spaced language. Files: `src/segment.ts` 3. Require a licence note at import One line saying where this came from and why you may use it. It is a habit worth enforcing. Files: `src/import/index.ts` ### Done when - [ ] A text segments into sentences and words - [ ] Segmentation is correct for a language without spaces - [ ] A licence note is required and stored at import - [ ] An EPUB preserves chapter structure ### Watch out - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. ## Phase 2 · Reading with status visible Opening a text shows at a glance what you know and what you do not. ### Steps 1. Render by status Unknown prominent, learning subtle, known plain. Readability first. Files: `src/reader.ts` 2. Share status across texts A lexeme is per language, not per text. Learning a word in one book means knowing it in the next. Files: `src/lexemes.ts` 3. Count exposure while reading Seen count and last seen. Reading is the practice, so it must register. Files: `src/exposure.ts` ### Done when - [ ] Known words look like ordinary text - [ ] Marking a word known updates it across texts - [ ] Reading increments seen counts - [ ] A fifty thousand word text scrolls smoothly ### Watch out - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. ## Phase 3 · Lookup and saving with context Saving a word keeps the sentence you met it in, because that is what makes it learnable. ### Steps 1. Support a local dictionary file A file you supply, so the tool works offline and without a service. A configured service is an addition rather than a requirement. Files: `src/dictionary.ts` 2. Store gloss and context together Your own words plus the sentence. Both, always. Files: `src/save.ts` 3. Accumulate contexts One lexeme, several contexts. This is what makes a word feel learned rather than memorised. Files: `src/contexts.ts` ### Done when - [ ] Saving stores a gloss and the context sentence - [ ] The same word in a second text adds a context, not a lexeme - [ ] A failed lookup still allows saving - [ ] Review shows the context sentence ### Watch out - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. ## Phase 4 · Review you can inspect Words come back on a schedule, and reading counts towards it. ### Steps 1. Implement an algorithm you can explain Documented, with the parameters visible. A schedule you cannot inspect is one you will stop trusting. Files: `src/srs.ts`, `docs/algorithm.md` 2. Let reading count as exposure A smaller interval extension than a graded review. This is what keeps the queue honest. Files: `src/srs.ts` 3. Cap the session A daily cap you set. An unbounded queue after a week away is why people stop. Files: `src/review.ts` ### Done when - [ ] Interval updates match a hand calculation - [ ] Reading exposure extends intervals by less than a graded review - [ ] The due queue is capped per session - [ ] Algorithm parameters are visible and adjustable ### Watch out - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. ## Phase 5 · Audio aligned to sentences You can listen while reading, and tapping a sentence plays it. ### Steps 1. Support supplied timings first If you have them, alignment is solved. Automatic matching is the fallback rather than the default. Files: `src/audio/align.ts` 2. Match in chunks when transcribing Anchor on matches through the text rather than assuming a constant speaking rate. Files: `src/audio/align.ts` 3. Verify at the end, not the start Drift accumulates. The first chapter always looks fine. ### Done when - [ ] Tapping a sentence plays that sentence - [ ] Alignment holds at the end of a long chapter - [ ] A text without audio behaves normally - [ ] A drifted sentence can be corrected by hand ### Watch out - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. ## Not in this build - The licensed content library. - Tutors and community. - A curriculum or accredited instruction. - Multi-device sync. ## After v1, if you want it - Lemmatisation for a language with heavy inflection, which collapses many forms into one lexeme - A difficulty estimate per text based on your known words, so you can pick what to read next - Sentence mining export to an existing flashcard tool, since some people already have one ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The language being studied. Drives segmentation, so it must be a locale the segmenter recognises. TARGET_LANGUAGE=ja # Required. Local dictionary file. Bundled so lookups work offline. DICTIONARY_PATH=/dict/ja-en.json # Required. Maximum reviews per session. An uncapped queue after time away is why people abandon this practice. REVIEW_DAILY_CAP=60 # Required. How much a word met while reading extends its interval, relative to a graded review. EXPOSURE_INTERVAL_FACTOR=0.3 # Required. How often to prompt for an export, because browser storage can be evicted. EXPORT_REMINDER_DAYS=21
You are building a production product version of LingQ Premium. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # LingQ Premium · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For LingQ Premium, import permitted text and audio, mark known words, and review vocabulary in context. The hard boundary is content library, import pipeline, multi-device sync, community, tutors, and polish, plus content, pedagogy, and network. ## Product outcome A reader where your own material becomes study, words carry the sentences you met them in, review reflects what you are actually reading, and the progress numbers say only what they measured. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - modern browser - user-supplied study material - optional speech or language-model API key ## Explicit non-goals for v1 - The licensed content library. - Tutors and community. - A curriculum or accredited instruction. - Multi-device sync. - content library, import pipeline, multi-device sync, community, tutors, and polish - licensed course catalog - expert curriculum - human feedback - large learner network and certification ## Success criteria - Segmentation is correct on the non-spaced language sample - Export and import round trip matches by checksum - Interval updates match a hand calculation - Audio alignment holds at the end of the longest text - No proficiency claim appears anywhere in the interface ===== BRIEF.md ===== # Build brief · LingQ Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a reader that turns text you own into language study, tracking known words and scheduling review to replace LingQ Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Import only material you are entitled to use. The library is most of what the subscription buys, and building a tool that helps someone copy a paywalled catalogue is not what this is. Your own books, openly licensed texts and material you have bought are the input. This also teaches nothing and certifies nothing. It tracks what you have seen, schedules what you should see again, and shows progress. Saying that plainly is more useful than implying a curriculum that does not exist. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, installable and offline capable · reading and reviewing happen on a phone in gaps, often without signal - Storage: IndexedDB locally with export, no account required · your reading history is yours and there is no reason for it to need a server - Tokenisation: Intl.Segmenter for word segmentation · the browser already knows word boundaries in many languages, including ones where spaces do not mark them - Scheduling: An open spaced repetition algorithm you can read · a schedule you cannot inspect is one you cannot trust when it says you have forgotten something - Audio: Optional aligned audio with sentence-level timings · listening while reading is the most useful feature and needs alignment rather than just playback ### Data model (create this before Phase 1) - texts(id, title, language, source, imported_at, word_count, licence_note) - sentences(id, text_id, ordinal, content, audio_start_ms, audio_end_ms) - tokens(id, sentence_id, ordinal, surface, lemma) · lemma where you have a lemmatiser, surface otherwise - lexemes(id, language, form, status, first_seen_at, last_seen_at, seen_count) · status is unknown, learning, known or ignored - meanings(id, lexeme_id, gloss, note, source) · your own note, or one you looked up - contexts(lexeme_id, sentence_id, saved_at) · the sentence you met it in, which is what makes review work - reviews(id, lexeme_id, at, grade, next_due_at, interval_days, ease) - A word met while reading counts as exposure and updates the schedule, because reading is the practice and reviewing is the backstop. ### Phase 1 · Import and segment text properly Build: Import plain text, Markdown or EPUB. Split into sentences and tokenise into words with Intl.Segmenter for the target language. Record where the text came from and its licence note. Verify segmentation on a language that does not separate words with spaces. Trap: Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. Done when: A text imports and segments into sentences and words. Segmentation is correct for a language without spaces between words, verified by inspecting a sentence. The licence note is recorded and required at import. An EPUB imports with chapter structure preserved. Do not build yet: No reading interface, lookups or review. ### Phase 2 · Reading with status visible Build: Render the text with each word coloured by its lexeme status. Unknown words are prominent, known words are plain. Tapping a word opens it. Reading a page marks the words on it as seen, which is exposure rather than knowledge. Trap: Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. Done when: A page renders with unknown words visibly marked and known words looking like normal text. Marking a word known changes its appearance everywhere it appears, including in other texts. Reading a page increments the seen count for its words. A text of fifty thousand words scrolls smoothly. Do not build yet: No lookups, saving or review yet. ### Phase 3 · Lookup and saving with context Build: Tapping a word offers a lookup, from a dictionary file you supply or a service you configure. Saving stores your own gloss and the sentence it came from. A word saved twice accumulates contexts rather than duplicating. Trap: A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. Done when: Saving a word stores your gloss and the sentence. Meeting the same word in another text adds a second context rather than a second lexeme. A lookup failure still allows saving with your own note. Review later shows the context sentence rather than the word alone. Do not build yet: No scheduling yet. ### Phase 4 · Review you can inspect Build: Implement an open spaced repetition algorithm you can read and explain. Grading a review updates the interval. Meeting a word while reading counts as a light review and extends the interval, so the schedule reflects what you are actually doing. Trap: A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. Done when: Grading a review updates the interval according to the documented algorithm, verified against a hand calculation. Meeting a word while reading extends its interval by less than a graded review would. The due queue is ordered by due date and is capped per session. The algorithm's parameters are visible and adjustable. Do not build yet: No audio or progress yet. ### Phase 5 · Audio aligned to sentences Build: Import audio alongside a text and align it at sentence level, either from timings you supply or by transcribing and matching. Tapping a sentence plays that sentence. Playback highlights the current sentence. Trap: Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. Done when: Tapping a sentence plays that sentence. The highlight stays with the audio at the end of a long chapter, verified by listening. A text imported without audio behaves normally with playback disabled. Alignment can be corrected by hand for a sentence that drifted. Do not build yet: Progress and export come last. ### Phase 6 · Progress, export and honest claims Build: Progress as words known, words met and texts read, with gaps left as gaps. Export everything as JSON and the lexicon as CSV. Then write plainly what the numbers mean: words met in reading, not proficiency and not a level. Trap: A known word count invites conversion into a proficiency level, and any such claim is unfounded: a word marked known in a reader is a word you recognised in context, which is a long way from being able to use it. Report what was measured and refuse to imply the rest. Done when: Progress charts render with unlogged days as gaps. Export and re-import reproduces the lexicon and review schedule exactly, verified by checksum. No screen shows a proficiency level or a claim about ability. The documentation states what a known word count does and does not mean. Do not build yet: Nothing after this. ### Out of scope (and why) - The content library, which is licensed material and is most of what the subscription buys. - Tutors and the community, which are people rather than software. - A curriculum or any claim to accredited instruction. This is a reading tool with a review schedule. - Multi-device sync, which needs accounts and a server, and which the export partly substitutes for. ### README must contain - That only material you are entitled to use should be imported, and that a licence note is required - That this teaches nothing and certifies nothing, and what the word counts actually measure - That word segmentation uses the browser's locale-aware segmenter, and why splitting on spaces fails - That saved words always keep their context sentence - That reading counts as exposure and affects the review schedule ===== ARCHITECTURE.md ===== # Architecture · LingQ Premium ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, installable and offline capable | reading and reviewing happen on a phone in gaps, often without signal | | Storage | IndexedDB locally with export, no account required | your reading history is yours and there is no reason for it to need a server | | Tokenisation | Intl.Segmenter for word segmentation | the browser already knows word boundaries in many languages, including ones where spaces do not mark them | | Scheduling | An open spaced repetition algorithm you can read | a schedule you cannot inspect is one you cannot trust when it says you have forgotten something | | Audio | Optional aligned audio with sentence-level timings | listening while reading is the most useful feature and needs alignment rather than just playback | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Import | Turning files into sentences and tokens. | Formats are independent modules. The licence note requirement is a habit worth keeping whatever changes. | | Segment | Word and sentence boundaries. | Intl.Segmenter is built in and locale aware. A space splitter is not a substitute for any language. | | Lexicon | Words, statuses and contexts. | This is the durable asset. It should outlive the application, which is what the export is for. | | Schedule | Review intervals and exposure. | Algorithms are replaceable; being inspectable is not, because that is what makes the schedule trustworthy. | | Audio | Alignment and playback. | Optional throughout. A text without audio must behave normally. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `TARGET_LANGUAGE` · required · The language being studied. Drives segmentation, so it must be a locale the segmenter recognises. - `DICTIONARY_PATH` · required · Local dictionary file. Bundled so lookups work offline. - `REVIEW_DAILY_CAP` · required · Maximum reviews per session. An uncapped queue after time away is why people abandon this practice. - `EXPOSURE_INTERVAL_FACTOR` · required · How much a word met while reading extends its interval, relative to a graded review. - `EXPORT_REMINDER_DAYS` · required · How often to prompt for an export, because browser storage can be evicted. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · LingQ Premium product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with Vite, installable and offline capable, IndexedDB locally with export, no account required, Intl.Segmenter for word segmentation, An open spaced repetition algorithm you can read, Optional aligned audio with sentence-level timings. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. - A known word count invites conversion into a proficiency level, and any such claim is unfounded: a word marked known in a reader is a word you recognised in context, which is a long way from being able to use it. Report what was measured and refuse to imply the rest. ===== MILESTONES.md ===== # Delivery milestones · LingQ Premium Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Import and segment text properly A text you own becomes sentences and words, correctly, in your target language. ### Steps 1. Import several formats Plain text, Markdown and EPUB. EPUB is structured already, which makes it the easiest good source. Files: `src/import/` 2. Segment with Intl.Segmenter Sentence and word granularity, with the target locale. Test on a non-spaced language. Files: `src/segment.ts` 3. Require a licence note at import One line saying where this came from and why you may use it. It is a habit worth enforcing. Files: `src/import/index.ts` ### Done when - [ ] A text segments into sentences and words - [ ] Segmentation is correct for a language without spaces - [ ] A licence note is required and stored at import - [ ] An EPUB preserves chapter structure ### Watch out - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. ## M2 · Reading with status visible Opening a text shows at a glance what you know and what you do not. ### Steps 1. Render by status Unknown prominent, learning subtle, known plain. Readability first. Files: `src/reader.ts` 2. Share status across texts A lexeme is per language, not per text. Learning a word in one book means knowing it in the next. Files: `src/lexemes.ts` 3. Count exposure while reading Seen count and last seen. Reading is the practice, so it must register. Files: `src/exposure.ts` ### Done when - [ ] Known words look like ordinary text - [ ] Marking a word known updates it across texts - [ ] Reading increments seen counts - [ ] A fifty thousand word text scrolls smoothly ### Watch out - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. ## M3 · Lookup and saving with context Saving a word keeps the sentence you met it in, because that is what makes it learnable. ### Steps 1. Support a local dictionary file A file you supply, so the tool works offline and without a service. A configured service is an addition rather than a requirement. Files: `src/dictionary.ts` 2. Store gloss and context together Your own words plus the sentence. Both, always. Files: `src/save.ts` 3. Accumulate contexts One lexeme, several contexts. This is what makes a word feel learned rather than memorised. Files: `src/contexts.ts` ### Done when - [ ] Saving stores a gloss and the context sentence - [ ] The same word in a second text adds a context, not a lexeme - [ ] A failed lookup still allows saving - [ ] Review shows the context sentence ### Watch out - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. ## M4 · Review you can inspect Words come back on a schedule, and reading counts towards it. ### Steps 1. Implement an algorithm you can explain Documented, with the parameters visible. A schedule you cannot inspect is one you will stop trusting. Files: `src/srs.ts`, `docs/algorithm.md` 2. Let reading count as exposure A smaller interval extension than a graded review. This is what keeps the queue honest. Files: `src/srs.ts` 3. Cap the session A daily cap you set. An unbounded queue after a week away is why people stop. Files: `src/review.ts` ### Done when - [ ] Interval updates match a hand calculation - [ ] Reading exposure extends intervals by less than a graded review - [ ] The due queue is capped per session - [ ] Algorithm parameters are visible and adjustable ### Watch out - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. ## M5 · Audio aligned to sentences You can listen while reading, and tapping a sentence plays it. ### Steps 1. Support supplied timings first If you have them, alignment is solved. Automatic matching is the fallback rather than the default. Files: `src/audio/align.ts` 2. Match in chunks when transcribing Anchor on matches through the text rather than assuming a constant speaking rate. Files: `src/audio/align.ts` 3. Verify at the end, not the start Drift accumulates. The first chapter always looks fine. ### Done when - [ ] Tapping a sentence plays that sentence - [ ] Alignment holds at the end of a long chapter - [ ] A text without audio behaves normally - [ ] A drifted sentence can be corrected by hand ### Watch out - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. ## M6 · Progress, export and honest claims (production only) You can see how far you have come, and the application does not overstate what that means. ### Steps 1. Chart what was measured Words known, words met, texts finished. Gaps stay gaps. Files: `src/progress.ts` 2. Export losslessly and re-import JSON for everything, CSV for the lexicon. Round-trip equality is the test. Files: `src/export.ts`, `src/import/json.ts` 3. State what the numbers mean In the interface, next to the number. Not only in documentation nobody opens. Files: `src/progress.ts` ### Done when - [ ] Progress charts leave gaps as gaps - [ ] Export and re-import matches by checksum - [ ] No proficiency level or ability claim appears - [ ] The meaning of the word count is stated next to it ### Watch out - A known word count invites conversion into a proficiency level, and any such claim is unfounded: a word marked known in a reader is a word you recognised in context, which is a long way from being able to use it. Report what was measured and refuse to imply the rest. ===== OPERATIONS.md ===== # Operations · LingQ Premium ## Backup The export file is the backup. Browser storage can be evicted and a lexicon built over a year is genuinely painful to lose, so prompt for export on a schedule. ## Restore Import the export into an empty installation. The round-trip checksum test is what makes that trustworthy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing runs as a service. Worth watching during development is reading performance on the largest text and review queue growth, since an unbounded queue is the usual reason this practice is abandoned. ## Incident checklist Storage eviction is the realistic incident and the import path is the response, which is why it is tested rather than assumed. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Segmentation is correct on the non-spaced language sample - [ ] Export and import round trip matches by checksum - [ ] Interval updates match a hand calculation - [ ] Audio alignment holds at the end of the longest text - [ ] No proficiency claim appears anywhere in the interface ## Launch constraint Do not market omitted LingQ Premium capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The language being studied. Drives segmentation, so it must be a locale the segmenter recognises. TARGET_LANGUAGE=ja # Required. Local dictionary file. Bundled so lookups work offline. DICTIONARY_PATH=/dict/ja-en.json # Required. Maximum reviews per session. An uncapped queue after time away is why people abandon this practice. REVIEW_DAILY_CAP=60 # Required. How much a word met while reading extends its interval, relative to a graded review. EXPOSURE_INTERVAL_FACTOR=0.3 # Required. How often to prompt for an export, because browser storage can be evicted. EXPORT_REMINDER_DAYS=21
# LingQ Premium · indie build Import text and optional aligned audio, read with unknown words highlighted, look a word up and save it with its sentence as context, and review saved words on a spaced schedule that treats a word met in reading as a review, with progress measured in words you have actually seen. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, installable and offline capable | reading and reviewing happen on a phone in gaps, often without signal | | Storage | IndexedDB locally with export, no account required | your reading history is yours and there is no reason for it to need a server | | Tokenisation | Intl.Segmenter for word segmentation | the browser already knows word boundaries in many languages, including ones where spaces do not mark them | | Scheduling | An open spaced repetition algorithm you can read | a schedule you cannot inspect is one you cannot trust when it says you have forgotten something | | Audio | Optional aligned audio with sentence-level timings | listening while reading is the most useful feature and needs alignment rather than just playback | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the build. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Texts you are entitled to use, in your target language** · free with openly licensed material - Why: The whole build works on your material, and importing something you may not use is both the wrong thing and legally your problem rather than a platform's. - Get it: Openly licensed texts, books you have bought in a format you can read, or your own writing. Record the source for each. - Verify: At least three texts with recorded licence notes - [ ] **A dictionary file for your target language** · free with an open dictionary - Why: Lookups should work offline and without a service. A licensed dictionary file makes the tool self-contained. - Get it: Use an openly licensed bilingual dictionary in a format you can parse, and record its licence. - Verify: A lookup returns a gloss with no network access - [ ] **A sample in a language without spaces between words** · free - Why: Phase 1's segmentation check is meaningless without one, and a naive tokeniser looks correct until it meets one. - Get it: A short openly licensed passage in Japanese, Chinese or Thai. You do not need to read it to check that segmentation produced words rather than one long token. - Verify: Segmentation produces plausible word boundaries in it - [ ] **A phone to install it on** · free - Why: Reading and reviewing happen in gaps, away from a desk, often with no signal. Desktop-only testing will produce a tool you do not use. - Get it: Serve over HTTPS with a tunnel and install to the home screen. - Verify: It installs and works with networking disabled ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The licensed content library. - Tutors and community. - A curriculum or accredited instruction. - Multi-device sync. - content library, import pipeline, multi-device sync, community, tutors, and polish - licensed course catalog - expert curriculum - human feedback - large learner network and certification If one of those is essential to you, that is the reason to keep paying for LingQ Premium, and the README should say so rather than pretend.
# Build brief · LingQ Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a reader that turns text you own into language study, tracking known words and scheduling review to replace LingQ Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Import only material you are entitled to use. The library is most of what the subscription buys, and building a tool that helps someone copy a paywalled catalogue is not what this is. Your own books, openly licensed texts and material you have bought are the input. This also teaches nothing and certifies nothing. It tracks what you have seen, schedules what you should see again, and shows progress. Saying that plainly is more useful than implying a curriculum that does not exist. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, installable and offline capable · reading and reviewing happen on a phone in gaps, often without signal - Storage: IndexedDB locally with export, no account required · your reading history is yours and there is no reason for it to need a server - Tokenisation: Intl.Segmenter for word segmentation · the browser already knows word boundaries in many languages, including ones where spaces do not mark them - Scheduling: An open spaced repetition algorithm you can read · a schedule you cannot inspect is one you cannot trust when it says you have forgotten something - Audio: Optional aligned audio with sentence-level timings · listening while reading is the most useful feature and needs alignment rather than just playback ### Data model (create this before Phase 1) - texts(id, title, language, source, imported_at, word_count, licence_note) - sentences(id, text_id, ordinal, content, audio_start_ms, audio_end_ms) - tokens(id, sentence_id, ordinal, surface, lemma) · lemma where you have a lemmatiser, surface otherwise - lexemes(id, language, form, status, first_seen_at, last_seen_at, seen_count) · status is unknown, learning, known or ignored - meanings(id, lexeme_id, gloss, note, source) · your own note, or one you looked up - contexts(lexeme_id, sentence_id, saved_at) · the sentence you met it in, which is what makes review work - reviews(id, lexeme_id, at, grade, next_due_at, interval_days, ease) - A word met while reading counts as exposure and updates the schedule, because reading is the practice and reviewing is the backstop. ### Phase 1 · Import and segment text properly Build: Import plain text, Markdown or EPUB. Split into sentences and tokenise into words with Intl.Segmenter for the target language. Record where the text came from and its licence note. Verify segmentation on a language that does not separate words with spaces. Trap: Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. Done when: A text imports and segments into sentences and words. Segmentation is correct for a language without spaces between words, verified by inspecting a sentence. The licence note is recorded and required at import. An EPUB imports with chapter structure preserved. Do not build yet: No reading interface, lookups or review. ### Phase 2 · Reading with status visible Build: Render the text with each word coloured by its lexeme status. Unknown words are prominent, known words are plain. Tapping a word opens it. Reading a page marks the words on it as seen, which is exposure rather than knowledge. Trap: Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. Done when: A page renders with unknown words visibly marked and known words looking like normal text. Marking a word known changes its appearance everywhere it appears, including in other texts. Reading a page increments the seen count for its words. A text of fifty thousand words scrolls smoothly. Do not build yet: No lookups, saving or review yet. ### Phase 3 · Lookup and saving with context Build: Tapping a word offers a lookup, from a dictionary file you supply or a service you configure. Saving stores your own gloss and the sentence it came from. A word saved twice accumulates contexts rather than duplicating. Trap: A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. Done when: Saving a word stores your gloss and the sentence. Meeting the same word in another text adds a second context rather than a second lexeme. A lookup failure still allows saving with your own note. Review later shows the context sentence rather than the word alone. Do not build yet: No scheduling yet. ### Phase 4 · Review you can inspect Build: Implement an open spaced repetition algorithm you can read and explain. Grading a review updates the interval. Meeting a word while reading counts as a light review and extends the interval, so the schedule reflects what you are actually doing. Trap: A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. Done when: Grading a review updates the interval according to the documented algorithm, verified against a hand calculation. Meeting a word while reading extends its interval by less than a graded review would. The due queue is ordered by due date and is capped per session. The algorithm's parameters are visible and adjustable. Do not build yet: No audio or progress yet. ### Phase 5 · Audio aligned to sentences Build: Import audio alongside a text and align it at sentence level, either from timings you supply or by transcribing and matching. Tapping a sentence plays that sentence. Playback highlights the current sentence. Trap: Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. Done when: Tapping a sentence plays that sentence. The highlight stays with the audio at the end of a long chapter, verified by listening. A text imported without audio behaves normally with playback disabled. Alignment can be corrected by hand for a sentence that drifted. Do not build yet: Progress and export come last. ### Phase 6 · Progress, export and honest claims Build: Progress as words known, words met and texts read, with gaps left as gaps. Export everything as JSON and the lexicon as CSV. Then write plainly what the numbers mean: words met in reading, not proficiency and not a level. Trap: A known word count invites conversion into a proficiency level, and any such claim is unfounded: a word marked known in a reader is a word you recognised in context, which is a long way from being able to use it. Report what was measured and refuse to imply the rest. Done when: Progress charts render with unlogged days as gaps. Export and re-import reproduces the lexicon and review schedule exactly, verified by checksum. No screen shows a proficiency level or a claim about ability. The documentation states what a known word count does and does not mean. Do not build yet: Nothing after this. ### Out of scope (and why) - The content library, which is licensed material and is most of what the subscription buys. - Tutors and the community, which are people rather than software. - A curriculum or any claim to accredited instruction. This is a reading tool with a review schedule. - Multi-device sync, which needs accounts and a server, and which the export partly substitutes for. ### README must contain - That only material you are entitled to use should be imported, and that a licence note is required - That this teaches nothing and certifies nothing, and what the word counts actually measure - That word segmentation uses the browser's locale-aware segmenter, and why splitting on spaces fails - That saved words always keep their context sentence - That reading counts as exposure and affects the review schedule
# Agent instructions · LingQ Premium indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, installable and offline capable, IndexedDB locally with export, no account required, Intl.Segmenter for word segmentation, An open spaced repetition algorithm you can read, Optional aligned audio with sentence-level timings. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning.
# Build plan · LingQ Premium Import text and optional aligned audio, read with unknown words highlighted, look a word up and save it with its sentence as context, and review saved words on a spaced schedule that treats a word met in reading as a review, with progress measured in words you have actually seen. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Import and segment text properly A text you own becomes sentences and words, correctly, in your target language. ### Steps 1. Import several formats Plain text, Markdown and EPUB. EPUB is structured already, which makes it the easiest good source. Files: `src/import/` 2. Segment with Intl.Segmenter Sentence and word granularity, with the target locale. Test on a non-spaced language. Files: `src/segment.ts` 3. Require a licence note at import One line saying where this came from and why you may use it. It is a habit worth enforcing. Files: `src/import/index.ts` ### Done when - [ ] A text segments into sentences and words - [ ] Segmentation is correct for a language without spaces - [ ] A licence note is required and stored at import - [ ] An EPUB preserves chapter structure ### Watch out - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. ## Phase 2 · Reading with status visible Opening a text shows at a glance what you know and what you do not. ### Steps 1. Render by status Unknown prominent, learning subtle, known plain. Readability first. Files: `src/reader.ts` 2. Share status across texts A lexeme is per language, not per text. Learning a word in one book means knowing it in the next. Files: `src/lexemes.ts` 3. Count exposure while reading Seen count and last seen. Reading is the practice, so it must register. Files: `src/exposure.ts` ### Done when - [ ] Known words look like ordinary text - [ ] Marking a word known updates it across texts - [ ] Reading increments seen counts - [ ] A fifty thousand word text scrolls smoothly ### Watch out - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. ## Phase 3 · Lookup and saving with context Saving a word keeps the sentence you met it in, because that is what makes it learnable. ### Steps 1. Support a local dictionary file A file you supply, so the tool works offline and without a service. A configured service is an addition rather than a requirement. Files: `src/dictionary.ts` 2. Store gloss and context together Your own words plus the sentence. Both, always. Files: `src/save.ts` 3. Accumulate contexts One lexeme, several contexts. This is what makes a word feel learned rather than memorised. Files: `src/contexts.ts` ### Done when - [ ] Saving stores a gloss and the context sentence - [ ] The same word in a second text adds a context, not a lexeme - [ ] A failed lookup still allows saving - [ ] Review shows the context sentence ### Watch out - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. ## Phase 4 · Review you can inspect Words come back on a schedule, and reading counts towards it. ### Steps 1. Implement an algorithm you can explain Documented, with the parameters visible. A schedule you cannot inspect is one you will stop trusting. Files: `src/srs.ts`, `docs/algorithm.md` 2. Let reading count as exposure A smaller interval extension than a graded review. This is what keeps the queue honest. Files: `src/srs.ts` 3. Cap the session A daily cap you set. An unbounded queue after a week away is why people stop. Files: `src/review.ts` ### Done when - [ ] Interval updates match a hand calculation - [ ] Reading exposure extends intervals by less than a graded review - [ ] The due queue is capped per session - [ ] Algorithm parameters are visible and adjustable ### Watch out - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. ## Phase 5 · Audio aligned to sentences You can listen while reading, and tapping a sentence plays it. ### Steps 1. Support supplied timings first If you have them, alignment is solved. Automatic matching is the fallback rather than the default. Files: `src/audio/align.ts` 2. Match in chunks when transcribing Anchor on matches through the text rather than assuming a constant speaking rate. Files: `src/audio/align.ts` 3. Verify at the end, not the start Drift accumulates. The first chapter always looks fine. ### Done when - [ ] Tapping a sentence plays that sentence - [ ] Alignment holds at the end of a long chapter - [ ] A text without audio behaves normally - [ ] A drifted sentence can be corrected by hand ### Watch out - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. ## Not in this build - The licensed content library. - Tutors and community. - A curriculum or accredited instruction. - Multi-device sync. ## After v1, if you want it - Lemmatisation for a language with heavy inflection, which collapses many forms into one lexeme - A difficulty estimate per text based on your known words, so you can pick what to read next - Sentence mining export to an existing flashcard tool, since some people already have one
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The language being studied. Drives segmentation, so it must be a locale the segmenter recognises. TARGET_LANGUAGE=ja # Required. Local dictionary file. Bundled so lookups work offline. DICTIONARY_PATH=/dict/ja-en.json # Required. Maximum reviews per session. An uncapped queue after time away is why people abandon this practice. REVIEW_DAILY_CAP=60 # Required. How much a word met while reading extends its interval, relative to a graded review. EXPOSURE_INTERVAL_FACTOR=0.3 # Required. How often to prompt for an export, because browser storage can be evicted. EXPORT_REMINDER_DAYS=21
# LingQ Premium · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For LingQ Premium, import permitted text and audio, mark known words, and review vocabulary in context. The hard boundary is content library, import pipeline, multi-device sync, community, tutors, and polish, plus content, pedagogy, and network. ## Product outcome A reader where your own material becomes study, words carry the sentences you met them in, review reflects what you are actually reading, and the progress numbers say only what they measured. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - modern browser - user-supplied study material - optional speech or language-model API key ## Explicit non-goals for v1 - The licensed content library. - Tutors and community. - A curriculum or accredited instruction. - Multi-device sync. - content library, import pipeline, multi-device sync, community, tutors, and polish - licensed course catalog - expert curriculum - human feedback - large learner network and certification ## Success criteria - Segmentation is correct on the non-spaced language sample - Export and import round trip matches by checksum - Interval updates match a hand calculation - Audio alignment holds at the end of the longest text - No proficiency claim appears anywhere in the interface
# Build brief · LingQ Premium The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a reader that turns text you own into language study, tracking known words and scheduling review to replace LingQ Premium. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Import only material you are entitled to use. The library is most of what the subscription buys, and building a tool that helps someone copy a paywalled catalogue is not what this is. Your own books, openly licensed texts and material you have bought are the input. This also teaches nothing and certifies nothing. It tracks what you have seen, schedules what you should see again, and shows progress. Saying that plainly is more useful than implying a curriculum that does not exist. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, installable and offline capable · reading and reviewing happen on a phone in gaps, often without signal - Storage: IndexedDB locally with export, no account required · your reading history is yours and there is no reason for it to need a server - Tokenisation: Intl.Segmenter for word segmentation · the browser already knows word boundaries in many languages, including ones where spaces do not mark them - Scheduling: An open spaced repetition algorithm you can read · a schedule you cannot inspect is one you cannot trust when it says you have forgotten something - Audio: Optional aligned audio with sentence-level timings · listening while reading is the most useful feature and needs alignment rather than just playback ### Data model (create this before Phase 1) - texts(id, title, language, source, imported_at, word_count, licence_note) - sentences(id, text_id, ordinal, content, audio_start_ms, audio_end_ms) - tokens(id, sentence_id, ordinal, surface, lemma) · lemma where you have a lemmatiser, surface otherwise - lexemes(id, language, form, status, first_seen_at, last_seen_at, seen_count) · status is unknown, learning, known or ignored - meanings(id, lexeme_id, gloss, note, source) · your own note, or one you looked up - contexts(lexeme_id, sentence_id, saved_at) · the sentence you met it in, which is what makes review work - reviews(id, lexeme_id, at, grade, next_due_at, interval_days, ease) - A word met while reading counts as exposure and updates the schedule, because reading is the practice and reviewing is the backstop. ### Phase 1 · Import and segment text properly Build: Import plain text, Markdown or EPUB. Split into sentences and tokenise into words with Intl.Segmenter for the target language. Record where the text came from and its licence note. Verify segmentation on a language that does not separate words with spaces. Trap: Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. Done when: A text imports and segments into sentences and words. Segmentation is correct for a language without spaces between words, verified by inspecting a sentence. The licence note is recorded and required at import. An EPUB imports with chapter structure preserved. Do not build yet: No reading interface, lookups or review. ### Phase 2 · Reading with status visible Build: Render the text with each word coloured by its lexeme status. Unknown words are prominent, known words are plain. Tapping a word opens it. Reading a page marks the words on it as seen, which is exposure rather than knowledge. Trap: Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. Done when: A page renders with unknown words visibly marked and known words looking like normal text. Marking a word known changes its appearance everywhere it appears, including in other texts. Reading a page increments the seen count for its words. A text of fifty thousand words scrolls smoothly. Do not build yet: No lookups, saving or review yet. ### Phase 3 · Lookup and saving with context Build: Tapping a word offers a lookup, from a dictionary file you supply or a service you configure. Saving stores your own gloss and the sentence it came from. A word saved twice accumulates contexts rather than duplicating. Trap: A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. Done when: Saving a word stores your gloss and the sentence. Meeting the same word in another text adds a second context rather than a second lexeme. A lookup failure still allows saving with your own note. Review later shows the context sentence rather than the word alone. Do not build yet: No scheduling yet. ### Phase 4 · Review you can inspect Build: Implement an open spaced repetition algorithm you can read and explain. Grading a review updates the interval. Meeting a word while reading counts as a light review and extends the interval, so the schedule reflects what you are actually doing. Trap: A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. Done when: Grading a review updates the interval according to the documented algorithm, verified against a hand calculation. Meeting a word while reading extends its interval by less than a graded review would. The due queue is ordered by due date and is capped per session. The algorithm's parameters are visible and adjustable. Do not build yet: No audio or progress yet. ### Phase 5 · Audio aligned to sentences Build: Import audio alongside a text and align it at sentence level, either from timings you supply or by transcribing and matching. Tapping a sentence plays that sentence. Playback highlights the current sentence. Trap: Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. Done when: Tapping a sentence plays that sentence. The highlight stays with the audio at the end of a long chapter, verified by listening. A text imported without audio behaves normally with playback disabled. Alignment can be corrected by hand for a sentence that drifted. Do not build yet: Progress and export come last. ### Phase 6 · Progress, export and honest claims Build: Progress as words known, words met and texts read, with gaps left as gaps. Export everything as JSON and the lexicon as CSV. Then write plainly what the numbers mean: words met in reading, not proficiency and not a level. Trap: A known word count invites conversion into a proficiency level, and any such claim is unfounded: a word marked known in a reader is a word you recognised in context, which is a long way from being able to use it. Report what was measured and refuse to imply the rest. Done when: Progress charts render with unlogged days as gaps. Export and re-import reproduces the lexicon and review schedule exactly, verified by checksum. No screen shows a proficiency level or a claim about ability. The documentation states what a known word count does and does not mean. Do not build yet: Nothing after this. ### Out of scope (and why) - The content library, which is licensed material and is most of what the subscription buys. - Tutors and the community, which are people rather than software. - A curriculum or any claim to accredited instruction. This is a reading tool with a review schedule. - Multi-device sync, which needs accounts and a server, and which the export partly substitutes for. ### README must contain - That only material you are entitled to use should be imported, and that a licence note is required - That this teaches nothing and certifies nothing, and what the word counts actually measure - That word segmentation uses the browser's locale-aware segmenter, and why splitting on spaces fails - That saved words always keep their context sentence - That reading counts as exposure and affects the review schedule
# Architecture · LingQ Premium ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, installable and offline capable | reading and reviewing happen on a phone in gaps, often without signal | | Storage | IndexedDB locally with export, no account required | your reading history is yours and there is no reason for it to need a server | | Tokenisation | Intl.Segmenter for word segmentation | the browser already knows word boundaries in many languages, including ones where spaces do not mark them | | Scheduling | An open spaced repetition algorithm you can read | a schedule you cannot inspect is one you cannot trust when it says you have forgotten something | | Audio | Optional aligned audio with sentence-level timings | listening while reading is the most useful feature and needs alignment rather than just playback | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Import | Turning files into sentences and tokens. | Formats are independent modules. The licence note requirement is a habit worth keeping whatever changes. | | Segment | Word and sentence boundaries. | Intl.Segmenter is built in and locale aware. A space splitter is not a substitute for any language. | | Lexicon | Words, statuses and contexts. | This is the durable asset. It should outlive the application, which is what the export is for. | | Schedule | Review intervals and exposure. | Algorithms are replaceable; being inspectable is not, because that is what makes the schedule trustworthy. | | Audio | Alignment and playback. | Optional throughout. A text without audio must behave normally. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `TARGET_LANGUAGE` · required · The language being studied. Drives segmentation, so it must be a locale the segmenter recognises. - `DICTIONARY_PATH` · required · Local dictionary file. Bundled so lookups work offline. - `REVIEW_DAILY_CAP` · required · Maximum reviews per session. An uncapped queue after time away is why people abandon this practice. - `EXPOSURE_INTERVAL_FACTOR` · required · How much a word met while reading extends its interval, relative to a graded review. - `EXPORT_REMINDER_DAYS` · required · How often to prompt for an export, because browser storage can be evicted. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · LingQ Premium product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with Vite, installable and offline capable, IndexedDB locally with export, no account required, Intl.Segmenter for word segmentation, An open spaced repetition algorithm you can read, Optional aligned audio with sentence-level timings. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. - A known word count invites conversion into a proficiency level, and any such claim is unfounded: a word marked known in a reader is a word you recognised in context, which is a long way from being able to use it. Report what was measured and refuse to imply the rest.
# Delivery milestones · LingQ Premium Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Import and segment text properly A text you own becomes sentences and words, correctly, in your target language. ### Steps 1. Import several formats Plain text, Markdown and EPUB. EPUB is structured already, which makes it the easiest good source. Files: `src/import/` 2. Segment with Intl.Segmenter Sentence and word granularity, with the target locale. Test on a non-spaced language. Files: `src/segment.ts` 3. Require a licence note at import One line saying where this came from and why you may use it. It is a habit worth enforcing. Files: `src/import/index.ts` ### Done when - [ ] A text segments into sentences and words - [ ] Segmentation is correct for a language without spaces - [ ] A licence note is required and stored at import - [ ] An EPUB preserves chapter structure ### Watch out - Splitting on spaces works for some languages and produces nonsense for others, including Japanese, Chinese and Thai, where a sentence has no spaces at all. Intl.Segmenter handles word segmentation per locale, and testing against a non-spaced language is the check that catches a naive implementation. ## M2 · Reading with status visible Opening a text shows at a glance what you know and what you do not. ### Steps 1. Render by status Unknown prominent, learning subtle, known plain. Readability first. Files: `src/reader.ts` 2. Share status across texts A lexeme is per language, not per text. Learning a word in one book means knowing it in the next. Files: `src/lexemes.ts` 3. Count exposure while reading Seen count and last seen. Reading is the practice, so it must register. Files: `src/exposure.ts` ### Done when - [ ] Known words look like ordinary text - [ ] Marking a word known updates it across texts - [ ] Reading increments seen counts - [ ] A fifty thousand word text scrolls smoothly ### Watch out - Colouring every word including known ones makes a page unreadable and defeats the purpose, which is to read. Known words should look like ordinary text; only what you have not learned should stand out. ## M3 · Lookup and saving with context Saving a word keeps the sentence you met it in, because that is what makes it learnable. ### Steps 1. Support a local dictionary file A file you supply, so the tool works offline and without a service. A configured service is an addition rather than a requirement. Files: `src/dictionary.ts` 2. Store gloss and context together Your own words plus the sentence. Both, always. Files: `src/save.ts` 3. Accumulate contexts One lexeme, several contexts. This is what makes a word feel learned rather than memorised. Files: `src/contexts.ts` ### Done when - [ ] Saving stores a gloss and the context sentence - [ ] The same word in a second text adds a context, not a lexeme - [ ] A failed lookup still allows saving - [ ] Review shows the context sentence ### Watch out - A saved word without its sentence is a flashcard with no meaning attached, and reviewing bare word pairs is the least effective form of this practice. Always store the context sentence, and show it during review rather than the word alone. ## M4 · Review you can inspect Words come back on a schedule, and reading counts towards it. ### Steps 1. Implement an algorithm you can explain Documented, with the parameters visible. A schedule you cannot inspect is one you will stop trusting. Files: `src/srs.ts`, `docs/algorithm.md` 2. Let reading count as exposure A smaller interval extension than a graded review. This is what keeps the queue honest. Files: `src/srs.ts` 3. Cap the session A daily cap you set. An unbounded queue after a week away is why people stop. Files: `src/review.ts` ### Done when - [ ] Interval updates match a hand calculation - [ ] Reading exposure extends intervals by less than a graded review - [ ] The due queue is capped per session - [ ] Algorithm parameters are visible and adjustable ### Watch out - A review schedule that ignores reading will keep testing you on words you meet daily, which wastes review time on the words that need it least. Let exposure while reading count, with a smaller effect than a graded review, so the queue drifts towards what you genuinely are not meeting. ## M5 · Audio aligned to sentences You can listen while reading, and tapping a sentence plays it. ### Steps 1. Support supplied timings first If you have them, alignment is solved. Automatic matching is the fallback rather than the default. Files: `src/audio/align.ts` 2. Match in chunks when transcribing Anchor on matches through the text rather than assuming a constant speaking rate. Files: `src/audio/align.ts` 3. Verify at the end, not the start Drift accumulates. The first chapter always looks fine. ### Done when - [ ] Tapping a sentence plays that sentence - [ ] Alignment holds at the end of a long chapter - [ ] A text without audio behaves normally - [ ] A drifted sentence can be corrected by hand ### Watch out - Automatic alignment drifts on a long recording, and by chapter three the highlight is a paragraph away from the audio. Align in chunks against transcript matches rather than assuming a constant rate, and verify alignment at the end of the longest text rather than the beginning. ## M6 · Progress, export and honest claims (production only) You can see how far you have come, and the application does not overstate what that means. ### Steps 1. Chart what was measured Words known, words met, texts finished. Gaps stay gaps. Files: `src/progress.ts` 2. Export losslessly and re-import JSON for everything, CSV for the lexicon. Round-trip equality is the test. Files: `src/export.ts`, `src/import/json.ts` 3. State what the numbers mean In the interface, next to the number. Not only in documentation nobody opens. Files: `src/progress.ts` ### Done when - [ ] Progress charts leave gaps as gaps - [ ] Export and re-import matches by checksum - [ ] No proficiency level or ability claim appears - [ ] The meaning of the word count is stated next to it ### Watch out - A known word count invites conversion into a proficiency level, and any such claim is unfounded: a word marked known in a reader is a word you recognised in context, which is a long way from being able to use it. Report what was measured and refuse to imply the rest.
# Operations · LingQ Premium ## Backup The export file is the backup. Browser storage can be evicted and a lexicon built over a year is genuinely painful to lose, so prompt for export on a schedule. ## Restore Import the export into an empty installation. The round-trip checksum test is what makes that trustworthy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing runs as a service. Worth watching during development is reading performance on the largest text and review queue growth, since an unbounded queue is the usual reason this practice is abandoned. ## Incident checklist Storage eviction is the realistic incident and the import path is the response, which is why it is tested rather than assumed. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Segmentation is correct on the non-spaced language sample - [ ] Export and import round trip matches by checksum - [ ] Interval updates match a hand calculation - [ ] Audio alignment holds at the end of the longest text - [ ] No proficiency claim appears anywhere in the interface ## Launch constraint Do not market omitted LingQ Premium capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The language being studied. Drives segmentation, so it must be a locale the segmenter recognises. TARGET_LANGUAGE=ja # Required. Local dictionary file. Bundled so lookups work offline. DICTIONARY_PATH=/dict/ja-en.json # Required. Maximum reviews per session. An uncapped queue after time away is why people abandon this practice. REVIEW_DAILY_CAP=60 # Required. How much a word met while reading extends its interval, relative to a graded review. EXPOSURE_INTERVAL_FACTOR=0.3 # Required. How often to prompt for an export, because browser storage can be evicted. EXPORT_REMINDER_DAYS=21
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for LingQ Premium because the learning loop is buildable, while trusted curriculum, instructors, feedback, credentials, and community are not. The recurring cost buys curriculum quality, assessment validity, content rights, spaced-repetition tuning, speech models, sync, moderation, and support, not just the visible interface.
xcontent library, import pipeline, multi-device sync, community, tutors, and polish
xlicensed course catalog
xexpert curriculum
xhuman feedback
xlarge learner network and certification
Don't feel like building it? These folks already made it free.
LLuteImport text and audio, mark every word by familiarity and review it in context; this is the unusually exact one.open source↗LLearning with TextsThe older, homelier version of the same idea: imported texts, audio, known-word states and contextual review.open source↗no votes, no pay-to-list · just what's real
LingQ Premium pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/user | $0/user | 20 saved words/phrases; 5 imported lessons; 1 audio playlist; access to libraries in 50+ languages |
| premium, monthly | $14.99/user | — | Unlimited saved words, imported lessons, and playlists; full-sentence translation; flashcards; stats; challenges; offline access |
| premium, 12 months | — | $10/user | Same Premium limits; billed $120 upfront for 12 months |
| premium, 24 months | — | $8.99/user | Same Premium limits; billed $215.76 upfront for 24 months |
| premium plus, monthly | $29.99/user | — | Premium features plus AI voices, advanced AI chat, and simplified lessons; AI usage quotas may apply |
| premium plus, 12 months | — | $22.50/user | Premium Plus features; billed $269.99 yearly; AI usage quotas may apply |
free tier20 saved words/phrases, 5 imported lessons, and 1 audio playlist; library access in 50+ languages
billingPremium monthly, 12-month, or 24-month prepaid terms; Premium Plus monthly or annual; auto-renews by storefront
hidden costsThe 24-month discount requires the entire two-year term upfront; Premium Plus AI features can have usage quotas that the current public page does not quantify.
verified 2026-08-12 · source ↗
Vibecode LingQ Premium
Kinda. The core of LingQ Premium is buildable in a weekend with the prompt on this page, but there are real gaps: content library, import pipeline, multi-device sync, community, tutors, and polish, licensed course catalog. Read the honest list above before committing.
How much does LingQ Premium cost?
LingQ Premium costs about $14.99/month (Premium, checked 2026-07-31), which is $179.88 per year.
What do I lose by replacing LingQ Premium?
Honestly: content library, import pipeline, multi-device sync, community, tutors, and polish; licensed course catalog; expert curriculum; human feedback; large learner network and certification. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to LingQ Premium?
Yes: Lute (Import text and audio, mark every word by familiarity and review it in context; this is the unusually exact one.) Learning with Texts (The older, homelier version of the same idea: imported texts, audio, known-word states and contextual review.) The prompt is for when you want it exactly your way.