FingerGo: A Cross-Platform Touch-Typing Trainer — Architecture Overview

Problem Statement
Touch typing is a fundamental skill for developers and people working at computers. I wanted to create a trainer for myself and for the people that:
- Has a Clean Interface → modern and minimalist, with only functionally necessary load. Designed to improve typing speed and accuracy
- Eye-friendly Dark/Light themes with the ability to customize
- Zen Mode 🧘
- Shortcuts, keyboard/Stat Toggle
- Real-time visual keyboard with finger mapping
- Live stats: WPM, CPM, accuracy, per‑key mistakes
- Hierarchical text/code library (plain text and code samples)
FingerGo delivers this with a native, lightweight architecture using Go + Wails v2 + JS.
System Architecture
High-Level Overview
┌─────────────────────────────────────────────────────────────┐
│ Wails Runtime │
│ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ GUI Layer │◄────────►│ Internal Layer │ │
│ │ (Frontend) │ Bridge │ (Backend) │ │
│ │ │ │ │ │
│ │ HTML/CSS/JS │ │ Go 1.25+ │ │
│ │ ES6+ Modules │ │ Repository Pattern │ │
│ │ EventBus │ │ DI │ │
│ └──────────────────┘ └────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────┐
│ JSON Storage │
│ XDG Directories │
│ ~/.local/share/... │
└──────────────────────┘
Technology Stack
| Layer | Technology | Purpose |
| Backend | Go 1.25+ | Business logic, data persistence |
| Bridge | Wails v2 | Go↔JS IPC, native webview wrapper |
| Frontend | Vanilla JS (ES6+) | UI, keyboard visualization, typing engine |
| Storage | JSON files | Texts, sessions, settings (XDG-compliant) |
| Platforms | Linux, macOS, Windows | Single codebase, native performance |
Backend Architecture (Go)
Paradigm: Repository Pattern with Domain-Driven Design. Interface-based dependency injection with idiomatic Go.
Repository Pattern with Dependency Injection
┌────────────────────────────────────────────────────────┐
│ App Layer │
│ (app/app.go) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Wails-exposed methods (API surface) │ │
│ │ • DefaultText() • SaveSession() │ │
│ │ • TextLibrary() • GetSettings() │ │
│ │ • Text(id) • UpdateSetting() │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Repository Interfaces │ │
│ │ (internal/domain/repository.go) │ │
│ │ │ │
│ │ type TextRepository interface { │ │
│ │ Library() (TextLibrary, error) │ │
│ │ Text(id string) (Text, error) │ │
│ │ ... │ │
│ │ } │ │
│ │ │ │
│ │ type SessionRepository interface { ... } │ │
│ │ type SettingsRepository interface { ... } │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Storage Layer (internal/storage) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Concrete implementations: │ │
│ │ • NewTextRepository(mgr) │ │
│ │ • NewSessionRepository(mgr) │ │
│ │ • NewSettingsRepository(mgr) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Storage Manager (storage.Manager) │ │
│ │ • Path resolution (XDG Base Directory) │ │
│ │ • File I/O (atomic writes, JSON encoding) │ │
│ │ • Validation & error handling │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────┐
│ Filesystem (JSON) │
│ texts.json │
│ sessions.json │
│ settings.json │
└─────────────────────────┘
Frontend Architecture (JavaScript)
Paradigm: Classic vanilla JavaScript approach (circa 2010-2015) without modern frameworks. Procedural code with functional patterns and Event-Driven Architecture.
Event-Driven Architecture with EventBus
┌─────────────────────────────────────────────────────────┐
│ EventBus (pub/sub) │
│ (js/events.js) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ on(event, callback) │ │
│ │ emit(event, data) │ │
│ │ off(event, callback) │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ │ │
│ │ │
┌──────┴──────┐ ┌──────┴──────┐ ┌─────┴─────┐
▼ ▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐
│ typing │ │keyboard │ │ session │ │ library │ │ settings │
│ .js │ │ .js │ │ .js │ │ .js │ │ .js │
└─────────┘ └─────────┘ └─────────┘ └──────────┘ └──────────┘
│ │ │ │ │
└─────────────┴─────────────┴────────────┴────────────┘
│
▼
┌─────────────────┐
│ Wails Bridge │
│ (Go methods) │
└─────────────────┘
Module Communication Flow
Example: User types a key
1. typing.js detects keypress
├─► emit('typing:keystroke', { key, correct })
│
├─► keyboard.js listener
│ └─► Highlights key on virtual keyboard
│
├─► session.js listener
│ └─► Updates WPM, accuracy, mistake counters
│
└─► typing.js self-listener
└─► Advances cursor, checks completion
Data Flow: End-to-End Example
Scenario: User completes a typing session
┌──────────────────────────────────────────────────────────────┐
│ Frontend (JS) │
├──────────────────────────────────────────────────────────────┤
│ 1. session.js detects completion │
│ └─► Calculates final stats (WPM, accuracy, mistakes) │
│ │
│ 2. emit('session:complete', statsPayload) │
│ │
│ 3. session.js listener calls: │
│ window.go.app.App.SaveSession(payload) │
└────────────────────────┬─────────────────────────────────────┘
│ Wails IPC (JSON-RPC over webview)
▼
┌──────────────────────────────────────────────────────────────┐
│ Backend (Go) │
├──────────────────────────────────────────────────────────────┤
│ 4. app.SaveSession(payload *domain.SessionPayload) │
│ └─► Validates payload │
│ │
│ 5. getSessionRepository() │
│ └─► Lazy-initializes if needed │
│ │
│ 6. sessionsRepo.Record(payload) │
│ └─► storage.SessionRepository implementation │
│ ├─► Reads sessions.json │
│ ├─► Appends new session │
│ ├─► Atomic write (tmpfile + rename) │
│ └─► Returns TypingSession with timestamp │
│ │
│ 7. Returns success/error to frontend │
└────────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Filesystem (JSON) │
├──────────────────────────────────────────────────────────────┤
│ ~/.local/share/FingerGo/sessions.json │
│ [ │
│ { │
│ "id": "uuid-v4", │
│ "timestamp": "2025-12-03T10:30:00Z", │
│ "textID": "en-basic-001", │
│ "wpm": 87, │
│ "accuracy": 96.5, │
│ "mistakes": { "a": 2, "s": 1 } │
│ } │
│ ] │
└──────────────────────────────────────────────────────────────┘
Native Performance
- No Electron overhead (~50MB vs 200MB+ memory footprint)
- Native webview (GTK WebKit on Linux, WKWebView on macOS, WebView2 on Windows)
- Compiled Go binary (no runtime interpreter)




