From c4cb3307fa1b541df5122cf4257ff7b3b442445d Mon Sep 17 00:00:00 2001 From: Benjamin Mako Hill Date: Fri, 31 Jul 2026 19:16:54 -0700 Subject: [PATCH] Update README and saltire docs to current behavior The README now documents the daily workflow (working date, live mode vs printed lists, regenerate semantics, full day-editor editability), describes cycle mode's rolling rotation accurately, repairs the custom-parameters section, and points to the saltire testing guide, which gains resume-after-a-break instructions. Co-Authored-By: Claude Fable 5 --- README.md | 58 +++++++++++++++++++++++++++++++++++++------------ docs/SALTIRE.md | 28 ++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d3c4629..335b7c9 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,34 @@ Selection uses the same weighting as the manual system it replaces: each answered call divides a student's weight by the course's weight factor (default 2), so students who have answered more questions become progressively less likely to be called. Each course can instead use -"cycle" mode, which shuffles the roster and calls everyone exactly once. -These, along with whether students can see their own assessments, are -per-course settings. +"cycle" mode, a rolling rotation through the roster in random order: +everyone is called once before anyone repeats, batches take the next +students in the current pass (continuing across lists and class days), +and skipped calls don't count as a turn. These, along with whether +students can see their own assessments, are per-course settings. + +## Daily use + +The instructor page works against a selectable date (defaulting to +today), so printing tomorrow's list a day in advance just means +switching the working date. There are two ways to run a class, per +day and freely mixed: + +- **Live mode**: a "call next student" button shows who's up (photo, + name, pronouns) with one-tap outcomes — the assessment levels, + missing (absent without an opt-out), or skip (as if the call never + happened). +- **Printed list**: generate a numbered list of any length, print it, + and mark it up on paper; after class, enter the outcomes in the day + editor. Generating is safe by design: when an unused list already + exists the button becomes an explicit "regenerate", and a warning + appears only if the day already has recorded outcomes (which are + always preserved). + +The day editor allows full correction of the record: change any call's +status, assessment, or note; delete lines entirely (effectively +excusing the student); and add calls after the fact for anyone on the +roster. ## Layout @@ -96,8 +121,8 @@ churn at the start of a term): Courses whose Canvas end date has passed are skipped automatically. Student names come from Canvas display names, which already reflect -preferred names. Pronouns require one extra piece of Developer Key -configuration: add a custom parameter +preferred names. Four custom parameters on the Developer Key give the +tool everything else it can use from Canvas: ``` pronouns=$com.instructure.Person.pronouns @@ -106,15 +131,14 @@ course_end=$Canvas.course.endAt grading_scheme=$com.instructure.Course.gradingScheme ``` -The course dates bound the schedule and date pickers; the tool works -fine without them when a course has no dates set in Canvas. - -to the key. Canvas then includes each person's pronouns in launches -and in the roster data (the tool requests memberships scoped to the -resource link, which is what makes Canvas attach per-member custom -fields). Pronouns appear on the live call card, printed call lists, -and each student's own page. If the account has pronouns disabled, -everything simply shows without them. +Pronouns then arrive in launches and in the roster data (the tool +requests memberships scoped to the resource link, which is what makes +Canvas attach per-member custom fields) and appear on the live call +card, printed lists, and each student's own page. The course dates +bound the schedule and date pickers, and the grading scheme becomes +importable into the grade display scale with one click in settings. +All four degrade gracefully: a course or account without them simply +does without. ## Opt-outs @@ -178,3 +202,9 @@ as real NRPS data, so everything past the launch behaves identically. Dev mode also relaxes the cookie settings that Canvas's iframe embedding requires in production (SameSite=None; Secure), which would otherwise break plain-http localhost use. + +The one thing dev mode cannot exercise is the real OIDC/JWT handshake +itself. For that, the app is tested against the saltire LTI platform +emulator — a hosted fake LMS that performs genuine LTI 1.3 launches +and answers NRPS requests. The full setup and re-run instructions are +in `docs/SALTIRE.md`. diff --git a/docs/SALTIRE.md b/docs/SALTIRE.md index a53c158..c0c5970 100644 --- a/docs/SALTIRE.md +++ b/docs/SALTIRE.md @@ -69,6 +69,34 @@ On https://saltire.lti.app → Test Platform → **Security Model**: `http://purl.imsglobal.org/vocab/lis/v2/membership#Learner`, save, and Connect again — you land on /me. +## Picking the test setup back up + +With the configuration saved under a saltire account (sign in, then +Save), resuming a test session is short: + +1. Start the local server (the keys and TLS cert in `instance/` are + already there): + + ``` + COLDCALL_SECRET_KEY=saltire-test .venv/bin/python -m flask \ + --app coldcall_lti run --port 5443 \ + --cert instance/tls-cert.pem --key instance/tls-key.pem + ``` + +2. If the browser has forgotten the self-signed certificate, open + https://localhost:5443/healthz and click through the warning again. +3. Sign in at https://saltire.lti.app → Test Platform, confirm the + saved configuration loaded (Security Model should show LTI 1.3 and + the localhost URLs), and press **Connect**. +4. If the launch fails at JWT validation or roster sync, compare the + token/jwks URLs shown in Platform Details against + `instance/lti_config.json` — the session id may have changed; copy + the new values in. Everything else should be stable. + +To test as a student instead of the instructor, change the role under +**User** to `.../membership#Learner`, Save, and Connect; switch back +to `#Instructor` the same way. + ## Caveats - **The token and jwks URLs embed a saltire session id** (the