diff --git a/src/app/learn/[level]/[slug]/page.tsx b/src/app/learn/[level]/[slug]/page.tsx
index 2579a47..6a6b1c8 100644
--- a/src/app/learn/[level]/[slug]/page.tsx
+++ b/src/app/learn/[level]/[slug]/page.tsx
@@ -7,6 +7,7 @@ import ColoringRunner from "@/components/ColoringRunner";
import { getLessonBySlug, getLevel, getSubjects, cumulativeSvg, type Lesson } from "@/lib/curriculum";
import { getCurrentUser } from "@/lib/session";
import { getCompletedSteps, hasBadge } from "@/lib/progress";
+import { getLatestDrawing } from "@/lib/drawings";
export const dynamic = "force-dynamic";
@@ -52,11 +53,23 @@ export default async function LessonPage({ params }: { params: Promise<{ level:
if (lesson.phase === "outline" || lesson.phase === "detail") {
const steps = lesson.steps.map((s) => ({ n: s.n, title: s.title, instruction: s.instruction, tip: s.tip ?? "", lines: s.lines ?? [] }));
const meta = { ...common, badgeKey: lesson.badgeKey, badgeName: lesson.badgeName, baseSvg: lesson.baseSvg ?? "", phaseLabel: lesson.phase === "outline" ? "Outline" : "Details" };
+ const subj = getSubjects(level).find((s) => s.key === lesson.subjectKey);
+ // Carry the user's own outline drawing into the Details lesson as the starting canvas.
+ let carryImage = "";
+ if (lesson.phase === "detail" && user) {
+ const o = subj?.lessons.find((l) => l.phase === "outline");
+ if (o) carryImage = getLatestDrawing(user.id, level, o.sublevel) ?? "";
+ }
+ // Link to the next lesson in the subject.
+ let nextHref = "", nextLabel = "";
+ const nextPhase = lesson.phase === "outline" ? "detail" : "extra";
+ const nextLesson = subj?.lessons.find((l) => l.phase === nextPhase);
+ if (nextLesson) { nextHref = `/learn/${level}/${nextLesson.slug}`; nextLabel = lesson.phase === "outline" ? "Add the details" : "Color it in!"; }
return (
<>
-
+
>
);
diff --git a/src/components/TraceRunner.tsx b/src/components/TraceRunner.tsx
index 6ef1083..94e5f94 100644
--- a/src/components/TraceRunner.tsx
+++ b/src/components/TraceRunner.tsx
@@ -16,8 +16,9 @@ const SIZES = [4, 8, 16];
interface Point { x: number; y: number }
interface Stroke { color: string; size: number; points: Point[] }
-export default function TraceRunner({ meta, steps, loggedIn, username, completedSteps, alreadyEarned }: {
+export default function TraceRunner({ meta, steps, loggedIn, username, completedSteps, alreadyEarned, carryImage = "", nextHref = "", nextLabel = "" }: {
meta: Meta; steps: StepData[]; loggedIn: boolean; username: string; completedSteps: number[]; alreadyEarned: boolean;
+ carryImage?: string; nextHref?: string; nextLabel?: string;
}) {
const lastIndex = steps.length - 1;
const [idx, setIdx] = useState(Math.min(completedSteps.length, lastIndex));
@@ -69,9 +70,14 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed
useEffect(() => {
if (!loggedIn || alreadyEarned) return; let alive = true;
- loadProgressDrawing(meta.level, meta.sublevel).then((img) => { if (!img || !alive) return; const im = new Image(); im.onload = () => { baseImgRef.current = im; redraw(); }; im.src = img; });
+ loadProgressDrawing(meta.level, meta.sublevel).then((img) => {
+ // Use this lesson's own saved canvas if any; otherwise carry over the previous lesson's drawing
+ const src = img || carryImage;
+ if (!src || !alive) return;
+ const im = new Image(); im.onload = () => { baseImgRef.current = im; redraw(); }; im.src = src;
+ });
return () => { alive = false; };
- }, [loggedIn, alreadyEarned, meta.level, meta.sublevel, redraw]);
+ }, [loggedIn, alreadyEarned, meta.level, meta.sublevel, carryImage, redraw]);
// Auto-play the line video whenever the step changes
useEffect(() => { setPlaying(true); setPlayKey((k) => k + 1); }, [idx]);
@@ -95,8 +101,14 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed
function prev() { flushSave(); if (idx > 0) setIdx(idx - 1); }
async function finish() {
await saveStep(step.n, meta.badgeKey || undefined);
- const c = canvasRef.current;
- if (c && hasContent()) { const data = exportCanvasToDataURL(c); setFinalImage(data); if (loggedIn) await saveFinalDrawing(meta.level, meta.sublevel, "canvas", data); }
+ // Only completing the level (the badge lesson) saves to the profile gallery.
+ // Earlier lessons (e.g. Outline) keep their working canvas so it carries forward, but aren't saved as finals.
+ if (meta.badgeKey) {
+ const c = canvasRef.current;
+ if (c && hasContent()) { const data = exportCanvasToDataURL(c); setFinalImage(data); if (loggedIn) await saveFinalDrawing(meta.level, meta.sublevel, "canvas", data); }
+ } else {
+ flushSave(); // persist the outline canvas so the Details lesson can carry it over
+ }
setEarned(loggedIn && !!meta.badgeKey);
setFinished(true);
}
@@ -122,6 +134,23 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed
}
async function downloadBadge() { const b = await rasterizeBadge(); if (b) downloadBlob(b, "drawit-badge.png"); }
+ if (finished && !meta.badgeKey) {
+ // Outline (or any non-badge step): completed, but the level isn't finished — no profile saving here.
+ return (
+
+
+
✏️ Outline complete!
+
Awesome lines! Your outline carries over to the next lesson, so you can add the details right on top.
+ {nextHref &&
{nextLabel || "Next"} →}
+
+
+ Back to lessons
+
+
+
+ );
+ }
+
if (finished) {
return (
@@ -152,7 +181,8 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed
{uploadMsg &&
{uploadMsg}
}
-
+ {nextHref &&
{nextLabel || "Next"} →
}
+
{loggedIn && 🖼️ My drawings}
Back to lessons
diff --git a/src/lib/drawings.ts b/src/lib/drawings.ts
index cb5f56a..a1a830b 100644
--- a/src/lib/drawings.ts
+++ b/src/lib/drawings.ts
@@ -74,6 +74,18 @@ export function saveFinalDrawing(
return Number(info.lastInsertRowid);
}
+/** The most recent drawing for a lesson — the latest completed one, else the in-progress canvas. */
+export function getLatestDrawing(userId: number, level: string, sublevel: number): string | null {
+ const f = getDb()
+ .prepare(
+ `SELECT image FROM drawings WHERE user_id = ? AND level = ? AND sublevel = ? AND status = 'final'
+ ORDER BY created_at DESC, id DESC LIMIT 1`
+ )
+ .get(userId, level, sublevel) as { image: string } | undefined;
+ if (f) return f.image;
+ return getProgressDrawing(userId, level, sublevel);
+}
+
export function listFinalDrawings(userId: number): DrawingRow[] {
return getDb()
.prepare(