Maturity-SE Onboarding Lesson 5 of 5
Follow one feature from click to database

End-to-end trace: template to report

You have learned the loop, the stack, the schema, and the guards. Now follow one feature through every file it touches. By the end you can trace any feature in this repo without guessing.

12 min One win: trace any feature end-to-end in under 5 minutes Mission
Step 1 - create template

User clicks "Generate with AI"

The flow starts in components/new-template-form.tsx:

1. User fills: title, topic, context, targetAudience, scaleLength
2. Optional: toggles webResearch + deepReasoning (costs 1-3 credits)
3. Clicks "Generate" -> calls generateTemplate server action

The server action in app/actions/templates.ts:109 does:

generateTemplate()
  -> getUserId()                          // auth guard
  -> sanitizeForLlm() on all inputs       // strip HTML, control chars, injection
  -> resolveLlm()                         // platform credits or BYOK key?
  -> build prompt with topic/scale/research
  -> callLlm() or callLlmWithPlatformCredentials()
  -> stripReasoningTags()                 // remove thinking blocks
  -> extractFirstJsonObject()             // brace-depth parser, repairs truncation
  -> JSON.parse() -> { scaleLevels, domains }
The robustness trick: extractFirstJsonObject (lines 22-75) walks char-by-char tracking brace depth and string escapes. If the LLM output is truncated, it repairs by closing remaining braces/brackets. This is why template generation rarely fails.
Step 2 - save template

Template editor -> saveTemplate

The generated draft loads into components/template-editor.tsx. User can edit domains, questions, scale labels. Then clicks Save:

saveTemplate() in app/actions/templates.ts
  -> getUserId()                          // auth guard
  -> Zod validation (SaveTemplateSchema)
  -> If id exists: db.update(templates).set({...}).where(eq(templates.id, id))
  -> If new: db.insert(templates).values({ id: nanoid(), userId, ... })
  -> revalidatePath("/templates")          // bust Next.js cache

The template now has visibility: private. To publish it to the gallery:

updateTemplateVisibility(id, "public")
  -> Check clonedFromId is null (cloned templates cannot be re-published)
  -> db.update(templates).set({ visibility: "public" })
The clone rule: cloneTemplate() copies a public template to your private list with clonedFromId set. updateTemplateVisibility blocks cloned templates from going public. This prevents re-publishing someone else's work.
Step 3 - create assessment

Assessment form -> createAssessment

User picks a template, fills teamName and dueDate. components/new-assessment-form.tsx calls:

createAssessment() in app/actions/assessments.ts
  -> getUserId()                          // auth guard
  -> Zod validation
  -> db.insert(assessments).values({
       id: nanoid(),
       userId,
       templateId,
       title,
       status: "draft",                   // starts as draft
       inviteToken: nanoid(32),           // the shareable key
       teamName,
       dueDate,
     })

The invite token is now live but the assessment is draft. Owner toggles to active via AssessmentStatusControl:

updateAssessmentStatus(id, "active")
  -> db.update(assessments).set({ status: "active" })

Now the invite link works. The link is /respond/{inviteToken}.

Step 4 - submit response

Respondent opens link -> submitResponse

app/respond/[token]/page.tsx is public (outside (app)/ layout). It does:

1. Lookup assessment by inviteToken
2. If not found: notFound()
3. If status === 'closed': show "Assessment Closed"
4. Lookup template by assessment.templateId
5. Pass both to RespondForm component

components/respond-form.tsx renders: intro screen, domain tabs, scale buttons. User picks values. On submit:

submitResponse() in app/actions/assessments.ts
  -> No getUserId() - this is the public action
  -> Verify assessment.status === 'active'
  -> db.insert(responses).values({
       id: nanoid(),
       assessmentId,
       respondentName,
       respondentRole,
       answers: { "q-dom1-q1": 3, "q-dom1-q2": 4, ... },
     })
The answers blob: Each key is a question ID from the template's domains.questions. Values are scale numbers (1-scaleLength) or text strings. The report aggregates these per domain and per question.
Step 5 - view report

Report page -> MaturityReport

app/(app)/assessments/[id]/report/page.tsx is behind the auth guard. It does:

1. getUserId() - auth guard
2. Get assessment by ID (owner check)
3. Get all responses for this assessment
4. Get template for domain/question labels
5. Compute aggregations:
   - domainScores: average per domain
   - scoreDistribution: count per scale value
   - respondentAverages: per-respondent average
   - overallScore: global average
6. Pass to MaturityReport component

components/maturity-report.tsx renders with Recharts:

- OverallScoreGauge: circular gauge showing global average
- DomainRadarChart: radar plot of domain averages
- DomainBarChart: horizontal bars for domain scores
- ScoreDistributionChart: histogram of scale values
- RespondentTable: per-respondent averages with roles
The full loop complete: Template (what to ask) -> Assessment (who+when, with token) -> Response (one jsonb blob each) -> Report (aggregated scores). You just traced 5 files, 4 tables, 2 auth layers, and 1 LLM pipeline.
Final check - you know this repo

Put it all together

Q1

A user reports "my template generation failed with a JSON parse error". What is the most likely cause?

Q2

You need to add a new required field to the templates table. What do you do?

Scores not stored - this is for your memory.

Congratulations

You are no longer lost

You can now: explain the core loop, run the app locally, read any Drizzle query, trace any route to its auth guard, and follow any feature end-to-end. That is more than most engineers know on day one.

Your toolkit

lib/db/schema.ts          - what data exists
app/actions/*.ts          - how mutations work
lib/auth-helpers.ts       - who is allowed
app/(app)/layout.tsx      - page-level guard
db/run.js                 - how migrations run
AGENTS.md                 - how to work here
Keep learning: Read the specs in docs/spec/ to understand why features exist. Check docs/issues-tracker/ for the lineage of decisions. Ask me anything about this repo - I am your teacher.
Navigation

All lessons

Previous: Auth guards Lesson 5 of 5 - Complete

Maturity-SE Onboarding - Glossary - Mission - Resources