feat: full feature buildout — streaming, i18n, mastery map, admin, jobs

Progressive lesson streaming via onSegment callback (fixes SSE for non-English
users — locale was shadowed in lesson-reader useEffect). Adds: BullMQ workers,
Redis stream buffer, token budget enforcement, Langfuse tracing, golden-eval
runner, Playwright e2e scaffolding, lesson depth/locale/preferences schema,
mastery map UI, admin panel (blueprints/users/reports/quality/misconceptions),
image queries, source citations, view transitions, reading animations, i18n
(next-intl), PDF export, surprise endpoint, and 402 passing unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 22:08:14 +02:00
parent 4e38b5a791
commit 5bf6013460
161 changed files with 27152 additions and 1161 deletions
@@ -0,0 +1 @@
ALTER TABLE "user" ADD COLUMN "locale" varchar(10) DEFAULT 'en' NOT NULL;
@@ -0,0 +1 @@
ALTER TABLE "lesson" ADD COLUMN "locale" varchar(10) DEFAULT 'en' NOT NULL;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "lesson" ADD COLUMN "locale" varchar(10) DEFAULT 'en' NOT NULL;
@@ -0,0 +1,13 @@
CREATE TABLE "invite" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"token" text NOT NULL,
"email" varchar(255),
"role" "user_role" DEFAULT 'learner' NOT NULL,
"invited_by" uuid NOT NULL,
"expires" timestamp NOT NULL,
"accepted_at" timestamp,
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "invite_token_unique" UNIQUE("token")
);
--> statement-breakpoint
ALTER TABLE "invite" ADD CONSTRAINT "invite_invited_by_user_id_fk" FOREIGN KEY ("invited_by") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "lesson" ADD COLUMN "depth" varchar(20) DEFAULT 'standard' NOT NULL;
@@ -0,0 +1 @@
ALTER TABLE "lesson" ADD COLUMN "depth" varchar(20) DEFAULT 'standard' NOT NULL;
@@ -0,0 +1,2 @@
ALTER TABLE "user" ADD COLUMN "preferences" jsonb NOT NULL DEFAULT '{}';--> statement-breakpoint
ALTER TABLE "lesson" ADD COLUMN "age_group" varchar(10) NOT NULL DEFAULT 'adult';
@@ -0,0 +1,2 @@
ALTER TABLE "lesson" ADD COLUMN "age_group" varchar(10) DEFAULT 'adult' NOT NULL;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "preferences" jsonb DEFAULT '{}'::jsonb NOT NULL;
+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE "blueprint" ADD COLUMN "prompt_version" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
ALTER TABLE "blueprint" ADD COLUMN "stale_at" timestamp;
@@ -0,0 +1,12 @@
CREATE TABLE "eval_run" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"suite" varchar(50) NOT NULL,
"false_fail_rate" real,
"accuracy" real,
"total" integer NOT NULL,
"passed" integer DEFAULT 0 NOT NULL,
"model_version" text NOT NULL,
"prompt_version" integer DEFAULT 0 NOT NULL,
"git_sha" varchar(40),
"created_at" timestamp DEFAULT now() NOT NULL
);
+30
View File
@@ -0,0 +1,30 @@
-- Theme taxonomy table (fixed, seeded once)
CREATE TABLE "theme" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
"slug" text NOT NULL UNIQUE,
"label_en" text NOT NULL,
"label_fr" text NOT NULL,
"emoji" text NOT NULL
);
-- Seed fixed taxonomy
INSERT INTO "theme" ("slug", "label_en", "label_fr", "emoji") VALUES
('sciences-naturelles', 'Natural sciences', 'Sciences naturelles', '🔬'),
('mathematiques', 'Mathematics', 'Mathématiques', ''),
('histoire', 'History', 'Histoire', '📜'),
('geographie', 'Geography', 'Géographie', '🌍'),
('langues-litterature', 'Languages & literature', 'Langues & littérature', '📖'),
('arts-culture', 'Arts & culture', 'Arts & culture', '🎨'),
('technologie-informatique', 'Technology', 'Technologie', '💻'),
('philosophie-ethique', 'Philosophy & ethics', 'Philosophie & éthique', '💭'),
('economie-societe', 'Economics & society', 'Économie & société', '📊'),
('sante-medecine', 'Health & medicine', 'Santé & médecine', '🏥'),
('sport-activite', 'Sport & activity', 'Sport & activité', ''),
('autre', 'Other', 'Autre', '');
-- Blueprint ↔ theme join
CREATE TABLE "blueprint_theme" (
"blueprint_id" uuid NOT NULL REFERENCES "blueprint"("id") ON DELETE CASCADE,
"theme_id" uuid NOT NULL REFERENCES "theme"("id") ON DELETE CASCADE,
PRIMARY KEY ("blueprint_id", "theme_id")
);
@@ -0,0 +1,17 @@
CREATE TABLE "blueprint_theme" (
"blueprint_id" uuid NOT NULL,
"theme_id" uuid NOT NULL,
CONSTRAINT "blueprint_theme_blueprint_id_theme_id_pk" PRIMARY KEY("blueprint_id","theme_id")
);
--> statement-breakpoint
CREATE TABLE "theme" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"slug" text NOT NULL,
"label_en" text NOT NULL,
"label_fr" text NOT NULL,
"emoji" text NOT NULL,
CONSTRAINT "theme_slug_unique" UNIQUE("slug")
);
--> statement-breakpoint
ALTER TABLE "blueprint_theme" ADD CONSTRAINT "blueprint_theme_blueprint_id_blueprint_id_fk" FOREIGN KEY ("blueprint_id") REFERENCES "public"."blueprint"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "blueprint_theme" ADD CONSTRAINT "blueprint_theme_theme_id_theme_id_fk" FOREIGN KEY ("theme_id") REFERENCES "public"."theme"("id") ON DELETE cascade ON UPDATE no action;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+84
View File
@@ -43,6 +43,90 @@
"when": 1782070177922,
"tag": "0005_site_email",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1782074310619,
"tag": "0006_slim_ultragirl",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1750550400000,
"tag": "0007_lesson_locale",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1782133019007,
"tag": "0008_noisy_cloak",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1782133583085,
"tag": "0009_unique_wallflower",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1782200000000,
"tag": "0010_lesson_depth",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1782138081002,
"tag": "0011_smooth_thanos",
"breakpoints": true
},
{
"idx": 12,
"version": "7",
"when": 1782500000000,
"tag": "0012_user_preferences",
"breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1782310455008,
"tag": "0013_illegal_random",
"breakpoints": true
},
{
"idx": 14,
"version": "7",
"when": 1782312574334,
"tag": "0014_faulty_namor",
"breakpoints": true
},
{
"idx": 15,
"version": "7",
"when": 1782313119046,
"tag": "0015_mighty_prowler",
"breakpoints": true
},
{
"idx": 16,
"version": "7",
"when": 1782400000000,
"tag": "0016_theme_map",
"breakpoints": true
},
{
"idx": 17,
"version": "7",
"when": 1782337567021,
"tag": "0017_married_betty_brant",
"breakpoints": true
}
]
}