Skip to content

feat(student): mobile-first lesson experience — AI chat takeover, keyboard-safe layout, UX polish#386

Merged
guillermoscript merged 7 commits into
masterfrom
feat/student-ux-polish
Jul 13, 2026
Merged

feat(student): mobile-first lesson experience — AI chat takeover, keyboard-safe layout, UX polish#386
guillermoscript merged 7 commits into
masterfrom
feat/student-ux-polish

Conversation

@guillermoscript

Copy link
Copy Markdown
Owner

Summary

Makes the student lesson page actually usable on mobile. Two commits:

1. Lesson progress line + UX polish pack — thin course-progress line, reading-progress indicator, lesson scroll area, route progress bar, loading skeletons.

2. Mobile-first lesson AI chat + keyboard-safe layout — fixes the three core mobile problems: the AI chat was a fixed-height scroll box embedded inside the page scroll (nested-scroll trap), the on-screen keyboard hid the chat input (iOS vh bug), and h-screen pushed the footer nav behind Safari's toolbar.

Mobile chat: launcher → full-screen takeover

Researched how Khanmigo (mobile web), Duolingo Max, and Brilliant surface AI tutors on mobile — none embed a chat mid-page; they use contextual triggers that open a dedicated chat surface. Below sm the chat now lives behind a launcher card ("Start/Continue with the AI Tutor", message count, green completed state) and opens as a full-screen overlay:

  • Height tracks visualViewport (iOS keyboard) with 100dvh fallback — input always stays above the keyboard
  • Safe-area insets top/bottom, Escape closes, role="dialog" + aria-modal
  • Single mounted instance — chat state survives open/close
  • overscroll-contain — chat scroll never chains into the lesson behind it
  • 16px textarea on mobile (kills iOS focus auto-zoom)
  • Desktop (sm+) unchanged

Contextual tutor entry (Duolingo "Explain my answer" pattern)

New AskTutorChip under lesson content: one tap opens the chat (full-screen on mobile, scrolled into view on desktop) and auto-sends a localized "I don't understand this lesson" prompt. Hidden once the lesson is completed.

