How to Implement Custom Practice Flows in TypeWords Using the Runtime API
TypeWords provides a comprehensive runtime API in practice-flow-runtime.ts that enables developers to create, persist, and execute custom practice flows by defining valid PracticeFlowConfig objects and managing them through createPracticeFlowRuntime().
The zyronon/TypeWords repository exposes a flexible runtime API that transforms static configuration into dynamic practice sessions. By leveraging the composables found in app/core/composables/practice-words/, developers can build personalized learning experiences without modifying core UI components. This guide walks through the complete implementation process using the actual source code structure.
Understanding the TypeWords Practice Flow Architecture
The runtime API separates concerns into three distinct layers. First, the flow configuration schema defined in practice-flow-types.ts establishes the shape of valid configurations. Second, the runtime factory in practice-flow-runtime.ts creates isolated session instances and handles state progression. Third, the persistent store manages custom flow definitions in localStorage under the key PracticeFlowV2.
The system validates configurations through isValidFlowConfig before persistence. Built-in flows (system, free, review) live in practice-flow-config.ts, but the runtime API specifically exposes methods for custom flow management including saveUserFlow, setActiveCustomFlowId, and deleteUserFlow.
Defining a Valid Custom Flow Configuration
A custom flow must conform to the PracticeFlowConfig interface and pass validation checks before the runtime accepts it.
Schema Requirements and Validation
According to lines 20-31 of practice-flow-runtime.ts, every custom flow requires:
id: A unique string identifier (e.g.,'myCustom')label: Human-readable display nameversion: Must equalCURRENT_FLOW_VERSION(currently6)mode: Must beWordPracticeMode.Custom(enum value5)nodes: An array containing at least one node object
Each node must specify a source from the allowed set: taskNew, taskReview, current, or wrongWords. Nodes also require a non-empty steps array defining the practice templates to execute.
Node and Step Structure
Steps reference template IDs defined in STEP_TEMPLATE_META: followWrite, spell, listen, dictation, or identify. Configure optional behavior through:
wordAdvance: Either{type:'increment'}for sequential progression or awordLoopdefinition (lines 85-99)shuffleOnEnter: Boolean to randomize word order when entering the steponEnd: Array ofPracticeEndActionobjects such aswrongWordClear
import { WordPracticeMode } from '@/core/types/enum'
const myCustomFlow = {
id: 'myCustom',
version: 6,
mode: WordPracticeMode.Custom,
label: 'My Custom Flow',
nodes: [
{
id: 'new',
label: 'New Words',
source: 'taskNew',
steps: [
{
templateId: 'followWrite',
wordAdvance: { type: 'increment' },
onEnd: [],
shuffleOnEnter: false,
},
{
templateId: 'listen',
shuffleOnEnter: true,
},
],
},
{
id: 'review',
label: 'Review',
source: 'taskReview',
steps: [
{ templateId: 'identify', onEnd: [] },
{ templateId: 'dictation', shuffleOnEnter: true },
],
},
],
}
Persisting and Activating Custom Flows
Once defined, configurations must persist to localStorage and activate before use.
Saving Flows to Local Storage
The createPracticeFlowRuntime() factory returns a saveUserFlow method that validates configurations and writes them to storage. Lines 71-73 of practice-flow-runtime.ts confirm the storage key is PracticeFlowV2.
import { createPracticeFlowRuntime } from '@/core/composables/practice-words/practice-flow-runtime'
const runtime = createPracticeFlowRuntime()
// Save returns void but throws 'INVALID_FLOW_CONFIG' on validation failure
runtime.saveUserFlow(myCustomFlow.id, myCustomFlow, 'My Custom Flow')
The second argument is the configuration object; the third argument specifies the display name shown in the UI selection interface.
Setting the Active Custom Flow
TypeWords maintains exactly one active custom flow at a time. Lines 60-64 of practice-flow-runtime.ts show that resolveFlowInput retrieves the active ID when passed the string 'custom'.
// Activate the flow for upcoming sessions
runtime.setActiveCustomFlowId('myCustom')
If the ID does not exist in storage, the runtime will fail when attempting to start a session with WordPracticeMode.Custom.
Executing a Practice Session with the Runtime API
After activation, drive the practice UI using the runtime's session management methods.
Starting the Session with resolveFlowStart
The resolveFlowStart method (lines 31-57) initializes the session state. It accepts the practice mode, a TaskWords payload containing new and review word arrays, and the flow identifier string.
const taskWords = {
new: newWordArray,
review: reviewWordArray
}
const start = runtime.resolveFlowStart(
WordPracticeMode.Custom,
taskWords,
'custom' // Signals runtime to use the active custom flow
)
console.log('Initial word set:', start.words)
console.log('Starting cursor:', start.cursor)
console.log('Total new words:', start.newWordNumber)
console.log('Total review words:', start.reviewWordNumber)
This method selects the first node containing words, applies shuffle settings if configured, and returns the initial cursor position along with task statistics.
Navigating Steps and Phases
Progress through the flow using cursor advancement methods exposed by the runtime factory:
let { cursor, complete } = {
cursor: start.cursor,
complete: false
}
while (!complete) {
// Resolve the current phase definition including sub-steps
const phase = runtime.resolvePhaseByCursor(cursor)
// Get primary phase details for UI rendering
const mainPhase = runtime.getMainPhase(cursor)
// Render UI according to phase.practiceType
// Handle user input, word advancement, etc.
// Advance to next step or node
const result = runtime.advanceStepCursor(cursor)
cursor = result.cursor
complete = result.complete
}
getMainPhase(cursor): Returns the primary phase definition for the current stepresolvePhaseByCursor(cursor): Handles special sub-steps like wrong-word-clear loopsadvanceStepCursor(cursor): Moves to the next step/node and returns completion status
Managing and Updating Custom Flows
Update existing flows by calling saveUserFlow again with the same ID. The method overwrites the previous definition in localStorage.
Remove flows permanently using deleteUserFlow, which also clears the active flow if it matches the deleted ID (lines 22-26):
// Update existing flow
runtime.saveUserFlow('myCustom', updatedFlowConfig, 'My Updated Flow')
// Delete and deactivate if currently active
runtime.deleteUserFlow('myCustom')
Summary
- Custom flows require a
PracticeFlowConfigobject withversion: 6,mode: WordPracticeMode.Custom, and valid node/step definitions as specified inpractice-flow-types.ts - Validation occurs through
isValidFlowConfigbefore persistence to thePracticeFlowV2localStorage key - Runtime instantiation happens via
createPracticeFlowRuntime(), which exposessaveUserFlow,setActiveCustomFlowId, and session management helpers - Session initiation uses
resolveFlowStartwith the'custom'identifier to load the active flow configuration - Step progression relies on
advanceStepCursor,getMainPhase, andresolvePhaseByCursorto drive the practice UI
Frequently Asked Questions
What is the required version number for custom flows?
The version field must equal CURRENT_FLOW_VERSION, which is currently 6 as defined in practice-flow-runtime.ts. Using any other version number causes isValidFlowConfig to reject the configuration with an invalid version error.
How does the runtime validate custom flow configurations?
The saveUserFlow method invokes isValidFlowConfig before writing to localStorage. It checks for required fields (id, label, version, mode, nodes), validates that mode matches WordPracticeMode.Custom (value 5), ensures node sources are from the allowed enumeration, and verifies step template IDs exist in STEP_TEMPLATE_META. Validation failures throw the string 'INVALID_FLOW_CONFIG'.
Can I use multiple custom flows simultaneously?
No. The runtime maintains only one active custom flow at a time through setActiveCustomFlowId. However, you can store multiple flows in localStorage using saveUserFlow and switch between them by calling setActiveCustomFlowId with different IDs before starting a new session. The listUserFlows method returns all stored custom configurations.
Where are custom flows stored in TypeWords?
Custom flows persist to localStorage under the key PracticeFlowV2 as implemented in lines 71-73 of practice-flow-runtime.ts. The storage format maps flow IDs to their configuration objects and metadata, enabling persistence across browser sessions without requiring a backend database.
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 →