Skip to content

Study Plans

Admin-defined study plan templates resolve when a user creates a study. Steps are snapshotted into per-study rows so template edits do not change in-flight studies.

Terminology: use study step in API/DB (not “TODO”) to avoid confusion with deferred mentorship tasks (Product Scope).

ConceptTableGraphQL
Plan templatestudy_plan_templatesStudyPlanTemplate (admin)
Template stepstudy_plan_template_stepsStudyPlanTemplateStep (admin)
Loop mapping (catalog)study_plan_mappings, study_plan_mapping_itemsStudyPlanMapping (admin)
Study step (instance)study_stepsStudyStep

Guided studies: published templates with study_method = guided are the catalog (title, description, spiritual phases). Users pick a template id as focus; see Study Methods.

Spiritual phases: each template has one or more spiritualPhases (pivot study_plan_template_spiritual_phases). Resolution matches when the user’s spiritual_phase is in that set (explicit allowlist, not a ladder).

On createStudy:

  1. Resolve a published template from method, user spiritual_phase, and focus.
  2. Set studies.plan_template_id and plan_applied_at.
  3. Copy each template step into study_steps (title, instructions, flow fields, step_key frozen). Loops expand here: mapped lists unroll fully; open loops snapshot the first iteration plus a continue/finish step (later iterations are appended at runtime).
  4. All steps in wave 1 are available; others remain pending until their wave unlocks.

Template changes affect new studies only.

Copy push (in-flight studies): after merging a revision on a published template, curriculum designers may use Force update in-flight studies in the admin plan editor. This copies title and instruction lanes onto matching study_steps for open studies (ended_at is null) and refreshes plan_loop_snapshot prototypes for remaining open-loop iterations. It does not add or remove steps, change waves/order, completion modes, quizzes, or learner notes. The action requires explicit confirmation and is never automatic on merge.

Template and study step instructions are stored as the same rich text envelope JSON string used for note CRDT bodies (column type remains text). Admins edit instructions in the TipTap-based admin editor; clients render with RichTextPreview.

Filter blocks (filterBlock nodes) hold conditional content keyed by user personality and learning style. At step materialization (snapshot onto study_steps), the server evaluates filters against the creating user’s profile and strips non-matching blocks so each study step stores only the instructions that user should see.

Legacy markdown instructions import via fromMarkdown() on read; new content is always envelope JSON.

MethodResolution order
Book (milk)Published template for (book, user phase) — strict; create fails if missing. UI lists books via availableBookStudyBooks.
Book (meat)Book-specific template if any → else generic meat template; if user already completed a study on the same book, variant=repeat template.
Book (transition)Book-specific if any → else generic transition → phase fallback.
GuidedPublished template for id in focus — strict if missing or phase-inaccessible. UI lists plans via availableGuidedStudyPlans.
Topical / word / character / hypotheticalGeneric template for (method, phase) with phase fallback; create allowed without steps if unpublished (soft).

Book focus is normalized to canonical USFM ids (e.g. Jn → Jhn) at create.

After a generic book template is chosen, the materializer checks loop book bindings on that template. If the study’s book has a binding, that loop unrolls; otherwise the loop stays open. Bindings do not change which template was resolved — tracks still list the generic plan.

A loop wraps a contiguous range of template steps (usually more than one: read → note → optional mentor review → apply). Pre- and post-loop steps stay outside the range. Multiple non-overlapping loops per participation flow are supported; one level of nesting is supported (a child loop fully inside a parent loop body). Partial overlap between sibling loops is rejected.

Default plans use open loops (the learner continues until they finish). Mapped iteration references the mapping catalog: designers pick mapping slugs on the loop (sources) and optional per-book bindings on generic book plans. Unlisted books keep the open loop.

Fully custom per-book templates (focus_kind = book) remain the layer above that — only when the steps themselves differ (milk John vs Romans).

Study at createBehavior
Open, no binding for this focusFirst iteration at create; after each body, continue_loop (Continue / Finish). Optional label binds {{item}} / {{section}}. Cap 50 iterations.
Book binding matches study focusUnroll selected mapping items at create (snapshot). Progress like “section 4 of 12”.
endMode = map with sourcesAlways unroll (one-book or non-book curated plan).

