diff --git a/.sisyphus/evidence/task-2-no-attributes.txt b/.sisyphus/evidence/task-2-no-attributes.txt
new file mode 100644
index 0000000..fdb8f90
--- /dev/null
+++ b/.sisyphus/evidence/task-2-no-attributes.txt
@@ -0,0 +1,8 @@
+hasFill:YES
+fillEnabled:NO
+hasStroke:YES
+strokeEnabled:NO
+hasShadow:YES
+shadowEnabled:NO
+hasFeather:YES
+featherEnabled:NO
diff --git a/.sisyphus/evidence/task-3-auto-arrangement.txt b/.sisyphus/evidence/task-3-auto-arrangement.txt
new file mode 100644
index 0000000..2bc4d3c
--- /dev/null
+++ b/.sisyphus/evidence/task-3-auto-arrangement.txt
@@ -0,0 +1,101 @@
+TASK: Auto-select default arrangement on song match
+DATE: 2026-03-02
+STATUS: COMPLETE ✓
+
+IMPLEMENTATION SUMMARY
+======================
+
+1. Modified SongMatchingService.php (app/Services/SongMatchingService.php)
+ - autoMatch() method (lines 34-40): Added arrangement lookup logic
+ - manualAssign() method (lines 65-76): Added conditional arrangement setting
+
+2. Added 4 new tests to SongMatchingTest.php
+ - autoMatch setzt song_arrangement_id auf Standard-Arrangement
+ - autoMatch bevorzugt is_default=true Arrangement
+ - autoMatch nutzt erstes Arrangement wenn kein Standard vorhanden
+ - manualAssign setzt song_arrangement_id wenn null
+ - manualAssign behält bestehende song_arrangement_id bei
+
+ARRANGEMENT SELECTION PRIORITY
+==============================
+1. is_default = true
+2. name = 'normal'
+3. first arrangement (any)
+4. null (if no arrangements exist)
+
+BEHAVIOR
+========
+
+autoMatch():
+- ALWAYS sets song_arrangement_id after matching song
+- Uses priority order: is_default → name='normal' → first
+- Handles case where song has no arrangements (sets to null)
+
+manualAssign():
+- ONLY sets song_arrangement_id if currently null
+- Preserves existing arrangement selection when reassigning song
+- Uses same priority order as autoMatch()
+
+TEST RESULTS
+============
+
+All 20 SongMatchingTest tests PASS:
+✓ autoMatch ordnet Song per CCLI-ID zu
+✓ autoMatch nutzt CTS-Song-ID als Fallback wenn keine CCLI passt
+✓ autoMatch gibt false zurück wenn kein CCLI-ID vorhanden
+✓ autoMatch gibt false zurück wenn kein passender Song in DB
+✓ autoMatch überspringt bereits zugeordnete Songs
+✓ autoMatch setzt song_arrangement_id auf Standard-Arrangement [NEW]
+✓ autoMatch bevorzugt is_default=true Arrangement [NEW]
+✓ autoMatch nutzt erstes Arrangement wenn kein Standard vorhanden [NEW]
+✓ manualAssign ordnet Song manuell zu
+✓ manualAssign überschreibt bestehende Zuordnung
+✓ manualAssign setzt song_arrangement_id wenn null [NEW]
+✓ manualAssign behält bestehende song_arrangement_id bei [NEW]
+✓ requestCreation sendet E-Mail und setzt request_sent_at
+✓ unassign entfernt Zuordnung
+✓ POST /api/service-songs/{id}/assign ordnet Song zu
+✓ POST /api/service-songs/{id}/assign validiert song_id
+✓ POST /api/service-songs/{id}/request sendet Anfrage-E-Mail
+✓ POST /api/service-songs/{id}/unassign entfernt Zuordnung
+✓ API Endpunkte erfordern Authentifizierung
+✓ API gibt 404 für nicht existierende ServiceSong
+
+Duration: 0.47s
+Tests: 20 passed (45 assertions)
+
+CODE QUALITY
+============
+✓ No LSP errors in SongMatchingService.php
+✓ Follows Laravel code style conventions
+✓ Uses nullsafe operator (?->)
+✓ Uses null coalescing (??)
+✓ Proper type hints and return types
+✓ Clear comments explaining logic
+
+VERIFICATION CHECKLIST
+======================
+✓ autoMatch() sets song_arrangement_id to default/normal/first arrangement
+✓ manualAssign() sets arrangement ONLY if currently null
+✓ New tests verify auto-arrangement selection
+✓ New tests verify arrangement preservation
+✓ All 20 SongMatching tests pass
+✓ No regressions in existing tests
+✓ Code follows project conventions
+✓ LSP diagnostics clean
+
+FILES MODIFIED
+==============
+1. app/Services/SongMatchingService.php
+ - autoMatch() method: Added arrangement lookup (lines 34-40)
+ - manualAssign() method: Added conditional arrangement setting (lines 65-76)
+
+2. tests/Feature/SongMatchingTest.php
+ - Added SongArrangement import
+ - Added 4 new test cases for arrangement selection
+
+NEXT STEPS
+==========
+Ready for commit:
+ git add app/Services/SongMatchingService.php tests/Feature/SongMatchingTest.php
+ git commit -m "feat(songs): auto-select default arrangement on song match"
diff --git a/.sisyphus/evidence/task-6-translated-bounds.txt b/.sisyphus/evidence/task-6-translated-bounds.txt
new file mode 100644
index 0000000..83c49c0
--- /dev/null
+++ b/.sisyphus/evidence/task-6-translated-bounds.txt
@@ -0,0 +1,154 @@
+# Task 6: Translated Textbox Positioning - QA Evidence
+
+## Test 1: Translated Slide Has Correct Dual Bounds
+
+### Command
+```bash
+cd /Users/thorsten/AI/cts-work && php -r "
+require 'vendor/autoload.php';
+use ProPresenter\Parser\ProFileGenerator;
+use ProPresenter\Parser\ProFileWriter;
+use ProPresenter\Parser\ProFileReader;
+\$song = ProFileGenerator::generate('TranslateTest',
+ [['name'=>'V1','color'=>[0,0,0,1],'slides'=>[['text'=>'Amazing Grace','translation'=>'Erstaunliche Gnade']]]],
+ [['name'=>'normal','groupNames'=>['V1']]]
+);
+ProFileWriter::write(\$song, '/tmp/translate-test.pro');
+\$readSong = ProFileReader::read('/tmp/translate-test.pro');
+\$slides = \$readSong->getSlides();
+\$elements = \$slides[0]->getAllElements();
+echo 'count: ' . count(\$elements) . PHP_EOL;
+echo 'name0: ' . \$elements[0]->getName() . PHP_EOL;
+echo 'name1: ' . \$elements[1]->getName() . PHP_EOL;
+\$b0 = \$elements[0]->getGraphicsElement()->getBounds();
+\$b1 = \$elements[1]->getGraphicsElement()->getBounds();
+echo 'height0: ' . round(\$b0->getSize()->getHeight(), 1) . PHP_EOL;
+echo 'height1: ' . round(\$b1->getSize()->getHeight(), 1) . PHP_EOL;
+echo 'y0: ' . round(\$b0->getOrigin()->getY(), 3) . PHP_EOL;
+echo 'y1: ' . round(\$b1->getOrigin()->getY(), 3) . PHP_EOL;
+"
+```
+
+### Output
+```
+count: 2
+name0: Orginal
+name1: Deutsch
+height0: 182.9
+height1: 113.9
+y0: 99.543
+y1: 303.166
+```
+
+### Verification
+✅ Element count: 2 (expected: 2)
+✅ Element 0 name: "Orginal" (expected: "Orginal")
+✅ Element 1 name: "Deutsch" (expected: "Deutsch")
+✅ Element 0 height: 182.9px (expected: ~182.946px)
+✅ Element 1 height: 113.9px (expected: ~113.889px)
+✅ Element 0 Y position: 99.543 (expected: 99.543)
+✅ Element 1 Y position: 303.166 (expected: 303.166)
+
+**Result**: PASS - Translated slides have correctly positioned dual textboxes matching TestTranslated.pro reference
+
+---
+
+## Test 2: Non-Translated Slide Has Single Full Bounds
+
+### Command
+```bash
+cd /Users/thorsten/AI/cts-work && php -r "
+require 'vendor/autoload.php';
+use ProPresenter\Parser\ProFileGenerator;
+use ProPresenter\Parser\ProFileWriter;
+use ProPresenter\Parser\ProFileReader;
+\$song = ProFileGenerator::generate('NoTranslateTest',
+ [['name'=>'V1','color'=>[0,0,0,1],'slides'=>[['text'=>'Amazing Grace']]]],
+ [['name'=>'normal','groupNames'=>['V1']]]
+);
+ProFileWriter::write(\$song, '/tmp/no-translate-test.pro');
+\$readSong = ProFileReader::read('/tmp/no-translate-test.pro');
+\$slides = \$readSong->getSlides();
+\$elements = \$slides[0]->getAllElements();
+echo 'count: ' . count(\$elements) . PHP_EOL;
+echo 'name0: ' . \$elements[0]->getName() . PHP_EOL;
+\$b = \$elements[0]->getGraphicsElement()->getBounds();
+echo 'height: ' . round(\$b->getSize()->getHeight(), 1) . PHP_EOL;
+echo 'width: ' . round(\$b->getSize()->getWidth(), 1) . PHP_EOL;
+echo 'y: ' . round(\$b->getOrigin()->getY(), 1) . PHP_EOL;
+echo 'x: ' . round(\$b->getOrigin()->getX(), 1) . PHP_EOL;
+"
+```
+
+### Output
+```
+count: 1
+name0: Orginal
+height: 880
+width: 1620
+y: 100
+x: 150
+```
+
+### Verification
+✅ Element count: 1 (expected: 1)
+✅ Element 0 name: "Orginal" (expected: "Orginal")
+✅ Height: 880px (expected: 880px)
+✅ Width: 1620px (expected: 1620px)
+✅ Y position: 100 (expected: 100)
+✅ X position: 150 (expected: 150)
+
+**Result**: PASS - Non-translated slides keep single full-size textbox
+
+---
+
+## PHPUnit Tests
+
+### Test: testTranslatedSlideHasCorrectDualBounds
+```bash
+./vendor/bin/phpunit vendor/propresenter/parser/tests/ProFileGeneratorTest.php --filter testTranslatedSlideHasCorrectDualBounds
+```
+
+**Result**: OK (1 test, 7 assertions)
+
+### Test: testNonTranslatedSlideHasSingleFullBounds
+```bash
+./vendor/bin/phpunit vendor/propresenter/parser/tests/ProFileGeneratorTest.php --filter testNonTranslatedSlideHasSingleFullBounds
+```
+
+**Result**: OK (1 test, 6 assertions)
+
+### All ProFileGeneratorTest Tests
+```bash
+./vendor/bin/phpunit vendor/propresenter/parser/tests/ProFileGeneratorTest.php
+```
+
+**Result**: OK (14 tests, 95 assertions)
+
+---
+
+## Laravel Tests
+
+### Command
+```bash
+php -d memory_limit=512M artisan test
+```
+
+**Result**: Tests: 203 passed (1115 assertions), Duration: 3.89s
+
+---
+
+## Summary
+
+✅ All acceptance criteria met:
+- [x] Two new methods created: buildOriginalBounds(), buildTranslationBounds()
+- [x] Translated slides have 2 elements with different bounds (heights: ~183px, ~114px)
+- [x] Non-translated slides keep single element with full bounds (1620×880)
+- [x] Textbox names unchanged: "Orginal" (typo intentional), "Deutsch"
+- [x] New test: translated slide has correct dual bounds
+- [x] New test: non-translated slide has single full-size bounds
+- [x] PHPUnit tests pass (14/14)
+- [x] Laravel tests pass (203/203)
+
+**Task Status**: COMPLETE
+**Date**: 2026-03-02
diff --git a/.sisyphus/evidence/task-8-bundle-contents.txt b/.sisyphus/evidence/task-8-bundle-contents.txt
new file mode 100644
index 0000000..7df55bf
--- /dev/null
+++ b/.sisyphus/evidence/task-8-bundle-contents.txt
@@ -0,0 +1,21 @@
+Task 8 Manual Bundle Verification
+Datum: 2026-03-02
+
+Erzeugter Bundle-Pfad:
+/var/folders/jf/qly82l3s6cg19v82rm_h0q9h0000gn/T/information-69a5fdfc20077.probundle
+
+Befehl:
+unzip -l /var/folders/jf/qly82l3s6cg19v82rm_h0q9h0000gn/T/information-69a5fdfc20077.probundle
+
+Ausgabe:
+Archive: /var/folders/jf/qly82l3s6cg19v82rm_h0q9h0000gn/T/information-69a5fdfc20077.probundle
+ Length Date Time Name
+--------- ---------- ----- ----
+ 1323 03-02-2026 22:15 information.pro
+ 12 03-02-2026 22:15 manual-bundle-1.jpg
+--------- -------
+ 1335 2 files
+
+Ergebnis:
+- Enthalten: 1 .pro Datei + 1 Bilddatei
+- Struktur ist flach (Root-Level), wie gefordert.
diff --git a/.sisyphus/notepads/pro-gen-and-ui-fixes/learnings.md b/.sisyphus/notepads/pro-gen-and-ui-fixes/learnings.md
new file mode 100644
index 0000000..8e14903
--- /dev/null
+++ b/.sisyphus/notepads/pro-gen-and-ui-fixes/learnings.md
@@ -0,0 +1,49 @@
+# Learnings: ProPresenter Generator and UI Fixes
+
+## Task 6: Translated Textbox Positioning (2026-03-02)
+
+### Implementation Pattern
+- **Dual bounds methods**: Created `buildOriginalBounds()` and `buildTranslationBounds()` following same pattern as existing `buildBounds()`
+- **Conditional logic in buildCue()**: Check for translation presence, use different bounds for each element
+- **Optional parameter pattern**: Modified `buildSlideElement()` to accept `?Rect $bounds = null` parameter, defaulting to `buildBounds()` for backward compatibility
+
+### Exact Values from TestTranslated.pro
+- Original textbox: origin(150, 99.543), size(1620×182.946) — top position, ~183px tall
+- Translation textbox: origin(150, 303.166), size(1620×113.889) — below, ~114px tall
+- Non-translated: origin(150, 100), size(1620×880) — full height
+
+### Testing Approach
+- **PHPUnit tests**: Added two new tests to verify bounds for translated and non-translated slides
+- **QA scenarios**: Used PHP CLI to generate, write, read back, and verify exact positioning values
+- **Method access**: Used `getGraphicsElement()->getBounds()` to access protobuf bounds from TextElement wrapper
+
+### Git Workflow
+- **Symlinked vendor**: `vendor/propresenter/parser` is symlink to `/Users/thorsten/AI/propresenter-work/php`
+- **Commit location**: Changes committed in propresenter-work repo, not cts-work
+- **Branch**: propresenter-parser
+
+### Test Results
+- PHPUnit: 14/14 tests pass (95 assertions)
+- Laravel: 203/203 tests pass (1115 assertions)
+- QA verification: All positioning values match TestTranslated.pro reference exactly
+
+## Task 8: .probundle Export fuer Service-Slide-Bloecke (2026-03-02)
+
+### Export-Pattern
+- Fuer ZIP-basierte Exporte ist `Storage::disk("public")->path(...)` teststabiler als harte `storage_path(...)`-Pfade, weil `Storage::fake("public")` dann direkt funktioniert.
+- `.probundle` wurde als flaches ZIP umgesetzt: genau eine `.pro` plus alle Bilddateien auf Root-Ebene.
+- Medienreferenzen in der `.pro` funktionieren robust mit Dateinamen (Root-Datei im Bundle) statt absoluten Pfaden.
+
+### Controller-Integration
+- Route-Parameter fuer Blocktyp (`information|moderation|sermon`) lassen sich sauber per Request-Validation nach `merge()` pruefen und liefern bei Fehlern korrekt 422 in JSON-Requests.
+- Download-Response fuer temporaere Bundle-Dateien mit `deleteFileAfterSend(true)` vermeidet Temp-Muell.
+
+### Test-Pattern
+- Fuer BinaryFileResponse in Feature-Tests: Datei aus Response in eigenen Temp-Pfad kopieren und dann mit `ZipArchive` validieren.
+- Vollsuite hatte einen sporadischen bestehenden Flake in `SongPdfTest`; Re-Run lief voll gruen (206/206).
+
+## Scope-Audit Learnings (F4, 2026-03-02)
+
+- Fuer strikte Scope-Fidelity prueft man nicht nur Dateiliste pro Commit, sondern auch unerwartete Hunks innerhalb der erwarteten Dateien (z.B. Header-Navigation oder Bulk-Delete-Features in einem Export-Task).
+- Wenn ein Task auf vendor-Dateien zielt, die im Repo nicht versioniert sind, ist der Task aus Sicht des Git-Diffs nicht nachweisbar umgesetzt ("missing", auch wenn QA-Evidence existiert).
+- Commit-Message-Match allein reicht nicht: Task 1/4/7/8 zeigen, dass ein passender Commit-Titel trotzdem deutliche Scope-Creep enthalten kann.
diff --git a/AGENTS.md b/AGENTS.md
index ace3ed3..7fa0ed8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -95,3 +95,115 @@ ## SongDB Import
- download: download generated .pro file from the songDB for this song
- translate: allow add a full text or an URL to the Full text, and then start an editor, that shows two columns for every slide of every group. Left the original text, right a texteditor, with the imported text - always the same line qty of text from the original is used from the given translated text. Save this as translation for this song, and mark it as `with translation`.
- UploadArea for drag'n'drop and click for upload, to upload a .pro file, a zip file with multiple .pro files, or a bunch of .pro files, which should be parsed (this module was integrated later, so show an Exception here till this was finalized) and added into the song DB.
+
+---
+
+## Build, Test, Lint Commands
+
+```bash
+# Setup (first time)
+composer setup
+
+# Dev server (Laravel + Vite + Queue + Logs via concurrently)
+composer dev
+
+# Build frontend
+npm run build
+
+# Run all PHP tests (clears config cache first)
+composer test
+# or directly:
+php artisan test
+
+# Run a single PHP test file
+php artisan test tests/Feature/ServiceControllerTest.php
+
+# Run a single test method
+php artisan test --filter=test_service_kann_abgeschlossen_werden
+
+# Run only Unit or Feature suite
+php artisan test --testsuite=Unit
+php artisan test --testsuite=Feature
+
+# PHP code formatting (Laravel Pint - default Laravel preset)
+./vendor/bin/pint
+# Check only (no changes):
+./vendor/bin/pint --test
+
+# Run e2e tests (requires running dev server at http://cts-work.test)
+npx playwright test
+# Single e2e file:
+npx playwright test tests/e2e/service-list.spec.ts
+
+# Migrations
+php artisan migrate
+```
+
+## Architecture Overview
+
+```
+app/
+ Http/Controllers/ # Inertia controllers, return Inertia::render() or JSON
+ Http/Requests/ # Form request validation
+ Http/Middleware/ # HandleInertiaRequests shares props
+ Models/ # Eloquent models with factories in database/factories/
+ Services/ # Business logic (ChurchToolsService, SongService, etc.)
+ Jobs/ # Queue jobs (PowerPoint conversion)
+ Mail/ # Mailable classes
+ Cts/ # CTS API spike/sync utilities
+resources/js/
+ Pages/ # Vue page components (mapped via Inertia::render)
+ Components/ # Reusable Vue components
+ Composables/ # Vue composables (useAutoSave)
+ Layouts/ # AuthenticatedLayout, GuestLayout, MainLayout
+tests/
+ Feature/ # HTTP/integration tests (class-based, PHPUnit style)
+ Unit/ # Unit tests
+ e2e/ # Playwright browser tests (TypeScript)
+```
+
+## Code Style — PHP
+
+- **Formatter**: Laravel Pint (default Laravel preset, no custom config)
+- **Indentation**: 4 spaces
+- **Imports**: Fully qualified, one per line, grouped (PHP classes, then Laravel, then app)
+- **Models**: Use `$fillable` array (not `$guarded`). Use `casts()` method (not `$casts` property). Relationships return typed `HasMany`/`BelongsTo`/etc.
+- **Controllers**: Return type hints (`Response`, `JsonResponse`, `RedirectResponse`). Use route-model binding. Use `Inertia::render()` for page responses.
+- **Migrations**: Anonymous class style: `return new class () extends Migration { ... }`
+- **Error messages**: German. Flash via `->with('success', '...')`. JSON errors use `message` key.
+- **Null safety**: Use nullsafe operator `?->` and null coalescing `??`
+- **DB operations**: Prefer Eloquent, fall back to `DB::table()` for bulk upserts in sync code
+- **SoftDeletes**: Used on `Song` model. Use `whereNull('deleted_at')` in manual queries.
+
+## Code Style — Vue / Frontend
+
+- **Vue 3 Composition API** only, always ` tags.
+ *
+ * ==== IMPORTANT ==== Enabling this for documents you do not trust (e.g. arbitrary remote html pages)
+ * is a security risk.
+ * Embedded scripts are run with the same level of system access available to dompdf.
+ * Set this option to false (recommended) if you wish to process untrusted documents.
+ * This setting may increase the risk of system exploit.
+ * Do not change this settings without understanding the consequences.
+ * Additional documentation is available on the dompdf wiki at:
+ * https://github.com/dompdf/dompdf/wiki
+ *
+ * @var bool
+ */
+ 'enable_php' => false,
+
+ /**
+ * Rnable inline JavaScript
+ *
+ * If this setting is set to true then DOMPDF will automatically insert JavaScript code contained
+ * within tags as written into the PDF.
+ * NOTE: This is PDF-based JavaScript to be executed by the PDF viewer,
+ * not browser-based JavaScript executed by Dompdf.
+ *
+ * @var bool
+ */
+ 'enable_javascript' => true,
+
+ /**
+ * Enable remote file access
+ *
+ * If this setting is set to true, DOMPDF will access remote sites for
+ * images and CSS files as required.
+ *
+ * ==== IMPORTANT ====
+ * This can be a security risk, in particular in combination with isPhpEnabled and
+ * allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...);
+ * This allows anonymous users to download legally doubtful internet content which on
+ * tracing back appears to being downloaded by your server, or allows malicious php code
+ * in remote html pages to be executed by your server with your account privileges.
+ *
+ * This setting may increase the risk of system exploit. Do not change
+ * this settings without understanding the consequences. Additional
+ * documentation is available on the dompdf wiki at:
+ * https://github.com/dompdf/dompdf/wiki
+ *
+ * @var bool
+ */
+ 'enable_remote' => false,
+
+ /**
+ * List of allowed remote hosts
+ *
+ * Each value of the array must be a valid hostname.
+ *
+ * This will be used to filter which resources can be loaded in combination with
+ * isRemoteEnabled. If enable_remote is FALSE, then this will have no effect.
+ *
+ * Leave to NULL to allow any remote host.
+ *
+ * @var array|null
+ */
+ 'allowed_remote_hosts' => null,
+
+ /**
+ * A ratio applied to the fonts height to be more like browsers' line height
+ */
+ 'font_height_ratio' => 1.1,
+
+ /**
+ * Use the HTML5 Lib parser
+ *
+ * @deprecated This feature is now always on in dompdf 2.x
+ *
+ * @var bool
+ */
+ 'enable_html5_parser' => true,
+ ],
+
+];
diff --git a/vite.config.js b/vite.config.js
index 779a896..472221d 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -31,3 +31,6 @@ export default defineConfig({
},
},
});
+
+// HMR: When using Herd, run `npm run build` to use production assets.
+// The ws://localhost:5173 error occurs when dev assets are loaded without `npm run dev`.