Skip to content

Commit 9f1863f

Browse files
willwearingclaude
andauthored
fix: resolve profile 500 from missing migrations + harden progress checks (#118)
Root cause: 3 pending migrations (paused_at_session_id, key_prerequisite, kp_failed_session_tracking) were never applied to production. Every query selecting from studentConceptState crashed with P2022 (column not found), breaking profile, mastery, and diagnostic endpoints. Applied migrations via `prisma migrate deploy`. Also fixes: - courseUnlocked now checks mastered/inProgress/needsReview > 0 instead of only completionPercent > 0 (which only counted mastered concepts) - CompletionEstimateService uses activeConceptWhere for totalConcepts count, matching ensureConceptStatesForAcademy (was including archived) - countMasteredConcepts wraps filter with activeConceptWhere for consistency with all other student state queries - Dashboard "Continue Studying" uses same broadened progress check Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3ce01ac commit 9f1863f

15 files changed

Lines changed: 290 additions & 112 deletions

File tree

apps/site/e2e/auth-docs.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ test.describe("Auth and docs", () => {
4040
await page.goto("/docs");
4141

4242
await expect(
43-
page.getByRole("heading", { name: "Guidance for creator teams" }),
43+
page.getByRole("heading", { name: "Getting started" }),
4444
).toBeVisible();
4545
await expect(
4646
page.getByRole("heading", { name: "Authoring" }),
@@ -49,7 +49,7 @@ test.describe("Auth and docs", () => {
4949
page.getByRole("heading", { name: "Operations" }),
5050
).toBeVisible();
5151
await expect(
52-
page.getByRole("link", { name: "Open product docs" }),
53-
).toHaveAttribute("href", "/docs");
52+
page.getByRole("heading", { name: "Reference", exact: true }),
53+
).toBeVisible();
5454
});
5555
});

apps/site/e2e/home.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test.describe("Graspful site", () => {
2121
await page.goto("/");
2222

2323
await expect(
24-
page.getByRole("link", { name: /start building free/i }).first(),
24+
page.getByRole("link", { name: /create free account/i }).first(),
2525
).toHaveAttribute("href", /sign-up/);
2626
});
2727

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,91 @@
11
import Link from "next/link";
2-
import { PageShell } from "@/components/site/page-shell";
2+
3+
const sections = [
4+
{
5+
title: "Authoring",
6+
items: [
7+
"Define course structure with concepts, sections, and dependencies.",
8+
"Review generated content before it reaches learners.",
9+
"Publish changes without rebuilding your own delivery platform.",
10+
],
11+
},
12+
{
13+
title: "Operations",
14+
items: [
15+
"Manage brands, API keys, and learner billing from the creator app.",
16+
"Use one platform runtime for learner delivery and revenue operations.",
17+
"Keep the flagship site separate from the white-label learner surfaces.",
18+
],
19+
},
20+
];
21+
22+
const docLinks = [
23+
{ title: "CLI Reference", href: "https://graspful.ai/docs/cli", description: "Install, authenticate, and run course commands from your terminal." },
24+
{ title: "Course Schema", href: "https://graspful.ai/docs/course-schema", description: "YAML structure for courses, concepts, knowledge points, and problems." },
25+
{ title: "Brand Schema", href: "https://graspful.ai/docs/brand-schema", description: "Configure your academy theme, domain, and landing page." },
26+
{ title: "Glossary", href: "https://graspful.ai/docs/glossary", description: "Key terms: concepts, knowledge points, mastery, diagnostics, and more." },
27+
];
328