Loop JSON (per participation flow on study_plan_templates.loops): key, start_step_key, end_step_key, end_mode (user | map), item_variable, passage_grain (none | verse | either), collects_items, collect_title, collects_on (first | last), sources ([{ mapping, loop_key, item_keys }]), book_bindings ([{ book_id, sources }]). Empty item_keys means all items in mapping order; non-empty is an ordered subset. Sources may reference the mapping catalog or a prior loop’s collected items via loop_key.

Input vs output: end_mode + sources are input (what the loop iterates). collects_items / collect_title / passage_grain / collects_on are output (what a write-note step asks the learner to record). Collection runs on the first or last write_note body step. Nested open loops inherit the parent item’s passageStart / passageEnd and must stay inside that range.

Variables (after filter-block stripping; {{name}} in title and instruction text nodes only): mapping field keys; {{index}} (1-based), {{total}}, {{book}}, {{bookName}}. Resolved strings are stored on study_steps.

Reusable iteration lists live in study_plan_mappings (not seeded — designers author in admin). System mappings (canon.books, canon.chapters) are read-only, resolved from KJV config at runtime.

ConceptPurpose
Parent mappingTree folder (e.g. “Book sections”)
book_id on childAuto-pick child when a generic book study matches focus
related_mapping_idItems keyed to another mapping’s rows (parent_item_key)
sources on loopOrdered list; concatenated at materialize
book_bindingsPer-book sources override on generic book plans

Admin: /admin/mappings. Designer guide: Loop mappings. Included in curriculum dump as loop-mappings/{slug}.json (custom only).

Waves: loop-body waves are relative and remapped per iteration. Quiz grade/review companions expand per iteration. Post-loop waves wait until the loop finishes (open loops materialize post-loop steps only on Finish).

Instance identity: study_steps.step_key is {templateKey}__{iteration}; template_step_id stays the prototype. Columns: loop_key, loop_iteration, loop_variables. Open-loop append uses studies.plan_loop_snapshot so later iterations do not read a mutated live template.

MethodOpen loopMappedSeparate plan
BookLearner walks sections they outlinedAdd book on the generic planDifferent pedagogy (focus_kind = book)
Topical / word / characterAnother passage, occurrence, or episodeA single curated list on a dedicated templateGuided catalog, or a unique step list
Hypothetical / guided / mentorshipRareRareExplicit steps

Milk overview steps should not be chapter loops (Study Methods). Loops belong on later section work, mainly transition/meat generics.

Templates maintain separate step lists for mentored and individual participation (mentoredSteps / individualSteps in admin GraphQL). Materialization copies only the list matching the study’s participation_mode.

Each step declares:

FieldPurpose
waveSteps in the same wave unlock together; the next wave opens when all steps in the current wave are completed
completionModewrite_note, review_note, meet, acknowledge, assign_track, answer_quiz, grade_quiz, review_quiz, attest_outcomes, promote_phase, or continue_loop
primaryActormentee, mentor, or pair — who must act to complete
completionConfigMode-specific options (e.g. review reject target, meet stub note flags)

Wave progression: wave 1 steps are available at create; others start pending. Completing every step in a wave unlocks the next wave. During parallel waves (e.g. meeting prep), mentor and mentee each see their own actionable step; the other party may see a waitingReason until the wave completes.

Review rejection: mentors can rejectStudyStepReview on a review_note step (when allowReject is true). A mentor note is required; the prior write_note step reopens for revision while the review step returns to pending.

Meet steps (stub): authored as meet; runtime behaves as acknowledge plus optional lane notes until study sessions ship.

Mentor private notes: optional per-step reminders on mentor_private_note_id — mentor read/write only, excluded from discipleship lane sharing.

  • Visibility: clients query study.nextSteps(limit) for viewer-actionable steps in the active wave (may return multiple during parallel work).
  • Complete: completeStudyStep validates against snapshotted completionMode (optional lane note ids, quizAttemptId, outcomeChecks, or loopContinue / loopItem for continue_loop).
  • Mentorship: mentored studies use mentee and mentor note lanes per step; individual studies use the individual lane. See Mentorship.
  • Study ended: updateStudy with endedAt cancels pending/available steps (cancelled). Notes and log entries remain the source of truth.

