83 KiB
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_itemsDB 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.typevalues 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.typeenum, plan uses a newservice_agenda_item_idFK on slides table. - isBeforeEvent items: Items with
isBeforeEvent=truewill 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_itemstable - Migration:
service_agenda_item_idFK onslidestable - Model:
ServiceAgendaItemwith relationships - Service:
AgendaMatcherService(wildcard namesmatching) - Updated:
ChurchToolsService::syncServiceAgendaItems()storing ALL items - Updated:
SettingsController+Settings.vuewith 4 new setting fields - Updated:
PlaylistExportServicefor 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_songsdata — 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_itemsMigration, Model + FactoryWhat to do:
- Create migration
create_service_agenda_items_tablewith columns:id(bigIncrements)service_id(FK to services, cascadeOnDelete)cts_agenda_item_id(string, nullable) — CTS API item IDposition(string) — CTS position value (e.g. "1", "2", "1.1")title(string) — item title from CTStype(string) — CTS type value (e.g. "Song", "Default", "Header" — actual values TBD from Task 5)note(text, nullable) — item noteduration(string, nullable) — planned durationstart(string, nullable) — planned start timeis_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 itemsort_order(unsignedInteger) — display order (derived from position)created_at,updated_at- Unique index:
[service_id, sort_order]
- Create
ServiceAgendaItemmodel with:$fillablearray with all columns- Relationships:
belongsToservice,belongsToserviceSong (nullable),hasManyslides casts()method:responsible→ array,is_before_event→ boolean- Scope:
scopeVisible($query)— filtersis_before_event = false - Scope:
scopeBetweenTitles($query, $startTitle, $endTitle)— filters items between matching header items using AgendaMatcherService
- Create factory
ServiceAgendaItemFactorywith 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_songstable - 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 indicesapp/Models/ServiceSong.php— Model pattern with fillable, casts, relationshipsapp/Models/Slide.php— SoftDeletes and nullable FK patterndatabase/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 patternServiceSong model— Copy the promoted constructor, fillable, casts, and relationship patternsEventAgendaItem.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.txtCommit: 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
- Create migration
-
2. Add
service_agenda_item_idFK to Slides TableWhat to do:
- Create migration
add_service_agenda_item_id_to_slides_table:- Add nullable
service_agenda_item_idFK column (nullOnDelete) - Add index on
service_agenda_item_id
- Add nullable
- Update
Slidemodel:- Add
service_agenda_item_idto$fillable - Add
agendaItem()belongsTo relationship →ServiceAgendaItem
- Add
- Update
ServiceAgendaItemmodel (from Task 1):- Verify
slides()hasMany relationship exists
- Verify
- 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 patternapp/Models/Slide.php— Current Slide model with fillable, relationships, SoftDeletesdatabase/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 compatibleSlide model— Must add to existing $fillable array and add new relationship methodadd_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.txtCommit: 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
- Create migration
-
3. Create AgendaMatcherService (Wildcard Namesmatching)
What to do:
- Create
app/Services/AgendaMatcherService.phpwith: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 matchesfindFirstMatch(array $agendaItems, array $patterns): ?ServiceAgendaItem— returns first item whose title matches any patternfilterBetween(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
- Exact match:
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.txtCommit: YES
- Message:
feat(service): add AgendaMatcherService with wildcard namesmatching - Files:
app/Services/AgendaMatcherService.php,tests/Feature/AgendaMatcherServiceTest.php - Pre-commit:
php artisan test
- Create
-
4. Add Agenda Settings Keys to SettingsController
What to do:
- Add 4 new setting keys to
SettingsController::MACRO_KEYS(rename toSETTING_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 patternapp/Http/Controllers/SettingsController.php:32-37— Validation with Rule::in patternapp/Models/Setting.php— Setting::get() and Setting::set() API
WHY Each Reference Matters:
SettingsController— Add to existing constant array and follow same validation patternSetting 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.txtCommit: YES
- Message:
feat(settings): add agenda configuration keys - Files:
app/Http/Controllers/SettingsController.php, test file - Pre-commit:
php artisan test
- Add 4 new setting keys to
-
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.typevalues
- Command:
- 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 fetchervendor/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 fetchingEventAgendaItem— Know which getter methods to call for output
Acceptance Criteria:
php artisan cts:discover-agenda-typesruns 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.txtCommit: YES
- Message:
chore(debug): add CTS agenda type discovery command - Files:
app/Console/Commands/DiscoverAgendaTypes.php, test file - Pre-commit:
php artisan test
- Create artisan command
-
6. Extend ChurchToolsService to Sync ALL Agenda Items
What to do:
- Rename
syncServiceAgendaSongs()tosyncServiceAgendaItems()(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_itemstable 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_songsupsert logic (create/update service_song row) - Link: set
service_agenda_item.service_song_id→ the service_song ID - Auto-match via
SongMatchingService(existing behavior)
- Continue the existing
- Upsert into
- Sort order: derive from item position or array index
- Remove orphaned agenda items: delete
service_agenda_itemswhereservice_idmatches butsort_ordernot in current sync batch - Preserve slides: when removing orphaned agenda items, set their slides'
service_agenda_item_idto null (don't delete slides)
- Call
- 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_songstable 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— CurrentsyncServiceAgendaSongs()— the code being extendedapp/Services/ChurchToolsService.php:145-159—syncAgenda()returns agenda objectapp/Services/SongMatchingService.php—autoMatch()method used for song matchingvendor/5pm-hdh/churchtools-api/src/Models/Events/Event/EventAgenda.php:43-52—getSongs()vsgetItems()differencevendor/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 lineEventAgenda.getSongs()— Shows current filtering; need to switch togetItems()EventAgendaItem— API surface for extracting all fieldsChurchToolsSyncTest— Follow same mocking pattern for new tests
Acceptance Criteria:
php artisan test --filter=ChurchToolsSyncTest→ all existing tests still passphp artisan test --filter=test_sync_stores_all_agenda_item_types→ passesphp 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.txtCommit: 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
- Rename
-
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.groupagendaItems.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 existinginformationSlides,songsCatalog - Keep existing
serviceSongsprop for backward compatibility during transition - Apply
filterBetweenusingAgendaMatcherServiceon 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
- Load
- 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::renderapp/Models/Setting.php:15-20— Setting::get() for retrieving settingsapp/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 loadingSetting::get()— How to retrieve the new settings for passing to frontendAgendaMatcherService— Used for filtering and marking items
Acceptance Criteria:
php artisan test --filter=ServiceControllerTest→ all existing tests passphp artisan test --filter=test_edit_returns_agenda_items→ passesphp 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.txtCommit: YES
- Message:
feat(controller): pass agenda items to edit page - Files:
app/Http/Controllers/ServiceController.php, test file - Pre-commit:
php artisan test
- Update
-
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() methodtests/Feature/FinalizationTest.php— Existing finalization tests
WHY Each Reference Matters:
Service::finalizationStatus()— The method being modified; understand current checksFinalizationTest— 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.txtCommit: YES
- Message:
refactor(model): update finalizationStatus for agenda model - Files:
app/Models/Service.php,tests/Feature/FinalizationTest.php - Pre-commit:
php artisan test
- Update
-
9. PlaylistExportService — Agenda-Ordered Export
What to do:
- Refactor
PlaylistExportService::generatePlaylist()to:- Load service with
agendaItemsordered bysort_order - Build playlist in agenda order:
- 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
- If item
- If NO agenda item matched announcement position → insert information slides at the beginning (fallback)
- For each agenda item in order:
- Keep the .proplaylist format (ProPlaylistGenerator)
- Handle backward compatibility: if service has no agenda items (old data), fall back to existing block-based export
- Load service with
- 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 refactoredapp/Services/ProExportService.php— generateProFile() for individual song .pro filesapp/Services/ProBundleExportService.php— generateBundle() for slides-to-.pro conversion patternapp/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 embeddingProExportService— Used unchanged for generating individual song .pro filesProBundleExportService— Pattern for converting uploaded slides into .pro presentationAgendaMatcherService— Used to find announcement insertion point
Acceptance Criteria:
php artisan test --filter=PlaylistExportTest→ all tests passphp artisan test --filter=test_export_follows_agenda_order→ passesphp 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.txtCommit: YES
- Message:
feat(export): agenda-ordered playlist export - Files:
app/Services/PlaylistExportService.php,tests/Feature/PlaylistExportTest.php - Pre-commit:
php artisan test
- Refactor
-
10. Redesign ArrangementDialog — Two-Column with Lyric Preview
What to do:
- Create new
resources/js/Components/ArrangementDialog.vue(replacesArrangementConfigurator.vuein 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_translatedshown 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)
- Each element shows:
- Left column (pills): Vertical list of arrangement group names as colored pills
- 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
- POST
- 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.vueyet (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-saveresources/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, orderapp/Models/SongSlide.php— Slide fields: text_content, text_content_translated, orderapp/Models/SongArrangement.php— Arrangement: name, is_defaultapp/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 handlingSongGroup/SongSlide— Data structure for rendering lyric preview and group colorsSongEditModal— 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.pngCommit: YES
- Message:
feat(ui): redesign ArrangementDialog with lyric preview - Files:
resources/js/Components/ArrangementDialog.vue - Pre-commit:
npm run build
- Create new
-
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
SlideUploaderarea 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)
- Generic agenda item display row with:
- 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
- Extends/wraps AgendaItemRow with song-specific features:
- Both components use
data-testidattributes 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 endpointsapp/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 logicSlideUploader/SlideGrid— Compose these into the agenda item row for slide managementServiceSongController— 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.pngCommit: 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
- Create
-
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
InformationBlockcomponent at the top - This is unchanged: global slides with expire dates, upload, delete, download bundle
- Keep the existing
- Agenda section (new): Replace the 3 block accordion (Moderation, Sermon, Songs) with:
- Header: "Ablauf" (Agenda)
- List of all agenda items in order (from
agendaItemsprop) - For each item:
- If item is a header/title type: render as section divider (styled differently, no "+" button)
- If item is a song: render
SongAgendaItemcomponent - If item is flagged as sermon (
is_sermon): renderAgendaItemRowwith sermon-specific upload area and label - If item is flagged as announcement position (
is_announcement_position): renderAgendaItemRowwith 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
- Top section (keep): Announcements/Information block
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 unchangedresources/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 sectionInformationBlock— 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.pngCommit: YES
- Message:
feat(ui): restructure Edit.vue with agenda view - Files:
resources/js/Pages/Services/Edit.vue - Pre-commit:
npm run build
- Major restructure of
-
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
SlideUploadercomponent below the row - SlideUploader configured with:
type='agenda_item',serviceId, and new propagendaItemId - After upload: reload page to show new slide thumbnails
- When "+" button clicked: show/hide a
- Update
SlideController::store():- Accept optional
service_agenda_item_idparameter - When provided: set
service_agenda_item_idon created Slide record - Keep existing type parameter for backward compat, but allow new type value
'agenda_item'
- Accept optional
- Update
SlideUploader.vue:- Add optional
agendaItemIdprop - When set: include
service_agenda_item_idin upload FormData - No expire-date picker for agenda item slides (they're service-specific)
- Add optional
- 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 extendapp/Http/Controllers/SlideController.php— store() method handling uploadsresources/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 componentSlideController::store()— Add service_agenda_item_id to the validation and storage logicSlideGrid— Reuse for inline thumbnail display in agenda rows
Acceptance Criteria:
npm run build→ no errorsphp 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.pngCommit: 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
- Enhance
-
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-testidattributes on all new inputs
- Add new section below existing "ProPresenter Makro-Konfiguration":
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.pngCommit: YES
- Message:
feat(ui): add agenda settings to Settings page - Files:
resources/js/Pages/Settings.vue - Pre-commit:
npm run build
- Update
-
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
- Replace moderation/sermon column with new status indicators:
- 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 countsresources/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 modelIndex.vue— Update display columns
Acceptance Criteria:
php artisan test --filter=ServiceControllerTest→ all tests passnpm 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.pngCommit: 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
- Update
-
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
- Use the updated
- Update
ServiceController::finalize():- Use updated
finalizationStatus()(from Task 8) - Keep confirmation dialog behavior unchanged
- Use updated
- 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() methodsapp/Services/PlaylistExportService.php— Updated service from Task 9
Test References:
tests/Feature/FinalizationTest.php— Existing finalize/download integration teststests/Feature/PlaylistExportTest.php— Export tests
WHY Each Reference Matters:
ServiceController— The methods being wired up to use the new exportPlaylistExportService— The updated service that now supports agenda ordering
Acceptance Criteria:
php artisan test --filter=FinalizationTest→ passesphp artisan test --filter=PlaylistExportTest→ passesphp 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.txtCommit: YES
- Message:
feat(export): wire agenda export into download flow - Files:
app/Http/Controllers/ServiceController.php, test file - Pre-commit:
php artisan test
- Update
-
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 proptests/Feature/FinalizationTest.php— Already updated in Task 8, verify integrationtests/Feature/PlaylistExportTest.php— Already updated in Task 9, verify integrationtests/Feature/ChurchToolsSyncTest.php— Already updated in Task 6, verify integrationtests/Feature/ModerationBlockTest.php— Mark tests as covering deprecated feature or updatetests/Feature/SermonBlockTest.php— Mark tests as covering deprecated feature or updatetests/Feature/SongsBlockTest.php— Update to test SongAgendaItem behaviortests/Feature/SlideControllerTest.php— Add tests for agenda_item slide typetests/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.txtCommit: YES
- Message:
test(php): update existing tests for agenda model - Files:
tests/Feature/*.php(multiple) - Pre-commit:
php artisan test
- Review and update ALL existing PHP tests that reference the old model:
-
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 relevanttests/e2e/service-edit-sermon.spec.ts— sermon is now part of agendatests/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 patternstests/e2e/auth.setup.ts— Auth setup for E2E teststests/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.txtCommit: YES
- Message:
test(e2e): Playwright tests for restructured edit page - Files:
tests/e2e/*.spec.ts - Pre-commit:
npx playwright test
- Update/create E2E test files:
-
19. Remove Deprecated Block Components
What to do:
- Remove Vue components no longer used:
resources/js/Components/Blocks/ModerationBlock.vue— replaced by AgendaItemRowresources/js/Components/Blocks/SermonBlock.vue— replaced by sermon-flagged AgendaItemRowresources/js/Components/Blocks/SongsBlock.vue— replaced by SongAgendaItem
- Update
ArrangementConfigurator.vue:- If still used by
SongEditModal.vue(SongDB page): keep it - If
SongEditModal.vuenow usesArrangementDialog.vue: removeArrangementConfigurator.vue
- If still used by
- Remove deprecated test files:
tests/Feature/ModerationBlockTest.php— if fully replaced by agenda teststests/Feature/SermonBlockTest.php— if fully replacedtests/e2e/service-edit-moderation.spec.tstests/e2e/service-edit-sermon.spec.tstests/e2e/service-edit-songs.spec.ts
- Run full test suite to verify nothing breaks:
php artisan test→ passnpx playwright test→ passnpm 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 reviewresources/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 passnpx 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.txtCommit: YES
- Message:
refactor(cleanup): remove deprecated block components - Files: deleted Vue components, deleted test files
- Pre-commit:
npm run build && php artisan test
- Remove Vue components no longer used:
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 Audit —
oracleRead 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 Review —
unspecified-highRun./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 QA —
unspecified-high(+playwrightskill) 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 Check —
deepFor 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