429
export default function DocsPage() {
530
return (
6-
<PageShell
7-
eyebrow="Documentation"
8-
title="Guidance for creator teams"
9-
intro="Use Graspful with structured course definitions, source material, and AI-assisted authoring workflows."
10-
>
11-
<div>
12-
<h2 className="section-heading">Authoring</h2>
13-
<ul className="plain-list">
14-
<li>Define course structure with concepts, sections, and dependencies.</li>
15-
<li>Review generated content before it reaches learners.</li>
16-
<li>Publish changes without rebuilding your own delivery platform.</li>
17-
</ul>
18-
</div>
19-
<div>
20-
<h2 className="section-heading">Operations</h2>
21-
<ul className="plain-list">
22-
<li>Manage brands, API keys, and learner billing from the creator app.</li>
23-
<li>Use one platform runtime for learner delivery and revenue operations.</li>
24-
<li>Keep the flagship site separate from the white-label learner surfaces.</li>
25-
</ul>
26-
<Link href="/docs" className="button">
27-
Open product docs
28-
</Link>
29-
</div>
30-
</PageShell>
31+
<main id="main-content" className="pt-24">
32+
<section className="py-16 bg-muted/50">
33+
<div className="mx-auto max-w-6xl px-6">
34+
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
35+
Documentation
36+
</p>
37+
<h1 className="text-4xl font-bold tracking-tight text-foreground mb-4">
38+
Getting started
39+
</h1>
40+
<p className="text-lg text-muted-foreground max-w-2xl">
41+
Everything you need to build and publish adaptive courses with
42+
Graspful.
43+
</p>
44+
</div>
45+
</section>
46+
<section className="py-16">
47+
<div className="mx-auto max-w-6xl px-6">
48+
<div className="grid gap-5 sm:grid-cols-2 mb-12">
49+
{sections.map((section) => (
50+
<div
51+
key={section.title}
52+
className="rounded-2xl border border-border/50 bg-card p-10 shadow-sm transition-all duration-300 hover:-translate-y-1 hover:shadow-lg"
53+
>
54+
<h2 className="text-lg font-semibold text-foreground mb-4">
55+
{section.title}
56+
</h2>
57+
<ul className="space-y-2 text-muted-foreground text-sm leading-relaxed list-none p-0 m-0">
58+
{section.items.map((item) => (
59+
<li key={item} className="flex gap-2">
60+
<span className="text-primary shrink-0">&#10003;</span>
61+
{item}
62+
</li>
63+
))}
64+
</ul>
65+
</div>
66+
))}
67+
</div>
68+
<h2 className="text-2xl font-bold tracking-tight text-foreground mb-6">
69+
Reference
70+
</h2>
71+
<div className="grid gap-5 sm:grid-cols-2">
72+
{docLinks.map((doc) => (
73+
<Link
74+
key={doc.href}
75+
href={doc.href}
76+
className="rounded-2xl border border-border/50 bg-card p-8 shadow-sm transition-all duration-300 hover:-translate-y-1 hover:shadow-lg no-underline group"
77+
>
78+
<h3 className="text-base font-semibold text-foreground mb-1 group-hover:text-primary transition-colors">
79+
{doc.title} &rarr;
80+
</h3>
81+
<p className="text-sm text-muted-foreground leading-relaxed">
82+
{doc.description}
83+
</p>
84+
</Link>
85+
))}
86+
</div>
87+
</div>
88+
</section>
89+
</main>
3190
);
3291
}
Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,69 @@
1-
import { PageShell } from "@/components/site/page-shell";
1+
const steps = [
2+
{
3+
number: "1",
4+
title: "Find out what they already know",
5+
description:
6+
"Every course starts with a diagnostic. The platform figures out what the learner can prove they understand and skips the rest. No one sits through material they've already mastered.",
7+
},
8+
{
9+
number: "2",
10+
title: "Teach what actually matters next",
11+
description:
12+
"The sequence changes based on gaps. If a learner is weak on prerequisites, the platform handles that first. If they're strong, it moves them forward. The path is different for everyone.",
13+
},
14+
{
15+
number: "3",
16+
title: "Prove mastery before moving on",
17+
description:
18+
"Progress is gated on evidence. Learners solve problems that test real understanding. Clicking through slides doesn't count.",
19+
},
20+
{
21+
number: "4",
22+
title: "Keep what you learned",
23+
description:
24+
"Spaced review brings knowledge back at the right time. It's built into the product, not a feature you have to turn on. Students retain what they learned instead of losing it.",
25+
},
26+
];
227

328
export default function HowItWorksPage() {
429
return (
5-
<PageShell
6-
eyebrow="How it works"
7-
title="How Graspful works"
8-
intro="Diagnosis, mastery, and spaced review. Not a video player with a progress bar."
9-
>
10-
<div>
11-
<h2 className="text-2xl font-bold tracking-tight text-foreground mb-4">1. Find out what they already know</h2>
12-
<p className="text-muted-foreground">
13-
Every course starts with a diagnostic. The platform figures out what
14-
the learner can prove they understand and skips the rest. No one sits
15-
through material they&apos;ve already mastered.
16-
</p>
17-
<h2 className="text-2xl font-bold tracking-tight text-foreground mb-4">2. Teach what actually matters next</h2>
18-
<p className="text-muted-foreground">
19-
The sequence changes based on gaps. If a learner is weak on
20-
prerequisites, the platform handles that first. If they&apos;re strong, it
21-
moves them forward. The path is different for everyone.
22-
</p>
23-
</div>
24-
<div>
25-
<h2 className="text-2xl font-bold tracking-tight text-foreground mb-4">3. Prove mastery before moving on</h2>
26-
<p className="text-muted-foreground">
27-
Progress is gated on evidence. Learners solve problems that test real
28-
understanding. Clicking through slides doesn&apos;t count.
29-
</p>
30-
<h2 className="text-2xl font-bold tracking-tight text-foreground mb-4">4. Keep what you learned</h2>
31-
<p className="text-muted-foreground">
32-
Spaced review brings knowledge back at the right time. It&apos;s built into
33-
the product, not a feature you have to turn on. Students retain what
34-
they learned instead of losing it.
35-
</p>
36-
</div>
37-
</PageShell>
30+
<main id="main-content" className="pt-24">
31+
<section className="py-16 bg-muted/50">
32+
<div className="mx-auto max-w-6xl px-6">
33+
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
34+
How it works
35+
</p>
36+
<h1 className="text-4xl font-bold tracking-tight text-foreground mb-4">
37+
How Graspful works
38+
</h1>
39+
<p className="text-lg text-muted-foreground max-w-2xl">
40+
Diagnosis, mastery, and spaced review. Not a video player with a
41+
progress bar.
42+
</p>
43+
</div>
44+
</section>
45+
<section className="py-16">
46+
<div className="mx-auto max-w-6xl px-6">
47+
<div className="grid gap-5 sm:grid-cols-2">
48+
{steps.map((step) => (
49+
<div
50+
key={step.number}
51+
className="group rounded-2xl border border-border/50 bg-card p-10 shadow-sm transition-all duration-300 hover:-translate-y-1 hover:shadow-lg"
52+
>
53+
<p className="text-sm font-semibold text-primary mb-2">
54+
Step {step.number}
55+
</p>
56+
<h2 className="text-lg font-semibold text-foreground mb-2">
57+
{step.title}
58+
</h2>
59+
<p className="text-muted-foreground leading-relaxed">
60+
{step.description}
61+
</p>
62+
</div>
63+
))}
64+
</div>
65+
</div>
66+
</section>
67+
</main>
3868
);
3969
}