Curriculum-quality feedback on the plan template (not mentor quality — see Mentorship quality backlog).

ConceptTableGraphQL
Final rating / skipstudy_plan_feedbackStudyPlanFeedback
Mid-study step reportstudy_plan_step_reportsStudyPlanStepReport

Capture

  • Mid-study: reportStudyPlanStep while the study is open, on a step in the viewer’s nextSteps. Reasons: missing_instructions, unclear_instructions, other. Unlimited reports per user.
  • End of study: submitStudyPlanFeedback or skipStudyPlanFeedback once per user per study when all steps are complete or the study has endedAt. Requires plan_template_id; skip/noop track templates are excluded.

Analysis (designers only)

  1. Rule-based priority on step reports (critical for missing_instructions on published plans, etc.).
  2. AI classify (when study_plan_ai.provider is set): ai_severity, ai_tags, ai_summary on reports and low-star final comments.
  3. Critical notify: in-app badges on /admin/plans; email to curriculum designers and platform admins once per unacknowledged critical report.
  4. Template analysis: analyzeStudyPlanFeedback → async job + studyPlanFeedbackAnalysis(requestId); MCP analyze-study-plan-feedback.

GraphQL (curriculum access): adminStudyPlanFeedback, adminStudyPlanStepReports, adminStudyPlanFeedbackCounts, acknowledgeStudyPlanStepReport. StudyPlanTemplate.feedbackCriticalCount / feedbackHighCount on admin queries only.

Curriculum designers and platform administrators manage templates at /admin/plans. Designer guide: Study plans. Platform-only routes (users, audit, feature flags, AI config) require a platform administrator.

RoutePurpose
/admin/plansList, filters, resolution simulator
/admin/plans/new, /admin/plans/[id]Template form (dual mentored/individual steps, AI review, learner feedback panel)
/admin/plans/new/assistantAI wizard — platform admin + admin_study_plan_ai
/admin/plans/rubricRubric dimension CRUD
/admin/plans/outcomesLearning outcome CRUD
/admin/mappingsLoop mapping catalog (iteration lists for loops)
/admin/quizzesQuiz bank list
/admin/quizzes/[id]Quiz authoring (questions, draw config, AI review)
/admin/curriculum/reviewsReview queue — submit, approve, request changes

GraphQL: adminStudyPlanTemplates, create/update/delete; adminStudyPlanMappings, createStudyPlanMapping, updateStudyPlanMapping, deleteStudyPlanMapping; learningOutcomes (authenticated catalog); admin createLearningOutcome / updateLearningOutcome / deleteLearningOutcome; adminQuizzes, adminQuiz, createQuiz, updateQuiz, deleteQuiz, question/variant CRUD, reviewQuiz; review mutations submitCurriculumReview, approveCurriculumReview, requestCurriculumChanges, withdrawCurriculumReview, mergeCurriculumRevision; pushStudyPlanTemplateCopy (explicit copy push to open studies); queries curriculumReviewQueue, curriculumReviewRequest, curriculumRevision. StudyPlanTemplate.inFlightStudyCount counts open studies on that template.

Unpublished templates follow a draft → in review → approved → publish flow (editorial CMS pattern):

  1. Designer edits a draft (is_published = false).
  2. Designer calls submitCurriculumReview — fingerprint snapshot stored on the review request.
  3. A different curriculum designer or platform administrator calls approveCurriculumReview (self-approval blocked).
  4. Designer calls updateStudyPlanTemplate with isPublished: true — allowed only when an approved fingerprint matches the current entity state.

Published templates: live rows stay visible to learners. Attribute edits on a published template are staged in curriculum_revisions (JSON payload) until mergeCurriculumRevision applies them. StudyPlanTemplate exposes reviewStatus, hasStagedChanges, and activeRevisionId for admin UI.

Platform administrators may publish or merge without a prior review.

Local dev: StudyPlanTemplateSeeder — sample book plans, generic method plans, guided catalog templates, mentorship intro pairing plans. LearningOutcomesAndCheckupSeeder — Milk→Transition outcomes, promotion quiz, checkup template, and implicit outcomes on guided Foundations steps. DevSeeder also creates designer@garrison.test (curriculum designer, not a platform admin).

Portable backup of the catalog (not learner data) as garrison.curriculum.v1 — a zip of JSON files keyed by slug.

