Skip to content

Judgment Trainer: system overview

A reference for orienting in this codebase. Read this first if you’re returning after a break (or are a fresh Claude context). For any deeper “why” question, follow the pointers at the end.

A solo-prototype mobile app that teaches the user to spot and apply judgments — short, hand-captured insights — in real life. Three mechanics:

  1. Capture — the user types a judgment; an LLM classifies it as Train (something to do, Gollwitzer if-then), Recognize (something to notice, a constructed spotter example), or Unsure.
  2. Review — entries resurface on an SRS schedule; user marks Practiced (take a rep) or Integrated (graduate to long-tail).
  3. Reinforce — at the end of a practice session, a user-authored if-then anchor or spotter is shown for the most-relevant entry (“carry-this”).

Built for Justin (the user). Multi-user is a future concern.

┌─────────────────────────────┐
│ Elm frontend (src/) │ Browser.element TEA app.
│ Pages / Ui kit / Effect │ All UI lives here.
└────────────┬────────────────┘
│ Js.coreDispatch port (JSON envelope)
┌─────────────────────────────┐
│ Tauri host (src-tauri/src) │ Per-platform shim.
│ TauriDevice impl │ Mobile = iPhone, Mac = desktop.
│ spawn for side-effects │ Plugin wiring (notifs, deep-link).
└────────────┬────────────────┘
│ template_api::dispatch_with_device(req, device)
┌─────────────────────────────┐
│ template-api │ Pure-Rust dispatch layer.
│ Command match → Response │ Returns (Response, SideEffect).
└────────────┬────────────────┘
│ template_core::* (read/write/classify)
┌─────────────────────────────┐
│ template-core │ Pure-logic crate.
│ domain.rs, classify.rs, │ No Tauri, no async, no I/O
│ AppendLog trait, srs.rs │ except via injected traits.
└────────────┬────────────────┘
│ AppendLog::append/read_all
┌─────────────────────────────┐
│ FsAppendLog or │ Local JSONL files (dev /
│ CloudAppendLog │ single-device) or R2 via the
│ │ Worker (when signed in).
└─────────────────────────────┘

make dev-web boots the same template-api against a tiny core_server HTTP binary instead of the Tauri host. The Elm side talks to it via dev-web-shim.js over HTTP. Same Rust code in dev and production — no JS mocks, no drift. Scenarios in src/dev-web-scenarios.js seed JSONL fixtures for fast iteration.

worker/ is a Cloudflare Worker handling Apple Sign-In (/auth/*), R2-backed log storage (/append/*, /read/*), and LLM proxy (/llm/classify). The classify route fronts Anthropic — secrets live in Wrangler env. See project_backend_deploy memory for URLs + deployment.

Append-only JSONL. Five logical streams today (today’s per-stream files; collapsing to one events.jsonl is the planned next refactor — see in-memory-projection.md):

StreamRecordsRead semanticsWrite semantics
entries.jsonlEntry (id, createdAt, source, content, shape, isDeleted)Latest-wins by id; tombstones filteredEdits supersede via same id; soft-delete via is_deleted: true
interactions.jsonlInteraction (id, entryId, timestamp, action, response)File-order, no dedupOne per resurface-action
firings.jsonlFiring (id, entryId, timestamp, note?)File-order, no dedupOne per real-world “this applied”
classifications.jsonlClassification (id, entryId, classifierShape, confidence, reasoning, classifiedAt)File-order; latest per entryId is current pickOne per LLM classify call (provenance only — the supersede write applies the pick)
Session tokenKeyring on iOS/macOSSingle recordSet on sign-in; cleared on sign-out

Shape is Train { triggerX, actionY } | Recognize { spotter } | Unsure. User-authored fields (the X/Y/spotter) come from the Detail page; the LLM never fills them — see project_shape_taxonomy memory for the cog-psych reasoning.

EntryView is the wire-format projection sent to Elm — Entry plus derived nextReviewMillis from the SRS calculator. list_entry_views joins entries × interactions × SRS at read time.

Reads do the work: list_entries reads JSONL, dedups, filters, returns. Every dispatch hits R2 (when signed in).

WRITE READ
───── ────
1. append to events.jsonl (durable) RwLock::read on Projection
2. apply reducer to memory (cache) walk HashMap + offset-compute SRS
return Vec<EntryView>

Source of truth = the log. In-memory projection = derived view that exists for speed. If phase 2 panics, log is still correct → next boot re-hydrates.

See event-sourced-state.md for the principles, in-memory-projection.md for the implementation plan.

QuestionRuleDoc
Side effect after user does X — Rust or Elm?Rust if writes-to-log / survives-app-close; Elm if session-ephemeralside-effect-orchestration.md
Read query — projection or log?Projection (after refactor); both work today, projection is cheaperevent-sourced-state.md
Per-stream files or one event log?One events.jsonl with t discriminatorin-memory-projection.md
New backend command?Add to Command enum + dispatch_with_device arm + Command.elm encoder + make generate-elm-typesCLAUDE.md § “Architecture rules”
Theming / colors / spacing?Ui.Tokens is the only swap pointCLAUDE.md rule 3
Wildcard match _?Never on custom types — enumerate every variantCLAUDE.md rule 2; user CLAUDE.md
AI feature — what does the LLM decide?Classification only. Anchors and spotters are user-authoredproject_shape_taxonomy memory
src/ Elm frontend
Main.elm App entry, dispatch routing, page switcher
Command.elm Backend command encoders
Page/ One module per screen (Capture, Detail, Entries, ...)
Ui/ Design system; Tokens.elm = theme swap point
Generated/Decoders.elm Generated from Rust types via elm_rs (don't edit)
src-tauri/ Tauri host
src/lib.rs TauriDevice impl + core_dispatch + spawn wiring
crates/core/ Pure logic
src/domain.rs Entry, Shape, Interaction, Firing, Classification, list_*/append_*
src/classify.rs classify_and_supersede (the Rust-orchestrated AI side-effect)
src/append_log.rs AppendLog trait + FsAppendLog
src/cloud_log.rs CloudAppendLog (R2-backed)
src/srs.rs Day-bucket SRS scheduler
crates/api/ Dispatch layer (Command match → Response + SideEffect)
src/lib.rs dispatch_with_device, DispatchSideEffect enum
src/bin/core_server.rs Dev-web HTTP binary
worker/ Cloudflare Worker (auth + R2 + LLM proxy)
docs/architecture/ "Why we do it this way"
docs/reference/ "How the system works" (this doc)
review/ elm-review config — read before writing Elm
scripts/ rename-app.sh, new-app.sh, deploy.sh
make dev-web # Browser harness; same Rust as Tauri
make dev-ios # iPhone simulator
make lint # elm-review (always green before "done")
cd src-tauri/crates/core && cargo test
cd src-tauri/crates/api && cargo test
make generate-elm-types # After any Rust type change

UI changes aren’t done until you’ve driven the new path in the browser harness or sim — type-checking + tests are necessary but not sufficient. See feedback_verify_ui_by_driving memory.

  • Architecture decisions: docs/architecture/*.md
  • Project state, history, gotchas: see MEMORY.md index in ~/.claude/projects/-Users-j7hoenix-Main-Code-jg/memory/
  • Hard rules + pitfalls: CLAUDE.md (project root)
  • Elm style: review/src/ReviewConfig.elm
  • Last session’s handoff: the most recent commit on main (currently 4b175d21 — TestFlight 15)