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.
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 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" })
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}.
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, ... },
})
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
A user reports "my template generation failed with a JSON parse error". What is the most likely cause?
You need to add a new required field to the templates table. What do you do?
Scores not stored - this is for your memory.
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.
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