Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Save Wisdom Tracker

A small local front-end for the Save Wisdom 1000 questions. A single Go binary (stdlib only) serves a JSON API and a single-page web UI that lets you see all 1000 questions, mark them done (with a completion date), filter and sort them, and pick random undone questions.

  • No build toolchain, no bundlers, no external Go dependencies (stdlib only).
  • No database: progress is a single JSON file, default ~/.savewisdom/state.json.
  • The question list is fetched once, at build time, checked in as JSON, and embedded into the binary — the server never needs the network.

Requirements

  • Go 1.26+ (anything that builds the code; the module declares go 1.26)
  • A browser with JavaScript enabled (any modern one)

Build & run

make build                    # produces ./savewisdom
./savewisdom                  # serves http://127.0.0.1:8080

Or in one step:

make run                      # builds, then runs with defaults

You can also pass flags directly:

./savewisdom -listen 0.0.0.0:8080 -state /path/to/state.json

Then open http://127.0.0.1:8080/.

Running with Docker

docker compose up -d        # builds locally and starts the service

Then open http://127.0.0.1:8080/.

  • The container listens on 0.0.0.0:8080 and stores state at /data/state.json, so the only thing the compose file sets is the host port (default 8080; override with SAVE_WISDOM_HOST_PORT=9000 docker compose up -d).
  • /data is a named volume by default. To keep data in a directory of your choice, mount it there in docker-compose.yml instead, e.g. ./data:/data.

Flags & environment

Flag Env var Default Purpose
-listen SAVE_WISDOM_LISTEN 127.0.0.1:8080 Address to bind. The localhost default keeps the app private; binding 0.0.0.0 exposes your done/not-done status (no answers are stored) to the network.
-state SAVE_WISDOM_STATE ~/.savewisdom/state.json Path of the JSON state file. A leading ~ is expanded.

Flags take precedence over the corresponding env vars.

State file

Progress lives in a single JSON file (never committed; it's in .gitignore). Missing file = no questions done. A corrupt file makes the server refuse to start with a clear error rather than overwrite your data.

{
  "version": 1,
  "questions": {
    "1":   { "done": true,  "completed_at": "2026-08-15T18:22:03Z" },
    "105": { "done": false }
  }
}
  • Keys are question numbers (stable across upstream list changes), not positions.
  • completed_at is RFC 3339 UTC, present only when done is true (cleared when you un-do a question). The UI renders it in your local timezone.
  • Writes are atomic: the server writes state.json.tmp in the same directory and renames it over the target, so a crash never leaves a torn file.

Web UI

  • Headerdone/total badge plus a gradient progress bar (green → gold), updated after every change.
  • Segmented tabsRandom (default) and Question List switch via pill tabs.
  • Random tab — a random undone question with Mark done (auto-picks the next one) and Pick another. When all 1000 are done it says so, celebratorily.
  • Question List tab — all 1000 questions with per-row status and completion date. Sort by question number (default) or completion date (done first, oldest → newest; not-done last). Filter by all / not done / done. Click (or press Enter/Space on a focused) row to toggle; changes are applied optimistically and rolled back with an error message if the server rejects them. Keyboard: rows are real buttons (Tab to focus, Enter to toggle); the tab menu supports Arrow keys. No CDN, no external fonts, no tracking.

HTTP API

Same-origin JSON; the UI is the only client you need, but the API is tiny and documented here anyway.

Method Path Description
GET / The single-page app (HTML).
GET /healthz Liveness probe, returns ok.
GET /api/questions All 1000 questions, list order: { id, number, text, category, done, completed_at? }[]
PATCH /api/questions/{id} Body {"done": true|false} — sets/clears the flag and completed_at, persists, returns the updated record. 400 bad body · 404 unknown id · 500 persistence failure.
GET /api/random One uniformly random undone question (server-side crypto/rand). When none remain: {"all_done": true} with HTTP 200.
GET /api/summary {"total": N, "done": M, "not_done": K}

Errors are JSON: {"error": "..."}.

Example:

curl -s http://127.0.0.1:8080/api/summary
# {"total":1000,"done":42,"not_done":958}

curl -s -X PATCH http://127.0.0.1:8080/api/questions/7 \
     -d '{"done":true}'
# {"id":7,"number":7,"text":"...","category":"...","done":true,"completed_at":"2026-08-15T22:49:04Z"}

Tests

go test ./...     # state store, JSON API (httptest), data invariants, web handler
go vet ./...
gofmt -l .

Regenerating the question list

The question list (site version v7.0, 2/8/2025 at time of writing) is open sourced by the project. If it changes upstream, re-run the generator:

go run ./tools/gen-questions        # fetches the live page, validates, writes
                                    # internal/data/questions.json

Useful flags: -url <page> (default: the live page), -html <file> (parse a saved copy offline), -out <path> (default: internal/data/questions.json), -expect <n> (default 1000). The generator validates 1000 sequential, non-duplicate, non-empty questions across 10 categories before writing, writes atomically, and internal/data re-validates invariants at startup (fail fast).

Project layout

├── main.go                    # flags, wiring, http.Server, graceful shutdown
├── internal/
│   ├── data/                  # embedded question list + invariants
│   ├── state/                 # done/not-done store: mutex, atomic save, crypto/rand
│   ├── api/                   # JSON HTTP API
│   └── web/                   # embedded SPA (index.html)
└── tools/gen-questions/       # one-time question fetcher/parser (build-time only)

See PLAN.md for the design rationale, data format, and the full phase plan.

About

A handy interactive interface for tracking progress through Save Wisdom questions.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages