Skip to main content

Command Palette

Search for a command to run...

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

Updated
5 min readView as Markdown
FingerGo: A Cross-Platform Touch-Typing Trainer — Architecture Overview
A

Software Engineer. Go, Linux, PostgreSQL, Containers, CI/CD, cloud-native systems. Exploring system programming, Rust, desktop apps, and practical AI.

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

LayerTechnologyPurpose
BackendGo 1.25+Business logic, data persistence
BridgeWails v2Go↔JS IPC, native webview wrapper
FrontendVanilla JS (ES6+)UI, keyboard visualization, typing engine
StorageJSON filesTexts, sessions, settings (XDG-compliant)
PlatformsLinux, macOS, WindowsSingle 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)

Try It: github.com/AshBuk/FingerGo