> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuturuuu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Learn learner app

> Student and parent-facing education app for Tuturuuu workspaces.

Learn lives in `apps/learn` and runs locally on port `7812`. It is the learner-facing companion to the main Tuturuuu web platform: students and parents use Learn for lessons, practice, assignments, reports, marks, XP, streaks, and hearts, while teachers and admins continue to manage education data in `apps/web`.

## Ownership model

Learn does not render its own login portal. Its `/login` route first checks for an existing Learn app-session JWT. If the app session is already present, it redirects inside Learn to the requested `next` path, usually `/dashboard`. Otherwise it redirects to the platform login at `apps/web` with a `returnUrl` pointing back to `/verify-token`. Learn stores a host-only `tuturuuu_app_session` cookie after token verification, not a Learn-local Supabase Auth session.

The `/dashboard` entry route must only send users to `/login` when the Learn app-session is missing. If the app-session exists but the central bootstrap API returns no eligible education workspace, render the empty workspace state instead of redirecting back to login.

Social auth providers still run on `apps/web` because OAuth provider callbacks are registered for `tuturuuu.com`. After the platform login confirms the current account, `apps/web` generates a cross-app token for the `learn` target app and redirects back to `/verify-token`. Learn's local `POST /api/auth/verify-app-token` route only completes the host-only cookie handoff; token validation is delegated back to the central web app so Learn does not depend on a Learn-local Supabase Auth project. The handoff stores both a Learn-local app-session cookie for satellite route guards and a Web-issued app-session cookie for rewritten Web API requests. Protected Learn routes require both cookies; if an older local-only cookie is present, Learn sends the user back through the platform handoff so the coordinated cookie pair is refreshed without manual cookie deletion.

Protected product data still belongs to the central platform. Learn calls `apps/web` APIs through `@tuturuuu/internal-api`, and the satellite app rewrites `/api/v1/*` to the central web app. Learn-local API routes must stay limited to the auth cookie handoff and logout surfaces that require the Learn host.

Learn-local logout clears the host-only app-session cookies and stale Supabase Auth cookies on `learn.tuturuuu.*`, then redirects browser form submissions to the central `apps/web` `/logout?from=Learn` continuation. JSON callers can still POST `/api/auth/logout` and receive `{ success: true }`.

Learn also exposes a learner-focused AI Chat surface at `/[wsId]/ai-chat`. The UI stays inside `apps/learn`, while the chat stream uses the central `apps/web` `/api/ai/*` routes through the satellite rewrite so model routing, credits, logging, and chat persistence remain centralized.

## Workspace access

Every Learn workspace API requires both:

* the authenticated user has student access or an explicit active parent-student link; and
* `workspace_secrets.name = ENABLE_EDUCATION` is set to the canonical value `true` for that workspace.

The bootstrap route only returns eligible education workspaces and linked students:

```txt theme={null}
GET /api/v1/tulearn/bootstrap
```

Workspace-scoped learner routes use the normalized workspace id so aliases like `personal` do not diverge between selection and final reads. Linked learners in `workspace_user_linked_users` count as eligible student access even when the platform user is not a direct workspace member, so course lists and learner dashboard bootstrap must agree on the same workspace set.

## Parent links

Parent access is explicit and read-only in v1. The existing Tulearn schema adds:

* `tulearn_parent_student_links` for accepted parent to student links;
* `tulearn_parent_invites` for invite-based parent linking;
* admin-managed parent-link APIs under `/api/v1/workspaces/[wsId]/tulearn/parent-links`.

Parents can switch between linked students in Learn. Parent requests may read home, courses, assignments, reports, marks, and practice context for linked students, but mutation routes reject parent writes.

## Gamification

Learn-owned gamification behavior is backed by the existing Tulearn tables, separate from teacher-owned course data:

* `tulearn_gamification_events` stores XP events with an idempotency key per workspace and learner.
* `tulearn_learner_state` stores hearts, max hearts, total XP, streaks, freezes, selected workspace, and UI preferences. Direct authenticated writes are limited by RLS to the learner's own row in workspaces where that learner is a member; non-null `selected_workspace_id` values must also point to a workspace the learner can access.

