pp-planer/.sisyphus/notepads/cts-presenter-app/learnings.md
Thorsten Bus 9a753caa8f chore: add oracle audit evidence and notepad files to main repo
Files created during F1 Plan Compliance Audit:
- .sisyphus/evidence/task-1-docker-status.txt
- .sisyphus/evidence/task-1-vite-build.txt
- .sisyphus/notepads/cts-presenter-app/learnings.md (10KB)
- .sisyphus/notepads/cts-presenter-app/decisions.md
- .sisyphus/notepads/cts-presenter-app/issues.md
- .sisyphus/notepads/cts-presenter-app/problems.md

These were created by the oracle agent but not committed to main repo.
Adding them now for completeness.
2026-03-01 20:53:48 +01:00

187 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Learnings & Conventions
Convention log for CTS Presenter App development.
## 2026-03-01 - Task 0 CTS API Spike
- `5pm-hdh/churchtools-api` v2.1.0 bietet sowohl `CTConfig::setApiKey()` als auch `CTConfig::authWithLoginToken()`; `setApiKey` ist als deprecated markiert, funktioniert aber weiterhin fuer Token-Auth.
- Events-Fetch fuer heute+zukunft laeuft ueber `EventRequest::where('from', 'YYYY-MM-DD')->get()` und sendet Query-Parameter `from` plus `page`.
- Song-Response enthaelt im verwendeten Shape `ccli`, `arrangements` und optional `lyrics` als nested Objekt.
- Ohne gesetzte Runtime-Variablen `CTS_API_URL`/`CTS_API_TOKEN` ist nur ein Mock-basierter Spike moeglich; Live-Auth muss spaeter mit echten Env-Werten nachgezogen werden.
## 2026-03-01 - Task 1 Laravel Scaffolding + Breeze Vue + Docker
- Laravel 11 + Breeze (Vue stack) + Pest scaffolding completed successfully
- Inertia.js integration required explicit app.js setup with createInertiaApp() and resolvePageComponent()
- Vite v7 requires @vitejs/plugin-vue to be explicitly configured in vite.config.js
- Tailwind CSS v4 requires @tailwindcss/vite v4 (not v3) - version mismatch caused build failures
- npm install requires --legacy-peer-deps flag due to Vite v7 + @vitejs/plugin-vue v5 compatibility
- Docker build: PHP 8.4-fpm-alpine chosen over 8.3 due to composer.lock requiring PHP 8.4+ dependencies
- Docker build: Removed Imagick PECL installation (requires autoconf) - can be added later if needed
- Locale set to 'de' in config/app.php via env() with fallback
- Vue packages (@vueuse/core, vue-draggable-plus, @jaxtheprime/vue3-dropzone) added to package.json
- docker-compose.yml v2 syntax used (no version field needed, but warning appears)
- Vite server configured for Docker with host: '0.0.0.0' and hmr settings for hot-reload
- QA Scenario 1: docker compose up -d successfully starts app + node containers
- QA Scenario 2: npm run build succeeds with 784 modules transformed, ~255KB gzipped app bundle
- All Pest tests pass (home route returns 200 with Inertia response)
- Task 0 spike files (CtsApiSpikeTest.php, CtsApiSpikeSync.php) preserved in new structure
## [2026-03-01 19:25] Task 1: Laravel Scaffolding + Breeze Vue + Docker
### Package Version Compatibility
- Laravel 12 requires specific package versions:
- `vite`: `^7.0.0` (matches laravel-vite-plugin@^2.0.0 peer dependency)
- `@vitejs/plugin-vue`: `^6.0.0` (compatible with Vite 7)
- Avoid version mismatches - check peer dependencies before install
### Docker Setup
- Multi-stage Dockerfile with PHP 8.3-fpm-alpine base
- LibreOffice + ImageMagick installed successfully in Alpine
- App container starts successfully on port 8000
- Node container requires `npm install` to be run inside container for first-time setup
- docker-compose.yml `version` attribute is obsolete (warning can be ignored)
### Laravel Autoload
- Spike files relocated from `src/Cts/` to `app/Cts/` to match Laravel PSR-4 autoload
- Laravel expects application code in `app/` directory
- Custom `src/` directory requires composer.json autoload configuration
### Vite Configuration
- Configured for Docker with `host: '0.0.0.0'` and `port: 5173`
- HMR configured for localhost access from host machine
- Build succeeds with 784 modules transformed
### Tests
- 5 tests passing (14 assertions)
- CTS API spike tests preserved from Task 0
- Home route test added for Inertia verification
- Breeze default tests included
### Locale
- App locale set to `'de'` in `config/app.php`
- All UI text must be in German with "Du" form (per project spec)
## [2026-03-01 19:45] Wave 1 Complete (T2-T7)
### Database Schema (T2)
- 10 migrations created in correct dependency order
- All Eloquent models have proper relationships (hasMany, belongsTo, belongsToMany)
- Factory classes generate valid test data
- Soft deletes on songs and slides tables working correctly
- JSON casts for cts_data, churchtools_groups, churchtools_roles
### ChurchTools OAuth (T3)
- Custom Socialite provider successfully replaces Breeze auth
- All Breeze register/password-reset pages removed
- OAuth-only login with German UI ("Mit ChurchTools anmelden")
- User creation from OAuth callback with churchtools_id, avatar, groups, roles
- 9 tests passing (54 assertions)
### CTS API Sync (T4)
- ChurchToolsService wraps 5pm-HDH/churchtools-api correctly
- CCLI-based song matching works (matched_at timestamp set)
- Unmatched songs preserved with song_id=null for manual matching
- Sync log entries created with counts and status
- German flash messages ("Daten wurden aktualisiert")
### File Conversion (T5)
- Intervention Image v3 letterbox/pillarbox working perfectly
- 400×300 PNG → 1920×1080 JPG with black bars (no upscaling)
- Portrait images get pillarbox (black bars left/right)
- PPT conversion queued via ConvertPowerPointJob (NOT synchronous)
- Thumbnail generation at 320×180
- ZIP extraction and recursive processing implemented
### Shared Vue Components (T6)
- AuthenticatedLayout with sticky nav, sync button, user avatar
- useAutoSave composable with 500ms debounce for text inputs
- FlashMessage, ConfirmDialog, LoadingSpinner components
- HandleInertiaRequests middleware shares auth.user, flash, last_synced_at, app_name
- All German UI text with "Du" form
- 7 tests passing
### Email Configuration (T7)
- MissingSongRequest mailable with German template
- Email includes song name, CCLI ID, service info, link to service
- SONG_REQUEST_EMAIL configurable via .env
- Subject: "Song-Anfrage: {songName} (CCLI: {ccliId})"
- 2 tests passing (10 assertions)
### Test Suite Health
- All 30 tests passing (233 assertions)
- No LSP errors (PHP LSP not configured, but tests validate correctness)
- Clean git history with atomic commits
### Blockers Resolved
- Fixed ChurchToolsSyncTest: changed `post()` to `$this->post()` (trivial fix)
- No other blockers encountered
### Next Steps
- Wave 2 ready to start (T8-T13): Service List, Song CRUD, Slide Upload, Arrangement Configurator, Song Matching, Translation Service
- All Wave 2 tasks can run in parallel (no dependencies between them)
## [2026-03-01] Task T12: Song Matching Service (CCLI ID)
### SongMatchingService Pattern
- Dedicated service class for all song matching operations: autoMatch, manualAssign, requestCreation, unassign
- autoMatch checks: (1) not already assigned, (2) has cts_ccli_id, (3) Song with matching ccli_id exists in DB
- manualAssign overwrites any existing assignment (no guard needed)
- requestCreation sends MissingSongRequest mailable to configurable SONG_REQUEST_EMAIL address
- unassign clears both song_id and matched_at
### ChurchToolsService Integration
- Refactored syncServiceAgendaSongs to use SongMatchingService::autoMatch instead of inline DB matching
- updateOrInsert creates the record first (without song_id), then Eloquent lookup + autoMatch
- Preserves existing manual assignments during re-sync (only autoMatches if song_id is null)
- app(SongMatchingService::class) used inside the method to avoid constructor injection on the sync service
### API Routes
- ServiceSongController with 3 POST endpoints under auth:sanctum middleware
- Routes: /api/service-songs/{id}/assign, /request, /unassign
- German response messages: 'Song erfolgreich zugeordnet', 'Anfrage wurde gesendet', 'Zuordnung entfernt'
- Validation on assign: song_id required + exists:songs,id
- findOrFail gives automatic 404 for missing ServiceSong records
### Testing
- 14 tests (33 assertions) covering service methods + API endpoints + auth + validation
- Factory-based tests with ServiceSong::factory(), Song::factory(), User::factory()
- Mail::fake() for email assertion without actually sending
- Unique constraint gotcha: Song factory generates ccli_id with 80% probability, need explicit overrides in tests
### Pre-existing Test Failures (NOT caused by T12)
- TranslationServiceTest: TranslationService class not yet created (future task)
- ArrangementControllerTest: Routes not yet defined (future task)
- ServiceControllerTest: Vite manifest missing Vue component (frontend task)
## [2026-03-01] Task T10: Slide Upload & Grid Components
### SlideController
- SlideController handles 3 routes: POST /slides, DELETE /slides/{slide}, PATCH /slides/{slide}/expire-date
- Image uploads processed synchronously via FileConversionService (1920×1080 JPG + 320×180 thumbnail)
- PPT uploads stored to temp location then dispatched via ConvertPowerPointJob (async)
- ZIP uploads processed recursively via processZip() — images sync, PPTs async
- Moderation & sermon slides require service_id; information slides can be global (service_id nullable)
- Extension validation done manually after Laravel validation (controller checks getClientOriginalExtension)
### Vue Components
- @jaxtheprime/vue3-dropzone v1.1.0: v-model for files, @change event when files selected, slots for placeholder-img/title/description
- CSS variables for theming: --v3-dropzone--primary, --v3-dropzone--border, etc.
- Must hide default preview with `.v3-dropzone__preview { display: none }` since we use SlideGrid
- SlideUploader emits 'uploaded' after all files processed sequentially
- SlideGrid emits 'deleted' and 'updated' for parent refresh
- Inline expire date editing: click-to-edit pattern with save/cancel buttons
### Testing
- PPTX controller test requires mocking FileConversionService since fake files aren't real PowerPoint
- Use `$this->postJson()` for validation tests expecting 422 (otherwise Laravel redirects with 302)
- Mockery mock with `app()->instance()` works for service container injection
- All 15 SlideControllerTest tests pass (37 assertions)
- Full suite: 103 tests, 488 assertions
### Design Patterns
- Amber/orange color palette matches AuthenticatedLayout nav gradient (from-amber-500 to-orange-600)
- Dropzone uses dashed border + amber gradient background for hover states
- Slide cards: rounded-xl, subtle shadow, hover lift (-translate-y-0.5), overlay controls
- Expire date color coding: red=expired, amber=expires within 7 days, emerald=active, gray=no date