How Article Practice Works in TypeWords: Sentence‑by‑Sentence Typing Architecture Explained
Article practice in TypeWords parses text into hierarchical sections, sentences, and typed tokens, then drives a word‑level typing loop with audio synchronization, idle detection, and granular timing statistics.
The TypeWords open‑source typing trainer treats every article as a structured document that users practice sentence by sentence. The core architecture separates parsing (converting raw text into typed word arrays) from runtime (managing the interactive typing session). This article breaks down both layers using the actual source code from the zyronon/TypeWords repository.
How Articles Are Parsed for Practice
Before a user types a single character, TypeWords transforms the raw article text into a rich, navigable data structure. This happens in app/core/hooks/article.ts, specifically through the genArticleSectionData function.
Section Splitting and Sentence Tokenization
The parser first normalizes the input, then splits on blank lines to create sections. Within each section, it splits lines into individual sentences and calls parseSentence for each:
// Called once per article to build practice-ready data
genArticleSectionData(article: Article): number // returns count of missing translations
The parseSentence function performs character‑level scanning to produce typed tokens. Each token receives a classification from PracticeArticleWordType:
Number— numeric values including currenciesWord— standard vocabularySymbol— punctuation and special characters
interface ArticleWord {
word: string
type: PracticeArticleWordType
index: number
start: number // character offset in original sentence
end: number
nextSpace: boolean // whether a space follows this token
}
The scanner handles edge cases like hyphenated compounds, contractions, abbreviations, and smart quote normalization. For every word, getDefaultArticleWord initializes a complete metadata object that the typing engine uses for validation and display.
Audio Position Mapping
If the article includes LRC (lyrics) timing data, the parser copies audioPosition values onto the corresponding sentences. This enables synchronized audio playback during practice without additional runtime lookup.
The Practice Session Runtime
Once parsed, the article enters the practice runtime — a coordinated system of Vue composables that manage state, input handling, navigation, and timing.
Core Store: Timing and Progress
The app/core/stores/practice.ts store (accessed via usePracticeStore) tracks:
| State | Purpose |
|---|---|
stage |
Current practice phase (idle, active, paused, finished) |
spend |
Total active milliseconds (excludes paused/idle time) |
segments |
Array of [startMs, endMs] tuples for granular time analysis |
inputWordNumber |
Progress through current article |
wrong |
Cumulative error count |
The store exposes resumeTimer() and pauseTimer() methods that handle manual pauses, tab visibility changes, and idle detection. Timer state persists across navigation so study statistics remain accurate even if the user switches sentences.
Word‑Level Typing Composables
The sentence‑by‑sentence experience builds on four specialized composables in app/core/composables/practice-words/:
usePracticeWordTyping.ts — The input validation engine. Receives keystrokes, compares against currentWord.word, advances the cursor on match, and increments practice.wrong on mismatch. Returns reactive state for UI rendering (current position, error highlighting).
usePracticeWordSession.ts — Session lifecycle manager. Initializes with a Sentence object, builds the local word list, and coordinates transitions between sentences. Calls practice.resumeTimer() on start and practice.pauseTimer() on completion.
usePracticeWordNavigator.ts — Provides next() and previous() methods for sentence traversal. Skips sentences marked unknown, handles boundary conditions (first/last sentence), and triggers auto‑scroll to keep the active sentence visible.
usePracticeDisplayPolicy.ts — Determines word visibility based on spaced‑repetition logic. New or failed words appear fully; mastered words may show only first letters or remain hidden, forcing recall.
usePracticeIdleTimer.ts — Monitors input inactivity. After a configurable timeout without keystrokes, automatically invokes practice.pauseTimer() to prevent inflated study time.
Sentence‑Level Orchestration
The app/composables/practice-sentences/useSentenceTypingFlow.ts composable wires everything together into a cohesive sentence practice flow:
export function useSentenceTypingFlow() {
const session = usePracticeWordSession()
const navigator = usePracticeWordNavigator()
const typing = usePracticeWordTyping()
const audio = usePlaySentenceAudio()
const display = usePracticeDisplayPolicy()
function startSentence(sentence: Sentence) {
session.start(sentence)
display.applyPolicy(sentence.words)
audio.playSentenceAudio(sentence, audioEl, onAudioEnd)
typing.reset(sentence.words[0])
}
function onWordComplete() {
if (session.isLastWord) {
navigator.next()
} else {
typing.advance()
}
}
return { startSentence, onWordComplete }
}
This flow executes for every sentence in the article, creating the seamless sentence‑by‑sentence typing experience.
Audio Synchronization
Two composables handle audio in article practice:
usePlaySentenceAudio.ts— Plays pre‑recorded audio segments using LRC timing data. Accepts a callback for completion events.usePlayArticleTextAudio.ts— Falls back to browser TTS when recorded audio is unavailable.
Both respect the practice timer, pausing audio automatically when the session pauses and resuming at the correct offset.
Code Example: Full Practice Initialization
import { useBaseStore } from '@/app/core/stores/base'
import { usePracticeStore } from '@/app/core/stores/practice'
import { useSentenceTypingFlow } from '@/app/composables/practice-sentences/useSentenceTypingFlow'
import { genArticleSectionData } from '@/app/core/hooks/article'
// 1. Parse and store the article
const base = useBaseStore()
const article = {
title: 'Sunday Morning',
text: `It was Sunday. I never get up early on Sundays.\n\nI sometimes stay in bed until lunchtime.`,
}
const missingTranslations = genArticleSectionData(article)
base.article.bookList.push(article)
// 2. Initialize practice state
const practice = usePracticeStore()
const flow = useSentenceTypingFlow()
// 3. Start practicing the first sentence
const firstSentence = article.sections[0][0]
flow.startSentence(firstSentence)
practice.resumeTimer()
This pattern mirrors the implementation in the TypeWords source, where genArticleSectionData returns the count of sentences lacking translations for optional user notification.
Summary
- Parsing layer (
article.ts): Converts raw text into hierarchical sections → sentences → typedArticleWordtokens with audio positions. - Runtime layer:
usePracticeStoremaintains global timing; specialized word‑level composables handle input, navigation, display policy, and idle detection. - Orchestration:
useSentenceTypingFlowcoordinates the sentence‑by‑sentence loop, advancing through the article while synchronizing audio and updating statistics. - Audio: Dual audio path supports pre‑recorded LRC segments or TTS fallback, both integrated with pause/resume logic.
Frequently Asked Questions
How does TypeWords split an article into practice sentences?
The genArticleSectionData function in app/core/hooks/article.ts splits on blank lines to create sections, then uses line breaks and punctuation to isolate sentences. Each sentence passes through parseSentence, which performs character‑level scanning to produce typed ArticleWord objects with metadata for position, type, and spacing.
What happens when the user pauses or goes idle during article practice?
The usePracticeStore timer pauses immediately, and usePracticeIdleTimer detects inactivity to trigger automatic pausing. The segments array records active intervals as [startMs, endMs] tuples, ensuring spend reflects only actual typing time. Audio playback pauses synchronously and resumes at the correct offset when the user returns.
Can article practice work without pre‑recorded audio?
Yes. The usePlayArticleTextAudio.ts composable provides TTS fallback when LRC timing data is absent. The useSentenceTypingFlow checks for audio availability and switches automatically, maintaining the same sentence‑advancement behavior regardless of audio source.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →