# Learnings — keyvisual-background-nametag

> Append with `## [TIMESTAMP] Task: {task-id}\n{content}`. NEVER overwrite.

## [2026-05-30] Task: T1 — Parser background-layer
- Parser repo is at `/Users/thorsten/AI/propresenter` (NOT `propresenter-work/php` as plan stated).
- composer.json uses VCS remote `https://git.stadtmission-butzbach.de/public/propresenter-php.git` (NOT a path repo).
- For development: copy changed parser files into `vendor/propresenter/parser/src/` in the app. All future parser changes must also be synced: `cp /Users/thorsten/AI/propresenter/src/CHANGED.php /Users/thorsten/AI/pp-planer/vendor/propresenter/parser/src/CHANGED.php`
- `slideData['background']` contract: `['path' => string, 'format' => 'JPG'|'PNG', 'width' => int, 'height' => int, 'bundleRelative' => bool(opt)]`
- `slideData['imageOnly'] = true` → skips text element entirely.
- Background action is appended BEFORE foreground media in actions array.
- New Slide accessors: `hasBackgroundMedia()`, `getBackgroundMediaUrl()`, `getBackgroundMediaFormat()`
- Parser commit: `582ef85 feat(parser): background-layer + image-only slide support` in `/Users/thorsten/AI/propresenter` repo.
- All 372 parser tests green + 503 app tests green after vendor sync.
- Evidence: `.sisyphus/evidence/task-1-background-layer.txt`, `task-1-image-only.txt`

## Initial Setup
- Parser pkg at /Users/thorsten/AI/propresenter-work/php (PHPUnit 11, #[Test], ref/ fixtures)
- App at /Users/thorsten/AI/pp-planer (Pest v4 + PHPUnit, in-memory sqlite, ddev commands)
- FileConversionService uses Intervention v3 GD, contain(1920,1080,'000000','center')
- COVER new path: cover(1920,1080) center crop. Thumbnails 320x180 reused.
- Parser GAPS: LAYER_TYPE_BACKGROUND unused; no image-only slide; no text styling needed.
- Settings: Setting::get/set key-value; SettingsController AGENDA_KEYS constant; HandleInertiaRequests shares globals.
- ServiceAgendaItem: is_before_event bool, responsible json, sort_order, service_song_id.
- PlaylistExportService: iterates by sort_order; song items->ProExportService; slide items->addSlidesFromCollection.

## [2026-05-30] Task: T1
- Parser repo was at `/Users/thorsten/AI/propresenter` in this workspace (planned `/Users/thorsten/AI/propresenter-work/php` path was absent).
- Implemented `slideData['background'] = ['path' => string, 'format' => string, 'width' => int, 'height' => int]` to emit `ActionType::ACTION_TYPE_MEDIA` on `LayerType::LAYER_TYPE_BACKGROUND`.
- `slideData['imageOnly'] === true` skips text element generation even if `text` is present, producing background-only/image-only slides with 0 text elements.
- Slides may combine background media plus existing foreground `media`; cue action order is slide action, background media, foreground media so background precedes foreground media without breaking slide-element reads.
- `Slide` read accessors now distinguish foreground media from background media via layer type: `hasBackgroundMedia()`, `getBackgroundMediaUrl()`, `getBackgroundMediaFormat()`.

## [2026-05-30] Task: T2
- `FileConversionService::convertImageCover()` now writes JPG slides with `cover(1920, 1080)` and thumbnail generation, returning `filename`, `thumbnail`, `warnings`, `fullCover => true`.
- Existing contain `convertImage()` keeps `contain(1920, 1080, '000000', 'center')` behavior and adds additive metadata `fullCover => false` for downstream export decisions.
- COVER warnings intentionally only include the German upscale/quality warning for sources smaller than 1920×1080, avoiding contain-specific black-bar wording.
- Evidence: `.sisyphus/evidence/task-2-cover-fill.txt`, `.sisyphus/evidence/task-2-contain-regression.txt`.

## [2026-05-30] Task: T3
- Migration filename: `2026_05_10_115900_add_image_fields_to_services_table.php`
- Columns added on `services`: `key_visual_filename`, `background_filename`, `moderator_name`, `preacher_name_override`
- `Service` accessors use `Attribute::get(fn () => $this->... ? '/storage/'.$this->... : null)` for `keyVisualUrl` and `backgroundUrl`
- Added all 4 new fields to `Service::$fillable` so `fill()`/`save()` persists them
- Full suite green after fresh migrate: `ddev exec php artisan test` → 510 passed

## [2026-05-30] Task: T4
- Setting keys added to `SettingsController::AGENDA_KEYS` constant (single source for both index() props and update() Rule::in validation): `current_key_visual`, `current_background`, `namenseinblender_macro_name`, `namenseinblender_macro_uuid`, `namenseinblender_macro_collection_name`, `namenseinblender_macro_collection_uuid`.
- PATCH /settings (route `settings.update`) validates `key` via `Rule::in(self::AGENDA_KEYS)`, `value` nullable string max:500, persists via `Setting::set()`.
- `HandleInertiaRequests::share()` now globally exposes: `namenseinblenderMacro` => {name, uuid, collection_name (default '--MAIN--'), collection_uuid}, plus `currentKeyVisual`, `currentBackground`.
- NOTE: namenseinblenderMacro uses snake_case keys (name/uuid/collection_name/collection_uuid) — differs from existing `macroSettings` which uses camelCase (collectionName/collectionUuid). T10/T14 consumers must use snake_case for namenseinblender.
- Test file `tests/Feature/NamenseinblenderSettingTest.php` (Pest, 4 tests). Inertia prop assertions use `->where('namenseinblenderMacro.name', ...)` dot-path on shared props from any authed page (settings.index).
- 514 app tests green (510 baseline + 4 new). Pint clean.
