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>