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.
What the app does
Section titled “What the app does”A solo-prototype mobile app that teaches the user to spot and apply judgments — short, hand-captured insights — in real life. Three mechanics:
- 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), orUnsure. - Review — entries resurface on an SRS schedule; user marks
Practiced(take a rep) orIntegrated(graduate to long-tail). - 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.
Runtime
Section titled “Runtime” ┌─────────────────────────────┐ │ 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). └─────────────────────────────┘Dev-web parity
Section titled “Dev-web parity”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.
Cloud Worker
Section titled “Cloud Worker”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.
Data model
Section titled “Data model”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):
| Stream | Records | Read semantics | Write semantics |
|---|---|---|---|
entries.jsonl | Entry (id, createdAt, source, content, shape, isDeleted) | Latest-wins by id; tombstones filtered | Edits supersede via same id; soft-delete via is_deleted: true |
interactions.jsonl | Interaction (id, entryId, timestamp, action, response) | File-order, no dedup | One per resurface-action |
firings.jsonl | Firing (id, entryId, timestamp, note?) | File-order, no dedup | One per real-world “this applied” |
classifications.jsonl | Classification (id, entryId, classifierShape, confidence, reasoning, classifiedAt) | File-order; latest per entryId is current pick | One per LLM classify call (provenance only — the supersede write applies the pick) |
| Session token | Keyring on iOS/macOS | Single record | Set 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.
The two-phase write story
Section titled “The two-phase write story”Reads do the work: list_entries reads JSONL, dedups, filters,
returns. Every dispatch hits R2 (when signed in).
After the projection refactor (planned)
Section titled “After the projection refactor (planned)”WRITE READ───── ────1. append to events.jsonl (durable) RwLock::read on Projection2. 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.
Where things live (decision rules)
Section titled “Where things live (decision rules)”| Question | Rule | Doc |
|---|---|---|
| Side effect after user does X — Rust or Elm? | Rust if writes-to-log / survives-app-close; Elm if session-ephemeral | side-effect-orchestration.md |
| Read query — projection or log? | Projection (after refactor); both work today, projection is cheaper | event-sourced-state.md |
| Per-stream files or one event log? | One events.jsonl with t discriminator | in-memory-projection.md |
| New backend command? | Add to Command enum + dispatch_with_device arm + Command.elm encoder + make generate-elm-types | CLAUDE.md § “Architecture rules” |
| Theming / colors / spacing? | Ui.Tokens is the only swap point | CLAUDE.md rule 3 |
Wildcard match _? | Never on custom types — enumerate every variant | CLAUDE.md rule 2; user CLAUDE.md |
| AI feature — what does the LLM decide? | Classification only. Anchors and spotters are user-authored | project_shape_taxonomy memory |
Layout (where to look)
Section titled “Layout (where to look)”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 Elmscripts/ rename-app.sh, new-app.sh, deploy.shVerification commands
Section titled “Verification commands”make dev-web # Browser harness; same Rust as Taurimake dev-ios # iPhone simulatormake lint # elm-review (always green before "done")cd src-tauri/crates/core && cargo testcd src-tauri/crates/api && cargo testmake generate-elm-types # After any Rust type changeUI 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.
Pointers for deeper context
Section titled “Pointers for deeper context”- Architecture decisions:
docs/architecture/*.md - Project state, history, gotchas: see
MEMORY.mdindex 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(currently4b175d21— TestFlight 15)