SurfaceWhoNotes
/admin/curriculum/backupDesigners download; platform admins importUpsert by slug; optional prune
php artisan curriculum:export / curriculum:importOperatorsImport outside local/testing needs --force

Excluded: studies, quiz attempts, review/revision queues. Elective-noop templates are recreated when tracks import.

Designer guide: Curriculum backup.

See Learning outcomes.

Quiz bank rows live in quizzes with ordered quiz_questions (multiple choice, true/false, short answer, etc.). Study steps with completion_mode = answer_quiz reference a quiz id in completion_config; attempts are stored per study step (quiz_attempts / quiz_responses).

Admin: curriculum designers and platform administrators author quizzes at /admin/quizzes (list) and /admin/quizzes/{id} (metadata, questions, learning-style variants, random draw config, async AI review). Unpublished quizzes follow the same review-before-publish gate as templates. Plan template steps pick a quiz via dropdown and link out to the editor — questions are not edited inline on the plan form.

Runtime: mentees call startQuizAttempt to draw questions (respecting pool config and prior attempts on retry), then submitQuizAttempt. Snapshots and raw answers are purged after grading completes; scores and outcome evidence are retained. Passing score ratio is compared to the step or quiz threshold.

Subjective questions (essay and future types that return needs_review) trigger an automatic pipeline at study materialization: companion steps grade_quiz and review_quiz are injected after the answer_quiz step in the same wave. Flow: submit → async AI pre-grade (AnalyzeQuizAttemptJob) → analyzed → grader confirms (grade_quiz; mentor in mentored studies, study owner in individual) → graded → mentee acknowledges results (review_quiz) → reviewed. Auto-only quizzes skip injection and complete on pass as before.

study_plan_rubric_dimensions holds the canonical checklist for template quality (seeded in dev and prod via StudyPlanRubricSeeder). Each dimension has a human-facing description and separate ai_instructions for agent prompts. Humans and AI agents share the same dimensions via StudyPlanRubric (forHumans(), forAgentPrompt()).

Public transparency (API): unauthenticated query studyPlanRubricDimensions { slug title description sortOrder } exposes rubric descriptions. aiInstructions is returned only for authenticated users with curriculum or platform admin access.

Admin AI assistance (platform administrator)

Section titled “Admin AI assistance (platform administrator)”

Behind the admin_study_plan_ai feature flag (feature_flags.default_enabled). See AI Guidelines. External agents use the same flows via Study plan template MCP.

FlowRoute / UIGraphQL mutationPoll queryMutates draft?
Wizard draft/admin/plans/new/assistant → prefills /admin/plans/newgenerateStudyPlanTemplateDraftstudyPlanTemplateDraft(requestId)Yes (once, unpublished)
Step instructionsPer-step actions in step editorgenerateStudyPlanStepInstructionsstudyPlanStepInstructions(requestId)Yes, after diff + Apply; single-level undo
Plan reviewReview plan on template formreviewStudyPlanTemplatestudyPlanTemplateReview(requestId)No — criticism only

Async job pattern: each mutation returns a handle type (StudyPlanTemplateDraftRequest, StudyPlanTemplateReviewRequest, or StudyPlanStepInstructionsRequest) with requestId. The admin UI subscribes to Laravel Echo for progress, then polls the matching query until the job completes or fails. MCP tools mirror the same operations (kebab-case tool names on the study-plan-templates server).

Draft agent may fetch optional wizard referenceUrls via WebFetch and search public examples (capped). Review and step agents do not use web tools in v1. All agents load rubric dimensions into their prompts. Usage is logged to ai_usage.

  • completion_rule automation (auto-complete from logs/notes).
  • Dynamic steps added after create, except open-loop iterations (continue/finish), which are in scope.
  • Nested loops deeper than one level, early-exit from a mapped list, and “add topic/word/character” catalogs.
  • Non-linear prerequisites, skip.
  • Template versioning (immutable publish versions) — partially addressed by staging revisions for published templates; full immutable version history remains in planning backlog.
  • Track runtime is documented in Tracks (assignment, skip/noop, electives, inferred progress). Still deferred: user_course_progress table and programmatic prerequisite graphs beyond phase — Architecture Questions.