pp-planer/.sisyphus/plans/edit-page-restructure.md

83 KiB
Raw Blame History

Service Edit Page Restructure — Agenda-Based View

TL;DR

Quick Summary: Replace the 4-block accordion service edit page (Information, Moderation, Sermon, Songs) with a full CTS agenda view showing all agenda items in order, with inline slide uploads, song arrangement management, and agenda-ordered presentation export.

Deliverables:

  • New service_agenda_items DB table storing ALL CTS agenda items (not just songs)
  • Restructured edit page showing full agenda with slide upload per item
  • Redesigned arrangement dialog (two-column: pill list + lyric preview)
  • Three new global settings (announcement position, sermon recognition, agenda boundaries)
  • Agenda-ordered .proplaylist export
  • Updated service list status columns
  • PHP Feature tests + Playwright E2E tests for all changes

Estimated Effort: Large Parallel Execution: YES — 5 waves Critical Path: Task 1 → Task 4 → Task 7 → Task 12 → Task 16 → Task 18 → F1-F4


Context

Original Request

Restructure the service edit page to show the complete CTS agenda instead of 4 separate blocks. Keep announcements at top; replace moderation/sermon/songs blocks with a unified agenda view. Every agenda element gets slide upload capability. Songs show arrangements inline. New arrangement dialog with two-column layout. Export follows agenda order.

Interview Summary

Key Discussions:

  • Titel-Konfiguration: Global in App-Settings (two separate fields for start/end with * wildcard)
  • Slide-Zuordnung: Service-specific (bound to service + agenda item)
  • Announcement-Position: Embedded in agenda at matched position during export
  • Nicht-Song Items: Skipped in export if no slides or songs
  • Moderation: Now a normal agenda item with "+" slide upload
  • Predigt: Recognized via namesmatching setting (like announcements), with special upload area
  • Export-Format: .proplaylist stays, but order follows agenda
  • Service-Liste: Adapt status — songs + predigt stay, add count of additional items with slides
  • Arrangement '+' Button: Quick-select showing ALL song groups (including already used ones)
  • 2x Button: Duplicates element directly below
  • Arrangement Preview: Shows actual lyric text, translation in gray/italic/small below
  • Tests: PHP Feature + Playwright E2E tests for all changes (TDD)

Metis Review

Identified Gaps (addressed):

  • CTS API type values: The actual EventAgendaItem.type values need real-world verification. Plan includes a diagnostic task to dump actual type values from the user's CTS instance before implementing filtering logic.
  • Data migration: Existing finalized services must remain downloadable. Plan keeps backward compatibility — old export path preserved for services finalized before migration.
  • Slide type enum: Instead of expanding the slides.type enum, plan uses a new service_agenda_item_id FK on slides table.
  • isBeforeEvent items: Items with isBeforeEvent=true will be excluded from the visible agenda display.
  • Re-sync behavior: If agenda items change in CTS, existing uploaded slides are preserved (orphaned but accessible).
  • Agenda item with song AND slides: Song .pro file takes precedence; uploaded slides are appended as additional slides within the same presentation item.

Work Objectives

Core Objective

Replace the rigid 4-block service edit page with a dynamic agenda-based view that mirrors the actual CTS service plan, enabling slide uploads on any agenda item and exporting presentations in the correct agenda order.

Concrete Deliverables

  • Migration: service_agenda_items table
  • Migration: service_agenda_item_id FK on slides table
  • Model: ServiceAgendaItem with relationships
  • Service: AgendaMatcherService (wildcard namesmatching)
  • Updated: ChurchToolsService::syncServiceAgendaItems() storing ALL items
  • Updated: SettingsController + Settings.vue with 4 new setting fields
  • Updated: PlaylistExportService for agenda-ordered export
  • Updated: ServiceController::edit() passing agenda items
  • Updated: ServiceController::index() with new status counts
  • New Vue: AgendaItemRow.vue, SongAgendaItem.vue, AnnouncementPositionIndicator.vue
  • Redesigned Vue: ArrangementDialog.vue (two-column with scroll-sync)
  • Restructured Vue: Edit.vue (agenda list replacing accordion)
  • Updated: Service::finalizationStatus() for new model
  • Tests: PHP Feature tests for all backend changes
  • Tests: Playwright E2E tests for all frontend changes

Definition of Done

  • php artisan test — all tests pass (existing + new)
  • npx playwright test — all E2E tests pass
  • ./vendor/bin/pint --test — no formatting issues
  • Service edit page shows full CTS agenda
  • Songs show arrangement pills inline
  • Arrangement dialog has two-column layout with lyric preview
  • Export .proplaylist follows agenda order with announcements at matched position
  • Settings page has 4 new fields for agenda configuration
  • Service list shows updated status columns

Must Have

  • Full CTS agenda display (all item types, not just songs)
  • Slide upload on any agenda item via "+" button
  • Song arrangement pills (Vers1, Chorus, etc.) shown inline
  • Redesigned arrangement dialog with lyric preview
  • Announcement slides embedded at matched agenda position in export
  • Sermon recognition via namesmatching
  • Configurable agenda start/end boundaries
  • Backward compatibility for existing finalized services

Must NOT Have (Guardrails)

  • MUST NOT change the CTS API integration fetcher pattern (injectable closures for testing)
  • MUST NOT modify the propresenter-parser package
  • MUST NOT touch SongDB page, auth/login, or file conversion logic
  • MUST NOT allow drag-and-drop reordering of agenda items (order comes from CTS API, immutable)
  • MUST NOT write to the CTS API (READ-ONLY constraint)
  • MUST NOT remove existing service_songs data — migrate gracefully
  • MUST NOT change Slide file storage pattern (public disk, filenames)
  • MUST NOT add English strings to UI — all German Du-form
  • MUST NOT use as any, @ts-ignore, or @ts-expect-error
  • MUST NOT add "save" buttons — all actions persist immediately
  • MUST NOT use Sie-form — always Du-form

Verification Strategy (MANDATORY)

ZERO HUMAN INTERVENTION — ALL verification is agent-executed. No exceptions.

Test Decision

  • Infrastructure exists: YES
  • Automated tests: TDD (tests first)
  • Framework: Pest v4 / PHPUnit for PHP, Playwright for E2E
  • TDD: Each task follows RED (failing test) → GREEN (minimal impl) → REFACTOR

QA Policy

Every task MUST include agent-executed QA scenarios. Evidence saved to .sisyphus/evidence/task-{N}-{scenario-slug}.{ext}.

  • Frontend/UI: Use Playwright — Navigate, interact, assert DOM, screenshot
  • Backend/API: Use Bash (curl/artisan) — Run commands, assert output
  • DB: Use php artisan test — Assert migrations, model relationships
  • Export: Use PHP tests — Assert file content and order

Execution Strategy

Parallel Execution Waves

Wave 1 (Foundation — no UI changes, all independent):
├── Task 1: service_agenda_items migration + model + factory [quick]
├── Task 2: Slides table FK migration (service_agenda_item_id) [quick]
├── Task 3: AgendaMatcherService (wildcard namesmatching logic) [quick]
├── Task 4: Settings keys + SettingsController extension [quick]
├── Task 5: CTS API type discovery diagnostic [quick]

Wave 2 (Backend services — depends on Wave 1):
├── Task 6: ChurchToolsService agenda sync (all items, not just songs) [deep] (depends: 1)
├── Task 7: ServiceController.edit() — pass agenda items to frontend [unspecified-high] (depends: 1, 2, 4)
├── Task 8: Service.finalizationStatus() update [quick] (depends: 1)
├── Task 9: PlaylistExportService — agenda-ordered export [deep] (depends: 1, 2, 3)

Wave 3 (Frontend components — depends on Wave 2):
├── Task 10: ArrangementDialog.vue redesign (two-column + lyric preview) [visual-engineering] (depends: 7)
├── Task 11: AgendaItemRow.vue + SongAgendaItem.vue components [visual-engineering] (depends: 7)
├── Task 12: Edit.vue restructure (agenda list + announcements section) [visual-engineering] (depends: 10, 11)
├── Task 13: Slide upload on agenda items (+ button + drag-and-drop) [visual-engineering] (depends: 2, 11)

Wave 4 (Settings UI + Service List + Export wiring):
├── Task 14: Settings.vue — add agenda configuration fields [quick] (depends: 4)
├── Task 15: ServiceController.index() + Index.vue — updated status columns [unspecified-high] (depends: 1, 8)
├── Task 16: Wire agenda-ordered export into download flow [unspecified-high] (depends: 9, 12)

Wave 5 (Tests + Cleanup):
├── Task 17: Update existing PHP tests for new model [unspecified-high] (depends: 6, 7, 8, 9)
├── Task 18: Playwright E2E tests for restructured edit page [unspecified-high] (depends: 12, 13, 14, 16)
├── Task 19: Remove deprecated components (ModerationBlock, SermonBlock) [quick] (depends: 17, 18)

Wave FINAL (After ALL tasks — 4 parallel reviews, then user okay):
├── Task F1: Plan compliance audit (oracle)
├── Task F2: Code quality review (unspecified-high)
├── Task F3: Real manual QA (unspecified-high)
└── Task F4: Scope fidelity check (deep)
-> Present results -> Get explicit user okay

Critical Path: Task 1 → Task 6 → Task 7 → Task 12 → Task 16 → Task 18 → F1-F4 → user okay
Parallel Speedup: ~65% faster than sequential
Max Concurrent: 5 (Wave 1)

