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

9.8 KiB
Raw Blame History

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