- Renderer: The editor surface is a custom React component (
TypingFeedback) rather than Monaco. Characters are animated withframer-motionand coloured via Tailwind utility classes. - Token source: highlight.js performs static tokenisation for each prompt. We parse the produced HTML using
DOMParserto recover token spans and map them to character indices. - Colour mapping:
getSyntaxColorClassconverts highlight.js token types (keyword, string, comment, …) into VS Code-inspired Tailwind classes and adapts opacity for “pending” versus “typed” characters. - Fallback behaviour: If highlight.js does not recognise a language, the UI still renders but falls back to neutral colours.
- UI composition:
Appstitches together a set of focused components—BackgroundGrid,ProjectHeader,MenuBar, andEditorSurface. - State management: Zustand holds the session state (
targetSnippet,typed, attempts, timestamps, status) so external components can subscribe without prop drilling. - Hooks:
useLanguageSelectionhandles persisted language choices by reading/writinglocalStorageand validating against available packs.usePromptSelectionkeeps the active prompt for the chosen language, randomises initial selection, and emits updates to the store.useMenuToggleencapsulates menu open/close logic, including escape-to-close and outside click handling.
- Typing surface:
EditorSurfacekeeps focus on an invisible<textarea>layered over the visual rendering. All text updates flow through the session store’shandleInput. - Metrics: WPM and elapsed time derive from
calculateMetrics, which combines the engine summary with session timestamps. - Tab-to-indent:
handleTabAdvanceinspects the remaining prompt content, injects whitespace into the store, and restores the caret, enabling editor-like indentation without losing accuracy tracking.
- Engine loading:
initializeEnginetries to import the WASM bundle generated bycrates/engine. If present it uses the Rust implementation (evaluate_typed); otherwise a TypeScript diff evaluator runs in process. - Session storage:
storage/sessionRepository.tsmanages an IndexedDB database (devkeys/sessions) for completed runs. Summaries include accuracy, WPM, elapsed time, attempt snapshots, and prompt metadata. Helper functions return the most recent sessions or export the full history. - Metrics calculator:
lib/metrics.tsconverts the raw engine summary plus timestamps into elapsed milliseconds and words-per-minute.
flowchart LR
subgraph UI
KB[Keyboard Input]
TF[TypingFeedback]
MB[MenuBar]
end
KB --> Store(Zustand Session Store)
MB --> Store
Store --> Engine{evaluateTyping}
Engine --> Metrics[calculateMetrics]
Store --> TF
Metrics --> Store
Store --> IDB[(IndexedDB Sessions)]
%% Toast removed from the app
- AI proxy: Not yet implemented. Future designs must preserve the privacy boundary—only derived weakness metadata may leave the device, never raw keystrokes.
- Tree-sitter: Still on the roadmap; highlight.js currently handles syntax needs adequately for MVP.