Package map

hone/
├── cmd/                  Cobra commands (thin wiring layer)
│   ├── root.go           PersistentPreRunE: config.Init + db.Open
│   ├── add.go            hone add [-f file] <url>
│   ├── practice.go       hone practice
│   ├── import.go         hone import <file>
│   ├── export.go         hone export [--backup] [-o file]
│   ├── init.go           hone init <backup-file>
│   └── playlist.go       hone playlist create|list|select
│
├── internal/
│   ├── config/           Viper config init, accessors, BuildURL
│   ├── db/               sqlx open + goose migrations (embedded SQL)
│   ├── platform/         URL parsing (ParseURL)
│   ├── srs/              Pure SM-2 logic (UpdateEF, UpdateSRS, QualityFromDuration)
│   ├── store/            All DB queries (PickNext, InsertProblem, SaveSRSState…)
│   ├── scraper/          Headless scraping via external Chrome + Rod (title, difficulty, topics)
│   ├── monitor/          Headful Rod browser monitor (submission result detection)
│   ├── importer/         Playlist-format file parser (ParseImportFile)
│   ├── backup/           Export + restore (ExportFullBackup, RestoreFromBackup)
│   └── tui/              All Bubble Tea models
│
└── docs/                 This documentation

Data flow

Adding a problem

sequenceDiagram
    participant User
    participant cmd/add
    participant platform
    participant scraper
    participant store

    User->>cmd/add: hone add <url>
    cmd/add->>platform: ParseURL(url)
    platform-->>cmd/add: platform, slug
    cmd/add->>scraper: Scrape(browser, platform, slug)
    scraper-->>cmd/add: title, difficulty, topics
    cmd/add->>store: InsertProblem(...)
    store-->>cmd/add: problemID
    cmd/add->>User: ✓ Added: Two Sum (easy)

Practice session

sequenceDiagram
    participant User
    participant cmd/practice
    participant store
    participant srs
    participant monitor

    User->>cmd/practice: hone practice
    cmd/practice->>store: PickNext(db, filter)
    store-->>cmd/practice: problem, srsState, isDue
    cmd/practice->>monitor: Monitor(ctx, platform, slug)
    monitor-->>cmd/practice: Result{Success, Timestamp}
    cmd/practice->>srs: QualityFromDuration(difficulty, duration)
    srs-->>cmd/practice: quality
    cmd/practice->>srs: UpdateSRS(state, result, duration, difficulty)
    srs-->>cmd/practice: newState
    cmd/practice->>store: SaveSRSState(db, newState)
    cmd/practice->>store: RecordAttempt(db, ...)
    cmd/practice->>User: show summary

Key design decisions

srs vs store separation

internal/srs/ contains only pure functions (SM-2 math, quality mapping, state transitions) — no DB, no config dependencies. internal/store/ has all DB queries and imports srs for types.

The wiring: commands call store to fetch data, pass to srs for computation, call store again to persist. Changing the algorithm only touches srs; changing the DB schema only touches store.

TUI via router stack

Navigation is a stack of tea.Model values managed by internal/tui/router.go. Push a new model with PushMsg{Model: m}; pop back with the Pop() command helper. This lets any model navigate to any other without hardcoded parent/child relationships.

colorTable instead of bubbles/table

bubbles/table v1.0.0 uses runewidth.Truncate() which doesn't understand ANSI escape sequences — it counts escape bytes as visible characters, causing column misalignment when cells contain lipgloss styles. internal/tui/color_table.go replaces it with a table that uses lipgloss.Width() for all width calculations.

No CGO

modernc.org/sqlite provides a pure-Go SQLite driver, so hone cross-compiles cleanly without a C toolchain. The GoReleaser build produces static macOS binaries.


Database schema

problems        -- id, platform, slug, title, difficulty, created_at
topics          -- id, name
problem_topics  -- problem_id, topic_id (many-to-many)
playlists       -- id, name, created_at
playlist_problems -- playlist_id, problem_id, position (many-to-many, ordered)
attempts        -- id, problem_id, started_at, completed_at, result, duration_seconds, quality
problem_srs     -- problem_id, easiness_factor, interval_days, repetition_count, next_review_date, mastered_before

A database trigger trg_init_problem_srs automatically inserts a default problem_srs row whenever a problem is inserted. This guarantees every problem has SRS state and simplifies all query paths.