# How to Manage Practice Flow Configurations and Transitions in TypeWords

> Learn to manage practice flow configurations and transitions in TypeWords. Explore declarative flow systems, runtime execution, and state transitions for effective practice sessions.

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

---

**TypeWords uses a declarative flow system where practice sessions are defined in [`practice-flow-config.ts`](https://github.com/zyronon/TypeWords/blob/main/practice-flow-config.ts), executed by [`practice-flow-runtime.ts`](https://github.com/zyronon/TypeWords/blob/main/practice-flow-runtime.ts), and driven through state transitions by [`usePracticeWordNavigator.ts`](https://github.com/zyronon/TypeWords/blob/main/usePracticeWordNavigator.ts).**

Managing practice flow configurations in TypeWords requires understanding three core layers: static flow definitions, runtime resolution, and navigator-driven transitions. This article explains how the open-source typing application structures practice sessions, persists custom configurations, and handles complex transitions including word loops, wrong-word clearing, and step advancement.

## Understanding Flow Structure and Configuration

### The PracticeFlowConfig schema

Every practice session in TypeWords follows a `PracticeFlowConfig` defined in [[`practice-flow-config.ts`](https://github.com/zyronon/TypeWords/blob/main/practice-flow-config.ts)](https://github.com/zyronon/TypeWords/blob/master/app/core/composables/practice-words/practice-flow-config.ts). This file contains both the **type definitions** and **built-in flow implementations**.

A flow consists of:

- **`id`** – unique identifier (e.g., `'system'`, `'free'`)
- **`mode`** – `WordPracticeMode` enum value
- **`nodes`** – sequential batches of words with their own step sequences
- **`steps`** – individual practice activities referencing step templates

### Built-in system flow example

The default `system` flow demonstrates the full structure:

```typescript
system: {
  id: 'system',
  version: CURRENT_FLOW_VERSION,
  mode: WordPracticeMode.System,
  label: 'smart_learning',
  nodes: [
    {
      id: 'new',
      label: 'new_words',
      source: 'taskNew',
      steps: [
        { templateId: 'followWrite', wordAdvance: WORD_LOOP_WITH_SPELL, onEnd: DEFAULT_ON_END },
        { templateId: 'listen', shuffleOnEnter: true, onEnd: DEFAULT_ON_END },
        { templateId: 'dictation', shuffleOnEnter: true, onEnd: DEFAULT_ON_END },
      ],
    },
    // additional nodes...
  ],
},

```

### Step templates and advance rules

**Step templates** (`followWrite`, `spell`, `listen`, `dictation`, `identify`) define UI behavior. **Advance rules** control progression:

- **`{ type: 'increment' }`** – move to next word linearly
- **`{ type: 'wordLoop', loopSize: number }`** – repeat words in groups before advancing

The `materializeWordAdvance` helper converts configurations into concrete `WordAdvanceRule` objects.

## Runtime Flow Management

### Creating and validating flow runtimes

[[`practice-flow-runtime.ts`](https://github.com/zyronon/TypeWords/blob/main/practice-flow-runtime.ts)](https://github.com/zyronon/TypeWords/blob/master/app/core/composables/practice-words/practice-flow-runtime.ts) exposes **`createPracticeFlowRuntime`** to instantiate isolated runtimes. Key functions include:

| Function | Purpose |
|----------|---------|
| `isValidFlowConfig` | Schema validation for nodes, steps, sources, and template IDs |
| `resolveFlowConfigOrSystem` | Fallback to system flow when validation fails |
| `loadPracticeFlow` | Resolution by `flowId`, custom flow object, or direct config |
| `resolveFlowStart` | Session initialization with word selection and cursor creation |
| `resolvePhaseByCursor` | Phase definition lookup including `inWrongWordClear` and loop states |

### Custom flow persistence

Custom flows integrate with **localStorage** via `getFlowStorage` and `setFlowStorage`. The storage key is `PracticeFlowV2`. Methods include:

- `saveUserFlow(id, config, label)` – persist a custom flow
- `deleteUserFlow(id)` – remove a custom flow
- `getActiveCustomFlowId()` / `setActiveCustomFlowId(id)` – manage active selection

## Cursor Model and State Tracking

The `PracticeFlowCursor` interface in [[`practice-flow-types.ts`](https://github.com/zyronon/TypeWords/blob/main/practice-flow-types.ts)](https://github.com/zyronon/TypeWords/blob/master/app/core/composables/practice-words/practice-flow-types.ts) uniquely identifies position within a flow:

```typescript
export interface PracticeFlowCursor {
  nodeIndex: number;        // Current node in flow.nodes
  stepIndex: number;        // Current step in node.steps
  inWrongWordClear: boolean; // Wrong-word clearing active flag
  loop: null | {            // Word loop sub-step state
    startIndex: number;
    endIndex: number;
    subStepIndex: number;
  };
  endActionIndex: number | null; // Position in onEnd action queue
}

```

This cursor enables **deterministic state restoration** and **transition prediction**.

## Navigator-Driven Transitions

### Core navigation architecture

[[`usePracticeWordNavigator.ts`](https://github.com/zyronon/TypeWords/blob/main/usePracticeWordNavigator.ts)](https://github.com/zyronon/TypeWords/blob/master/app/core/composables/practice-words/usePracticeWordNavigator.ts) composes runtime logic with UI state. It receives dependencies including `getPracticeData`, `getTaskWords`, and completion callbacks.

### Word source resolution

The `resolveWordsFromSource` function maps node `source` values to word lists:

- **`'taskNew'`** – new words from task
- **`'taskReview'`** – review words from task
- **`'current'`** – user-selected current list
- **`'wrongWords'`** – accumulated wrong words

### Loop handling mechanics

**Word loops** create sub-steps within a main step:

1. **`enterLoop`** – triggers when group size reached; sets `cursor.loop` and resets index
2. **`advanceLoopSubStep`** – progresses through loop iterations
3. **`leaveLoop`** – terminates loop, returns to main step progression

```typescript
// Simplified loop entry logic from navigator
function enterLoop(start: number, end: number) {
  cursor.value = {
    ...cursor.value,
    loop: { startIndex: start, endIndex: end, subStepIndex: 0 },
    index: start, // Reset to loop start
  };
}

```

### onEnd action queue processing

Each step's `onEnd` array defines **post-completion actions**:

- **`wrongWordClear`** – suspend progress, clear wrong words first
- **`navigate`** – conditional routing (complete session or advance step)
- **`collectWrongWords`** – gather statistics
- **`generateReport`** – create session summary

The `processNextEndAction` method executes actions sequentially, handling suspension for `wrongWordClear`.

### Step advancement and list ending

`runStepAdvance` handles transitions between steps and nodes:

- Skips empty nodes automatically
- Applies `shuffleOnEnter` when configured
- Updates `nodeWorkingWords` for the new phase
- Signals completion at final step of final node

`handleListEnd` coordinates three scenarios when a word list exhausts:
1. Enter `wordLoop` sub-step if configured
2. Continue `wrongWordClear` phase
3. Execute `onEnd` queue and advance

## Practical Implementation Examples

### Starting a practice session

```typescript
import { usePracticeWordNavigator } from '@/app/core/composables/practice-words/usePracticeWordNavigator';
import { WordPracticeMode } from '@/core/types/enum';

const deps = {
  getPracticeData: () => practiceData,
  getTaskWords: () => taskWords,
  getCurrentWord: () => practiceData.words[practiceData.index],
  checkWordIsNeedNext: (w) => !w.mastered,
  complete: () => console.log('Session complete'),
  notify: (lvl, msg) => console.info(`[${lvl}] ${msg}`),
};

const nav = usePracticeWordNavigator(deps);

// Initialize from system flow
const start = nav.resolveFlowStart(WordPracticeMode.System, taskWords);
nav.initializeNodeWords(start.words);
// start.cursor contains initial position

```

### Creating and saving a custom flow

```typescript
import { CURRENT_FLOW_VERSION } from '@/app/core/composables/practice-words/practice-flow-config';
import { saveUserFlow } from '@/app/core/composables/practice-words/practice-flow-runtime';
import { WordPracticeMode } from '@/core/types/enum';

const customFlow = {
  id: 'minimalDrill',
  version: CURRENT_FLOW_VERSION,
  mode: WordPracticeMode.Custom,
  label: 'Minimal Drill',
  nodes: [
    {
      id: 'drill',
      label: 'drill_node',
      source: 'current',
      steps: [
        { 
          templateId: 'followWrite', 
          wordAdvance: { type: 'wordLoop', loopSize: 3 },
          onEnd: ['collectWrongWords', 'navigate'],
        },
      ],
    },
  ],
};

saveUserFlow(customFlow.id, customFlow, 'Minimal Drill');

```

### Session snapshot restoration

```typescript
// Before app backgrounding or navigation
const snapshot = nav.buildSessionSnapshot();
// Persist to IndexedDB or localStorage

// Later restoration
const restored = nav.restoreSessionSnapshot(storedSnapshot);
if (!restored) {
  // Fallback handled automatically: system flow with reset cursor
  console.warn('Corrupted snapshot, using default flow');
}

```

## Transition Mechanics Summary

| Trigger | Method | Outcome |
|---------|--------|---------|
| User completes word | `next()` → `runWordAdvance()` | Index increments; may trigger loop entry |
| Loop group filled | `enterLoop()` | Cursor enters sub-step mode |
| Loop iterations complete | `leaveLoop()` | Returns to main step progression |
| Main list exhausted | `handleListEnd()` | Evaluates `onEnd` queue or advances step |
| Wrong-word clearing | `runWrongWordRetry()` | Shuffles wrong words, sets clearing flag |
| Navigate action | `processNextEndAction()` | Completes session or advances via `runStepAdvance()` |
| Manual restore | `restoreSessionSnapshot()` | Reloads full session state with validation |

## Summary

- **Flow configurations** are declarative objects in [`practice-flow-config.ts`](https://github.com/zyronon/TypeWords/blob/main/practice-flow-config.ts) combining nodes, steps, templates, and advance rules.

- **Runtime validation** in [`practice-flow-runtime.ts`](https://github.com/zyronon/TypeWords/blob/main/practice-flow-runtime.ts) ensures integrity and provides fallback to system defaults.

- **Custom flows** persist to localStorage with full CRUD operations and active-flow tracking.

- **The cursor model** captures complete session state including nested loop positions and action queue progress.

- **The navigator** orchestrates all transitions: word advancement, loop handling, wrong-word clearing, and step progression.

- **Session snapshots** enable seamless resumption across application lifecycles.

## Frequently Asked Questions

### How do I add a completely new practice mode to TypeWords?

Define a new `PracticeFlowConfig` with a unique `id`, then register it via `saveUserFlow()`. The mode must use `WordPracticeMode.Custom`. Ensure your step `templateId` values correspond to existing templates in `STEP_TEMPLATE_META`, or extend the template system in [`practice-flow-config.ts`](https://github.com/zyronon/TypeWords/blob/main/practice-flow-config.ts).

### What happens if my custom flow configuration is invalid?

`resolveFlowConfigOrSystem()` automatically falls back to the built-in `system` flow. Validation in `isValidFlowConfig` checks node structure, step references, template IDs, and source values. Errors are logged via the navigator's `notify` dependency.

### Can I resume a practice session after closing the browser?

Yes. Call `buildSessionSnapshot()` before shutdown to capture cursor position, working words, and flow state. Store this object persistently, then use `restoreSessionSnapshot()` on return. If restoration fails, the navigator automatically initializes a fresh session with the system flow.

### How does word loop sub-step counting work?

When `wordAdvance.type === 'wordLoop'`, the navigator tracks group boundaries. Upon reaching `loopSize` words, `enterLoop()` sets `cursor.loop` with `startIndex`, `endIndex`, and `subStepIndex: 0`. The index resets to `startIndex`, and `advanceLoopSubStep()` increments `subStepIndex` each iteration until the loop completes.