Before:
- Task slug "t1" was generic, meaningless to user and crawlers.
- Question bank was keyed by `color`, so EVERY math task (multiplikation,
geometrie, ...) shared identical questions.
- No way for child / parent to ask AI for an explanation step-by-step.
Now (Workbook + Lerni variant from Übungsseite template):
Semantic slugs in config/tasks.php:
math-multiplikation-bis-100
math-geometrie-flaechen
deutsch-wortarten-nomen-verben
deutsch-gedicht-lernen
englisch-vocab-animals
englisch-past-simple
sachkunde-wasserkreislauf
Each task now has:
slug, title, topic, subject, meta, icon, color, points, status, due
config/task-questions.php re-keyed by `slug` (not color):
Every task gets its OWN question set — math-multiplikation has
multiplication questions, math-geometrie has area/perimeter, deutsch
has wortarten/grammar, etc.
Each question now has 5 keys:
question, options[4], correct, lerni_hint, lerni_steps[]
Lerni AI-helper (right sidebar in Workbook layout):
- Closed by default; "Frag Lerni" button reveals
- Hint box (💡) with short text-explanation
- Numbered Lösungsweg with method-steps (colored badges per subject)
- Shortlink URL `?lerni=1` (parents/teachers can deep-link to a
pre-opened explanation)
- #[Url(as: 'lerni')] hydrates from URL
- Auto-closes on nextStep (fresh question = fresh thinking)
Route updated:
Route::get('/tasks/{slug}/run', \App\Livewire\Tasks\Run::class)
->where('slug', '[a-z0-9-]+') // tighter than whereAlphaNumeric;
// allows hyphens, rejects UPPER
->name('tasks.run')
Tasks/Index::startTask / repeatTask: $slug arg + redirectRoute to
tasks.run with slug.
View livewire/tasks/run.blade.php switched from single column to
2-column grid (max-w-6xl): col-span-2 question card + col-span-1
Lerni aside. Per-subject color theming (focus-ring, progress bar,
step badges, option border on feedback).
Cleanup:
- Removed App\Livewire\Tasks\Modals\Runner (obsolete; page-based now)
- Removed livewire.tasks.modals.runner view
- Removed TasksRunnerModalTest
Tests (TasksRunPageTest 22, TaskQuestionBankTest 6, TasksStartButton 8):
- Semantic slug validation (no t[0-9]+ patterns)
- Per-slug question selection (math vs deutsch vs english vs sachkunde)
- math-multiplikation != math-geometrie (different content same color)
- Lerni-panel renders; lerni-open trigger; toggleLerni reveals steps
- Shortlink contains slug + ?lerni=1
- ?lerni=1 query auto-opens helper
- Route 404 for UPPER chars and unknown slugs
- startTask redirects to tasks.run with slug
Verification:
- 188 tests passed, 0 failed, 0 skipped (1024 assertions)
- npm build OK
- All 8 page routes 302 unauth
- All 7 task slug routes 302 unauth
- tasks/UPPER/run 404 (route regex)
- 7 modal aliases resolve (Runner removed)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bug fixes (browser console + UX):
1. Photo /photo: "Alpine Expression Error: preview is not defined" during
Lazy hydration. Placeholder now pre-declares the same x-data scope as
the real view (file, preview, status) so Alpine bindings on children
resolve from first paint and survive the morph.
2. Tasks Start button: "Unable to resolve dependency [Parameter #0 $component]
in LivewireUI\\Modal\\Modal". Livewire 4 server-side dispatch with
->to('livewire-ui-modal') drops named payload args. Switched to
$this->js("Livewire.dispatch('openModal', {...})") which emits a
browser-side dispatch that wire-elements reliably picks up.
3. Skeleton was generic across all pages. Replaced single
components.skeleton.page placeholder with page-specific placeholders that
mirror each view's actual layout (hero header, stat-tile grid,
subject-card grid, table rows, sub-nav, etc.).
New skeleton primitives:
components/skeleton/header.blade.php
components/skeleton/stat-tile.blade.php
components/skeleton/subject-card.blade.php
components/skeleton/table-row.blade.php
components/skeleton/card.blade.php
Per-page placeholders:
livewire/{dashboard,subjects,tasks,photo-help,rewards,progress,
notifications,settings}/placeholder.blade.php
Each placeholder() now returns its feature-local view; each placeholder
has a unique data-testid (dashboard-skeleton, tasks-skeleton, etc.)
Tests:
- PerPageSkeletonTest (18 assertions): every page returns its own
placeholder view, each has unique testid, photo-help placeholder
declares preview in x-data.
- TasksStartButtonTest updated: assertScriptRan -> assertion against
Livewire effects xjs key (correct Livewire 4 API).
- LazySkeletonTest removed (superseded by PerPageSkeletonTest).
Verification:
- 156 tests passed, 0 failed, 0 skipped (390 assertions)
- All 8 routes return 302 unauth (auth middleware correct)
- All 7 modal aliases resolve correctly via livewire.finder
- Build artifacts: manifest.json, logo/favicon.svg, manifest.webmanifest
all reachable
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BUG B — navigation.blade.php crashed on null auth()->user() for
unauthenticated visitors of /dev/quick-login. Two-layer fix:
- DevQuickLogin: add #[Layout('components.layouts.auth')] so page
renders without the authenticated navigation bar
- navigation.blade.php: null-safe ?-> on all auth()->user() calls
(three occurrences: name, name again, email) as defense-in-depth
BUG A — @vite() generated http:// asset URLs causing Mixed Content
on HTTPS domain. Root cause: ASSET_URL removed but APP_URL changed to
http://localhost by previous agent. Fixed: APP_URL restored to
https://app.dev.lernschiff.com so URL::forceScheme('https') activates
and assets render as https://app.dev.lernschiff.com/build/assets/*.
TDD: test written → RED (500) → fix → GREEN. 46/46 pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
Phase 9: verified dashboard.blade.php uses x-layouts.app, lizenz-abgelaufen.blade.php uses x-layouts.auth with German text, app.css has all @theme tokens and @font-face declarations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Restore app.css with Tailwind v4 @import and @theme design tokens (Breeze had overwritten with v3 directives)
- Add @tailwindcss/vite tailwindcss() plugin back to vite.config.js
- Upgrade tailwindcss in package.json from ^3.1.0 to ^4.0.0; remove @tailwindcss/forms and autoprefixer (v3-only)
- Delete postcss.config.js (v3 PostCSS plugin, not needed for v4 Vite plugin)
- Comment out Volt::route('register', ...) in routes/auth.php — platform is invite-only
- Update RegistrationTest to assert /register returns 404 instead of 200
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaced vite.config.js with laravel+tailwind v4+VitePWA config, added auth.js and
touch.js entry points. Rewrote app.css with Tailwind v4 @theme custom tokens and local
@font-face declarations for Plus Jakarta Sans (variable) and JetBrains Mono. Downloaded
both woff2 font files locally to public/fonts/ — no CDN links. Vite build: exit 0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>