Layout fixes

  • h-screenh-dvh on the lesson shell (footer nav visible with Safari's URL bar)
  • Footer nav: env(safe-area-inset-bottom) padding + 40px minimum touch targets
  • Lesson scroll area: overscroll-y-contain (no accidental pull-to-refresh)
  • viewport export: interactive-widget=resizes-content (Android keyboard resizes layout) + viewport-fit=cover (enables safe-area env vars)
  • Replaced legacy anys in the chat with typed UIMessage / tool-invocation shapes

i18n

New lessonAIChat.mobile.* and lessonAIChat.contextual.* keys (en/es, plural-aware).

Test plan

  • npm run build green (compile + typecheck)
  • ESLint clean on changed files
  • On a phone (or DevTools 390×844): lesson scrolls as a single region; launcher opens full-screen chat; keyboard keeps the input visible (iOS Safari especially); chat scroll doesn't move the lesson; chip auto-sends and opens the chat; completed state shows the green launcher card; footer nav visible above the home indicator
  • Desktop lesson page unchanged (inline chat, prev/next nav, sidebar)

🤖 Generated with Claude Code

guillermoscript and others added 3 commits July 12, 2026 19:01
- Course progress line at the top of lesson pages (tenant-themed, a11y-labeled)
- Reading scroll progress under the lesson header (usehooks-ts)
- Route-change progress bar via next-nprogress-bar wired to var(--primary)
- Loading skeletons for course detail and lesson routes
- Optimistic mark-complete with revert on error
- Course-100% celebration: full-width confetti + localized toast
- Arrow-key prev/next lesson navigation (@tanstack/react-hotkeys, respects sequential lock and input focus)
- beforeunload guard on exam taker when answers are unsubmitted
- Fix pre-existing lint errors in touched files (no-explicit-any, unescaped entity, handleSubmit declaration order)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On mobile the lesson AI chat was a fixed-height scroll box embedded in the
page scroll (nested-scroll trap), the keyboard hid the input (iOS vh bug),
and the lesson shell used h-screen so the footer nav fell behind Safari's
toolbar.

- Chat on <sm is now a launcher card that opens a full-screen takeover
  (Khanmigo/Duolingo Max pattern): visualViewport-tracked height with
  100dvh fallback, safe-area insets, Escape to close, dialog semantics,
  single mounted instance so chat state survives open/close
- New AskTutorChip under lesson content: one tap opens the chat and
  auto-sends a localized "I don't understand" prompt
- Textarea 16px on mobile (prevents iOS focus zoom)
- overscroll containment on lesson + chat scroll areas (no scroll chaining
  or accidental pull-to-refresh)
- h-screen -> h-dvh on lesson shell; footer nav gets safe-area padding and
  40px+ touch targets
- viewport export: interactive-widget=resizes-content + viewport-fit=cover
- new lessonAIChat.mobile/contextual i18n keys (en/es)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Compile lesson MDX on the server (new serialize-lesson.ts, used by the
  student lesson page and teacher preview): no in-browser MDX compiler,
  no loading skeleton, no layout shift
- Reading-progress line switches to a neutral color so it can't be
  confused with the primary course-progress line above the header
- Comments flow with the page instead of a nested max-h scroll box
  (same scroll-trap the chat had)
- Localize hardcoded strings: lesson empty/error states, certificate
  banner + toasts, completion failure toasts (en/es)
- Replace native confirm() with AlertDialog for chat restart
- Quieter completion banner: solid emerald, no blur-glow pulse
  (celebration stays with the confetti), respects reduced motion
- Flatten the AI task section (task text is a plain block; the chat is
  the only card)
- Sidebar: drop strike-through on completed lessons (check already
  communicates it); locked-lesson CTA gets a verb ("Go to: …");
  video frame ring uses border token (visible in light mode)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@guillermoscript

Copy link
Copy Markdown
Owner Author

Pushed f07bf9f1 — design-critique fixes on top of the mobile work:

  • Server-side MDX: lesson content compiles in serialize-lesson.ts on the server (student page + teacher preview). No more in-browser MDX compiler, loading skeleton, or layout shift.
  • Progress bars disambiguated: course progress stays primary; reading progress is now neutral.
  • Comments flow with the page — removed the max-h-[800px] + ScrollArea nested-scroll box.
  • i18n: lesson empty/error states, certificate banner/toasts, completion-failure toasts now localized (en/es).
  • AlertDialog replaces native confirm() for chat restart.
  • Quieter completion banner: solid emerald, no blur-glow pulse; celebration stays with the confetti. AI task section flattened (one card, not three).
  • Minors: no strike-through on completed sidebar lessons, locked-lesson CTA gets a verb, video ring visible in light mode.

Kept as-is by design: AI task stays at the end of the lesson (it validates that the student read the content).

guillermoscript and others added 4 commits July 12, 2026 20:19
Replaces the native confirm() in lesson comments with the themed
AlertDialog (destructive action), matching the chat-restart dialog.
New deleteDialog i18n keys (en/es).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolves layout.tsx import conflict (keep StoredPreset + new seo imports)
and regenerates package-lock.json in sync with package.json (CI npm ci
was failing on a missing webpack tree).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
npm 10 (bundled with Node 20) rejects the npm-11-generated lockfile
("Missing ... from lock file") and lint-staged@17 requires Node >=22.
Matches local dev (Node 24 / npm 11).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Local installs use legacy-peer-deps (user-level npmrc), so the lockfile
omits peer-dependency trees (webpack et al). CI's default npm then fails
npm ci with "Missing ... from lock file". A project .npmrc makes every
environment resolve the same way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@guillermoscript guillermoscript merged commit 2d3997d into master Jul 13, 2026
2 checks passed
@guillermoscript guillermoscript deleted the feat/student-ux-polish branch July 13, 2026 02:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant