Skip to content

Commit f51d0b7

Browse files
authored
Merge pull request #21 from willwearing/add-autoresearch-self-improvement-loop
Add autoresearch self-improvement loop to docs
2 parents 76d9673 + cc52ea3 commit f51d0b7

17 files changed

Lines changed: 763 additions & 18 deletions

.dockerignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules
2+
dist
3+
.turbo
4+
*.env
5+
*.env.local
6+
.DS_Store
7+
coverage
8+
playwright-report
9+
tasks
10+
docs
11+
.git

.github/workflows/ci-deploy.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: CI & Deploy
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
# ─── Tests ───────────────────────────────────────────
15+
test:
16+
name: Test
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: oven-sh/setup-bun@v2
22+
with:
23+
bun-version: "1.3.6"
24+
25+
- name: Install dependencies
26+
run: bun install --frozen-lockfile
27+
28+
- name: Build shared package
29+
run: cd packages/shared && bun run build
30+
31+
- name: Generate Prisma client
32+
run: cd backend && bun x prisma generate
33+
34+
- name: Run backend tests
35+
run: cd backend && bun run test
36+
37+
- name: Run frontend tests
38+
run: cd apps/web && bun run test
39+
40+
# ─── Deploy backend to Railway (only on main) ───────
41+
deploy-backend:
42+
name: Deploy Backend (Railway)
43+
needs: test
44+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
45+
runs-on: ubuntu-latest
46+
steps:
47+
- uses: actions/checkout@v4
48+
49+
- name: Install Railway CLI
50+
run: npm install -g @railway/cli
51+
52+
- name: Deploy to Railway
53+
run: railway up --service ${{ vars.RAILWAY_SERVICE_NAME }}
54+
env:
55+
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
56+
57+
# ─── Deploy frontend to Vercel (only on main) ───────
58+
deploy-frontend:
59+
name: Deploy Frontend (Vercel)
60+
needs: test
61+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
62+
runs-on: ubuntu-latest
63+
steps:
64+
- uses: actions/checkout@v4
65+
66+
- uses: oven-sh/setup-bun@v2
67+
with:
68+
bun-version: "1.3.6"
69+
70+
- name: Install Vercel CLI
71+
run: npm install -g vercel
72+
73+
- name: Pull Vercel env
74+
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
75+
working-directory: apps/web
76+
77+
- name: Build
78+
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
79+
working-directory: apps/web
80+
81+
- name: Deploy
82+
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
83+
working-directory: apps/web

apps/web/.posthog-events.json

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
[
2+
{
3+
"event": "sign_up",
4+
"description": "User created a new account",
5+
"file": "src/components/auth/auth-form.tsx",
6+
"properties": ["method"]
7+
},
8+
{
9+
"event": "sign_in",
10+
"description": "User signed in successfully",
11+
"file": "src/components/auth/auth-form.tsx",
12+
"properties": ["method"]
13+
},
14+
{
15+
"event": "course_enrolled",
16+
"description": "User enrolled in a course",
17+
"file": "various",
18+
"properties": ["course_id", "course_name"]
19+
},
20+
{
21+
"event": "subscription_started",
22+
"description": "User started a paid subscription",
23+
"file": "various",
24+
"properties": ["plan", "billing_period"]
25+
},
26+
{
27+
"event": "diagnostic_started",
28+
"description": "User began a diagnostic assessment",
29+
"file": "src/components/app/diagnostic-flow.tsx",
30+
"properties": ["course_id", "total_estimated"]
31+
},
32+
{
33+
"event": "diagnostic_question_answered",
34+
"description": "User answered a diagnostic question (or skipped with 'I don't know')",
35+
"file": "src/components/app/diagnostic-flow.tsx",
36+
"properties": ["course_id", "question_number", "was_correct", "was_skipped", "response_time_ms"]
37+
},
38+
{
39+
"event": "diagnostic_completed",
40+
"description": "Diagnostic assessment finished with results",
41+
"file": "src/components/app/diagnostic-flow.tsx",
42+
"properties": ["course_id", "concepts_known", "total_concepts"]
43+
},
44+
{
45+
"event": "lesson_started",
46+
"description": "User started a lesson for a concept",
47+
"file": "src/components/app/lesson-flow.tsx",
48+
"properties": ["course_id", "concept_id", "concept_name", "total_knowledge_points"]
49+
},
50+
{
51+
"event": "lesson_practice_answered",
52+
"description": "User answered a practice problem within a lesson",
53+
"file": "src/components/app/lesson-flow.tsx",
54+
"properties": ["concept_id", "problem_id", "was_correct", "response_time_ms"]
55+
},
56+
{
57+
"event": "lesson_audio_played",
58+
"description": "User played audio for a lesson knowledge point",
59+
"file": "src/components/app/lesson-flow.tsx",
60+
"properties": ["concept_id", "kp_slug"]
61+
},
62+
{
63+
"event": "lesson_completed",
64+
"description": "User completed all knowledge points in a lesson",
65+
"file": "src/components/app/lesson-flow.tsx",
66+
"properties": ["concept_id", "concept_name", "duration_seconds"]
67+
},
68+
{
69+
"event": "quiz_started",
70+
"description": "User started a timed quiz",
71+
"file": "src/components/app/quiz-flow.tsx",
72+
"properties": ["quiz_id", "total_problems", "time_limit_ms"]
73+
},
74+
{
75+
"event": "quiz_question_answered",
76+
"description": "User answered a quiz question",
77+
"file": "src/components/app/quiz-flow.tsx",
78+
"properties": ["quiz_id", "question_index", "response_time_ms"]
79+
},
80+
{
81+
"event": "quiz_completed",
82+
"description": "Quiz finished with pass/fail result",
83+
"file": "src/components/app/quiz-flow.tsx",
84+
"properties": ["concept_id", "passed", "score"]
85+
},
86+
{
87+
"event": "review_started",
88+
"description": "User started a spaced repetition review session",
89+
"file": "src/components/app/review-flow.tsx",
90+
"properties": ["concept_id", "total_problems"]
91+
},
92+
{
93+
"event": "review_problem_answered",
94+
"description": "User answered a review problem",
95+
"file": "src/components/app/review-flow.tsx",
96+
"properties": ["concept_id", "problem_id", "was_correct", "response_time_ms"]
97+
},
98+
{
99+
"event": "review_completed",
100+
"description": "Review session finished with pass/fail result",
101+
"file": "src/components/app/review-flow.tsx",
102+
"properties": ["concept_id", "passed", "score"]
103+
},
104+
{
105+
"event": "section_exam_started",
106+
"description": "User started a timed section exam",
107+
"file": "src/components/app/section-exam-flow.tsx",
108+
"properties": ["section_id", "total_problems", "time_limit_ms"]
109+
},
110+
{
111+
"event": "section_exam_question_answered",
112+
"description": "User answered a section exam question",
113+
"file": "src/components/app/section-exam-flow.tsx",
114+
"properties": ["section_id", "question_index", "response_time_ms"]
115+
},
116+
{
117+
"event": "section_exam_completed",
118+
"description": "Section exam finished with pass/fail and score",
119+
"file": "src/components/app/section-exam-flow.tsx",
120+
"properties": ["section_id", "passed", "score"]
121+
},
122+
{
123+
"event": "study_task_dispatched",
124+
"description": "Study router redirected user to their next task",
125+
"file": "src/components/app/study-router.tsx",
126+
"properties": ["course_id", "task_type"]
127+
}
128+
]

apps/web/src/components/app/diagnostic-flow.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ProblemRenderer, type ProblemFeedback } from "@/components/app/problems
88
import { Button } from "@/components/ui/button";
99
import { Progress } from "@/components/ui/progress";
1010
import type { Problem, ProblemAnswer } from "@/lib/types";
11-
import { trackDiagnosticComplete } from "@/lib/posthog/events";
11+
import { trackDiagnosticComplete, trackDiagnosticStarted, trackDiagnosticQuestionAnswered } from "@/lib/posthog/events";
1212

