# How Article Practice Works in TypeWords: Sentence‑by‑Sentence Typing Architecture Explained

> Discover how article practice in TypeWords works sentence by sentence. Learn about its hierarchical parsing, word-level typing loop, and timing statistics for efficient language learning.

- Repository: [Zyronon/TypeWords](https://github.com/zyronon/TypeWords)
- Tags: architecture
- Published: 2026-09-03

---

**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](https://github.com/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`](https://github.com/zyronon/TypeWords/blob/main/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:

```typescript
// 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 currencies
- `Word` — standard vocabulary
- `Symbol` — punctuation and special characters

```typescript
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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/app/composables/practice-sentences/useSentenceTypingFlow.ts)** composable wires everything together into a cohesive sentence practice flow:

```typescript
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`](https://github.com/zyronon/TypeWords/blob/main/usePlaySentenceAudio.ts)** — Plays pre‑recorded audio segments using LRC timing data. Accepts a callback for completion events.
- **[`usePlayArticleTextAudio.ts`](https://github.com/zyronon/TypeWords/blob/main/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

```typescript
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](https://github.com/zyronon/TypeWords/blob/master/app/core/hooks/article.ts), where `genArticleSectionData` returns the count of sentences lacking translations for optional user notification.

---

## Summary

- **Parsing layer** ([`article.ts`](https://github.com/zyronon/TypeWords/blob/main/article.ts)): Converts raw text into hierarchical sections → sentences → typed `ArticleWord` tokens with audio positions.
- **Runtime layer**: `usePracticeStore` maintains global timing; specialized word‑level composables handle input, navigation, display policy, and idle detection.
- **Orchestration**: `useSentenceTypingFlow` coordinates 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`](https://github.com/zyronon/TypeWords/blob/main/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`](https://github.com/zyronon/TypeWords/blob/main/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.