Dependency Matrix

Task Depends On Blocks
1 6, 7, 8, 9, 15
2 7, 9, 13
3 9
4 7, 14
5
6 1 17
7 1, 2, 4 10, 11, 12
8 1 15, 17
9 1, 2, 3 16, 17
10 7 12
11 7 12, 13
12 10, 11 16, 18
13 2, 11 18
14 4 18
15 1, 8
16 9, 12 18
17 6, 7, 8, 9 19
18 12, 13, 14, 16 19
19 17, 18

Agent Dispatch Summary

  • Wave 1: 5 — T1→quick, T2→quick, T3→quick, T4→quick, T5→quick
  • Wave 2: 4 — T6→deep, T7→unspecified-high, T8→quick, T9→deep
  • Wave 3: 4 — T10→visual-engineering, T11→visual-engineering, T12→visual-engineering, T13→visual-engineering
  • Wave 4: 3 — T14→quick, T15→unspecified-high, T16→unspecified-high
  • Wave 5: 3 — T17→unspecified-high, T18→unspecified-high, T19→quick
  • FINAL: 4 — F1→oracle, F2→unspecified-high, F3→unspecified-high, F4→deep

TODOs

  • 1. Create service_agenda_items Migration, Model + Factory

    What to do:

    • Create migration create_service_agenda_items_table with columns:
      • id (bigIncrements)
      • service_id (FK to services, cascadeOnDelete)
      • cts_agenda_item_id (string, nullable) — CTS API item ID
      • position (string) — CTS position value (e.g. "1", "2", "1.1")
      • title (string) — item title from CTS
      • type (string) — CTS type value (e.g. "Song", "Default", "Header" — actual values TBD from Task 5)
      • note (text, nullable) — item note
      • duration (string, nullable) — planned duration
      • start (string, nullable) — planned start time
      • is_before_event (boolean, default false)
      • responsible (json, nullable)
      • service_song_id (FK to service_songs, nullable, nullOnDelete) — links to existing service_song if this is a song item
      • sort_order (unsignedInteger) — display order (derived from position)
      • created_at, updated_at
      • Unique index: [service_id, sort_order]
    • Create ServiceAgendaItem model with:
      • $fillable array with all columns
      • Relationships: belongsTo service, belongsTo serviceSong (nullable), hasMany slides
      • casts() method: responsible → array, is_before_event → boolean
      • Scope: scopeVisible($query) — filters is_before_event = false
      • Scope: scopeBetweenTitles($query, $startTitle, $endTitle) — filters items between matching header items using AgendaMatcherService
    • Create factory ServiceAgendaItemFactory with reasonable defaults
    • Write Pest tests:
      • Test migration creates table with all columns
      • Test model relationships (service, serviceSong, slides)
      • Test factory creates valid records
      • Test scopeVisible excludes is_before_event=true items
      • Test unique constraint on [service_id, sort_order]

    Must NOT do:

    • Do NOT modify service_songs table
    • Do NOT add any UI code

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 2, 3, 4, 5)
    • Blocks: Tasks 6, 7, 8, 9, 15
    • Blocked By: None

    References: Pattern References:

    • database/migrations/2026_03_01_100700_create_service_songs_table.php — Migration pattern with FK constraints and unique indices
    • app/Models/ServiceSong.php — Model pattern with fillable, casts, relationships
    • app/Models/Slide.php — SoftDeletes and nullable FK pattern
    • database/factories/ — Factory pattern for existing models

    API/Type References:

    • vendor/5pm-hdh/churchtools-api/src/Models/Events/Event/EventAgendaItem.php — CTS API item fields: position, title, type, note, duration, start, isBeforeEvent, responsible, song

    WHY Each Reference Matters:

    • service_songs migration — Follow exact FK constraint style and anonymous class migration pattern
    • ServiceSong model — Copy the promoted constructor, fillable, casts, and relationship patterns
    • EventAgendaItem.php — Source of truth for all fields the model must store

    Acceptance Criteria:

    • Migration runs: php artisan migrate → no errors
    • php artisan test --filter=ServiceAgendaItemTest → all tests pass

    QA Scenarios:

    Scenario: Migration creates table correctly
      Tool: Bash (php artisan)
      Preconditions: Fresh database
      Steps:
        1. Run `php artisan migrate:fresh`
        2. Run `php artisan test --filter=test_service_agenda_items_table_has_all_columns`
      Expected Result: Test passes, asserting all columns exist
      Evidence: .sisyphus/evidence/task-1-migration.txt
    
    Scenario: Model relationships work
      Tool: Bash (php artisan test)
      Preconditions: Migrated database with factories
      Steps:
        1. Run `php artisan test --filter=test_service_agenda_item_belongs_to_service`
        2. Run `php artisan test --filter=test_service_agenda_item_belongs_to_service_song`
      Expected Result: Both tests pass
      Evidence: .sisyphus/evidence/task-1-relationships.txt
    
    Scenario: Unique constraint prevents duplicate sort_order per service
      Tool: Bash (php artisan test)
      Preconditions: Migrated database
      Steps:
        1. Run `php artisan test --filter=test_unique_sort_order_per_service`
      Expected Result: Test verifies that inserting duplicate [service_id, sort_order] throws exception
      Evidence: .sisyphus/evidence/task-1-unique-constraint.txt
    

    Commit: YES

    • Message: feat(db): add service_agenda_items table and model
    • Files: database/migrations/*_create_service_agenda_items_table.php, app/Models/ServiceAgendaItem.php, database/factories/ServiceAgendaItemFactory.php, tests/Feature/ServiceAgendaItemTest.php
    • Pre-commit: php artisan test

  • 2. Add service_agenda_item_id FK to Slides Table

    What to do:

    • Create migration add_service_agenda_item_id_to_slides_table:
      • Add nullable service_agenda_item_id FK column (nullOnDelete)
      • Add index on service_agenda_item_id
    • Update Slide model:
      • Add service_agenda_item_id to $fillable
      • Add agendaItem() belongsTo relationship → ServiceAgendaItem
    • Update ServiceAgendaItem model (from Task 1):
      • Verify slides() hasMany relationship exists
    • Write Pest tests:
      • Test migration adds column
      • Test Slide.agendaItem() relationship returns correct ServiceAgendaItem
      • Test slides can be created with service_agenda_item_id
      • Test nullOnDelete: when agenda item is deleted, slide.service_agenda_item_id becomes null

    Must NOT do:

    • Do NOT change existing slide type enum
    • Do NOT modify existing slide upload logic
    • Do NOT remove existing service_id FK

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 1, 3, 4, 5)
    • Blocks: Tasks 7, 9, 13
    • Blocked By: None (Task 1 is parallel but this migration can run independently)

    References: Pattern References:

    • database/migrations/2026_03_01_100800_create_slides_table.php — Existing slides migration pattern
    • app/Models/Slide.php — Current Slide model with fillable, relationships, SoftDeletes
    • database/migrations/2026_03_02_130249_add_cts_song_id_to_songs_and_service_songs_tables.php — Pattern for adding columns to existing tables

    WHY Each Reference Matters:

    • slides migration — See current column structure to ensure new FK is compatible
    • Slide model — Must add to existing $fillable array and add new relationship method
    • add_cts_song_id migration — Follow same pattern for "add column to existing table" migration

    Acceptance Criteria:

    • Migration runs: php artisan migrate → no errors
    • php artisan test --filter=SlideAgendaItemRelationTest → passes

    QA Scenarios:

    Scenario: Slide links to agenda item correctly
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_slide_belongs_to_agenda_item`
      Expected Result: Test creates a Slide with service_agenda_item_id, asserts ->agendaItem returns correct model
      Evidence: .sisyphus/evidence/task-2-slide-relationship.txt
    
    Scenario: Null on delete works
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_slide_agenda_item_id_nulled_on_delete`
      Expected Result: Deleting agenda item sets slide.service_agenda_item_id to null (not cascade delete)
      Evidence: .sisyphus/evidence/task-2-null-on-delete.txt
    

    Commit: YES

    • Message: feat(db): add service_agenda_item_id FK to slides
    • Files: database/migrations/*_add_service_agenda_item_id_to_slides_table.php, app/Models/Slide.php, test file
    • Pre-commit: php artisan test

  • 3. Create AgendaMatcherService (Wildcard Namesmatching)

    What to do:

    • Create app/Services/AgendaMatcherService.php with:
      • matches(string $itemTitle, string $pattern): bool — glob-style wildcard matching (* matches any chars, case-insensitive)
      • matchesAny(string $itemTitle, array $patterns): bool — returns true if any pattern matches
      • findFirstMatch(array $agendaItems, array $patterns): ?ServiceAgendaItem — returns first item whose title matches any pattern
      • filterBetween(array $items, ?string $startPattern, ?string $endPattern): array — filters items between first match of start and end patterns
    • Write comprehensive unit tests:
      • Exact match: "Predigt" matches "Predigt"
      • Wildcard suffix: "Predigt*" matches "Predigt Thema XY"
      • Wildcard prefix: "*Predigt" matches "Die Predigt"
      • Wildcard both: "*Predigt*" matches "Die Predigt heute"
      • Case insensitive: "predigt*" matches "Predigt Thema"
      • No match: "Predigt*" does NOT match "Lobpreis"
      • Multiple patterns: ["Predigt*", "Sermon*"] matches "Predigt Thema"
      • findFirstMatch: returns first matching item from array
      • filterBetween with start only, end only, both, neither
      • filterBetween excludes the boundary items themselves

    Must NOT do:

    • Do NOT use regex — use simple glob-style * wildcard only
    • Do NOT add any UI or controller code

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 1, 2, 4, 5)
    • Blocks: Task 9
    • Blocked By: None

    References: Pattern References:

    • app/Services/SongMatchingService.php — Existing service class pattern (constructor injection, single responsibility)
    • app/Services/ChurchToolsService.php:454-462 — normalizeCcli() for input sanitization pattern

    WHY Each Reference Matters:

    • SongMatchingService — Follow same class structure (namespace, constructor, public methods)
    • normalizeCcli — Same trim/normalize approach for pattern input sanitization

    Acceptance Criteria:

    • php artisan test --filter=AgendaMatcherServiceTest → all tests pass (10+ test cases)

    QA Scenarios:

    Scenario: Wildcard matching works correctly
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=AgendaMatcherServiceTest`
      Expected Result: All wildcard matching test cases pass (exact, prefix*, *suffix, *both*, case-insensitive)
      Evidence: .sisyphus/evidence/task-3-matching.txt
    
    Scenario: filterBetween returns correct subset
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_filter_between_start_and_end`
      Expected Result: Given items [Header:Start, Item1, Item2, Header:End, Item3], returns [Item1, Item2]
      Evidence: .sisyphus/evidence/task-3-filter-between.txt
    
    Scenario: Empty patterns return all items
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_filter_between_no_patterns`
      Expected Result: When both start and end patterns are null/empty, returns all non-header items
      Evidence: .sisyphus/evidence/task-3-no-filter.txt
    

    Commit: YES

    • Message: feat(service): add AgendaMatcherService with wildcard namesmatching
    • Files: app/Services/AgendaMatcherService.php, tests/Feature/AgendaMatcherServiceTest.php
    • Pre-commit: php artisan test

  • 4. Add Agenda Settings Keys to SettingsController

    What to do:

    • Add 4 new setting keys to SettingsController::MACRO_KEYS (rename to SETTING_KEYS):
      • agenda_announcement_position — comma-separated patterns for announcement position matching (e.g. "Information*,Hinweis*")
      • agenda_sermon_matching — comma-separated patterns for sermon recognition (e.g. "Predigt*,Sermon*")
      • agenda_start_title — single pattern for agenda start boundary (e.g. "Ablauf*")
      • agenda_end_title — single pattern for agenda end boundary (e.g. "Ende*")
    • Update validation in update() to accept the new keys
    • Update index() to pass all settings including new ones
    • Write Pest tests:
      • Test settings page returns new keys
      • Test updating each new key
      • Test validation rejects unknown keys
      • Test comma-separated values stored correctly

    Must NOT do:

    • Do NOT change the Settings.vue yet (Task 14)
    • Do NOT change the Setting model

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 1, 2, 3, 5)
    • Blocks: Tasks 7, 14
    • Blocked By: None

    References: Pattern References:

    • app/Http/Controllers/SettingsController.php:13-18 — Existing MACRO_KEYS constant array pattern
    • app/Http/Controllers/SettingsController.php:32-37 — Validation with Rule::in pattern
    • app/Models/Setting.php — Setting::get() and Setting::set() API

    WHY Each Reference Matters:

    • SettingsController — Add to existing constant array and follow same validation pattern
    • Setting model — Understand get/set API for new keys

    Acceptance Criteria:

    • php artisan test --filter=SettingsControllerTest → all tests pass including new key tests

    QA Scenarios:

    Scenario: New setting keys are returned by index
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_settings_page_includes_agenda_keys`
      Expected Result: Inertia page props include agenda_announcement_position, agenda_sermon_matching, agenda_start_title, agenda_end_title
      Evidence: .sisyphus/evidence/task-4-settings-keys.txt
    
    Scenario: Updating agenda setting works
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_update_agenda_announcement_position`
      Expected Result: PATCH request with key=agenda_announcement_position, value="Information*,Hinweis*" returns 200
      Evidence: .sisyphus/evidence/task-4-update-setting.txt
    

    Commit: YES

    • Message: feat(settings): add agenda configuration keys
    • Files: app/Http/Controllers/SettingsController.php, test file
    • Pre-commit: php artisan test

  • 5. CTS Agenda Type Discovery Command

    What to do:

    • Create artisan command app/Console/Commands/DiscoverAgendaTypes.php:
      • Command: cts:discover-agenda-types
      • Fetches agenda for the next upcoming service via ChurchToolsService::syncAgenda()
      • Iterates agenda.getItems() and prints a table:
        • Position | Title | Type | Has Song | Duration | IsBeforeEvent
      • This is a diagnostic tool to discover actual CTS EventAgendaItem.type values
    • Write basic test:
      • Test command runs with mocked API data
      • Test output contains expected columns

    Must NOT do:

    • Do NOT use this to modify any data
    • Do NOT make this part of the sync flow

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 1, 2, 3, 4)
    • Blocks: None (informational only)
    • Blocked By: None

    References: Pattern References:

    • app/Services/ChurchToolsService.php:145-159 — syncAgenda method with injectable fetcher
    • vendor/5pm-hdh/churchtools-api/src/Models/Events/Event/EventAgendaItem.php — Item getters: getPosition(), getTitle(), getType(), getSong(), getIsBeforeEvent()

    WHY Each Reference Matters:

    • ChurchToolsService — Reuse syncAgenda() method for fetching
    • EventAgendaItem — Know which getter methods to call for output

    Acceptance Criteria:

    • php artisan cts:discover-agenda-types runs without error (with valid API config)
    • php artisan test --filter=DiscoverAgendaTypesTest → passes with mocked data

    QA Scenarios:

    Scenario: Command outputs agenda items
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_discover_command_outputs_table`
      Expected Result: Test verifies command outputs table with Position, Title, Type columns using mocked agenda data
      Evidence: .sisyphus/evidence/task-5-discover-command.txt
    

    Commit: YES

    • Message: chore(debug): add CTS agenda type discovery command
    • Files: app/Console/Commands/DiscoverAgendaTypes.php, test file
    • Pre-commit: php artisan test

  • 6. Extend ChurchToolsService to Sync ALL Agenda Items

    What to do:

    • Rename syncServiceAgendaSongs() to syncServiceAgendaItems() (keep old method as deprecated wrapper for backward compat)
    • New syncServiceAgendaItems():
      • Call $agenda->getItems() instead of $agenda->getSongs()
      • For EACH EventAgendaItem:
        • Upsert into service_agenda_items table using [service_id, sort_order] unique key
        • Store all fields: cts_agenda_item_id, position, title, type, note, duration, start, is_before_event, responsible
        • If item has a getSong() that is not null:
          • Continue the existing service_songs upsert logic (create/update service_song row)
          • Link: set service_agenda_item.service_song_id → the service_song ID
          • Auto-match via SongMatchingService (existing behavior)
      • Sort order: derive from item position or array index
      • Remove orphaned agenda items: delete service_agenda_items where service_id matches but sort_order not in current sync batch
      • Preserve slides: when removing orphaned agenda items, set their slides' service_agenda_item_id to null (don't delete slides)
    • Update sync summary to include agenda_items_count
    • Write comprehensive tests:
      • Test all agenda items (songs + non-songs) are stored
      • Test song items create both service_song AND service_agenda_item records with correct link
      • Test non-song items don't create service_song records
      • Test re-sync updates existing items
      • Test orphaned items are cleaned up
      • Test slides are preserved when orphaned items are removed

    Must NOT do:

    • Do NOT change the syncAgenda() API call method
    • Do NOT remove existing service_songs table or data
    • Do NOT change the injectable closure pattern

    Recommended Agent Profile:

    • Category: deep
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 7, 8, 9)
    • Blocks: Task 17
    • Blocked By: Task 1

    References: Pattern References:

    • app/Services/ChurchToolsService.php:324-385 — Current syncServiceAgendaSongs() — the code being extended
    • app/Services/ChurchToolsService.php:145-159syncAgenda() returns agenda object
    • app/Services/SongMatchingService.phpautoMatch() method used for song matching
    • vendor/5pm-hdh/churchtools-api/src/Models/Events/Event/EventAgenda.php:43-52getSongs() vs getItems() difference
    • vendor/5pm-hdh/churchtools-api/src/Models/Events/Event/EventAgendaItem.php — Full item API

    Test References:

    • tests/Feature/ChurchToolsSyncTest.php — Existing sync tests with mocked API data and fake event objects

    WHY Each Reference Matters:

    • syncServiceAgendaSongs — This IS the method being refactored; understand every line
    • EventAgenda.getSongs() — Shows current filtering; need to switch to getItems()
    • EventAgendaItem — API surface for extracting all fields
    • ChurchToolsSyncTest — Follow same mocking pattern for new tests

    Acceptance Criteria:

    • php artisan test --filter=ChurchToolsSyncTest → all existing tests still pass
    • php artisan test --filter=test_sync_stores_all_agenda_item_types → passes
    • php artisan test --filter=test_sync_links_song_items_to_service_songs → passes

    QA Scenarios:

    Scenario: Sync stores all agenda item types
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_sync_stores_all_agenda_item_types`
      Expected Result: Given mock agenda with 8 items (3 songs, 2 headers, 3 default), all 8 appear in service_agenda_items table
      Evidence: .sisyphus/evidence/task-6-all-types.txt
    
    Scenario: Song items link to service_songs
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_sync_links_song_items_to_service_songs`
      Expected Result: Song agenda items have non-null service_song_id pointing to correct service_songs row
      Evidence: .sisyphus/evidence/task-6-song-links.txt
    
    Scenario: Orphaned agenda items cleaned up on re-sync
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_resync_removes_orphaned_agenda_items`
      Expected Result: After re-sync with fewer items, old items are removed but their slides retain null FK
      Evidence: .sisyphus/evidence/task-6-orphan-cleanup.txt
    

    Commit: YES

    • Message: feat(sync): sync all CTS agenda items (not just songs)
    • Files: app/Services/ChurchToolsService.php, tests/Feature/ChurchToolsSyncTest.php
    • Pre-commit: php artisan test

  • 7. Update ServiceController.edit() to Pass Agenda Items

    What to do:

    • Update ServiceController::edit() to:
      • Load service.agendaItems (ordered by sort_order) with eager-loaded relationships:
        • agendaItems.serviceSong.song.groups.slides (for song items)
        • agendaItems.serviceSong.song.arrangements.arrangementGroups.group
        • agendaItems.serviceSong.arrangement.arrangementGroups.group (selected arrangement)
        • agendaItems.slides (uploaded slides per agenda item)
      • Load settings: agenda_announcement_position, agenda_sermon_matching, agenda_start_title, agenda_end_title
      • Pass to Inertia: agendaItems, agendaSettings, plus existing informationSlides, songsCatalog
      • Keep existing serviceSongs prop for backward compatibility during transition
      • Apply filterBetween using AgendaMatcherService on the agenda items based on start/end title settings
      • Mark items: add computed properties is_announcement_position (matches announcement pattern), is_sermon (matches sermon pattern) to each item
    • Write tests:
      • Test edit page returns agendaItems prop
      • Test agendaItems are filtered by start/end titles
      • Test is_announcement_position and is_sermon flags are set correctly
      • Test settings are passed to frontend

    Must NOT do:

    • Do NOT remove existing props yet (backward compat)
    • Do NOT change the route definition

    Recommended Agent Profile:

    • Category: unspecified-high
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 6, 8, 9)
    • Blocks: Tasks 10, 11, 12
    • Blocked By: Tasks 1, 2, 4

    References: Pattern References:

    • app/Http/Controllers/ServiceController.php:88-224 — Current edit() method with Eloquent eager loading and Inertia::render
    • app/Models/Setting.php:15-20 — Setting::get() for retrieving settings
    • app/Services/AgendaMatcherService.php — filterBetween() and findFirstMatch() (from Task 3)

    Test References:

    • tests/Feature/ServiceControllerTest.php — Existing edit page tests with assertInertia

    WHY Each Reference Matters:

    • ServiceController::edit() — This method is being modified; need to understand existing eager loading
    • Setting::get() — How to retrieve the new settings for passing to frontend
    • AgendaMatcherService — Used for filtering and marking items

    Acceptance Criteria:

    • php artisan test --filter=ServiceControllerTest → all existing tests pass
    • php artisan test --filter=test_edit_returns_agenda_items → passes
    • php artisan test --filter=test_edit_filters_agenda_by_title_settings → passes

    QA Scenarios:

    Scenario: Edit page returns agenda items
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_edit_returns_agenda_items`
      Expected Result: Inertia page has 'agendaItems' prop with correct structure including type, title, serviceSong relation
      Evidence: .sisyphus/evidence/task-7-agenda-items-prop.txt
    
    Scenario: Agenda items filtered by start/end title settings
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_edit_filters_agenda_by_title_settings`
      Expected Result: With agenda_start_title="Ablauf*" and agenda_end_title="Ende*", only items between those headers are returned
      Evidence: .sisyphus/evidence/task-7-filtered-items.txt
    
    Scenario: Sermon and announcement flags set correctly
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_edit_marks_sermon_and_announcement_items`
      Expected Result: Items matching agenda_sermon_matching have is_sermon=true, items matching agenda_announcement_position have is_announcement_position=true
      Evidence: .sisyphus/evidence/task-7-item-flags.txt
    

    Commit: YES

    • Message: feat(controller): pass agenda items to edit page
    • Files: app/Http/Controllers/ServiceController.php, test file
    • Pre-commit: php artisan test

  • 8. Update Service.finalizationStatus() for Agenda Model

    What to do:

    • Update Service::finalizationStatus() to check:
      • All song-type agenda items have their service_song matched (song_id not null)
      • All matched songs have an arrangement selected (song_arrangement_id not null)
      • At least one agenda item flagged as sermon has slides uploaded (replaces "sermon slides exist" check)
      • Announce if announcement position matching found a match (informational, not blocking)
    • Keep the same return shape: {ready: bool, warnings: string[]}
    • Warning messages in German Du-form
    • Write tests:
      • Test all songs matched → no warning
      • Test unmatched song → warning "Song X wurde noch nicht zugeordnet"
      • Test song without arrangement → warning "Song X hat kein Arrangement"
      • Test no sermon slides → warning "Keine Predigt-Folien hochgeladen"
      • Test all checks pass → ready=true

    Must NOT do:

    • Do NOT remove existing checks — replace them to work with new model
    • Do NOT change the finalization flow/endpoint

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 6, 7, 9)
    • Blocks: Tasks 15, 17
    • Blocked By: Task 1

    References: Pattern References:

    • app/Models/Service.php — Current finalizationStatus() method
    • tests/Feature/FinalizationTest.php — Existing finalization tests

    WHY Each Reference Matters:

    • Service::finalizationStatus() — The method being modified; understand current checks
    • FinalizationTest — Update existing tests to use new model

    Acceptance Criteria:

    • php artisan test --filter=FinalizationTest → all tests pass (updated for new model)

    QA Scenarios:

    Scenario: Finalization warns about unmatched songs
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_finalization_warns_unmatched_songs`
      Expected Result: Service with unmatched song agenda item returns warning
      Evidence: .sisyphus/evidence/task-8-unmatched-warning.txt
    
    Scenario: All checks pass allows finalization
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_finalization_ready_when_all_checks_pass`
      Expected Result: ready=true, warnings=[]
      Evidence: .sisyphus/evidence/task-8-ready.txt
    

    Commit: YES

    • Message: refactor(model): update finalizationStatus for agenda model
    • Files: app/Models/Service.php, tests/Feature/FinalizationTest.php
    • Pre-commit: php artisan test

  • 9. PlaylistExportService — Agenda-Ordered Export

    What to do:

    • Refactor PlaylistExportService::generatePlaylist() to:
      • Load service with agendaItems ordered by sort_order
      • Build playlist in agenda order:
        1. For each agenda item in order:
          • If item is_announcement_position → insert information slides as .pro presentation at this position
          • If item has a matched song with arrangement → generate .pro via ProExportService
          • If item has uploaded slides (via agendaItem.slides) → generate .pro presentation from those slides
          • If item has both song AND slides → song .pro first, then slides .pro
          • If item has nothing → skip
        2. If NO agenda item matched announcement position → insert information slides at the beginning (fallback)
      • Keep the .proplaylist format (ProPlaylistGenerator)
      • Handle backward compatibility: if service has no agenda items (old data), fall back to existing block-based export
    • Write comprehensive tests:
      • Test export follows agenda order
      • Test announcements embedded at matched position
      • Test announcements at beginning if no match
      • Test items with slides are included
      • Test items without slides or songs are skipped
      • Test backward compat: old services use block-based export
      • Test song + slides combination

    Must NOT do:

    • Do NOT change the ProExportService (individual .pro generation)
    • Do NOT change the propresenter-parser package
    • Do NOT change the .proplaylist format

    Recommended Agent Profile:

    • Category: deep
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 6, 7, 8)
    • Blocks: Tasks 16, 17
    • Blocked By: Tasks 1, 2, 3

    References: Pattern References:

    • app/Services/PlaylistExportService.php — Current generatePlaylist() — the method being refactored
    • app/Services/ProExportService.php — generateProFile() for individual song .pro files
    • app/Services/ProBundleExportService.php — generateBundle() for slides-to-.pro conversion pattern
    • app/Services/AgendaMatcherService.php — findFirstMatch() for announcement position detection

    Test References:

    • tests/Feature/PlaylistExportTest.php — Existing export tests

    WHY Each Reference Matters:

    • PlaylistExportService — The service being refactored; understand current item ordering and embedding
    • ProExportService — Used unchanged for generating individual song .pro files
    • ProBundleExportService — Pattern for converting uploaded slides into .pro presentation
    • AgendaMatcherService — Used to find announcement insertion point

    Acceptance Criteria:

    • php artisan test --filter=PlaylistExportTest → all tests pass
    • php artisan test --filter=test_export_follows_agenda_order → passes
    • php artisan test --filter=test_export_embeds_announcements_at_matched_position → passes

    QA Scenarios:

    Scenario: Export follows agenda order
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_export_follows_agenda_order`
      Expected Result: Given agenda [Begrüßung, Song1, Information, Predigt, Song2], export order is [Song1.pro, InfoSlides.pro, Predigt-Slides.pro, Song2.pro]
      Evidence: .sisyphus/evidence/task-9-agenda-order.txt
    
    Scenario: Announcements at matched position
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_export_embeds_announcements_at_matched_position`
      Expected Result: With announcement setting "Information*", announcements .pro is embedded right at the "Informationen" agenda item position
      Evidence: .sisyphus/evidence/task-9-announcement-position.txt
    
    Scenario: Backward compat for old services
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_export_falls_back_to_block_order`
      Expected Result: Service without agenda items exports in old block order (info→songs→moderation→sermon)
      Evidence: .sisyphus/evidence/task-9-backward-compat.txt
    

    Commit: YES

    • Message: feat(export): agenda-ordered playlist export
    • Files: app/Services/PlaylistExportService.php, tests/Feature/PlaylistExportTest.php
    • Pre-commit: php artisan test
  • 10. Redesign ArrangementDialog — Two-Column with Lyric Preview

    What to do:

    • Create new resources/js/Components/ArrangementDialog.vue (replaces ArrangementConfigurator.vue in the arrangement popup context):
      • Top section: Arrangement selection dropdown + "Neu" (create) + "Duplizieren" (clone) buttons
      • Two-column layout:
        • Left column (pills): Vertical list of arrangement group names as colored pills
          • Drag-and-drop reorderable (VueDraggable)
          • Each pill shows group name + group color as background/border
          • Right side of each pill: "2x" button (duplicate this group element directly below) + "+" button (quick-select popup showing ALL available song groups to insert below)
          • "+" button shows a dropdown/popup with all song groups (Vers1, Vers2, Chorus, Bridge, etc.) — clicking one inserts it below the current pill
        • Right column (lyric preview): Same elements as left, scroll-synced
          • Each element shows:
            • Colored left border matching group color
            • Small pill-style title (e.g. "Vers 1") inside the top-left of the element
            • Actual lyric text from SongSlide.text_content — one line per slide
            • If song has translation: SongSlide.text_content_translated shown below each original line in gray, italic, smaller font
            • If a group has multiple slides: show all slides together with a thin gray line separator between them
          • Scroll position synced with left column (when user scrolls left, right scrolls to matching position and vice versa)
      • Delete arrangement: Button to delete selected arrangement (prevent deleting last one)
      • API calls (reuse existing endpoints):
        • POST /songs/{songId}/arrangements — create new
        • POST /arrangements/{arrangementId}/clone — clone
        • PUT /arrangements/{arrangementId} — update groups order (on drag-end, on 2x, on + add)
        • DELETE /arrangements/{arrangementId} — delete
      • Emits: arrangement-selected(arrangementId), close
      • Props: songId, arrangements, selectedArrangementId, groups (all song groups with slides)
    • This is a modal/dialog component, opened by edit button on song agenda items

    Must NOT do:

    • Do NOT delete ArrangementConfigurator.vue yet (used by SongEditModal — removed in Task 19)
    • Do NOT change the API endpoints (reuse existing)
    • Do NOT add English text — all German Du-form

    Recommended Agent Profile:

    • Category: visual-engineering
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 11, 13)
    • Blocks: Task 12
    • Blocked By: Task 7

    References: Pattern References:

    • resources/js/Components/ArrangementConfigurator.vue — Current arrangement UI with VueDraggable, create/clone/delete buttons (270 lines). Reuse API call patterns and VueDraggable configuration.
    • resources/js/Components/SongEditModal.vue — Modal dialog pattern with auto-save
    • resources/js/Components/Blocks/SongsBlock.vue:1-100 — How arrangements are currently displayed and selected

    API/Type References:

    • app/Models/SongGroup.php — Group fields: name, color, order
    • app/Models/SongSlide.php — Slide fields: text_content, text_content_translated, order
    • app/Models/SongArrangement.php — Arrangement: name, is_default
    • app/Models/SongArrangementGroup.php — ArrangementGroup: song_arrangement_id, song_group_id, order

    External References:

    • VueDraggable: already used in project, see existing import pattern in ArrangementConfigurator.vue

    WHY Each Reference Matters:

    • ArrangementConfigurator.vue — Core logic being redesigned; reuse API calls, VueDraggable config, error handling
    • SongGroup/SongSlide — Data structure for rendering lyric preview and group colors
    • SongEditModal — Follow same modal dialog UX patterns (overlay, close on escape, etc.)

    Acceptance Criteria:

    • npm run build → no errors
    • ArrangementDialog renders with two columns
    • Drag-and-drop reordering saves via PUT endpoint
    • "2x" duplicates element below
    • "+" shows quick-select of all groups

    QA Scenarios:

    Scenario: Two-column layout renders correctly
      Tool: Playwright
      Preconditions: Dev server running, authenticated, service with matched song that has groups/slides
      Steps:
        1. Navigate to service edit page
        2. Find song agenda item, click edit button (data-testid="song-edit-arrangement")
        3. Assert dialog is visible
        4. Assert left column has pill elements (data-testid="arrangement-pill-list")
        5. Assert right column has lyric content (data-testid="arrangement-lyric-preview")
        6. Take screenshot
      Expected Result: Dialog shows with two columns, pills on left, lyrics on right
      Failure Indicators: Dialog not opening, missing columns, no content
      Evidence: .sisyphus/evidence/task-10-two-column-layout.png
    
    Scenario: Drag-and-drop reorders elements
      Tool: Playwright
      Preconditions: Dialog open with 3+ arrangement elements
      Steps:
        1. Note current order of pills
        2. Drag first pill to third position
        3. Wait for save indicator (auto-save)
        4. Assert pills are in new order
      Expected Result: Pills reorder and save persists (PUT request sent)
      Evidence: .sisyphus/evidence/task-10-drag-reorder.png
    
    Scenario: 2x button duplicates element
      Tool: Playwright
      Preconditions: Dialog open with arrangement elements
      Steps:
        1. Count current pills (e.g. 4)
        2. Click "2x" button on second pill (data-testid="arrangement-duplicate-btn")
        3. Assert pill count is now 5
        4. Assert new pill is same group name as the one duplicated
        5. Assert it appears directly after the duplicated pill
      Expected Result: Duplicate appears below, same group name
      Evidence: .sisyphus/evidence/task-10-duplicate.png
    
    Scenario: Lyric preview shows translation in gray/italic
      Tool: Playwright
      Preconditions: Song with has_translation=true and text_content_translated populated
      Steps:
        1. Open arrangement dialog for translated song
        2. Assert original text is visible in normal font
        3. Assert translated text is visible with classes for gray, italic, small (check computed styles)
      Expected Result: Translation text displayed below original in gray italic small font
      Evidence: .sisyphus/evidence/task-10-translation-preview.png
    

    Commit: YES

    • Message: feat(ui): redesign ArrangementDialog with lyric preview
    • Files: resources/js/Components/ArrangementDialog.vue
    • Pre-commit: npm run build

  • 11. Create AgendaItemRow and SongAgendaItem Components

    What to do:

    • Create resources/js/Components/AgendaItemRow.vue:
      • Generic agenda item display row with:
        • Left: position number, title, optional note
        • Center: slide thumbnails (if any uploaded)
        • Right: "+" button for slide upload (opens drag-and-drop area below)
      • When "+" clicked: toggles a SlideUploader area below the row (service-specific, type='agenda_item')
      • Highlight behavior:
        • Items with uploaded slides: success-style highlight (green border/bg)
        • Items matching announcement position: special indicator/icon
        • Items matching sermon pattern: sermon label/indicator
      • Props: agendaItem (full object with slides, flags), serviceId, serviceDate
      • Emits: slides-updated (triggers page reload)
    • Create resources/js/Components/SongAgendaItem.vue:
      • Extends/wraps AgendaItemRow with song-specific features:
        • Song name from CTS + matched song name from DB
        • If NOT matched: search dropdown to assign from catalog + "Erstellung anfragen" button (reuse from SongsBlock)
        • If matched:
          • Arrangement name shown as label
          • Element pills (Vers1, Chorus, Bridge, etc.) from selected arrangement's groups — shown as small colored pills in a row
          • Edit button → opens ArrangementDialog (Task 10) as modal
          • Translation checkbox (if has_translation)
        • Highlight: success (has arrangement), warning (no arrangement), neutral (not matched)
      • Props: agendaItem (with serviceSong relation), songsCatalog, serviceId
      • Emits: arrangement-selected, slides-updated
    • Both components use data-testid attributes on all interactive elements

    Must NOT do:

    • Do NOT implement the full upload logic (reuse existing SlideUploader)
    • Do NOT change existing API endpoints
    • Do NOT use Sie-form

    Recommended Agent Profile:

    • Category: visual-engineering
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 10, 13)
    • Blocks: Tasks 12, 13
    • Blocked By: Task 7

    References: Pattern References:

    • resources/js/Components/Blocks/SongsBlock.vue — Song matching UI (search dropdown, request button, arrangement selector, translation checkbox). Reuse the matching/assign/request logic.
    • resources/js/Components/SlideUploader.vue — Reusable uploader component (already built)
    • resources/js/Components/SlideGrid.vue — Thumbnail display for uploaded slides

    API/Type References:

    • routes/api.php — Service-song assign/request/update endpoints
    • app/Http/Controllers/ServiceSongController.php — API for song matching operations

    WHY Each Reference Matters:

    • SongsBlock.vue — Has the complete song matching UX; extract/reuse the match/assign/request logic
    • SlideUploader/SlideGrid — Compose these into the agenda item row for slide management
    • ServiceSongController — API endpoints to reuse for song operations

    Acceptance Criteria:

    • npm run build → no errors
    • AgendaItemRow renders with title, position, "+" button
    • SongAgendaItem shows arrangement pills for matched songs

    QA Scenarios:

    Scenario: Non-song agenda item shows with "+" upload button
      Tool: Playwright
      Preconditions: Service with non-song agenda items (e.g. "Gebet")
      Steps:
        1. Navigate to service edit page
        2. Find non-song agenda item row (data-testid="agenda-item-row")
        3. Assert title "Gebet" is visible
        4. Assert "+" button exists (data-testid="agenda-item-add-slides")
      Expected Result: Row displays with title and "+" button
      Evidence: .sisyphus/evidence/task-11-agenda-item-row.png
    
    Scenario: Song shows arrangement pills
      Tool: Playwright
      Preconditions: Service with matched song that has arrangement with groups
      Steps:
        1. Navigate to service edit page
        2. Find song agenda item (data-testid="song-agenda-item")
        3. Assert arrangement name label is visible
        4. Assert group pills (data-testid="arrangement-pill") exist with correct names (e.g. "Vers 1", "Chorus")
      Expected Result: Song row shows arrangement name + colored pills for each group
      Evidence: .sisyphus/evidence/task-11-song-pills.png
    
    Scenario: Unmatched song shows search dropdown
      Tool: Playwright
      Preconditions: Service with unmatched song (no song_id)
      Steps:
        1. Navigate to service edit page
        2. Find unmatched song row
        3. Assert search dropdown (data-testid="song-search-select") is visible
        4. Assert "Erstellung anfragen" button (data-testid="request-song-btn") exists
      Expected Result: Unmatched song shows assignment controls
      Evidence: .sisyphus/evidence/task-11-unmatched-song.png
    

    Commit: YES

    • Message: feat(ui): add AgendaItemRow and SongAgendaItem components
    • Files: resources/js/Components/AgendaItemRow.vue, resources/js/Components/SongAgendaItem.vue
    • Pre-commit: npm run build

  • 12. Restructure Edit.vue — Agenda View Replacing Accordion

    What to do:

    • Major restructure of resources/js/Pages/Services/Edit.vue:
      • Top section (keep): Announcements/Information block
        • Keep the existing InformationBlock component at the top
        • This is unchanged: global slides with expire dates, upload, delete, download bundle
      • Agenda section (new): Replace the 3 block accordion (Moderation, Sermon, Songs) with:
        • Header: "Ablauf" (Agenda)
        • List of all agenda items in order (from agendaItems prop)
        • For each item:
          • If item is a header/title type: render as section divider (styled differently, no "+" button)
          • If item is a song: render SongAgendaItem component
          • If item is flagged as sermon (is_sermon): render AgendaItemRow with sermon-specific upload area and label
          • If item is flagged as announcement position (is_announcement_position): render AgendaItemRow with announcement indicator
          • Otherwise: render AgendaItemRow (generic with "+" button)
        • Each item with uploaded slides or song arrangement: highlighted (success/green border)
        • Song without arrangement: warning highlight (amber/orange border)
      • Footer (keep, adapted): Finalization status + action buttons
        • Keep Finalize, Reopen, Download buttons
        • Update finalization status display for new model
      • Remove: ModerationBlock, SermonBlock, SongsBlock imports and usage
      • Add: imports for AgendaItemRow, SongAgendaItem, ArrangementDialog

    Must NOT do:

    • Do NOT change InformationBlock (announcements stay as-is)
    • Do NOT change finalization/reopen/download endpoints
    • Do NOT add English strings

    Recommended Agent Profile:

    • Category: visual-engineering
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO
    • Parallel Group: Wave 3 (after Tasks 10, 11)
    • Blocks: Tasks 16, 18
    • Blocked By: Tasks 10, 11

    References: Pattern References:

    • resources/js/Pages/Services/Edit.vue — Current 662-line edit page (the file being restructured)
    • resources/js/Components/Blocks/InformationBlock.vue — Keep this component unchanged
    • resources/js/Layouts/AuthenticatedLayout.vue — Layout pattern

    WHY Each Reference Matters:

    • Edit.vue — Must understand current structure to preserve header, footer, navigation, and toast functionality while replacing the middle section
    • InformationBlock — Must continue to work as-is in the new layout

    Acceptance Criteria:

    • npm run build → no errors
    • Edit page shows announcements section at top
    • Edit page shows agenda items list below
    • Songs show inline arrangement pills
    • Finalize/Download buttons still work

    QA Scenarios:

    Scenario: Edit page shows agenda instead of accordion
      Tool: Playwright
      Preconditions: Service with synced agenda items (mix of songs, default items, headers)
      Steps:
        1. Navigate to /services/{id}/edit
        2. Assert InformationBlock (data-testid="information-block") is visible at top
        3. Assert agenda section (data-testid="agenda-section") is visible below
        4. Assert NO accordion blocks (data-testid="moderation-block") exist
        5. Assert agenda items are listed in order
        6. Take screenshot
      Expected Result: Page shows announcements + agenda list, no accordion
      Evidence: .sisyphus/evidence/task-12-edit-page-structure.png
    
    Scenario: Header items render as dividers
      Tool: Playwright
      Preconditions: Agenda with header-type items
      Steps:
        1. Navigate to edit page
        2. Find header items (data-testid="agenda-header-item")
        3. Assert they have divider styling (larger text, different background)
        4. Assert they do NOT have a "+" upload button
      Expected Result: Headers are visual dividers without interaction
      Evidence: .sisyphus/evidence/task-12-header-dividers.png
    
    Scenario: Highlighted items show correct status
      Tool: Playwright
      Steps:
        1. Navigate to edit page with: 1 song with arrangement, 1 song without arrangement, 1 item with slides, 1 item without slides
        2. Assert song with arrangement has success highlight (data-testid contains class with green/emerald)
        3. Assert song without arrangement has warning highlight (amber/orange)
        4. Assert item with slides has success highlight
        5. Assert plain item has no highlight
      Expected Result: Correct visual status for each item type
      Evidence: .sisyphus/evidence/task-12-highlights.png
    

    Commit: YES

    • Message: feat(ui): restructure Edit.vue with agenda view
    • Files: resources/js/Pages/Services/Edit.vue
    • Pre-commit: npm run build

  • 13. Slide Upload on Agenda Items — "+" Button + Drag-and-Drop

    What to do:

    • Enhance AgendaItemRow.vue (from Task 11):
      • When "+" button clicked: show/hide a SlideUploader component below the row
      • SlideUploader configured with: type='agenda_item', serviceId, and new prop agendaItemId
      • After upload: reload page to show new slide thumbnails
    • Update SlideController::store():
      • Accept optional service_agenda_item_id parameter
      • When provided: set service_agenda_item_id on created Slide record
      • Keep existing type parameter for backward compat, but allow new type value 'agenda_item'
    • Update SlideUploader.vue:
      • Add optional agendaItemId prop
      • When set: include service_agenda_item_id in upload FormData
      • No expire-date picker for agenda item slides (they're service-specific)
    • Show uploaded slides as small thumbnails inline in the agenda item row (reuse SlideGrid patterns)
    • Update slide deletion to work for agenda-item slides
    • Write tests:
      • Test uploading slide with service_agenda_item_id
      • Test slide appears linked to correct agenda item
      • Test deletion works

    Must NOT do:

    • Do NOT change file conversion logic (images, PPT processing)
    • Do NOT add expire dates to agenda item slides

    Recommended Agent Profile:

    • Category: visual-engineering
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 10, 11 — but after 11 creates base component)
    • Blocks: Task 18
    • Blocked By: Tasks 2, 11

    References: Pattern References:

    • resources/js/Components/SlideUploader.vue — Existing uploader component to extend
    • app/Http/Controllers/SlideController.php — store() method handling uploads
    • resources/js/Components/SlideGrid.vue — Thumbnail display pattern

    Test References:

    • tests/Feature/SlideControllerTest.php — Existing slide upload tests

    WHY Each Reference Matters:

    • SlideUploader — Adding agendaItemId prop, minimal change to existing component
    • SlideController::store() — Add service_agenda_item_id to the validation and storage logic
    • SlideGrid — Reuse for inline thumbnail display in agenda rows

    Acceptance Criteria:

    • npm run build → no errors
    • php artisan test --filter=SlideControllerTest → all tests pass
    • Slides uploaded via agenda item "+" button are linked to correct agenda item

    QA Scenarios:

    Scenario: Upload slide on agenda item
      Tool: Playwright
      Preconditions: Service with agenda items, dev server running
      Steps:
        1. Navigate to service edit page
        2. Find a non-song agenda item (e.g. "Gebet")
        3. Click "+" button (data-testid="agenda-item-add-slides")
        4. Assert upload area appears below the row
        5. Upload a test image file
        6. Wait for upload to complete and page reload
        7. Assert thumbnail appears in the agenda item row
      Expected Result: Slide uploaded and visible as thumbnail in the agenda item
      Evidence: .sisyphus/evidence/task-13-upload-slide.png
    
    Scenario: Delete slide from agenda item
      Tool: Playwright
      Steps:
        1. Navigate to edit page with agenda item that has slides
        2. Click delete button on slide thumbnail
        3. Confirm deletion
        4. Assert slide is removed
      Expected Result: Slide deleted (soft-delete), no longer visible
      Evidence: .sisyphus/evidence/task-13-delete-slide.png
    

    Commit: YES

    • Message: feat(ui): add slide upload on agenda items
    • Files: resources/js/Components/AgendaItemRow.vue, resources/js/Components/SlideUploader.vue, app/Http/Controllers/SlideController.php, test file
    • Pre-commit: npm run build && php artisan test

  • 14. Settings.vue — Add Agenda Configuration Fields

    What to do:

    • Update resources/js/Pages/Settings.vue:
      • Add new section below existing "ProPresenter Makro-Konfiguration":
        • Section title: "Agenda-Konfiguration"
        • Description: "Diese Einstellungen steuern, wie der Gottesdienst-Ablauf angezeigt und exportiert wird."
      • Add 4 new fields (same pattern as existing macro fields):
        • agenda_start_title — Label: "Ablauf-Start", Placeholder: "z.B. Ablauf*"
        • agenda_end_title — Label: "Ablauf-Ende", Placeholder: "z.B. Ende*"
        • agenda_announcement_position — Label: "Ankündigungen-Position", Placeholder: "z.B. Information*,Hinweis*"
          • Add help text: "Komma-getrennte Liste. Das erste passende Element im Ablauf bestimmt die Position der Ankündigungen im Export. * als Platzhalter."
        • agenda_sermon_matching — Label: "Predigt-Erkennung", Placeholder: "z.B. Predigt*,Sermon*"
          • Add help text: "Komma-getrennte Liste. Erkannte Elemente bekommen einen speziellen Upload-Bereich für Predigt-Folien. * als Platzhalter."
      • Reuse existing saveField() auto-save on blur pattern
      • Add data-testid attributes on all new inputs

    Must NOT do:

    • Do NOT change existing macro settings section
    • Do NOT add validation beyond what SettingsController provides

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 4 (with Tasks 15, 16)
    • Blocks: Task 18
    • Blocked By: Task 4

    References: Pattern References:

    • resources/js/Pages/Settings.vue — Existing settings page (155 lines) with field rendering and auto-save pattern. Add new section following same structure.

    WHY Each Reference Matters:

    • Settings.vue — The file being modified. Follow exact same field rendering pattern (v-for, auto-save on blur, saving/saved/error states)

    Acceptance Criteria:

    • npm run build → no errors
    • Settings page shows "Agenda-Konfiguration" section with 4 fields

    QA Scenarios:

    Scenario: Agenda settings fields visible and functional
      Tool: Playwright
      Steps:
        1. Navigate to /settings
        2. Assert section "Agenda-Konfiguration" is visible
        3. Assert input (data-testid="setting-agenda_start_title") exists with placeholder "z.B. Ablauf*"
        4. Assert input (data-testid="setting-agenda_announcement_position") exists
        5. Type "Predigt*" into sermon matching field
        6. Click outside (blur) to trigger save
        7. Assert saved indicator (green checkmark) appears
        8. Reload page
        9. Assert value "Predigt*" persists
      Expected Result: All 4 fields visible, auto-save works, values persist
      Evidence: .sisyphus/evidence/task-14-agenda-settings.png
    

    Commit: YES

    • Message: feat(ui): add agenda settings to Settings page
    • Files: resources/js/Pages/Settings.vue
    • Pre-commit: npm run build

  • 15. Update Service List — Status Columns

    What to do:

    • Update ServiceController::index():
      • Add count of agenda items with uploaded slides (non-song items that have slides)
      • Keep songs_mapped_count and songs_arranged_count
      • Keep sermon slides check (now: "any agenda item matching sermon pattern has slides")
      • Add: agenda_slides_count — count of non-song agenda items that have at least one slide
    • Update resources/js/Pages/Services/Index.vue:
      • Replace moderation/sermon column with new status indicators:
        • Songs: "x/y Lieder zugeordnet" (keep)
        • Arrangements: "x/y Arrangements" (keep)
        • Predigt: "Predigt-Folien ✓/✗" (keep, but source changes to agenda item matching)
        • New: "N weitere Folien" — count of non-song agenda items with slides
      • Remove references to moderation slides count
    • Write tests:
      • Test index returns correct agenda_slides_count
      • Test sermon detection works via agenda item matching

    Must NOT do:

    • Do NOT redesign the service list layout
    • Do NOT change finalize/reopen/download from list view

    Recommended Agent Profile:

    • Category: unspecified-high
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 4 (with Tasks 14, 16)
    • Blocks: None
    • Blocked By: Tasks 1, 8

    References: Pattern References:

    • app/Http/Controllers/ServiceController.php:30-86 — Current index() method with status counts
    • resources/js/Pages/Services/Index.vue — Current list page (512 lines) with status columns

    Test References:

    • tests/Feature/ServiceControllerTest.php — Existing index tests

    WHY Each Reference Matters:

    • ServiceController::index() — Modify count queries for new model
    • Index.vue — Update display columns

    Acceptance Criteria:

    • php artisan test --filter=ServiceControllerTest → all tests pass
    • npm run build → no errors

    QA Scenarios:

    Scenario: Service list shows updated status columns
      Tool: Playwright
      Steps:
        1. Navigate to /services
        2. Find a service row with known data
        3. Assert songs count is visible (e.g. "3/3 Lieder")
        4. Assert predigt status is visible
        5. Assert "N weitere Folien" count is visible
        6. Assert NO "Moderation" column exists
      Expected Result: List shows songs, predigt, and additional slides count
      Evidence: .sisyphus/evidence/task-15-service-list-status.png
    

    Commit: YES

    • Message: feat(ui): update service list status columns
    • Files: resources/js/Pages/Services/Index.vue, app/Http/Controllers/ServiceController.php, test file
    • Pre-commit: npm run build && php artisan test

  • 16. Wire Agenda-Ordered Export into Download Flow

    What to do:

    • Update ServiceController::download():
      • Use the updated PlaylistExportService::generatePlaylist() (from Task 9) which now follows agenda order
      • Add agenda items and settings loading before calling export
      • Pass announcement slides and their position to the export service
      • Handle the fallback: if service has no agenda items, use old block-based export path
    • Update ServiceController::finalize():
      • Use updated finalizationStatus() (from Task 8)
      • Keep confirmation dialog behavior unchanged
    • Write integration test:
      • Test full flow: create service with agenda items → upload slides → finalize → download → verify .proplaylist content order

    Must NOT do:

    • Do NOT change the download endpoint route
    • Do NOT change the .proplaylist format
    • Do NOT break existing downloadBundle() endpoint

    Recommended Agent Profile:

    • Category: unspecified-high
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO
    • Parallel Group: Wave 4 (after Tasks 9, 12)
    • Blocks: Task 18
    • Blocked By: Tasks 9, 12

    References: Pattern References:

    • app/Http/Controllers/ServiceController.php:226-260 — Current download() and finalize() methods
    • app/Services/PlaylistExportService.php — Updated service from Task 9

    Test References:

    • tests/Feature/FinalizationTest.php — Existing finalize/download integration tests
    • tests/Feature/PlaylistExportTest.php — Export tests

    WHY Each Reference Matters:

    • ServiceController — The methods being wired up to use the new export
    • PlaylistExportService — The updated service that now supports agenda ordering

    Acceptance Criteria:

    • php artisan test --filter=FinalizationTest → passes
    • php artisan test --filter=PlaylistExportTest → passes
    • php artisan test --filter=test_download_follows_agenda_order → passes

    QA Scenarios:

    Scenario: Full finalize → download flow with agenda order
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_full_finalize_download_flow`
      Expected Result: Service with agenda items finalizes and downloads .proplaylist with presentations in agenda order
      Evidence: .sisyphus/evidence/task-16-full-flow.txt
    
    Scenario: Old service without agenda items still downloadable
      Tool: Bash (php artisan test)
      Steps:
        1. Run `php artisan test --filter=test_old_service_download_backward_compat`
      Expected Result: Service finalized before migration downloads correctly using old block order
      Evidence: .sisyphus/evidence/task-16-backward-compat.txt
    

    Commit: YES

    • Message: feat(export): wire agenda export into download flow
    • Files: app/Http/Controllers/ServiceController.php, test file
    • Pre-commit: php artisan test

  • 17. Update Existing PHP Tests for Agenda Model

    What to do:

    • Review and update ALL existing PHP tests that reference the old model:
      • tests/Feature/ServiceControllerTest.php — Update edit page assertions for agendaItems prop
      • tests/Feature/FinalizationTest.php — Already updated in Task 8, verify integration
      • tests/Feature/PlaylistExportTest.php — Already updated in Task 9, verify integration
      • tests/Feature/ChurchToolsSyncTest.php — Already updated in Task 6, verify integration
      • tests/Feature/ModerationBlockTest.php — Mark tests as covering deprecated feature or update
      • tests/Feature/SermonBlockTest.php — Mark tests as covering deprecated feature or update
      • tests/Feature/SongsBlockTest.php — Update to test SongAgendaItem behavior
      • tests/Feature/SlideControllerTest.php — Add tests for agenda_item slide type
      • tests/Feature/InformationBlockTest.php — Verify still passes unchanged
    • Run full test suite: php artisan test — ALL tests must pass
    • Do NOT delete old tests yet — only update them to work with new model

    Must NOT do:

    • Do NOT delete any test files (deprecation in Task 19)
    • Do NOT skip tests — update them

    Recommended Agent Profile:

    • Category: unspecified-high
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 5 (with Tasks 18)
    • Blocks: Task 19
    • Blocked By: Tasks 6, 7, 8, 9

    References: Pattern References:

    • All test files in tests/Feature/ — each file listed above needs review

    WHY Each Reference Matters:

    • Every test file may reference old props (moderationSlides, sermonSlides) or old model structure

    Acceptance Criteria:

    • php artisan test → ALL tests pass (0 failures, 0 errors)

    QA Scenarios:

    Scenario: Full PHP test suite passes
      Tool: Bash
      Steps:
        1. Run `php artisan test`
      Expected Result: All tests pass with 0 failures and 0 errors
      Evidence: .sisyphus/evidence/task-17-full-test-suite.txt
    

    Commit: YES

    • Message: test(php): update existing tests for agenda model
    • Files: tests/Feature/*.php (multiple)
    • Pre-commit: php artisan test

  • 18. Playwright E2E Tests for Restructured Edit Page

    What to do:

    • Update/create E2E test files:
      • tests/e2e/service-edit-agenda.spec.ts (NEW):
        • Test agenda items display in correct order
        • Test song items show arrangement pills
        • Test non-song items show "+" upload button
        • Test header items render as dividers
        • Test slide upload on agenda item
        • Test arrangement dialog opens from song edit button
        • Test arrangement drag-and-drop
        • Test arrangement 2x duplicate
        • Test announcement position indicator
        • Test sermon item detection
        • Test highlight states (success/warning/neutral)
      • Update tests/e2e/service-edit-information.spec.ts:
        • Verify information block still at top, working as before
      • Update tests/e2e/service-finalization.spec.ts:
        • Verify finalize/download flow with new agenda model
      • Deprecate/remove:
        • tests/e2e/service-edit-moderation.spec.ts — no longer relevant
        • tests/e2e/service-edit-sermon.spec.ts — sermon is now part of agenda
        • tests/e2e/service-edit-songs.spec.ts — songs are now part of agenda
      • Update tests/e2e/sync-and-pro.spec.ts:
        • Verify sync creates agenda items
    • Run: npx playwright test — all tests pass

    Must NOT do:

    • Do NOT change auth setup
    • Do NOT change base URL configuration

    Recommended Agent Profile:

    • Category: unspecified-high
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 5 (with Task 17)
    • Blocks: Task 19
    • Blocked By: Tasks 12, 13, 14, 16

    References: Pattern References:

    • tests/e2e/service-edit-songs.spec.ts — Existing song E2E tests with authentication and Playwright patterns
    • tests/e2e/auth.setup.ts — Auth setup for E2E tests
    • tests/e2e/service-finalization.spec.ts — Finalization E2E flow

    WHY Each Reference Matters:

    • Existing E2E tests — Follow same patterns (authentication, selectors, assertions, German text)

    Acceptance Criteria:

    • npx playwright test → all tests pass

    QA Scenarios:

    Scenario: E2E test suite passes
      Tool: Bash
      Steps:
        1. Run `npx playwright test`
      Expected Result: All E2E tests pass
      Evidence: .sisyphus/evidence/task-18-e2e-results.txt
    

    Commit: YES

    • Message: test(e2e): Playwright tests for restructured edit page
    • Files: tests/e2e/*.spec.ts
    • Pre-commit: npx playwright test

  • 19. Remove Deprecated Block Components

    What to do:

    • Remove Vue components no longer used:
      • resources/js/Components/Blocks/ModerationBlock.vue — replaced by AgendaItemRow
      • resources/js/Components/Blocks/SermonBlock.vue — replaced by sermon-flagged AgendaItemRow
      • resources/js/Components/Blocks/SongsBlock.vue — replaced by SongAgendaItem
    • Update ArrangementConfigurator.vue:
      • If still used by SongEditModal.vue (SongDB page): keep it
      • If SongEditModal.vue now uses ArrangementDialog.vue: remove ArrangementConfigurator.vue
    • Remove deprecated test files:
      • tests/Feature/ModerationBlockTest.php — if fully replaced by agenda tests
      • tests/Feature/SermonBlockTest.php — if fully replaced
      • tests/e2e/service-edit-moderation.spec.ts
      • tests/e2e/service-edit-sermon.spec.ts
      • tests/e2e/service-edit-songs.spec.ts
    • Run full test suite to verify nothing breaks:
      • php artisan test → pass
      • npx playwright test → pass
      • npm run build → pass

    Must NOT do:

    • Do NOT remove InformationBlock (still in use)
    • Do NOT remove SlideGrid, SlideUploader (still in use)
    • Do NOT remove ArrangementConfigurator if SongEditModal still uses it

    Recommended Agent Profile:

    • Category: quick
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO
    • Parallel Group: Wave 5 (after Tasks 17, 18)
    • Blocks: None
    • Blocked By: Tasks 17, 18

    References: Pattern References:

    • resources/js/Components/Blocks/ — Directory containing block components to review
    • resources/js/Components/SongEditModal.vue — Check if it still uses ArrangementConfigurator

    Acceptance Criteria:

    • npm run build → no errors (no missing imports)
    • php artisan test → all tests pass
    • npx playwright test → all tests pass

    QA Scenarios:

    Scenario: Build succeeds after component removal
      Tool: Bash
      Steps:
        1. Run `npm run build`
        2. Run `php artisan test`
        3. Run `npx playwright test`
      Expected Result: All three commands succeed with zero errors
      Evidence: .sisyphus/evidence/task-19-cleanup-verification.txt
    

    Commit: YES

    • Message: refactor(cleanup): remove deprecated block components
    • Files: deleted Vue components, deleted test files
    • Pre-commit: npm run build && php artisan test

Final Verification Wave (MANDATORY — after ALL implementation tasks)

4 review agents run in PARALLEL. ALL must APPROVE. Present consolidated results to user and get explicit "okay" before completing.

  • F1. Plan Compliance Auditoracle Read the plan end-to-end. For each "Must Have": verify implementation exists (read file, curl endpoint, run command). For each "Must NOT Have": search codebase for forbidden patterns — reject with file:line if found. Check evidence files exist in .sisyphus/evidence/. Compare deliverables against plan. Output: Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT

  • F2. Code Quality Reviewunspecified-high Run ./vendor/bin/pint --test + php artisan test. Review all changed files for: as any/@ts-ignore, empty catches, console.log in prod, commented-out code, unused imports. Check AI slop: excessive comments, over-abstraction, generic names. Verify German Du-form in all UI text. Output: Pint [PASS/FAIL] | Tests [N pass/N fail] | Files [N clean/N issues] | VERDICT

  • F3. Real Manual QAunspecified-high (+ playwright skill) Start from clean state. Execute EVERY QA scenario from EVERY task — follow exact steps, capture evidence. Test cross-task integration: create service → sync agenda → upload slides on agenda items → configure arrangement → finalize → download .proplaylist → verify order. Edge cases: empty agenda, no songs, no matching settings. Output: Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT

  • F4. Scope Fidelity Checkdeep For each task: read "What to do", read actual diff (git log/diff). Verify 1:1 — everything in spec was built, nothing beyond spec was built. Check "Must NOT do" compliance. Detect cross-task contamination. Flag unaccounted changes. Output: Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | VERDICT


Commit Strategy

Task Commit Message Files Pre-commit
1 feat(db): add service_agenda_items table and model migration, model, factory, test php artisan test
2 feat(db): add service_agenda_item_id FK to slides migration, Slide model, test php artisan test
3 feat(service): add AgendaMatcherService with wildcard namesmatching service, test php artisan test
4 feat(settings): add agenda configuration keys controller, test php artisan test
5 chore(debug): add CTS agenda type discovery command artisan command php artisan test
6 feat(sync): sync all CTS agenda items (not just songs) ChurchToolsService, test php artisan test
7 feat(controller): pass agenda items to edit page ServiceController, test php artisan test
8 refactor(model): update finalizationStatus for agenda model Service model, test php artisan test
9 feat(export): agenda-ordered playlist export PlaylistExportService, test php artisan test
10 feat(ui): redesign ArrangementDialog with lyric preview Vue component npm run build
11 feat(ui): add AgendaItemRow and SongAgendaItem components Vue components npm run build
12 feat(ui): restructure Edit.vue with agenda view Edit.vue npm run build
13 feat(ui): add slide upload on agenda items Vue component, SlideController npm run build && php artisan test
14 feat(ui): add agenda settings to Settings page Settings.vue npm run build
15 feat(ui): update service list status columns Index.vue, ServiceController npm run build && php artisan test
16 feat(export): wire agenda export into download flow ServiceController php artisan test
17 test(php): update existing tests for agenda model test files php artisan test
18 test(e2e): Playwright tests for restructured edit page E2E test files npx playwright test
19 refactor(cleanup): remove deprecated block components Vue components, test files npm run build && php artisan test

Success Criteria

Verification Commands

php artisan test                    # Expected: all tests pass
npx playwright test                 # Expected: all E2E tests pass
./vendor/bin/pint --test            # Expected: no formatting issues
npm run build                       # Expected: no build errors

Final Checklist

  • All "Must Have" present
  • All "Must NOT Have" absent
  • All tests pass (PHP + E2E)
  • Service edit page shows CTS agenda
  • Arrangement dialog has two-column layout
  • Export follows agenda order
  • Settings page has agenda configuration
  • Existing finalized services still downloadable