1313
interface DiagnosticState {
1414
sessionId: string;
@@ -66,6 +66,12 @@ export function DiagnosticFlow({ orgSlug, courseId, academyId, token, initialDat
6666
);
6767
}, [diagnosticBasePath, token]);
6868

69+
// Track diagnostic start
70+
useEffect(() => {
71+
trackDiagnosticStarted(courseId, initialData.totalEstimated);
72+
// eslint-disable-next-line react-hooks/exhaustive-deps
73+
}, []);
74+
6975
// If initially complete, fetch result
7076
useEffect(() => {
7177
if (state.isComplete && !result && !fetchingRef.current) {
@@ -96,6 +102,13 @@ export function DiagnosticFlow({ orgSlug, courseId, academyId, token, initialDat
96102
);
97103

98104
const skipped = answer === "__I_DONT_KNOW__";
105+
trackDiagnosticQuestionAnswered(
106+
courseId,
107+
state.questionNumber,
108+
response.wasCorrect,
109+
skipped,
110+
Date.now() - startTimeRef.current,
111+
);
99112
setFeedback({ wasCorrect: response.wasCorrect, skipped });
100113

101114
// After brief delay, show next question or complete

apps/web/src/components/app/lesson-flow.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Progress } from "@/components/ui/progress";
99
import { BookOpen, CheckCircle2, ClipboardList, Play, Pause, Volume2 } from "lucide-react";
1010
import { useAudioPlayer } from "@/lib/hooks/use-audio-player";
1111
import { useLessonAudio } from "@/lib/hooks/use-lesson-audio";
12-
import { trackLessonComplete } from "@/lib/posthog/events";
12+
import { trackLessonComplete, trackLessonStarted, trackLessonPracticeAnswered, trackLessonAudioPlayed } from "@/lib/posthog/events";
1313
import { LessonRichContent } from "@/components/app/lesson-rich-content";
1414
import type { Problem, ProblemAnswer, RichContentBlock } from "@/lib/types";
1515

@@ -68,6 +68,8 @@ export function LessonFlow({ orgSlug, courseId, token, lesson }: LessonFlowProps
6868
const startedAt = Date.now();
6969
lessonStartRef.current = startedAt;
7070
practiceStartRef.current = startedAt;
71+
trackLessonStarted(courseId, lesson.conceptId, lesson.conceptName, lesson.knowledgePoints.length);
72+
// eslint-disable-next-line react-hooks/exhaustive-deps
7173
}, []);
7274

7375
function resetPractice() {
@@ -149,6 +151,12 @@ export function LessonFlow({ orgSlug, courseId, token, lesson }: LessonFlowProps
149151
}
150152
);
151153

154+
trackLessonPracticeAnswered(
155+
lesson.conceptId,
156+
currentProblem.id,
157+
response.correct,
158+
Date.now() - practiceStartRef.current,
159+
);
152160
setPracticeFeedback({
153161
wasCorrect: response.correct,
154162
explanation: response.feedback,
@@ -207,16 +215,17 @@ export function LessonFlow({ orgSlug, courseId, token, lesson }: LessonFlowProps
207215

208216
return (
209217
<button
210-
onClick={() =>
218+
onClick={() => {
219+
trackLessonAudioPlayed(lesson.conceptId, kp.slug);
211220
loadQueue([
212221
{
213222
id: kp.id,
214223
title: `${lesson.conceptName} - ${kp.slug}`,
215224
audioUrl: kpAudio.instructionUrl!,
216225
durationSeconds: kpAudio.instructionDuration,
217226
},
218-
])
219-
}
227+
]);
228+
}}
220229
className="flex items-center gap-2 rounded-lg bg-primary/10 p-3 text-sm text-primary hover:bg-primary/20 transition-colors w-full"
221230
>
222231
{isCurrentlyPlaying ? (

apps/web/src/components/app/quiz-flow.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Progress } from "@/components/ui/progress";
99
import type { Problem, ProblemAnswer } from "@/lib/types";
1010
import Link from "next/link";
1111
import { Clock } from "lucide-react";
12-
import { trackQuizComplete } from "@/lib/posthog/events";
12+
import { trackQuizComplete, trackQuizStarted, trackQuizQuestionAnswered } from "@/lib/posthog/events";
1313

1414
interface QuizData {
1515
quizId: string;
@@ -50,6 +50,12 @@ export function QuizFlow({ orgSlug, courseId, token, quizData }: QuizFlowProps)
5050

5151
const basePath = `/orgs/${orgSlug}/courses/${courseId}`;
5252

53+
// Track quiz start
54+
useEffect(() => {
55+
trackQuizStarted(quizData.quizId, quizData.totalProblems, quizData.timeLimitMs);
56+
// eslint-disable-next-line react-hooks/exhaustive-deps
57+
}, []);
58+
5359
// Timer
5460
useEffect(() => {
5561
if (result) return;
@@ -94,6 +100,11 @@ export function QuizFlow({ orgSlug, courseId, token, quizData }: QuizFlowProps)
94100
}
95101
);
96102

103+
trackQuizQuestionAnswered(
104+
quizData.quizId,
105+
currentIndex,
106+
Date.now() - questionStartRef.current,
107+
);
97108
setAnsweredCount(response.answeredCount);
98109

99110
// Move to next or stay on last

apps/web/src/components/app/review-flow.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState, useRef } from "react";
3+
import { useState, useRef, useEffect } from "react";
44
import { useRouter } from "next/navigation";
55
import { apiClientFetch } from "@/lib/api-client";
66
import { ProblemRenderer, type ProblemFeedback } from "@/components/app/problems/problem-renderer";
@@ -9,6 +9,7 @@ import { Progress } from "@/components/ui/progress";
99
import type { Problem, ProblemAnswer } from "@/lib/types";
1010
import Link from "next/link";
1111
import { CheckCircle, XCircle } from "lucide-react";
12+
import { trackReviewStarted, trackReviewProblemAnswered, trackReviewCompleted } from "@/lib/posthog/events";
1213

1314
interface ReviewData {
1415
sessionId: string;
@@ -49,6 +50,12 @@ export function ReviewFlow({ orgSlug, courseId, conceptId, token, initialData }:
4950

5051
const basePath = `/orgs/${orgSlug}/courses/${courseId}`;
5152

53+
// Track review start on mount
54+
useEffect(() => {
55+
trackReviewStarted(conceptId, initialData.totalProblems);
56+
// eslint-disable-next-line react-hooks/exhaustive-deps
57+
}, []);
58+
5259
async function handleSubmit(answer: ProblemAnswer) {
5360
if (submitting) return;
5461
setSubmitting(true);
@@ -69,6 +76,12 @@ export function ReviewFlow({ orgSlug, courseId, conceptId, token, initialData }:
6976
);
7077

7178
const wasCorrect = response.correct;
79+
trackReviewProblemAnswered(
80+
conceptId,
81+
problem.id,
82+
wasCorrect,
83+
Date.now() - startTimeRef.current,
84+
);
7285
if (wasCorrect) setCorrectCount((prev) => prev + 1);
7386
setFeedback({ wasCorrect, explanation: response.feedback });
7487

@@ -91,6 +104,7 @@ export function ReviewFlow({ orgSlug, courseId, conceptId, token, initialData }:
91104
body: JSON.stringify({ sessionId }),
92105
}
93106
);
107+
trackReviewCompleted(conceptId, completeResult.passed, completeResult.score);
94108
setResult(completeResult);
95109
} catch {
96110
// Still show what we have

0 commit comments

Comments
 (0)