OmniTutor
▸ P4 LLD plan modal · Haiku-first · iterate · transition

P4 · plan modal

Replace the /plan/<id> placeholder with the real Plan modal. Owl narrating · Haiku-generated 7-beat plan · iterate chips → "Sounds good · start" → routes to /lesson/<id> (P5 placeholder). The bridge from "I told the tutor what I want" to "I'm in the lesson." Contracts in schema, drama spec in drama_modals.html, owl in owl_brand.html.
▸ phaseP4 · plan modal
▸ depends onP3 shipped · /v1/lesson + setup form
▸ usesHaiku 4.5 · the only LLM call in P4
▸ unblocksP5 · streaming pipeline

1.Scope

By end of P4, GET /plan/<id> renders the real Plan modal: owl avatar with subtle lip-sync animation · typing-subtitle welcome ("Hey — here's how I'm thinking…") · Haiku-generated 7-beat plan with per-beat title + estimated minutes · "After this…" next-step hint · iterate chips (Looks good · Less math · Change level · More problems · Different scope · Different angle · Try again) + free-text + mic placeholder · Send / Lock ▸ button → cinematic veil → /lesson/<id> (P5 placeholder). Plan persists to plan_revisions on each iteration.

Acceptance demo: complete the P3 setup form for "Hooke's law" → land on /plan/<id> → see Haiku-generated 7 beats appear in <3s · click "Less math" → plan shimmers + regenerates with more conceptual flavor · click "Looks good · start" → cinematic veil · routes to /lesson/<id> placeholder.

2.Before / after

▸ end of P3

  • /plan/<id> → static placeholder showing captured intent JSON
  • No LLM call on plan view
  • No iteration · no transition forward
  • plan_revisions table empty
  • lessons.plan_json always NULL

▸ end of P4

  • /plan/<id> renders real Plan modal · auto-fires Haiku call
  • Haiku returns plan in <3s · 7 beats with title + est_min · drama covers latency
  • plan_revisions row written for revision_num=1, triggered_by='initial'
  • Iterate chips POST /v1/lesson/<id>/iterate · regenerates plan in-place with shimmer
  • Each iteration writes new plan_revisions row · revision_num+1, triggered_by='iterate', feedback_text=chip-or-text
  • Send/Lock writes lessons.plan_json + status='active' · routes to /lesson/<id>
  • /lesson/<id> placeholder exists (P5 fills it)

3.Work plan

▸ ordered work · backend → service → routes → template → JS

  1. Anthropic client extension: add generate_plan(intent, subject, topic) in app/models/anthropic_client.py. Calls Haiku 4.5 with structured prompt + JSON schema response. Returns {plan: {summary, beats:[{ord,title,est_min}], after}, raw}. Logs cost via existing track_run.
  2. Plan repo: app/repos/plans.py with insert_revision(lesson_id, plan_json, triggered_by, feedback_text) · increments revision_num · returns the row.
  3. Plan service: app/services/plan.py with build_plan_for_lesson(pool, lesson_id, *, feedback=None) · loads lesson · calls Haiku · writes revision · returns plan dict.
  4. POST /v1/lesson/<id>/plan · idempotent (uses Idempotency-Key from §3) · returns latest plan · creates revision_num=1 if none exists.
  5. POST /v1/lesson/<id>/iterate · body {chip?, free_text?} · calls Haiku with iterate prompt · writes new revision · returns new plan.
  6. POST /v1/lesson/<id>/lock · sets lessons.plan_json to latest revision · status='active' · returns {redirect:"/lesson/<id>"}.
  7. GET /plan/<id> rewrite: serves plan.html instead of placeholder. Template fires POST /v1/lesson/<id>/plan on load. Renders skeleton + drama animation while Haiku runs · hydrates beats when response arrives.
  8. plan.html template: owl avatar (large) · subtitle bubble (typing animation) · audio-player placeholder (real TTS in P10) · 7-beat plan card · "After this…" note · iterate chips row · free-text + mic + Send/Lock buttons · cinematic veil overlay.
  9. GET /lesson/<id> placeholder: friendly "Active runtime · P5 in progress" page so Send/Lock has somewhere to land.
  10. Drama modal: reuse the Physics-equations / Math-curve / History-parchment / Languages-script visuals from drama_modals.html · pick by subject.archetype. Renders during the <3s Haiku wait.
  11. Latency budget enforcement: if Haiku returns >5s, surface "tutor is thinking harder than usual" copy in the drama bubble. If Haiku errors, surface err envelope and offer "try again" button.

▸ Haiku prompt sketch

# SYSTEM
You are OmniTutor planning a {{ subject }} lesson on "{{ topic }}".
Read the student's intent. Output a 7-beat plan as JSON. Each beat has
ord (1-7), title (5-8 words), est_min (2-8). Plus an after string
suggesting the natural next lesson.

# USER
INTENT:
{intent_json}

OUTPUT (strict JSON, no preamble):
{
  "summary": "...",
  "beats": [{"ord":1,"title":"...","est_min":3}, ...],
  "after": "..."
}

4.Test plan

▸ acceptance tests

  1. Plan generates: POST /v1/lesson/<id>/plan returns 200 with 7-beat JSON in <3s · plan_revisions row written with revision_num=1 · model_runs row written with cost+latency
  2. Idempotency: repeat POST with same Idempotency-Key returns identical response · only 1 model_runs call
  3. Iterate: POST /v1/lesson/<id>/iterate {chip:"less-math"} returns new plan · plan_revisions revision_num=2 · feedback_text="less-math"
  4. Lock: POST /v1/lesson/<id>/lock writes lessons.plan_json · status flips to 'active' · returns redirect URL
  5. Plan page renders: GET /plan/<id> returns 200 HTML with owl SVG · subtitle bubble · drama animation · iterate chips visible
  6. Live load: visit /plan/<id> in browser · observe owl appears immediately · drama animation runs · plan beats hydrate within 3s
  7. Iterate UX: click "Less math" · observe shimmer + new beats · trace_id header from second model_runs row
  8. Lock UX: click "Sounds good · start" · cinematic veil shows · navigates to /lesson/<id> placeholder
  9. Lesson placeholder: /lesson/<id> renders friendly "P5 in progress" page
  10. Cold start error: if Anthropic key missing/invalid, plan page shows err envelope + "try again" button
  11. No regressions: sweep all 16 P0–P3 routes · all green

5.Acceptance gate

P4 ships when every line below is true. P5 cannot start until then.

▸ P4 done · sign-off list

  1. All 11 work-plan steps (§3) green · code in main
  2. All 11 acceptance tests (§4) pass
  3. End-to-end flow: discovery → physics → topic → setup → submit → plan modal renders Haiku plan → iterate once → lock → lands on /lesson/<id> placeholder
  4. Latency budget: Haiku call <3s · drama covers it · zero spinners
  5. plan_revisions has at least 2 rows for the demo lesson (initial + iterate)
  6. Mukesh signs off · git tag v0.21-p4-shipped