XP awards and heart loss must go through the database RPCs `award_tulearn_xp` and `lose_tulearn_heart`. Those functions lock the learner state row and keep event insertion, streak updates, XP totals, heart decrements, and refill timer resets atomic under concurrent practice or assignment submissions. Streak dates currently use UTC day boundaries until Learn stores learner or workspace time zones.

Courses, modules, quiz sets, flashcards, assignments, monthly reports, and marks continue to reuse existing education and user-group tables.

## Code organization

Learn learner pages keep the public `apps/learn/src/components/learner-pages.tsx` entrypoint as a thin barrel export. Route-level screens live under `apps/learn/src/components/learner-pages/*`, with shared page motion, loading, empty state, and section primitives in `shared.tsx`.

The home dashboard should be a real learner workspace, not a landing page. Keep daily plan, quest board, learner toolkit, course path, assignments, marks, reports, practice, and AI Chat entrypoints visible as responsive Neobrutalist panels. Secondary rails should stack below the main grid until there is enough width for readable cards, and color should use dynamic theme tokens rather than a single accent.

Keep Learn learner files under 400 LOC and individual components under 200 LOC. When a screen grows, split reusable cards, rows, panels, and route-specific helpers into adjacent modules before final verification instead of letting `learner-pages.tsx` or any route screen become a monolith again.

Language switching is handled inside Learn with `next-intl`, but locale prefixes are hidden from URLs. Legacy locale-prefixed paths such as `/vi/dashboard` are canonicalized back to `/dashboard` while preserving the selected locale in `NEXT_LOCALE`. Keep English and Vietnamese strings in `apps/learn/messages/en.json` and `apps/learn/messages/vi.json`, then run `bun i18n:sort`.

Learn metadata and auth return URLs must resolve to absolute HTTP(S) app URLs. Prefer `LEARN_APP_URL` or `NEXT_PUBLIC_LEARN_APP_URL` for the app origin. A valid absolute `BASE_URL` can be used as a fallback, but non-URL environment values such as `development` are ignored so local development falls back to `https://learn.tuturuuu.localhost` through Portless.

Do not configure Learn return URLs with the retired `tulearn.tuturuuu.com` origin. Production learner redirects should use `https://learn.tuturuuu.com`, and teacher-facing redirects should use `https://teach.tuturuuu.com`.

## Verification

After changing Learn schema, API, or UI code, run:

```bash theme={null}
bun sb:up
bun sb:typegen
bun i18n:sort
bun type-check:learn
bun check
```

Run focused route or component tests around parent read-only access, student-only writes, XP idempotency, assignment completion, marks, and reports when those behaviors change.

## CI and deployment

Learn has dedicated Vercel workflows:

* `.github/workflows/vercel-preview-learn.yaml`
* `.github/workflows/vercel-production-learn.yaml`

Both workflows are registered in `tuturuuu.ts` and use the shared `ci-check.yml` switchboard. They require environment-scoped Vercel credentials plus `VERCEL_LEARN_PROJECT_ID`; production Supabase values should live in the Vercel project environment rather than GitHub Actions.

Deployment credentials must stay environment-scoped in GitHub Actions. The preview job is bound to the `vercel-preview-learn` GitHub Environment, and the production job is bound to `vercel-production-learn`. Store `VERCEL_TOKEN`, `VERCEL_ORG_ID`, and `VERCEL_LEARN_PROJECT_ID` in those environments instead of repository-wide or organization-wide secrets. Production Supabase values remain in the Vercel project environment pulled by `vercel pull`. The repository-level `TURBO_TOKEN` and `TURBO_TEAM` variable are passed only to the wrapped `vercel build` step; never place them at workflow or job scope, and never expose the token to pull-request or Dependabot code.

Preview dispatch is manual-only. Run `vercel-preview-learn.yaml` from `main`, set `preview_ref` to the reviewed branch, tag, or SHA, and keep `TRUSTED_PREVIEW_DEPLOY_ACTORS` limited to maintainers approved to run secret-backed preview builds. Manual production dispatch is only valid from `refs/heads/production`.

`apps/learn/vercel.json` disables Vercel Git deployments and GitHub integration so preview and production deploys only happen through the CI workflows.