apps/site/src/app/(marketing)/pricing/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default function PricingPage() {
1010
>
1111
<div>
1212
<h2 className="text-2xl font-bold tracking-tight text-foreground mb-4">What creators pay</h2>
13-
<p className="text-muted-foreground">
13+
<p className="text-muted-foreground mb-6">
1414
No platform subscription. No setup fee. Graspful takes 30% of learner
1515
revenue and runs the infrastructure. You keep the rest.
1616
</p>
@@ -29,19 +29,19 @@ export default function PricingPage() {
2929
</div>
3030
<div>
3131
<h2 className="text-2xl font-bold tracking-tight text-foreground mb-4">What learners pay</h2>
32-
<p className="text-muted-foreground">
32+
<p className="text-muted-foreground mb-6">
3333
You set the price. Graspful handles checkout, access control, and
3434
subscription management through Stripe.
3535
</p>
36-
<ul className="text-sm space-y-2 list-none p-0 mb-6">
36+
<ul className="space-y-2 list-none p-0 mb-6">
3737
{[
3838
"Monthly and annual plans",
3939
"Checkout and payouts handled by Stripe",
4040
"Your own branded academy site",
4141
"One platform for course, billing, and learner access",
4242
].map((item) => (
43-
<li key={item} className="flex gap-2">
44-
<span className="text-primary">&#10003;</span> {item}
43+
<li key={item} className="flex items-center gap-2 text-sm">
44+
<span className="text-primary shrink-0">&#10003;</span> {item}
4545
</li>
4646
))}
4747
</ul>

apps/site/src/app/icon.svg

Lines changed: 3 additions & 3 deletions
Loading

apps/site/src/components/auth/auth-form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export function AuthForm({ mode }: AuthFormProps) {
9595
? `Sign in to ${brand.name}`
9696
: isConfirmationPending
9797
? "Check your email for a confirmation link."
98-
: `Start building courses on ${brand.name}`;
98+
: "Next step: run graspful register from your editor to connect the CLI.";
9999
const submitText = isSignIn ? "Sign In" : "Create Account";
100100
const switchText = isSignIn ? "Don't have an account?" : "Already have an account?";
101101
const switchHref = isSignIn ? "/sign-up" : "/sign-in";

apps/site/src/components/site/__tests__/home-page.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ describe("HomePage", () => {
1414
screen.getByRole("heading", { name: /what we do for you/i }),
1515
).toBeVisible();
1616
expect(
17-
screen.getByRole("heading", { name: /how it works/i }),
17+
screen.getByRole("heading", { name: /three commands/i }),
1818
).toBeVisible();
1919
});
2020

2121
it("renders hero calls to action", () => {
2222
render(<HomePage />);
2323

2424
expect(
25-
screen.getAllByRole("link", { name: /start building free/i }).length,
25+
screen.getAllByRole("link", { name: /create free account/i }).length,
2626
).toBeGreaterThanOrEqual(1);
2727
});
2828
});

apps/site/src/components/site/header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Link from "next/link";
44
import { useTheme } from "@/lib/theme";
55

66
const navigationItems = [
7-
{ href: "/how-graspful-works", label: "Agents" },
7+
{ href: "/how-graspful-works", label: "How It Works" },
88
{ href: "/pricing", label: "Pricing" },
99
{ href: "/docs", label: "Docs" },
1010
];
@@ -78,9 +78,9 @@ export function SiteHeader() {
7878
</Link>
7979
<Link
8080
href="/sign-up"
81-
className="btn-gradient px-5 py-2 text-sm font-medium"
81+
className="btn-gradient px-5 py-2.5 text-sm font-medium shadow-md"
8282
>
83-
Get Started
83+
Create Free Account
8484
</Link>
8585
</div>
8686
</div>

0 commit comments

Comments
 (0)